Browse Source

Merge pull request #3323 from tig/v2_3269_Bounds-ContentArea

Fixes #3169. `Bounds` -> `Viewport`: Content Scrolling in `View`
Tig 1 year ago
parent
commit
56922b4357
100 changed files with 3846 additions and 2560 deletions
  1. 6 4
      README.md
  2. 1 1
      ReactiveExample/ReactiveExample.csproj
  3. 51 21
      Terminal.Gui/Application.cs
  4. 55 38
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  5. 1 1
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  6. 118 48
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  7. 24 24
      Terminal.Gui/Drawing/LineCanvas.cs
  8. 2 2
      Terminal.Gui/Drawing/StraightLine.cs
  9. 1 0
      Terminal.Gui/Drawing/Thickness.cs
  10. 4 4
      Terminal.Gui/Input/Mouse.cs
  11. 2 2
      Terminal.Gui/Terminal.Gui.csproj
  12. 1 1
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs
  13. 1 1
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.PopUp.cs
  14. 11 11
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  15. 45 45
      Terminal.Gui/Text/TextFormatter.cs
  16. 28 52
      Terminal.Gui/View/Adornment/Adornment.cs
  17. 20 14
      Terminal.Gui/View/Adornment/Border.cs
  18. 1 1
      Terminal.Gui/View/Adornment/Padding.cs
  19. 215 251
      Terminal.Gui/View/Layout/ViewLayout.cs
  20. 3 3
      Terminal.Gui/View/View.cs
  21. 6 6
      Terminal.Gui/View/ViewAdornments.cs
  22. 483 0
      Terminal.Gui/View/ViewContent.cs
  23. 181 106
      Terminal.Gui/View/ViewDrawing.cs
  24. 24 9
      Terminal.Gui/View/ViewEventArgs.cs
  25. 28 24
      Terminal.Gui/View/ViewMouse.cs
  26. 12 7
      Terminal.Gui/View/ViewSubViews.cs
  27. 21 21
      Terminal.Gui/View/ViewText.cs
  28. 8 9
      Terminal.Gui/Views/Button.cs
  29. 5 18
      Terminal.Gui/Views/ColorPicker.cs
  30. 18 18
      Terminal.Gui/Views/ComboBox.cs
  31. 5 4
      Terminal.Gui/Views/Dialog.cs
  32. 12 12
      Terminal.Gui/Views/FileDialog.cs
  33. 8 8
      Terminal.Gui/Views/GraphView/Annotations.cs
  34. 19 19
      Terminal.Gui/Views/GraphView/Axis.cs
  35. 9 9
      Terminal.Gui/Views/GraphView/GraphView.cs
  36. 1 1
      Terminal.Gui/Views/GraphView/Series.cs
  37. 10 10
      Terminal.Gui/Views/HexView.cs
  38. 2 2
      Terminal.Gui/Views/Line.cs
  39. 3 3
      Terminal.Gui/Views/LineView.cs
  40. 72 96
      Terminal.Gui/Views/ListView.cs
  41. 3 3
      Terminal.Gui/Views/Menu/ContextMenu.cs
  42. 15 13
      Terminal.Gui/Views/Menu/Menu.cs
  43. 11 15
      Terminal.Gui/Views/Menu/MenuBar.cs
  44. 2 2
      Terminal.Gui/Views/MessageBox.cs
  45. 10 10
      Terminal.Gui/Views/ProgressBar.cs
  46. 2 2
      Terminal.Gui/Views/RadioGroup.cs
  47. 52 42
      Terminal.Gui/Views/ScrollBarView.cs
  48. 49 47
      Terminal.Gui/Views/ScrollView.cs
  49. 22 22
      Terminal.Gui/Views/Slider.cs
  50. 1 1
      Terminal.Gui/Views/StatusBar.cs
  51. 14 14
      Terminal.Gui/Views/TabView.cs
  52. 4 4
      Terminal.Gui/Views/TableView/ListTableSource.cs
  53. 22 22
      Terminal.Gui/Views/TableView/TableView.cs
  54. 8 8
      Terminal.Gui/Views/TextField.cs
  55. 5 5
      Terminal.Gui/Views/TextValidateField.cs
  56. 3 3
      Terminal.Gui/Views/TextView.cs
  57. 46 43
      Terminal.Gui/Views/TileView.cs
  58. 10 9
      Terminal.Gui/Views/Toplevel.cs
  59. 14 14
      Terminal.Gui/Views/TreeView/TreeView.cs
  60. 5 0
      Terminal.sln.DotSettings
  61. 2 2
      UICatalog/Scenarios/ASCIICustomButton.cs
  62. 0 55
      UICatalog/Scenarios/AdornmentExperiments.cs
  63. 139 103
      UICatalog/Scenarios/Adornments.cs
  64. 5 5
      UICatalog/Scenarios/Animation.cs
  65. 6 1
      UICatalog/Scenarios/BackgroundWorkerCollection.cs
  66. 290 167
      UICatalog/Scenarios/Buttons.cs
  67. 248 134
      UICatalog/Scenarios/CharacterMap.cs
  68. 5 2
      UICatalog/Scenarios/Clipping.cs
  69. 2 2
      UICatalog/Scenarios/ColorPicker.cs
  70. 4 4
      UICatalog/Scenarios/ComputedLayout.cs
  71. 402 0
      UICatalog/Scenarios/ContentScrolling.cs
  72. 2 2
      UICatalog/Scenarios/Editor.cs
  73. 2 2
      UICatalog/Scenarios/GraphViewExample.cs
  74. 2 2
      UICatalog/Scenarios/LineDrawing.cs
  75. 2 0
      UICatalog/Scenarios/ListColumns.cs
  76. 1 1
      UICatalog/Scenarios/Notepad.cs
  77. 51 43
      UICatalog/Scenarios/ProgressBarStyles.cs
  78. 72 77
      UICatalog/Scenarios/Scrolling.cs
  79. 4 4
      UICatalog/Scenarios/Snake.cs
  80. 22 18
      UICatalog/Scenarios/TextFormatterDemo.cs
  81. 7 7
      UICatalog/Scenarios/ViewExperiments.cs
  82. 6 6
      UICatalog/Scenarios/VkeyPacketSimulator.cs
  83. 1 0
      UICatalog/UICatalog.cs
  84. 3 3
      UICatalog/UICatalog.csproj
  85. 7 2
      UnitTests/Application/ApplicationTests.cs
  86. 3 3
      UnitTests/Application/MouseTests.cs
  87. 53 88
      UnitTests/Dialogs/MessageBoxTests.cs
  88. 26 26
      UnitTests/Drawing/LineCanvasTests.cs
  89. 2 2
      UnitTests/Drawing/StraightLineTests.cs
  90. 21 0
      UnitTests/TestHelpers.cs
  91. 71 83
      UnitTests/UICatalog/ScenarioTests.cs
  92. 5 5
      UnitTests/UnitTests.csproj
  93. 38 27
      UnitTests/View/Adornment/AdornmentTests.cs
  94. 9 3
      UnitTests/View/Adornment/BorderTests.cs
  95. 28 29
      UnitTests/View/Adornment/ToScreenTests.cs
  96. 316 109
      UnitTests/View/DrawTests.cs
  97. 161 2
      UnitTests/View/FindDeepestViewTests.cs
  98. 0 49
      UnitTests/View/FrameTests.cs
  99. 13 155
      UnitTests/View/Layout/AbsoluteLayoutTests.cs
  100. 0 152
      UnitTests/View/Layout/BoundsTests.cs

+ 6 - 4
README.md

@@ -1,6 +1,6 @@
-![Terminal.Gui](https://socialify.git.ci/gui-cs/Terminal.GuiV2Docs/image?description=1&font=Rokkitt&forks=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2Fgui-cs%2FTerminal.Gui%2Fdevelop%2Fdocfx%2Fimages%2Flogo.png&name=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Auto)
-![.NET Core](https://github.com/gui-cs/Terminal.GuiV2Docs/workflows/.NET%20Core/badge.svg?branch=develop)
-![Code scanning - action](https://github.com/gui-cs/Terminal.GuiV2Docs/workflows/Code%20scanning%20-%20action/badge.svg)
+![Terminal.Gui](https://socialify.git.ci/gui-cs/Terminal.Gui/image?description=1&font=Rokkitt&forks=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2Fgui-cs%2FTerminal.Gui%2Fdevelop%2Fdocfx%2Fimages%2Flogo.png&name=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Auto)
+![.NET Core](https://github.com/gui-cs/Terminal.Gui/workflows/.NET%20Core/badge.svg?branch=develop)
+![Code scanning - action](https://github.com/gui-cs/Terminal.Gui/workflows/Code%20scanning%20-%20action/badge.svg)
 [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui)
 [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui)
 ![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27/raw/code-coverage.json)
 ![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27/raw/code-coverage.json)
 [![Downloads](https://img.shields.io/nuget/dt/Terminal.Gui)](https://www.nuget.org/packages/Terminal.Gui)
 [![Downloads](https://img.shields.io/nuget/dt/Terminal.Gui)](https://www.nuget.org/packages/Terminal.Gui)
@@ -31,8 +31,10 @@ dotnet run
 
 
 ## Documentation 
 ## Documentation 
 
 
-* [Documentation Home](https://gui-cs.github.io/Terminal.GuiV2Docs)
+* [Getting Started](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/getting-started.html)
+* [What's new in v2](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/newinv2.html)
 * [API Documentation](https://gui-cs.github.io/Terminal.GuiV2Docs/api/Terminal.Gui.html)
 * [API Documentation](https://gui-cs.github.io/Terminal.GuiV2Docs/api/Terminal.Gui.html)
+* [Documentation Home](https://gui-cs.github.io/Terminal.GuiV2Docs)
 
 
 ## Showcase & Examples
 ## Showcase & Examples
 
 

+ 1 - 1
ReactiveExample/ReactiveExample.csproj

@@ -12,7 +12,7 @@
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
     <PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
-    <PackageReference Include="ReactiveUI" Version="19.5.41" />
+    <PackageReference Include="ReactiveUI" Version="19.6.1" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.3.1" PrivateAssets="all" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.3.1" PrivateAssets="all" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>

+ 51 - 21
Terminal.Gui/Application.cs

@@ -88,7 +88,7 @@ public static partial class Application
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
 
 
             // Don't dispose the toplevels. It's up to caller dispose them
             // Don't dispose the toplevels. It's up to caller dispose them
-            Debug.Assert (t.WasDisposed);
+            //Debug.Assert (t.WasDisposed);
 #endif
 #endif
         }
         }
 
 
@@ -99,6 +99,7 @@ public static partial class Application
         // Don't dispose the Top. It's up to caller dispose it
         // Don't dispose the Top. It's up to caller dispose it
         if (Top is { })
         if (Top is { })
         {
         {
+
             Debug.Assert (Top.WasDisposed);
             Debug.Assert (Top.WasDisposed);
 
 
             // If End wasn't called _cachedRunStateToplevel may be null
             // If End wasn't called _cachedRunStateToplevel may be null
@@ -132,7 +133,7 @@ public static partial class Application
 
 
         // Don't reset ForceDriver; it needs to be set before Init is called.
         // Don't reset ForceDriver; it needs to be set before Init is called.
         //ForceDriver = string.Empty;
         //ForceDriver = string.Empty;
-        Force16Colors = false;
+        //Force16Colors = false;
         _forceFakeConsole = false;
         _forceFakeConsole = false;
 
 
         // Run State stuff
         // Run State stuff
@@ -525,7 +526,10 @@ public static partial class Application
             MoveCurrent (Current);
             MoveCurrent (Current);
         }
         }
 
 
-        toplevel.SetRelativeLayout (Driver.Bounds);
+        //if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
+        toplevel.SetRelativeLayout (Driver.Screen.Size);
+
+        //}
 
 
         // BUGBUG: This call is likely not needed.
         // BUGBUG: This call is likely not needed.
         toplevel.LayoutSubviews ();
         toplevel.LayoutSubviews ();
@@ -638,7 +642,7 @@ public static partial class Application
     public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null)
     public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null)
     {
     {
         ArgumentNullException.ThrowIfNull (view);
         ArgumentNullException.ThrowIfNull (view);
-        
+
         if (_initialized)
         if (_initialized)
         {
         {
             if (Driver is null)
             if (Driver is null)
@@ -878,7 +882,7 @@ public static partial class Application
         }
         }
         else
         else
         {
         {
-            Driver.UpdateCursor ();
+            //Driver.UpdateCursor ();
         }
         }
 
 
         if (state.Toplevel != Top && !state.Toplevel.Modal && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
         if (state.Toplevel != Top && !state.Toplevel.Modal && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
@@ -1307,7 +1311,7 @@ public static partial class Application
 
 
         foreach (Toplevel t in _topLevels)
         foreach (Toplevel t in _topLevels)
         {
         {
-            t.SetRelativeLayout (Rectangle.Empty with { Size = args.Size });
+            t.SetRelativeLayout (args.Size);
             t.LayoutSubviews ();
             t.LayoutSubviews ();
             t.PositionToplevels ();
             t.PositionToplevels ();
             t.OnSizeChanging (new (args.Size));
             t.OnSizeChanging (new (args.Size));
@@ -1437,15 +1441,15 @@ public static partial class Application
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
     ///         Use this event to receive mouse events in screen coordinates. Use <see cref="MouseEvent"/> to
     ///         Use this event to receive mouse events in screen coordinates. Use <see cref="MouseEvent"/> to
-    ///         receive mouse events relative to a <see cref="View"/>'s bounds.
+    ///         receive mouse events relative to a <see cref="View.Viewport"/>.
     ///     </para>
     ///     </para>
     ///     <para>The <see cref="MouseEvent.View"/> will contain the <see cref="View"/> that contains the mouse coordinates.</para>
     ///     <para>The <see cref="MouseEvent.View"/> will contain the <see cref="View"/> that contains the mouse coordinates.</para>
     /// </remarks>
     /// </remarks>
-    public static event EventHandler<MouseEvent> MouseEvent;
+    public static event EventHandler<MouseEvent>? MouseEvent;
 
 
     /// <summary>Called when a mouse event occurs. Raises the <see cref="MouseEvent"/> event.</summary>
     /// <summary>Called when a mouse event occurs. Raises the <see cref="MouseEvent"/> event.</summary>
     /// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
     /// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
-    /// <param name="a">The mouse event with coordinates relative to the screen.</param>
+    /// <param name="mouseEvent">The mouse event with coordinates relative to the screen.</param>
     internal static void OnMouseEvent (MouseEvent mouseEvent)
     internal static void OnMouseEvent (MouseEvent mouseEvent)
     {
     {
         if (IsMouseDisabled)
         if (IsMouseDisabled)
@@ -1453,7 +1457,6 @@ public static partial class Application
             return;
             return;
         }
         }
 
 
-        // TODO: In PR #3273, FindDeepestView will return adornments. Update logic below to fix adornment mouse handling
         var view = View.FindDeepestView (Current, mouseEvent.X, mouseEvent.Y);
         var view = View.FindDeepestView (Current, mouseEvent.X, mouseEvent.Y);
 
 
         if (view is { })
         if (view is { })
@@ -1472,18 +1475,18 @@ public static partial class Application
         {
         {
             // If the mouse is grabbed, send the event to the view that grabbed it.
             // If the mouse is grabbed, send the event to the view that grabbed it.
             // The coordinates are relative to the Bounds of the view that grabbed the mouse.
             // The coordinates are relative to the Bounds of the view that grabbed the mouse.
-            Point boundsLoc = MouseGrabView.ScreenToBounds (mouseEvent.X, mouseEvent.Y);
+            Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.X, mouseEvent.Y);
 
 
             var viewRelativeMouseEvent = new MouseEvent
             var viewRelativeMouseEvent = new MouseEvent
             {
             {
-                X = boundsLoc.X,
-                Y = boundsLoc.Y,
+                X = frameLoc.X,
+                Y = frameLoc.Y,
                 Flags = mouseEvent.Flags,
                 Flags = mouseEvent.Flags,
                 ScreenPosition = new (mouseEvent.X, mouseEvent.Y),
                 ScreenPosition = new (mouseEvent.X, mouseEvent.Y),
                 View = MouseGrabView
                 View = MouseGrabView
             };
             };
 
 
-            if (MouseGrabView.Bounds.Contains (viewRelativeMouseEvent.X, viewRelativeMouseEvent.Y) is false)
+            if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.X, viewRelativeMouseEvent.Y) is false)
             {
             {
                 // The mouse has moved outside the bounds of the view that grabbed the mouse
                 // The mouse has moved outside the bounds of the view that grabbed the mouse
                 _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent);
                 _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent);
@@ -1546,14 +1549,14 @@ public static partial class Application
                 View = view
                 View = view
             };
             };
         }
         }
-        else if (view.BoundsToScreen (view.Bounds).Contains (mouseEvent.X, mouseEvent.Y))
+        else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.X, mouseEvent.Y))
         {
         {
-            Point boundsPoint = view.ScreenToBounds (mouseEvent.X, mouseEvent.Y);
+            Point viewportLocation = view.ScreenToViewport (mouseEvent.X, mouseEvent.Y);
 
 
             me = new ()
             me = new ()
             {
             {
-                X = boundsPoint.X,
-                Y = boundsPoint.Y,
+                X = viewportLocation.X,
+                Y = viewportLocation.Y,
                 Flags = mouseEvent.Flags,
                 Flags = mouseEvent.Flags,
                 ScreenPosition = new (mouseEvent.X, mouseEvent.Y),
                 ScreenPosition = new (mouseEvent.X, mouseEvent.Y),
                 View = view
                 View = view
@@ -1586,10 +1589,37 @@ public static partial class Application
 
 
         //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
         //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
 
 
-        if (view.NewMouseEvent (me) == false)
+        while (view.NewMouseEvent (me) != true)
         {
         {
-            // Should we bubble up the event, if it is not handled?
-            //return;
+            if (MouseGrabView is { })
+            {
+                break;
+            }
+
+            if (view is Adornment adornmentView)
+            {
+                view = adornmentView.Parent.SuperView;
+            }
+            else
+            {
+                view = view.SuperView;
+            }
+
+            if (view is null)
+            {
+                break;
+            }
+
+            Point boundsPoint = view.ScreenToViewport (mouseEvent.X, mouseEvent.Y);
+
+            me = new ()
+            {
+                X = boundsPoint.X,
+                Y = boundsPoint.Y,
+                Flags = mouseEvent.Flags,
+                ScreenPosition = new (mouseEvent.X, mouseEvent.Y),
+                View = view
+            };
         }
         }
 
 
         BringOverlappedTopToFront ();
         BringOverlappedTopToFront ();

+ 55 - 38
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -18,15 +18,31 @@ public abstract class ConsoleDriver
     // This is in addition to the dirty flag on each cell.
     // This is in addition to the dirty flag on each cell.
     internal bool [] _dirtyLines;
     internal bool [] _dirtyLines;
 
 
-    /// <summary>Gets the dimensions of the terminal.</summary>
-    public Rectangle Bounds => new (0, 0, Cols, Rows);
+    // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application?
+    /// <summary>Gets the location and size of the terminal screen.</summary>
+    public Rectangle Screen => new (0, 0, Cols, Rows);
+
+    private Rectangle _clip;
 
 
     /// <summary>
     /// <summary>
     ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject
     ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject
     ///     to.
     ///     to.
     /// </summary>
     /// </summary>
-    /// <value>The rectangle describing the bounds of <see cref="Clip"/>.</value>
-    public Rectangle Clip { get; set; }
+    /// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
+    public Rectangle Clip
+    {
+        get => _clip;
+        set
+        {
+            if (_clip == value)
+            {
+                return;
+            }
+
+            // Don't ever let Clip be bigger than Screen
+            _clip = Rectangle.Intersect (Screen, value);
+        }
+    }
 
 
     /// <summary>Get the operating system clipboard.</summary>
     /// <summary>Get the operating system clipboard.</summary>
     public IClipboard Clipboard { get; internal set; }
     public IClipboard Clipboard { get; internal set; }
@@ -278,16 +294,6 @@ public abstract class ConsoleDriver
 
 
         for (var i = 0; i < runes.Count; i++)
         for (var i = 0; i < runes.Count; i++)
         {
         {
-            //if (runes [i].IsCombiningMark()) {
-
-            //	// Attempt to normalize
-            //	string combined = runes [i-1] + runes [i].ToString();
-
-            //	// Normalize to Form C (Canonical Composition)
-            //	string normalized = combined.Normalize (NormalizationForm.FormC);
-
-            //	runes [i-]
-            //}
             AddRune (runes [i]);
             AddRune (runes [i]);
         }
         }
     }
     }
@@ -295,31 +301,27 @@ public abstract class ConsoleDriver
     /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>
     /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>
     public void ClearContents ()
     public void ClearContents ()
     {
     {
-        // TODO: This method is really "Clear Contents" now and should not be abstract (or virtual)
         Contents = new Cell [Rows, Cols];
         Contents = new Cell [Rows, Cols];
         //CONCURRENCY: Unsynchronized access to Clip isn't safe.
         //CONCURRENCY: Unsynchronized access to Clip isn't safe.
-        Clip = new (0, 0, Cols, Rows);
+        // TODO: ClearContents should not clear the clip; it should only clear the contents. Move clearing it elsewhere.
+        Clip = Screen;
         _dirtyLines = new bool [Rows];
         _dirtyLines = new bool [Rows];
 
 
         lock (Contents)
         lock (Contents)
         {
         {
-            // Can raise an exception while is still resizing.
-            try
+            for (var row = 0; row < Rows; row++)
             {
             {
-                for (var row = 0; row < Rows; row++)
+                for (var c = 0; c < Cols; c++)
                 {
                 {
-                    for (var c = 0; c < Cols; c++)
+                    Contents [row, c] = new Cell
                     {
                     {
-                        Contents [row, c] = new Cell
-                        {
-                            Rune = (Rune)' ', Attribute = new Attribute (Color.White, Color.Black), IsDirty = true
-                        };
-                        _dirtyLines [row] = true;
-                    }
+                        Rune = (Rune)' ', 
+                        Attribute = new Attribute (Color.White, Color.Black), 
+                        IsDirty = true
+                    };
+                    _dirtyLines [row] = true;
                 }
                 }
             }
             }
-            catch (IndexOutOfRangeException)
-            { }
         }
         }
     }
     }
 
 
@@ -327,18 +329,28 @@ public abstract class ConsoleDriver
     /// <returns><see langword="true"/> upon success</returns>
     /// <returns><see langword="true"/> upon success</returns>
     public abstract bool EnsureCursorVisibility ();
     public abstract bool EnsureCursorVisibility ();
 
 
-    // TODO: Move FillRect to ./Drawing	
-    /// <summary>Fills the specified rectangle with the specified rune.</summary>
-    /// <param name="rect"></param>
-    /// <param name="rune"></param>
+    /// <summary>Fills the specified rectangle with the specified rune, using <see cref="CurrentAttribute"/></summary>
+    /// <remarks>
+    /// The value of <see cref="Clip"/> is honored. Any parts of the rectangle not in the clip will not be drawn.
+    /// </remarks>
+    /// <param name="rect">The Screen-relative rectangle.</param>
+    /// <param name="rune">The Rune used to fill the rectangle</param>
     public void FillRect (Rectangle rect, Rune rune = default)
     public void FillRect (Rectangle rect, Rune rune = default)
     {
     {
-        for (int r = rect.Y; r < rect.Y + rect.Height; r++)
+        rect = Rectangle.Intersect (rect, Clip);
+        lock (Contents)
         {
         {
-            for (int c = rect.X; c < rect.X + rect.Width; c++)
+            for (int r = rect.Y; r < rect.Y + rect.Height; r++)
             {
             {
-                Application.Driver.Move (c, r);
-                Application.Driver.AddRune (rune == default (Rune) ? new Rune (' ') : rune);
+                for (int c = rect.X; c < rect.X + rect.Width; c++)
+                {
+                    Contents [r, c] = new Cell
+                    {
+                        Rune = (rune != default ? rune : (Rune)' '),
+                        Attribute = CurrentAttribute, IsDirty = true
+                    };
+                    _dirtyLines [r] = true;
+                }
             }
             }
         }
         }
     }
     }
@@ -372,10 +384,13 @@ public abstract class ConsoleDriver
     /// <param name="col">The column.</param>
     /// <param name="col">The column.</param>
     /// <param name="row">The row.</param>
     /// <param name="row">The row.</param>
     /// <returns>
     /// <returns>
-    ///     <see langword="false"/> if the coordinate is outside of the screen bounds or outside of <see cref="Clip"/>.
+    ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of <see cref="Clip"/>.
     ///     <see langword="true"/> otherwise.
     ///     <see langword="true"/> otherwise.
     /// </returns>
     /// </returns>
-    public bool IsValidLocation (int col, int row) { return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row); }
+    public bool IsValidLocation (int col, int row)
+    {
+        return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row);
+    }
 
 
     /// <summary>
     /// <summary>
     ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
     ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
@@ -437,6 +452,7 @@ public abstract class ConsoleDriver
     /// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
     /// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
     public virtual bool SupportsTrueColor => true;
     public virtual bool SupportsTrueColor => true;
 
 
+    // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
     /// <summary>
     /// <summary>
     ///     Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors.
     ///     Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors.
     ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
     ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
@@ -466,6 +482,7 @@ public abstract class ConsoleDriver
         get => _currentAttribute;
         get => _currentAttribute;
         set
         set
         {
         {
+            // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. Once Attribute.PlatformColor is removed, this can be fixed.
             if (Application.Driver is { })
             if (Application.Driver is { })
             {
             {
                 _currentAttribute = new Attribute (value.Foreground, value.Background);
                 _currentAttribute = new Attribute (value.Foreground, value.Background);

+ 1 - 1
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1315,7 +1315,7 @@ internal class NetDriver : ConsoleDriver
     {
     {
         EnsureCursorVisibility ();
         EnsureCursorVisibility ();
 
 
-        if (Col >= 0 && Col < Cols && Row >= 0 && Row < Rows)
+        if (Col >= 0 && Col < Cols && Row >= 0 && Row <= Rows)
         {
         {
             SetCursorPosition (Col, Row);
             SetCursorPosition (Col, Row);
             SetWindowPosition (0, Row);
             SetWindowPosition (0, Row);

+ 118 - 48
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -18,6 +18,7 @@
 using System.ComponentModel;
 using System.ComponentModel;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
+using System.Text;
 using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
@@ -110,6 +111,7 @@ internal class WindowsConsole
             }
             }
 
 
             _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
             _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
+            _stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
 
 
             var s = _stringBuilder.ToString ();
             var s = _stringBuilder.ToString ();
 
 
@@ -129,6 +131,11 @@ internal class WindowsConsole
         return result;
         return result;
     }
     }
 
 
+    public bool WriteANSI (string ansi)
+    {
+        return WriteConsole (_screenBuffer, ansi, (uint)ansi.Length, out uint _, null);
+    }
+
     public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window)
     public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window)
     {
     {
         _screenBuffer = CreateConsoleScreenBuffer (
         _screenBuffer = CreateConsoleScreenBuffer (
@@ -167,7 +174,10 @@ internal class WindowsConsole
         }
         }
     }
     }
 
 
-    public bool SetCursorPosition (Coord position) { return SetConsoleCursorPosition (_screenBuffer, position); }
+    public bool SetCursorPosition (Coord position)
+    {
+        return SetConsoleCursorPosition (_screenBuffer, position);
+    }
 
 
     public void SetInitialCursorVisibility ()
     public void SetInitialCursorVisibility ()
     {
     {
@@ -577,14 +587,14 @@ internal class WindowsConsole
         public readonly override string ToString ()
         public readonly override string ToString ()
         {
         {
             return EventType switch
             return EventType switch
-                   {
-                       EventType.Focus => FocusEvent.ToString (),
-                       EventType.Key => KeyEvent.ToString (),
-                       EventType.Menu => MenuEvent.ToString (),
-                       EventType.Mouse => MouseEvent.ToString (),
-                       EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (),
-                       _ => "Unknown event type: " + EventType
-                   };
+            {
+                EventType.Focus => FocusEvent.ToString (),
+                EventType.Key => KeyEvent.ToString (),
+                EventType.Menu => MenuEvent.ToString (),
+                EventType.Mouse => MouseEvent.ToString (),
+                EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (),
+                _ => "Unknown event type: " + EventType
+            };
         }
         }
     }
     }
 
 
@@ -980,7 +990,6 @@ internal class WindowsDriver : ConsoleDriver
 {
 {
     private readonly bool _isWindowsTerminal;
     private readonly bool _isWindowsTerminal;
 
 
-    private CursorVisibility _cachedCursorVisibility;
     private WindowsConsole.SmallRect _damageRegion;
     private WindowsConsole.SmallRect _damageRegion;
     private bool _isButtonDoubleClicked;
     private bool _isButtonDoubleClicked;
     private bool _isButtonPressed;
     private bool _isButtonPressed;
@@ -1023,9 +1032,6 @@ internal class WindowsDriver : ConsoleDriver
 
 
     public WindowsConsole WinConsole { get; private set; }
     public WindowsConsole WinConsole { get; private set; }
 
 
-    /// <inheritdoc/>
-    public override bool EnsureCursorVisibility () { return WinConsole is null || WinConsole.EnsureCursorVisibility (); }
-
     public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
     public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
     {
     {
         if (keyEvent.wVirtualKeyCode != (VK)ConsoleKey.Packet)
         if (keyEvent.wVirtualKeyCode != (VK)ConsoleKey.Packet)
@@ -1072,25 +1078,12 @@ internal class WindowsDriver : ConsoleDriver
         };
         };
     }
     }
 
 
-    /// <inheritdoc/>
-    public override bool GetCursorVisibility (out CursorVisibility visibility)
-    {
-        if (WinConsole is { })
-        {
-            return WinConsole.GetCursorVisibility (out visibility);
-        }
-
-        visibility = _cachedCursorVisibility;
-
-        return true;
-    }
-
     public override bool IsRuneSupported (Rune rune) { return base.IsRuneSupported (rune) && rune.IsBmp; }
     public override bool IsRuneSupported (Rune rune) { return base.IsRuneSupported (rune) && rune.IsBmp; }
 
 
     public override void Refresh ()
     public override void Refresh ()
     {
     {
         UpdateScreen ();
         UpdateScreen ();
-        WinConsole?.SetInitialCursorVisibility ();
+        //WinConsole?.SetInitialCursorVisibility ();
         UpdateCursor ();
         UpdateCursor ();
     }
     }
 
 
@@ -1164,13 +1157,6 @@ internal class WindowsDriver : ConsoleDriver
         }
         }
     }
     }
 
 
-    /// <inheritdoc/>
-    public override bool SetCursorVisibility (CursorVisibility visibility)
-    {
-        _cachedCursorVisibility = visibility;
-
-        return WinConsole is null || WinConsole.SetCursorVisibility (visibility);
-    }
 
 
     #region Not Implemented
     #region Not Implemented
 
 
@@ -1194,9 +1180,13 @@ internal class WindowsDriver : ConsoleDriver
         return new WindowsConsole.ConsoleKeyInfoEx (cki, capslock, numlock, scrolllock);
         return new WindowsConsole.ConsoleKeyInfoEx (cki, capslock, numlock, scrolllock);
     }
     }
 
 
+    #region Cursor Handling
+
+    private CursorVisibility? _cachedCursorVisibility;
+
     public override void UpdateCursor ()
     public override void UpdateCursor ()
     {
     {
-        if (Col < 0 || Row < 0 || Col > Cols || Row > Rows)
+        if (Col < 0 || Row < 0 || Col >= Cols || Row >= Rows)
         {
         {
             GetCursorVisibility (out CursorVisibility cursorVisibility);
             GetCursorVisibility (out CursorVisibility cursorVisibility);
             _cachedCursorVisibility = cursorVisibility;
             _cachedCursorVisibility = cursorVisibility;
@@ -1205,16 +1195,90 @@ internal class WindowsDriver : ConsoleDriver
             return;
             return;
         }
         }
 
 
-        SetCursorVisibility (_cachedCursorVisibility);
-
         var position = new WindowsConsole.Coord
         var position = new WindowsConsole.Coord
         {
         {
             X = (short)Col,
             X = (short)Col,
             Y = (short)Row
             Y = (short)Row
         };
         };
-        WinConsole?.SetCursorPosition (position);
+
+        if (Force16Colors)
+        {
+            WinConsole?.SetCursorPosition (position);
+        }
+        else
+        {
+            var sb = new StringBuilder ();
+            sb.Append (EscSeqUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1));
+            WinConsole?.WriteANSI (sb.ToString ());
+        }
+
+        if (_cachedCursorVisibility is { })
+        {
+            SetCursorVisibility (_cachedCursorVisibility.Value);
+        }
+        //EnsureCursorVisibility ();
     }
     }
 
 
+    /// <inheritdoc/>
+    public override bool GetCursorVisibility (out CursorVisibility visibility)
+    {
+        if (WinConsole is { })
+        {
+            return WinConsole.GetCursorVisibility (out visibility);
+        }
+
+        visibility = _cachedCursorVisibility ?? CursorVisibility.Default;
+
+        return true;
+    }
+
+    /// <inheritdoc/>
+    public override bool SetCursorVisibility (CursorVisibility visibility)
+    {
+        _cachedCursorVisibility = visibility;
+
+        if (Force16Colors)
+        {
+            return WinConsole is null || WinConsole.SetCursorVisibility (visibility);
+        }
+        else
+        {
+            var sb = new StringBuilder ();
+            sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
+            return WinConsole?.WriteANSI (sb.ToString ()) ?? false;
+        }
+    }
+
+    /// <inheritdoc/>
+    public override bool EnsureCursorVisibility ()
+    {
+        if (Force16Colors)
+        {
+            return WinConsole is null || WinConsole.EnsureCursorVisibility ();
+        }
+        else
+        {
+            var sb = new StringBuilder ();
+            sb.Append (_cachedCursorVisibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
+            return WinConsole?.WriteANSI (sb.ToString ()) ?? false;
+        }
+
+        if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
+        {
+            GetCursorVisibility (out CursorVisibility cursorVisibility);
+            _cachedCursorVisibility = cursorVisibility;
+            SetCursorVisibility (CursorVisibility.Invisible);
+
+            return false;
+        }
+
+        SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default);
+
+        return _cachedCursorVisibility == CursorVisibility.Default;
+    }
+
+    #endregion Cursor Handling
+
     public override void UpdateScreen ()
     public override void UpdateScreen ()
     {
     {
         Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (Cols, Rows);
         Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (Cols, Rows);
@@ -1226,8 +1290,8 @@ internal class WindowsDriver : ConsoleDriver
 
 
         var bufferCoords = new WindowsConsole.Coord
         var bufferCoords = new WindowsConsole.Coord
         {
         {
-            X = (short)Clip.Width,
-            Y = (short)Clip.Height
+            X = (short)Cols, //Clip.Width,
+            Y = (short)Rows, //Clip.Height
         };
         };
 
 
         for (var row = 0; row < Rows; row++)
         for (var row = 0; row < Rows; row++)
@@ -1574,13 +1638,13 @@ internal class WindowsDriver : ConsoleDriver
                         // returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important
                         // returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important
                         // for key persistence ("Ctrl++" vs. "Ctrl+=").
                         // for key persistence ("Ctrl++" vs. "Ctrl+=").
                         mappedChar = keyInfo.Key switch
                         mappedChar = keyInfo.Key switch
-                                     {
-                                         ConsoleKey.OemPeriod => '.',
-                                         ConsoleKey.OemComma => ',',
-                                         ConsoleKey.OemPlus => '+',
-                                         ConsoleKey.OemMinus => '-',
-                                         _ => mappedChar
-                                     };
+                        {
+                            ConsoleKey.OemPeriod => '.',
+                            ConsoleKey.OemComma => ',',
+                            ConsoleKey.OemPlus => '+',
+                            ConsoleKey.OemMinus => '-',
+                            _ => mappedChar
+                        };
                     }
                     }
 
 
                     // Return the mappedChar with they modifiers. Because mappedChar is un-shifted, if Shift was down
                     // Return the mappedChar with they modifiers. Because mappedChar is un-shifted, if Shift was down
@@ -1745,7 +1809,10 @@ internal class WindowsDriver : ConsoleDriver
                 Flags = mouseFlag
                 Flags = mouseFlag
             };
             };
 
 
-            if (Application.WantContinuousButtonPressedView is null)
+            // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
+            View view = Application.WantContinuousButtonPressedView;
+
+            if (view is null)
             {
             {
                 break;
                 break;
             }
             }
@@ -1759,6 +1826,7 @@ internal class WindowsDriver : ConsoleDriver
             //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}");
             //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}");
             if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
             if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
             {
             {
+                // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
                 Application.Invoke (() => OnMouseEvent (me));
                 Application.Invoke (() => OnMouseEvent (me));
             }
             }
         }
         }
@@ -1813,6 +1881,7 @@ internal class WindowsDriver : ConsoleDriver
 
 
         if (_isButtonDoubleClicked || _isOneFingerDoubleClicked)
         if (_isButtonDoubleClicked || _isOneFingerDoubleClicked)
         {
         {
+            // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
             Application.MainLoop.AddIdle (
             Application.MainLoop.AddIdle (
                                           () =>
                                           () =>
                                           {
                                           {
@@ -1884,6 +1953,7 @@ internal class WindowsDriver : ConsoleDriver
 
 
             if ((mouseFlag & MouseFlags.ReportMousePosition) == 0)
             if ((mouseFlag & MouseFlags.ReportMousePosition) == 0)
             {
             {
+                // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
                 Application.MainLoop.AddIdle (
                 Application.MainLoop.AddIdle (
                                               () =>
                                               () =>
                                               {
                                               {

+ 24 - 24
Terminal.Gui/Drawing/LineCanvas.cs

@@ -48,7 +48,7 @@ public class LineCanvas : IDisposable
         // TODO: Add other resolvers
         // TODO: Add other resolvers
     };
     };
 
 
-    private Rectangle _cachedBounds;
+    private Rectangle _cachedViewport;
 
 
     /// <summary>Creates a new instance.</summary>
     /// <summary>Creates a new instance.</summary>
     public LineCanvas ()
     public LineCanvas ()
@@ -67,37 +67,37 @@ public class LineCanvas : IDisposable
     ///     Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is
     ///     Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is
     ///     furthest left/top and Size is defined by the line that extends the furthest right/bottom.
     ///     furthest left/top and Size is defined by the line that extends the furthest right/bottom.
     /// </summary>
     /// </summary>
-    public Rectangle Bounds
+    public Rectangle Viewport
     {
     {
         get
         get
         {
         {
-            if (_cachedBounds.IsEmpty)
+            if (_cachedViewport.IsEmpty)
             {
             {
                 if (_lines.Count == 0)
                 if (_lines.Count == 0)
                 {
                 {
-                    return _cachedBounds;
+                    return _cachedViewport;
                 }
                 }
 
 
-                Rectangle bounds = _lines [0].Bounds;
+                Rectangle viewport = _lines [0].Viewport;
 
 
                 for (var i = 1; i < _lines.Count; i++)
                 for (var i = 1; i < _lines.Count; i++)
                 {
                 {
-                    bounds = Rectangle.Union (bounds, _lines [i].Bounds);
+                    viewport = Rectangle.Union (viewport, _lines [i].Viewport);
                 }
                 }
 
 
-                if (bounds is {Width: 0} or {Height: 0})
+                if (viewport is {Width: 0} or {Height: 0})
                 {
                 {
-                    bounds = bounds with
+                    viewport = viewport with
                     {
                     {
-                        Width = Math.Clamp (bounds.Width, 1, short.MaxValue),
-                        Height = Math.Clamp (bounds.Height, 1, short.MaxValue)
+                        Width = Math.Clamp (viewport.Width, 1, short.MaxValue),
+                        Height = Math.Clamp (viewport.Height, 1, short.MaxValue)
                     };
                     };
                 }
                 }
 
 
-                _cachedBounds = bounds;
+                _cachedViewport = viewport;
             }
             }
 
 
-            return _cachedBounds;
+            return _cachedViewport;
         }
         }
     }
     }
 
 
@@ -134,7 +134,7 @@ public class LineCanvas : IDisposable
         Attribute? attribute = default
         Attribute? attribute = default
     )
     )
     {
     {
-        _cachedBounds = Rectangle.Empty;
+        _cachedViewport = Rectangle.Empty;
         _lines.Add (new StraightLine (start, length, orientation, style, attribute));
         _lines.Add (new StraightLine (start, length, orientation, style, attribute));
     }
     }
 
 
@@ -142,14 +142,14 @@ public class LineCanvas : IDisposable
     /// <param name="line"></param>
     /// <param name="line"></param>
     public void AddLine (StraightLine line)
     public void AddLine (StraightLine line)
     {
     {
-        _cachedBounds = Rectangle.Empty;
+        _cachedViewport = Rectangle.Empty;
         _lines.Add (line);
         _lines.Add (line);
     }
     }
 
 
     /// <summary>Clears all lines from the LineCanvas.</summary>
     /// <summary>Clears all lines from the LineCanvas.</summary>
     public void Clear ()
     public void Clear ()
     {
     {
-        _cachedBounds = Rectangle.Empty;
+        _cachedViewport = Rectangle.Empty;
         _lines.Clear ();
         _lines.Clear ();
     }
     }
 
 
@@ -157,7 +157,7 @@ public class LineCanvas : IDisposable
     ///     Clears any cached states from the canvas Call this method if you make changes to lines that have already been
     ///     Clears any cached states from the canvas Call this method if you make changes to lines that have already been
     ///     added.
     ///     added.
     /// </summary>
     /// </summary>
-    public void ClearCache () { _cachedBounds = Rectangle.Empty; }
+    public void ClearCache () { _cachedViewport = Rectangle.Empty; }
 
 
     /// <summary>
     /// <summary>
     ///     Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their
     ///     Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their
@@ -170,9 +170,9 @@ public class LineCanvas : IDisposable
         Dictionary<Point, Cell> map = new ();
         Dictionary<Point, Cell> map = new ();
 
 
         // walk through each pixel of the bitmap
         // walk through each pixel of the bitmap
-        for (int y = Bounds.Y; y < Bounds.Y + Bounds.Height; y++)
+        for (int y = Viewport.Y; y < Viewport.Y + Viewport.Height; y++)
         {
         {
-            for (int x = Bounds.X; x < Bounds.X + Bounds.Width; x++)
+            for (int x = Viewport.X; x < Viewport.X + Viewport.Width; x++)
             {
             {
                 IntersectionDefinition? [] intersects = _lines
                 IntersectionDefinition? [] intersects = _lines
                                                         .Select (l => l.Intersects (x, y))
                                                         .Select (l => l.Intersects (x, y))
@@ -232,7 +232,7 @@ public class LineCanvas : IDisposable
     ///     intersection symbols.
     ///     intersection symbols.
     /// </summary>
     /// </summary>
     /// <returns>A map of all the points within the canvas.</returns>
     /// <returns>A map of all the points within the canvas.</returns>
-    public Dictionary<Point, Rune> GetMap () { return GetMap (Bounds); }
+    public Dictionary<Point, Rune> GetMap () { return GetMap (Viewport); }
 
 
     /// <summary>Merges one line canvas into this one.</summary>
     /// <summary>Merges one line canvas into this one.</summary>
     /// <param name="lineCanvas"></param>
     /// <param name="lineCanvas"></param>
@@ -260,13 +260,13 @@ public class LineCanvas : IDisposable
 
 
     /// <summary>
     /// <summary>
     ///     Returns the contents of the line canvas rendered to a string. The string will include all columns and rows,
     ///     Returns the contents of the line canvas rendered to a string. The string will include all columns and rows,
-    ///     even if <see cref="Bounds"/> has negative coordinates. For example, if the canvas contains a single line that
+    ///     even if <see cref="Viewport"/> has negative coordinates. For example, if the canvas contains a single line that
     ///     starts at (-1,-1) with a length of 2, the rendered string will have a length of 2.
     ///     starts at (-1,-1) with a length of 2, the rendered string will have a length of 2.
     /// </summary>
     /// </summary>
     /// <returns>The canvas rendered to a string.</returns>
     /// <returns>The canvas rendered to a string.</returns>
     public override string ToString ()
     public override string ToString ()
     {
     {
-        if (Bounds.IsEmpty)
+        if (Viewport.IsEmpty)
         {
         {
             return string.Empty;
             return string.Empty;
         }
         }
@@ -275,13 +275,13 @@ public class LineCanvas : IDisposable
         Dictionary<Point, Rune> runeMap = GetMap ();
         Dictionary<Point, Rune> runeMap = GetMap ();
 
 
         // Create the rune canvas
         // Create the rune canvas
-        Rune [,] canvas = new Rune [Bounds.Height, Bounds.Width];
+        Rune [,] canvas = new Rune [Viewport.Height, Viewport.Width];
 
 
         // Copy the rune map to the canvas, adjusting for any negative coordinates
         // Copy the rune map to the canvas, adjusting for any negative coordinates
         foreach (KeyValuePair<Point, Rune> kvp in runeMap)
         foreach (KeyValuePair<Point, Rune> kvp in runeMap)
         {
         {
-            int x = kvp.Key.X - Bounds.X;
-            int y = kvp.Key.Y - Bounds.Y;
+            int x = kvp.Key.X - Viewport.X;
+            int y = kvp.Key.Y - Viewport.Y;
             canvas [y, x] = kvp.Value;
             canvas [y, x] = kvp.Value;
         }
         }
 
 

+ 2 - 2
Terminal.Gui/Drawing/StraightLine.cs

@@ -45,8 +45,8 @@ public class StraightLine
     ///     Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is
     ///     Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is
     ///     furthest left/top and Size is defined by the line that extends the furthest right/bottom.
     ///     furthest left/top and Size is defined by the line that extends the furthest right/bottom.
     /// </summary>
     /// </summary>
-    // PERF: Probably better to store the rectangle rather than make a new one on every single access to Bounds.
-    internal Rectangle Bounds
+    // PERF: Probably better to store the rectangle rather than make a new one on every single access to Viewport.
+    internal Rectangle Viewport
     {
     {
         get
         get
         {
         {

+ 1 - 0
Terminal.Gui/Drawing/Thickness.cs

@@ -161,6 +161,7 @@ public class Thickness : IEquatable<Thickness>
             Application.Driver.FillRect (rect with { Height = Math.Min (rect.Height, Top) }, topChar);
             Application.Driver.FillRect (rect with { Height = Math.Min (rect.Height, Top) }, topChar);
         }
         }
 
 
+        // Draw the Left side
         // Draw the Left side
         // Draw the Left side
         if (Left > 0)
         if (Left > 0)
         {
         {

+ 4 - 4
Terminal.Gui/Input/Mouse.cs

@@ -116,10 +116,10 @@ public class MouseEvent
     /// <summary>The View at the location for the mouse event.</summary>
     /// <summary>The View at the location for the mouse event.</summary>
     public View View { get; set; }
     public View View { get; set; }
 
 
-    /// <summary>The X position of the mouse in <see cref="View.Bounds"/>-relative coordinates.</summary>
+    /// <summary>The X position of the mouse in <see cref="Gui.View.Viewport"/>-relative coordinates.</summary>
     public int X { get; set; }
     public int X { get; set; }
 
 
-    /// <summary>The Y position of the mouse in <see cref="View.Bounds"/>-relative coordinates.</summary>
+    /// <summary>The Y position of the mouse in <see cref="Gui.View.Viewport"/>-relative coordinates.</summary>
     public int Y { get; set; }
     public int Y { get; set; }
 
 
     /// <summary>
     /// <summary>
@@ -133,12 +133,12 @@ public class MouseEvent
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
-    ///         The <see cref="X"/> and <see cref="Y"/> properties are always <see cref="View.Bounds"/>-relative. When the mouse is grabbed by a view,
+    ///         The <see cref="X"/> and <see cref="Y"/> properties are always <see cref="Gui.View.Viewport"/>-relative. When the mouse is grabbed by a view,
     ///         <see cref="ScreenPosition"/> provides the mouse position screen-relative coordinates, enabling the grabbed view to know how much the
     ///         <see cref="ScreenPosition"/> provides the mouse position screen-relative coordinates, enabling the grabbed view to know how much the
     ///         mouse has moved.
     ///         mouse has moved.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
-    ///         Calculated and processed in <see cref="Application.OnMouseEvent(MouseEventEventArgs)"/>.
+    ///         Calculated and processed in <see cref="Application.OnMouseEvent(MouseEvent)"/>.
     ///     </para>
     ///     </para>
     /// </remarks>
     /// </remarks>
     public Point ScreenPosition { get; set; }
     public Point ScreenPosition { get; set; }

+ 2 - 2
Terminal.Gui/Terminal.Gui.csproj

@@ -48,8 +48,8 @@
     <PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
     <PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
     <!-- Enable Nuget Source Link for github -->
     <!-- Enable Nuget Source Link for github -->
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
-    <PackageReference Include="System.IO.Abstractions" Version="20.0.15" />
-    <PackageReference Include="System.Text.Json" Version="8.0.2" />
+    <PackageReference Include="System.IO.Abstractions" Version="21.0.2" />
+    <PackageReference Include="System.Text.Json" Version="8.0.3" />
     <PackageReference Include="Wcwidth" Version="2.0.0" />
     <PackageReference Include="Wcwidth" Version="2.0.0" />
   </ItemGroup>
   </ItemGroup>
   <!-- =================================================================== -->
   <!-- =================================================================== -->

+ 1 - 1
Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs

@@ -117,7 +117,7 @@ public class AppendAutocomplete : AutocompleteBase
         Suggestion suggestion = Suggestions.ElementAt (SelectedIdx);
         Suggestion suggestion = Suggestions.ElementAt (SelectedIdx);
         string fragment = suggestion.Replacement.Substring (suggestion.Remove);
         string fragment = suggestion.Replacement.Substring (suggestion.Remove);
 
 
-        int spaceAvailable = textField.Bounds.Width - textField.Text.GetColumns ();
+        int spaceAvailable = textField.Viewport.Width - textField.Text.GetColumns ();
         int spaceRequired = fragment.EnumerateRunes ().Sum (c => c.GetColumns ());
         int spaceRequired = fragment.EnumerateRunes ().Sum (c => c.GetColumns ());
 
 
         if (spaceAvailable < spaceRequired)
         if (spaceAvailable < spaceRequired)

+ 1 - 1
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.PopUp.cs

@@ -16,7 +16,7 @@ public abstract partial class PopupAutocomplete
 
 
         protected internal override bool OnMouseEvent  (MouseEvent mouseEvent) { return _autoComplete.OnMouseEvent (mouseEvent); }
         protected internal override bool OnMouseEvent  (MouseEvent mouseEvent) { return _autoComplete.OnMouseEvent (mouseEvent); }
 
 
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
         {
             if (!_autoComplete.LastPopupPos.HasValue)
             if (!_autoComplete.LastPopupPos.HasValue)
             {
             {

+ 11 - 11
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs

@@ -271,13 +271,13 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         if (PopupInsideContainer)
         if (PopupInsideContainer)
         {
         {
             // don't overspill vertically
             // don't overspill vertically
-            height = Math.Min (HostControl.Bounds.Height - renderAt.Y, MaxHeight);
+            height = Math.Min (HostControl.Viewport.Height - renderAt.Y, MaxHeight);
 
 
             // There is no space below, lets see if can popup on top
             // There is no space below, lets see if can popup on top
-            if (height < Suggestions.Count && HostControl.Bounds.Height - renderAt.Y >= height)
+            if (height < Suggestions.Count && HostControl.Viewport.Height - renderAt.Y >= height)
             {
             {
                 // Verifies that the upper limit available is greater than the lower limit
                 // Verifies that the upper limit available is greater than the lower limit
-                if (renderAt.Y > HostControl.Bounds.Height - renderAt.Y)
+                if (renderAt.Y > HostControl.Viewport.Height - renderAt.Y)
                 {
                 {
                     renderAt.Y = Math.Max (renderAt.Y - Math.Min (Suggestions.Count + 1, MaxHeight + 1), 0);
                     renderAt.Y = Math.Max (renderAt.Y - Math.Min (Suggestions.Count + 1, MaxHeight + 1), 0);
                     height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), LastPopupPos.Value.Y - 1);
                     height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), LastPopupPos.Value.Y - 1);
@@ -287,13 +287,13 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         else
         else
         {
         {
             // don't overspill vertically
             // don't overspill vertically
-            height = Math.Min (Math.Min (top.Bounds.Height - HostControl.Frame.Bottom, MaxHeight), Suggestions.Count);
+            height = Math.Min (Math.Min (top.Viewport.Height - HostControl.Frame.Bottom, MaxHeight), Suggestions.Count);
 
 
             // There is no space below, lets see if can popup on top
             // There is no space below, lets see if can popup on top
             if (height < Suggestions.Count && HostControl.Frame.Y - top.Frame.Y >= height)
             if (height < Suggestions.Count && HostControl.Frame.Y - top.Frame.Y >= height)
             {
             {
                 // Verifies that the upper limit available is greater than the lower limit
                 // Verifies that the upper limit available is greater than the lower limit
-                if (HostControl.Frame.Y > top.Bounds.Height - HostControl.Frame.Y)
+                if (HostControl.Frame.Y > top.Viewport.Height - HostControl.Frame.Y)
                 {
                 {
                     renderAt.Y = Math.Max (HostControl.Frame.Y - Math.Min (Suggestions.Count, MaxHeight), 0);
                     renderAt.Y = Math.Max (HostControl.Frame.Y - Math.Min (Suggestions.Count, MaxHeight), 0);
                     height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), HostControl.Frame.Y);
                     height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), HostControl.Frame.Y);
@@ -323,34 +323,34 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         if (PopupInsideContainer)
         if (PopupInsideContainer)
         {
         {
             // don't overspill horizontally, let's see if can be displayed on the left
             // don't overspill horizontally, let's see if can be displayed on the left
-            if (width > HostControl.Bounds.Width - renderAt.X)
+            if (width > HostControl.Viewport.Width - renderAt.X)
             {
             {
                 // Verifies that the left limit available is greater than the right limit
                 // Verifies that the left limit available is greater than the right limit
-                if (renderAt.X > HostControl.Bounds.Width - renderAt.X)
+                if (renderAt.X > HostControl.Viewport.Width - renderAt.X)
                 {
                 {
                     renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
                     renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
                     width = Math.Min (width, LastPopupPos.Value.X);
                     width = Math.Min (width, LastPopupPos.Value.X);
                 }
                 }
                 else
                 else
                 {
                 {
-                    width = Math.Min (width, HostControl.Bounds.Width - renderAt.X);
+                    width = Math.Min (width, HostControl.Viewport.Width - renderAt.X);
                 }
                 }
             }
             }
         }
         }
         else
         else
         {
         {
             // don't overspill horizontally, let's see if can be displayed on the left
             // don't overspill horizontally, let's see if can be displayed on the left
-            if (width > top.Bounds.Width - (renderAt.X + HostControl.Frame.X))
+            if (width > top.Viewport.Width - (renderAt.X + HostControl.Frame.X))
             {
             {
                 // Verifies that the left limit available is greater than the right limit
                 // Verifies that the left limit available is greater than the right limit
-                if (renderAt.X + HostControl.Frame.X > top.Bounds.Width - (renderAt.X + HostControl.Frame.X))
+                if (renderAt.X + HostControl.Frame.X > top.Viewport.Width - (renderAt.X + HostControl.Frame.X))
                 {
                 {
                     renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
                     renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
                     width = Math.Min (width, LastPopupPos.Value.X);
                     width = Math.Min (width, LastPopupPos.Value.X);
                 }
                 }
                 else
                 else
                 {
                 {
-                    width = Math.Min (width, top.Bounds.Width - renderAt.X);
+                    width = Math.Min (width, top.Viewport.Width - renderAt.X);
                 }
                 }
             }
             }
         }
         }

+ 45 - 45
Terminal.Gui/Text/TextFormatter.cs

@@ -30,7 +30,7 @@ public class TextFormatter
 
 
     /// <summary>Gets or sets whether the <see cref="Size"/> should be automatically changed to fit the <see cref="Text"/>.</summary>
     /// <summary>Gets or sets whether the <see cref="Size"/> should be automatically changed to fit the <see cref="Text"/>.</summary>
     /// <remarks>
     /// <remarks>
-    ///     <para>Used by <see cref="View.AutoSize"/> to resize the view's <see cref="View.Bounds"/> to fit <see cref="Size"/>.</para>
+    ///     <para>Used by <see cref="View.AutoSize"/> to resize the view's <see cref="View.Viewport"/> to fit <see cref="Size"/>.</para>
     ///     <para>
     ///     <para>
     ///         AutoSize is ignored if <see cref="TextAlignment.Justified"/> and
     ///         AutoSize is ignored if <see cref="TextAlignment.Justified"/> and
     ///         <see cref="VerticalTextAlignment.Justified"/> are used.
     ///         <see cref="VerticalTextAlignment.Justified"/> are used.
@@ -73,8 +73,8 @@ public class TextFormatter
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Determines if the bounds width will be used or only the text width will be used,
-    ///     If <see langword="true"/> all the bounds area will be filled with whitespaces and the same background color
+    ///     Determines if the viewport width will be used or only the text width will be used,
+    ///     If <see langword="true"/> all the viewport area will be filled with whitespaces and the same background color
     ///     showing a perfect rectangle.
     ///     showing a perfect rectangle.
     /// </summary>
     /// </summary>
     public bool FillRemaining { get; set; }
     public bool FillRemaining { get; set; }
@@ -202,17 +202,17 @@ public class TextFormatter
     ///     Causes the text to be formatted (references <see cref="GetLines"/>). Sets <see cref="NeedsFormat"/> to
     ///     Causes the text to be formatted (references <see cref="GetLines"/>). Sets <see cref="NeedsFormat"/> to
     ///     <c>false</c>.
     ///     <c>false</c>.
     /// </remarks>
     /// </remarks>
-    /// <param name="bounds">Specifies the screen-relative location and maximum size for drawing the text.</param>
+    /// <param name="screen">Specifies the screen-relative location and maximum size for drawing the text.</param>
     /// <param name="normalColor">The color to use for all text except the hotkey</param>
     /// <param name="normalColor">The color to use for all text except the hotkey</param>
     /// <param name="hotColor">The color to use to draw the hotkey</param>
     /// <param name="hotColor">The color to use to draw the hotkey</param>
-    /// <param name="containerBounds">Specifies the screen-relative location and maximum container size.</param>
+    /// <param name="maximum">Specifies the screen-relative location and maximum container size.</param>
     /// <param name="driver">The console driver currently used by the application.</param>
     /// <param name="driver">The console driver currently used by the application.</param>
     /// <exception cref="ArgumentOutOfRangeException"></exception>
     /// <exception cref="ArgumentOutOfRangeException"></exception>
     public void Draw (
     public void Draw (
-        Rectangle bounds,
+        Rectangle screen,
         Attribute normalColor,
         Attribute normalColor,
         Attribute hotColor,
         Attribute hotColor,
-        Rectangle containerBounds = default,
+        Rectangle maximum = default,
         ConsoleDriver driver = null
         ConsoleDriver driver = null
     )
     )
     {
     {
@@ -240,46 +240,46 @@ public class TextFormatter
         }
         }
 
 
         bool isVertical = IsVerticalDirection (Direction);
         bool isVertical = IsVerticalDirection (Direction);
-        Rectangle maxBounds = bounds;
+        Rectangle maxScreen = screen;
 
 
         if (driver is { })
         if (driver is { })
         {
         {
             // INTENT: What, exactly, is the intent of this?
             // INTENT: What, exactly, is the intent of this?
-            maxBounds = containerBounds == default (Rectangle)
-                            ? bounds
+            maxScreen = maximum == default (Rectangle)
+                            ? screen
                             : new (
                             : new (
-                                   Math.Max (containerBounds.X, bounds.X),
-                                   Math.Max (containerBounds.Y, bounds.Y),
+                                   Math.Max (maximum.X, screen.X),
+                                   Math.Max (maximum.Y, screen.Y),
                                    Math.Max (
                                    Math.Max (
-                                             Math.Min (containerBounds.Width, containerBounds.Right - bounds.Left),
+                                             Math.Min (maximum.Width, maximum.Right - screen.Left),
                                              0
                                              0
                                             ),
                                             ),
                                    Math.Max (
                                    Math.Max (
                                              Math.Min (
                                              Math.Min (
-                                                       containerBounds.Height,
-                                                       containerBounds.Bottom - bounds.Top
+                                                       maximum.Height,
+                                                       maximum.Bottom - screen.Top
                                                       ),
                                                       ),
                                              0
                                              0
                                             )
                                             )
                                   );
                                   );
         }
         }
 
 
-        if (maxBounds.Width == 0 || maxBounds.Height == 0)
+        if (maxScreen.Width == 0 || maxScreen.Height == 0)
         {
         {
             return;
             return;
         }
         }
 
 
-        int lineOffset = !isVertical && bounds.Y < 0 ? Math.Abs (bounds.Y) : 0;
+        int lineOffset = !isVertical && screen.Y < 0 ? Math.Abs (screen.Y) : 0;
 
 
         for (int line = lineOffset; line < linesFormatted.Count; line++)
         for (int line = lineOffset; line < linesFormatted.Count; line++)
         {
         {
-            if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height))
+            if ((isVertical && line > screen.Width) || (!isVertical && line > screen.Height))
             {
             {
                 continue;
                 continue;
             }
             }
 
 
-            if ((isVertical && line >= maxBounds.Left + maxBounds.Width)
-                || (!isVertical && line >= maxBounds.Top + maxBounds.Height + lineOffset))
+            if ((isVertical && line >= maxScreen.Left + maxScreen.Width)
+                || (!isVertical && line >= maxScreen.Top + maxScreen.Height + lineOffset))
             {
             {
                 break;
                 break;
             }
             }
@@ -305,14 +305,14 @@ public class TextFormatter
                 if (isVertical)
                 if (isVertical)
                 {
                 {
                     int runesWidth = GetWidestLineLength (linesFormatted, line, TabWidth);
                     int runesWidth = GetWidestLineLength (linesFormatted, line, TabWidth);
-                    x = bounds.Right - runesWidth;
-                    CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
+                    x = screen.Right - runesWidth;
+                    CursorPosition = screen.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
                 }
                 }
                 else
                 else
                 {
                 {
                     int runesWidth = StringExtensions.ToString (runes).GetColumns ();
                     int runesWidth = StringExtensions.ToString (runes).GetColumns ();
-                    x = bounds.Right - runesWidth;
-                    CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
+                    x = screen.Right - runesWidth;
+                    CursorPosition = screen.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
                 }
                 }
             }
             }
             else if (Alignment is TextAlignment.Left or TextAlignment.Justified)
             else if (Alignment is TextAlignment.Left or TextAlignment.Justified)
@@ -322,11 +322,11 @@ public class TextFormatter
                     int runesWidth = line > 0
                     int runesWidth = line > 0
                                          ? GetWidestLineLength (linesFormatted, 0, line, TabWidth)
                                          ? GetWidestLineLength (linesFormatted, 0, line, TabWidth)
                                          : 0;
                                          : 0;
-                    x = bounds.Left + runesWidth;
+                    x = screen.Left + runesWidth;
                 }
                 }
                 else
                 else
                 {
                 {
-                    x = bounds.Left;
+                    x = screen.Left;
                 }
                 }
 
 
                 CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0;
                 CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0;
@@ -336,16 +336,16 @@ public class TextFormatter
                 if (isVertical)
                 if (isVertical)
                 {
                 {
                     int runesWidth = GetWidestLineLength (linesFormatted, line, TabWidth);
                     int runesWidth = GetWidestLineLength (linesFormatted, line, TabWidth);
-                    x = bounds.Left + line + (bounds.Width - runesWidth) / 2;
+                    x = screen.Left + line + (screen.Width - runesWidth) / 2;
 
 
-                    CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
+                    CursorPosition = (screen.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
                 }
                 }
                 else
                 else
                 {
                 {
                     int runesWidth = StringExtensions.ToString (runes).GetColumns ();
                     int runesWidth = StringExtensions.ToString (runes).GetColumns ();
-                    x = bounds.Left + (bounds.Width - runesWidth) / 2;
+                    x = screen.Left + (screen.Width - runesWidth) / 2;
 
 
-                    CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
+                    CursorPosition = (screen.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
                 }
                 }
             }
             }
             else
             else
@@ -358,35 +358,35 @@ public class TextFormatter
             {
             {
                 if (isVertical)
                 if (isVertical)
                 {
                 {
-                    y = bounds.Bottom - runes.Length;
+                    y = screen.Bottom - runes.Length;
                 }
                 }
                 else
                 else
                 {
                 {
-                    y = bounds.Bottom - linesFormatted.Count + line;
+                    y = screen.Bottom - linesFormatted.Count + line;
                 }
                 }
             }
             }
             else if (VerticalAlignment is VerticalTextAlignment.Top or VerticalTextAlignment.Justified)
             else if (VerticalAlignment is VerticalTextAlignment.Top or VerticalTextAlignment.Justified)
             {
             {
                 if (isVertical)
                 if (isVertical)
                 {
                 {
-                    y = bounds.Top;
+                    y = screen.Top;
                 }
                 }
                 else
                 else
                 {
                 {
-                    y = bounds.Top + line;
+                    y = screen.Top + line;
                 }
                 }
             }
             }
             else if (VerticalAlignment == VerticalTextAlignment.Middle)
             else if (VerticalAlignment == VerticalTextAlignment.Middle)
             {
             {
                 if (isVertical)
                 if (isVertical)
                 {
                 {
-                    int s = (bounds.Height - runes.Length) / 2;
-                    y = bounds.Top + s;
+                    int s = (screen.Height - runes.Length) / 2;
+                    y = screen.Top + s;
                 }
                 }
                 else
                 else
                 {
                 {
-                    int s = (bounds.Height - linesFormatted.Count) / 2;
-                    y = bounds.Top + line + s;
+                    int s = (screen.Height - linesFormatted.Count) / 2;
+                    y = screen.Top + line + s;
                 }
                 }
             }
             }
             else
             else
@@ -394,9 +394,9 @@ public class TextFormatter
                 throw new ArgumentOutOfRangeException ($"{nameof (VerticalAlignment)}");
                 throw new ArgumentOutOfRangeException ($"{nameof (VerticalAlignment)}");
             }
             }
 
 
-            int colOffset = bounds.X < 0 ? Math.Abs (bounds.X) : 0;
-            int start = isVertical ? bounds.Top : bounds.Left;
-            int size = isVertical ? bounds.Height : bounds.Width;
+            int colOffset = screen.X < 0 ? Math.Abs (screen.X) : 0;
+            int start = isVertical ? screen.Top : screen.Left;
+            int size = isVertical ? screen.Height : screen.Width;
             int current = start + colOffset;
             int current = start + colOffset;
             List<Point?> lastZeroWidthPos = null;
             List<Point?> lastZeroWidthPos = null;
             Rune rune = default;
             Rune rune = default;
@@ -422,15 +422,15 @@ public class TextFormatter
                         break;
                         break;
                     }
                     }
 
 
-                    if ((!isVertical && current - start > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
-                        || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y))
+                    if ((!isVertical && current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset)
+                        || (isVertical && idx > maxScreen.Top + maxScreen.Height - screen.Y))
                     {
                     {
                         break;
                         break;
                     }
                     }
                 }
                 }
 
 
-                //if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
-                //	|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y))
+                //if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - viewport.X + colOffset)
+                //	|| (isVertical && idx > maxBounds.Top + maxBounds.Height - viewport.Y))
 
 
                 //	break;
                 //	break;
 
 

+ 28 - 52
Terminal.Gui/View/Adornment/Adornment.cs

@@ -1,14 +1,14 @@
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
 /// <summary>
 /// <summary>
-///     Adornments are a special form of <see cref="View"/> that appear outside the <see cref="View.Bounds"/>:
+///     Adornments are a special form of <see cref="View"/> that appear outside the <see cref="View.Viewport"/>:
 ///     <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>. They are defined using the
 ///     <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>. They are defined using the
 ///     <see cref="Thickness"/> class, which specifies the thickness of the sides of a rectangle.
 ///     <see cref="Thickness"/> class, which specifies the thickness of the sides of a rectangle.
 /// </summary>
 /// </summary>
 /// <remarsk>
 /// <remarsk>
 ///     <para>
 ///     <para>
 ///         Each of <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/> has slightly different
 ///         Each of <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/> has slightly different
-///         behavior relative to <see cref="ColorScheme"/>, <see cref="View.SetFocus"/>, keyboard input, and
+///         behavior relative to <see cref="ColorScheme"/>, <see cref="View.SetFocus()"/>, keyboard input, and
 ///         mouse input. Each can be customized by manipulating their Subviews.
 ///         mouse input. Each can be customized by manipulating their Subviews.
 ///     </para>
 ///     </para>
 /// </remarsk>
 /// </remarsk>
@@ -89,7 +89,7 @@ public class Adornment : View
     public override View SuperView
     public override View SuperView
     {
     {
         get => null;
         get => null;
-        set => throw new NotImplementedException ();
+        set => throw new InvalidOperationException (@"Adornments can not be Subviews or have SuperViews. Use Parent instead.");
     }
     }
 
 
     //internal override Adornment CreateAdornment (Type adornmentType)
     //internal override Adornment CreateAdornment (Type adornmentType)
@@ -107,10 +107,14 @@ public class Adornment : View
     ///     Gets the rectangle that describes the area of the Adornment. The Location is always (0,0).
     ///     Gets the rectangle that describes the area of the Adornment. The Location is always (0,0).
     ///     The size is the size of the <see cref="View.Frame"/>.
     ///     The size is the size of the <see cref="View.Frame"/>.
     /// </summary>
     /// </summary>
-    public override Rectangle Bounds
+    /// <remarks>
+    ///     The Viewport of an Adornment cannot be modified. Attempting to set this property will throw an
+    ///     <see cref="InvalidOperationException"/>.
+    /// </remarks>
+    public override Rectangle Viewport
     {
     {
         get => Frame with { Location = Point.Empty };
         get => Frame with { Location = Point.Empty };
-        set => throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
+        set => throw new InvalidOperationException (@"The Viewport of an Adornment cannot be modified.");
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
@@ -123,10 +127,11 @@ public class Adornment : View
 
 
         // 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 an Adornment, we need get the parent's Frame
         // To get the screen-relative coordinates of an Adornment, we need get the parent's Frame
-        // in screen coords, and add our Frame location to it.
-        Rectangle parent = Parent.FrameToScreen ();
+        // in screen coords, ...
+        Rectangle parentScreen = Parent.FrameToScreen ();
 
 
-        return new (new (parent.X + Frame.X, parent.Y + Frame.Y), Frame.Size);
+        // ...and add our Frame location to it.
+        return new (new (parentScreen.X + Frame.X, parentScreen.Y + Frame.Y), Frame.Size);
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
@@ -137,19 +142,21 @@ public class Adornment : View
     public override bool OnDrawAdornments () { return false; }
     public override bool OnDrawAdornments () { return false; }
 
 
     /// <summary>Redraws the Adornments that comprise the <see cref="Adornment"/>.</summary>
     /// <summary>Redraws the Adornments that comprise the <see cref="Adornment"/>.</summary>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         if (Thickness == Thickness.Empty)
         if (Thickness == Thickness.Empty)
         {
         {
             return;
             return;
         }
         }
 
 
-        Rectangle screenBounds = BoundsToScreen (contentArea);
+        Rectangle prevClip = SetClip ();
+
+        Rectangle screen = ViewportToScreen (viewport);
         Attribute normalAttr = GetNormalColor ();
         Attribute normalAttr = GetNormalColor ();
         Driver.SetAttribute (normalAttr);
         Driver.SetAttribute (normalAttr);
 
 
         // This just draws/clears the thickness, not the insides.
         // This just draws/clears the thickness, not the insides.
-        Thickness.Draw (screenBounds, ToString ());
+        Thickness.Draw (screen, ToString ());
 
 
         if (!string.IsNullOrEmpty (TextFormatter.Text))
         if (!string.IsNullOrEmpty (TextFormatter.Text))
         {
         {
@@ -160,11 +167,16 @@ public class Adornment : View
             }
             }
         }
         }
 
 
-        TextFormatter?.Draw (screenBounds, normalAttr, normalAttr, Rectangle.Empty);
+        TextFormatter?.Draw (screen, normalAttr, normalAttr, Rectangle.Empty);
 
 
         if (Subviews.Count > 0)
         if (Subviews.Count > 0)
         {
         {
-            base.OnDrawContent (contentArea);
+            base.OnDrawContent (viewport);
+        }
+
+        if (Driver is { })
+        {
+           Driver.Clip = prevClip;
         }
         }
 
 
         ClearLayoutNeeded ();
         ClearLayoutNeeded ();
@@ -181,8 +193,8 @@ public class Adornment : View
     /// </summary>
     /// </summary>
     public override bool SuperViewRendersLineCanvas
     public override bool SuperViewRendersLineCanvas
     {
     {
-        get => false; // throw new NotImplementedException ();
-        set => throw new NotImplementedException ();
+        get => false; 
+        set => throw new InvalidOperationException (@"Adornment can only render to their Parent or Parent's Superview.");
     }
     }
 
 
     #endregion View Overrides
     #endregion View Overrides
@@ -205,6 +217,7 @@ public class Adornment : View
         {
         {
             return false;
             return false;
         }
         }
+
         Rectangle frame = Frame;
         Rectangle frame = Frame;
         frame.Offset (Parent.Frame.Location);
         frame.Offset (Parent.Frame.Location);
 
 
@@ -227,43 +240,6 @@ public class Adornment : View
         return base.OnMouseEnter (mouseEvent);
         return base.OnMouseEnter (mouseEvent);
     }
     }
 
 
-    /// <summary>Called when a mouse event occurs within the Adornment.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         The coordinates are relative to <see cref="View.Bounds"/>.
-    ///     </para>
-    ///     <para>
-    ///         A mouse click on the Adornment will cause the Parent to focus.
-    ///     </para>
-    ///     <para>
-    ///         A mouse drag on the Adornment will cause the Parent to move.
-    ///     </para>
-    /// </remarks>
-    /// <param name="mouseEvent"></param>
-    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
-    protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
-    {
-        if (Parent is null)
-        {
-            return false;
-        }
-
-        var args = new MouseEventEventArgs (mouseEvent);
-
-        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
-        {
-            if (Parent.CanFocus && !Parent.HasFocus)
-            {
-                Parent.SetFocus ();
-                Parent.SetNeedsDisplay ();
-            }
-
-            return OnMouseClick (args);
-        }
-
-        return false;
-    }
-
     /// <inheritdoc/>
     /// <inheritdoc/>
     protected internal override bool OnMouseLeave (MouseEvent mouseEvent)
     protected internal override bool OnMouseLeave (MouseEvent mouseEvent)
     {
     {

+ 20 - 14
Terminal.Gui/View/Adornment/Border.cs

@@ -149,14 +149,14 @@ public class Border : Adornment
         }
         }
     }
     }
 
 
-    Rectangle GetBorderBounds (Rectangle screenBounds)
+    Rectangle GetBorderRectangle (Rectangle screenRect)
     {
     {
         return new (
         return new (
-                                      screenBounds.X + Math.Max (0, Thickness.Left - 1),
-                                      screenBounds.Y + Math.Max (0, Thickness.Top - 1),
+                                      screenRect.X + Math.Max (0, Thickness.Left - 1),
+                                      screenRect.Y + Math.Max (0, Thickness.Top - 1),
                                       Math.Max (
                                       Math.Max (
                                                 0,
                                                 0,
-                                                screenBounds.Width
+                                                screenRect.Width
                                                 - Math.Max (
                                                 - Math.Max (
                                                             0,
                                                             0,
                                                             Math.Max (0, Thickness.Left - 1)
                                                             Math.Max (0, Thickness.Left - 1)
@@ -165,7 +165,7 @@ public class Border : Adornment
                                                ),
                                                ),
                                       Math.Max (
                                       Math.Max (
                                                 0,
                                                 0,
-                                                screenBounds.Height
+                                                screenRect.Height
                                                 - Math.Max (
                                                 - Math.Max (
                                                             0,
                                                             0,
                                                             Math.Max (0, Thickness.Top - 1)
                                                             Math.Max (0, Thickness.Top - 1)
@@ -204,6 +204,7 @@ public class Border : Adornment
     {
     {
         if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable))
         if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable))
         {
         {
+            e.Cancel = true;
             return;
             return;
         }
         }
 
 
@@ -300,7 +301,7 @@ public class Border : Adornment
 
 
                 _dragPosition = new Point (mouseEvent.X, mouseEvent.Y);
                 _dragPosition = new Point (mouseEvent.X, mouseEvent.Y);
 
 
-                Point parentLoc = Parent.SuperView?.ScreenToBounds (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y) ?? mouseEvent.ScreenPosition;
+                Point parentLoc = Parent.SuperView?.ScreenToViewport (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y) ?? mouseEvent.ScreenPosition;
 
 
                 GetLocationEnsuringFullVisibility (
                 GetLocationEnsuringFullVisibility (
                                      Parent,
                                      Parent,
@@ -360,9 +361,9 @@ public class Border : Adornment
 #endregion Mouse Support
 #endregion Mouse Support
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
 
         if (Thickness == Thickness.Empty)
         if (Thickness == Thickness.Empty)
         {
         {
@@ -370,7 +371,7 @@ public class Border : Adornment
         }
         }
 
 
         //Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal);
         //Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal);
-        Rectangle screenBounds = BoundsToScreen (contentArea);
+        Rectangle screenBounds = ViewportToScreen (viewport);
 
 
         //OnDrawSubviews (bounds); 
         //OnDrawSubviews (bounds); 
 
 
@@ -381,7 +382,7 @@ public class Border : Adornment
         // ...thickness extends outward (border/title is always as far in as possible)
         // ...thickness extends outward (border/title is always as far in as possible)
         // PERF: How about a call to Rectangle.Offset?
         // PERF: How about a call to Rectangle.Offset?
 
 
-        var borderBounds = GetBorderBounds (screenBounds);
+        var borderBounds = GetBorderRectangle (screenBounds);
         int topTitleLineY = borderBounds.Y;
         int topTitleLineY = borderBounds.Y;
         int titleY = borderBounds.Y;
         int titleY = borderBounds.Y;
         var titleBarsLength = 0; // the little vertical thingies
         var titleBarsLength = 0; // the little vertical thingies
@@ -432,10 +433,17 @@ public class Border : Adornment
 
 
         if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title))
         if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title))
         {
         {
+            var focus = Parent.GetNormalColor();
+            if (Parent.SuperView is { } && Parent.SuperView?.Subviews!.Count (s => s.CanFocus) > 1)
+            {
+                // Only use focus color if there are multiple focusable views
+                focus = Parent.GetFocusColor() ;
+            }
+
             Parent.TitleTextFormatter.Draw (
             Parent.TitleTextFormatter.Draw (
                                             new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
                                             new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
-                                            Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
-                                            Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetHotNormalColor ());
+                                            Parent.HasFocus ? focus : Parent.GetNormalColor (),
+                                            Parent.HasFocus ? focus : Parent.GetHotNormalColor ());
         }
         }
 
 
         if (canDrawBorder && LineStyle != LineStyle.None)
         if (canDrawBorder && LineStyle != LineStyle.None)
@@ -641,7 +649,5 @@ public class Border : Adornment
                 }
                 }
             }
             }
         }
         }
-
-        //base.OnDrawContent (contentArea);
     }
     }
 }
 }

+ 1 - 1
Terminal.Gui/View/Adornment/Padding.cs

@@ -42,7 +42,7 @@ public class Padding : Adornment
     /// <summary>Called when a mouse event occurs within the Padding.</summary>
     /// <summary>Called when a mouse event occurs within the Padding.</summary>
     /// <remarks>
     /// <remarks>
     /// <para>
     /// <para>
-    /// The coordinates are relative to <see cref="View.Bounds"/>.
+    /// The coordinates are relative to <see cref="View.Viewport"/>.
     /// </para>
     /// </para>
     /// <para>
     /// <para>
     /// A mouse click on the Padding will cause the Parent to focus.
     /// A mouse click on the Padding will cause the Parent to focus.

+ 215 - 251
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -1,4 +1,5 @@
 using System.Diagnostics;
 using System.Diagnostics;
+using System.IO.Compression;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
@@ -42,18 +43,18 @@ public partial class View
     /// <summary>Gets or sets the absolute location and dimension of the view.</summary>
     /// <summary>Gets or sets the absolute location and dimension of the view.</summary>
     /// <value>
     /// <value>
     ///     The rectangle describing absolute location and dimension of the view, in coordinates relative to the
     ///     The rectangle describing absolute location and dimension of the view, in coordinates relative to the
-    ///     <see cref="SuperView"/>'s <see cref="Bounds"/>.
+    ///     <see cref="SuperView"/>'s Content, which is bound by <see cref="ContentSize"/>.
     /// </value>
     /// </value>
     /// <remarks>
     /// <remarks>
-    ///     <para>Frame is relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.</para>
+    ///     <para>Frame is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="ContentSize"/>.</para>
     ///     <para>
     ///     <para>
     ///         Setting Frame will set <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> to the
     ///         Setting Frame will set <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> to the
     ///         values of the corresponding properties of the <paramref name="value"/> parameter.
     ///         values of the corresponding properties of the <paramref name="value"/> parameter.
+    ///         This causes <see cref="LayoutStyle"/> to be <see cref="LayoutStyle.Absolute"/>.
     ///     </para>
     ///     </para>
-    ///     <para>This causes <see cref="LayoutStyle"/> to be <see cref="LayoutStyle.Absolute"/>.</para>
     ///     <para>
     ///     <para>
     ///         Altering the Frame will eventually (when the view hierarchy is next laid out via  see
     ///         Altering the Frame will eventually (when the view hierarchy is next laid out via  see
-    ///         cref="LayoutSubviews"/>) cause <see cref="LayoutSubview(View, Rectangle)"/> and
+    ///         cref="LayoutSubviews"/>) cause <see cref="LayoutSubview(View, Size)"/> and
     ///         <see cref="OnDrawContent(Rectangle)"/>
     ///         <see cref="OnDrawContent(Rectangle)"/>
     ///         methods to be called.
     ///         methods to be called.
     ///     </para>
     ///     </para>
@@ -63,7 +64,12 @@ public partial class View
         get => _frame;
         get => _frame;
         set
         set
         {
         {
-            _frame = value with { Width = Math.Max (value.Width, 0), Height = Math.Max (value.Height, 0) };
+            if (_frame == value)
+            {
+                return;
+            }
+
+            SetFrame (value with { Width = Math.Max (value.Width, 0), Height = Math.Max (value.Height, 0) });
 
 
             // If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so
             // If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so
             // set all Pos/Dim to Absolute values.
             // set all Pos/Dim to Absolute values.
@@ -73,62 +79,83 @@ public partial class View
             _height = _frame.Height;
             _height = _frame.Height;
 
 
             // TODO: Figure out if the below can be optimized.
             // TODO: Figure out if the below can be optimized.
-            if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/)
+            if (IsInitialized)
             {
             {
-                LayoutAdornments ();
-                SetTextFormatterSize ();
-                SetNeedsLayout ();
-                SetNeedsDisplay ();
+                OnResizeNeeded ();
             }
             }
         }
         }
     }
     }
 
 
+    private void SetFrame (Rectangle frame)
+    {
+        Rectangle oldViewport  = Rectangle.Empty;
+        if (IsInitialized)
+        {
+            oldViewport = Viewport;
+        }
+        // This is the only place where _frame should be set directly. Use Frame = or SetFrame instead.
+        _frame = frame;
+
+        OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport));
+    }
+
     /// <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>
     public virtual Rectangle FrameToScreen ()
     public virtual Rectangle FrameToScreen ()
     {
     {
-        Rectangle ret = Frame;
-        View super = SuperView;
+        Rectangle screen = Frame;
+        View current = SuperView;
 
 
-        while (super is { })
+        while (current is { })
         {
         {
-            if (super is Adornment adornment)
+            if (current is Adornment adornment)
             {
             {
                 // Adornments don't have SuperViews; use Adornment.FrameToScreen override
                 // Adornments don't have SuperViews; use Adornment.FrameToScreen override
-                ret = adornment.FrameToScreen ();
-                ret.Offset (Frame.X, Frame.Y);
+                // which will give us the screen coordinates of the parent
+
+                var parentScreen = adornment.FrameToScreen ();
+
+                // Now add our Frame location
+                parentScreen.Offset (screen.X, screen.Y);
 
 
-                return ret;
+                return parentScreen;
             }
             }
 
 
-            Point boundsOffset = super.GetBoundsOffset ();
-            boundsOffset.Offset(super.Frame.X, super.Frame.Y);
-            ret.X += boundsOffset.X;
-            ret.Y += boundsOffset.Y;
-            super = super.SuperView;
+            Point viewportOffset = current.GetViewportOffsetFromFrame ();
+            viewportOffset.Offset (current.Frame.X - current.Viewport.X, current.Frame.Y - current.Viewport.Y);
+            screen.X += viewportOffset.X;
+            screen.Y += viewportOffset.Y;
+            current = current.SuperView;
         }
         }
 
 
-        return ret;
+        return screen;
     }
     }
 
 
     /// <summary>
     /// <summary>
     ///     Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means relative to the
     ///     Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means relative to the
-    ///     View's <see cref="SuperView"/>'s <see cref="Bounds"/>.
+    ///     View's <see cref="SuperView"/>'s <see cref="Viewport"/>.
     /// </summary>
     /// </summary>
-    /// <returns>The coordinate relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.</returns>
+    /// <returns>The coordinate relative to the <see cref="SuperView"/>'s <see cref="Viewport"/>.</returns>
     /// <param name="x">Screen-relative column.</param>
     /// <param name="x">Screen-relative column.</param>
     /// <param name="y">Screen-relative row.</param>
     /// <param name="y">Screen-relative row.</param>
     public virtual Point ScreenToFrame (int x, int y)
     public virtual Point ScreenToFrame (int x, int y)
     {
     {
-        Point superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty;
         if (SuperView is null)
         if (SuperView is null)
         {
         {
-            superViewBoundsOffset.Offset (x - Frame.X, y - Frame.Y);
-            return superViewBoundsOffset;
+            return new Point (x - Frame.X, y - Frame.Y);
         }
         }
 
 
-        var frame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y);
-        frame.Offset (-Frame.X, -Frame.Y);
+        Point superViewViewportOffset = SuperView.GetViewportOffsetFromFrame ();
+        superViewViewportOffset.X -= SuperView.Viewport.X;
+        superViewViewportOffset.Y -= SuperView.Viewport.Y;
+
+        x -= superViewViewportOffset.X;
+        y -= superViewViewportOffset.Y;
+
+        Point frame = SuperView.ScreenToFrame (x, y);
+        frame.X -= Frame.X;
+        frame.Y -= Frame.Y;
+
         return frame;
         return frame;
     }
     }
 
 
@@ -138,13 +165,16 @@ public partial class View
     /// <value>The <see cref="Pos"/> object representing the X position.</value>
     /// <value>The <see cref="Pos"/> object representing the X position.</value>
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
+    ///         The position is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="ContentSize"/>.
+    ///     </para>
+    ///     <para>
     ///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the view has been
     ///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the view has been
-    ///         initialized ( <see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rectangle)"/> has been
+    ///         initialized ( <see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Size)"/> has been
     ///         called.
     ///         called.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         Changing this property will eventually (when the view is next drawn) cause the
     ///         Changing this property will eventually (when the view is next drawn) cause the
-    ///         <see cref="LayoutSubview(View, Rectangle)"/> and <see cref="OnDrawContent(Rectangle)"/> methods to be called.
+    ///         <see cref="LayoutSubview(View, Size)"/> and <see cref="OnDrawContent(Rectangle)"/> methods to be called.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
     ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
@@ -157,7 +187,13 @@ public partial class View
         get => VerifyIsInitialized (_x, nameof (X));
         get => VerifyIsInitialized (_x, nameof (X));
         set
         set
         {
         {
+            if (Equals (_x, value))
+            {
+                return;
+            }
+
             _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null");
             _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null");
+
             OnResizeNeeded ();
             OnResizeNeeded ();
         }
         }
     }
     }
@@ -168,13 +204,16 @@ public partial class View
     /// <value>The <see cref="Pos"/> object representing the Y position.</value>
     /// <value>The <see cref="Pos"/> object representing the Y position.</value>
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
+    ///         The position is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="ContentSize"/>.
+    ///     </para>
+    ///     <para>
     ///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the view has been
     ///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the view has been
-    ///         initialized ( <see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rectangle)"/> has been
+    ///         initialized ( <see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Size)"/> has been
     ///         called.
     ///         called.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         Changing this property will eventually (when the view is next drawn) cause the
     ///         Changing this property will eventually (when the view is next drawn) cause the
-    ///         <see cref="LayoutSubview(View, Rectangle)"/> and <see cref="OnDrawContent(Rectangle)"/> methods to be called.
+    ///         <see cref="LayoutSubview(View, Size)"/> and <see cref="OnDrawContent(Rectangle)"/> methods to be called.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
     ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
@@ -187,6 +226,11 @@ public partial class View
         get => VerifyIsInitialized (_y, nameof (Y));
         get => VerifyIsInitialized (_y, nameof (Y));
         set
         set
         {
         {
+            if (Equals (_y, value))
+            {
+                return;
+            }
+
             _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null");
             _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null");
             OnResizeNeeded ();
             OnResizeNeeded ();
         }
         }
@@ -198,13 +242,16 @@ public partial class View
     /// <value>The <see cref="Dim"/> object representing the height of the view (the number of rows).</value>
     /// <value>The <see cref="Dim"/> object representing the height of the view (the number of rows).</value>
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
+    ///         The dimension is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="ContentSize"/>.
+    ///     </para>
+    ///     <para>
     ///         If set to a relative value (e.g. <see cref="Dim.Fill(int)"/>) the value is indeterminate until the view has
     ///         If set to a relative value (e.g. <see cref="Dim.Fill(int)"/>) the value is indeterminate until the view has
-    ///         been initialized ( <see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rectangle)"/> has been
+    ///         been initialized ( <see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Size)"/> has been
     ///         called.
     ///         called.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         Changing this property will eventually (when the view is next drawn) cause the
     ///         Changing this property will eventually (when the view is next drawn) cause the
-    ///         <see cref="LayoutSubview(View, Rectangle)"/> and <see cref="OnDrawContent(Rectangle)"/> methods to be called.
+    ///         <see cref="LayoutSubview(View, Size)"/> and <see cref="OnDrawContent(Rectangle)"/> methods to be called.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
     ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
@@ -217,6 +264,11 @@ public partial class View
         get => VerifyIsInitialized (_height, nameof (Height));
         get => VerifyIsInitialized (_height, nameof (Height));
         set
         set
         {
         {
+            if (Equals (_height, value))
+            {
+                return;
+            }
+
             _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null");
             _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null");
 
 
             if (AutoSize)
             if (AutoSize)
@@ -247,13 +299,16 @@ public partial class View
     /// <value>The <see cref="Dim"/> object representing the width of the view (the number of columns).</value>
     /// <value>The <see cref="Dim"/> object representing the width of the view (the number of columns).</value>
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
+    ///         The dimension is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="ContentSize"/>.
+    ///     </para>
+    ///     <para>
     ///         If set to a relative value (e.g. <see cref="Dim.Fill(int)"/>) the value is indeterminate until the view has
     ///         If set to a relative value (e.g. <see cref="Dim.Fill(int)"/>) the value is indeterminate until the view has
-    ///         been initialized ( <see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rectangle)"/> has been
+    ///         been initialized ( <see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Size)"/> has been
     ///         called.
     ///         called.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         Changing this property will eventually (when the view is next drawn) cause the
     ///         Changing this property will eventually (when the view is next drawn) cause the
-    ///         <see cref="LayoutSubview(View, Rectangle)"/> and <see cref="OnDrawContent(Rectangle)"/> methods to be called.
+    ///         <see cref="LayoutSubview(View, Size)"/> and <see cref="OnDrawContent(Rectangle)"/> methods to be called.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
     ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
@@ -266,6 +321,11 @@ public partial class View
         get => VerifyIsInitialized (_width, nameof (Width));
         get => VerifyIsInitialized (_width, nameof (Width));
         set
         set
         {
         {
+            if (Equals (_width, value))
+            {
+                return;
+            }
+
             _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null");
             _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null");
 
 
             if (AutoSize)
             if (AutoSize)
@@ -288,123 +348,13 @@ public partial class View
 
 
     #endregion Frame
     #endregion Frame
 
 
-    #region Bounds
-
-    /// <summary>
-    ///     The bounds represent the View-relative rectangle used for this view; the area inside the view where
-    ///     subviews and content are presented.
-    /// </summary>
-    /// <value>The rectangle describing the location and size of the area where the views' subviews and content are drawn.</value>
-    /// <remarks>
-    ///     <para>
-    ///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value of Bounds is indeterminate until
-    ///         the view has been initialized ( <see cref="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been
-    ///         called.
-    ///     </para>
-    ///     <para>
-    ///         Updates to the Bounds updates <see cref="Frame"/>, and has the same effect as updating the
-    ///         <see cref="Frame"/>.
-    ///     </para>
-    ///     <para>
-    ///         Altering the Bounds will eventually (when the view is next laid out) cause the
-    ///         <see cref="LayoutSubview(View, Rectangle)"/> and <see cref="OnDrawContent(Rectangle)"/> methods to be called.
-    ///     </para>
-    ///     <para>
-    ///         Because <see cref="Bounds"/> coordinates are relative to the upper-left corner of the <see cref="View"/>, the
-    ///         coordinates of the upper-left corner of the rectangle returned by this property are (0,0). Use this property to
-    ///         obtain the size of the area of the view for tasks such as drawing the view's contents.
-    ///     </para>
-    /// </remarks>
-    public virtual Rectangle Bounds
-    {
-        get
-        {
-#if DEBUG
-            if (LayoutStyle == LayoutStyle.Computed && !IsInitialized)
-            {
-                Debug.WriteLine (
-                                 $"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}"
-                                );
-            }
-#endif // DEBUG
-
-            if (Margin is null || Border is null || Padding is null)
-            {
-                // CreateAdornments has not been called yet.
-                return Rectangle.Empty with { Size = 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))
-            };
-        }
-        set
-        {
-            // TODO: Should we enforce Bounds.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: Bounds.Location must always be 0,0. Location ({value.Location}) is ignored. {this}"
-                                );
-            }
-#endif // DEBUG
-            Thickness totalThickness = GetAdornmentsThickness ();
-
-            Frame = Frame with
-            {
-                Size = new (
-                            value.Size.Width + totalThickness.Horizontal,
-                            value.Size.Height + totalThickness.Vertical)
-            };
-        }
-    }
-
-    /// <summary>Converts a <see cref="Bounds"/>-relative rectangle to a screen-relative rectangle.</summary>
-    public Rectangle BoundsToScreen (in Rectangle bounds)
-    {
-        // Translate bounds to Frame (our SuperView's Bounds-relative coordinates)
-        Rectangle screen = FrameToScreen ();
-        Point boundsOffset = GetBoundsOffset ();
-        screen.Offset (boundsOffset.X + bounds.X, boundsOffset.Y + bounds.Y);
-
-        return new (screen.Location, bounds.Size);
-    }
-
-    /// <summary>Converts a screen-relative coordinate to a bounds-relative coordinate.</summary>
-    /// <returns>The coordinate relative to this view's <see cref="Bounds"/>.</returns>
-    /// <param name="x">Screen-relative column.</param>
-    /// <param name="y">Screen-relative row.</param>
-    public Point ScreenToBounds (int x, int y)
-    {
-        Point boundsOffset = GetBoundsOffset ();
-        Point screen = ScreenToFrame (x, y);
-        screen.Offset (-boundsOffset.X, -boundsOffset.Y);
-
-        return screen;
-    }
-
-    /// <summary>
-    ///     Helper to get the X and Y offset of the Bounds 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 GetBoundsOffset () { return Padding is null ? Point.Empty : Padding.Thickness.GetInside (Padding.Frame).Location; }
-
-    #endregion Bounds
-
     #region AutoSize
     #region AutoSize
 
 
     private bool _autoSize;
     private bool _autoSize;
 
 
     /// <summary>
     /// <summary>
     ///     Gets or sets a flag that determines whether the View will be automatically resized to fit the <see cref="Text"/>
     ///     Gets or sets a flag that determines whether the View will be automatically resized to fit the <see cref="Text"/>
-    ///     within <see cref="Bounds"/>.
+    ///     within <see cref="Viewport"/>.
     ///     <para>
     ///     <para>
     ///         The default is <see langword="false"/>. Set to <see langword="true"/> to turn on AutoSize. If
     ///         The default is <see langword="false"/>. Set to <see langword="true"/> to turn on AutoSize. If
     ///         <see langword="true"/> then <see cref="Width"/> and <see cref="Height"/> will be used if <see cref="Text"/> can
     ///         <see langword="true"/> then <see cref="Width"/> and <see cref="Height"/> will be used if <see cref="Text"/> can
@@ -462,7 +412,7 @@ public partial class View
             if (ValidatePosDim)
             if (ValidatePosDim)
             {
             {
                 // BUGBUG: This ain't right, obviously.  We need to figure out how to handle this.
                 // BUGBUG: This ain't right, obviously.  We need to figure out how to handle this.
-                boundsChanged = ResizeBoundsToFit (newFrameSize);
+                boundsChanged = ResizeViewportToFit (newFrameSize);
             }
             }
             else
             else
             {
             {
@@ -577,35 +527,35 @@ public partial class View
     /// <summary>Resizes the View to fit the specified size. Factors in the HotKey.</summary>
     /// <summary>Resizes the View to fit the specified size. Factors in the HotKey.</summary>
     /// <remarks>ResizeBoundsToFit can only be called when AutoSize is true (or being set to true).</remarks>
     /// <remarks>ResizeBoundsToFit can only be called when AutoSize is true (or being set to true).</remarks>
     /// <param name="size"></param>
     /// <param name="size"></param>
-    /// <returns>whether the Bounds was changed or not</returns>
-    private bool ResizeBoundsToFit (Size size)
+    /// <returns>whether the Viewport was changed or not</returns>
+    private bool ResizeViewportToFit (Size size)
     {
     {
         //if (AutoSize == false) {
         //if (AutoSize == false) {
-        //	throw new InvalidOperationException ("ResizeBoundsToFit can only be called when AutoSize is true");
+        //	throw new InvalidOperationException ("ResizeViewportToFit can only be called when AutoSize is true");
         //}
         //}
 
 
-        var boundsChanged = false;
+        var changed = false;
         bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW);
         bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW);
         bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH);
         bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH);
 
 
         if (canSizeW)
         if (canSizeW)
         {
         {
-            boundsChanged = true;
+            changed = true;
             _width = rW;
             _width = rW;
         }
         }
 
 
         if (canSizeH)
         if (canSizeH)
         {
         {
-            boundsChanged = true;
+            changed = true;
             _height = rH;
             _height = rH;
         }
         }
 
 
-        if (boundsChanged)
+        if (changed)
         {
         {
-            Bounds = new (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
+            Viewport = new (Viewport.X, Viewport.Y, canSizeW ? rW : Viewport.Width, canSizeH ? rH : Viewport.Height);
         }
         }
 
 
-        return boundsChanged;
+        return changed;
     }
     }
 
 
     #endregion AutoSize
     #endregion AutoSize
@@ -651,25 +601,20 @@ public partial class View
 
 
     #endregion Layout Engine
     #endregion Layout Engine
 
 
-    internal bool LayoutNeeded { get; private set; } = true;
-
     /// <summary>
     /// <summary>
-    /// Indicates whether the specified SuperView-relative coordinates are within the View's <see cref="Frame"/>.
+    ///     Indicates whether the specified SuperView-relative coordinates are within the View's <see cref="Frame"/>.
     /// </summary>
     /// </summary>
     /// <param name="x">SuperView-relative X coordinate.</param>
     /// <param name="x">SuperView-relative X coordinate.</param>
     /// <param name="y">SuperView-relative Y coordinate.</param>
     /// <param name="y">SuperView-relative Y coordinate.</param>
     /// <returns><see langword="true"/> if the specified SuperView-relative coordinates are within the View.</returns>
     /// <returns><see langword="true"/> if the specified SuperView-relative coordinates are within the View.</returns>
-    public virtual bool Contains (int x, int y)
-    {
-        return Frame.Contains (x, y);
-    }
+    public virtual bool Contains (int x, int y) { return Frame.Contains (x, y); }
 
 
 #nullable enable
 #nullable enable
     /// <summary>Finds the first Subview of <paramref name="start"/> that is visible at the provided location.</summary>
     /// <summary>Finds the first Subview of <paramref name="start"/> that is visible at the provided location.</summary>
     /// <remarks>
     /// <remarks>
-    /// <para>
-    ///     Used to determine what view the mouse is over.
-    /// </para>
+    ///     <para>
+    ///         Used to determine what view the mouse is over.
+    ///     </para>
     /// </remarks>
     /// </remarks>
     /// <param name="start">The view to scope the search by.</param>
     /// <param name="start">The view to scope the search by.</param>
     /// <param name="x"><paramref name="start"/>.SuperView-relative X coordinate.</param>
     /// <param name="x"><paramref name="start"/>.SuperView-relative X coordinate.</param>
@@ -682,57 +627,66 @@ public partial class View
     // CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
     // CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
     internal static View? FindDeepestView (View? start, int x, int y)
     internal static View? FindDeepestView (View? start, int x, int y)
     {
     {
-        if (start is null || !start.Visible || !start.Contains (x, y))
+        while (start is { Visible: true } && start.Contains (x, y))
         {
         {
-            return null;
-        }
-
-        Adornment? found = null;
+            Adornment? found = null;
 
 
-        if (start.Margin.Contains (x, y))
-        {
-            found = start.Margin;
-        }
-        else if (start.Border.Contains (x, y))
-        {
-            found = start.Border;
-        }
-        else if (start.Padding.Contains (x, y))
-        {
-            found = start.Padding;
-        }
+            if (start.Margin.Contains (x, y))
+            {
+                found = start.Margin;
+            }
+            else if (start.Border.Contains (x, y))
+            {
+                found = start.Border;
+            }
+            else if (start.Padding.Contains (x, y))
+            {
+                found = start.Padding;
+            }
 
 
-        Point boundsOffset = start.GetBoundsOffset ();
+            Point viewportOffset = start.GetViewportOffsetFromFrame ();
 
 
-        if (found is { })
-        {
-            start = found;
-            boundsOffset = found.Parent.Frame.Location;
-        }
+            if (found is { })
+            {
+                start = found;
+                viewportOffset = found.Parent.Frame.Location;
+            }
 
 
-        if (start.InternalSubviews is { Count: > 0 })
-        {
-            int startOffsetX = x - (start.Frame.X + boundsOffset.X);
-            int startOffsetY = y - (start.Frame.Y + boundsOffset.Y);
+            int startOffsetX = x - (start.Frame.X + viewportOffset.X);
+            int startOffsetY = y - (start.Frame.Y + viewportOffset.Y);
 
 
+            View? subview = null;
             for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
             for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
             {
             {
-                View nextStart = start.InternalSubviews [i];
-
-                if (nextStart.Visible && nextStart.Contains (startOffsetX, startOffsetY))
+                if (start.InternalSubviews [i].Visible
+                    && start.InternalSubviews [i].Contains (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y))
                 {
                 {
-                    // TODO: Remove recursion
-                    return FindDeepestView (nextStart, startOffsetX, startOffsetY) ?? nextStart;
+                    subview = start.InternalSubviews [i];
+                    x = startOffsetX + start.Viewport.X;
+                    y = startOffsetY + start.Viewport.Y;
+
+                    // start is the deepest subview under the mouse; stop searching the subviews
+                    break;
                 }
                 }
             }
             }
+
+            if (subview is null)
+            {
+                // No subview was found that's under the mouse, so we're done
+                return start;
+            }
+
+            // We found a subview of start that's under the mouse, continue...
+            start = subview;
         }
         }
 
 
-        return start;
+        return null;
     }
     }
+
 #nullable restore
 #nullable restore
 
 
     /// <summary>
     /// <summary>
-    ///     Gets a new location of the <see cref="View"/> that is within the Bounds of the <paramref name="viewToMove"/>'s
+    ///     Gets a new location of the <see cref="View"/> that is within the Viewport of the <paramref name="viewToMove"/>'s
     ///     <see cref="View.SuperView"/> (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates.
     ///     <see cref="View.SuperView"/> (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
@@ -761,6 +715,7 @@ public partial class View
     {
     {
         int maxDimension;
         int maxDimension;
         View superView;
         View superView;
+        statusBar = null;
 
 
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         {
         {
@@ -769,8 +724,8 @@ public partial class View
         }
         }
         else
         else
         {
         {
-            // Use the SuperView's Bounds, not Frame
-            maxDimension = viewToMove.SuperView.Bounds.Width;
+            // Use the SuperView's Viewport, not Frame
+            maxDimension = viewToMove.SuperView.Viewport.Width;
             superView = viewToMove.SuperView;
             superView = viewToMove.SuperView;
         }
         }
 
 
@@ -795,7 +750,8 @@ public partial class View
         }
         }
 
 
         //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
         //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
-        bool menuVisible, statusVisible;
+        bool menuVisible = false;
+        bool statusVisible = false;
 
 
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         {
         {
@@ -805,12 +761,15 @@ public partial class View
         {
         {
             View t = viewToMove.SuperView;
             View t = viewToMove.SuperView;
 
 
-            while (t is not Toplevel)
+            while (t is { } and not Toplevel)
             {
             {
                 t = t.SuperView;
                 t = t.SuperView;
             }
             }
 
 
-            menuVisible = ((Toplevel)t).MenuBar?.Visible == true;
+            if (t is Toplevel toplevel)
+            {
+                menuVisible = toplevel.MenuBar?.Visible == true;
+            }
         }
         }
 
 
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
@@ -833,13 +792,16 @@ public partial class View
         {
         {
             View t = viewToMove.SuperView;
             View t = viewToMove.SuperView;
 
 
-            while (t is not Toplevel)
+            while (t is { } and not Toplevel)
             {
             {
                 t = t.SuperView;
                 t = t.SuperView;
             }
             }
 
 
-            statusVisible = ((Toplevel)t).StatusBar?.Visible == true;
-            statusBar = ((Toplevel)t).StatusBar;
+            if (t is Toplevel toplevel)
+            {
+                statusVisible = toplevel.StatusBar?.Visible == true;
+                statusBar = toplevel.StatusBar;
+            }
         }
         }
 
 
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
@@ -848,7 +810,7 @@ public partial class View
         }
         }
         else
         else
         {
         {
-            maxDimension = statusVisible ? viewToMove.SuperView.Frame.Height - 1 : viewToMove.SuperView.Frame.Height;
+            maxDimension = statusVisible ? viewToMove.SuperView.Viewport.Height - 1 : viewToMove.SuperView.Viewport.Height;
         }
         }
 
 
         if (superView.Margin is { } && superView == viewToMove.SuperView)
         if (superView.Margin is { } && superView == viewToMove.SuperView)
@@ -916,8 +878,7 @@ public partial class View
 
 
         LayoutAdornments ();
         LayoutAdornments ();
 
 
-        Rectangle oldBounds = Bounds;
-        OnLayoutStarted (new () { OldBounds = oldBounds });
+        OnLayoutStarted (new (ContentSize));
 
 
         SetTextFormatterSize ();
         SetTextFormatterSize ();
 
 
@@ -929,7 +890,7 @@ public partial class View
 
 
         foreach (View v in ordered)
         foreach (View v in ordered)
         {
         {
-            LayoutSubview (v, new (GetBoundsOffset (), Bounds.Size));
+            LayoutSubview (v, ContentSize);
         }
         }
 
 
         // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case.
         // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case.
@@ -938,13 +899,19 @@ public partial class View
         {
         {
             foreach ((View from, View to) in edges)
             foreach ((View from, View to) in edges)
             {
             {
-                LayoutSubview (to, from.Frame);
+                LayoutSubview (to, from.ContentSize);
             }
             }
         }
         }
 
 
         LayoutNeeded = false;
         LayoutNeeded = false;
 
 
-        OnLayoutComplete (new () { OldBounds = oldBounds });
+        OnLayoutComplete (new (ContentSize));
+    }
+    private void LayoutSubview (View v, Size contentSize)
+    {
+        v.SetRelativeLayout (contentSize);
+        v.LayoutSubviews ();
+        v.LayoutNeeded = false;
     }
     }
 
 
     /// <summary>Indicates that the view does not need to be laid out.</summary>
     /// <summary>Indicates that the view does not need to be laid out.</summary>
@@ -969,19 +936,19 @@ public partial class View
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
     ///         Determines the relative bounds of the <see cref="View"/> and its <see cref="Frame"/>s, and then calls
     ///         Determines the relative bounds of the <see cref="View"/> and its <see cref="Frame"/>s, and then calls
-    ///         <see cref="SetRelativeLayout(Rectangle)"/> to update the view.
+    ///         <see cref="SetRelativeLayout(Size)"/> to update the view.
     ///     </para>
     ///     </para>
     /// </remarks>
     /// </remarks>
     internal void OnResizeNeeded ()
     internal void OnResizeNeeded ()
     {
     {
         // TODO: Identify a real-world use-case where this API should be virtual. 
         // TODO: Identify a real-world use-case where this API should be virtual. 
         // TODO: Until then leave it `internal` and non-virtual
         // TODO: Until then leave it `internal` and non-virtual
-        // First try SuperView.Bounds, then Application.Top, then Driver.Bounds.
+        // First try SuperView.Viewport, then Application.Top, then Driver.Viewport.
         // Finally, if none of those are valid, use int.MaxValue (for Unit tests).
         // Finally, if none of those are valid, use int.MaxValue (for Unit tests).
-        Rectangle relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds :
-                                   Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.Bounds :
-                                   Application.Driver?.Bounds ?? new Rectangle (0, 0, int.MaxValue, int.MaxValue);
-        SetRelativeLayout (relativeBounds);
+        Size contentSize = SuperView is { IsInitialized: true } ? SuperView.ContentSize :
+                           Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.ContentSize :
+                           Application.Driver?.Screen.Size ?? new (int.MaxValue, int.MaxValue);
+        SetRelativeLayout (contentSize);
 
 
         // TODO: Determine what, if any of the below is actually needed here.
         // TODO: Determine what, if any of the below is actually needed here.
         if (IsInitialized)
         if (IsInitialized)
@@ -997,6 +964,7 @@ public partial class View
             SetNeedsLayout ();
             SetNeedsLayout ();
         }
         }
     }
     }
+    internal bool LayoutNeeded { get; private set; } = true;
 
 
     /// <summary>
     /// <summary>
     ///     Sets the internal <see cref="LayoutNeeded"/> flag for this View and all of it's subviews and it's SuperView.
     ///     Sets the internal <see cref="LayoutNeeded"/> flag for this View and all of it's subviews and it's SuperView.
@@ -1021,15 +989,22 @@ public partial class View
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Applies the view's position (<see cref="X"/>, <see cref="Y"/>) and dimension (<see cref="Width"/>, and
-    ///     <see cref="Height"/>) to <see cref="Frame"/>, given a rectangle describing the SuperView's Bounds (nominally the
-    ///     same as <c>this.SuperView.Bounds</c>).
+    ///     Adjusts <see cref="Frame"/> given the SuperView's ContentSize (nominally the same as
+    ///     <c>this.SuperView.ContentSize</c>)
+    ///     and the position (<see cref="X"/>, <see cref="Y"/>) and dimension (<see cref="Width"/>, and
+    ///     <see cref="Height"/>).
     /// </summary>
     /// </summary>
-    /// <param name="superviewBounds">
-    ///     The rectangle describing the SuperView's Bounds (nominally the same as
-    ///     <c>this.SuperView.Bounds</c>).
+    /// <remarks>
+    /// <para>
+    /// If <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, or <see cref="Height"/> are
+    /// absolute, they will be updated to reflect the new size and position of the view. Otherwise, they
+    /// are left unchanged.
+    /// </para>
+    /// </remarks>
+    /// <param name="superviewContentSize">
+    ///     The size of the SuperView's content (nominally the same as <c>this.SuperView.ContentSize</c>).
     /// </param>
     /// </param>
-    internal void SetRelativeLayout (Rectangle superviewBounds)
+    internal void SetRelativeLayout (Size superviewContentSize)
     {
     {
         Debug.Assert (_x is { });
         Debug.Assert (_x is { });
         Debug.Assert (_y is { });
         Debug.Assert (_y is { });
@@ -1051,13 +1026,13 @@ public partial class View
         // TODO: View.AutoSize stuff is removed.
         // TODO: View.AutoSize stuff is removed.
 
 
         // Returns the new dimension (width or height) and location (x or y) for the View given
         // Returns the new dimension (width or height) and location (x or y) for the View given
-        //   the superview's Bounds
+        //   the superview's Viewport
         //   the current Pos (View.X or View.Y)
         //   the current Pos (View.X or View.Y)
         //   the current Dim (View.Width or View.Height)
         //   the current Dim (View.Width or View.Height)
         // This method is called recursively if pos is Pos.PosCombine
         // This method is called recursively if pos is Pos.PosCombine
         (int newLocation, int newDimension) GetNewLocationAndDimension (
         (int newLocation, int newDimension) GetNewLocationAndDimension (
             bool width,
             bool width,
-            Rectangle superviewBounds,
+            Size superviewContentSize,
             Pos pos,
             Pos pos,
             Dim dim,
             Dim dim,
             int autosizeDimension
             int autosizeDimension
@@ -1119,7 +1094,7 @@ public partial class View
             }
             }
 
 
             int newDimension, newLocation;
             int newDimension, newLocation;
-            int superviewDimension = width ? superviewBounds.Width : superviewBounds.Height;
+            int superviewDimension = width ? superviewContentSize.Width : superviewContentSize.Height;
 
 
             // Determine new location
             // Determine new location
             switch (pos)
             switch (pos)
@@ -1143,7 +1118,7 @@ public partial class View
 
 
                     (left, newDimension) = GetNewLocationAndDimension (
                     (left, newDimension) = GetNewLocationAndDimension (
                                                                        width,
                                                                        width,
-                                                                       superviewBounds,
+                                                                       superviewContentSize,
                                                                        combine._left,
                                                                        combine._left,
                                                                        dim,
                                                                        dim,
                                                                        autosizeDimension
                                                                        autosizeDimension
@@ -1151,7 +1126,7 @@ public partial class View
 
 
                     (right, newDimension) = GetNewLocationAndDimension (
                     (right, newDimension) = GetNewLocationAndDimension (
                                                                         width,
                                                                         width,
-                                                                        superviewBounds,
+                                                                        superviewContentSize,
                                                                         combine._right,
                                                                         combine._right,
                                                                         dim,
                                                                         dim,
                                                                         autosizeDimension
                                                                         autosizeDimension
@@ -1193,10 +1168,10 @@ public partial class View
         }
         }
 
 
         // horizontal/width
         // horizontal/width
-        (newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width);
+        (newX, newW) = GetNewLocationAndDimension (true, superviewContentSize, _x, _width, autosize.Width);
 
 
         // vertical/height
         // vertical/height
-        (newY, newH) = GetNewLocationAndDimension (false, superviewBounds, _y, _height, autosize.Height);
+        (newY, newH) = GetNewLocationAndDimension (false, superviewContentSize, _y, _height, autosize.Height);
 
 
         Rectangle r = new (newX, newY, newW, newH);
         Rectangle r = new (newX, newY, newW, newH);
 
 
@@ -1204,7 +1179,7 @@ public partial class View
         {
         {
             // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making
             // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making
             // the view LayoutStyle.Absolute.
             // the view LayoutStyle.Absolute.
-            _frame = r;
+            SetFrame (r);
 
 
             if (_x is Pos.PosAbsolute)
             if (_x is Pos.PosAbsolute)
             {
             {
@@ -1236,7 +1211,7 @@ public partial class View
             {
             {
                 // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making
                 // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making
                 // the view LayoutStyle.Absolute.
                 // the view LayoutStyle.Absolute.
-                _frame = _frame with { Size = autosize };
+                SetFrame (_frame with { Size = autosize });
 
 
                 if (autosize.Width == 0)
                 if (autosize.Width == 0)
                 {
                 {
@@ -1417,17 +1392,6 @@ public partial class View
         return result;
         return result;
     } // TopologicalSort
     } // TopologicalSort
 
 
-    private void LayoutSubview (View v, Rectangle contentArea)
-    {
-        //if (v.LayoutStyle == LayoutStyle.Computed) {
-        v.SetRelativeLayout (contentArea);
-
-        //}
-
-        v.LayoutSubviews ();
-        v.LayoutNeeded = false;
-    }
-
     #region Diagnostics
     #region Diagnostics
 
 
     // Diagnostics to highlight when Width or Height is read before the view has been initialized
     // Diagnostics to highlight when Width or Height is read before the view has been initialized
@@ -1468,4 +1432,4 @@ public partial class View
     public bool ValidatePosDim { get; set; }
     public bool ValidatePosDim { get; set; }
 
 
     #endregion
     #endregion
-}
+}

+ 3 - 3
Terminal.Gui/View/View.cs

@@ -52,7 +52,7 @@ namespace Terminal.Gui;
 ///         To create a View using Absolute layout, call a constructor that takes a Rect parameter to specify the
 ///         To create a View using Absolute layout, call a constructor that takes a Rect parameter to specify the
 ///         absolute position and size or simply set <see cref="View.Frame "/>). To create a View using Computed layout use
 ///         absolute position and size or simply set <see cref="View.Frame "/>). To create a View using Computed layout use
 ///         a constructor that does not take a Rect parameter and set the X, Y, Width and Height properties on the view to
 ///         a constructor that does not take a Rect parameter and set the X, Y, Width and Height properties on the view to
-///         non-absolute values. Both approaches use coordinates that are relative to the <see cref="Bounds"/> of the
+///         non-absolute values. Both approaches use coordinates that are relative to the <see cref="Viewport"/> of the
 ///         <see cref="SuperView"/> the View is added to.
 ///         <see cref="SuperView"/> the View is added to.
 ///     </para>
 ///     </para>
 ///     <para>
 ///     <para>
@@ -73,7 +73,7 @@ namespace Terminal.Gui;
 ///         a View can be accessed with the <see cref="SuperView"/> property.
 ///         a View can be accessed with the <see cref="SuperView"/> property.
 ///     </para>
 ///     </para>
 ///     <para>
 ///     <para>
-///         To flag a region of the View's <see cref="Bounds"/> to be redrawn call <see cref="SetNeedsDisplay(Rectangle)"/>
+///         To flag a region of the View's <see cref="Viewport"/> to be redrawn call <see cref="SetNeedsDisplay(Rectangle)"/>
 ///         .
 ///         .
 ///         To flag the entire view for redraw call <see cref="SetNeedsDisplay()"/>.
 ///         To flag the entire view for redraw call <see cref="SetNeedsDisplay()"/>.
 ///     </para>
 ///     </para>
@@ -234,7 +234,7 @@ public partial class View : Responder, ISupportInitializeNotification
 
 
         // TODO: Move these into ViewText.cs as EndInit_Text() to consolodate.
         // TODO: Move these into ViewText.cs as EndInit_Text() to consolodate.
         // TODO: Verify UpdateTextDirection really needs to be called here.
         // TODO: Verify UpdateTextDirection really needs to be called here.
-        // These calls were moved from BeginInit as they access Bounds which is indeterminate until EndInit is called.
+        // These calls were moved from BeginInit as they access Viewport which is indeterminate until EndInit is called.
         UpdateTextDirection (TextDirection);
         UpdateTextDirection (TextDirection);
         UpdateTextFormatterText ();
         UpdateTextFormatterText ();
         OnResizeNeeded ();
         OnResizeNeeded ();

+ 6 - 6
Terminal.Gui/View/ViewAdornments.cs

@@ -39,7 +39,7 @@ public partial class View
 
 
     /// <summary>
     /// <summary>
     ///     The <see cref="Adornment"/> that enables separation of a View from other SubViews of the same
     ///     The <see cref="Adornment"/> that enables separation of a View from other SubViews of the same
-    ///     SuperView. The margin offsets the <see cref="Bounds"/> from the <see cref="Frame"/>.
+    ///     SuperView. The margin offsets the <see cref="Viewport"/> from the <see cref="Frame"/>.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
@@ -55,7 +55,7 @@ public partial class View
     public Margin Margin { get; private set; }
     public Margin Margin { get; private set; }
 
 
     /// <summary>
     /// <summary>
-    ///     The <see cref="Adornment"/> that offsets the <see cref="Bounds"/> from the <see cref="Margin"/>.
+    ///     The <see cref="Adornment"/> that offsets the <see cref="Viewport"/> from the <see cref="Margin"/>.
     ///     The Border provides the space for a visual border (drawn using
     ///     The Border provides the space for a visual border (drawn using
     ///     line-drawing glyphs) and the Title. The Border expands inward; in other words if `Border.Thickness.Top == 2` the
     ///     line-drawing glyphs) and the Title. The Border expands inward; in other words if `Border.Thickness.Top == 2` the
     ///     border and title will take up the first row and the second row will be filled with spaces.
     ///     border and title will take up the first row and the second row will be filled with spaces.
@@ -116,7 +116,7 @@ public partial class View
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     The <see cref="Adornment"/> inside of the view that offsets the <see cref="Bounds"/>
+    ///     The <see cref="Adornment"/> inside of the view that offsets the <see cref="Viewport"/>
     ///     from the <see cref="Border"/>.
     ///     from the <see cref="Border"/>.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
@@ -151,7 +151,7 @@ public partial class View
 
 
         if (Margin.Frame.Size != Frame.Size)
         if (Margin.Frame.Size != Frame.Size)
         {
         {
-            Margin._frame = Rectangle.Empty with { Size = Frame.Size };
+            Margin.SetFrame (Rectangle.Empty with { Size = Frame.Size });
             Margin.X = 0;
             Margin.X = 0;
             Margin.Y = 0;
             Margin.Y = 0;
             Margin.Width = Frame.Size.Width;
             Margin.Width = Frame.Size.Width;
@@ -170,7 +170,7 @@ public partial class View
 
 
         if (border != Border.Frame)
         if (border != Border.Frame)
         {
         {
-            Border._frame = border;
+            Border.SetFrame (border);
             Border.X = border.Location.X;
             Border.X = border.Location.X;
             Border.Y = border.Location.Y;
             Border.Y = border.Location.Y;
             Border.Width = border.Size.Width;
             Border.Width = border.Size.Width;
@@ -189,7 +189,7 @@ public partial class View
 
 
         if (padding != Padding.Frame)
         if (padding != Padding.Frame)
         {
         {
-            Padding._frame = padding;
+            Padding.SetFrame (padding);
             Padding.X = padding.Location.X;
             Padding.X = padding.Location.X;
             Padding.Y = padding.Location.Y;
             Padding.Y = padding.Location.Y;
             Padding.Width = padding.Size.Width;
             Padding.Width = padding.Size.Width;

+ 483 - 0
Terminal.Gui/View/ViewContent.cs

@@ -0,0 +1,483 @@
+using System.Diagnostics;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Settings for how the <see cref="View.Viewport"/> behaves relative to the View's Content area.
+/// </summary>
+[Flags]
+public enum ViewportSettings
+{
+    /// <summary>
+    ///     No settings.
+    /// </summary>
+    None = 0,
+
+    /// <summary>
+    ///     If set, <see cref="View.Viewport"/><c>.X</c> can be set to negative values enabling scrolling beyond the left of
+    ///     the
+    ///     content area.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         When not set, <see cref="View.Viewport"/><c>.X</c> is constrained to positive values.
+    ///     </para>
+    /// </remarks>
+    AllowNegativeX = 1,
+
+    /// <summary>
+    ///     If set, <see cref="View.Viewport"/><c>.Y</c> can be set to negative values enabling scrolling beyond the top of the
+    ///     content area.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         When not set, <see cref="View.Viewport"/><c>.Y</c> is constrained to positive values.
+    ///     </para>
+    /// </remarks>
+    AllowNegativeY = 2,
+
+    /// <summary>
+    ///     If set, <see cref="View.Viewport"/><c>.Size</c> can be set to negative coordinates enabling scrolling beyond the
+    ///     top-left of the
+    ///     content area.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         When not set, <see cref="View.Viewport"/><c>.Size</c> is constrained to positive coordinates.
+    ///     </para>
+    /// </remarks>
+    AllowNegativeLocation = AllowNegativeX | AllowNegativeY,
+
+    /// <summary>
+    ///     If set, <see cref="View.Viewport"/><c>.X</c> can be set values greater than <see cref="View.ContentSize"/>
+    ///     <c>.Width</c> enabling scrolling beyond the right
+    ///     of the content area.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         When not set, <see cref="View.Viewport"/><c>.X</c> is constrained to <see cref="View.ContentSize"/>
+    ///         <c>.Width - 1</c>.
+    ///         This means the last column of the content will remain visible even if there is an attempt to scroll the
+    ///         Viewport past the last column.
+    ///     </para>
+    ///     <para>
+    ///         The practical effect of this is that the last column of the content will always be visible.
+    ///     </para>
+    /// </remarks>
+    AllowXGreaterThanContentWidth = 4,
+
+    /// <summary>
+    ///     If set, <see cref="View.Viewport"/><c>.Y</c> can be set values greater than <see cref="View.ContentSize"/>
+    ///     <c>.Height</c> enabling scrolling beyond the right
+    ///     of the content area.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         When not set, <see cref="View.Viewport"/><c>.Y</c> is constrained to <see cref="View.ContentSize"/>
+    ///         <c>.Height - 1</c>.
+    ///         This means the last row of the content will remain visible even if there is an attempt to scroll the Viewport
+    ///         past the last row.
+    ///     </para>
+    ///     <para>
+    ///         The practical effect of this is that the last row of the content will always be visible.
+    ///     </para>
+    /// </remarks>
+    AllowYGreaterThanContentHeight = 8,
+
+    /// <summary>
+    ///     If set, <see cref="View.Viewport"/><c>.Size</c> can be set values greater than <see cref="View.ContentSize"/>
+    ///     enabling scrolling beyond the bottom-right
+    ///     of the content area.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         When not set, <see cref="View.Viewport"/> is constrained to <see cref="View.ContentSize"/><c> -1</c>.
+    ///         This means the last column and row of the content will remain visible even if there is an attempt to
+    ///         scroll the Viewport past the last column or row.
+    ///     </para>
+    /// </remarks>
+    AllowLocationGreaterThanContentSize = AllowXGreaterThanContentWidth | AllowYGreaterThanContentHeight,
+
+    /// <summary>
+    ///     By default, clipping is applied to the <see cref="View.Viewport"/>. Setting this flag will cause clipping to be
+    ///     applied to the visible content area.
+    /// </summary>
+    ClipContentOnly = 16,
+
+    /// <summary>
+    ///     If set <see cref="View.Clear()"/> will clear only the portion of the content
+    ///     area that is visible within the <see cref="View.Viewport"/>. This is useful for views that have a
+    ///     content area larger than the Viewport and want the area outside the content to be visually distinct.
+    /// </summary>
+    /// <remarks>
+    ///     <see cref="ClipContentOnly"/> must be set for this setting to work (clipping beyond the visible area must be
+    ///     disabled).
+    /// </remarks>
+    ClearContentOnly = 32
+}
+
+public partial class View
+{
+    #region Content Area
+
+    private Size _contentSize;
+
+    /// <summary>
+    ///     Gets or sets the size of the View's content. If not set, the value will be the same as the size of <see cref="Viewport"/>,
+    ///     and <c>Viewport.Location</c> will always be <c>0, 0</c>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If a positive size is provided, <see cref="Viewport"/> describes the portion of the content currently visible
+    ///         to the view. This enables virtual scrolling.
+    ///     </para>
+    ///     <para>
+    ///         Negative sizes are not supported.
+    ///     </para>
+    /// </remarks>
+    public Size ContentSize
+    {
+        get => _contentSize == Size.Empty ? Viewport.Size : _contentSize;
+        set
+        {
+            if (value.Width < 0 || value.Height < 0)
+            {
+                throw new ArgumentException (@"ContentSize cannot be negative.", nameof (value));
+            }
+
+            if (value == _contentSize)
+            {
+                return;
+            }
+
+            _contentSize = value;
+            OnContentSizeChanged (new (_contentSize));
+        }
+    }
+
+    /// <summary>
+    ///     Called when <see cref="ContentSize"/> changes. Invokes the <see cref="ContentSizeChanged"/> event.
+    /// </summary>
+    /// <param name="e"></param>
+    /// <returns></returns>
+    protected bool? OnContentSizeChanged (SizeChangedEventArgs e)
+    {
+        ContentSizeChanged?.Invoke (this, e);
+
+        if (e.Cancel != true)
+        {
+            SetNeedsLayout ();
+            SetNeedsDisplay ();
+        }
+
+        return e.Cancel;
+    }
+
+    /// <summary>
+    ///     Event raised when the <see cref="ContentSize"/> changes.
+    /// </summary>
+    public event EventHandler<SizeChangedEventArgs> ContentSizeChanged;
+
+    /// <summary>
+    ///     Converts a Content-relative location to a Screen-relative location.
+    /// </summary>
+    /// <param name="location">The Content-relative location.</param>
+    /// <returns>The Screen-relative location.</returns>
+    public Point ContentToScreen (in Point location)
+    {
+        // Subtract the ViewportOffsetFromFrame to get the Viewport-relative location.
+        Point viewportOffset = GetViewportOffsetFromFrame ();
+        Point contentRelativeToViewport = location;
+        contentRelativeToViewport.Offset (-Viewport.X, -Viewport.Y);
+
+        // Translate to Screen-Relative (our SuperView's Viewport-relative coordinates)
+        Rectangle screen = ViewportToScreen (new (contentRelativeToViewport, Size.Empty));
+
+        return screen.Location;
+    }
+
+    /// <summary>Converts a Screen-relative coordinate to a Content-relative coordinate.</summary>
+    /// <remarks>
+    ///     Content-relative means relative to the top-left corner of the view's Content, which is
+    ///     always at <c>0, 0</c>.
+    /// </remarks>
+    /// <param name="location">The Screen-relative location.</param>
+    /// <returns>The coordinate relative to this view's Content.</returns>
+    public Point ScreenToContent (in Point location)
+    {
+        Point viewportOffset = GetViewportOffsetFromFrame ();
+        Point screen = ScreenToFrame (location.X, location.Y);
+        screen.Offset (Viewport.X - viewportOffset.X, Viewport.Y - viewportOffset.Y);
+
+        return screen;
+    }
+
+    #endregion Content Area
+
+    #region Viewport
+
+    private ViewportSettings _viewportSettings;
+
+    /// <summary>
+    ///     Gets or sets how scrolling the <see cref="View.Viewport"/> on the View's Content Area is handled.
+    /// </summary>
+    public ViewportSettings ViewportSettings
+    {
+        get => _viewportSettings;
+        set
+        {
+            if (_viewportSettings == value)
+            {
+                return;
+            }
+
+            _viewportSettings = value;
+
+            if (IsInitialized)
+            {
+                // Force set Viewport to cause settings to be applied as needed
+                SetViewport (Viewport);
+            }
+        }
+    }
+
+    /// <summary>
+    ///     The location of the viewport into the view's content (0,0) is the top-left corner of the content. The Content
+    ///     area's size
+    ///     is <see cref="ContentSize"/>.
+    /// </summary>
+    private Point _viewportLocation;
+
+    /// <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 <see cref="Padding"/>.
+    ///     If the viewport Size is the same as <see cref="ContentSize"/> the Location will be <c>0, 0</c>.
+    /// </summary>
+    /// <value>
+    ///     The rectangle describing the location and size of the viewport into the View's virtual content, described by
+    ///     <see cref="ContentSize"/>.
+    /// </value>
+    /// <remarks>
+    ///     <para>
+    ///         Positive values for the location indicate the visible area is offset into (down-and-right) the View's virtual
+    ///         <see cref="ContentSize"/>. This enables scrolling down and to the right (e.g. in a <see cref="ListView"/>.
+    ///     </para>
+    ///     <para>
+    ///         Negative values for the location indicate the visible area is offset above (up-and-left) the View's virtual
+    ///         <see cref="ContentSize"/>. This enables scrolling up and to the left (e.g. in an image viewer that supports zoom
+    ///         where the image stays centered).
+    ///     </para>
+    ///     <para>
+    ///         The <see cref="ViewportSettings"/> property controls how scrolling is handled. 
+    ///     </para>
+    ///     <para>
+    ///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value of Viewport is indeterminate until
+    ///         the view has been initialized ( <see cref="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been
+    ///         called.
+    ///     </para>
+    ///     <para>
+    ///         Updates to the Viewport Size updates <see cref="Frame"/>, and has the same impact as updating the
+    ///         <see cref="Frame"/>.
+    ///     </para>
+    ///     <para>
+    ///         Altering the Viewport Size will eventually (when the view is next laid out) cause the
+    ///         <see cref="LayoutSubview(View, Size)"/> and <see cref="OnDrawContent(Rectangle)"/> methods to be called.
+    ///     </para>
+    /// </remarks>
+    public virtual Rectangle Viewport
+    {
+        get
+        {
+#if DEBUG
+            if (LayoutStyle == LayoutStyle.Computed && !IsInitialized)
+            {
+                Debug.WriteLine (
+                                 $"WARNING: Viewport is being accessed before the View has been initialized. This is likely a bug in {this}"
+                                );
+            }
+#endif // DEBUG
+
+            if (Margin is null || Border is null || Padding is null)
+            {
+                // CreateAdornments has not been called yet.
+                return new (_viewportLocation, Frame.Size);
+            }
+
+            Thickness thickness = GetAdornmentsThickness ();
+
+            return new (
+                        _viewportLocation,
+                        new (
+                             Math.Max (0, Frame.Size.Width - thickness.Horizontal),
+                             Math.Max (0, Frame.Size.Height - thickness.Vertical)
+                            ));
+        }
+        set => SetViewport (value);
+    }
+
+    private void SetViewport (Rectangle viewport)
+    {
+        Rectangle oldViewport = viewport;
+        ApplySettings (ref viewport);
+
+        Thickness thickness = GetAdornmentsThickness ();
+
+        Size newSize = new (
+                            viewport.Size.Width + thickness.Horizontal,
+                            viewport.Size.Height + thickness.Vertical);
+
+        if (newSize == Frame.Size)
+        {
+            // The change is not changing the Frame, so we don't need to update it.
+            // Just call SetNeedsLayout to update the layout.
+            if (_viewportLocation != viewport.Location)
+            {
+                _viewportLocation = viewport.Location;
+                SetNeedsLayout ();
+            }
+
+            OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport));
+
+            return;
+        }
+
+        _viewportLocation = viewport.Location;
+
+        // Update the Frame because we made it bigger or smaller which impacts subviews.
+        Frame = Frame with
+        {
+            Size = newSize
+        };
+
+        void ApplySettings (ref Rectangle newViewport)
+        {
+            if (!ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth))
+            {
+                if (newViewport.X >= ContentSize.Width)
+                {
+                    newViewport.X = ContentSize.Width - 1;
+                }
+            }
+
+            // IMPORTANT: Check for negative location AFTER checking for location greater than content width
+            if (!ViewportSettings.HasFlag (ViewportSettings.AllowNegativeX))
+            {
+                if (newViewport.X < 0)
+                {
+                    newViewport.X = 0;
+                }
+            }
+
+            if (!ViewportSettings.HasFlag (ViewportSettings.AllowYGreaterThanContentHeight))
+            {
+                if (newViewport.Y >= ContentSize.Height)
+                {
+                    newViewport.Y = ContentSize.Height - 1;
+                }
+            }
+
+            // IMPORTANT: Check for negative location AFTER checking for location greater than content width
+            if (!ViewportSettings.HasFlag (ViewportSettings.AllowNegativeY))
+            {
+                if (newViewport.Y < 0)
+                {
+                    newViewport.Y = 0;
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Fired when the <see cref="Viewport"/> changes. This event is fired after the <see cref="Viewport"/> has been updated.
+    /// </summary>
+    [CanBeNull]
+    public event EventHandler<DrawEventArgs> ViewportChanged;
+
+    /// <summary>
+    ///     Called when the <see cref="Viewport"/> changes. Invokes the <see cref="ViewportChanged"/> event.
+    /// </summary>
+    /// <param name="e"></param>
+    protected virtual void OnViewportChanged (DrawEventArgs e) { ViewportChanged?.Invoke (this, e); }
+
+    /// <summary>
+    ///     Converts a <see cref="Viewport"/>-relative location to a screen-relative location.
+    /// </summary>
+    /// <remarks>
+    ///     Viewport-relative means relative to the top-left corner of the inner rectangle of the <see cref="Padding"/>.
+    /// </remarks>
+    public Rectangle ViewportToScreen (in Rectangle location)
+    {
+        // Translate bounds to Frame (our SuperView's Viewport-relative coordinates)
+        Rectangle screen = FrameToScreen ();
+        Point viewportOffset = GetViewportOffsetFromFrame ();
+        screen.Offset (viewportOffset.X + location.X, viewportOffset.Y + location.Y);
+
+        return new (screen.Location, location.Size);
+    }
+
+    /// <summary>Converts a screen-relative coordinate to a Viewport-relative coordinate.</summary>
+    /// <returns>The coordinate relative to this view's <see cref="Viewport"/>.</returns>
+    /// <remarks>
+    ///     Viewport-relative means relative to the top-left corner of the inner rectangle of the <see cref="Padding"/>.
+    /// </remarks>
+    /// <param name="x">Column relative to the left side of the Viewport.</param>
+    /// <param name="y">Row relative to the top of the Viewport</param>
+    public Point ScreenToViewport (int x, int y)
+    {
+        Point viewportOffset = GetViewportOffsetFromFrame ();
+        Point screen = ScreenToFrame (x, y);
+        screen.Offset (-viewportOffset.X, -viewportOffset.Y);
+
+        return screen;
+    }
+
+    /// <summary>
+    ///     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 GetViewportOffsetFromFrame () { return Padding is null ? Point.Empty : Padding.Thickness.GetInside (Padding.Frame).Location; }
+
+    /// <summary>
+    ///     Scrolls the view vertically by the specified number of rows.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///     </para>
+    /// </remarks>
+    /// <param name="rows"></param>
+    /// <returns><see langword="true"/> if the <see cref="Viewport"/> was changed.</returns>
+    public bool? ScrollVertical (int rows)
+    {
+        if (ContentSize == Size.Empty || ContentSize == Viewport.Size)
+        {
+            return false;
+        }
+
+        Viewport = Viewport with { Y = Viewport.Y + rows };
+
+        return true;
+    }
+
+    /// <summary>
+    ///     Scrolls the view horizontally by the specified number of columns.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///     </para>
+    /// </remarks>
+    /// <param name="cols"></param>
+    /// <returns><see langword="true"/> if the <see cref="Viewport"/> was changed.</returns>
+    public bool? ScrollHorizontal (int cols)
+    {
+        if (ContentSize == Size.Empty || ContentSize == Viewport.Size)
+        {
+            return false;
+        }
+
+        Viewport = Viewport with { X = Viewport.X + cols };
+
+        return true;
+    }
+
+    #endregion Viewport
+}

+ 181 - 106
Terminal.Gui/View/ViewDrawing.cs

@@ -1,4 +1,6 @@
-namespace Terminal.Gui;
+using System.Drawing;
+
+namespace Terminal.Gui;
 
 
 public partial class View
 public partial class View
 {
 {
@@ -54,67 +56,108 @@ public partial class View
     public bool SubViewNeedsDisplay { get; private set; }
     public bool SubViewNeedsDisplay { get; private set; }
 
 
     /// <summary>
     /// <summary>
-    ///     Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for rendering any border
-    ///     lines. If <see langword="true"/> the rendering of any borders drawn by this Frame will be done by it's parent's
+    ///     Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for rendering any 
+    ///     lines. If <see langword="true"/> the rendering of any borders drawn by this Frame will be done by its parent's
     ///     SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawAdornments"/> method will be
     ///     SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawAdornments"/> method will be
     ///     called to render the borders.
     ///     called to render the borders.
     /// </summary>
     /// </summary>
     public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
     public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
 
 
-    /// <summary>Displays the specified character in the specified column and row of the View.</summary>
-    /// <param name="col">Column (view-relative).</param>
-    /// <param name="row">Row (view-relative).</param>
-    /// <param name="ch">Ch.</param>
-    public void AddRune (int col, int row, Rune ch)
+    /// <summary>Draws the specified character in the specified viewport-relative column and row of the View.</summary>
+    /// <para>
+    ///     If the provided coordinates are outside the visible content area, this method does nothing.
+    /// </para>
+    /// <remarks>
+    ///     The top-left corner of the visible content area is <c>ViewPort.Location</c>.
+    /// </remarks>
+    /// <param name="col">Column (viewport-relative).</param>
+    /// <param name="row">Row (viewport-relative).</param>
+    /// <param name="rune">The Rune.</param>
+    public void AddRune (int col, int row, Rune rune)
     {
     {
-        if (row < 0 || col < 0)
+        if (Move (col, row))
         {
         {
-            return;
+            Driver.AddRune (rune);
         }
         }
+    }
 
 
-        if (row > _frame.Height - 1 || col > _frame.Width - 1)
+    /// <summary>Clears <see cref="Viewport"/> with the normal background.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         If <see cref="ViewportSettings"/> has <see cref="Gui.ViewportSettings.ClearContentOnly"/> only
+    ///         the portion of the content
+    ///         area that is visible within the <see cref="View.Viewport"/> will be cleared. This is useful for views that have a
+    ///         content area larger than the Viewport (e.g. when <see cref="ViewportSettings.AllowNegativeLocation"/> is
+    ///         enabled) and want
+    ///         the area outside the content to be visually distinct.
+    ///     </para>
+    /// </remarks>
+    public void Clear ()
+    {
+        if (Driver is null)
         {
         {
             return;
             return;
         }
         }
 
 
-        Move (col, row);
-        Driver.AddRune (ch);
-    }
+        // Get screen-relative coords
+        Rectangle toClear = ViewportToScreen (Viewport with { Location = new (0, 0) });
+
+        Rectangle prevClip = Driver.Clip;
+
+        if (ViewportSettings.HasFlag (ViewportSettings.ClearContentOnly))
+        {
+            Rectangle visibleContent = ViewportToScreen (new (new (-Viewport.X, -Viewport.Y), ContentSize));
+            toClear = Rectangle.Intersect (toClear, visibleContent);
+        }
 
 
-    /// <summary>Clears <see cref="Bounds"/> with the normal background.</summary>
-    /// <remarks></remarks>
-    public void Clear () { Clear (Bounds); }
+        Attribute prev = Driver.SetAttribute (GetNormalColor());
+        Driver.FillRect (toClear);
+        Driver.SetAttribute (prev);
+
+        Driver.Clip = prevClip;
+    }
 
 
-    /// <summary>Clears the specified <see cref="Bounds"/>-relative rectangle with the normal background.</summary>
-    /// <remarks></remarks>
-    /// <param name="contentArea">The Bounds-relative rectangle to clear.</param>
-    public void Clear (Rectangle contentArea)
+    /// <summary>Fills the specified <see cref="Viewport"/>-relative rectangle with the specified color.</summary>
+    /// <param name="rect">The Viewport-relative rectangle to clear.</param>
+    /// <param name="color">The color to use to fill the rectangle. If not provided, the Normal background color will be used.</param>
+    public void FillRect (Rectangle rect, Color? color = null)
     {
     {
         if (Driver is null)
         if (Driver is null)
         {
         {
             return;
             return;
         }
         }
 
 
-        Attribute prev = Driver.SetAttribute (GetNormalColor ());
+        // Get screen-relative coords
+        Rectangle toClear = ViewportToScreen (rect);
+
+        Rectangle prevClip = Driver.Clip;
+
+        Driver.Clip = Rectangle.Intersect (prevClip, ViewportToScreen (Viewport with { Location = new (0, 0) }));
 
 
-        // Clamp the region to the bounds of the view
-        contentArea = Rectangle.Intersect (contentArea, Bounds);
-        Driver.FillRect (BoundsToScreen (contentArea));
+        Attribute prev = Driver.SetAttribute (new (color ?? GetNormalColor().Background));
+        Driver.FillRect (toClear);
         Driver.SetAttribute (prev);
         Driver.SetAttribute (prev);
+
+        Driver.Clip = prevClip;
     }
     }
 
 
-    /// <summary>Expands the <see cref="ConsoleDriver"/>'s clip region to include <see cref="Bounds"/>.</summary>
+    /// <summary>Sets the <see cref="ConsoleDriver"/>'s clip region to <see cref="Viewport"/>.</summary>
+    /// <remarks>
+    /// <para>
+    ///     By default, the clip rectangle is set to the intersection of the current clip region and the
+    ///     <see cref="Viewport"/>. This ensures that drawing is constrained to the viewport, but allows
+    ///     content to be drawn beyond the viewport.
+    /// </para>
+    /// <para>
+    ///     If <see cref="ViewportSettings"/> has <see cref="Gui.ViewportSettings.ClipContentOnly"/> set, clipping will be
+    ///     applied to just the visible content area.
+    /// </para>
+    /// </remarks>
     /// <returns>
     /// <returns>
     ///     The current screen-relative clip region, which can be then re-applied by setting
     ///     The current screen-relative clip region, which can be then re-applied by setting
     ///     <see cref="ConsoleDriver.Clip"/>.
     ///     <see cref="ConsoleDriver.Clip"/>.
     /// </returns>
     /// </returns>
-    /// <remarks>
-    ///     <para>
-    ///         If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to
-    ///         <see cref="Rectangle.Empty"/>.
-    ///     </para>
-    /// </remarks>
-    public Rectangle ClipToBounds ()
+    public Rectangle SetClip ()
     {
     {
         if (Driver is null)
         if (Driver is null)
         {
         {
@@ -122,7 +165,18 @@ public partial class View
         }
         }
 
 
         Rectangle previous = Driver.Clip;
         Rectangle previous = Driver.Clip;
-        Driver.Clip = Rectangle.Intersect (previous, BoundsToScreen (Bounds));
+
+        // Clamp the Clip to the entire visible area
+        Rectangle clip = Rectangle.Intersect (ViewportToScreen (Viewport with { Location = Point.Empty }), previous);
+
+        if (ViewportSettings.HasFlag (ViewportSettings.ClipContentOnly))
+        {
+            // Clamp the Clip to the just content area that is within the viewport
+            Rectangle visibleContent = ViewportToScreen (new (new (-Viewport.X, -Viewport.Y), ContentSize));
+            clip = Rectangle.Intersect (clip, visibleContent);
+        }
+
+        Driver.Clip = clip;
 
 
         return previous;
         return previous;
     }
     }
@@ -133,7 +187,7 @@ public partial class View
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
-    ///         Always use <see cref="Bounds"/> (view-relative) when calling <see cref="OnDrawContent(Rectangle)"/>, NOT
+    ///         Always use <see cref="Viewport"/> (view-relative) when calling <see cref="OnDrawContent(Rectangle)"/>, NOT
     ///         <see cref="Frame"/> (superview-relative).
     ///         <see cref="Frame"/> (superview-relative).
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
@@ -142,7 +196,8 @@ public partial class View
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         Overrides of <see cref="OnDrawContent(Rectangle)"/> must ensure they do not set <c>Driver.Clip</c> to a clip
     ///         Overrides of <see cref="OnDrawContent(Rectangle)"/> must ensure they do not set <c>Driver.Clip</c> to a clip
-    ///         region larger than the <ref name="Bounds"/> property, as this will cause the driver to clip the entire region.
+    ///         region larger than the <ref name="Viewport"/> property, as this will cause the driver to clip the entire
+    ///         region.
     ///     </para>
     ///     </para>
     /// </remarks>
     /// </remarks>
     public void Draw ()
     public void Draw ()
@@ -154,21 +209,24 @@ public partial class View
 
 
         OnDrawAdornments ();
         OnDrawAdornments ();
 
 
-        Rectangle prevClip = ClipToBounds ();
-
         if (ColorScheme is { })
         if (ColorScheme is { })
         {
         {
             //Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
             //Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
             Driver?.SetAttribute (GetNormalColor ());
             Driver?.SetAttribute (GetNormalColor ());
         }
         }
 
 
+        // By default, we clip to the viewport preventing drawing outside the viewport
+        // We also clip to the content, but if a developer wants to draw outside the viewport, they can do
+        // so via settings. SetClip honors the ViewportSettings.DisableVisibleContentClipping flag.
+        Rectangle prevClip = SetClip ();
+
         // Invoke DrawContentEvent
         // Invoke DrawContentEvent
-        var dev = new DrawEventArgs (Bounds);
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
         DrawContent?.Invoke (this, dev);
         DrawContent?.Invoke (this, dev);
 
 
         if (!dev.Cancel)
         if (!dev.Cancel)
         {
         {
-            OnDrawContent (Bounds);
+            OnDrawContent (Viewport);
         }
         }
 
 
         if (Driver is { })
         if (Driver is { })
@@ -179,7 +237,7 @@ public partial class View
         OnRenderLineCanvas ();
         OnRenderLineCanvas ();
 
 
         // Invoke DrawContentCompleteEvent
         // Invoke DrawContentCompleteEvent
-        OnDrawContentComplete (Bounds);
+        OnDrawContentComplete (Viewport);
 
 
         // BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details.
         // BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details.
         ClearLayoutNeeded ();
         ClearLayoutNeeded ();
@@ -264,14 +322,13 @@ public partial class View
 
 
     /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
     /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
     /// <returns>
     /// <returns>
-    ///     <see cref="Terminal.Gui.ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/> or
-    ///     <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>. If it's
+    ///     <see cref="ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/> or
+    ///     <see cref="ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>. If it's
     ///     overridden can return other values.
     ///     overridden can return other values.
     /// </returns>
     /// </returns>
     public virtual Attribute GetFocusColor ()
     public virtual Attribute GetFocusColor ()
     {
     {
         ColorScheme cs = ColorScheme;
         ColorScheme cs = ColorScheme;
-
         if (ColorScheme is null)
         if (ColorScheme is null)
         {
         {
             cs = new ();
             cs = new ();
@@ -316,19 +373,33 @@ public partial class View
         return Enabled ? cs.Normal : cs.Disabled;
         return Enabled ? cs.Normal : cs.Disabled;
     }
     }
 
 
-    /// <summary>This moves the cursor to the specified column and row in the view.</summary>
-    /// <returns>The move.</returns>
-    /// <param name="col">The column to move to, in view-relative coordinates.</param>
-    /// <param name="row">the row to move to, in view-relative coordinates.</param>
-    public void Move (int col, int row)
+    /// <summary>Moves the drawing cursor to the specified <see cref="Viewport"/>-relative location in the view.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         If the provided coordinates are outside the visible content area, this method does nothing.
+    ///     </para>
+    ///     <para>
+    ///         The top-left corner of the visible content area is <c>ViewPort.Location</c>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="col">Column (viewport-relative).</param>
+    /// <param name="row">Row (viewport-relative).</param>
+    public bool Move (int col, int row)
     {
     {
         if (Driver is null || Driver?.Rows == 0)
         if (Driver is null || Driver?.Rows == 0)
         {
         {
-            return;
+            return false;
         }
         }
 
 
-        Rectangle screen = BoundsToScreen (new (col, row, 0, 0));
+        if (col < 0 || row < 0 || col >= Viewport.Width || row >= Viewport.Height)
+        {
+            return false;
+        }
+
+        Rectangle screen = ViewportToScreen (new (col, row, 0, 0));
         Driver?.Move (screen.X, screen.Y);
         Driver?.Move (screen.X, screen.Y);
+
+        return true;
     }
     }
 
 
     // TODO: Make this cancelable
     // TODO: Make this cancelable
@@ -347,26 +418,51 @@ public partial class View
 
 
         // Each of these renders lines to either this View's LineCanvas 
         // Each of these renders lines to either this View's LineCanvas 
         // Those lines will be finally rendered in OnRenderLineCanvas
         // Those lines will be finally rendered in OnRenderLineCanvas
-        Margin?.OnDrawContent (Margin.Bounds);
-        Border?.OnDrawContent (Border.Bounds);
-        Padding?.OnDrawContent (Padding.Bounds);
+        Margin?.OnDrawContent (Margin.Viewport);
+        Border?.OnDrawContent (Border.Viewport);
+        Padding?.OnDrawContent (Padding.Viewport);
 
 
         return true;
         return true;
     }
     }
 
 
-    /// <summary>Enables overrides to draw infinitely scrolled content and/or a background behind added controls.</summary>
-    /// <param name="contentArea">
-    ///     The view-relative rectangle describing the currently visible viewport into the
-    ///     <see cref="View"/>
+    /// <summary>
+    ///     Draws the view's content, including Subviews.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         The <paramref name="viewport"/> parameter is provided as a convenience; it has the same values as the
+    ///         <see cref="Viewport"/> property.
+    ///     </para>
+    ///     <para>
+    ///         The <see cref="Viewport"/> Location and Size indicate what part of the View's content, defined
+    ///         by <see cref="ContentSize"/>, is visible and should be drawn. The coordinates taken by <see cref="Move"/> and
+    ///         <see cref="AddRune"/> are relative to <see cref="Viewport"/>, thus if <c>ViewPort.Location.Y</c> is <c>5</c>
+    ///         the 6th row of the content should be drawn using <c>MoveTo (x, 5)</c>.
+    ///     </para>
+    ///     <para>
+    ///         If <see cref="ContentSize"/> is larger than <c>ViewPort.Size</c> drawing code should use <see cref="Viewport"/>
+    ///         to constrain drawing for better performance.
+    ///     </para>
+    ///     <para>
+    ///         The <see cref="ConsoleDriver.Clip"/> may define smaller area than <see cref="Viewport"/>; complex drawing code
+    ///         can be more
+    ///         efficient by using <see cref="ConsoleDriver.Clip"/> to constrain drawing for better performance.
+    ///     </para>
+    ///     <para>
+    ///         Overrides should loop through the subviews and call <see cref="Draw"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="viewport">
+    ///     The rectangle describing the currently visible viewport into the <see cref="View"/>; has the same value as
+    ///     <see cref="Viewport"/>.
     /// </param>
     /// </param>
-    /// <remarks>This method will be called before any subviews added with <see cref="Add(View)"/> have been drawn.</remarks>
-    public virtual void OnDrawContent (Rectangle contentArea)
+    public virtual void OnDrawContent (Rectangle viewport)
     {
     {
         if (NeedsDisplay)
         if (NeedsDisplay)
         {
         {
             if (SuperView is { })
             if (SuperView is { })
             {
             {
-                Clear (contentArea);
+                Clear ();
             }
             }
 
 
             if (!string.IsNullOrEmpty (TextFormatter.Text))
             if (!string.IsNullOrEmpty (TextFormatter.Text))
@@ -378,8 +474,11 @@ public partial class View
             }
             }
 
 
             // This should NOT clear 
             // This should NOT clear 
+            // TODO: If the output is not in the Viewport, do nothing
+            var drawRect = new Rectangle (ContentToScreen (Point.Empty), ContentSize);
+
             TextFormatter?.Draw (
             TextFormatter?.Draw (
-                                 BoundsToScreen (contentArea),
+                                 drawRect,
                                  HasFocus ? GetFocusColor () : GetNormalColor (),
                                  HasFocus ? GetFocusColor () : GetNormalColor (),
                                  HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
                                  HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
                                  Rectangle.Empty
                                  Rectangle.Empty
@@ -387,6 +486,7 @@ public partial class View
             SetSubViewNeedsDisplay ();
             SetSubViewNeedsDisplay ();
         }
         }
 
 
+        // TODO: Move drawing of subviews to a separate OnDrawSubviews virtual method
         // Draw subviews
         // Draw subviews
         // TODO: Implement OnDrawSubviews (cancelable);
         // TODO: Implement OnDrawSubviews (cancelable);
         if (_subviews is { } && SubViewNeedsDisplay)
         if (_subviews is { } && SubViewNeedsDisplay)
@@ -395,39 +495,25 @@ public partial class View
                                                                      view => view.Visible
                                                                      view => view.Visible
                                                                              && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
                                                                              && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
                                                                     );
                                                                     );
-
             foreach (View view in subviewsNeedingDraw)
             foreach (View view in subviewsNeedingDraw)
             {
             {
-                //view.Frame.IntersectsWith (bounds)) {
-                // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
                 if (view.LayoutNeeded)
                 if (view.LayoutNeeded)
                 {
                 {
                     view.LayoutSubviews ();
                     view.LayoutSubviews ();
                 }
                 }
-
-                // Draw the subview
-                // Use the view's bounds (view-relative; Location will always be (0,0)
-                //if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
                 view.Draw ();
                 view.Draw ();
-
-                //}
             }
             }
         }
         }
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed
-    ///     controls.
+    ///     Called after <see cref="OnDrawContent"/> to enable overrides.
     /// </summary>
     /// </summary>
-    /// <param name="contentArea">
-    ///     The view-relative rectangle describing the currently visible viewport into the
+    /// <param name="viewport">
+    ///     The viewport-relative rectangle describing the currently visible viewport into the
     ///     <see cref="View"/>
     ///     <see cref="View"/>
     /// </param>
     /// </param>
-    /// <remarks>
-    ///     This method will be called after any subviews removed with <see cref="Remove(View)"/> have been completed
-    ///     drawing.
-    /// </remarks>
-    public virtual void OnDrawContentComplete (Rectangle contentArea) { DrawContentComplete?.Invoke (this, new (contentArea)); }
+    public virtual void OnDrawContentComplete (Rectangle viewport) { DrawContentComplete?.Invoke (this, new (viewport, Rectangle.Empty)); }
 
 
     // TODO: Make this cancelable
     // TODO: Make this cancelable
     /// <summary>
     /// <summary>
@@ -438,13 +524,13 @@ public partial class View
     /// <returns></returns>
     /// <returns></returns>
     public virtual bool OnRenderLineCanvas ()
     public virtual bool OnRenderLineCanvas ()
     {
     {
-        if (!IsInitialized)
+        if (!IsInitialized || Driver is null)
         {
         {
             return false;
             return false;
         }
         }
 
 
         // If we have a SuperView, it'll render our frames.
         // If we have a SuperView, it'll render our frames.
-        if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rectangle.Empty)
+        if (!SuperViewRendersLineCanvas && LineCanvas.Viewport != Rectangle.Empty)
         {
         {
             foreach (KeyValuePair<Point, Cell> p in LineCanvas.GetCellMap ())
             foreach (KeyValuePair<Point, Cell> p in LineCanvas.GetCellMap ())
             {
             {
@@ -484,7 +570,7 @@ public partial class View
         return true;
         return true;
     }
     }
 
 
-    /// <summary>Sets the area of this view needing to be redrawn to <see cref="Bounds"/>.</summary>
+    /// <summary>Sets the area of this view needing to be redrawn to <see cref="Viewport"/>.</summary>
     /// <remarks>
     /// <remarks>
     ///     If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), this method
     ///     If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), this method
     ///     does nothing.
     ///     does nothing.
@@ -493,21 +579,28 @@ public partial class View
     {
     {
         if (IsInitialized)
         if (IsInitialized)
         {
         {
-            SetNeedsDisplay (Bounds);
+            SetNeedsDisplay (Viewport);
         }
         }
     }
     }
 
 
     /// <summary>Expands the area of this view needing to be redrawn to include <paramref name="region"/>.</summary>
     /// <summary>Expands the area of this view needing to be redrawn to include <paramref name="region"/>.</summary>
     /// <remarks>
     /// <remarks>
-    ///     If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), the area to be
-    ///     redrawn will be the <paramref name="region"/>.
+    ///     <para>
+    ///         The location of <paramref name="region"/> is relative to the View's content, bound by <c>Size.Empty</c> and
+    ///         <see cref="ContentSize"/>.
+    ///     </para>
+    ///     <para>
+    ///         If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), the area to be
+    ///         redrawn will be the <paramref name="region"/>.
+    ///     </para>
     /// </remarks>
     /// </remarks>
-    /// <param name="region">The Bounds-relative region that needs to be redrawn.</param>
+    /// <param name="region">The content-relative region that needs to be redrawn.</param>
     public void SetNeedsDisplay (Rectangle region)
     public void SetNeedsDisplay (Rectangle region)
     {
     {
         if (!IsInitialized)
         if (!IsInitialized)
         {
         {
             _needsDisplayRect = region;
             _needsDisplayRect = region;
+
             return;
             return;
         }
         }
 
 
@@ -572,25 +665,7 @@ public partial class View
 
 
         foreach (View subview in Subviews)
         foreach (View subview in Subviews)
         {
         {
-            subview.ClearNeedsDisplay();
+            subview.ClearNeedsDisplay ();
         }
         }
     }
     }
-
-    // INTENT: Isn't this just intersection? It isn't used anyway.
-    // Clips a rectangle in screen coordinates to the dimensions currently available on the screen
-    internal Rectangle ScreenClip (Rectangle regionScreen)
-    {
-        int x = regionScreen.X < 0 ? 0 : regionScreen.X;
-        int y = regionScreen.Y < 0 ? 0 : regionScreen.Y;
-
-        int w = regionScreen.X + regionScreen.Width >= Driver.Cols
-                    ? Driver.Cols - regionScreen.X
-                    : regionScreen.Width;
-
-        int h = regionScreen.Y + regionScreen.Height >= Driver.Rows
-                    ? Driver.Rows - regionScreen.Y
-                    : regionScreen.Height;
-
-        return new (x, y, w, h);
-    }
 }
 }

+ 24 - 9
Terminal.Gui/View/ViewEventArgs.cs

@@ -3,8 +3,8 @@
 /// <summary>Args for events that relate to specific <see cref="View"/></summary>
 /// <summary>Args for events that relate to specific <see cref="View"/></summary>
 public class ViewEventArgs : EventArgs
 public class ViewEventArgs : EventArgs
 {
 {
-    /// <summary>Creates a new instance of the <see cref="Terminal.Gui.View"/> class.</summary>
-    /// <param name="view"></param>
+    /// <summary>Creates a new instance of the <see cref="Terminal.Gui.ViewEventArgs"/> class.</summary>
+    /// <param name="view">The view that the event is about.</param>
     public ViewEventArgs (View view) { View = view; }
     public ViewEventArgs (View view) { View = view; }
 
 
     /// <summary>The view that the event is about.</summary>
     /// <summary>The view that the event is about.</summary>
@@ -18,25 +18,40 @@ public class ViewEventArgs : EventArgs
 /// <summary>Event arguments for the <see cref="View.LayoutComplete"/> event.</summary>
 /// <summary>Event arguments for the <see cref="View.LayoutComplete"/> event.</summary>
 public class LayoutEventArgs : EventArgs
 public class LayoutEventArgs : EventArgs
 {
 {
-    /// <summary>The view-relative bounds of the <see cref="View"/> before it was laid out.</summary>
-    public Rectangle OldBounds { get; set; }
+    /// <summary>Creates a new instance of the <see cref="Terminal.Gui.LayoutEventArgs"/> class.</summary>
+    /// <param name="oldContentSize">The view that the event is about.</param>
+    public LayoutEventArgs (Size oldContentSize) { OldContentSize = oldContentSize; }
+
+    /// <summary>The viewport of the <see cref="View"/> before it was laid out.</summary>
+    public Size OldContentSize { get; set; }
 }
 }
 
 
 /// <summary>Event args for draw events</summary>
 /// <summary>Event args for draw events</summary>
 public class DrawEventArgs : EventArgs
 public class DrawEventArgs : EventArgs
 {
 {
     /// <summary>Creates a new instance of the <see cref="DrawEventArgs"/> class.</summary>
     /// <summary>Creates a new instance of the <see cref="DrawEventArgs"/> class.</summary>
-    /// <param name="rect">
-    ///     Gets the view-relative rectangle describing the currently visible viewport into the
+    /// <param name="newViewport">
+    ///     The Content-relative rectangle describing the new visible viewport into the
+    ///     <see cref="View"/>.
+    /// </param>
+    /// <param name="oldViewport">
+    ///     The Content-relative rectangle describing the old visible viewport into the
     ///     <see cref="View"/>.
     ///     <see cref="View"/>.
     /// </param>
     /// </param>
-    public DrawEventArgs (Rectangle rect) { Rectangle = rect; }
+    public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport)
+    {
+        NewViewport = newViewport;
+        OldViewport = oldViewport;
+    }
 
 
     /// <summary>If set to true, the draw operation will be canceled, if applicable.</summary>
     /// <summary>If set to true, the draw operation will be canceled, if applicable.</summary>
     public bool Cancel { get; set; }
     public bool Cancel { get; set; }
 
 
-    /// <summary>Gets the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.</summary>
-    public Rectangle Rectangle { get; }
+    /// <summary>Gets the Content-relative rectangle describing the old visible viewport into the <see cref="View"/>.</summary>
+    public Rectangle OldViewport { get; }
+
+    /// <summary>Gets the Content-relative rectangle describing the currently visible viewport into the <see cref="View"/>.</summary>
+    public Rectangle NewViewport { get; }
 }
 }
 
 
 /// <summary>Defines the event arguments for <see cref="View.SetFocus()"/></summary>
 /// <summary>Defines the event arguments for <see cref="View.SetFocus()"/></summary>

+ 28 - 24
Terminal.Gui/View/ViewMouse.cs

@@ -21,12 +21,12 @@ public enum HighlightStyle
 #endif
 #endif
 
 
     /// <summary>
     /// <summary>
-    /// The mouse is pressed within the <see cref="View.Bounds"/>.
+    /// The mouse is pressed within the <see cref="View.Viewport"/>.
     /// </summary>
     /// </summary>
     Pressed = 2,
     Pressed = 2,
 
 
     /// <summary>
     /// <summary>
-    /// The mouse is pressed but moved outside the <see cref="View.Bounds"/>.
+    /// The mouse is pressed but moved outside the <see cref="View.Viewport"/>.
     /// </summary>
     /// </summary>
     PressedOutside = 4
     PressedOutside = 4
 }
 }
@@ -36,6 +36,10 @@ public enum HighlightStyle
 /// </summary>
 /// </summary>
 public class HighlightEventArgs : CancelEventArgs
 public class HighlightEventArgs : CancelEventArgs
 {
 {
+    /// <summary>
+    /// Constructs a new instance of <see cref="HighlightEventArgs"/>.
+    /// </summary>
+    /// <param name="style"></param>
     public HighlightEventArgs (HighlightStyle style)
     public HighlightEventArgs (HighlightStyle style)
     {
     {
         HighlightStyle = style;
         HighlightStyle = style;
@@ -63,7 +67,7 @@ public partial class View
     public virtual bool WantMousePositionReports { get; set; }
     public virtual bool WantMousePositionReports { get; set; }
 
 
     /// <summary>
     /// <summary>
-    ///     Called by <see cref="Application.OnMouseEvent"/> when the mouse enters <see cref="Bounds"/>. The view will
+    ///     Called by <see cref="Application.OnMouseEvent"/> when the mouse enters <see cref="Viewport"/>. The view will
     ///     then receive mouse events until <see cref="NewMouseLeaveEvent"/> is called indicating the mouse has left
     ///     then receive mouse events until <see cref="NewMouseLeaveEvent"/> is called indicating the mouse has left
     ///     the view.
     ///     the view.
     /// </summary>
     /// </summary>
@@ -110,7 +114,7 @@ public partial class View
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Called by <see cref="NewMouseEvent"/> when the mouse enters <see cref="Bounds"/>. The view will
+    ///     Called by <see cref="NewMouseEvent"/> when the mouse enters <see cref="Viewport"/>. The view will
     ///     then receive mouse events until <see cref="OnMouseLeave"/> is called indicating the mouse has left
     ///     then receive mouse events until <see cref="OnMouseLeave"/> is called indicating the mouse has left
     ///     the view.
     ///     the view.
     /// </summary>
     /// </summary>
@@ -119,7 +123,7 @@ public partial class View
     /// Override this method or subscribe to <see cref="MouseEnter"/> to change the default enter behavior.
     /// Override this method or subscribe to <see cref="MouseEnter"/> to change the default enter behavior.
     /// </para>
     /// </para>
     /// <para>
     /// <para>
-    ///     The coordinates are relative to <see cref="View.Bounds"/>.
+    ///     The coordinates are relative to <see cref="View.Viewport"/>.
     /// </para>
     /// </para>
     /// </remarks>
     /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <param name="mouseEvent"></param>
@@ -133,12 +137,12 @@ public partial class View
         return args.Handled;
         return args.Handled;
     }
     }
 
 
-    /// <summary>Event fired when the mouse moves into the View's <see cref="Bounds"/>.</summary>
+    /// <summary>Event fired when the mouse moves into the View's <see cref="Viewport"/>.</summary>
     public event EventHandler<MouseEventEventArgs> MouseEnter;
     public event EventHandler<MouseEventEventArgs> MouseEnter;
 
 
 
 
     /// <summary>
     /// <summary>
-    ///     Called by <see cref="Application.OnMouseEvent"/> when the mouse leaves <see cref="Bounds"/>. The view will
+    ///     Called by <see cref="Application.OnMouseEvent"/> when the mouse leaves <see cref="Viewport"/>. The view will
     ///     then no longer receive mouse events.
     ///     then no longer receive mouse events.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
@@ -180,15 +184,15 @@ public partial class View
         return false;
         return false;
     }
     }
     /// <summary>
     /// <summary>
-    ///     Called by <see cref="NewMouseEvent"/> when a mouse leaves <see cref="Bounds"/>. The view will
+    ///     Called by <see cref="NewMouseEvent"/> when a mouse leaves <see cref="Viewport"/>. The view will
     ///     no longer receive mouse events.
     ///     no longer receive mouse events.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
     /// <para>
     /// <para>
-    /// Override this method or subscribe to <see cref="MouseEnter"/> to change the default leave behavior.
+    ///     Override this method or subscribe to <see cref="MouseEnter"/> to change the default leave behavior.
     /// </para>
     /// </para>
     /// <para>
     /// <para>
-    ///     The coordinates are relative to <see cref="View.Bounds"/>.
+    ///     The coordinates are relative to <see cref="View.Viewport"/>.
     /// </para>
     /// </para>
     /// </remarks>
     /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <param name="mouseEvent"></param>
@@ -211,7 +215,7 @@ public partial class View
         return args.Handled;
         return args.Handled;
     }
     }
 
 
-    /// <summary>Event fired when the mouse leaves the View's <see cref="Bounds"/>.</summary>
+    /// <summary>Event fired when the mouse leaves the View's <see cref="Viewport"/>.</summary>
     public event EventHandler<MouseEventEventArgs> MouseLeave;
     public event EventHandler<MouseEventEventArgs> MouseLeave;
 
 
     /// <summary>
     /// <summary>
@@ -227,7 +231,7 @@ public partial class View
     ///         mouse buttons was clicked, it calls <see cref="OnMouseClick"/> to process the click.
     ///         mouse buttons was clicked, it calls <see cref="OnMouseClick"/> to process the click.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
-    ///         See <see cref="SetHighlight"/> and <see cref="DisableHighlight"/> for more information.
+    ///         See <see cref="SetHighlight"/> for more information.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
     ///         If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, the <see cref="OnMouseClick"/> event
     ///         If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, the <see cref="OnMouseClick"/> event
@@ -307,7 +311,7 @@ public partial class View
     ///     </para>
     ///     </para>
     /// </remarks>
     /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <param name="mouseEvent"></param>
-    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
+    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>    
     private bool HandlePressed (MouseEvent mouseEvent)
     private bool HandlePressed (MouseEvent mouseEvent)
     {
     {
         if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
         if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
@@ -325,9 +329,11 @@ public partial class View
                     // Set the focus, but don't invoke Accept
                     // Set the focus, but don't invoke Accept
                     SetFocus ();
                     SetFocus ();
                 }
                 }
+
+                mouseEvent.Handled = true;
             }
             }
 
 
-            if (Bounds.Contains (mouseEvent.X, mouseEvent.Y))
+            if (Viewport.Contains (mouseEvent.X, mouseEvent.Y))
             {
             {
                 if (SetHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None) == true)
                 if (SetHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None) == true)
                 {
                 {
@@ -408,7 +414,7 @@ public partial class View
             }
             }
 
 
             // If mouse is still in bounds, click
             // If mouse is still in bounds, click
-            if (!WantContinuousButtonPressed && Bounds.Contains (mouseEvent.X, mouseEvent.Y))
+            if (!WantContinuousButtonPressed && Viewport.Contains (mouseEvent.X, mouseEvent.Y))
             {
             {
                 return OnMouseClick (new (mouseEvent));
                 return OnMouseClick (new (mouseEvent));
             }
             }
@@ -489,9 +495,9 @@ public partial class View
                     };
                     };
                     ColorScheme = cs;
                     ColorScheme = cs;
                 }
                 }
-
-                return true;
             }
             }
+            // Return false since we don't want to eat the event
+            return false;
         }
         }
 
 
 
 
@@ -526,10 +532,10 @@ public partial class View
         return args.Cancel;
         return args.Cancel;
     }
     }
 
 
-    /// <summary>Called when a mouse event occurs within the view's <see cref="Bounds"/>.</summary>
+    /// <summary>Called when a mouse event occurs within the view's <see cref="Viewport"/>.</summary>
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
-    ///         The coordinates are relative to <see cref="View.Bounds"/>.
+    ///         The coordinates are relative to <see cref="View.Viewport"/>.
     ///     </para>
     ///     </para>
     /// </remarks>
     /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <param name="mouseEvent"></param>
@@ -546,7 +552,7 @@ public partial class View
     /// <summary>Event fired when a mouse event occurs.</summary>
     /// <summary>Event fired when a mouse event occurs.</summary>
     /// <remarks>
     /// <remarks>
     ///     <para>
     ///     <para>
-    ///         The coordinates are relative to <see cref="View.Bounds"/>.
+    ///         The coordinates are relative to <see cref="View.Viewport"/>.
     ///     </para>
     ///     </para>
     /// </remarks>
     /// </remarks>
     public event EventHandler<MouseEventEventArgs> MouseEvent;
     public event EventHandler<MouseEventEventArgs> MouseEvent;
@@ -564,9 +570,7 @@ public partial class View
         if (!Enabled)
         if (!Enabled)
         {
         {
             // QUESTION: Is this right? Should a disabled view eat mouse clicks?
             // QUESTION: Is this right? Should a disabled view eat mouse clicks?
-            args.Handled = true;
-
-            return true;
+            return args.Handled = true;
         }
         }
 
 
         MouseClick?.Invoke (this, args);
         MouseClick?.Invoke (this, args);
@@ -592,7 +596,7 @@ public partial class View
     ///         <see cref="MouseEvent.Flags"/> to see which button was clicked.
     ///         <see cref="MouseEvent.Flags"/> to see which button was clicked.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
-    ///         The coordinates are relative to <see cref="View.Bounds"/>.
+    ///         The coordinates are relative to <see cref="View.Viewport"/>.
     ///     </para>
     ///     </para>
     /// </remarks>
     /// </remarks>
     public event EventHandler<MouseEventEventArgs> MouseClick;
     public event EventHandler<MouseEventEventArgs> MouseClick;

+ 12 - 7
Terminal.Gui/View/ViewSubViews.cs

@@ -234,7 +234,15 @@ public partial class View
         }
         }
     }
     }
 
 
-    /// <summary>Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.</summary>
+    /// <summary>
+    /// Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    ///     Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the Subview's
+    ///     lifecycle to be transferred to the caller; the caller must call <see cref="Dispose"/> on any Views that were added.
+    /// </para>
+    /// </remarks>
     public virtual void RemoveAll ()
     public virtual void RemoveAll ()
     {
     {
         if (_subviews is null)
         if (_subviews is null)
@@ -485,7 +493,7 @@ public partial class View
         {
         {
             return true;
             return true;
         }
         }
-
+        
         return false;
         return false;
     }
     }
 
 
@@ -855,16 +863,13 @@ public partial class View
             return;
             return;
         }
         }
 
 
-        // BUGBUG: v2 - This needs to support children of Frames too
+        // BUGBUG: v2 - This needs to support Subviews of Adornments too
 
 
         if (Focused is null && SuperView is { })
         if (Focused is null && SuperView is { })
         {
         {
             SuperView.EnsureFocus ();
             SuperView.EnsureFocus ();
         }
         }
-        else if (Focused?.Visible == true
-                 && Focused?.Enabled == true
-                 && Focused?.Frame.Width > 0
-                 && Focused.Frame.Height > 0)
+        else if (Focused is { Visible: true, Enabled: true, Frame: { Width: > 0, Height: > 0 } })
         {
         {
             Focused.PositionCursor ();
             Focused.PositionCursor ();
         }
         }

+ 21 - 21
Terminal.Gui/View/ViewText.cs

@@ -30,10 +30,10 @@ public partial class View
     ///         <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
     ///         <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
-    ///         The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Bounds"/>'s height
+    ///         The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Viewport"/>'s height
     ///         is 1, the text will be clipped.
     ///         is 1, the text will be clipped.
     ///     </para>
     ///     </para>
-    ///     <para>If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.</para>
+    ///     <para>If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Viewport"/> will be adjusted to fit the text.</para>
     ///     <para>When the text changes, the <see cref="TextChanged"/> is fired.</para>
     ///     <para>When the text changes, the <see cref="TextChanged"/> is fired.</para>
     /// </remarks>
     /// </remarks>
     public virtual string Text
     public virtual string Text
@@ -80,7 +80,7 @@ public partial class View
     ///     redisplay the <see cref="View"/>.
     ///     redisplay the <see cref="View"/>.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
-    ///     <para>If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.</para>
+    ///     <para>If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Viewport"/> will be adjusted to fit the text.</para>
     /// </remarks>
     /// </remarks>
     /// <value>The text alignment.</value>
     /// <value>The text alignment.</value>
     public virtual TextAlignment TextAlignment
     public virtual TextAlignment TextAlignment
@@ -99,7 +99,7 @@ public partial class View
     ///     <see cref="View"/>.
     ///     <see cref="View"/>.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
-    ///     <para>If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.</para>
+    ///     <para>If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Viewport"/> will be adjusted to fit the text.</para>
     /// </remarks>
     /// </remarks>
     /// <value>The text alignment.</value>
     /// <value>The text alignment.</value>
     public virtual TextDirection TextDirection
     public virtual TextDirection TextDirection
@@ -120,7 +120,7 @@ public partial class View
     ///     redisplay the <see cref="View"/>.
     ///     redisplay the <see cref="View"/>.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
-    ///     <para>If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.</para>
+    ///     <para>If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Viewport"/> will be adjusted to fit the text.</para>
     /// </remarks>
     /// </remarks>
     /// <value>The text alignment.</value>
     /// <value>The text alignment.</value>
     public virtual VerticalTextAlignment VerticalTextAlignment
     public virtual VerticalTextAlignment VerticalTextAlignment
@@ -134,7 +134,7 @@ public partial class View
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Gets the Frame dimensions required to fit <see cref="Text"/> within <see cref="Bounds"/> using the text
+    ///     Gets the Frame dimensions required to fit <see cref="Text"/> within <see cref="Viewport"/> using the text
     ///     <see cref="NavigationDirection"/> specified by the <see cref="TextFormatter"/> property and accounting for any
     ///     <see cref="NavigationDirection"/> specified by the <see cref="TextFormatter"/> property and accounting for any
     ///     <see cref="HotKeySpecifier"/> characters.
     ///     <see cref="HotKeySpecifier"/> characters.
     /// </summary>
     /// </summary>
@@ -146,8 +146,8 @@ public partial class View
 
 
         if (IsInitialized)
         if (IsInitialized)
         {
         {
-            x = Bounds.X;
-            y = Bounds.Y;
+            x = Viewport.X;
+            y = Viewport.Y;
         }
         }
 
 
         Rectangle rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
         Rectangle rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
@@ -225,7 +225,7 @@ public partial class View
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Internal API. Sets <see cref="TextFormatter"/>.Size to the current <see cref="Bounds"/> size, adjusted for
+    ///     Internal API. Sets <see cref="TextFormatter"/>.Size to the current <see cref="Viewport"/> size, adjusted for
     ///     <see cref="TextFormatter.HotKeySpecifier"/>.
     ///     <see cref="TextFormatter.HotKeySpecifier"/>.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
@@ -244,14 +244,14 @@ public partial class View
 
 
         if (string.IsNullOrEmpty (TextFormatter.Text))
         if (string.IsNullOrEmpty (TextFormatter.Text))
         {
         {
-            TextFormatter.Size = Bounds.Size;
+            TextFormatter.Size = ContentSize;
 
 
             return;
             return;
         }
         }
 
 
         TextFormatter.Size = new (
         TextFormatter.Size = new (
-                                  Bounds.Size.Width + GetHotKeySpecifierLength (),
-                                  Bounds.Size.Height + GetHotKeySpecifierLength (false)
+                                  ContentSize.Width + GetHotKeySpecifierLength (),
+                                  ContentSize.Height + GetHotKeySpecifierLength (false)
                                  );
                                  );
     }
     }
 
 
@@ -304,12 +304,12 @@ public partial class View
             throw new InvalidOperationException ("SetFrameToFitText can only be called when AutoSize is true");
             throw new InvalidOperationException ("SetFrameToFitText can only be called when AutoSize is true");
         }
         }
 
 
-        // BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height
+        // BUGBUG: This API is broken - should not assume Frame.Height == Viewport.Height
         // <summary>
         // <summary>
         // Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
         // Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
         // </summary>
         // </summary>
         // <param name="sizeRequired">The minimum dimensions required.</param>
         // <param name="sizeRequired">The minimum dimensions required.</param>
-        // <returns><see langword="true"/> if the dimensions fit within the View's <see cref="Bounds"/>, <see langword="false"/> otherwise.</returns>
+        // <returns><see langword="true"/> if the dimensions fit within the View's <see cref="Viewport"/>, <see langword="false"/> otherwise.</returns>
         // <remarks>
         // <remarks>
         // Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
         // Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
         // if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
         // if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
@@ -324,7 +324,7 @@ public partial class View
                 return false;
                 return false;
             }
             }
 
 
-            sizeRequired = Bounds.Size;
+            sizeRequired = ContentSize;
 
 
             if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text))
             if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text))
             {
             {
@@ -336,11 +336,11 @@ public partial class View
                 case true:
                 case true:
                     int colWidth = TextFormatter.GetWidestLineLength (new List<string> { TextFormatter.Text }, 0, 1);
                     int colWidth = TextFormatter.GetWidestLineLength (new List<string> { TextFormatter.Text }, 0, 1);
 
 
-                    // TODO: v2 - This uses frame.Width; it should only use Bounds
+                    // TODO: v2 - This uses frame.Width; it should only use Viewport
                     if (_frame.Width < colWidth
                     if (_frame.Width < colWidth
-                        && (Width is null || (Bounds.Width >= 0 && Width is Dim.DimAbsolute && Width.Anchor (0) >= 0 && Width.Anchor (0) < colWidth)))
+                        && (Width is null || (ContentSize.Width >= 0 && Width is Dim.DimAbsolute && Width.Anchor (0) >= 0 && Width.Anchor (0) < colWidth)))
                     {
                     {
-                        sizeRequired = new (colWidth, Bounds.Height);
+                        sizeRequired = new (colWidth, ContentSize.Height);
 
 
                         return true;
                         return true;
                     }
                     }
@@ -349,7 +349,7 @@ public partial class View
                 default:
                 default:
                     if (_frame.Height < 1 && (Height is null || (Height is Dim.DimAbsolute && Height.Anchor (0) == 0)))
                     if (_frame.Height < 1 && (Height is null || (Height is Dim.DimAbsolute && Height.Anchor (0) == 0)))
                     {
                     {
-                        sizeRequired = new (Bounds.Width, 1);
+                        sizeRequired = new (ContentSize.Width, 1);
 
 
                         return true;
                         return true;
                     }
                     }
@@ -365,7 +365,7 @@ public partial class View
             // TODO: This is a hack.
             // TODO: This is a hack.
             //_width  = size.Width;
             //_width  = size.Width;
             //_height = size.Height;
             //_height = size.Height;
-            _frame = new (_frame.Location, size);
+            SetFrame (new (_frame.Location, size));
 
 
             //throw new InvalidOperationException ("This is a hack.");
             //throw new InvalidOperationException ("This is a hack.");
             return true;
             return true;
@@ -392,7 +392,7 @@ public partial class View
         }
         }
         else if (AutoSize && directionChanged && IsAdded)
         else if (AutoSize && directionChanged && IsAdded)
         {
         {
-            ResizeBoundsToFit (Bounds.Size);
+            ResizeViewportToFit (Viewport.Size);
         }
         }
 
 
         SetTextFormatterSize ();
         SetTextFormatterSize ();

+ 8 - 9
Terminal.Gui/Views/Button.cs

@@ -33,9 +33,6 @@ public class Button : View
     private readonly Rune _rightDefault;
     private readonly Rune _rightDefault;
     private bool _isDefault;
     private bool _isDefault;
 
 
-    /// <inheritdoc />
-    private bool _wantContinuousButtonPressed;
-
     /// <summary>Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.</summary>
     /// <summary>Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.</summary>
     /// <remarks>The width of the <see cref="Button"/> is computed based on the text length. The height will always be 1.</remarks>
     /// <remarks>The width of the <see cref="Button"/> is computed based on the text length. The height will always be 1.</remarks>
     public Button ()
     public Button ()
@@ -59,10 +56,10 @@ public class Button : View
 #endif
 #endif
         // Override default behavior of View
         // Override default behavior of View
         AddCommand (Command.HotKey, () =>
         AddCommand (Command.HotKey, () =>
-                                     {
-                                         SetFocus ();
-                                         return !OnAccept ();
-                                     });
+        {
+            SetFocus ();
+            return !OnAccept ();
+        });
 
 
         KeyBindings.Add (Key.Space, Command.HotKey);
         KeyBindings.Add (Key.Space, Command.HotKey);
         KeyBindings.Add (Key.Enter, Command.HotKey);
         KeyBindings.Add (Key.Enter, Command.HotKey);
@@ -71,6 +68,8 @@ public class Button : View
         MouseClick += Button_MouseClick;
         MouseClick += Button_MouseClick;
     }
     }
 
 
+    private bool _wantContinuousButtonPressed;
+
     /// <inheritdoc />
     /// <inheritdoc />
     public override bool WantContinuousButtonPressed
     public override bool WantContinuousButtonPressed
     {
     {
@@ -97,7 +96,7 @@ public class Button : View
 
 
     private void Button_MouseClick (object sender, MouseEventEventArgs e)
     private void Button_MouseClick (object sender, MouseEventEventArgs e)
     {
     {
-        e.Handled = InvokeCommand (Command.HotKey) == true;
+       e.Handled = InvokeCommand (Command.HotKey) == true;
     }
     }
 
 
     private void Button_TitleChanged (object sender, StateEventArgs<string> e)
     private void Button_TitleChanged (object sender, StateEventArgs<string> e)
@@ -189,4 +188,4 @@ public class Button : View
             }
             }
         }
         }
     }
     }
-}
+}

+ 5 - 18
Terminal.Gui/Views/ColorPicker.cs

@@ -43,24 +43,11 @@ public class ColorPicker : View
                              Width = _cols * BoxWidth + thickness.Vertical;
                              Width = _cols * BoxWidth + thickness.Vertical;
                              Height = _rows * BoxHeight + thickness.Horizontal;
                              Height = _rows * BoxHeight + thickness.Horizontal;
                          };
                          };
-//        MouseEvent += ColorPicker_MouseEvent;
+
         MouseClick += ColorPicker_MouseClick;
         MouseClick += ColorPicker_MouseClick;
     }
     }
 
 
     // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
     // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
-    //private void ColorPicker_MouseEvent (object sender, MouseEventEventArgs me)
-    //{
-    //    if (me.MouseEvent.X > Bounds.Width || me.MouseEvent.Y > Bounds.Height)
-    //    {
-    //        me.Handled = true;
-
-    //        return;
-    //    }
-
-    //    me.Handled = true;
-
-    //    return;
-    //}
 
 
     private void ColorPicker_MouseClick (object sender, MouseEventEventArgs me)
     private void ColorPicker_MouseClick (object sender, MouseEventEventArgs me)
     {
     {
@@ -181,16 +168,16 @@ public class ColorPicker : View
     }
     }
 
 
     ///<inheritdoc/>
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
 
         Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
         Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
         var colorIndex = 0;
         var colorIndex = 0;
 
 
-        for (var y = 0; y < Bounds.Height / BoxHeight; y++)
+        for (var y = 0; y < Viewport.Height / BoxHeight; y++)
         {
         {
-            for (var x = 0; x < Bounds.Width / BoxWidth; x++)
+            for (var x = 0; x < Viewport.Width / BoxWidth; x++)
             {
             {
                 int foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
                 int foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
                 Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
                 Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex));

+ 18 - 18
Terminal.Gui/Views/ComboBox.cs

@@ -245,8 +245,8 @@ public class ComboBox : View
     /// <inheritdoc/>
     /// <inheritdoc/>
     protected internal override bool OnMouseEvent  (MouseEvent me)
     protected internal override bool OnMouseEvent  (MouseEvent me)
     {
     {
-        if (me.X == Bounds.Right - 1
-            && me.Y == Bounds.Top
+        if (me.X == Viewport.Right - 1
+            && me.Y == Viewport.Top
             && me.Flags == MouseFlags.Button1Pressed
             && me.Flags == MouseFlags.Button1Pressed
             && _autoHide)
             && _autoHide)
         {
         {
@@ -284,9 +284,9 @@ public class ComboBox : View
     public virtual void OnCollapsed () { Collapsed?.Invoke (this, EventArgs.Empty); }
     public virtual void OnCollapsed () { Collapsed?.Invoke (this, EventArgs.Empty); }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
 
         if (!_autoHide)
         if (!_autoHide)
         {
         {
@@ -294,7 +294,7 @@ public class ComboBox : View
         }
         }
 
 
         Driver.SetAttribute (ColorScheme.Focus);
         Driver.SetAttribute (ColorScheme.Focus);
-        Move (Bounds.Right - 1, 0);
+        Move (Viewport.Right - 1, 0);
         Driver.AddRune (Glyphs.DownArrow);
         Driver.AddRune (Glyphs.DownArrow);
     }
     }
 
 
@@ -401,15 +401,15 @@ public class ComboBox : View
     /// <returns></returns>
     /// <returns></returns>
     private int CalculatetHeight ()
     private int CalculatetHeight ()
     {
     {
-        if (!IsInitialized || Bounds.Height == 0)
+        if (!IsInitialized || Viewport.Height == 0)
         {
         {
             return 0;
             return 0;
         }
         }
 
 
         return Math.Min (
         return Math.Min (
-                         Math.Max (Bounds.Height - 1, _minimumHeight - 1),
+                         Math.Max (Viewport.Height - 1, _minimumHeight - 1),
                          _searchset?.Count > 0 ? _searchset.Count :
                          _searchset?.Count > 0 ? _searchset.Count :
-                         IsShow ? Math.Max (Bounds.Height - 1, _minimumHeight - 1) : 0
+                         IsShow ? Math.Max (Viewport.Height - 1, _minimumHeight - 1) : 0
                         );
                         );
     }
     }
 
 
@@ -491,10 +491,10 @@ public class ComboBox : View
         }
         }
 
 
         Reset (true);
         Reset (true);
-        _listview.Clear (_listview.IsInitialized ? _listview.Bounds : Rectangle.Empty);
+        _listview.Clear ();
         _listview.TabStop = false;
         _listview.TabStop = false;
         SuperView?.SendSubviewToBack (this);
         SuperView?.SendSubviewToBack (this);
-        Rectangle rect = _listview.BoundsToScreen (_listview.IsInitialized ? _listview.Bounds : Rectangle.Empty);
+        Rectangle rect = _listview.ViewportToScreen (_listview.IsInitialized ? _listview.Viewport : Rectangle.Empty);
         SuperView?.SetNeedsDisplay (rect);
         SuperView?.SetNeedsDisplay (rect);
         OnCollapsed ();
         OnCollapsed ();
     }
     }
@@ -607,18 +607,18 @@ public class ComboBox : View
 
 
     private void ProcessLayout ()
     private void ProcessLayout ()
     {
     {
-        if (Bounds.Height < _minimumHeight && (Height is null || Height is Dim.DimAbsolute))
+        if (Viewport.Height < _minimumHeight && (Height is null || Height is Dim.DimAbsolute))
         {
         {
             Height = _minimumHeight;
             Height = _minimumHeight;
         }
         }
 
 
-        if ((!_autoHide && Bounds.Width > 0 && _search.Frame.Width != Bounds.Width)
-            || (_autoHide && Bounds.Width > 0 && _search.Frame.Width != Bounds.Width - 1))
+        if ((!_autoHide && Viewport.Width > 0 && _search.Frame.Width != Viewport.Width)
+            || (_autoHide && Viewport.Width > 0 && _search.Frame.Width != Viewport.Width - 1))
         {
         {
-            _search.Width = _listview.Width = _autoHide ? Bounds.Width - 1 : Bounds.Width;
+            _search.Width = _listview.Width = _autoHide ? Viewport.Width - 1 : Viewport.Width;
             _listview.Height = CalculatetHeight ();
             _listview.Height = CalculatetHeight ();
-            _search.SetRelativeLayout (Bounds);
-            _listview.SetRelativeLayout (Bounds);
+            _search.SetRelativeLayout (ContentSize);
+            _listview.SetRelativeLayout (ContentSize);
         }
         }
     }
     }
 
 
@@ -761,7 +761,7 @@ public class ComboBox : View
     private void ShowList ()
     private void ShowList ()
     {
     {
         _listview.SetSource (_searchset);
         _listview.SetSource (_searchset);
-        _listview.Clear (Bounds); // Ensure list shrinks in Dialog as you type
+        _listview.Clear (); 
         _listview.Height = CalculatetHeight ();
         _listview.Height = CalculatetHeight ();
         SuperView?.BringSubviewToFront (this);
         SuperView?.BringSubviewToFront (this);
     }
     }
@@ -839,7 +839,7 @@ public class ComboBox : View
             return res;
             return res;
         }
         }
 
 
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
         {
             Attribute current = ColorScheme.Focus;
             Attribute current = ColorScheme.Focus;
             Driver.SetAttribute (current);
             Driver.SetAttribute (current);

+ 5 - 4
Terminal.Gui/Views/Dialog.cs

@@ -56,6 +56,7 @@ public class Dialog : Window
     /// </remarks>
     /// </remarks>
     public Dialog ()
     public Dialog ()
     {
     {
+        Arrangement = ViewArrangement.Movable;
         X = Pos.Center ();
         X = Pos.Center ();
         Y = Pos.Center ();
         Y = Pos.Center ();
         ValidatePosDim = true;
         ValidatePosDim = true;
@@ -201,7 +202,7 @@ public class Dialog : Window
         {
         {
             case ButtonAlignments.Center:
             case ButtonAlignments.Center:
                 // Center Buttons
                 // Center Buttons
-                shiftLeft = (Bounds.Width - buttonsWidth - _buttons.Count - 1) / 2 + 1;
+                shiftLeft = (Viewport.Width - buttonsWidth - _buttons.Count - 1) / 2 + 1;
 
 
                 for (int i = _buttons.Count - 1; i >= 0; i--)
                 for (int i = _buttons.Count - 1; i >= 0; i--)
                 {
                 {
@@ -214,7 +215,7 @@ public class Dialog : Window
                     }
                     }
                     else
                     else
                     {
                     {
-                        button.X = Bounds.Width - shiftLeft;
+                        button.X = Viewport.Width - shiftLeft;
                     }
                     }
 
 
                     button.Y = Pos.AnchorEnd (1);
                     button.Y = Pos.AnchorEnd (1);
@@ -226,7 +227,7 @@ public class Dialog : Window
                 // Justify Buttons
                 // Justify Buttons
                 // leftmost and rightmost buttons are hard against edges. The rest are evenly spaced.
                 // leftmost and rightmost buttons are hard against edges. The rest are evenly spaced.
 
 
-                var spacing = (int)Math.Ceiling ((double)(Bounds.Width - buttonsWidth) / (_buttons.Count - 1));
+                var spacing = (int)Math.Ceiling ((double)(Viewport.Width - buttonsWidth) / (_buttons.Count - 1));
 
 
                 for (int i = _buttons.Count - 1; i >= 0; i--)
                 for (int i = _buttons.Count - 1; i >= 0; i--)
                 {
                 {
@@ -242,7 +243,7 @@ public class Dialog : Window
                         if (i == 0)
                         if (i == 0)
                         {
                         {
                             // first (leftmost) button 
                             // first (leftmost) button 
-                            int left = Bounds.Width;
+                            int left = Viewport.Width;
                             button.X = Pos.AnchorEnd (left);
                             button.X = Pos.AnchorEnd (left);
                         }
                         }
                         else
                         else

+ 12 - 12
Terminal.Gui/Views/FileDialog.cs

@@ -410,23 +410,23 @@ public class FileDialog : Dialog
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
 
         if (!string.IsNullOrWhiteSpace (_feedback))
         if (!string.IsNullOrWhiteSpace (_feedback))
         {
         {
             int feedbackWidth = _feedback.EnumerateRunes ().Sum (c => c.GetColumns ());
             int feedbackWidth = _feedback.EnumerateRunes ().Sum (c => c.GetColumns ());
-            int feedbackPadLeft = (Bounds.Width - feedbackWidth) / 2 - 1;
+            int feedbackPadLeft = (Viewport.Width - feedbackWidth) / 2 - 1;
 
 
-            feedbackPadLeft = Math.Min (Bounds.Width, feedbackPadLeft);
+            feedbackPadLeft = Math.Min (Viewport.Width, feedbackPadLeft);
             feedbackPadLeft = Math.Max (0, feedbackPadLeft);
             feedbackPadLeft = Math.Max (0, feedbackPadLeft);
 
 
-            int feedbackPadRight = Bounds.Width - (feedbackPadLeft + feedbackWidth + 2);
-            feedbackPadRight = Math.Min (Bounds.Width, feedbackPadRight);
+            int feedbackPadRight = Viewport.Width - (feedbackPadLeft + feedbackWidth + 2);
+            feedbackPadRight = Math.Min (Viewport.Width, feedbackPadRight);
             feedbackPadRight = Math.Max (0, feedbackPadRight);
             feedbackPadRight = Math.Max (0, feedbackPadRight);
 
 
-            Move (0, Bounds.Height / 2);
+            Move (0, Viewport.Height / 2);
 
 
             Driver.SetAttribute (new Attribute (Color.Red, ColorScheme.Normal.Background));
             Driver.SetAttribute (new Attribute (Color.Red, ColorScheme.Normal.Background));
             Driver.AddStr (new string (' ', feedbackPadLeft));
             Driver.AddStr (new string (' ', feedbackPadLeft));
@@ -519,7 +519,7 @@ public class FileDialog : Dialog
 
 
             _allowedTypeMenuBar.DrawContentComplete += (s, e) =>
             _allowedTypeMenuBar.DrawContentComplete += (s, e) =>
                                                        {
                                                        {
-                                                           _allowedTypeMenuBar.Move (e.Rectangle.Width - 1, 0);
+                                                           _allowedTypeMenuBar.Move (e.NewViewport.Width - 1, 0);
                                                            Driver.AddRune (Glyphs.DownArrow);
                                                            Driver.AddRune (Glyphs.DownArrow);
                                                        };
                                                        };
 
 
@@ -776,12 +776,12 @@ public class FileDialog : Dialog
             return 0;
             return 0;
         }
         }
 
 
-        return Bounds.Width
-               - _btnOk.Bounds.Width
-               - _btnCancel.Bounds.Width
+        return Viewport.Width
+               - _btnOk.Viewport.Width
+               - _btnCancel.Viewport.Width
                - 1
                - 1
 
 
-               // TODO: Fiddle factor, seems the Bounds are wrong for someone
+               // TODO: Fiddle factor, seems the Viewport are wrong for someone
                - 2;
                - 2;
     }
     }
 
 

+ 8 - 8
Terminal.Gui/Views/GraphView/Annotations.cs

@@ -18,7 +18,7 @@ public interface IAnnotation
 
 
     /// <summary>
     /// <summary>
     ///     Called once after series have been rendered (or before if <see cref="BeforeSeries"/> is true). Use
     ///     Called once after series have been rendered (or before if <see cref="BeforeSeries"/> is true). Use
-    ///     <see cref="View.Driver"/> to draw and <see cref="View.Bounds"/> to avoid drawing outside of graph
+    ///     <see cref="View.Driver"/> to draw and <see cref="View.Viewport"/> to avoid drawing outside of graph
     /// </summary>
     /// </summary>
     /// <param name="graph"></param>
     /// <param name="graph"></param>
     void Render (GraphView graph);
     void Render (GraphView graph);
@@ -62,7 +62,7 @@ public class TextAnnotation : IAnnotation
 
 
     /// <summary>
     /// <summary>
     ///     Draws the <see cref="Text"/> at the given coordinates with truncation to avoid spilling over
     ///     Draws the <see cref="Text"/> at the given coordinates with truncation to avoid spilling over
-    ///     <see name="View.Bounds"/> of the <paramref name="graph"/>
+    ///     <see name="View.Viewport"/> of the <paramref name="graph"/>
     /// </summary>
     /// </summary>
     /// <param name="graph"></param>
     /// <param name="graph"></param>
     /// <param name="x">Screen x position to start drawing string</param>
     /// <param name="x">Screen x position to start drawing string</param>
@@ -70,7 +70,7 @@ public class TextAnnotation : IAnnotation
     protected void DrawText (GraphView graph, int x, int y)
     protected void DrawText (GraphView graph, int x, int y)
     {
     {
         // the draw point is out of control bounds
         // the draw point is out of control bounds
-        if (!graph.Bounds.Contains (new Point (x, y)))
+        if (!graph.Viewport.Contains (new Point (x, y)))
         {
         {
             return;
             return;
         }
         }
@@ -83,7 +83,7 @@ public class TextAnnotation : IAnnotation
 
 
         graph.Move (x, y);
         graph.Move (x, y);
 
 
-        int availableWidth = graph.Bounds.Width - x;
+        int availableWidth = graph.Viewport.Width - x;
 
 
         if (availableWidth <= 0)
         if (availableWidth <= 0)
         {
         {
@@ -127,7 +127,7 @@ public class LegendAnnotation : View, IAnnotation
     /// <summary>Returns false i.e. Legends render after series</summary>
     /// <summary>Returns false i.e. Legends render after series</summary>
     public bool BeforeSeries => false;
     public bool BeforeSeries => false;
 
 
-    /// <summary>Draws the Legend and all entries into the area within <see cref="View.Bounds"/></summary>
+    /// <summary>Draws the Legend and all entries into the area within <see cref="View.Viewport"/></summary>
     /// <param name="graph"></param>
     /// <param name="graph"></param>
     public void Render (GraphView graph)
     public void Render (GraphView graph)
     {
     {
@@ -165,13 +165,13 @@ public class LegendAnnotation : View, IAnnotation
             // add the text
             // add the text
             Move (1, linesDrawn);
             Move (1, linesDrawn);
 
 
-            string str = TextFormatter.ClipOrPad (entry.Item2, Bounds.Width - 1);
+            string str = TextFormatter.ClipOrPad (entry.Item2, Viewport.Width - 1);
             Application.Driver.AddStr (str);
             Application.Driver.AddStr (str);
 
 
             linesDrawn++;
             linesDrawn++;
 
 
             // Legend has run out of space
             // Legend has run out of space
-            if (linesDrawn >= Bounds.Height)
+            if (linesDrawn >= Viewport.Height)
             {
             {
                 break;
                 break;
             }
             }
@@ -182,7 +182,7 @@ public class LegendAnnotation : View, IAnnotation
     /// <param name="graphCellToRender">The symbol appearing on the graph that should appear in the legend</param>
     /// <param name="graphCellToRender">The symbol appearing on the graph that should appear in the legend</param>
     /// <param name="text">
     /// <param name="text">
     ///     Text to render on this line of the legend.  Will be truncated if outside of Legend
     ///     Text to render on this line of the legend.  Will be truncated if outside of Legend
-    ///     <see cref="View.Bounds"/>
+    ///     <see cref="View.Viewport"/>
     /// </param>
     /// </param>
     public void AddEntry (GraphCellToRender graphCellToRender, string text) { _entries.Add (Tuple.Create (graphCellToRender, text)); }
     public void AddEntry (GraphCellToRender graphCellToRender, string text) { _entries.Add (Tuple.Create (graphCellToRender, text)); }
 }
 }

+ 19 - 19
Terminal.Gui/Views/GraphView/Axis.cs

@@ -113,7 +113,7 @@ public class HorizontalAxis : Axis
             string toRender = text;
             string toRender = text;
 
 
             // this is how much space is left
             // this is how much space is left
-            int xSpaceAvailable = graph.Bounds.Width - drawAtX;
+            int xSpaceAvailable = graph.Viewport.Width - drawAtX;
 
 
             // There is no space for the label at all!
             // There is no space for the label at all!
             if (xSpaceAvailable <= 0)
             if (xSpaceAvailable <= 0)
@@ -127,7 +127,7 @@ public class HorizontalAxis : Axis
                 toRender = toRender.Substring (0, xSpaceAvailable);
                 toRender = toRender.Substring (0, xSpaceAvailable);
             }
             }
 
 
-            graph.Move (drawAtX, Math.Min (y + 1, graph.Bounds.Height - 1));
+            graph.Move (drawAtX, Math.Min (y + 1, graph.Viewport.Height - 1));
             driver.AddStr (toRender);
             driver.AddStr (toRender);
         }
         }
     }
     }
@@ -140,9 +140,9 @@ public class HorizontalAxis : Axis
             return;
             return;
         }
         }
 
 
-        Rectangle bounds = graph.Bounds;
+        Rectangle viewport = graph.Viewport;
 
 
-        IEnumerable<AxisIncrementToRender> labels = GetLabels (graph, bounds);
+        IEnumerable<AxisIncrementToRender> labels = GetLabels (graph, viewport);
 
 
         foreach (AxisIncrementToRender label in labels)
         foreach (AxisIncrementToRender label in labels)
         {
         {
@@ -155,12 +155,12 @@ public class HorizontalAxis : Axis
             string toRender = Text;
             string toRender = Text;
 
 
             // if label is too long
             // if label is too long
-            if (toRender.Length > graph.Bounds.Width)
+            if (toRender.Length > graph.Viewport.Width)
             {
             {
-                toRender = toRender.Substring (0, graph.Bounds.Width);
+                toRender = toRender.Substring (0, graph.Viewport.Width);
             }
             }
 
 
-            graph.Move (graph.Bounds.Width / 2 - toRender.Length / 2, graph.Bounds.Height - 1);
+            graph.Move (graph.Viewport.Width / 2 - toRender.Length / 2, graph.Viewport.Height - 1);
             Application.Driver.AddStr (toRender);
             Application.Driver.AddStr (toRender);
         }
         }
     }
     }
@@ -174,7 +174,7 @@ public class HorizontalAxis : Axis
             return;
             return;
         }
         }
 
 
-        Rectangle bounds = graph.Bounds;
+        Rectangle bounds = graph.Viewport;
 
 
         graph.Move (0, 0);
         graph.Move (0, 0);
 
 
@@ -212,7 +212,7 @@ public class HorizontalAxis : Axis
 
 
         // float the X axis so that it accurately represents the origin of the graph
         // float the X axis so that it accurately represents the origin of the graph
         // but anchor it to top/bottom if the origin is offscreen
         // but anchor it to top/bottom if the origin is offscreen
-        return Math.Min (Math.Max (0, origin.Y), graph.Bounds.Height - ((int)graph.MarginBottom + 1));
+        return Math.Min (Math.Max (0, origin.Y), graph.Viewport.Height - ((int)graph.MarginBottom + 1));
     }
     }
 
 
     /// <summary>Draws a horizontal axis line at the given <paramref name="x"/>, <paramref name="y"/> screen coordinates</summary>
     /// <summary>Draws a horizontal axis line at the given <paramref name="x"/>, <paramref name="y"/> screen coordinates</summary>
@@ -225,7 +225,7 @@ public class HorizontalAxis : Axis
         Application.Driver.AddRune (Glyphs.HLine);
         Application.Driver.AddRune (Glyphs.HLine);
     }
     }
 
 
-    private IEnumerable<AxisIncrementToRender> GetLabels (GraphView graph, Rectangle bounds)
+    private IEnumerable<AxisIncrementToRender> GetLabels (GraphView graph, Rectangle viewport)
     {
     {
         // if no labels
         // if no labels
         if (Increment == 0)
         if (Increment == 0)
@@ -237,7 +237,7 @@ public class HorizontalAxis : Axis
         int y = GetAxisYPosition (graph);
         int y = GetAxisYPosition (graph);
 
 
         RectangleF start = graph.ScreenToGraphSpace ((int)graph.MarginLeft, y);
         RectangleF start = graph.ScreenToGraphSpace ((int)graph.MarginLeft, y);
-        RectangleF end = graph.ScreenToGraphSpace (bounds.Width, y);
+        RectangleF end = graph.ScreenToGraphSpace (viewport.Width, y);
 
 
         // don't draw labels below the minimum
         // don't draw labels below the minimum
         if (Minimum.HasValue)
         if (Minimum.HasValue)
@@ -266,7 +266,7 @@ public class HorizontalAxis : Axis
                 ;
                 ;
             }
             }
 
 
-            // Label or no label definetly render it
+            // Label or no label definitely render it
             yield return toRender;
             yield return toRender;
 
 
             current.X += Increment;
             current.X += Increment;
@@ -317,7 +317,7 @@ public class VerticalAxis : Axis
             return;
             return;
         }
         }
 
 
-        Rectangle bounds = graph.Bounds;
+        Rectangle bounds = graph.Viewport;
         IEnumerable<AxisIncrementToRender> labels = GetLabels (graph, bounds);
         IEnumerable<AxisIncrementToRender> labels = GetLabels (graph, bounds);
 
 
         foreach (AxisIncrementToRender label in labels)
         foreach (AxisIncrementToRender label in labels)
@@ -331,13 +331,13 @@ public class VerticalAxis : Axis
             string toRender = Text;
             string toRender = Text;
 
 
             // if label is too long
             // if label is too long
-            if (toRender.Length > graph.Bounds.Height)
+            if (toRender.Length > graph.Viewport.Height)
             {
             {
-                toRender = toRender.Substring (0, graph.Bounds.Height);
+                toRender = toRender.Substring (0, graph.Viewport.Height);
             }
             }
 
 
             // Draw it 1 letter at a time vertically down row 0 of the control
             // Draw it 1 letter at a time vertically down row 0 of the control
-            int startDrawingAtY = graph.Bounds.Height / 2 - toRender.Length / 2;
+            int startDrawingAtY = graph.Viewport.Height / 2 - toRender.Length / 2;
 
 
             for (var i = 0; i < toRender.Length; i++)
             for (var i = 0; i < toRender.Length; i++)
             {
             {
@@ -356,7 +356,7 @@ public class VerticalAxis : Axis
             return;
             return;
         }
         }
 
 
-        Rectangle bounds = graph.Bounds;
+        Rectangle bounds = graph.Viewport;
 
 
         int x = GetAxisXPosition (graph);
         int x = GetAxisXPosition (graph);
 
 
@@ -385,7 +385,7 @@ public class VerticalAxis : Axis
 
 
         // float the Y axis so that it accurately represents the origin of the graph
         // float the Y axis so that it accurately represents the origin of the graph
         // but anchor it to left/right if the origin is offscreen
         // but anchor it to left/right if the origin is offscreen
-        return Math.Min (Math.Max ((int)graph.MarginLeft, origin.X), graph.Bounds.Width - 1);
+        return Math.Min (Math.Max ((int)graph.MarginLeft, origin.X), graph.Viewport.Width - 1);
     }
     }
 
 
     /// <summary>Draws a vertical axis line at the given <paramref name="x"/>, <paramref name="y"/> screen coordinates</summary>
     /// <summary>Draws a vertical axis line at the given <paramref name="x"/>, <paramref name="y"/> screen coordinates</summary>
@@ -409,7 +409,7 @@ public class VerticalAxis : Axis
             return graph.GraphSpaceToScreen (new PointF (0, Minimum.Value)).Y;
             return graph.GraphSpaceToScreen (new PointF (0, Minimum.Value)).Y;
         }
         }
 
 
-        return graph.Bounds.Height;
+        return graph.Viewport.Height;
     }
     }
 
 
     private IEnumerable<AxisIncrementToRender> GetLabels (GraphView graph, Rectangle bounds)
     private IEnumerable<AxisIncrementToRender> GetLabels (GraphView graph, Rectangle bounds)

+ 9 - 9
Terminal.Gui/Views/GraphView/GraphView.cs

@@ -192,12 +192,12 @@ public class GraphView : View
                           (int)((location.X - ScrollOffset.X) / CellSize.X) + (int)MarginLeft,
                           (int)((location.X - ScrollOffset.X) / CellSize.X) + (int)MarginLeft,
 
 
                           // screen coordinates are top down while graph coordinates are bottom up
                           // screen coordinates are top down while graph coordinates are bottom up
-                          Bounds.Height - 1 - (int)MarginBottom - (int)((location.Y - ScrollOffset.Y) / CellSize.Y)
+                          Viewport.Height - 1 - (int)MarginBottom - (int)((location.Y - ScrollOffset.Y) / CellSize.Y)
                          );
                          );
     }
     }
 
 
     ///<inheritdoc/>
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         if (CellSize.X == 0 || CellSize.Y == 0)
         if (CellSize.X == 0 || CellSize.Y == 0)
         {
         {
@@ -209,10 +209,10 @@ public class GraphView : View
         Move (0, 0);
         Move (0, 0);
 
 
         // clear all old content
         // clear all old content
-        for (var i = 0; i < Bounds.Height; i++)
+        for (var i = 0; i < Viewport.Height; i++)
         {
         {
             Move (0, i);
             Move (0, i);
-            Driver.AddStr (new string (' ', Bounds.Width));
+            Driver.AddStr (new string (' ', Viewport.Width));
         }
         }
 
 
         // If there is no data do not display a graph
         // If there is no data do not display a graph
@@ -222,8 +222,8 @@ public class GraphView : View
         }
         }
 
 
         // The drawable area of the graph (anything that isn't in the margins)
         // The drawable area of the graph (anything that isn't in the margins)
-        int graphScreenWidth = Bounds.Width - (int)MarginLeft;
-        int graphScreenHeight = Bounds.Height - (int)MarginBottom;
+        int graphScreenWidth = Viewport.Width - (int)MarginLeft;
+        int graphScreenHeight = Viewport.Height - (int)MarginBottom;
 
 
         // if the margins take up the full draw bounds don't render
         // if the margins take up the full draw bounds don't render
         if (graphScreenWidth < 0 || graphScreenHeight < 0)
         if (graphScreenWidth < 0 || graphScreenHeight < 0)
@@ -287,10 +287,10 @@ public class GraphView : View
     }
     }
 
 
     /// <summary>Scrolls the graph down 1 page.</summary>
     /// <summary>Scrolls the graph down 1 page.</summary>
-    public void PageDown () { Scroll (0, -1 * CellSize.Y * Bounds.Height); }
+    public void PageDown () { Scroll (0, -1 * CellSize.Y * Viewport.Height); }
 
 
     /// <summary>Scrolls the graph up 1 page.</summary>
     /// <summary>Scrolls the graph up 1 page.</summary>
-    public void PageUp () { Scroll (0, CellSize.Y * Bounds.Height); }
+    public void PageUp () { Scroll (0, CellSize.Y * Viewport.Height); }
 
 
     /// <summary>
     /// <summary>
     ///     Clears all settings configured on the graph and resets all properties to default values (
     ///     Clears all settings configured on the graph and resets all properties to default values (
@@ -316,7 +316,7 @@ public class GraphView : View
     {
     {
         return new (
         return new (
                     ScrollOffset.X + (col - MarginLeft) * CellSize.X,
                     ScrollOffset.X + (col - MarginLeft) * CellSize.X,
-                    ScrollOffset.Y + (Bounds.Height - (row + MarginBottom + 1)) * CellSize.Y,
+                    ScrollOffset.Y + (Viewport.Height - (row + MarginBottom + 1)) * CellSize.Y,
                     CellSize.X,
                     CellSize.X,
                     CellSize.Y
                     CellSize.Y
                    );
                    );

+ 1 - 1
Terminal.Gui/Views/GraphView/Series.cs

@@ -192,7 +192,7 @@ public class BarSeries : ISeries
                 screenStart.X = graph.AxisY.GetAxisXPosition (graph);
                 screenStart.X = graph.AxisY.GetAxisXPosition (graph);
 
 
                 // dont draw bar off the right of the control
                 // dont draw bar off the right of the control
-                screenEnd.X = Math.Min (graph.Bounds.Width - 1, screenEnd.X);
+                screenEnd.X = Math.Min (graph.Viewport.Width - 1, screenEnd.X);
 
 
                 // if bar is off the screen
                 // if bar is off the screen
                 if (screenStart.Y < 0 || screenStart.Y > drawBounds.Height - graph.MarginBottom)
                 if (screenStart.Y < 0 || screenStart.Y > drawBounds.Height - graph.MarginBottom)

+ 10 - 10
Terminal.Gui/Views/HexView.cs

@@ -264,7 +264,7 @@ public class HexView : View
     /// <inheritdoc/>
     /// <inheritdoc/>
     protected internal override bool OnMouseEvent  (MouseEvent me)
     protected internal override bool OnMouseEvent  (MouseEvent me)
     {
     {
-        // BUGBUG: Test this with a border! Assumes Frame == Bounds!
+        // BUGBUG: Test this with a border! Assumes Frame == Viewport!
 
 
         if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)
         if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)
             && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
             && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
@@ -351,14 +351,14 @@ public class HexView : View
     }
     }
 
 
     ///<inheritdoc/>
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         Attribute currentAttribute;
         Attribute currentAttribute;
         Attribute current = ColorScheme.Focus;
         Attribute current = ColorScheme.Focus;
         Driver.SetAttribute (current);
         Driver.SetAttribute (current);
         Move (0, 0);
         Move (0, 0);
 
 
-        // BUGBUG: Bounds!!!!
+        // BUGBUG: Viewport!!!!
         Rectangle frame = Frame;
         Rectangle frame = Frame;
 
 
         int nblocks = bytesPerLine / bsize;
         int nblocks = bytesPerLine / bsize;
@@ -373,7 +373,7 @@ public class HexView : View
         {
         {
             Rectangle lineRect = new (0, line, frame.Width, 1);
             Rectangle lineRect = new (0, line, frame.Width, 1);
 
 
-            if (!Bounds.Contains (lineRect))
+            if (!Viewport.Contains (lineRect))
             {
             {
                 continue;
                 continue;
             }
             }
@@ -611,15 +611,15 @@ public class HexView : View
         // Small buffers will just show the position, with the bsize field value (4 bytes)
         // Small buffers will just show the position, with the bsize field value (4 bytes)
         bytesPerLine = bsize;
         bytesPerLine = bsize;
 
 
-        if (Bounds.Width - displayWidth > 17)
+        if (Viewport.Width - displayWidth > 17)
         {
         {
-            bytesPerLine = bsize * ((Bounds.Width - displayWidth) / 18);
+            bytesPerLine = bsize * ((Viewport.Width - displayWidth) / 18);
         }
         }
     }
     }
 
 
     private bool MoveDown (int bytes)
     private bool MoveDown (int bytes)
     {
     {
-        // BUGBUG: Bounds!
+        // BUGBUG: Viewport!
         RedisplayLine (position);
         RedisplayLine (position);
 
 
         if (position + bytes < source.Length)
         if (position + bytes < source.Length)
@@ -657,7 +657,7 @@ public class HexView : View
     {
     {
         position = source.Length;
         position = source.Length;
 
 
-        // BUGBUG: Bounds!
+        // BUGBUG: Viewport!
         if (position >= DisplayStart + bytesPerLine * Frame.Height)
         if (position >= DisplayStart + bytesPerLine * Frame.Height)
         {
         {
             SetDisplayStart (position);
             SetDisplayStart (position);
@@ -744,7 +744,7 @@ public class HexView : View
             position++;
             position++;
         }
         }
 
 
-        // BUGBUG: Bounds!
+        // BUGBUG: Viewport!
         if (position >= DisplayStart + bytesPerLine * Frame.Height)
         if (position >= DisplayStart + bytesPerLine * Frame.Height)
         {
         {
             SetDisplayStart (DisplayStart + bytesPerLine);
             SetDisplayStart (DisplayStart + bytesPerLine);
@@ -793,7 +793,7 @@ public class HexView : View
         var delta = (int)(pos - DisplayStart);
         var delta = (int)(pos - DisplayStart);
         int line = delta / bytesPerLine;
         int line = delta / bytesPerLine;
 
 
-        // BUGBUG: Bounds!
+        // BUGBUG: Viewport!
         SetNeedsDisplay (new (0, line, Frame.Width, 1));
         SetNeedsDisplay (new (0, line, Frame.Width, 1));
     }
     }
 
 

+ 2 - 2
Terminal.Gui/Views/Line.cs

@@ -17,7 +17,7 @@ public class Line : View
     public Orientation Orientation { get; set; }
     public Orientation Orientation { get; set; }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         LineCanvas lc = LineCanvas;
         LineCanvas lc = LineCanvas;
 
 
@@ -26,7 +26,7 @@ public class Line : View
             lc = adornment.Parent.LineCanvas;
             lc = adornment.Parent.LineCanvas;
         }
         }
         lc.AddLine (
         lc.AddLine (
-                    BoundsToScreen (contentArea).Location,
+                    ViewportToScreen (viewport).Location,
                     Orientation == Orientation.Horizontal ? Frame.Width : Frame.Height,
                     Orientation == Orientation.Horizontal ? Frame.Width : Frame.Height,
                     Orientation,
                     Orientation,
                     BorderStyle
                     BorderStyle

+ 3 - 3
Terminal.Gui/Views/LineView.cs

@@ -54,16 +54,16 @@ public class LineView : View
     public Rune? StartingAnchor { get; set; }
     public Rune? StartingAnchor { get; set; }
 
 
     /// <summary>Draws the line including any starting/ending anchors</summary>
     /// <summary>Draws the line including any starting/ending anchors</summary>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
 
         Move (0, 0);
         Move (0, 0);
         Driver.SetAttribute (GetNormalColor ());
         Driver.SetAttribute (GetNormalColor ());
 
 
         int hLineWidth = Math.Max (1, Glyphs.HLine.GetColumns ());
         int hLineWidth = Math.Max (1, Glyphs.HLine.GetColumns ());
 
 
-        int dEnd = Orientation == Orientation.Horizontal ? Bounds.Width : Bounds.Height;
+        int dEnd = Orientation == Orientation.Horizontal ? Viewport.Width : Viewport.Height;
 
 
         for (var d = 0; d < dEnd; d += hLineWidth)
         for (var d = 0; d < dEnd; d += hLineWidth)
         {
         {

+ 72 - 96
Terminal.Gui/Views/ListView.cs

@@ -1,4 +1,5 @@
 using System.Collections;
 using System.Collections;
+using static Terminal.Gui.SpinnerStyle;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
@@ -94,7 +95,10 @@ public class ListView : View
     private int _lastSelectedItem = -1;
     private int _lastSelectedItem = -1;
     private int _selected = -1;
     private int _selected = -1;
     private IListDataSource _source;
     private IListDataSource _source;
-    private int _top, _left;
+    // TODO: ListView has been upgraded to use Viewport and ContentSize instead of the
+    // TODO: bespoke _top and _left. It was a quick & dirty port. There is now duplicate logic
+    // TODO: that could be removed. 
+    //private int _top, _left;
 
 
     /// <summary>
     /// <summary>
     ///     Initializes a new instance of <see cref="ListView"/>. Set the <see cref="Source"/> property to display
     ///     Initializes a new instance of <see cref="ListView"/>. Set the <see cref="Source"/> property to display
@@ -107,8 +111,8 @@ public class ListView : View
         // Things this view knows how to do
         // Things this view knows how to do
         AddCommand (Command.LineUp, () => MoveUp ());
         AddCommand (Command.LineUp, () => MoveUp ());
         AddCommand (Command.LineDown, () => MoveDown ());
         AddCommand (Command.LineDown, () => MoveDown ());
-        AddCommand (Command.ScrollUp, () => ScrollUp (1));
-        AddCommand (Command.ScrollDown, () => ScrollDown (1));
+        AddCommand (Command.ScrollUp, () => ScrollVertical (-1));
+        AddCommand (Command.ScrollDown, () => ScrollVertical (1));
         AddCommand (Command.PageUp, () => MovePageUp ());
         AddCommand (Command.PageUp, () => MovePageUp ());
         AddCommand (Command.PageDown, () => MovePageDown ());
         AddCommand (Command.PageDown, () => MovePageDown ());
         AddCommand (Command.TopHome, () => MoveHome ());
         AddCommand (Command.TopHome, () => MoveHome ());
@@ -117,6 +121,9 @@ public class ListView : View
         AddCommand (Command.OpenSelectedItem, () => OnOpenSelectedItem ());
         AddCommand (Command.OpenSelectedItem, () => OnOpenSelectedItem ());
         AddCommand (Command.Select, () => MarkUnmarkRow ());
         AddCommand (Command.Select, () => MarkUnmarkRow ());
 
 
+        AddCommand (Command.ScrollLeft, () => ScrollHorizontal (-1));
+        AddCommand (Command.ScrollRight, () => ScrollHorizontal (1));
+
         // Default keybindings for all ListViews
         // Default keybindings for all ListViews
         KeyBindings.Add (Key.CursorUp, Command.LineUp);
         KeyBindings.Add (Key.CursorUp, Command.LineUp);
         KeyBindings.Add (Key.P.WithCtrl, Command.LineUp);
         KeyBindings.Add (Key.P.WithCtrl, Command.LineUp);
@@ -199,7 +206,7 @@ public class ListView : View
     /// <value>The left position.</value>
     /// <value>The left position.</value>
     public int LeftItem
     public int LeftItem
     {
     {
-        get => _left;
+        get => Viewport.X;
         set
         set
         {
         {
             if (_source is null)
             if (_source is null)
@@ -212,7 +219,7 @@ public class ListView : View
                 throw new ArgumentException ("value");
                 throw new ArgumentException ("value");
             }
             }
 
 
-            _left = value;
+            Viewport = Viewport with { X = value };
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
     }
     }
@@ -250,20 +257,34 @@ public class ListView : View
         get => _source;
         get => _source;
         set
         set
         {
         {
+            if (_source == value)
+
+            {
+                return;
+            }
             _source = value;
             _source = value;
+
+            ContentSize = new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width);
+            if (IsInitialized)
+            {
+                Viewport = Viewport with { Y = 0 };
+            }
+
             KeystrokeNavigator.Collection = _source?.ToList ();
             KeystrokeNavigator.Collection = _source?.ToList ();
-            _top = 0;
             _selected = -1;
             _selected = -1;
             _lastSelectedItem = -1;
             _lastSelectedItem = -1;
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
     }
     }
 
 
-    /// <summary>Gets or sets the item that is displayed at the top of the <see cref="ListView"/>.</summary>
+    /// <summary>Gets or sets the index of the item that will appear at the top of the <see cref="View.Viewport"/>.</summary>
+    /// <remarks>
+    /// This a helper property for accessing <c>listView.Viewport.Y</c>.
+    /// </remarks>
     /// <value>The top item.</value>
     /// <value>The top item.</value>
     public int TopItem
     public int TopItem
     {
     {
-        get => _top;
+        get => Viewport.Y;
         set
         set
         {
         {
             if (_source is null)
             if (_source is null)
@@ -271,13 +292,7 @@ public class ListView : View
                 return;
                 return;
             }
             }
 
 
-            if (value < 0 || (_source.Count > 0 && value >= _source.Count))
-            {
-                throw new ArgumentException ("value");
-            }
-
-            _top = Math.Max (value, 0);
-            SetNeedsDisplay ();
+            Viewport = Viewport with { Y = value };
         }
         }
     }
     }
 
 
@@ -314,13 +329,14 @@ public class ListView : View
     {
     {
         if (SuperView?.IsInitialized == true)
         if (SuperView?.IsInitialized == true)
         {
         {
-            if (_selected < _top)
+            if (_selected < Viewport.Y)
             {
             {
-                _top = Math.Max (_selected, 0);
+                // TODO: The Max check here is not needed because, by default, Viewport enforces staying w/in ContentArea (View.ScrollSettings).
+                Viewport = Viewport with { Y = _selected };
             }
             }
-            else if (Bounds.Height > 0 && _selected >= _top + Bounds.Height)
+            else if (Viewport.Height > 0 && _selected >= Viewport.Y + Viewport.Height)
             {
             {
-                _top = Math.Max (_selected - Bounds.Height + 1, 0);
+                Viewport = Viewport with { Y = _selected - Viewport.Height + 1};
             }
             }
 
 
             LayoutStarted -= ListView_LayoutStarted;
             LayoutStarted -= ListView_LayoutStarted;
@@ -347,7 +363,7 @@ public class ListView : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    protected internal override bool OnMouseEvent  (MouseEvent me)
+    protected internal override bool OnMouseEvent (MouseEvent me)
     {
     {
         if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)
         if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)
             && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
             && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
@@ -371,40 +387,40 @@ public class ListView : View
 
 
         if (me.Flags == MouseFlags.WheeledDown)
         if (me.Flags == MouseFlags.WheeledDown)
         {
         {
-            ScrollDown (1);
+            ScrollVertical (1);
 
 
             return true;
             return true;
         }
         }
 
 
         if (me.Flags == MouseFlags.WheeledUp)
         if (me.Flags == MouseFlags.WheeledUp)
         {
         {
-            ScrollUp (1);
+            ScrollVertical (-1);
 
 
             return true;
             return true;
         }
         }
 
 
         if (me.Flags == MouseFlags.WheeledRight)
         if (me.Flags == MouseFlags.WheeledRight)
         {
         {
-            ScrollRight (1);
+            ScrollHorizontal (1);
 
 
             return true;
             return true;
         }
         }
 
 
         if (me.Flags == MouseFlags.WheeledLeft)
         if (me.Flags == MouseFlags.WheeledLeft)
         {
         {
-            ScrollLeft (1);
+            ScrollHorizontal(-1);
 
 
             return true;
             return true;
         }
         }
 
 
-        if (me.Y + _top >= _source.Count
-            || me.Y + _top < 0
-            || me.Y + _top > _top + Bounds.Height)
+        if (me.Y + Viewport.Y >= _source.Count
+            || me.Y + Viewport.Y < 0
+            || me.Y + Viewport.Y > Viewport.Y + Viewport.Height)
         {
         {
             return true;
             return true;
         }
         }
 
 
-        _selected = _top + me.Y;
+        _selected = Viewport.Y + me.Y;
 
 
         if (AllowsAll ())
         if (AllowsAll ())
         {
         {
@@ -449,13 +465,13 @@ public class ListView : View
             //can move by down by one.
             //can move by down by one.
             _selected++;
             _selected++;
 
 
-            if (_selected >= _top + Bounds.Height)
+            if (_selected >= Viewport.Y + Viewport.Height)
             {
             {
-                _top++;
+                Viewport = Viewport with { Y = Viewport.Y + 1 };
             }
             }
-            else if (_selected < _top)
+            else if (_selected < Viewport.Y)
             {
             {
-                _top = Math.Max (_selected, 0);
+                Viewport = Viewport with { Y = _selected };
             }
             }
 
 
             OnSelectedChanged ();
             OnSelectedChanged ();
@@ -466,9 +482,9 @@ public class ListView : View
             OnSelectedChanged ();
             OnSelectedChanged ();
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
-        else if (_selected >= _top + Bounds.Height)
+        else if (_selected >= Viewport.Y + Viewport.Height)
         {
         {
-            _top = Math.Max (_source.Count - Bounds.Height, 0);
+            Viewport = Viewport with { Y = _source.Count - Viewport.Height };
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
 
 
@@ -483,9 +499,9 @@ public class ListView : View
         {
         {
             _selected = _source.Count - 1;
             _selected = _source.Count - 1;
 
 
-            if (_top + _selected > Bounds.Height - 1)
+            if (Viewport.Y + _selected > Viewport.Height - 1)
             {
             {
-                _top = Math.Max (_selected, 0);
+                Viewport = Viewport with { Y = _selected };
             }
             }
 
 
             OnSelectedChanged ();
             OnSelectedChanged ();
@@ -502,7 +518,7 @@ public class ListView : View
         if (_selected != 0)
         if (_selected != 0)
         {
         {
             _selected = 0;
             _selected = 0;
-            _top = Math.Max (_selected, 0);
+            Viewport = Viewport with { Y = _selected };
             OnSelectedChanged ();
             OnSelectedChanged ();
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
@@ -522,7 +538,7 @@ public class ListView : View
             return true;
             return true;
         }
         }
 
 
-        int n = _selected + Bounds.Height;
+        int n = _selected + Viewport.Height;
 
 
         if (n >= _source.Count)
         if (n >= _source.Count)
         {
         {
@@ -533,13 +549,13 @@ public class ListView : View
         {
         {
             _selected = n;
             _selected = n;
 
 
-            if (_source.Count >= Bounds.Height)
+            if (_source.Count >= Viewport.Height)
             {
             {
-                _top = Math.Max (_selected, 0);
+                Viewport = Viewport with { Y = _selected };
             }
             }
             else
             else
             {
             {
-                _top = 0;
+                Viewport = Viewport with { Y = 0 };
             }
             }
 
 
             OnSelectedChanged ();
             OnSelectedChanged ();
@@ -553,7 +569,7 @@ public class ListView : View
     /// <returns></returns>
     /// <returns></returns>
     public virtual bool MovePageUp ()
     public virtual bool MovePageUp ()
     {
     {
-        int n = _selected - Bounds.Height;
+        int n = _selected - Viewport.Height;
 
 
         if (n < 0)
         if (n < 0)
         {
         {
@@ -563,7 +579,7 @@ public class ListView : View
         if (n != _selected)
         if (n != _selected)
         {
         {
             _selected = n;
             _selected = n;
-            _top = Math.Max (_selected, 0);
+            Viewport = Viewport with { Y = _selected };
             OnSelectedChanged ();
             OnSelectedChanged ();
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
@@ -599,21 +615,21 @@ public class ListView : View
                 _selected = Source.Count - 1;
                 _selected = Source.Count - 1;
             }
             }
 
 
-            if (_selected < _top)
+            if (_selected < Viewport.Y)
             {
             {
-                _top = Math.Max (_selected, 0);
+                Viewport = Viewport with { Y = _selected };
             }
             }
-            else if (_selected > _top + Bounds.Height)
+            else if (_selected > Viewport.Y + Viewport.Height)
             {
             {
-                _top = Math.Max (_selected - Bounds.Height + 1, 0);
+                Viewport = Viewport with { Y = _selected - Viewport.Height + 1 };
             }
             }
 
 
             OnSelectedChanged ();
             OnSelectedChanged ();
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
-        else if (_selected < _top)
+        else if (_selected < Viewport.Y)
         {
         {
-            _top = Math.Max (_selected, 0);
+            Viewport = Viewport with { Y = _selected };
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
 
 
@@ -621,18 +637,18 @@ public class ListView : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
 
         Attribute current = ColorScheme.Focus;
         Attribute current = ColorScheme.Focus;
         Driver.SetAttribute (current);
         Driver.SetAttribute (current);
         Move (0, 0);
         Move (0, 0);
-        Rectangle f = Bounds;
-        int item = _top;
+        Rectangle f = Viewport;
+        int item = Viewport.Y;
         bool focused = HasFocus;
         bool focused = HasFocus;
         int col = _allowsMarking ? 2 : 0;
         int col = _allowsMarking ? 2 : 0;
-        int start = _left;
+        int start = Viewport.X;
 
 
         for (var row = 0; row < f.Height; row++, item++)
         for (var row = 0; row < f.Height; row++, item++)
         {
         {
@@ -769,57 +785,17 @@ public class ListView : View
     {
     {
         if (_allowsMarking)
         if (_allowsMarking)
         {
         {
-            Move (0, _selected - _top);
+            Move (0, _selected - Viewport.Y);
         }
         }
         else
         else
         {
         {
-            Move (Bounds.Width - 1, _selected - _top);
+            Move (Viewport.Width - 1, _selected - Viewport.Y);
         }
         }
     }
     }
 
 
     /// <summary>This event is invoked when this <see cref="ListView"/> is being drawn before rendering.</summary>
     /// <summary>This event is invoked when this <see cref="ListView"/> is being drawn before rendering.</summary>
     public event EventHandler<ListViewRowEventArgs> RowRender;
     public event EventHandler<ListViewRowEventArgs> RowRender;
 
 
-    /// <summary>Scrolls the view down by <paramref name="items"/> items.</summary>
-    /// <param name="items">Number of items to scroll down.</param>
-    public virtual bool ScrollDown (int items)
-    {
-        _top = Math.Max (Math.Min (_top + items, _source.Count - 1), 0);
-        SetNeedsDisplay ();
-
-        return true;
-    }
-
-    /// <summary>Scrolls the view left.</summary>
-    /// <param name="cols">Number of columns to scroll left.</param>
-    public virtual bool ScrollLeft (int cols)
-    {
-        _left = Math.Max (_left - cols, 0);
-        SetNeedsDisplay ();
-
-        return true;
-    }
-
-    /// <summary>Scrolls the view right.</summary>
-    /// <param name="cols">Number of columns to scroll right.</param>
-    public virtual bool ScrollRight (int cols)
-    {
-        _left = Math.Max (Math.Min (_left + cols, MaxLength - 1), 0);
-        SetNeedsDisplay ();
-
-        return true;
-    }
-
-    /// <summary>Scrolls the view up by <paramref name="items"/> items.</summary>
-    /// <param name="items">Number of items to scroll up.</param>
-    public virtual bool ScrollUp (int items)
-    {
-        _top = Math.Max (_top - items, 0);
-        SetNeedsDisplay ();
-
-        return true;
-    }
-
     /// <summary>This event is raised when the selected item in the <see cref="ListView"/> has changed.</summary>
     /// <summary>This event is raised when the selected item in the <see cref="ListView"/> has changed.</summary>
     public event EventHandler<ListViewItemEventArgs> SelectedItemChanged;
     public event EventHandler<ListViewItemEventArgs> SelectedItemChanged;
 
 

+ 3 - 3
Terminal.Gui/Views/Menu/ContextMenu.cs

@@ -144,12 +144,12 @@ public sealed class ContextMenu : IDisposable
         _container = Application.Current;
         _container = Application.Current;
         _container.Closing += Container_Closing;
         _container.Closing += Container_Closing;
         _container.Deactivate += Container_Deactivate;
         _container.Deactivate += Container_Deactivate;
-        Rectangle frame = Application.Driver.Bounds;
+        Rectangle frame = Application.Driver.Screen;
         Point position = Position;
         Point position = Position;
 
 
         if (Host is { })
         if (Host is { })
         {
         {
-            Point pos = Host.BoundsToScreen (frame).Location;
+            Point pos = Host.ViewportToScreen (frame).Location;
             pos.Y += Host.Frame.Height - 1;
             pos.Y += Host.Frame.Height - 1;
 
 
             if (position != pos)
             if (position != pos)
@@ -186,7 +186,7 @@ public sealed class ContextMenu : IDisposable
                 }
                 }
                 else
                 else
                 {
                 {
-                    Point pos = Host.BoundsToScreen (frame).Location;
+                    Point pos = Host.ViewportToScreen (frame).Location;
                     position.Y = pos.Y - rect.Height - 1;
                     position.Y = pos.Y - rect.Height - 1;
                 }
                 }
             }
             }

+ 15 - 13
Terminal.Gui/Views/Menu/Menu.cs

@@ -726,7 +726,7 @@ internal sealed class Menu : View
 
 
         View view = a.View ?? this;
         View view = a.View ?? this;
 
 
-        Point boundsPoint = view.ScreenToBounds (a.X, a.Y);
+        Point boundsPoint = view.ScreenToViewport (a.X, a.Y);
         var me = new MouseEvent
         var me = new MouseEvent
         {
         {
             X = boundsPoint.X,
             X = boundsPoint.X,
@@ -757,7 +757,7 @@ internal sealed class Menu : View
         return !item.IsEnabled () ? ColorScheme.Disabled : GetNormalColor ();
         return !item.IsEnabled () ? ColorScheme.Disabled : GetNormalColor ();
     }
     }
 
 
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         if (_barItems.Children is null)
         if (_barItems.Children is null)
         {
         {
@@ -771,14 +771,14 @@ internal sealed class Menu : View
         OnDrawAdornments ();
         OnDrawAdornments ();
         OnRenderLineCanvas ();
         OnRenderLineCanvas ();
 
 
-        for (int i = Bounds.Y; i < _barItems.Children.Length; i++)
+        for (int i = Viewport.Y; i < _barItems.Children.Length; i++)
         {
         {
             if (i < 0)
             if (i < 0)
             {
             {
                 continue;
                 continue;
             }
             }
 
 
-            if (BoundsToScreen (Bounds).Y + i >= Driver.Rows)
+            if (ViewportToScreen (Viewport).Y + i >= Driver.Rows)
             {
             {
                 break;
                 break;
             }
             }
@@ -792,7 +792,8 @@ internal sealed class Menu : View
 
 
             if (item is null && BorderStyle != LineStyle.None)
             if (item is null && BorderStyle != LineStyle.None)
             {
             {
-                Move (-1, i);
+                var s = ViewportToScreen (new (-1, i, 0, 0));
+                Driver.Move (s.X, s.Y);
                 Driver.AddRune (Glyphs.LeftTee);
                 Driver.AddRune (Glyphs.LeftTee);
             }
             }
             else if (Frame.X < Driver.Cols)
             else if (Frame.X < Driver.Cols)
@@ -802,7 +803,7 @@ internal sealed class Menu : View
 
 
             Driver.SetAttribute (DetermineColorSchemeFor (item, i));
             Driver.SetAttribute (DetermineColorSchemeFor (item, i));
 
 
-            for (int p = Bounds.X; p < Frame.Width - 2; p++)
+            for (int p = Viewport.X; p < Frame.Width - 2; p++)
             {
             {
                 // This - 2 is for the border
                 // This - 2 is for the border
                 if (p < 0)
                 if (p < 0)
@@ -810,7 +811,7 @@ internal sealed class Menu : View
                     continue;
                     continue;
                 }
                 }
 
 
-                if (BoundsToScreen (Bounds).X + p >= Driver.Cols)
+                if (ViewportToScreen (Viewport).X + p >= Driver.Cols)
                 {
                 {
                     break;
                     break;
                 }
                 }
@@ -839,7 +840,8 @@ internal sealed class Menu : View
             {
             {
                 if (BorderStyle != LineStyle.None && SuperView?.Frame.Right - Frame.X > Frame.Width)
                 if (BorderStyle != LineStyle.None && SuperView?.Frame.Right - Frame.X > Frame.Width)
                 {
                 {
-                    Move (Frame.Width - 2, i);
+                    var s = ViewportToScreen (new (Frame.Width - 2, i, 0, 0));
+                    Driver.Move (s.X, s.Y);
                     Driver.AddRune (Glyphs.RightTee);
                     Driver.AddRune (Glyphs.RightTee);
                 }
                 }
 
 
@@ -875,7 +877,7 @@ internal sealed class Menu : View
                 textToDraw = item.Title;
                 textToDraw = item.Title;
             }
             }
 
 
-            Rectangle screen = BoundsToScreen (new (new (0 , i), Size.Empty));
+            Rectangle screen = ViewportToScreen (new (new (0 , i), Size.Empty));
             if (screen.X < Driver.Cols)
             if (screen.X < Driver.Cols)
             {
             {
                 Driver.Move (screen.X + 1, screen.Y);
                 Driver.Move (screen.X + 1, screen.Y);
@@ -893,10 +895,10 @@ internal sealed class Menu : View
 
 
                     // The -3 is left/right border + one space (not sure what for)
                     // The -3 is left/right border + one space (not sure what for)
                     tf.Draw (
                     tf.Draw (
-                             BoundsToScreen (new (1, i, Frame.Width - 3, 1)),
+                             ViewportToScreen (new (1, i, Frame.Width - 3, 1)),
                              i == _currentChild ? ColorScheme.Focus : GetNormalColor (),
                              i == _currentChild ? ColorScheme.Focus : GetNormalColor (),
                              i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal,
                              i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal,
-                             SuperView?.BoundsToScreen (SuperView.Bounds) ?? Rectangle.Empty
+                             SuperView?.ViewportToScreen (SuperView.Viewport) ?? Rectangle.Empty
                             );
                             );
                 }
                 }
                 else
                 else
@@ -913,7 +915,7 @@ internal sealed class Menu : View
                             ? item.Help.GetColumns ()
                             ? item.Help.GetColumns ()
                             : item.Help.GetColumns () + item.ShortcutTag.GetColumns () + 2;
                             : item.Help.GetColumns () + item.ShortcutTag.GetColumns () + 2;
                 int col = Frame.Width - l - 3;
                 int col = Frame.Width - l - 3;
-                screen = BoundsToScreen (new (new (col, i), Size.Empty));
+                screen = ViewportToScreen (new (new (col, i), Size.Empty));
 
 
                 if (screen.X < Driver.Cols)
                 if (screen.X < Driver.Cols)
                 {
                 {
@@ -939,7 +941,7 @@ internal sealed class Menu : View
     {
     {
         if (Visible)
         if (Visible)
         {
         {
-            OnDrawContent (Bounds);
+            OnDrawContent (Viewport);
         }
         }
     }
     }
 
 

+ 11 - 15
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -469,15 +469,11 @@ public class MenuBar : View
     public event EventHandler<MenuOpeningEventArgs> MenuOpening;
     public event EventHandler<MenuOpeningEventArgs> MenuOpening;
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
-        Move (0, 0);
         Driver.SetAttribute (GetNormalColor ());
         Driver.SetAttribute (GetNormalColor ());
 
 
-        for (var i = 0; i < Frame.Width; i++)
-        {
-            Driver.AddRune ((Rune)' ');
-        }
+        Clear ();
 
 
         var pos = 0;
         var pos = 0;
 
 
@@ -824,13 +820,13 @@ public class MenuBar : View
             return Point.Empty;
             return Point.Empty;
         }
         }
 
 
-        Rectangle superViewFrame = SuperView is null ? Driver.Bounds : SuperView.Frame;
+        Rectangle superViewFrame = SuperView is null ? Driver.Screen : SuperView.Frame;
         View sv = SuperView is null ? Application.Current : SuperView;
         View sv = SuperView is null ? Application.Current : SuperView;
-        Point boundsOffset = sv.GetBoundsOffset ();
+        Point viewportOffset = sv.GetViewportOffsetFromFrame ();
 
 
         return new (
         return new (
-                    superViewFrame.X - sv.Frame.X - boundsOffset.X,
-                    superViewFrame.Y - sv.Frame.Y - boundsOffset.Y
+                    superViewFrame.X - sv.Frame.X - viewportOffset.X,
+                    superViewFrame.Y - sv.Frame.Y - viewportOffset.Y
                    );
                    );
     }
     }
 
 
@@ -841,11 +837,11 @@ public class MenuBar : View
     /// <returns>The location offset.</returns>
     /// <returns>The location offset.</returns>
     internal Point GetScreenOffsetFromCurrent ()
     internal Point GetScreenOffsetFromCurrent ()
     {
     {
-        Rectangle screen = Driver.Bounds;
+        Rectangle screen = Driver.Screen;
         Rectangle currentFrame = Application.Current.Frame;
         Rectangle currentFrame = Application.Current.Frame;
-        Point boundsOffset = Application.Top.GetBoundsOffset ();
+        Point viewportOffset = Application.Top.GetViewportOffsetFromFrame ();
 
 
-        return new (screen.X - currentFrame.X - boundsOffset.X, screen.Y - currentFrame.Y - boundsOffset.Y);
+        return new (screen.X - currentFrame.X - viewportOffset.X, screen.Y - currentFrame.Y - viewportOffset.Y);
     }
     }
 
 
     internal void NextMenu (bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
     internal void NextMenu (bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
@@ -1319,7 +1315,7 @@ public class MenuBar : View
 
 
         if (mi.IsTopLevel)
         if (mi.IsTopLevel)
         {
         {
-            Rectangle screen = BoundsToScreen (new (new (0, i), Size.Empty));
+            Rectangle screen = ViewportToScreen (new (new (0, i), Size.Empty));
             var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = mi };
             var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = mi };
             menu.Run (mi.Action);
             menu.Run (mi.Action);
             menu.Dispose ();
             menu.Dispose ();
@@ -1689,7 +1685,7 @@ public class MenuBar : View
                     {
                     {
                         if (Menus [i].IsTopLevel)
                         if (Menus [i].IsTopLevel)
                         {
                         {
-                            Rectangle screen = BoundsToScreen (new (new (0, i), Size.Empty));
+                            Rectangle screen = ViewportToScreen (new (new (0, i), Size.Empty));
                             var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = Menus [i] };
                             var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = Menus [i] };
                             menu.Run (Menus [i].Action);
                             menu.Run (Menus [i].Action);
                             menu.Dispose ();
                             menu.Dispose ();

+ 2 - 2
Terminal.Gui/Views/MessageBox.cs

@@ -422,7 +422,7 @@ public static class MessageBox
             }
             }
 
 
             // TODO: replace with Dim.Fit when implemented
             // TODO: replace with Dim.Fit when implemented
-            Rectangle maxBounds = d.SuperView?.Bounds ?? Application.Top.Bounds;
+            Rectangle maxBounds = d.SuperView?.Viewport ?? Application.Top.Viewport;
 
 
             Thickness adornmentsThickness = d.GetAdornmentsThickness ();
             Thickness adornmentsThickness = d.GetAdornmentsThickness ();
 
 
@@ -467,7 +467,7 @@ public static class MessageBox
                                      + adornmentsThickness.Vertical);
                                      + adornmentsThickness.Vertical);
             }
             }
 
 
-            d.SetRelativeLayout (d.SuperView?.Frame ?? Application.Top.Frame);
+            d.SetRelativeLayout (d.SuperView?.ContentSize ?? Application.Top.ContentSize);
             d.LayoutSubviews ();
             d.LayoutSubviews ();
         }
         }
     }
     }

+ 10 - 10
Terminal.Gui/Views/ProgressBar.cs

@@ -143,7 +143,7 @@ public class ProgressBar : View
     }
     }
 
 
     ///<inheritdoc/>
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         Driver.SetAttribute (GetHotNormalColor ());
         Driver.SetAttribute (GetHotNormalColor ());
 
 
@@ -151,7 +151,7 @@ public class ProgressBar : View
 
 
         if (_isActivity)
         if (_isActivity)
         {
         {
-            for (var i = 0; i < Bounds.Width; i++)
+            for (var i = 0; i < Viewport.Width; i++)
             {
             {
                 if (Array.IndexOf (_activityPos, i) != -1)
                 if (Array.IndexOf (_activityPos, i) != -1)
                 {
                 {
@@ -165,15 +165,15 @@ public class ProgressBar : View
         }
         }
         else
         else
         {
         {
-            var mid = (int)(_fraction * Bounds.Width);
+            var mid = (int)(_fraction * Viewport.Width);
             int i;
             int i;
 
 
-            for (i = 0; (i < mid) & (i < Bounds.Width); i++)
+            for (i = 0; (i < mid) & (i < Viewport.Width); i++)
             {
             {
                 Driver.AddRune (SegmentCharacter);
                 Driver.AddRune (SegmentCharacter);
             }
             }
 
 
-            for (; i < Bounds.Width; i++)
+            for (; i < Viewport.Width; i++)
             {
             {
                 Driver.AddRune ((Rune)' ');
                 Driver.AddRune ((Rune)' ');
             }
             }
@@ -190,10 +190,10 @@ public class ProgressBar : View
             }
             }
 
 
             tf?.Draw (
             tf?.Draw (
-                      BoundsToScreen (Bounds),
+                      ViewportToScreen (Viewport),
                       attr,
                       attr,
                       ColorScheme.Normal,
                       ColorScheme.Normal,
-                      SuperView?.BoundsToScreen (SuperView.Bounds) ?? default (Rectangle)
+                      SuperView?.ViewportToScreen (SuperView.Viewport) ?? default (Rectangle)
                      );
                      );
         }
         }
     }
     }
@@ -244,13 +244,13 @@ public class ProgressBar : View
 
 
                 _delta = 1;
                 _delta = 1;
             }
             }
-            else if (_activityPos [0] >= Bounds.Width)
+            else if (_activityPos [0] >= Viewport.Width)
             {
             {
                 if (_bidirectionalMarquee)
                 if (_bidirectionalMarquee)
                 {
                 {
                     for (var i = 0; i < _activityPos.Length; i++)
                     for (var i = 0; i < _activityPos.Length; i++)
                     {
                     {
-                        _activityPos [i] = Bounds.Width + i - 2;
+                        _activityPos [i] = Viewport.Width + i - 2;
                     }
                     }
 
 
                     _delta = -1;
                     _delta = -1;
@@ -291,7 +291,7 @@ public class ProgressBar : View
 
 
     private void SetInitialProperties ()
     private void SetInitialProperties ()
     {
     {
-        Height = 1; // This will be updated when Bounds is updated in ProgressBar_LayoutStarted
+        Height = 1; // This will be updated when Viewport is updated in ProgressBar_LayoutStarted
         CanFocus = false;
         CanFocus = false;
         _fraction = 0;
         _fraction = 0;
         LayoutStarted += ProgressBar_LayoutStarted;
         LayoutStarted += ProgressBar_LayoutStarted;

+ 2 - 2
Terminal.Gui/Views/RadioGroup.cs

@@ -196,9 +196,9 @@ public class RadioGroup : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
 
         Driver.SetAttribute (GetNormalColor ());
         Driver.SetAttribute (GetNormalColor ());
 
 

+ 52 - 42
Terminal.Gui/Views/ScrollBarView.cs

@@ -152,14 +152,14 @@ public class ScrollBarView : View
                 _keepContentAlwaysInViewport = value;
                 _keepContentAlwaysInViewport = value;
                 var pos = 0;
                 var pos = 0;
 
 
-                if (value && !_vertical && _position + Host.Bounds.Width > _size)
+                if (value && !_vertical && _position + Host.Viewport.Width > _size)
                 {
                 {
-                    pos = _size - Host.Bounds.Width + (_showBothScrollIndicator ? 1 : 0);
+                    pos = _size - Host.Viewport.Width + (_showBothScrollIndicator ? 1 : 0);
                 }
                 }
 
 
-                if (value && _vertical && _position + Host.Bounds.Height > _size)
+                if (value && _vertical && _position + Host.Viewport.Height > _size)
                 {
                 {
-                    pos = _size - Host.Bounds.Height + (_showBothScrollIndicator ? 1 : 0);
+                    pos = _size - Host.Viewport.Height + (_showBothScrollIndicator ? 1 : 0);
                 }
                 }
 
 
                 if (pos != 0)
                 if (pos != 0)
@@ -204,13 +204,12 @@ public class ScrollBarView : View
         get => _position;
         get => _position;
         set
         set
         {
         {
-            _position = value;
-
-            if (IsInitialized)
+            if (_position == value)
             {
             {
-                // We're not initialized so we can't do anything fancy. Just cache value.
-                SetPosition (value);
+                return;
             }
             }
+
+            SetPosition (value);
         }
         }
     }
     }
 
 
@@ -262,7 +261,7 @@ public class ScrollBarView : View
 
 
             if (IsInitialized)
             if (IsInitialized)
             {
             {
-                SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+                SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size);
                 ShowHideScrollBars (false);
                 ShowHideScrollBars (false);
                 SetNeedsDisplay ();
                 SetNeedsDisplay ();
             }
             }
@@ -275,7 +274,7 @@ public class ScrollBarView : View
     public event EventHandler ChangedPosition;
     public event EventHandler ChangedPosition;
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    protected internal override bool OnMouseEvent  (MouseEvent mouseEvent)
+    protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
     {
     {
         if (mouseEvent.Flags != MouseFlags.Button1Pressed
         if (mouseEvent.Flags != MouseFlags.Button1Pressed
             && mouseEvent.Flags != MouseFlags.Button1DoubleClicked
             && mouseEvent.Flags != MouseFlags.Button1DoubleClicked
@@ -301,7 +300,7 @@ public class ScrollBarView : View
         }
         }
 
 
         int location = _vertical ? mouseEvent.Y : mouseEvent.X;
         int location = _vertical ? mouseEvent.Y : mouseEvent.X;
-        int barsize = _vertical ? Bounds.Height : Bounds.Width;
+        int barsize = _vertical ? Viewport.Height : Viewport.Width;
         int posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1;
         int posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1;
         int posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1;
         int posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1;
         barsize -= 2;
         barsize -= 2;
@@ -449,7 +448,7 @@ public class ScrollBarView : View
     public virtual void OnChangedPosition () { ChangedPosition?.Invoke (this, EventArgs.Empty); }
     public virtual void OnChangedPosition () { ChangedPosition?.Invoke (this, EventArgs.Empty); }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         if (ColorScheme is null || ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible))
         if (ColorScheme is null || ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible))
         {
         {
@@ -461,7 +460,7 @@ public class ScrollBarView : View
             return;
             return;
         }
         }
 
 
-        if (Size == 0 || (_vertical && Bounds.Height == 0) || (!_vertical && Bounds.Width == 0))
+        if (Size == 0 || (_vertical && Viewport.Height == 0) || (!_vertical && Viewport.Width == 0))
         {
         {
             return;
             return;
         }
         }
@@ -470,13 +469,13 @@ public class ScrollBarView : View
 
 
         if (_vertical)
         if (_vertical)
         {
         {
-            if (Bounds.Right < Bounds.Width - 1)
+            if (Viewport.Right < Viewport.Width - 1)
             {
             {
                 return;
                 return;
             }
             }
 
 
-            int col = Bounds.Width - 1;
-            int bh = Bounds.Height;
+            int col = Viewport.Width - 1;
+            int bh = Viewport.Height;
             Rune special;
             Rune special;
 
 
             if (bh < 4)
             if (bh < 4)
@@ -486,7 +485,7 @@ public class ScrollBarView : View
 
 
                 Move (col, 0);
                 Move (col, 0);
 
 
-                if (Bounds.Height == 1)
+                if (Viewport.Height == 1)
                 {
                 {
                     Driver.AddRune (Glyphs.Diamond);
                     Driver.AddRune (Glyphs.Diamond);
                 }
                 }
@@ -495,15 +494,15 @@ public class ScrollBarView : View
                     Driver.AddRune (Glyphs.UpArrow);
                     Driver.AddRune (Glyphs.UpArrow);
                 }
                 }
 
 
-                if (Bounds.Height == 3)
+                if (Viewport.Height == 3)
                 {
                 {
                     Move (col, 1);
                     Move (col, 1);
                     Driver.AddRune (Glyphs.Diamond);
                     Driver.AddRune (Glyphs.Diamond);
                 }
                 }
 
 
-                if (Bounds.Height > 1)
+                if (Viewport.Height > 1)
                 {
                 {
-                    Move (col, Bounds.Height - 1);
+                    Move (col, Viewport.Height - 1);
                     Driver.AddRune (Glyphs.DownArrow);
                     Driver.AddRune (Glyphs.DownArrow);
                 }
                 }
             }
             }
@@ -524,8 +523,7 @@ public class ScrollBarView : View
                     by1 = Math.Max (by1 - 1, 0);
                     by1 = Math.Max (by1 - 1, 0);
                 }
                 }
 
 
-                Move (col, 0);
-                Driver.AddRune (Glyphs.UpArrow);
+                AddRune (col, 0, Glyphs.UpArrow);
 
 
                 var hasTopTee = false;
                 var hasTopTee = false;
                 var hasDiamond = false;
                 var hasDiamond = false;
@@ -533,7 +531,6 @@ public class ScrollBarView : View
 
 
                 for (var y = 0; y < bh; y++)
                 for (var y = 0; y < bh; y++)
                 {
                 {
-                    Move (col, y + 1);
 
 
                     if ((y < by1 || y > by2) && ((_position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee)))
                     if ((y < by1 || y > by2) && ((_position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee)))
                     {
                     {
@@ -567,28 +564,26 @@ public class ScrollBarView : View
                         }
                         }
                     }
                     }
 
 
-                    Driver.AddRune (special);
+                    AddRune (col, y + 1, special);
                 }
                 }
 
 
                 if (!hasTopTee)
                 if (!hasTopTee)
                 {
                 {
-                    Move (col, Bounds.Height - 2);
-                    Driver.AddRune (Glyphs.TopTee);
+                    AddRune (col, Viewport.Height - 2, Glyphs.TopTee);
                 }
                 }
 
 
-                Move (col, Bounds.Height - 1);
-                Driver.AddRune (Glyphs.DownArrow);
+                AddRune (col, Viewport.Height - 1, Glyphs.DownArrow);
             }
             }
         }
         }
         else
         else
         {
         {
-            if (Bounds.Bottom < Bounds.Height - 1)
+            if (Viewport.Bottom < Viewport.Height - 1)
             {
             {
                 return;
                 return;
             }
             }
 
 
-            int row = Bounds.Height - 1;
-            int bw = Bounds.Width;
+            int row = Viewport.Height - 1;
+            int bw = Viewport.Width;
             Rune special;
             Rune special;
 
 
             if (bw < 4)
             if (bw < 4)
@@ -663,7 +658,7 @@ public class ScrollBarView : View
 
 
                 if (!hasLeftTee)
                 if (!hasLeftTee)
                 {
                 {
-                    Move (Bounds.Width - 2, row);
+                    Move (Viewport.Width - 2, row);
                     Driver.AddRune (Glyphs.LeftTee);
                     Driver.AddRune (Glyphs.LeftTee);
                 }
                 }
 
 
@@ -685,7 +680,7 @@ public class ScrollBarView : View
 
 
     internal bool CanScroll (int n, out int max, bool isVertical = false)
     internal bool CanScroll (int n, out int max, bool isVertical = false)
     {
     {
-        if (Host?.Bounds.IsEmpty != false)
+        if (Host?.Viewport.IsEmpty != false)
         {
         {
             max = 0;
             max = 0;
 
 
@@ -706,7 +701,7 @@ public class ScrollBarView : View
 
 
     private bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false)
     private bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false)
     {
     {
-        int barsize = scrollBarView._vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width;
+        int barsize = scrollBarView._vertical ? scrollBarView.Viewport.Height : scrollBarView.Viewport.Width;
 
 
         if (barsize == 0 || barsize >= scrollBarView._size)
         if (barsize == 0 || barsize >= scrollBarView._size)
         {
         {
@@ -845,15 +840,15 @@ public class ScrollBarView : View
 
 
     private int GetBarsize (bool isVertical)
     private int GetBarsize (bool isVertical)
     {
     {
-        if (Host?.Bounds.IsEmpty != false)
+        if (Host?.Viewport.IsEmpty != false)
         {
         {
             return 0;
             return 0;
         }
         }
 
 
         return isVertical ? KeepContentAlwaysInViewport
         return isVertical ? KeepContentAlwaysInViewport
-                                ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1)
+                                ? Host.Viewport.Height + (_showBothScrollIndicator ? -2 : -1)
                                 : 0 :
                                 : 0 :
-               KeepContentAlwaysInViewport ? Host.Bounds.Width + (_showBothScrollIndicator ? -2 : -1) : 0;
+               KeepContentAlwaysInViewport ? Host.Viewport.Width + (_showBothScrollIndicator ? -2 : -1) : 0;
     }
     }
 
 
     private void Host_EnabledChanged (object sender, EventArgs e)
     private void Host_EnabledChanged (object sender, EventArgs e)
@@ -890,7 +885,7 @@ public class ScrollBarView : View
     private void ScrollBarView_Initialized (object sender, EventArgs e)
     private void ScrollBarView_Initialized (object sender, EventArgs e)
     {
     {
         SetWidthHeight ();
         SetWidthHeight ();
-        SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame);
+        SetRelativeLayout (SuperView?.Frame.Size ?? Host?.Frame.Size ?? Frame.Size);
 
 
         if (OtherScrollBarView is null)
         if (OtherScrollBarView is null)
         {
         {
@@ -904,7 +899,22 @@ public class ScrollBarView : View
     // Helper to assist Initialized event handler
     // Helper to assist Initialized event handler
     private void SetPosition (int newPosition)
     private void SetPosition (int newPosition)
     {
     {
-        if (CanScroll (newPosition - _position, out int max, _vertical))
+        if (!IsInitialized)
+        {
+            // We're not initialized so we can't do anything fancy. Just cache value.
+            _position = newPosition;
+
+            return;
+        }
+
+        if (newPosition < 0)
+        {
+            _position = 0;
+            SetNeedsDisplay ();
+
+            return;
+        }
+        else if (CanScroll (newPosition - _position, out int max, _vertical))
         {
         {
             if (max == newPosition - _position)
             if (max == newPosition - _position)
             {
             {
@@ -995,11 +1005,11 @@ public class ScrollBarView : View
         }
         }
 
 
         SetWidthHeight ();
         SetWidthHeight ();
-        SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+        SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size);
 
 
         if (_otherScrollBarView is { })
         if (_otherScrollBarView is { })
         {
         {
-            OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+            OtherScrollBarView.SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size);
         }
         }
 
 
         if (_showBothScrollIndicator)
         if (_showBothScrollIndicator)

+ 49 - 47
Terminal.Gui/Views/ScrollView.cs

@@ -21,7 +21,7 @@ namespace Terminal.Gui;
 ///     <para>
 ///     <para>
 ///         The subviews that are added to this <see cref="Gui.ScrollView"/> are offset by the
 ///         The subviews that are added to this <see cref="Gui.ScrollView"/> are offset by the
 ///         <see cref="ContentOffset"/> property.  The view itself is a window into the space represented by the
 ///         <see cref="ContentOffset"/> property.  The view itself is a window into the space represented by the
-///         <see cref="ContentSize"/>.
+///         <see cref="View.ContentSize"/>.
 ///     </para>
 ///     </para>
 ///     <para>Use the</para>
 ///     <para>Use the</para>
 /// </remarks>
 /// </remarks>
@@ -33,7 +33,6 @@ public class ScrollView : View
     private bool _autoHideScrollBars = true;
     private bool _autoHideScrollBars = true;
     private View _contentBottomRightCorner;
     private View _contentBottomRightCorner;
     private Point _contentOffset;
     private Point _contentOffset;
-    private Size _contentSize;
     private bool _keepContentAlwaysInViewport = true;
     private bool _keepContentAlwaysInViewport = true;
     private bool _showHorizontalScrollIndicator;
     private bool _showHorizontalScrollIndicator;
     private bool _showVerticalScrollIndicator;
     private bool _showVerticalScrollIndicator;
@@ -85,14 +84,14 @@ public class ScrollView : View
         AddCommand (Command.ScrollDown, () => ScrollDown (1));
         AddCommand (Command.ScrollDown, () => ScrollDown (1));
         AddCommand (Command.ScrollLeft, () => ScrollLeft (1));
         AddCommand (Command.ScrollLeft, () => ScrollLeft (1));
         AddCommand (Command.ScrollRight, () => ScrollRight (1));
         AddCommand (Command.ScrollRight, () => ScrollRight (1));
-        AddCommand (Command.PageUp, () => ScrollUp (Bounds.Height));
-        AddCommand (Command.PageDown, () => ScrollDown (Bounds.Height));
-        AddCommand (Command.PageLeft, () => ScrollLeft (Bounds.Width));
-        AddCommand (Command.PageRight, () => ScrollRight (Bounds.Width));
-        AddCommand (Command.TopHome, () => ScrollUp (_contentSize.Height));
-        AddCommand (Command.BottomEnd, () => ScrollDown (_contentSize.Height));
-        AddCommand (Command.LeftHome, () => ScrollLeft (_contentSize.Width));
-        AddCommand (Command.RightEnd, () => ScrollRight (_contentSize.Width));
+        AddCommand (Command.PageUp, () => ScrollUp (Viewport.Height));
+        AddCommand (Command.PageDown, () => ScrollDown (Viewport.Height));
+        AddCommand (Command.PageLeft, () => ScrollLeft (Viewport.Width));
+        AddCommand (Command.PageRight, () => ScrollRight (Viewport.Width));
+        AddCommand (Command.TopHome, () => ScrollUp (ContentSize.Height));
+        AddCommand (Command.BottomEnd, () => ScrollDown (ContentSize.Height));
+        AddCommand (Command.LeftHome, () => ScrollLeft (ContentSize.Width));
+        AddCommand (Command.RightEnd, () => ScrollRight (ContentSize.Width));
 
 
         // Default keybindings for this view
         // Default keybindings for this view
         KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
         KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
@@ -134,6 +133,14 @@ public class ScrollView : View
                            _vertical.ChangedPosition += delegate { ContentOffset = new Point (ContentOffset.X, _vertical.Position); };
                            _vertical.ChangedPosition += delegate { ContentOffset = new Point (ContentOffset.X, _vertical.Position); };
                            _horizontal.ChangedPosition += delegate { ContentOffset = new Point (_horizontal.Position, ContentOffset.Y); };
                            _horizontal.ChangedPosition += delegate { ContentOffset = new Point (_horizontal.Position, ContentOffset.Y); };
                        };
                        };
+        ContentSizeChanged += ScrollViewContentSizeChanged;
+    }
+
+    private void ScrollViewContentSizeChanged (object sender, SizeChangedEventArgs e)
+    {
+        _contentView.Frame = new Rectangle (ContentOffset, e.Size with {Width = e.Size.Width-1, Height = e.Size.Height-1});
+        _vertical.Size = e.Size.Height;
+        _horizontal.Size = e.Size.Width;
     }
     }
 
 
     private void Application_UnGrabbedMouse (object sender, ViewEventArgs e)
     private void Application_UnGrabbedMouse (object sender, ViewEventArgs e)
@@ -194,7 +201,6 @@ public class ScrollView : View
             {
             {
                 // We're not initialized so we can't do anything fancy. Just cache value.
                 // We're not initialized so we can't do anything fancy. Just cache value.
                 _contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y));
                 _contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y));
-                ;
 
 
                 return;
                 return;
             }
             }
@@ -203,23 +209,23 @@ public class ScrollView : View
         }
         }
     }
     }
 
 
-    /// <summary>Represents the contents of the data shown inside the scrollview</summary>
-    /// <value>The size of the content.</value>
-    public Size ContentSize
-    {
-        get => _contentSize;
-        set
-        {
-            if (_contentSize != value)
-            {
-                _contentSize = value;
-                _contentView.Frame = new Rectangle (_contentOffset, value);
-                _vertical.Size = _contentSize.Height;
-                _horizontal.Size = _contentSize.Width;
-                SetNeedsDisplay ();
-            }
-        }
-    }
+    ///// <summary>Represents the contents of the data shown inside the scrollview</summary>
+    ///// <value>The size of the content.</value>
+    //public new Size ContentSize
+    //{
+    //    get => ContentSize;
+    //    set
+    //    {
+    //        if (ContentSize != value)
+    //        {
+    //            ContentSize = value;
+    //            _contentView.Frame = new Rectangle (_contentOffset, value);
+    //            _vertical.Size = ContentSize.Height;
+    //            _horizontal.Size = ContentSize.Width;
+    //            SetNeedsDisplay ();
+    //        }
+    //    }
+    //}
 
 
     /// <summary>Get or sets if the view-port is kept always visible in the area of this <see cref="ScrollView"/></summary>
     /// <summary>Get or sets if the view-port is kept always visible in the area of this <see cref="ScrollView"/></summary>
     public bool KeepContentAlwaysInViewport
     public bool KeepContentAlwaysInViewport
@@ -234,26 +240,26 @@ public class ScrollView : View
                 _horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value;
                 _horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value;
                 Point p = default;
                 Point p = default;
 
 
-                if (value && -_contentOffset.X + Bounds.Width > _contentSize.Width)
+                if (value && -_contentOffset.X + Viewport.Width > ContentSize.Width)
                 {
                 {
                     p = new Point (
                     p = new Point (
-                                   _contentSize.Width - Bounds.Width + (_showVerticalScrollIndicator ? 1 : 0),
+                                   ContentSize.Width - Viewport.Width + (_showVerticalScrollIndicator ? 1 : 0),
                                    -_contentOffset.Y
                                    -_contentOffset.Y
                                   );
                                   );
                 }
                 }
 
 
-                if (value && -_contentOffset.Y + Bounds.Height > _contentSize.Height)
+                if (value && -_contentOffset.Y + Viewport.Height > ContentSize.Height)
                 {
                 {
                     if (p == default (Point))
                     if (p == default (Point))
                     {
                     {
                         p = new Point (
                         p = new Point (
                                        -_contentOffset.X,
                                        -_contentOffset.X,
-                                       _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0)
+                                       ContentSize.Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0)
                                       );
                                       );
                     }
                     }
                     else
                     else
                     {
                     {
-                        p.Y = _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0);
+                        p.Y = ContentSize.Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0);
                     }
                     }
                 }
                 }
 
 
@@ -359,14 +365,12 @@ public class ScrollView : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         SetViewsNeedsDisplay ();
         SetViewsNeedsDisplay ();
 
 
-        Rectangle savedClip = ClipToBounds ();
-
         // TODO: It's bad practice for views to always clear a view. It negates clipping.
         // TODO: It's bad practice for views to always clear a view. It negates clipping.
-        Clear (contentArea);
+        Clear ();
 
 
         if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0)
         if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0)
         {
         {
@@ -374,8 +378,6 @@ public class ScrollView : View
         }
         }
 
 
         DrawScrollBars ();
         DrawScrollBars ();
-
-        Driver.Clip = savedClip;
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
@@ -613,7 +615,7 @@ public class ScrollView : View
     {
     {
         // INTENT: Unclear intent. How about a call to Offset?
         // INTENT: Unclear intent. How about a call to Offset?
         _contentOffset = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y));
         _contentOffset = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y));
-        _contentView.Frame = new Rectangle (_contentOffset, _contentSize);
+        _contentView.Frame = new Rectangle (_contentOffset, ContentSize);
         int p = Math.Max (0, -_contentOffset.Y);
         int p = Math.Max (0, -_contentOffset.Y);
 
 
         if (_vertical.Position != p)
         if (_vertical.Position != p)
@@ -644,7 +646,7 @@ public class ScrollView : View
         bool v = false, h = false;
         bool v = false, h = false;
         var p = false;
         var p = false;
 
 
-        if (Bounds.Height == 0 || Bounds.Height > _contentSize.Height)
+        if (Viewport.Height == 0 || Viewport.Height > ContentSize.Height)
         {
         {
             if (ShowVerticalScrollIndicator)
             if (ShowVerticalScrollIndicator)
             {
             {
@@ -653,7 +655,7 @@ public class ScrollView : View
 
 
             v = false;
             v = false;
         }
         }
-        else if (Bounds.Height > 0 && Bounds.Height == _contentSize.Height)
+        else if (Viewport.Height > 0 && Viewport.Height == ContentSize.Height)
         {
         {
             p = true;
             p = true;
         }
         }
@@ -667,7 +669,7 @@ public class ScrollView : View
             v = true;
             v = true;
         }
         }
 
 
-        if (Bounds.Width == 0 || Bounds.Width > _contentSize.Width)
+        if (Viewport.Width == 0 || Viewport.Width > ContentSize.Width)
         {
         {
             if (ShowHorizontalScrollIndicator)
             if (ShowHorizontalScrollIndicator)
             {
             {
@@ -676,7 +678,7 @@ public class ScrollView : View
 
 
             h = false;
             h = false;
         }
         }
-        else if (Bounds.Width > 0 && Bounds.Width == _contentSize.Width && p)
+        else if (Viewport.Width > 0 && Viewport.Width == ContentSize.Width && p)
         {
         {
             if (ShowHorizontalScrollIndicator)
             if (ShowHorizontalScrollIndicator)
             {
             {
@@ -728,13 +730,13 @@ public class ScrollView : View
 
 
         if (v)
         if (v)
         {
         {
-            _vertical.SetRelativeLayout (Bounds);
+            _vertical.SetRelativeLayout (Viewport.Size);
             _vertical.Draw ();
             _vertical.Draw ();
         }
         }
 
 
         if (h)
         if (h)
         {
         {
-            _horizontal.SetRelativeLayout (Bounds);
+            _horizontal.SetRelativeLayout (Viewport.Size);
             _horizontal.Draw ();
             _horizontal.Draw ();
         }
         }
 
 
@@ -742,7 +744,7 @@ public class ScrollView : View
 
 
         if (v && h)
         if (v && h)
         {
         {
-            _contentBottomRightCorner.SetRelativeLayout (Bounds);
+            _contentBottomRightCorner.SetRelativeLayout (Viewport.Size);
             _contentBottomRightCorner.Draw ();
             _contentBottomRightCorner.Draw ();
         }
         }
     }
     }

+ 22 - 22
Terminal.Gui/Views/Slider.cs

@@ -374,7 +374,7 @@ public class Slider<T> : View
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     If <see langword="true"/> the slider will be sized to fit the available space (the Bounds of the the
+    ///     If <see langword="true"/> the slider will be sized to fit the available space (the Viewport of the the
     ///     SuperView).
     ///     SuperView).
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
@@ -663,17 +663,17 @@ public class Slider<T> : View
 
 
         if (AutoSize)
         if (AutoSize)
         {
         {
-            // Max size is SuperView's Bounds. Min Size is size that will fit.
+            // Max size is SuperView's Viewport. Min Size is size that will fit.
             if (SuperView is { })
             if (SuperView is { })
             {
             {
-                // Calculate the size of the slider based on the size of the SuperView's Bounds.
+                // Calculate the size of the slider based on the size of the SuperView's Viewport.
                 if (_config._sliderOrientation == Orientation.Horizontal)
                 if (_config._sliderOrientation == Orientation.Horizontal)
                 {
                 {
-                    size = int.Min (SuperView.Bounds.Width, CalcBestLength ());
+                    size = int.Min (SuperView.Viewport.Width, CalcBestLength ());
                 }
                 }
                 else
                 else
                 {
                 {
-                    size = int.Min (SuperView.Bounds.Height, CalcBestLength ());
+                    size = int.Min (SuperView.Viewport.Height, CalcBestLength ());
                 }
                 }
             }
             }
             else
             else
@@ -686,14 +686,14 @@ public class Slider<T> : View
         }
         }
         else
         else
         {
         {
-            // Fit Slider to the Bounds
+            // Fit Slider to the Viewport
             if (_config._sliderOrientation == Orientation.Horizontal)
             if (_config._sliderOrientation == Orientation.Horizontal)
             {
             {
-                size = Bounds.Width;
+                size = Viewport.Width;
             }
             }
             else
             else
             {
             {
-                size = Bounds.Height;
+                size = Viewport.Height;
             }
             }
         }
         }
 
 
@@ -777,15 +777,15 @@ public class Slider<T> : View
 
 
         if (_config._sliderOrientation == Orientation.Horizontal)
         if (_config._sliderOrientation == Orientation.Horizontal)
         {
         {
-            Bounds = new (
-                          Bounds.Location,
+            Viewport = new (
+                          Viewport.Location,
                           new (
                           new (
                                int.Min (
                                int.Min (
-                                        SuperView.Bounds.Width - adornmentsThickness.Horizontal,
+                                        SuperView.Viewport.Width - adornmentsThickness.Horizontal,
                                         CalcBestLength ()
                                         CalcBestLength ()
                                        ),
                                        ),
                                int.Min (
                                int.Min (
-                                        SuperView.Bounds.Height - adornmentsThickness.Vertical,
+                                        SuperView.Viewport.Height - adornmentsThickness.Vertical,
                                         CalcThickness ()
                                         CalcThickness ()
                                        )
                                        )
                               )
                               )
@@ -793,15 +793,15 @@ public class Slider<T> : View
         }
         }
         else
         else
         {
         {
-            Bounds = new (
-                          Bounds.Location,
+            Viewport = new (
+                          Viewport.Location,
                           new (
                           new (
                                int.Min (
                                int.Min (
-                                        SuperView.Bounds.Width - adornmentsThickness.Horizontal,
+                                        SuperView.Viewport.Width - adornmentsThickness.Horizontal,
                                         CalcThickness ()
                                         CalcThickness ()
                                        ),
                                        ),
                                int.Min (
                                int.Min (
-                                        SuperView.Bounds.Height - adornmentsThickness.Vertical,
+                                        SuperView.Viewport.Height - adornmentsThickness.Vertical,
                                         CalcBestLength ()
                                         CalcBestLength ()
                                        )
                                        )
                               )
                               )
@@ -988,7 +988,7 @@ public class Slider<T> : View
 
 
         if (TryGetPositionByOption (FocusedOption, out (int x, int y) position))
         if (TryGetPositionByOption (FocusedOption, out (int x, int y) position))
         {
         {
-            if (IsInitialized && Bounds.Contains (position.x, position.y))
+            if (IsInitialized && Viewport.Contains (position.x, position.y))
             {
             {
                 Move (position.x, position.y);
                 Move (position.x, position.y);
             }
             }
@@ -996,7 +996,7 @@ public class Slider<T> : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         // TODO: make this more surgical to reduce repaint
         // TODO: make this more surgical to reduce repaint
 
 
@@ -1009,9 +1009,9 @@ public class Slider<T> : View
 #if (DEBUG)
 #if (DEBUG)
         Driver?.SetAttribute (new Attribute (Color.White, Color.Red));
         Driver?.SetAttribute (new Attribute (Color.White, Color.Red));
 
 
-        for (var y = 0; y < contentArea.Height; y++)
+        for (var y = 0; y < viewport.Height; y++)
         {
         {
-            for (var x = 0; x < contentArea.Width; x++)
+            for (var x = 0; x < viewport.Width; x++)
             {
             {
                 // MoveAndAdd (x, y, '·');
                 // MoveAndAdd (x, y, '·');
             }
             }
@@ -1073,7 +1073,7 @@ public class Slider<T> : View
     private void DrawSlider ()
     private void DrawSlider ()
     {
     {
         // TODO: be more surgical on clear
         // TODO: be more surgical on clear
-        Clear (Bounds);
+        Clear ();
 
 
         // Attributes
         // Attributes
 
 
@@ -1241,7 +1241,7 @@ public class Slider<T> : View
             }
             }
         }
         }
 
 
-        int remaining = isVertical ? Bounds.Height - y : Bounds.Width - x;
+        int remaining = isVertical ? Viewport.Height - y : Viewport.Width - x;
 
 
         // Right Spacing
         // Right Spacing
         if (_config._showEndSpacing)
         if (_config._showEndSpacing)

+ 1 - 1
Terminal.Gui/Views/StatusBar.cs

@@ -172,7 +172,7 @@ public class StatusBar : View
     }
     }
 
 
     ///<inheritdoc/>
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         Move (0, 0);
         Move (0, 0);
         Driver.SetAttribute (GetNormalColor ());
         Driver.SetAttribute (GetNormalColor ());

+ 14 - 14
Terminal.Gui/Views/TabView.cs

@@ -297,7 +297,7 @@ public class TabView : View
         }
         }
 
 
         // if current viewport does not include the selected tab
         // if current viewport does not include the selected tab
-        if (!CalculateViewport (Bounds).Any (r => Equals (SelectedTab, r.Tab)))
+        if (!CalculateViewport (Viewport).Any (r => Equals (SelectedTab, r.Tab)))
         {
         {
             // Set scroll offset so the first tab rendered is the
             // Set scroll offset so the first tab rendered is the
             TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab));
             TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab));
@@ -311,14 +311,14 @@ public class TabView : View
     public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); }
     public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         Driver.SetAttribute (GetNormalColor ());
         Driver.SetAttribute (GetNormalColor ());
 
 
         if (Tabs.Any ())
         if (Tabs.Any ())
         {
         {
-            Rectangle savedClip = ClipToBounds ();
-            _tabsBar.OnDrawContent (contentArea);
+            Rectangle savedClip = SetClip ();
+            _tabsBar.OnDrawContent (viewport);
             _contentView.SetNeedsDisplay ();
             _contentView.SetNeedsDisplay ();
             _contentView.Draw ();
             _contentView.Draw ();
             Driver.Clip = savedClip;
             Driver.Clip = savedClip;
@@ -326,7 +326,7 @@ public class TabView : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContentComplete (Rectangle contentArea) { _tabsBar.OnDrawContentComplete (contentArea); }
+    public override void OnDrawContentComplete (Rectangle viewport) { _tabsBar.OnDrawContentComplete (viewport); }
 
 
     /// <summary>
     /// <summary>
     ///     Removes the given <paramref name="tab"/> from <see cref="Tabs"/>. Caller is responsible for disposing the
     ///     Removes the given <paramref name="tab"/> from <see cref="Tabs"/>. Caller is responsible for disposing the
@@ -657,12 +657,12 @@ public class TabView : View
             return false;
             return false;
         }
         }
 
 
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
         {
-            _host._tabLocations = _host.CalculateViewport (Bounds).ToArray ();
+            _host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
 
 
             // clear any old text
             // clear any old text
-            Clear (contentArea);
+            Clear ();
 
 
             RenderTabLine ();
             RenderTabLine ();
 
 
@@ -670,7 +670,7 @@ public class TabView : View
             Driver.SetAttribute (GetNormalColor ());
             Driver.SetAttribute (GetNormalColor ());
         }
         }
 
 
-        public override void OnDrawContentComplete (Rectangle contentArea)
+        public override void OnDrawContentComplete (Rectangle viewport)
         {
         {
             if (_host._tabLocations is null)
             if (_host._tabLocations is null)
             {
             {
@@ -683,7 +683,7 @@ public class TabView : View
             for (var i = 0; i < tabLocations.Length; i++)
             for (var i = 0; i < tabLocations.Length; i++)
             {
             {
                 View tab = tabLocations [i].Tab;
                 View tab = tabLocations [i].Tab;
-                Rectangle vts = tab.BoundsToScreen (tab.Bounds);
+                Rectangle vts = tab.ViewportToScreen (tab.Viewport);
                 var lc = new LineCanvas ();
                 var lc = new LineCanvas ();
                 int selectedOffset = _host.Style.ShowTopLine && tabLocations [i].IsSelected ? 0 : 1;
                 int selectedOffset = _host.Style.ShowTopLine && tabLocations [i].IsSelected ? 0 : 1;
 
 
@@ -1115,7 +1115,7 @@ public class TabView : View
 
 
                     int lastSelectedTab = !_host.Style.ShowTopLine && i == selectedTab ? 1 :
                     int lastSelectedTab = !_host.Style.ShowTopLine && i == selectedTab ? 1 :
                                           _host.Style.TabsOnBottom ? 1 : 0;
                                           _host.Style.TabsOnBottom ? 1 : 0;
-                    Rectangle tabsBarVts = BoundsToScreen (Bounds);
+                    Rectangle tabsBarVts = ViewportToScreen (Viewport);
                     int lineLength = tabsBarVts.Right - vts.Right;
                     int lineLength = tabsBarVts.Right - vts.Right;
 
 
                     // Right horizontal line
                     // Right horizontal line
@@ -1238,7 +1238,7 @@ public class TabView : View
 
 
             View selected = null;
             View selected = null;
             int topLine = _host.Style.ShowTopLine ? 1 : 0;
             int topLine = _host.Style.ShowTopLine ? 1 : 0;
-            int width = Bounds.Width;
+            int width = Viewport.Width;
 
 
             foreach (TabToRender toRender in tabLocations)
             foreach (TabToRender toRender in tabLocations)
             {
             {
@@ -1314,7 +1314,7 @@ public class TabView : View
                 }
                 }
 
 
                 tab.TextFormatter.Draw (
                 tab.TextFormatter.Draw (
-                                        tab.BoundsToScreen (tab.Bounds),
+                                        tab.ViewportToScreen (tab.Viewport),
                                         prevAttr,
                                         prevAttr,
                                         ColorScheme.HotNormal
                                         ColorScheme.HotNormal
                                        );
                                        );
@@ -1360,7 +1360,7 @@ public class TabView : View
             // if there are more tabs to the right not visible
             // if there are more tabs to the right not visible
             if (ShouldDrawRightScrollIndicator ())
             if (ShouldDrawRightScrollIndicator ())
             {
             {
-                _rightScrollIndicator.X = Bounds.Width - 1;
+                _rightScrollIndicator.X = Viewport.Width - 1;
                 _rightScrollIndicator.Y = y;
                 _rightScrollIndicator.Y = y;
 
 
                 // indicate that
                 // indicate that

+ 4 - 4
Terminal.Gui/Views/TableView/ListTableSource.cs

@@ -109,12 +109,12 @@ public class ListTableSource : ITableSource
 
 
         if (Style.Orientation == Orientation.Vertical != Style.ScrollParallel)
         if (Style.Orientation == Orientation.Vertical != Style.ScrollParallel)
         {
         {
-            float f = (float)_tableView.Bounds.Height - _tableView.GetHeaderHeight ();
+            float f = (float)_tableView.Viewport.Height - _tableView.GetHeaderHeight ();
             cols = (int)Math.Ceiling (Count / f);
             cols = (int)Math.Ceiling (Count / f);
         }
         }
         else
         else
         {
         {
-            cols = (int)Math.Ceiling (((float)_tableView.Bounds.Width - 1) / colWidth) - 2;
+            cols = (int)Math.Ceiling (((float)_tableView.Viewport.Width - 1) / colWidth) - 2;
         }
         }
 
 
         return cols > 1 ? cols : 1;
         return cols > 1 ? cols : 1;
@@ -179,7 +179,7 @@ public class ListTableSource : ITableSource
 
 
     private void TableView_DrawContent (object sender, DrawEventArgs e)
     private void TableView_DrawContent (object sender, DrawEventArgs e)
     {
     {
-        if (!_tableView.Bounds.Equals (_lastBounds)
+        if (!_tableView.Viewport.Equals (_lastBounds)
             || _tableView.MaxCellWidth != _lastMaxCellWidth
             || _tableView.MaxCellWidth != _lastMaxCellWidth
             || _tableView.MinCellWidth != _lastMinCellWidth
             || _tableView.MinCellWidth != _lastMinCellWidth
             || Style != _lastStyle
             || Style != _lastStyle
@@ -188,7 +188,7 @@ public class ListTableSource : ITableSource
             DataTable = CreateTable (CalculateColumns ());
             DataTable = CreateTable (CalculateColumns ());
         }
         }
 
 
-        _lastBounds = _tableView.Bounds;
+        _lastBounds = _tableView.Viewport;
         _lastMinCellWidth = _tableView.MaxCellWidth;
         _lastMinCellWidth = _tableView.MaxCellWidth;
         _lastMaxCellWidth = _tableView.MaxCellWidth;
         _lastMaxCellWidth = _tableView.MaxCellWidth;
         _lastStyle = Style;
         _lastStyle = Style;

+ 22 - 22
Terminal.Gui/Views/TableView/TableView.cs

@@ -483,7 +483,7 @@ public class TableView : View
             return null;
             return null;
         }
         }
 
 
-        IEnumerable<ColumnToRender> viewPort = CalculateViewport (Bounds);
+        IEnumerable<ColumnToRender> viewPort = CalculateViewport (Viewport);
 
 
         int headerHeight = GetHeaderHeightIfAny ();
         int headerHeight = GetHeaderHeightIfAny ();
 
 
@@ -502,7 +502,7 @@ public class TableView : View
         }
         }
 
 
         // the cell is way down below the scroll area and off the screen
         // the cell is way down below the scroll area and off the screen
-        if (tableRow > RowOffset + (Bounds.Height - headerHeight))
+        if (tableRow > RowOffset + (Viewport.Height - headerHeight))
         {
         {
             return null;
             return null;
         }
         }
@@ -577,7 +577,7 @@ public class TableView : View
             return;
             return;
         }
         }
 
 
-        ColumnToRender [] columnsToRender = CalculateViewport (Bounds).ToArray ();
+        ColumnToRender [] columnsToRender = CalculateViewport (Viewport).ToArray ();
         int headerHeight = GetHeaderHeightIfAny ();
         int headerHeight = GetHeaderHeightIfAny ();
 
 
         //if we have scrolled too far to the left 
         //if we have scrolled too far to the left 
@@ -595,7 +595,7 @@ public class TableView : View
                 while (SelectedColumn > columnsToRender.Max (r => r.Column))
                 while (SelectedColumn > columnsToRender.Max (r => r.Column))
                 {
                 {
                     ColumnOffset++;
                     ColumnOffset++;
-                    columnsToRender = CalculateViewport (Bounds).ToArray ();
+                    columnsToRender = CalculateViewport (Viewport).ToArray ();
 
 
                     // if we are already scrolled to the last column then break
                     // if we are already scrolled to the last column then break
                     // this will prevent any theoretical infinite loop
                     // this will prevent any theoretical infinite loop
@@ -612,9 +612,9 @@ public class TableView : View
         }
         }
 
 
         //if we have scrolled too far down
         //if we have scrolled too far down
-        if (SelectedRow >= RowOffset + (Bounds.Height - headerHeight))
+        if (SelectedRow >= RowOffset + (Viewport.Height - headerHeight))
         {
         {
-            RowOffset = SelectedRow - (Bounds.Height - headerHeight) + 1;
+            RowOffset = SelectedRow - (Viewport.Height - headerHeight) + 1;
         }
         }
 
 
         //if we have scrolled too far up
         //if we have scrolled too far up
@@ -896,9 +896,9 @@ public class TableView : View
     }
     }
 
 
     ///<inheritdoc/>
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
 
         Move (0, 0);
         Move (0, 0);
 
 
@@ -906,12 +906,12 @@ public class TableView : View
         scrollLeftPoint = null;
         scrollLeftPoint = null;
 
 
         // What columns to render at what X offset in viewport
         // What columns to render at what X offset in viewport
-        ColumnToRender [] columnsToRender = CalculateViewport (Bounds).ToArray ();
+        ColumnToRender [] columnsToRender = CalculateViewport (Viewport).ToArray ();
 
 
         Driver.SetAttribute (GetNormalColor ());
         Driver.SetAttribute (GetNormalColor ());
 
 
         //invalidate current row (prevents scrolling around leaving old characters in the frame
         //invalidate current row (prevents scrolling around leaving old characters in the frame
-        Driver.AddStr (new string (' ', Bounds.Width));
+        Driver.AddStr (new string (' ', Viewport.Width));
 
 
         var line = 0;
         var line = 0;
 
 
@@ -925,7 +925,7 @@ public class TableView : View
             */
             */
             if (Style.ShowHorizontalHeaderOverline)
             if (Style.ShowHorizontalHeaderOverline)
             {
             {
-                RenderHeaderOverline (line, Bounds.Width, columnsToRender);
+                RenderHeaderOverline (line, Viewport.Width, columnsToRender);
                 line++;
                 line++;
             }
             }
 
 
@@ -937,7 +937,7 @@ public class TableView : View
 
 
             if (Style.ShowHorizontalHeaderUnderline)
             if (Style.ShowHorizontalHeaderUnderline)
             {
             {
-                RenderHeaderUnderline (line, Bounds.Width, columnsToRender);
+                RenderHeaderUnderline (line, Viewport.Width, columnsToRender);
                 line++;
                 line++;
             }
             }
         }
         }
@@ -945,9 +945,9 @@ public class TableView : View
         int headerLinesConsumed = line;
         int headerLinesConsumed = line;
 
 
         //render the cells
         //render the cells
-        for (; line < Bounds.Height; line++)
+        for (; line < Viewport.Height; line++)
         {
         {
-            ClearLine (line, Bounds.Width);
+            ClearLine (line, Viewport.Width);
 
 
             //work out what Row to render
             //work out what Row to render
             int rowToRender = RowOffset + (line - headerLinesConsumed);
             int rowToRender = RowOffset + (line - headerLinesConsumed);
@@ -963,7 +963,7 @@ public class TableView : View
             {
             {
                 if (rowToRender == Table.Rows && Style.ShowHorizontalBottomline)
                 if (rowToRender == Table.Rows && Style.ShowHorizontalBottomline)
                 {
                 {
-                    RenderBottomLine (line, Bounds.Width, columnsToRender);
+                    RenderBottomLine (line, Viewport.Width, columnsToRender);
                 }
                 }
 
 
                 continue;
                 continue;
@@ -1001,7 +1001,7 @@ public class TableView : View
     /// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
     /// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
     public void PageDown (bool extend)
     public void PageDown (bool extend)
     {
     {
-        ChangeSelectionByOffset (0, Bounds.Height - GetHeaderHeightIfAny (), extend);
+        ChangeSelectionByOffset (0, Viewport.Height - GetHeaderHeightIfAny (), extend);
         Update ();
         Update ();
     }
     }
 
 
@@ -1009,7 +1009,7 @@ public class TableView : View
     /// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
     /// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
     public void PageUp (bool extend)
     public void PageUp (bool extend)
     {
     {
-        ChangeSelectionByOffset (0, -(Bounds.Height - GetHeaderHeightIfAny ()), extend);
+        ChangeSelectionByOffset (0, -(Viewport.Height - GetHeaderHeightIfAny ()), extend);
         Update ();
         Update ();
     }
     }
 
 
@@ -1073,7 +1073,7 @@ public class TableView : View
             return null;
             return null;
         }
         }
 
 
-        IEnumerable<ColumnToRender> viewPort = CalculateViewport (Bounds);
+        IEnumerable<ColumnToRender> viewPort = CalculateViewport (Viewport);
 
 
         int headerHeight = GetHeaderHeightIfAny ();
         int headerHeight = GetHeaderHeightIfAny ();
 
 
@@ -1658,7 +1658,7 @@ public class TableView : View
         // Renders something like:
         // Renders something like:
         // │ArithmeticComparator│chi       │Healthboard│Interpretation│Labnumber│
         // │ArithmeticComparator│chi       │Healthboard│Interpretation│Labnumber│
 
 
-        ClearLine (row, Bounds.Width);
+        ClearLine (row, Viewport.Width);
 
 
         //render start of line
         //render start of line
         if (style.ShowVerticalHeaderLines)
         if (style.ShowVerticalHeaderLines)
@@ -1688,7 +1688,7 @@ public class TableView : View
         //render end of line
         //render end of line
         if (style.ShowVerticalHeaderLines)
         if (style.ShowVerticalHeaderLines)
         {
         {
-            AddRune (Bounds.Width - 1, row, Glyphs.VLine);
+            AddRune (Viewport.Width - 1, row, Glyphs.VLine);
         }
         }
     }
     }
 
 
@@ -1846,7 +1846,7 @@ public class TableView : View
         }
         }
 
 
         Driver.SetAttribute (color);
         Driver.SetAttribute (color);
-        Driver.AddStr (new string (' ', Bounds.Width));
+        Driver.AddStr (new string (' ', Viewport.Width));
 
 
         // Render cells for each visible header for the current row
         // Render cells for each visible header for the current row
         for (var i = 0; i < columnsToRender.Length; i++)
         for (var i = 0; i < columnsToRender.Length; i++)
@@ -1955,7 +1955,7 @@ public class TableView : View
 
 
             //render start and end of line
             //render start and end of line
             AddRune (0, row, Glyphs.VLine);
             AddRune (0, row, Glyphs.VLine);
-            AddRune (Bounds.Width - 1, row, Glyphs.VLine);
+            AddRune (Viewport.Width - 1, row, Glyphs.VLine);
         }
         }
     }
     }
 
 

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

@@ -973,7 +973,7 @@ public class TextField : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         _isDrawing = true;
         _isDrawing = true;
 
 
@@ -1196,8 +1196,8 @@ public class TextField : View
 
 
         int pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
         int pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
         int offB = OffSetBackground ();
         int offB = OffSetBackground ();
-        Rectangle containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default (Rectangle);
-        Rectangle thisFrame = BoundsToScreen (Bounds);
+        Rectangle containerFrame = SuperView?.ViewportToScreen (SuperView.Viewport) ?? default (Rectangle);
+        Rectangle thisFrame = ViewportToScreen (Viewport);
 
 
         if (pos > -1
         if (pos > -1
             && col >= pos
             && col >= pos
@@ -1746,7 +1746,7 @@ public class TextField : View
         }
         }
     }
     }
 
 
-    // BUGBUG: This assumes Frame == Bounds. It's also not clear what the intention is. For now, changed to always return 0.
+    // BUGBUG: This assumes Frame == Viewport. It's also not clear what the intention is. For now, changed to always return 0.
     private int OffSetBackground ()
     private int OffSetBackground ()
     {
     {
         var offB = 0;
         var offB = 0;
@@ -1878,9 +1878,9 @@ public class TextField : View
         Move (0, 0);
         Move (0, 0);
         string render = Caption;
         string render = Caption;
 
 
-        if (render.GetColumns () > Bounds.Width)
+        if (render.GetColumns () > Viewport.Width)
         {
         {
-            render = render [..Bounds.Width];
+            render = render [..Viewport.Width];
         }
         }
 
 
         Driver.AddStr (render);
         Driver.AddStr (render);
@@ -1941,9 +1941,9 @@ public class TextField : View
     {
     {
         _cursorPosition = Text.GetRuneCount ();
         _cursorPosition = Text.GetRuneCount ();
 
 
-        if (Bounds.Width > 0)
+        if (Viewport.Width > 0)
         {
         {
-            ScrollOffset = _cursorPosition > Bounds.Width + 1 ? _cursorPosition - Bounds.Width + 1 : 0;
+            ScrollOffset = _cursorPosition > Viewport.Width + 1 ? _cursorPosition - Viewport.Width + 1 : 0;
         }
         }
 
 
         Autocomplete.HostControl = this;
         Autocomplete.HostControl = this;

+ 5 - 5
Terminal.Gui/Views/TextValidateField.cs

@@ -537,7 +537,7 @@ namespace Terminal.Gui
         {
         {
             if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
             if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
             {
             {
-                int c = _provider.Cursor (mouseEvent.X - GetMargins (Bounds.Width).left);
+                int c = _provider.Cursor (mouseEvent.X - GetMargins (Viewport.Width).left);
 
 
                 if (_provider.Fixed == false && TextAlignment == TextAlignment.Right && Text.Length > 0)
                 if (_provider.Fixed == false && TextAlignment == TextAlignment.Right && Text.Length > 0)
                 {
                 {
@@ -555,7 +555,7 @@ namespace Terminal.Gui
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
         {
             if (_provider is null)
             if (_provider is null)
             {
             {
@@ -568,7 +568,7 @@ namespace Terminal.Gui
             Color bgcolor = !IsValid ? new Color (Color.BrightRed) : ColorScheme.Focus.Background;
             Color bgcolor = !IsValid ? new Color (Color.BrightRed) : ColorScheme.Focus.Background;
             var textColor = new Attribute (ColorScheme.Focus.Foreground, bgcolor);
             var textColor = new Attribute (ColorScheme.Focus.Foreground, bgcolor);
 
 
-            (int margin_left, int margin_right) = GetMargins (Bounds.Width);
+            (int margin_left, int margin_right) = GetMargins (Viewport.Width);
 
 
             Move (0, 0);
             Move (0, 0);
 
 
@@ -642,7 +642,7 @@ namespace Terminal.Gui
         /// <inheritdoc/>
         /// <inheritdoc/>
         public override void PositionCursor ()
         public override void PositionCursor ()
         {
         {
-            (int left, _) = GetMargins (Bounds.Width);
+            (int left, _) = GetMargins (Viewport.Width);
 
 
             // Fixed = true, is for inputs that have fixed width, like masked ones.
             // Fixed = true, is for inputs that have fixed width, like masked ones.
             // Fixed = false, is for normal input.
             // Fixed = false, is for normal input.
@@ -660,7 +660,7 @@ namespace Terminal.Gui
                 Move (curPos, 0);
                 Move (curPos, 0);
             }
             }
 
 
-            if (curPos < 0 || curPos >= Bounds.Width)
+            if (curPos < 0 || curPos >= Viewport.Width)
             {
             {
                 Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
                 Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
             }
             }

+ 3 - 3
Terminal.Gui/Views/TextView.cs

@@ -3586,7 +3586,7 @@ public class TextView : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         _isDrawing = true;
         _isDrawing = true;
 
 
@@ -3649,7 +3649,7 @@ public class TextView : View
                     AddRune (col, row, rune);
                     AddRune (col, row, rune);
                 }
                 }
 
 
-                if (!TextModel.SetCol (ref col, contentArea.Right, cols))
+                if (!TextModel.SetCol (ref col, viewport.Right, cols))
                 {
                 {
                     break;
                     break;
                 }
                 }
@@ -3672,7 +3672,7 @@ public class TextView : View
         if (row < bottom)
         if (row < bottom)
         {
         {
             SetNormalColor ();
             SetNormalColor ();
-            ClearRegion (contentArea.Left, row, right, bottom);
+            ClearRegion (viewport.Left, row, right, bottom);
         }
         }
 
 
         PositionCursor ();
         PositionCursor ();

+ 46 - 43
Terminal.Gui/Views/TileView.cs

@@ -156,19 +156,19 @@ public class TileView : View
             return;
             return;
         }
         }
 
 
-        Rectangle contentArea = Bounds;
+        Rectangle viewport = Viewport;
 
 
         if (HasBorder ())
         if (HasBorder ())
         {
         {
-            contentArea = new (
-                               contentArea.X + 1,
-                               contentArea.Y + 1,
-                               Math.Max (0, contentArea.Width - 2),
-                               Math.Max (0, contentArea.Height - 2)
-                              );
+            viewport = new (
+                            viewport.X + 1,
+                            viewport.Y + 1,
+                            Math.Max (0, viewport.Width - 2),
+                            Math.Max (0, viewport.Height - 2)
+                           );
         }
         }
 
 
-        Setup (contentArea);
+        Setup (viewport);
         base.LayoutSubviews ();
         base.LayoutSubviews ();
     }
     }
 
 
@@ -179,12 +179,13 @@ public class TileView : View
     public override bool OnDrawAdornments () { return false; }
     public override bool OnDrawAdornments () { return false; }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         Driver.SetAttribute (ColorScheme.Normal);
         Driver.SetAttribute (ColorScheme.Normal);
-        Clear (contentArea);
 
 
-        base.OnDrawContent (contentArea);
+        Clear ();
+
+        base.OnDrawContent (viewport);
 
 
         var lc = new LineCanvas ();
         var lc = new LineCanvas ();
 
 
@@ -195,19 +196,19 @@ public class TileView : View
         {
         {
             if (HasBorder ())
             if (HasBorder ())
             {
             {
-                lc.AddLine (Point.Empty, Bounds.Width, Orientation.Horizontal, LineStyle);
-                lc.AddLine (Point.Empty, Bounds.Height, Orientation.Vertical, LineStyle);
+                lc.AddLine (Point.Empty, Viewport.Width, Orientation.Horizontal, LineStyle);
+                lc.AddLine (Point.Empty, Viewport.Height, Orientation.Vertical, LineStyle);
 
 
                 lc.AddLine (
                 lc.AddLine (
-                            new Point (Bounds.Width - 1, Bounds.Height - 1),
-                            -Bounds.Width,
+                            new Point (Viewport.Width - 1, Viewport.Height - 1),
+                            -Viewport.Width,
                             Orientation.Horizontal,
                             Orientation.Horizontal,
                             LineStyle
                             LineStyle
                            );
                            );
 
 
                 lc.AddLine (
                 lc.AddLine (
-                            new Point (Bounds.Width - 1, Bounds.Height - 1),
-                            -Bounds.Height,
+                            new Point (Viewport.Width - 1, Viewport.Height - 1),
+                            -Viewport.Height,
                             Orientation.Vertical,
                             Orientation.Vertical,
                             LineStyle
                             LineStyle
                            );
                            );
@@ -217,7 +218,7 @@ public class TileView : View
             {
             {
                 bool isRoot = _splitterLines.Contains (line);
                 bool isRoot = _splitterLines.Contains (line);
 
 
-                Rectangle screen = line.BoundsToScreen (Rectangle.Empty);
+                Rectangle screen = line.ViewportToScreen (Rectangle.Empty);
                 Point origin = ScreenToFrame (screen.X, screen.Y);
                 Point origin = ScreenToFrame (screen.X, screen.Y);
                 int length = line.Orientation == Orientation.Horizontal ? line.Frame.Width : line.Frame.Height;
                 int length = line.Orientation == Orientation.Horizontal ? line.Frame.Width : line.Frame.Height;
 
 
@@ -241,7 +242,7 @@ public class TileView : View
 
 
         Driver.SetAttribute (ColorScheme.Normal);
         Driver.SetAttribute (ColorScheme.Normal);
 
 
-        foreach (KeyValuePair<Point, Rune> p in lc.GetMap (Bounds))
+        foreach (KeyValuePair<Point, Rune> p in lc.GetMap (Viewport))
         {
         {
             AddRune (p.Key.X, p.Key.Y, p.Value);
             AddRune (p.Key.X, p.Key.Y, p.Value);
         }
         }
@@ -423,7 +424,7 @@ public class TileView : View
                                         );
                                         );
         }
         }
 
 
-        int fullSpace = _orientation == Orientation.Vertical ? Bounds.Width : Bounds.Height;
+        int fullSpace = _orientation == Orientation.Vertical ? Viewport.Width : Viewport.Height;
 
 
         if (fullSpace != 0 && !IsValidNewSplitterPos (idx, value, fullSpace))
         if (fullSpace != 0 && !IsValidNewSplitterPos (idx, value, fullSpace))
         {
         {
@@ -758,9 +759,9 @@ public class TileView : View
         return false;
         return false;
     }
     }
 
 
-    private void Setup (Rectangle contentArea)
+    private void Setup (Rectangle viewport)
     {
     {
-        if (contentArea.IsEmpty || contentArea.Height <= 0 || contentArea.Width <= 0)
+        if (viewport.IsEmpty || viewport.Height <= 0 || viewport.Width <= 0)
         {
         {
             return;
             return;
         }
         }
@@ -803,18 +804,20 @@ public class TileView : View
 
 
             if (Orientation == Orientation.Vertical)
             if (Orientation == Orientation.Vertical)
             {
             {
-                tile.ContentView.X = i == 0 ? contentArea.X : Pos.Right (visibleSplitterLines [i - 1]);
-                tile.ContentView.Y = contentArea.Y;
-                tile.ContentView.Height = contentArea.Height;
-                tile.ContentView.Width = GetTileWidthOrHeight (i, Bounds.Width, visibleTiles, visibleSplitterLines);
+                tile.ContentView.X = i == 0 ? viewport.X : Pos.Right (visibleSplitterLines [i - 1]);
+                tile.ContentView.Y = viewport.Y;
+                tile.ContentView.Height = viewport.Height;
+                tile.ContentView.Width = GetTileWidthOrHeight (i, Viewport.Width, visibleTiles, visibleSplitterLines);
             }
             }
             else
             else
             {
             {
-                tile.ContentView.X = contentArea.X;
-                tile.ContentView.Y = i == 0 ? contentArea.Y : Pos.Bottom (visibleSplitterLines [i - 1]);
-                tile.ContentView.Width = contentArea.Width;
-                tile.ContentView.Height = GetTileWidthOrHeight (i, Bounds.Height, visibleTiles, visibleSplitterLines);
+                tile.ContentView.X = viewport.X;
+                tile.ContentView.Y = i == 0 ? viewport.Y : Pos.Bottom (visibleSplitterLines [i - 1]);
+                tile.ContentView.Width = viewport.Width;
+                tile.ContentView.Height = GetTileWidthOrHeight (i, Viewport.Height, visibleTiles, visibleSplitterLines);
             }
             }
+            //  BUGBUG: This should not be needed. If any of the pos/dim setters above actually changed values, NeedsDisplay should have already been set. 
+            tile.ContentView.SetNeedsDisplay ();
         }
         }
     }
     }
 
 
@@ -837,7 +840,7 @@ public class TileView : View
         /// </summary>
         /// </summary>
         public Point GetLocalCoordinateForTitle (TileView intoCoordinateSpace)
         public Point GetLocalCoordinateForTitle (TileView intoCoordinateSpace)
         {
         {
-            Rectangle screen = Tile.ContentView.BoundsToScreen (Rectangle.Empty);
+            Rectangle screen = Tile.ContentView.ViewportToScreen (Rectangle.Empty);
             return intoCoordinateSpace.ScreenToFrame (screen.X, screen.Y - 1);
             return intoCoordinateSpace.ScreenToFrame (screen.X, screen.Y - 1);
         }
         }
 
 
@@ -845,7 +848,7 @@ public class TileView : View
         {
         {
             Dim spaceDim = Tile.ContentView.Width;
             Dim spaceDim = Tile.ContentView.Width;
 
 
-            int spaceAbs = spaceDim.Anchor (Parent.Bounds.Width);
+            int spaceAbs = spaceDim.Anchor (Parent.Viewport.Width);
 
 
             var title = $" {Tile.Title} ";
             var title = $" {Tile.Title} ";
 
 
@@ -893,13 +896,13 @@ public class TileView : View
         {
         {
             if (dragPosition is { } || CanFocus)
             if (dragPosition is { } || CanFocus)
             {
             {
-                Point location = moveRuneRenderLocation ?? new Point (Bounds.Width / 2, Bounds.Height / 2);
+                Point location = moveRuneRenderLocation ?? new Point (Viewport.Width / 2, Viewport.Height / 2);
 
 
                 AddRune (location.X, location.Y, Glyphs.Diamond);
                 AddRune (location.X, location.Y, Glyphs.Diamond);
             }
             }
         }
         }
 
 
-        protected internal override bool OnMouseEvent  (MouseEvent mouseEvent)
+        protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
         {
         {
             if (!dragPosition.HasValue && mouseEvent.Flags == MouseFlags.Button1Pressed)
             if (!dragPosition.HasValue && mouseEvent.Flags == MouseFlags.Button1Pressed)
             {
             {
@@ -919,7 +922,7 @@ public class TileView : View
                     {
                     {
                         moveRuneRenderLocation = new Point (
                         moveRuneRenderLocation = new Point (
                                                             0,
                                                             0,
-                                                            Math.Max (1, Math.Min (Bounds.Height - 2, mouseEvent.Y))
+                                                            Math.Max (1, Math.Min (Viewport.Height - 2, mouseEvent.Y))
                                                            );
                                                            );
                     }
                     }
                 }
                 }
@@ -943,7 +946,7 @@ public class TileView : View
                 {
                 {
                     int dx = mouseEvent.X - dragPosition.Value.X;
                     int dx = mouseEvent.X - dragPosition.Value.X;
                     Parent.SetSplitterPos (Idx, Offset (X, dx));
                     Parent.SetSplitterPos (Idx, Offset (X, dx));
-                    moveRuneRenderLocation = new Point (0, Math.Max (1, Math.Min (Bounds.Height - 2, mouseEvent.Y)));
+                    moveRuneRenderLocation = new Point (0, Math.Max (1, Math.Min (Viewport.Height - 2, mouseEvent.Y)));
                 }
                 }
 
 
                 Parent.SetNeedsDisplay ();
                 Parent.SetNeedsDisplay ();
@@ -969,9 +972,9 @@ public class TileView : View
             return false;
             return false;
         }
         }
 
 
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
         {
-            base.OnDrawContent (contentArea);
+            base.OnDrawContent (viewport);
 
 
             DrawSplitterSymbol ();
             DrawSplitterSymbol ();
         }
         }
@@ -988,7 +991,7 @@ public class TileView : View
         {
         {
             base.PositionCursor ();
             base.PositionCursor ();
 
 
-            Point location = moveRuneRenderLocation ?? new Point (Bounds.Width / 2, Bounds.Height / 2);
+            Point location = moveRuneRenderLocation ?? new Point (Viewport.Width / 2, Viewport.Height / 2);
             Move (location.X, location.Y);
             Move (location.X, location.Y);
         }
         }
 
 
@@ -1032,10 +1035,10 @@ public class TileView : View
             {
             {
                 if (Orientation == Orientation.Horizontal)
                 if (Orientation == Orientation.Horizontal)
                 {
                 {
-                    return Parent.SetSplitterPos (Idx, ConvertToPosFactor (newValue, Parent.Bounds.Height));
+                    return Parent.SetSplitterPos (Idx, ConvertToPosFactor (newValue, Parent.Viewport.Height));
                 }
                 }
 
 
-                return Parent.SetSplitterPos (Idx, ConvertToPosFactor (newValue, Parent.Bounds.Width));
+                return Parent.SetSplitterPos (Idx, ConvertToPosFactor (newValue, Parent.Viewport.Width));
             }
             }
 
 
             return Parent.SetSplitterPos (Idx, newValue);
             return Parent.SetSplitterPos (Idx, newValue);
@@ -1071,8 +1074,8 @@ public class TileView : View
         {
         {
             int posAbsolute = pos.Anchor (
             int posAbsolute = pos.Anchor (
                                           Orientation == Orientation.Horizontal
                                           Orientation == Orientation.Horizontal
-                                              ? Parent.Bounds.Height
-                                              : Parent.Bounds.Width
+                                              ? Parent.Viewport.Height
+                                              : Parent.Viewport.Width
                                          );
                                          );
 
 
             return posAbsolute + delta;
             return posAbsolute + delta;

+ 10 - 9
Terminal.Gui/Views/Toplevel.cs

@@ -29,7 +29,7 @@ public partial class Toplevel : View
     /// </summary>
     /// </summary>
     public Toplevel ()
     public Toplevel ()
     {
     {
-        Arrangement = ViewArrangement.Movable;
+        Arrangement = ViewArrangement.Fixed;
         Width = Dim.Fill ();
         Width = Dim.Fill ();
         Height = Dim.Fill ();
         Height = Dim.Fill ();
 
 
@@ -249,7 +249,7 @@ public partial class Toplevel : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         if (!Visible)
         if (!Visible)
         {
         {
@@ -268,12 +268,12 @@ public partial class Toplevel : View
             {
             {
                 foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ())
                 foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ())
                 {
                 {
-                    if (top.Frame.IntersectsWith (Bounds))
+                    if (top.Frame.IntersectsWith (Viewport))
                     {
                     {
                         if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible)
                         if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible)
                         {
                         {
                             top.SetNeedsLayout ();
                             top.SetNeedsLayout ();
-                            top.SetNeedsDisplay (top.Bounds);
+                            top.SetNeedsDisplay (top.Viewport);
                             top.Draw ();
                             top.Draw ();
                             top.OnRenderLineCanvas ();
                             top.OnRenderLineCanvas ();
                         }
                         }
@@ -284,20 +284,20 @@ public partial class Toplevel : View
             // This should not be here, but in base
             // This should not be here, but in base
             foreach (View view in Subviews)
             foreach (View view in Subviews)
             {
             {
-                if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this))
+                if (view.Frame.IntersectsWith (Viewport) && !OutsideTopFrame (this))
                 {
                 {
                     //view.SetNeedsLayout ();
                     //view.SetNeedsLayout ();
-                    view.SetNeedsDisplay (view.Bounds);
+                    view.SetNeedsDisplay ();
                     view.SetSubViewNeedsDisplay ();
                     view.SetSubViewNeedsDisplay ();
                 }
                 }
             }
             }
 
 
-            base.OnDrawContent (contentArea);
+            base.OnDrawContent (viewport);
 
 
             // This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true
             // This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true
             //if (this.MenuBar is { } && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu is { }) {
             //if (this.MenuBar is { } && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu is { }) {
             //	// TODO: Hack until we can get compositing working right.
             //	// TODO: Hack until we can get compositing working right.
-            //	this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Bounds);
+            //	this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Viewport);
             //}
             //}
         }
         }
     }
     }
@@ -380,6 +380,7 @@ public partial class Toplevel : View
     /// <param name="top">The Toplevel to adjust.</param>
     /// <param name="top">The Toplevel to adjust.</param>
     public virtual void PositionToplevel (Toplevel top)
     public virtual void PositionToplevel (Toplevel top)
     {
     {
+
         View superView = GetLocationEnsuringFullVisibility (
         View superView = GetLocationEnsuringFullVisibility (
                                               top,
                                               top,
                                               top.Frame.X,
                                               top.Frame.X,
@@ -547,7 +548,7 @@ public partial class Toplevel : View
     ///     to dispose objects after calling <see cref="Application.End(RunState)"/>.
     ///     to dispose objects after calling <see cref="Application.End(RunState)"/>.
     /// </summary>
     /// </summary>
     public event EventHandler Unloaded;
     public event EventHandler Unloaded;
-    
+
     internal void AddMenuStatusBar (View view)
     internal void AddMenuStatusBar (View view)
     {
     {
         if (view is MenuBar)
         if (view is MenuBar)

+ 14 - 14
Terminal.Gui/Views/TreeView/TreeView.cs

@@ -742,10 +742,10 @@ public class TreeView<T> : View, ITreeView where T : class
             //if user has scrolled up too far to see their selection
             //if user has scrolled up too far to see their selection
             ScrollOffsetVertical = idx;
             ScrollOffsetVertical = idx;
         }
         }
-        else if (idx >= ScrollOffsetVertical + Bounds.Height - leaveSpace)
+        else if (idx >= ScrollOffsetVertical + Viewport.Height - leaveSpace)
         {
         {
             //if user has scrolled off bottom of visible tree
             //if user has scrolled off bottom of visible tree
-            ScrollOffsetVertical = Math.Max (0, idx + 1 - (Bounds.Height - leaveSpace));
+            ScrollOffsetVertical = Math.Max (0, idx + 1 - (Viewport.Height - leaveSpace));
         }
         }
     }
     }
 
 
@@ -868,12 +868,12 @@ public class TreeView<T> : View, ITreeView where T : class
             }
             }
 
 
             // If control has no height to it then there is no visible area for content
             // If control has no height to it then there is no visible area for content
-            if (Bounds.Height == 0)
+            if (Viewport.Height == 0)
             {
             {
                 return 0;
                 return 0;
             }
             }
 
 
-            return map.Skip (ScrollOffsetVertical).Take (Bounds.Height).Max (b => b.GetWidth (Driver));
+            return map.Skip (ScrollOffsetVertical).Take (Viewport.Height).Max (b => b.GetWidth (Driver));
         }
         }
 
 
         return map.Max (b => b.GetWidth (Driver));
         return map.Max (b => b.GetWidth (Driver));
@@ -886,13 +886,13 @@ public class TreeView<T> : View, ITreeView where T : class
     ///     If you have screen coordinates then use <see cref="View.ScreenToFrame"/> to translate these into the client area of
     ///     If you have screen coordinates then use <see cref="View.ScreenToFrame"/> to translate these into the client area of
     ///     the <see cref="TreeView{T}"/>.
     ///     the <see cref="TreeView{T}"/>.
     /// </summary>
     /// </summary>
-    /// <param name="row">The row of the <see cref="View.Bounds"/> of the <see cref="TreeView{T}"/>.</param>
+    /// <param name="row">The row of the <see cref="View.Viewport"/> of the <see cref="TreeView{T}"/>.</param>
     /// <returns>The object currently displayed on this row or null.</returns>
     /// <returns>The object currently displayed on this row or null.</returns>
     public T GetObjectOnRow (int row) { return HitTest (row)?.Model; }
     public T GetObjectOnRow (int row) { return HitTest (row)?.Model; }
 
 
     /// <summary>
     /// <summary>
     ///     <para>
     ///     <para>
-    ///         Returns the Y coordinate within the <see cref="View.Bounds"/> of the tree at which <paramref name="toFind"/>
+    ///         Returns the Y coordinate within the <see cref="View.Viewport"/> of the tree at which <paramref name="toFind"/>
     ///         would be displayed or null if it is not currently exposed (e.g. its parent is collapsed).
     ///         would be displayed or null if it is not currently exposed (e.g. its parent is collapsed).
     ///     </para>
     ///     </para>
     ///     <para>
     ///     <para>
@@ -967,7 +967,7 @@ public class TreeView<T> : View, ITreeView where T : class
     public void GoToEnd ()
     public void GoToEnd ()
     {
     {
         IReadOnlyCollection<Branch<T>> map = BuildLineMap ();
         IReadOnlyCollection<Branch<T>> map = BuildLineMap ();
-        ScrollOffsetVertical = Math.Max (0, map.Count - Bounds.Height + 1);
+        ScrollOffsetVertical = Math.Max (0, map.Count - Viewport.Height + 1);
         SelectedObject = map.LastOrDefault ()?.Model;
         SelectedObject = map.LastOrDefault ()?.Model;
 
 
         SetNeedsDisplay ();
         SetNeedsDisplay ();
@@ -1129,12 +1129,12 @@ public class TreeView<T> : View, ITreeView where T : class
     /// <summary>Moves the selection down by the height of the control (1 page).</summary>
     /// <summary>Moves the selection down by the height of the control (1 page).</summary>
     /// <param name="expandSelection">True if the navigation should add the covered nodes to the selected current selection.</param>
     /// <param name="expandSelection">True if the navigation should add the covered nodes to the selected current selection.</param>
     /// <exception cref="NotImplementedException"></exception>
     /// <exception cref="NotImplementedException"></exception>
-    public void MovePageDown (bool expandSelection = false) { AdjustSelection (Bounds.Height, expandSelection); }
+    public void MovePageDown (bool expandSelection = false) { AdjustSelection (Viewport.Height, expandSelection); }
 
 
     /// <summary>Moves the selection up by the height of the control (1 page).</summary>
     /// <summary>Moves the selection up by the height of the control (1 page).</summary>
     /// <param name="expandSelection">True if the navigation should add the covered nodes to the selected current selection.</param>
     /// <param name="expandSelection">True if the navigation should add the covered nodes to the selected current selection.</param>
     /// <exception cref="NotImplementedException"></exception>
     /// <exception cref="NotImplementedException"></exception>
-    public void MovePageUp (bool expandSelection = false) { AdjustSelection (-Bounds.Height, expandSelection); }
+    public void MovePageUp (bool expandSelection = false) { AdjustSelection (-Viewport.Height, expandSelection); }
 
 
     /// <summary>
     /// <summary>
     ///     This event is raised when an object is activated e.g. by double clicking or pressing
     ///     This event is raised when an object is activated e.g. by double clicking or pressing
@@ -1143,7 +1143,7 @@ public class TreeView<T> : View, ITreeView where T : class
     public event EventHandler<ObjectActivatedEventArgs<T>> ObjectActivated;
     public event EventHandler<ObjectActivatedEventArgs<T>> ObjectActivated;
 
 
     ///<inheritdoc/>
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
         if (roots is null)
         if (roots is null)
         {
         {
@@ -1160,7 +1160,7 @@ public class TreeView<T> : View, ITreeView where T : class
 
 
         IReadOnlyCollection<Branch<T>> map = BuildLineMap ();
         IReadOnlyCollection<Branch<T>> map = BuildLineMap ();
 
 
-        for (var line = 0; line < Bounds.Height; line++)
+        for (var line = 0; line < Viewport.Height; line++)
         {
         {
             int idxToRender = ScrollOffsetVertical + line;
             int idxToRender = ScrollOffsetVertical + line;
 
 
@@ -1168,14 +1168,14 @@ public class TreeView<T> : View, ITreeView where T : class
             if (idxToRender < map.Count)
             if (idxToRender < map.Count)
             {
             {
                 // Render the line
                 // Render the line
-                map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Bounds.Width);
+                map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Viewport.Width);
             }
             }
             else
             else
             {
             {
                 // Else clear the line to prevent stale symbols due to scrolling etc
                 // Else clear the line to prevent stale symbols due to scrolling etc
                 Move (0, line);
                 Move (0, line);
                 Driver.SetAttribute (GetNormalColor ());
                 Driver.SetAttribute (GetNormalColor ());
-                Driver.AddStr (new string (' ', Bounds.Width));
+                Driver.AddStr (new string (' ', Viewport.Width));
             }
             }
         }
         }
     }
     }
@@ -1247,7 +1247,7 @@ public class TreeView<T> : View, ITreeView where T : class
             int idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
             int idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
 
 
             // if currently selected line is visible
             // if currently selected line is visible
-            if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Bounds.Height)
+            if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Viewport.Height)
             {
             {
                 Move (0, idx - ScrollOffsetVertical);
                 Move (0, idx - ScrollOffsetVertical);
             }
             }

+ 5 - 0
Terminal.sln.DotSettings

@@ -386,10 +386,15 @@
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=70345118_002D4b40_002D4ece_002D937c_002Dbbeb7a0b2e70/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=c873eafb_002Dd57f_002D481d_002D8c93_002D77f6863c2f88/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
 	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=1BE867097E5CBD4A911FDC0FC0E0BAAC/@KeyIndexDefined">True</s:Boolean>
 	<s:Boolean x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=1BE867097E5CBD4A911FDC0FC0E0BAAC/@KeyIndexDefined">True</s:Boolean>
 	<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=1BE867097E5CBD4A911FDC0FC0E0BAAC/Color/@EntryValue">#FFCF9D32</s:String>
 	<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=1BE867097E5CBD4A911FDC0FC0E0BAAC/Color/@EntryValue">#FFCF9D32</s:String>
 	<s:Boolean x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=1BE867097E5CBD4A911FDC0FC0E0BAAC/MatchComments/@EntryValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=1BE867097E5CBD4A911FDC0FC0E0BAAC/MatchComments/@EntryValue">True</s:Boolean>

+ 2 - 2
UICatalog/Scenarios/ASCIICustomButton.cs

@@ -86,14 +86,14 @@ public class ASCIICustomButtonTest : Scenario
 
 
             var fillText = new StringBuilder ();
             var fillText = new StringBuilder ();
 
 
-            for (var i = 0; i < Bounds.Height; i++)
+            for (var i = 0; i < Viewport.Height; i++)
             {
             {
                 if (i > 0)
                 if (i > 0)
                 {
                 {
                     fillText.AppendLine ("");
                     fillText.AppendLine ("");
                 }
                 }
 
 
-                for (var j = 0; j < Bounds.Width; j++)
+                for (var j = 0; j < Viewport.Width; j++)
                 {
                 {
                     fillText.Append ("█");
                     fillText.Append ("█");
                 }
                 }

+ 0 - 55
UICatalog/Scenarios/AdornmentExperiments.cs

@@ -1,55 +0,0 @@
-using Terminal.Gui;
-
-namespace UICatalog.Scenarios;
-
-[ScenarioMetadata ("Adornment Experiments", "Playground for Adornment experiments")]
-[ScenarioCategory ("Controls")]
-public class AdornmentExperiments : Scenario
-{
-    private ViewDiagnosticFlags _diagnosticFlags;
-
-    private View _frameView;
-
-    public override void Init ()
-    {
-        Application.Init ();
-        ConfigurationManager.Themes.Theme = Theme;
-        ConfigurationManager.Apply ();
-        Top = new ();
-        Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
-
-        _diagnosticFlags = View.Diagnostics;
-        //View.Diagnostics = ViewDiagnosticFlags.MouseEnter;
-
-        _frameView = new View ()
-        {
-            Title = "Frame View",
-            X = 0,
-            Y = 0,
-            Width = Dim.Percent (90),
-            Height = Dim.Percent (90),
-            CanFocus = true,
-        };
-        Top.Add (_frameView);
-        _frameView.Initialized += FrameView_Initialized;
-
-        Top.Closed += (s, e) => View.Diagnostics = _diagnosticFlags;
-    }
-
-    private void FrameView_Initialized (object sender, System.EventArgs e)
-    {
-        _frameView.Border.Thickness = new (1, 1, 1, 1);
-        _frameView.Padding.Thickness = new (0, 10, 0, 0);
-        _frameView.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
-
-        var label = new Label ()
-        {
-            Text = "In Padding",
-            X = Pos.Center (),
-            Y = 0,
-            BorderStyle = LineStyle.Dashed
-        };
-        _frameView.Padding.Add (label);
-    }
-
-}

+ 139 - 103
UICatalog/Scenarios/Adornments.cs

@@ -13,15 +13,30 @@ public class Adornments : Scenario
 {
 {
     private ViewDiagnosticFlags _diagnosticFlags;
     private ViewDiagnosticFlags _diagnosticFlags;
 
 
-    public override void Init ()
+    public override void Main ()
     {
     {
         Application.Init ();
         Application.Init ();
-        ConfigurationManager.Themes.Theme = Theme;
-        ConfigurationManager.Apply ();
-        Top = new ();
-        Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
 
 
-        var view = new Window { Title = "The _Window" };
+        _diagnosticFlags = View.Diagnostics;
+
+        Window app = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+        };
+
+        var editor = new AdornmentsEditor ();
+        app.Add (editor);
+
+        var window = new Window
+        {
+            Title = "The _Window",
+            Arrangement = ViewArrangement.Movable,
+            X = Pos.Right(editor),
+            Width = Dim.Percent (60),
+            Height = Dim.Percent (80),
+        };
+        app.Add (window);
+
         var tf1 = new TextField { Width = 10, Text = "TextField" };
         var tf1 = new TextField { Width = 10, Text = "TextField" };
         var color = new ColorPicker { Title = "BG", BoxHeight = 1, BoxWidth = 1, X = Pos.AnchorEnd (11) };
         var color = new ColorPicker { Title = "BG", BoxHeight = 1, BoxWidth = 1, X = Pos.AnchorEnd (11) };
         color.BorderStyle = LineStyle.RoundedDotted;
         color.BorderStyle = LineStyle.RoundedDotted;
@@ -40,7 +55,7 @@ public class Adornments : Scenario
         var button = new Button { X = Pos.Center (), Y = Pos.Center (), Text = "Press me!" };
         var button = new Button { X = Pos.Center (), Y = Pos.Center (), Text = "Press me!" };
 
 
         button.Accept += (s, e) =>
         button.Accept += (s, e) =>
-                             MessageBox.Query (20, 7, "Hi", $"Am I a {view.GetType ().Name}?", "Yes", "No");
+                             MessageBox.Query (20, 7, "Hi", $"Am I a {window.GetType ().Name}?", "Yes", "No");
 
 
         var label = new TextView
         var label = new TextView
         {
         {
@@ -64,45 +79,39 @@ public class Adornments : Scenario
             Text = "Label\nY=AnchorEnd(3),Height=Dim.Fill()"
             Text = "Label\nY=AnchorEnd(3),Height=Dim.Fill()"
         };
         };
 
 
-        view.Margin.Data = "Margin";
-        view.Margin.Thickness = new (3);
+        window.Margin.Data = "Margin";
+        window.Margin.Thickness = new (3);
 
 
-        view.Border.Data = "Border";
-        view.Border.Thickness = new (3);
+        window.Border.Data = "Border";
+        window.Border.Thickness = new (3);
 
 
-        view.Padding.Data = "Padding";
-        view.Padding.Thickness = new (3);
+        window.Padding.Data = "Padding";
+        window.Padding.Thickness = new (3);
 
 
-        view.Add (tf1, color, button, label, btnButtonInWindow, tv);
-
-        var editor = new AdornmentsEditor
+        var longLabel = new Label ()
         {
         {
-            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
-            ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]
+            X = 40, Y = 5, Title = "This is long text (in a label) that should clip.",
 
 
-            //BorderStyle = LineStyle.None,
         };
         };
-        view.X = 36;
-        view.Y = 0;
-        view.Width = Dim.Percent (60);
-        view.Height = Dim.Percent (80); 
+        longLabel.TextFormatter.WordWrap = true;
+        window.Add (tf1, color, button, label, btnButtonInWindow, tv, longLabel);
 
 
-        editor.Initialized += (s, e) => { editor.ViewToEdit = view; };
+        editor.Initialized += (s, e) => { editor.ViewToEdit = window; };
 
 
-        view.Initialized += (s, e) =>
+        window.Initialized += (s, e) =>
                             {
                             {
-                                var labelInPadding = new Label () {  X = 1, Y = 0, Title = "_Text:" };
-                                view.Padding.Add (labelInPadding);
+                                var labelInPadding = new Label () { X = 1, Y = 0, Title = "_Text:" };
+                                window.Padding.Add (labelInPadding);
 
 
                                 var textFieldInPadding = new TextField () { X = Pos.Right (labelInPadding) + 1, Y = Pos.Top (labelInPadding), Width = 15, Text = "some text" };
                                 var textFieldInPadding = new TextField () { X = Pos.Right (labelInPadding) + 1, Y = Pos.Top (labelInPadding), Width = 15, Text = "some text" };
                                 textFieldInPadding.Accept += (s, e) => MessageBox.Query (20, 7, "TextField", textFieldInPadding.Text, "Ok");
                                 textFieldInPadding.Accept += (s, e) => MessageBox.Query (20, 7, "TextField", textFieldInPadding.Text, "Ok");
-                                view.Padding.Add (textFieldInPadding);
+                                window.Padding.Add (textFieldInPadding);
 
 
                                 var btnButtonInPadding = new Button { X = Pos.Center (), Y = 0, Text = "_Button in Padding" };
                                 var btnButtonInPadding = new Button { X = Pos.Center (), Y = 0, Text = "_Button in Padding" };
                                 btnButtonInPadding.Accept += (s, e) => MessageBox.Query (20, 7, "Hi", "Button in Padding Pressed!", "Ok");
                                 btnButtonInPadding.Accept += (s, e) => MessageBox.Query (20, 7, "Hi", "Button in Padding Pressed!", "Ok");
                                 btnButtonInPadding.BorderStyle = LineStyle.Dashed;
                                 btnButtonInPadding.BorderStyle = LineStyle.Dashed;
-                                btnButtonInPadding.Border.Thickness = new (1,1,1,1);
-                                view.Padding.Add (btnButtonInPadding);
+                                btnButtonInPadding.Border.Thickness = new (1, 1, 1, 1);
+                                window.Padding.Add (btnButtonInPadding);
 
 
 #if SUBVIEW_BASED_BORDER
 #if SUBVIEW_BASED_BORDER
                                 btnButtonInPadding.Border.CloseButton.Visible = true;
                                 btnButtonInPadding.Border.CloseButton.Visible = true;
@@ -118,16 +127,17 @@ public class Adornments : Scenario
 #endif
 #endif
                             };
                             };
 
 
-        Top.Closed += (s, e) => View.Diagnostics = _diagnosticFlags;
+        app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags;
 
 
-        Application.Run (editor);
-        editor.Dispose ();
+        Application.Run (app);
+        app.Dispose ();
 
 
         Application.Shutdown ();
         Application.Shutdown ();
     }
     }
 
 
-    public override void Run () { }
-
+    /// <summary>
+    /// Provides a composable UI for editing the settings of an Adornment.
+    /// </summary>
     public class AdornmentEditor : View
     public class AdornmentEditor : View
     {
     {
         private readonly ColorPicker _backgroundColorPicker = new ()
         private readonly ColorPicker _backgroundColorPicker = new ()
@@ -148,12 +158,12 @@ public class Adornments : Scenario
             SuperViewRendersLineCanvas = true
             SuperViewRendersLineCanvas = true
         };
         };
 
 
-        private TextField _bottomEdit;
-        private bool _isUpdating;
-        private TextField _leftEdit;
-        private TextField _rightEdit;
+        private Buttons.NumericUpDown<int> _topEdit;
+        private Buttons.NumericUpDown<int> _leftEdit;
+        private Buttons.NumericUpDown<int> _bottomEdit;
+        private Buttons.NumericUpDown<int> _rightEdit;
         private Thickness _thickness;
         private Thickness _thickness;
-        private TextField _topEdit;
+        private bool _isUpdating;
 
 
         public AdornmentEditor ()
         public AdornmentEditor ()
         {
         {
@@ -161,7 +171,6 @@ public class Adornments : Scenario
             BorderStyle = LineStyle.Double;
             BorderStyle = LineStyle.Double;
             Initialized += AdornmentEditor_Initialized;
             Initialized += AdornmentEditor_Initialized;
         }
         }
-
         public Attribute Color
         public Attribute Color
         {
         {
             get => new (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor);
             get => new (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor);
@@ -183,31 +192,15 @@ public class Adornments : Scenario
                 }
                 }
 
 
                 _thickness = value;
                 _thickness = value;
-                ThicknessChanged?.Invoke (this, new() { Thickness = Thickness });
+                ThicknessChanged?.Invoke (this, new () { Thickness = Thickness });
 
 
                 if (IsInitialized)
                 if (IsInitialized)
                 {
                 {
                     _isUpdating = true;
                     _isUpdating = true;
-
-                    if (_topEdit.Text != _thickness.Top.ToString ())
-                    {
-                        _topEdit.Text = _thickness.Top.ToString ();
-                    }
-
-                    if (_leftEdit.Text != _thickness.Left.ToString ())
-                    {
-                        _leftEdit.Text = _thickness.Left.ToString ();
-                    }
-
-                    if (_rightEdit.Text != _thickness.Right.ToString ())
-                    {
-                        _rightEdit.Text = _thickness.Right.ToString ();
-                    }
-
-                    if (_bottomEdit.Text != _thickness.Bottom.ToString ())
-                    {
-                        _bottomEdit.Text = _thickness.Bottom.ToString ();
-                    }
+                    _topEdit.Value = _thickness.Top;
+                    _leftEdit.Value = _thickness.Left;
+                    _rightEdit.Value = _thickness.Right;
+                    _bottomEdit.Value = _thickness.Bottom;
 
 
                     _isUpdating = false;
                     _isUpdating = false;
                 }
                 }
@@ -219,49 +212,44 @@ public class Adornments : Scenario
 
 
         private void AdornmentEditor_Initialized (object sender, EventArgs e)
         private void AdornmentEditor_Initialized (object sender, EventArgs e)
         {
         {
-            var editWidth = 3;
-
-            _topEdit = new() { X = Pos.Center (), Y = 0, Width = editWidth };
+            _topEdit = new ()
+            {
+                X = Pos.Center (), Y = 0
+            };
 
 
-            _topEdit.Accept += Edit_Accept;
+            _topEdit.ValueChanging += Top_ValueChanging;
             Add (_topEdit);
             Add (_topEdit);
 
 
-            _leftEdit = new()
+            _leftEdit = new ()
             {
             {
-                X = Pos.Left (_topEdit) - editWidth, Y = Pos.Bottom (_topEdit), Width = editWidth
+                X = Pos.Left (_topEdit) - Pos.Function (() => _topEdit.Digits) - 2, Y = Pos.Bottom (_topEdit)
             };
             };
 
 
-            _leftEdit.Accept += Edit_Accept;
+            _leftEdit.ValueChanging += Left_ValueChanging;
             Add (_leftEdit);
             Add (_leftEdit);
 
 
-            _rightEdit = new() { X = Pos.Right (_topEdit), Y = Pos.Bottom (_topEdit), Width = editWidth };
+            _rightEdit = new () { X = Pos.Right (_leftEdit) + 5, Y = Pos.Bottom (_topEdit) };
 
 
-            _rightEdit.Accept += Edit_Accept;
+            _rightEdit.ValueChanging += Right_ValueChanging;
             Add (_rightEdit);
             Add (_rightEdit);
 
 
-            _bottomEdit = new() { X = Pos.Center (), Y = Pos.Bottom (_leftEdit), Width = editWidth };
+            _bottomEdit = new () { X = Pos.Center (), Y = Pos.Bottom (_leftEdit) };
 
 
-            _bottomEdit.Accept += Edit_Accept;
+            _bottomEdit.ValueChanging += Bottom_ValueChanging;
             Add (_bottomEdit);
             Add (_bottomEdit);
 
 
-            var copyTop = new Button { X = Pos.Center () + 1, Y = Pos.Bottom (_bottomEdit), Text = "Cop_y Top" };
+            var copyTop = new Button { X = Pos.Center (), Y = Pos.Bottom (_bottomEdit), Text = "Cop_y Top" };
 
 
             copyTop.Accept += (s, e) =>
             copyTop.Accept += (s, e) =>
                               {
                               {
                                   Thickness = new (Thickness.Top);
                                   Thickness = new (Thickness.Top);
-
-                                  if (string.IsNullOrEmpty (_topEdit.Text))
-                                  {
-                                      _topEdit.Text = "0";
-                                  }
-
-                                  _bottomEdit.Text = _leftEdit.Text = _rightEdit.Text = _topEdit.Text;
+                                  _leftEdit.Value = _rightEdit.Value = _bottomEdit.Value = _topEdit.Value;
                               };
                               };
             Add (copyTop);
             Add (copyTop);
 
 
             // Foreground ColorPicker.
             // Foreground ColorPicker.
             _foregroundColorPicker.X = -1;
             _foregroundColorPicker.X = -1;
-            _foregroundColorPicker.Y = Pos.Bottom (copyTop) + 1;
+            _foregroundColorPicker.Y = Pos.Bottom (copyTop);
             _foregroundColorPicker.SelectedColor = Color.Foreground.GetClosestNamedColor ();
             _foregroundColorPicker.SelectedColor = Color.Foreground.GetClosestNamedColor ();
 
 
             _foregroundColorPicker.ColorChanged += (o, a) =>
             _foregroundColorPicker.ColorChanged += (o, a) =>
@@ -289,29 +277,69 @@ public class Adornments : Scenario
                                                                                 );
                                                                                 );
             Add (_backgroundColorPicker);
             Add (_backgroundColorPicker);
 
 
-            _topEdit.Text = $"{Thickness.Top}";
-            _leftEdit.Text = $"{Thickness.Left}";
-            _rightEdit.Text = $"{Thickness.Right}";
-            _bottomEdit.Text = $"{Thickness.Bottom}";
+            _topEdit.Value = Thickness.Top;
+            _leftEdit.Value = Thickness.Left;
+            _rightEdit.Value = Thickness.Right;
+            _bottomEdit.Value = Thickness.Bottom;
 
 
             LayoutSubviews ();
             LayoutSubviews ();
-            Height = GetAdornmentsThickness ().Vertical + 4 + 4;
+            Height = GetAdornmentsThickness ().Vertical + 4 + 3;
             Width = GetAdornmentsThickness ().Horizontal + _foregroundColorPicker.Frame.Width * 2 - 3;
             Width = GetAdornmentsThickness ().Horizontal + _foregroundColorPicker.Frame.Width * 2 - 3;
         }
         }
 
 
-        private void Edit_Accept (object sender, CancelEventArgs e)
+        private void Top_ValueChanging (object sender, StateEventArgs<int> e)
         {
         {
-            e.Cancel = true;
+            if (e.NewValue < 0)
+            {
+                e.Cancel = true;
 
 
-            Thickness = new (
-                             int.Parse (_leftEdit.Text),
-                             int.Parse (_topEdit.Text),
-                             int.Parse (_rightEdit.Text),
-                             int.Parse (_bottomEdit.Text));
+                return;
+            }
+
+            Thickness.Top = e.NewValue;
+        }
+
+        private void Left_ValueChanging (object sender, StateEventArgs<int> e)
+        {
+            if (e.NewValue < 0)
+            {
+                e.Cancel = true;
+
+                return;
+            }
+
+            Thickness.Left = e.NewValue;
+        }
+
+        private void Right_ValueChanging (object sender, StateEventArgs<int> e)
+        {
+            if (e.NewValue < 0)
+            {
+                e.Cancel = true;
+
+                return;
+            }
+
+            Thickness.Right = e.NewValue;
+        }
+
+        private void Bottom_ValueChanging (object sender, StateEventArgs<int> e)
+        {
+            if (e.NewValue < 0)
+            {
+                e.Cancel = true;
+
+                return;
+            }
+
+            Thickness.Bottom = e.NewValue;
         }
         }
     }
     }
 
 
-    public class AdornmentsEditor : Window
+    /// <summary>
+    /// Provides an editor UI for the Margin, Border, and Padding of a View.
+    /// </summary>
+    public class AdornmentsEditor : View
     {
     {
         private AdornmentEditor _borderEditor;
         private AdornmentEditor _borderEditor;
         private CheckBox _diagCheckBox;
         private CheckBox _diagCheckBox;
@@ -320,6 +348,15 @@ public class Adornments : Scenario
         private AdornmentEditor _paddingEditor;
         private AdornmentEditor _paddingEditor;
         private View _viewToEdit;
         private View _viewToEdit;
 
 
+        public AdornmentsEditor ()
+        {
+            ColorScheme = Colors.ColorSchemes ["Dialog"];
+
+            // TOOD: Use Dim.Auto
+            Width = 36;
+            Height = Dim.Fill ();
+        }
+
         public View ViewToEdit
         public View ViewToEdit
         {
         {
             get => _viewToEdit;
             get => _viewToEdit;
@@ -328,26 +365,26 @@ public class Adornments : Scenario
                 _origTitle = value.Title;
                 _origTitle = value.Title;
                 _viewToEdit = value;
                 _viewToEdit = value;
 
 
-                _marginEditor = new()
+                _marginEditor = new ()
                 {
                 {
                     X = 0,
                     X = 0,
                     Y = 0,
                     Y = 0,
                     Title = "_Margin",
                     Title = "_Margin",
                     Thickness = _viewToEdit.Margin.Thickness,
                     Thickness = _viewToEdit.Margin.Thickness,
-                    Color = new (_viewToEdit.Margin.ColorScheme.Normal),
+                    Color = new (_viewToEdit.Margin.ColorScheme?.Normal ?? ColorScheme.Normal),
                     SuperViewRendersLineCanvas = true
                     SuperViewRendersLineCanvas = true
                 };
                 };
                 _marginEditor.ThicknessChanged += Editor_ThicknessChanged;
                 _marginEditor.ThicknessChanged += Editor_ThicknessChanged;
                 _marginEditor.AttributeChanged += Editor_AttributeChanged;
                 _marginEditor.AttributeChanged += Editor_AttributeChanged;
                 Add (_marginEditor);
                 Add (_marginEditor);
 
 
-                _borderEditor = new()
+                _borderEditor = new ()
                 {
                 {
                     X = Pos.Left (_marginEditor),
                     X = Pos.Left (_marginEditor),
                     Y = Pos.Bottom (_marginEditor),
                     Y = Pos.Bottom (_marginEditor),
                     Title = "B_order",
                     Title = "B_order",
                     Thickness = _viewToEdit.Border.Thickness,
                     Thickness = _viewToEdit.Border.Thickness,
-                    Color = new (_viewToEdit.Border.ColorScheme.Normal),
+                    Color = new (_viewToEdit.Border.ColorScheme?.Normal ?? ColorScheme.Normal),
                     SuperViewRendersLineCanvas = true
                     SuperViewRendersLineCanvas = true
                 };
                 };
                 _borderEditor.ThicknessChanged += Editor_ThicknessChanged;
                 _borderEditor.ThicknessChanged += Editor_ThicknessChanged;
@@ -411,7 +448,7 @@ public class Adornments : Scenario
                                     {
                                     {
                                         if (ckbTitle.Checked == true)
                                         if (ckbTitle.Checked == true)
                                         {
                                         {
-                                            _viewToEdit.Title = _origTitle;
+                                            //_viewToEdit.Title = _origTitle;
                                         }
                                         }
                                         else
                                         else
                                         {
                                         {
@@ -420,13 +457,13 @@ public class Adornments : Scenario
                                     };
                                     };
                 Add (ckbTitle);
                 Add (ckbTitle);
 
 
-                _paddingEditor = new()
+                _paddingEditor = new ()
                 {
                 {
                     X = Pos.Left (_borderEditor),
                     X = Pos.Left (_borderEditor),
                     Y = Pos.Bottom (rbBorderStyle),
                     Y = Pos.Bottom (rbBorderStyle),
                     Title = "_Padding",
                     Title = "_Padding",
                     Thickness = _viewToEdit.Padding.Thickness,
                     Thickness = _viewToEdit.Padding.Thickness,
-                    Color = new (_viewToEdit.Padding.ColorScheme.Normal),
+                    Color = new (_viewToEdit.Padding.ColorScheme?.Normal ?? ColorScheme.Normal),
                     SuperViewRendersLineCanvas = true
                     SuperViewRendersLineCanvas = true
                 };
                 };
                 _paddingEditor.ThicknessChanged += Editor_ThicknessChanged;
                 _paddingEditor.ThicknessChanged += Editor_ThicknessChanged;
@@ -450,7 +487,6 @@ public class Adornments : Scenario
                                          };
                                          };
 
 
                 Add (_diagCheckBox);
                 Add (_diagCheckBox);
-                Add (_viewToEdit);
 
 
                 _viewToEdit.LayoutComplete += (s, e) =>
                 _viewToEdit.LayoutComplete += (s, e) =>
                                               {
                                               {

+ 5 - 5
UICatalog/Scenarios/Animation.cs

@@ -176,16 +176,16 @@ public class Animation : Scenario
         private Rectangle oldSize = Rectangle.Empty;
         private Rectangle oldSize = Rectangle.Empty;
         public void NextFrame () { currentFrame = (currentFrame + 1) % frameCount; }
         public void NextFrame () { currentFrame = (currentFrame + 1) % frameCount; }
 
 
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
         {
-            base.OnDrawContent (contentArea);
+            base.OnDrawContent (viewport);
 
 
-            if (oldSize != Bounds)
+            if (oldSize != Viewport)
             {
             {
                 // Invalidate cached images now size has changed
                 // Invalidate cached images now size has changed
                 matchSizes = new Image<Rgba32> [frameCount];
                 matchSizes = new Image<Rgba32> [frameCount];
                 brailleCache = new string [frameCount];
                 brailleCache = new string [frameCount];
-                oldSize = Bounds;
+                oldSize = Viewport;
             }
             }
 
 
             Image<Rgba32> imgScaled = matchSizes [currentFrame];
             Image<Rgba32> imgScaled = matchSizes [currentFrame];
@@ -196,7 +196,7 @@ public class Animation : Scenario
                 Image<Rgba32> imgFull = fullResImages [currentFrame];
                 Image<Rgba32> imgFull = fullResImages [currentFrame];
 
 
                 // keep aspect ratio
                 // keep aspect ratio
-                int newSize = Math.Min (Bounds.Width, Bounds.Height);
+                int newSize = Math.Min (Viewport.Width, Viewport.Height);
 
 
                 // generate one
                 // generate one
                 matchSizes [currentFrame] = imgScaled = imgFull.Clone (
                 matchSizes [currentFrame] = imgScaled = imgFull.Clone (

+ 6 - 1
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -36,6 +36,7 @@ public class BackgroundWorkerCollection : Scenario
 
 
         public OverlappedMain ()
         public OverlappedMain ()
         {
         {
+            Arrangement = ViewArrangement.Movable;
             Data = "OverlappedMain";
             Data = "OverlappedMain";
 
 
             IsOverlappedContainer = true;
             IsOverlappedContainer = true;
@@ -258,6 +259,8 @@ public class BackgroundWorkerCollection : Scenario
 
 
         public StagingUIController ()
         public StagingUIController ()
         {
         {
+            Arrangement = ViewArrangement.Movable;
+
             X = Pos.Center ();
             X = Pos.Center ();
             Y = Pos.Center ();
             Y = Pos.Center ();
             Width = Dim.Percent (85);
             Width = Dim.Percent (85);
@@ -303,7 +306,7 @@ public class BackgroundWorkerCollection : Scenario
             LayoutStarted += (s, e) =>
             LayoutStarted += (s, e) =>
                              {
                              {
                                  int btnsWidth = _start.Frame.Width + _close.Frame.Width + 2 - 1;
                                  int btnsWidth = _start.Frame.Width + _close.Frame.Width + 2 - 1;
-                                 int shiftLeft = Math.Max ((Bounds.Width - btnsWidth) / 2 - 2, 0);
+                                 int shiftLeft = Math.Max ((Viewport.Width - btnsWidth) / 2 - 2, 0);
 
 
                                  shiftLeft += _close.Frame.Width + 1;
                                  shiftLeft += _close.Frame.Width + 1;
                                  _close.X = Pos.AnchorEnd (shiftLeft);
                                  _close.X = Pos.AnchorEnd (shiftLeft);
@@ -338,6 +341,8 @@ public class BackgroundWorkerCollection : Scenario
 
 
         public WorkerApp ()
         public WorkerApp ()
         {
         {
+            Arrangement = ViewArrangement.Movable;
+
             Data = "WorkerApp";
             Data = "WorkerApp";
             Title = "Worker collection Log";
             Title = "Worker collection Log";
 
 

+ 290 - 167
UICatalog/Scenarios/Buttons.cs

@@ -1,5 +1,8 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
 using System.Text;
 using System.Text;
+using JetBrains.Annotations;
 using Terminal.Gui;
 using Terminal.Gui;
 
 
 namespace UICatalog.Scenarios;
 namespace UICatalog.Scenarios;
@@ -13,8 +16,9 @@ public class Buttons : Scenario
     {
     {
         Window main = new ()
         Window main = new ()
         {
         {
-            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"
         };
         };
+
         // Add a label & text field so we can demo IsDefault
         // Add a label & text field so we can demo IsDefault
         var editLabel = new Label { X = 0, Y = 0, TabStop = true, Text = "TextField (to demo IsDefault):" };
         var editLabel = new Label { X = 0, Y = 0, TabStop = true, Text = "TextField (to demo IsDefault):" };
         main.Add (editLabel);
         main.Add (editLabel);
@@ -32,19 +36,19 @@ public class Buttons : Scenario
         var swapButton = new Button { X = 50, Text = "S_wap Default (Absolute Layout)" };
         var swapButton = new Button { X = 50, Text = "S_wap Default (Absolute Layout)" };
 
 
         swapButton.Accept += (s, e) =>
         swapButton.Accept += (s, e) =>
-                              {
-                                  defaultButton.IsDefault = !defaultButton.IsDefault;
-                                  swapButton.IsDefault = !swapButton.IsDefault;
-                              };
+                             {
+                                 defaultButton.IsDefault = !defaultButton.IsDefault;
+                                 swapButton.IsDefault = !swapButton.IsDefault;
+                             };
         main.Add (swapButton);
         main.Add (swapButton);
 
 
         static void DoMessage (Button button, string txt)
         static void DoMessage (Button button, string txt)
         {
         {
             button.Accept += (s, e) =>
             button.Accept += (s, e) =>
-                              {
-                                  string btnText = button.Text;
-                                  MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No");
-                              };
+                             {
+                                 string btnText = button.Text;
+                                 MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No");
+                             };
         }
         }
 
 
         var colorButtonsLabel = new Label { X = 0, Y = Pos.Bottom (editLabel) + 1, Text = "Color Buttons:" };
         var colorButtonsLabel = new Label { X = 0, Y = Pos.Bottom (editLabel) + 1, Text = "Color Buttons:" };
@@ -75,20 +79,20 @@ public class Buttons : Scenario
         Button button;
         Button button;
 
 
         main.Add (
         main.Add (
-                 button = new Button
-                 {
-                     X = 2,
-                     Y = Pos.Bottom (colorButtonsLabel) + 1,
-                     Text =
-                         "A super l_öng Button that will probably expose a bug in clipping or wrapping of text. Will it?"
-                 }
-                );
+                  button = new ()
+                  {
+                      X = 2,
+                      Y = Pos.Bottom (colorButtonsLabel) + 1,
+                      Text =
+                          "A super l_öng Button that will probably expose a bug in clipping or wrapping of text. Will it?"
+                  }
+                 );
         DoMessage (button, button.Text);
         DoMessage (button, button.Text);
 
 
         // Note the 'N' in 'Newline' will be the hotkey
         // Note the 'N' in 'Newline' will be the hotkey
         main.Add (
         main.Add (
-                 button = new Button { X = 2, Y = Pos.Bottom (button) + 1, Text = "a Newline\nin the button" }
-                );
+                  button = new () { X = 2, Y = Pos.Bottom (button) + 1, Text = "a Newline\nin the button" }
+                 );
         button.Accept += (s, e) => MessageBox.Query ("Message", "Question?", "Yes", "No");
         button.Accept += (s, e) => MessageBox.Query ("Message", "Question?", "Yes", "No");
 
 
         var textChanger = new Button { X = 2, Y = Pos.Bottom (button) + 1, Text = "Te_xt Changer" };
         var textChanger = new Button { X = 2, Y = Pos.Bottom (button) + 1, Text = "Te_xt Changer" };
@@ -96,13 +100,13 @@ public class Buttons : Scenario
         textChanger.Accept += (s, e) => textChanger.Text += "!";
         textChanger.Accept += (s, e) => textChanger.Text += "!";
 
 
         main.Add (
         main.Add (
-                 button = new Button
-                 {
-                     X = Pos.Right (textChanger) + 2,
-                     Y = Pos.Y (textChanger),
-                     Text = "Lets see if this will move as \"Text Changer\" grows"
-                 }
-                );
+                  button = new ()
+                  {
+                      X = Pos.Right (textChanger) + 2,
+                      Y = Pos.Y (textChanger),
+                      Text = "Lets see if this will move as \"Text Changer\" grows"
+                  }
+                 );
 
 
         var removeButton = new Button
         var removeButton = new Button
         {
         {
@@ -112,12 +116,12 @@ public class Buttons : Scenario
 
 
         // This in interesting test case because `moveBtn` and below are laid out relative to this one!
         // This in interesting test case because `moveBtn` and below are laid out relative to this one!
         removeButton.Accept += (s, e) =>
         removeButton.Accept += (s, e) =>
-                                {
-                                    // Now this throw a InvalidOperationException on the TopologicalSort method as is expected.
-                                    //main.Remove (removeButton);
+                               {
+                                   // Now this throw a InvalidOperationException on the TopologicalSort method as is expected.
+                                   //main.Remove (removeButton);
 
 
-                                    removeButton.Visible = false;
-                                };
+                                   removeButton.Visible = false;
+                               };
 
 
         var computedFrame = new FrameView
         var computedFrame = new FrameView
         {
         {
@@ -142,12 +146,12 @@ public class Buttons : Scenario
         };
         };
 
 
         moveBtn.Accept += (s, e) =>
         moveBtn.Accept += (s, e) =>
-                           {
-                               moveBtn.X = moveBtn.Frame.X + 5;
+                          {
+                              moveBtn.X = moveBtn.Frame.X + 5;
 
 
-                               // This is already fixed with the call to SetNeedDisplay() in the Pos Dim.
-                               //computedFrame.LayoutSubviews (); // BUGBUG: This call should not be needed. View.X is not causing relayout correctly
-                           };
+                              // This is already fixed with the call to SetNeedDisplay() in the Pos Dim.
+                              //computedFrame.LayoutSubviews (); // BUGBUG: This call should not be needed. View.X is not causing relayout correctly
+                          };
         computedFrame.Add (moveBtn);
         computedFrame.Add (moveBtn);
 
 
         // Demonstrates how changing the View.Frame property can SIZE Views (#583)
         // Demonstrates how changing the View.Frame property can SIZE Views (#583)
@@ -163,11 +167,11 @@ public class Buttons : Scenario
         };
         };
 
 
         sizeBtn.Accept += (s, e) =>
         sizeBtn.Accept += (s, e) =>
-                           {
-                               sizeBtn.Width = sizeBtn.Frame.Width + 5;
+                          {
+                              sizeBtn.Width = sizeBtn.Frame.Width + 5;
 
 
-                               //computedFrame.LayoutSubviews (); // FIXED: This call should not be needed. View.X is not causing relayout correctly
-                           };
+                              //computedFrame.LayoutSubviews (); // FIXED: This call should not be needed. View.X is not causing relayout correctly
+                          };
         computedFrame.Add (sizeBtn);
         computedFrame.Add (sizeBtn);
 
 
         var absoluteFrame = new FrameView
         var absoluteFrame = new FrameView
@@ -184,14 +188,14 @@ public class Buttons : Scenario
         var moveBtnA = new Button { ColorScheme = Colors.ColorSchemes ["Error"], Text = "Move This Button via Frame" };
         var moveBtnA = new Button { ColorScheme = Colors.ColorSchemes ["Error"], Text = "Move This Button via Frame" };
 
 
         moveBtnA.Accept += (s, e) =>
         moveBtnA.Accept += (s, e) =>
-                            {
-                                moveBtnA.Frame = new Rectangle (
-                                                           moveBtnA.Frame.X + 5,
-                                                           moveBtnA.Frame.Y,
-                                                           moveBtnA.Frame.Width,
-                                                           moveBtnA.Frame.Height
-                                                          );
-                            };
+                           {
+                               moveBtnA.Frame = new (
+                                                     moveBtnA.Frame.X + 5,
+                                                     moveBtnA.Frame.Y,
+                                                     moveBtnA.Frame.Width,
+                                                     moveBtnA.Frame.Height
+                                                    );
+                           };
         absoluteFrame.Add (moveBtnA);
         absoluteFrame.Add (moveBtnA);
 
 
         // Demonstrates how changing the View.Frame property can SIZE Views (#583)
         // Demonstrates how changing the View.Frame property can SIZE Views (#583)
@@ -201,14 +205,14 @@ public class Buttons : Scenario
         };
         };
 
 
         sizeBtnA.Accept += (s, e) =>
         sizeBtnA.Accept += (s, e) =>
-                            {
-                                sizeBtnA.Frame = new Rectangle (
-                                                           sizeBtnA.Frame.X,
-                                                           sizeBtnA.Frame.Y,
-                                                           sizeBtnA.Frame.Width + 5,
-                                                           sizeBtnA.Frame.Height
-                                                          );
-                            };
+                           {
+                               sizeBtnA.Frame = new (
+                                                     sizeBtnA.Frame.X,
+                                                     sizeBtnA.Frame.Y,
+                                                     sizeBtnA.Frame.Width + 5,
+                                                     sizeBtnA.Frame.Height
+                                                    );
+                           };
         absoluteFrame.Add (sizeBtnA);
         absoluteFrame.Add (sizeBtnA);
 
 
         var label = new Label
         var label = new Label
@@ -289,154 +293,273 @@ public class Buttons : Scenario
         main.Add (moveUnicodeHotKeyBtn);
         main.Add (moveUnicodeHotKeyBtn);
 
 
         radioGroup.SelectedItemChanged += (s, args) =>
         radioGroup.SelectedItemChanged += (s, args) =>
-        {
-            switch (args.SelectedItem)
-            {
-                case 0:
-                    moveBtn.TextAlignment = TextAlignment.Left;
-                    sizeBtn.TextAlignment = TextAlignment.Left;
-                    moveBtnA.TextAlignment = TextAlignment.Left;
-                    sizeBtnA.TextAlignment = TextAlignment.Left;
-                    moveHotKeyBtn.TextAlignment = TextAlignment.Left;
-                    moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left;
-
-                    break;
-                case 1:
-                    moveBtn.TextAlignment = TextAlignment.Right;
-                    sizeBtn.TextAlignment = TextAlignment.Right;
-                    moveBtnA.TextAlignment = TextAlignment.Right;
-                    sizeBtnA.TextAlignment = TextAlignment.Right;
-                    moveHotKeyBtn.TextAlignment = TextAlignment.Right;
-                    moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right;
-
-                    break;
-                case 2:
-                    moveBtn.TextAlignment = TextAlignment.Centered;
-                    sizeBtn.TextAlignment = TextAlignment.Centered;
-                    moveBtnA.TextAlignment = TextAlignment.Centered;
-                    sizeBtnA.TextAlignment = TextAlignment.Centered;
-                    moveHotKeyBtn.TextAlignment = TextAlignment.Centered;
-                    moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered;
-
-                    break;
-                case 3:
-                    moveBtn.TextAlignment = TextAlignment.Justified;
-                    sizeBtn.TextAlignment = TextAlignment.Justified;
-                    moveBtnA.TextAlignment = TextAlignment.Justified;
-                    sizeBtnA.TextAlignment = TextAlignment.Justified;
-                    moveHotKeyBtn.TextAlignment = TextAlignment.Justified;
-                    moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified;
-
-                    break;
-            }
-        };
-
-        label = new Label ()
+                                          {
+                                              switch (args.SelectedItem)
+                                              {
+                                                  case 0:
+                                                      moveBtn.TextAlignment = TextAlignment.Left;
+                                                      sizeBtn.TextAlignment = TextAlignment.Left;
+                                                      moveBtnA.TextAlignment = TextAlignment.Left;
+                                                      sizeBtnA.TextAlignment = TextAlignment.Left;
+                                                      moveHotKeyBtn.TextAlignment = TextAlignment.Left;
+                                                      moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left;
+
+                                                      break;
+                                                  case 1:
+                                                      moveBtn.TextAlignment = TextAlignment.Right;
+                                                      sizeBtn.TextAlignment = TextAlignment.Right;
+                                                      moveBtnA.TextAlignment = TextAlignment.Right;
+                                                      sizeBtnA.TextAlignment = TextAlignment.Right;
+                                                      moveHotKeyBtn.TextAlignment = TextAlignment.Right;
+                                                      moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right;
+
+                                                      break;
+                                                  case 2:
+                                                      moveBtn.TextAlignment = TextAlignment.Centered;
+                                                      sizeBtn.TextAlignment = TextAlignment.Centered;
+                                                      moveBtnA.TextAlignment = TextAlignment.Centered;
+                                                      sizeBtnA.TextAlignment = TextAlignment.Centered;
+                                                      moveHotKeyBtn.TextAlignment = TextAlignment.Centered;
+                                                      moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered;
+
+                                                      break;
+                                                  case 3:
+                                                      moveBtn.TextAlignment = TextAlignment.Justified;
+                                                      sizeBtn.TextAlignment = TextAlignment.Justified;
+                                                      moveBtnA.TextAlignment = TextAlignment.Justified;
+                                                      sizeBtnA.TextAlignment = TextAlignment.Justified;
+                                                      moveHotKeyBtn.TextAlignment = TextAlignment.Justified;
+                                                      moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified;
+
+                                                      break;
+                                              }
+                                          };
+
+        label = new ()
         {
         {
             X = 0,
             X = 0,
             Y = Pos.Bottom (moveUnicodeHotKeyBtn) + 1,
             Y = Pos.Bottom (moveUnicodeHotKeyBtn) + 1,
             Title = "_Numeric Up/Down (press-and-hold):",
             Title = "_Numeric Up/Down (press-and-hold):",
         };
         };
-        var downButton = new Button ()
-        {
-            CanFocus = false,
-            AutoSize = false,
-            X = Pos.Right(label)+1,
-            Y = Pos.Top (label),
-            Height = 1,
-            Width = 1,
-            NoPadding = true,
-            NoDecorations = true,
-            Title = $"{CM.Glyphs.DownArrow}",
-            WantContinuousButtonPressed = true,
-        };
 
 
-        var numericEdit = new TextField ()
+        var numericUpDown = new NumericUpDown<int>
         {
         {
-            Text = "1966",
-            X = Pos.Right (downButton),
-            Y = Pos.Top (downButton),
+            Value = 69,
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
             Width = 5,
             Width = 5,
-            Height = 1,
+            Height = 1
         };
         };
-        var upButton = new Button ()
-        {
-            CanFocus = false,
-            AutoSize = false,
-            X = Pos.Right (numericEdit),
-            Y = Pos.Top (numericEdit),
-            Height = 1,
-            Width = 1,
-            NoPadding = true,
-            NoDecorations = true,
-            Title = $"{CM.Glyphs.UpArrow}",
-            WantContinuousButtonPressed = true,
-        };
-        downButton.Accept += (s, e) =>
-                             {
-                                 numericEdit.Text = $"{int.Parse(numericEdit.Text) - 1}";
-                             };
-        upButton.Accept += (s, e) =>
-                           {
-                               numericEdit.Text = $"{int.Parse (numericEdit.Text) + 1}";
-                           };
+        numericUpDown.ValueChanged += NumericUpDown_ValueChanged;
 
 
-        main.Add (label, downButton, numericEdit, upButton);
+        void NumericUpDown_ValueChanged (object sender, StateEventArgs<int> e) { }
 
 
-        label = new Label ()
+        main.Add (label, numericUpDown);
+
+        label = new ()
         {
         {
             X = 0,
             X = 0,
-            Y = Pos.Bottom (label) + 1,
-            Title = "_No Repeat:",
+            Y = Pos.Bottom (numericUpDown) + 1,
+            Title = "_No Repeat:"
         };
         };
-        int noRepeatAcceptCount = 0;
-        var noRepeatButton = new Button ()
+        var noRepeatAcceptCount = 0;
+
+        var noRepeatButton = new Button
         {
         {
             X = Pos.Right (label) + 1,
             X = Pos.Right (label) + 1,
             Y = Pos.Top (label),
             Y = Pos.Top (label),
             Title = $"Accept Cou_nt: {noRepeatAcceptCount}",
             Title = $"Accept Cou_nt: {noRepeatAcceptCount}",
-            WantContinuousButtonPressed = false,
+            WantContinuousButtonPressed = false
         };
         };
-        noRepeatButton.Accept += (s, e) =>
-                                 {
-                                     noRepeatButton.Title = $"Accept Cou_nt: {++noRepeatAcceptCount}";
-                                 };
-        main.Add(label, noRepeatButton);
+        noRepeatButton.Accept += (s, e) => { noRepeatButton.Title = $"Accept Cou_nt: {++noRepeatAcceptCount}"; };
+        main.Add (label, noRepeatButton);
 
 
-        label = new Label ()
+        label = new ()
         {
         {
             X = 0,
             X = 0,
             Y = Pos.Bottom (label) + 1,
             Y = Pos.Bottom (label) + 1,
-            Title = "_Repeat (press-and-hold):",
+            Title = "_Repeat (press-and-hold):"
         };
         };
-        int acceptCount = 0;
-        var repeatButton = new Button ()
+        var acceptCount = 0;
+
+        var repeatButton = new Button
         {
         {
             X = Pos.Right (label) + 1,
             X = Pos.Right (label) + 1,
             Y = Pos.Top (label),
             Y = Pos.Top (label),
             Title = $"Accept Co_unt: {acceptCount}",
             Title = $"Accept Co_unt: {acceptCount}",
-            WantContinuousButtonPressed = true,
+            WantContinuousButtonPressed = true
         };
         };
-        repeatButton.Accept += (s, e) =>
-                               {
-                                   repeatButton.Title = $"Accept Co_unt: {++acceptCount}";
-                               };
+        repeatButton.Accept += (s, e) => { repeatButton.Title = $"Accept Co_unt: {++acceptCount}"; };
 
 
-        var enableCB = new CheckBox ()
+        var enableCB = new CheckBox
         {
         {
             X = Pos.Right (repeatButton) + 1,
             X = Pos.Right (repeatButton) + 1,
             Y = Pos.Top (repeatButton),
             Y = Pos.Top (repeatButton),
             Title = "Enabled",
             Title = "Enabled",
-            Checked = true,
+            Checked = true
         };
         };
-        enableCB.Toggled += (s, e) =>
-                            {
-                                repeatButton.Enabled = !repeatButton.Enabled;
-                            };
-        main.Add(label, repeatButton, enableCB);
+        enableCB.Toggled += (s, e) => { repeatButton.Enabled = !repeatButton.Enabled; };
+        main.Add (label, repeatButton, enableCB);
 
 
         main.Ready += (s, e) => radioGroup.Refresh ();
         main.Ready += (s, e) => radioGroup.Refresh ();
         Application.Run (main);
         Application.Run (main);
         main.Dispose ();
         main.Dispose ();
     }
     }
+
+    /// <summary>
+    /// Enables the user to increase or decrease a value by clicking on the up or down buttons.
+    /// </summary>
+    /// <remarks>
+    ///     Supports the following types: <see cref="int"/>, <see cref="long"/>, <see cref="float"/>, <see cref="double"/>, <see cref="decimal"/>.
+    ///     Supports only one digit of precision.
+    /// </remarks>
+    public class NumericUpDown<T> : View
+    {
+        private readonly Button _down;
+        // TODO: Use a TextField instead of a Label
+        private readonly View _number;
+        private readonly Button _up;
+
+        public NumericUpDown ()
+        {
+            Type type = typeof (T);
+            if (!(type == typeof (int) || type == typeof (long) || type == typeof (float) || type == typeof (double) || type == typeof (decimal)))
+            {
+                throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction.");
+            }
+
+            // TODO: Use Dim.Auto for the Width and Height
+            Height = 1;
+            Width = Dim.Function (() => Digits + 2); // button + 3 for number + button
+
+            _down = new ()
+            {
+                AutoSize = false,
+                Height = 1,
+                Width = 1,
+                NoPadding = true,
+                NoDecorations = true,
+                Title = $"{CM.Glyphs.DownArrow}",
+                WantContinuousButtonPressed = true,
+                CanFocus = false,
+            };
+
+            _number = new ()
+            {
+                Text = Value.ToString (),
+                AutoSize = false,
+                X = Pos.Right (_down),
+                Y = Pos.Top (_down),
+                Width = Dim.Function (() => Digits),
+                Height = 1,
+                TextAlignment = TextAlignment.Centered,
+                CanFocus = true
+            };
+
+            _up = new ()
+            {
+                AutoSize = false,
+                X = Pos.AnchorEnd (1),
+                Y = Pos.Top (_number),
+                Height = 1,
+                Width = 1,
+                NoPadding = true,
+                NoDecorations = true,
+                Title = $"{CM.Glyphs.UpArrow}",
+                WantContinuousButtonPressed = true,
+                CanFocus = false,
+            };
+
+            CanFocus = true;
+
+            _down.Accept += OnDownButtonOnAccept;
+            _up.Accept += OnUpButtonOnAccept;
+
+            Add (_down, _number, _up);
+
+
+            AddCommand (Command.ScrollUp, () =>
+                                          {
+                                              Value = (dynamic)Value + 1;
+                                              _number.Text = Value.ToString ();
+
+                                              return true;
+                                          });
+            AddCommand (Command.ScrollDown, () =>
+                                            {
+                                                Value = (dynamic)Value - 1;
+                                                _number.Text = Value.ToString ();
+
+                                                return true;
+                                            });
+
+            KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
+            KeyBindings.Add (Key.CursorDown, Command.ScrollDown);
+
+            return;
+
+            void OnDownButtonOnAccept (object s, CancelEventArgs e)
+            {
+                InvokeCommand (Command.ScrollDown);
+            }
+
+            void OnUpButtonOnAccept (object s, CancelEventArgs e)
+            {
+                InvokeCommand (Command.ScrollUp);
+            }
+        }
+
+        private void _up_Enter (object sender, FocusEventArgs e)
+        {
+            throw new NotImplementedException ();
+        }
+
+        private T _value;
+
+        /// <summary>
+        /// The value that will be incremented or decremented.
+        /// </summary>
+        public T Value
+        {
+            get => _value;
+            set
+            {
+                if (_value.Equals (value))
+                {
+                    return;
+                }
+
+                T oldValue = value;
+                StateEventArgs<T> args = new StateEventArgs<T> (_value, value);
+                ValueChanging?.Invoke (this, args);
+
+                if (args.Cancel)
+                {
+                    return;
+                }
+
+                _value = value;
+                _number.Text = _value.ToString ();
+                ValueChanged?.Invoke (this, new (oldValue, _value));
+            }
+        }
+
+        /// <summary>
+        /// Fired when the value is about to change. Set <see cref="StateEventArgs{T}.Cancel"/> to true to prevent the change.
+        /// </summary>
+        [CanBeNull]
+        public event EventHandler<StateEventArgs<T>> ValueChanging;
+
+        /// <summary>
+        /// Fired when the value has changed.
+        /// </summary>
+        [CanBeNull]
+        public event EventHandler<StateEventArgs<T>> ValueChanged;
+
+        /// <summary>
+        /// The number of digits to display. The <see cref="View.Viewport"/> will be resized to fit this number of characters plus the buttons. The default is 3.
+        /// </summary>
+        public int Digits { get; set; } = 3;
+    }
 }
 }
+

+ 248 - 134
UICatalog/Scenarios/CharacterMap.cs

@@ -1,3 +1,5 @@
+#define OTHER_CONTROLS
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
@@ -17,12 +19,15 @@ namespace UICatalog.Scenarios;
 /// <summary>
 /// <summary>
 ///     This Scenario demonstrates building a custom control (a class deriving from View) that: - Provides a
 ///     This Scenario demonstrates building a custom control (a class deriving from View) that: - Provides a
 ///     "Character Map" application (like Windows' charmap.exe). - Helps test unicode character rendering in Terminal.Gui -
 ///     "Character Map" application (like Windows' charmap.exe). - Helps test unicode character rendering in Terminal.Gui -
-///     Illustrates how to use ScrollView to do infinite scrolling
+///     Illustrates how to do infinite scrolling
 /// </summary>
 /// </summary>
-[ScenarioMetadata ("Character Map", "Unicode viewer demonstrating the ScrollView control.")]
+[ScenarioMetadata ("Character Map", "Unicode viewer demonstrating infinite content, scrolling, and Unicode.")]
 [ScenarioCategory ("Text and Formatting")]
 [ScenarioCategory ("Text and Formatting")]
+[ScenarioCategory ("Drawing")]
 [ScenarioCategory ("Controls")]
 [ScenarioCategory ("Controls")]
-[ScenarioCategory ("ScrollView")]
+[ScenarioCategory ("Layout")]
+[ScenarioCategory ("Scrolling")]
+
 public class CharacterMap : Scenario
 public class CharacterMap : Scenario
 {
 {
     public Label _errorLabel;
     public Label _errorLabel;
@@ -30,17 +35,26 @@ public class CharacterMap : Scenario
     private CharMap _charMap;
     private CharMap _charMap;
 
 
     // Don't create a Window, just return the top-level view
     // Don't create a Window, just return the top-level view
-    public override void Init ()
+    public override void Main ()
     {
     {
         Application.Init ();
         Application.Init ();
-        Top = new ();
-        Top.ColorScheme = Colors.ColorSchemes ["Base"];
-    }
 
 
-    public override void Setup ()
-    {
-        _charMap = new() { X = 0, Y = 1, Height = Dim.Fill () };
-        Top.Add (_charMap);
+        var top = new Window
+        {
+            BorderStyle = LineStyle.None
+        };
+
+        _charMap = new ()
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+        };
+        top.Add (_charMap);
+
+#if OTHER_CONTROLS
+        _charMap.Y = 1;
 
 
         var jumpLabel = new Label
         var jumpLabel = new Label
         {
         {
@@ -49,19 +63,19 @@ public class CharacterMap : Scenario
             HotKeySpecifier = (Rune)'_',
             HotKeySpecifier = (Rune)'_',
             Text = "_Jump To Code Point:"
             Text = "_Jump To Code Point:"
         };
         };
-        Top.Add (jumpLabel);
+        top.Add (jumpLabel);
 
 
         var jumpEdit = new TextField
         var jumpEdit = new TextField
         {
         {
             X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, Caption = "e.g. 01BE3"
             X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, Caption = "e.g. 01BE3"
         };
         };
-        Top.Add (jumpEdit);
+        top.Add (jumpEdit);
 
 
-        _errorLabel = new()
+        _errorLabel = new ()
         {
         {
             X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"], Text = "err"
             X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"], Text = "err"
         };
         };
-        Top.Add (_errorLabel);
+        top.Add (_errorLabel);
 
 
 #if TEXT_CHANGED_TO_JUMP
 #if TEXT_CHANGED_TO_JUMP
         jumpEdit.TextChanged += JumpEdit_TextChanged;
         jumpEdit.TextChanged += JumpEdit_TextChanged;
@@ -76,8 +90,7 @@ public class CharacterMap : Scenario
             e.Cancel = true;
             e.Cancel = true;
         }
         }
 #endif
 #endif
-        _categoryList = new() { X = Pos.Right (_charMap), Y = Pos.Bottom (jumpLabel), Height = Dim.Fill () };
-
+        _categoryList = new () { X = Pos.Right (_charMap), Y = Pos.Bottom (jumpLabel), Height = Dim.Fill () };
         _categoryList.FullRowSelect = true;
         _categoryList.FullRowSelect = true;
 
 
         //jumpList.Style.ShowHeaders = false;
         //jumpList.Style.ShowHeaders = false;
@@ -120,10 +133,10 @@ public class CharacterMap : Scenario
 
 
         _categoryList.Style.ColumnStyles.Add (
         _categoryList.Style.ColumnStyles.Add (
                                               0,
                                               0,
-                                              new() { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName }
+                                              new () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName }
                                              );
                                              );
-        _categoryList.Style.ColumnStyles.Add (1, new() { MaxWidth = 1, MinWidth = 6 });
-        _categoryList.Style.ColumnStyles.Add (2, new() { MaxWidth = 1, MinWidth = 6 });
+        _categoryList.Style.ColumnStyles.Add (1, new () { MaxWidth = 1, MinWidth = 6 });
+        _categoryList.Style.ColumnStyles.Add (2, new () { MaxWidth = 1, MinWidth = 6 });
 
 
         _categoryList.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4;
         _categoryList.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4;
 
 
@@ -133,10 +146,7 @@ public class CharacterMap : Scenario
                                                  _charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
                                                  _charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
                                              };
                                              };
 
 
-        Top.Add (_categoryList);
-
-        _charMap.SelectedCodePoint = 0;
-        _charMap.SetFocus ();
+        top.Add (_categoryList);
 
 
         // TODO: Replace this with Dim.Auto when that's ready
         // TODO: Replace this with Dim.Auto when that's ready
         _categoryList.Initialized += _categoryList_Initialized;
         _categoryList.Initialized += _categoryList_Initialized;
@@ -162,7 +172,14 @@ public class CharacterMap : Scenario
                     )
                     )
             ]
             ]
         };
         };
-        Top.Add (menu);
+        top.Add (menu);
+#endif // OTHER_CONTROLS
+
+        _charMap.SelectedCodePoint = 0;
+        _charMap.SetFocus ();
+
+        Application.Run (top);
+        top.Dispose ();
     }
     }
 
 
     private void _categoryList_Initialized (object sender, EventArgs e) { _charMap.Width = Dim.Fill () - _categoryList.Width; }
     private void _categoryList_Initialized (object sender, EventArgs e) { _charMap.Width = Dim.Fill () - _categoryList.Width; }
@@ -203,7 +220,7 @@ public class CharacterMap : Scenario
 
 
         return new (
         return new (
                     sortedRanges,
                     sortedRanges,
-                    new()
+                    new ()
                     {
                     {
                         { $"Category{categorySort}", s => s.Category },
                         { $"Category{categorySort}", s => s.Category },
                         { $"Start{startSort}", s => $"{s.Start:x5}" },
                         { $"Start{startSort}", s => $"{s.Start:x5}" },
@@ -296,7 +313,7 @@ public class CharacterMap : Scenario
     }
     }
 }
 }
 
 
-internal class CharMap : ScrollView
+internal class CharMap : View
 {
 {
     private const CursorVisibility _cursor = CursorVisibility.Default;
     private const CursorVisibility _cursor = CursorVisibility.Default;
     private const int COLUMN_WIDTH = 3;
     private const int COLUMN_WIDTH = 3;
@@ -311,10 +328,7 @@ internal class CharMap : ScrollView
         ColorScheme = Colors.ColorSchemes ["Dialog"];
         ColorScheme = Colors.ColorSchemes ["Dialog"];
         CanFocus = true;
         CanFocus = true;
 
 
-        ContentSize = new (
-                           RowWidth,
-                           (MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)) * _rowHeight
-                          );
+        ContentSize = new (RowWidth, (MaxCodePoint / 16 + 2) * _rowHeight);
 
 
         AddCommand (
         AddCommand (
                     Command.ScrollUp,
                     Command.ScrollUp,
@@ -325,6 +339,8 @@ internal class CharMap : ScrollView
                             SelectedCodePoint -= 16;
                             SelectedCodePoint -= 16;
                         }
                         }
 
 
+                        ScrollVertical (-_rowHeight);
+
                         return true;
                         return true;
                     }
                     }
                    );
                    );
@@ -333,11 +349,16 @@ internal class CharMap : ScrollView
                     Command.ScrollDown,
                     Command.ScrollDown,
                     () =>
                     () =>
                     {
                     {
-                        if (SelectedCodePoint < MaxCodePoint - 16)
+                        if (SelectedCodePoint <= MaxCodePoint - 16)
                         {
                         {
                             SelectedCodePoint += 16;
                             SelectedCodePoint += 16;
                         }
                         }
 
 
+                        if (Cursor.Y >= Viewport.Height)
+                        {
+                            ScrollVertical (_rowHeight);
+                        }
+
                         return true;
                         return true;
                     }
                     }
                    );
                    );
@@ -351,6 +372,11 @@ internal class CharMap : ScrollView
                             SelectedCodePoint--;
                             SelectedCodePoint--;
                         }
                         }
 
 
+                        if (Cursor.X > RowLabelWidth + 1)
+                        {
+                            ScrollHorizontal (-COLUMN_WIDTH);
+                        }
+
                         return true;
                         return true;
                     }
                     }
                    );
                    );
@@ -364,6 +390,11 @@ internal class CharMap : ScrollView
                             SelectedCodePoint++;
                             SelectedCodePoint++;
                         }
                         }
 
 
+                        if (Cursor.X >= Viewport.Width)
+                        {
+                            ScrollHorizontal (COLUMN_WIDTH);
+                        }
+
                         return true;
                         return true;
                     }
                     }
                    );
                    );
@@ -372,8 +403,9 @@ internal class CharMap : ScrollView
                     Command.PageUp,
                     Command.PageUp,
                     () =>
                     () =>
                     {
                     {
-                        int page = (Bounds.Height / _rowHeight - 1) * 16;
+                        int page = (Viewport.Height - 1 / _rowHeight) * 16;
                         SelectedCodePoint -= Math.Min (page, SelectedCodePoint);
                         SelectedCodePoint -= Math.Min (page, SelectedCodePoint);
+                        Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight };
 
 
                         return true;
                         return true;
                     }
                     }
@@ -383,8 +415,9 @@ internal class CharMap : ScrollView
                     Command.PageDown,
                     Command.PageDown,
                     () =>
                     () =>
                     {
                     {
-                        int page = (Bounds.Height / _rowHeight - 1) * 16;
+                        int page = (Viewport.Height - 1 / _rowHeight) * 16;
                         SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint);
                         SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint);
+                        Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight };
 
 
                         return true;
                         return true;
                     }
                     }
@@ -405,11 +438,11 @@ internal class CharMap : ScrollView
                     () =>
                     () =>
                     {
                     {
                         SelectedCodePoint = MaxCodePoint;
                         SelectedCodePoint = MaxCodePoint;
+                        Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight };
 
 
                         return true;
                         return true;
                     }
                     }
                    );
                    );
-        KeyBindings.Add (Key.Enter, Command.Accept);
 
 
         AddCommand (
         AddCommand (
                     Command.Accept,
                     Command.Accept,
@@ -421,7 +454,116 @@ internal class CharMap : ScrollView
                     }
                     }
                    );
                    );
 
 
+        KeyBindings.Add (Key.Enter, Command.Accept);
+        KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
+        KeyBindings.Add (Key.CursorDown, Command.ScrollDown);
+        KeyBindings.Add (Key.CursorLeft, Command.ScrollLeft);
+        KeyBindings.Add (Key.CursorRight, Command.ScrollRight);
+        KeyBindings.Add (Key.PageUp, Command.PageUp);
+        KeyBindings.Add (Key.PageDown, Command.PageDown);
+        KeyBindings.Add (Key.Home, Command.TopHome);
+        KeyBindings.Add (Key.End, Command.BottomEnd);
+
         MouseClick += Handle_MouseClick;
         MouseClick += Handle_MouseClick;
+        MouseEvent += Handle_MouseEvent;
+
+        // Prototype scrollbars
+        Padding.Thickness = new (0, 0, 1, 1);
+
+        var up = new Button
+        {
+            AutoSize = false,
+            X = Pos.AnchorEnd (1),
+            Y = 0,
+            Height = 1,
+            Width = 1,
+            NoPadding = true,
+            NoDecorations = true,
+            Title = CM.Glyphs.UpArrow.ToString (),
+            WantContinuousButtonPressed = true,
+            CanFocus = false
+        };
+        up.Accept += (sender, args) => { args.Cancel = ScrollVertical (-1) == true; };
+
+        var down = new Button
+        {
+            AutoSize = false,
+            X = Pos.AnchorEnd (1),
+            Y = Pos.AnchorEnd (2),
+            Height = 1,
+            Width = 1,
+            NoPadding = true,
+            NoDecorations = true,
+            Title = CM.Glyphs.DownArrow.ToString (),
+            WantContinuousButtonPressed = true,
+            CanFocus = false
+        };
+        down.Accept += (sender, args) => { ScrollVertical (1); };
+
+        var left = new Button
+        {
+            AutoSize = false,
+            X = 0,
+            Y = Pos.AnchorEnd (1),
+            Height = 1,
+            Width = 1,
+            NoPadding = true,
+            NoDecorations = true,
+            Title = CM.Glyphs.LeftArrow.ToString (),
+            WantContinuousButtonPressed = true,
+            CanFocus = false
+        };
+        left.Accept += (sender, args) => { ScrollHorizontal (-1); };
+
+        var right = new Button
+        {
+            AutoSize = false,
+            X = Pos.AnchorEnd (2),
+            Y = Pos.AnchorEnd (1),
+            Height = 1,
+            Width = 1,
+            NoPadding = true,
+            NoDecorations = true,
+            Title = CM.Glyphs.RightArrow.ToString (),
+            WantContinuousButtonPressed = true,
+            CanFocus = false
+        };
+        right.Accept += (sender, args) => { ScrollHorizontal (1); };
+
+        Padding.Add (up, down, left, right);
+    }
+
+    private void Handle_MouseEvent (object sender, MouseEventEventArgs e)
+    {
+        if (e.MouseEvent.Flags == MouseFlags.WheeledDown)
+        {
+            ScrollVertical (1);
+            e.Handled = true;
+
+            return;
+        }
+
+        if (e.MouseEvent.Flags == MouseFlags.WheeledUp)
+        {
+            ScrollVertical (-1);
+            e.Handled = true;
+
+            return;
+        }
+
+        if (e.MouseEvent.Flags == MouseFlags.WheeledRight)
+        {
+            ScrollHorizontal (1);
+            e.Handled = true;
+
+            return;
+        }
+
+        if (e.MouseEvent.Flags == MouseFlags.WheeledLeft)
+        {
+            ScrollHorizontal (-1);
+            e.Handled = true;
+        }
     }
     }
 
 
     /// <summary>Gets the coordinates of the Cursor based on the SelectedCodePoint in screen coordinates</summary>
     /// <summary>Gets the coordinates of the Cursor based on the SelectedCodePoint in screen coordinates</summary>
@@ -429,16 +571,16 @@ internal class CharMap : ScrollView
     {
     {
         get
         get
         {
         {
-            int row = SelectedCodePoint / 16 * _rowHeight + ContentOffset.Y + 1;
+            int row = SelectedCodePoint / 16 * _rowHeight - Viewport.Y + 1;
 
 
-            int col = SelectedCodePoint % 16 * COLUMN_WIDTH + ContentOffset.X + RowLabelWidth + 1; // + 1 for padding
+            int col = SelectedCodePoint % 16 * COLUMN_WIDTH - Viewport.X + RowLabelWidth + 1; // + 1 for padding between label and first column
 
 
             return new (col, row);
             return new (col, row);
         }
         }
         set => throw new NotImplementedException ();
         set => throw new NotImplementedException ();
     }
     }
 
 
-    public static int MaxCodePoint => 0x10FFFF;
+    public static int MaxCodePoint = UnicodeRange.Ranges.Max (r => r.End);
 
 
     /// <summary>
     /// <summary>
     ///     Specifies the starting offset for the character map. The default is 0x2500 which is the Box Drawing
     ///     Specifies the starting offset for the character map. The default is 0x2500 which is the Box Drawing
@@ -449,6 +591,11 @@ internal class CharMap : ScrollView
         get => _selected;
         get => _selected;
         set
         set
         {
         {
+            if (_selected == value)
+            {
+                return;
+            }
+
             _selected = value;
             _selected = value;
 
 
             if (IsInitialized)
             if (IsInitialized)
@@ -456,36 +603,28 @@ internal class CharMap : ScrollView
                 int row = SelectedCodePoint / 16 * _rowHeight;
                 int row = SelectedCodePoint / 16 * _rowHeight;
                 int col = SelectedCodePoint % 16 * COLUMN_WIDTH;
                 int col = SelectedCodePoint % 16 * COLUMN_WIDTH;
 
 
-                int height = Bounds.Height - (ShowHorizontalScrollIndicator ? 2 : 1);
-
-                if (row + ContentOffset.Y < 0)
+                if (row - Viewport.Y < 0)
                 {
                 {
                     // Moving up.
                     // Moving up.
-                    ContentOffset = new (ContentOffset.X, row);
+                    Viewport = Viewport with { Y = row };
                 }
                 }
-                else if (row + ContentOffset.Y >= height)
+                else if (row - Viewport.Y >= Viewport.Height)
                 {
                 {
                     // Moving down.
                     // Moving down.
-                    ContentOffset = new (
-                                         ContentOffset.X,
-                                         Math.Min (row, row - height + _rowHeight)
-                                        );
+                    Viewport = Viewport with { Y = row - Viewport.Height };
                 }
                 }
 
 
-                int width = Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
+                int width = Viewport.Width / COLUMN_WIDTH * COLUMN_WIDTH - RowLabelWidth;
 
 
-                if (col + ContentOffset.X < 0)
+                if (col - Viewport.X < 0)
                 {
                 {
                     // Moving left.
                     // Moving left.
-                    ContentOffset = new (col, ContentOffset.Y);
+                    Viewport = Viewport with { X = col };
                 }
                 }
-                else if (col + ContentOffset.X >= width)
+                else if (col - Viewport.X >= width)
                 {
                 {
                     // Moving right.
                     // Moving right.
-                    ContentOffset = new (
-                                         Math.Min (col, col - width + COLUMN_WIDTH),
-                                         ContentOffset.Y
-                                        );
+                    Viewport = Viewport with { X = col - width };
                 }
                 }
             }
             }
 
 
@@ -515,6 +654,7 @@ internal class CharMap : ScrollView
         {
         {
             _start = value;
             _start = value;
             SelectedCodePoint = value;
             SelectedCodePoint = value;
+            Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight };
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
     }
     }
@@ -523,98 +663,71 @@ internal class CharMap : ScrollView
     private static int RowWidth => RowLabelWidth + COLUMN_WIDTH * 16;
     private static int RowWidth => RowLabelWidth + COLUMN_WIDTH * 16;
     public event EventHandler<ListViewItemEventArgs> Hover;
     public event EventHandler<ListViewItemEventArgs> Hover;
 
 
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
     {
-        if (contentArea.Height == 0 || contentArea.Width == 0)
+        if (viewport.Height == 0 || viewport.Width == 0)
         {
         {
             return;
             return;
         }
         }
 
 
-        // Call the base (ScrollView) to draw the scrollbars. Do this ahead of our own drawing so that
-        // any wide or tall glyphs actually render over the scrollbars (on platforms like Windows Terminal) that 
-        // does this correctly.
-        base.OnDrawContent (contentArea);
-
-        Rectangle viewport = new (
-                                  ContentOffset,
-                                  new (
-                                       Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0),
-                                       Math.Max (Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0), 0)
-                                      )
-                                 );
-
-        Rectangle oldClip = ClipToBounds ();
-
-        if (ShowHorizontalScrollIndicator)
-        {
-            // ClipToBounds doesn't know about the scroll indicators, so if off, subtract one from height
-            Driver.Clip = new (Driver.Clip.Location, new (Driver.Clip.Size.Width, Driver.Clip.Size.Height - 1));
-        }
-
-        if (ShowVerticalScrollIndicator)
-        {
-            // ClipToBounds doesn't know about the scroll indicators, so if off, subtract one from width
-            Driver.Clip = new (Driver.Clip.Location, new (Driver.Clip.Size.Width - 1, Driver.Clip.Size.Height));
-        }
+        Clear ();
 
 
-        int cursorCol = Cursor.X - ContentOffset.X - RowLabelWidth - 1;
-        int cursorRow = Cursor.Y - ContentOffset.Y - 1;
+        int cursorCol = Cursor.X + Viewport.X - RowLabelWidth - 1;
+        int cursorRow = Cursor.Y + Viewport.Y - 1;
 
 
         Driver.SetAttribute (GetHotNormalColor ());
         Driver.SetAttribute (GetHotNormalColor ());
         Move (0, 0);
         Move (0, 0);
         Driver.AddStr (new (' ', RowLabelWidth + 1));
         Driver.AddStr (new (' ', RowLabelWidth + 1));
 
 
+        int firstColumnX = RowLabelWidth - Viewport.X;
+
+        // Header
         for (var hexDigit = 0; hexDigit < 16; hexDigit++)
         for (var hexDigit = 0; hexDigit < 16; hexDigit++)
         {
         {
-            int x = ContentOffset.X + RowLabelWidth + hexDigit * COLUMN_WIDTH;
+            int x = firstColumnX + hexDigit * COLUMN_WIDTH;
 
 
             if (x > RowLabelWidth - 2)
             if (x > RowLabelWidth - 2)
             {
             {
                 Move (x, 0);
                 Move (x, 0);
                 Driver.SetAttribute (GetHotNormalColor ());
                 Driver.SetAttribute (GetHotNormalColor ());
                 Driver.AddStr (" ");
                 Driver.AddStr (" ");
-
-                Driver.SetAttribute (
-                                     HasFocus && cursorCol + ContentOffset.X + RowLabelWidth == x
-                                         ? ColorScheme.HotFocus
-                                         : GetHotNormalColor ()
-                                    );
+                Driver.SetAttribute (HasFocus && cursorCol + firstColumnX == x ? ColorScheme.HotFocus : GetHotNormalColor ());
                 Driver.AddStr ($"{hexDigit:x}");
                 Driver.AddStr ($"{hexDigit:x}");
                 Driver.SetAttribute (GetHotNormalColor ());
                 Driver.SetAttribute (GetHotNormalColor ());
                 Driver.AddStr (" ");
                 Driver.AddStr (" ");
             }
             }
         }
         }
 
 
-        int firstColumnX = viewport.X + RowLabelWidth;
-
         // Even though the Clip is set to prevent us from drawing on the row potentially occupied by the horizontal
         // Even though the Clip is set to prevent us from drawing on the row potentially occupied by the horizontal
         // scroll bar, we do the smart thing and not actually draw that row if not necessary.
         // scroll bar, we do the smart thing and not actually draw that row if not necessary.
-        for (var y = 1; y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0); y++)
+        for (var y = 1; y < Viewport.Height; y++)
         {
         {
             // What row is this?
             // What row is this?
-            int row = (y - ContentOffset.Y - 1) / _rowHeight;
+            int row = (y + Viewport.Y - 1) / _rowHeight;
 
 
             int val = row * 16;
             int val = row * 16;
 
 
             if (val > MaxCodePoint)
             if (val > MaxCodePoint)
             {
             {
-                continue;
+                break;
             }
             }
 
 
             Move (firstColumnX + COLUMN_WIDTH, y);
             Move (firstColumnX + COLUMN_WIDTH, y);
             Driver.SetAttribute (GetNormalColor ());
             Driver.SetAttribute (GetNormalColor ());
 
 
-            // Note, this code naïvely draws all columns, even if the viewport is smaller than
-            // the needed width. We rely on Clip to ensure we don't draw past the viewport.
-            // If we were *really* worried about performance, we'd optimize this code to only draw the
-            // parts of the row that are actually visible in the viewport.
             for (var col = 0; col < 16; col++)
             for (var col = 0; col < 16; col++)
             {
             {
                 int x = firstColumnX + COLUMN_WIDTH * col + 1;
                 int x = firstColumnX + COLUMN_WIDTH * col + 1;
 
 
+                if (x < 0 || x > Viewport.Width - 1)
+                {
+                    continue;
+                }
+
                 Move (x, y);
                 Move (x, y);
 
 
-                if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus)
+                // If we're at the cursor position, and we don't have focus, invert the colors.
+                if (row == cursorRow && x == cursorCol && !HasFocus)
                 {
                 {
                     Driver.SetAttribute (GetFocusColor ());
                     Driver.SetAttribute (GetFocusColor ());
                 }
                 }
@@ -629,9 +742,9 @@ internal class CharMap : ScrollView
 
 
                 int width = rune.GetColumns ();
                 int width = rune.GetColumns ();
 
 
-                // are we at first row of the row?
-                if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0)
+                if (!ShowGlyphWidths || (y + Viewport.Y) % _rowHeight > 0)
                 {
                 {
+                    // Draw the rune
                     if (width > 0)
                     if (width > 0)
                     {
                     {
                         Driver.AddRune (rune);
                         Driver.AddRune (rune);
@@ -666,25 +779,24 @@ internal class CharMap : ScrollView
                 }
                 }
                 else
                 else
                 {
                 {
+                    // Draw the width of the rune
                     Driver.SetAttribute (ColorScheme.HotNormal);
                     Driver.SetAttribute (ColorScheme.HotNormal);
                     Driver.AddStr ($"{width}");
                     Driver.AddStr ($"{width}");
                 }
                 }
 
 
-                if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus)
+                // If we're at the cursor position, and we don't have focus, revert the colors to normal
+                if (row == cursorRow && x == cursorCol && !HasFocus)
                 {
                 {
                     Driver.SetAttribute (GetNormalColor ());
                     Driver.SetAttribute (GetNormalColor ());
                 }
                 }
             }
             }
 
 
+            // Draw row label (U+XXXX_)
             Move (0, y);
             Move (0, y);
 
 
-            Driver.SetAttribute (
-                                 HasFocus && cursorRow + ContentOffset.Y + 1 == y
-                                     ? ColorScheme.HotFocus
-                                     : ColorScheme.HotNormal
-                                );
+            Driver.SetAttribute (HasFocus && y + Viewport.Y - 1 == cursorRow ? ColorScheme.HotFocus : ColorScheme.HotNormal);
 
 
-            if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0)
+            if (!ShowGlyphWidths || (y + Viewport.Y) % _rowHeight > 0)
             {
             {
                 Driver.AddStr ($"U+{val / 16:x5}_ ");
                 Driver.AddStr ($"U+{val / 16:x5}_ ");
             }
             }
@@ -693,8 +805,6 @@ internal class CharMap : ScrollView
                 Driver.AddStr (new (' ', RowLabelWidth));
                 Driver.AddStr (new (' ', RowLabelWidth));
             }
             }
         }
         }
-
-        Driver.Clip = oldClip;
     }
     }
 
 
     public override bool OnEnter (View view)
     public override bool OnEnter (View view)
@@ -718,9 +828,9 @@ internal class CharMap : ScrollView
     {
     {
         if (HasFocus
         if (HasFocus
             && Cursor.X >= RowLabelWidth
             && Cursor.X >= RowLabelWidth
-            && Cursor.X < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0)
+            && Cursor.X < Viewport.Width
             && Cursor.Y > 0
             && Cursor.Y > 0
-            && Cursor.Y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0))
+            && Cursor.Y < Viewport.Height)
         {
         {
             Driver.SetCursorVisibility (_cursor);
             Driver.SetCursorVisibility (_cursor);
             Move (Cursor.X, Cursor.Y);
             Move (Cursor.X, Cursor.Y);
@@ -760,6 +870,8 @@ internal class CharMap : ScrollView
             return;
             return;
         }
         }
 
 
+        args.Handled = true;
+
         if (me.Y == 0)
         if (me.Y == 0)
         {
         {
             me.Y = Cursor.Y;
             me.Y = Cursor.Y;
@@ -773,8 +885,8 @@ internal class CharMap : ScrollView
             me.X = Cursor.X;
             me.X = Cursor.X;
         }
         }
 
 
-        int row = (me.Y - 1 - ContentOffset.Y) / _rowHeight; // -1 for header
-        int col = (me.X - RowLabelWidth - ContentOffset.X) / COLUMN_WIDTH;
+        int row = (me.Y - 1 - -Viewport.Y) / _rowHeight; // -1 for header
+        int col = (me.X - RowLabelWidth - -Viewport.X) / COLUMN_WIDTH;
 
 
         if (col > 15)
         if (col > 15)
         {
         {
@@ -812,7 +924,7 @@ internal class CharMap : ScrollView
         {
         {
             SelectedCodePoint = val;
             SelectedCodePoint = val;
 
 
-            _contextMenu = new()
+            _contextMenu = new ()
             {
             {
                 Position = new (me.X + 1, me.Y + 1),
                 Position = new (me.X + 1, me.Y + 1),
                 MenuItems = new (
                 MenuItems = new (
@@ -846,6 +958,7 @@ internal class CharMap : ScrollView
     {
     {
         var client = new UcdApiClient ();
         var client = new UcdApiClient ();
         var decResponse = string.Empty;
         var decResponse = string.Empty;
+        var getCodePointError = string.Empty;
 
 
         var waitIndicator = new Dialog
         var waitIndicator = new Dialog
         {
         {
@@ -854,7 +967,7 @@ internal class CharMap : ScrollView
             Y = Pos.Center (),
             Y = Pos.Center (),
             Height = 7,
             Height = 7,
             Width = 50,
             Width = 50,
-            Buttons = [new() { Text = "Cancel" }]
+            Buttons = [new () { Text = "Cancel" }]
         };
         };
 
 
         var errorLabel = new Label
         var errorLabel = new Label
@@ -877,12 +990,13 @@ internal class CharMap : ScrollView
                                    try
                                    try
                                    {
                                    {
                                        decResponse = await client.GetCodepointDec (SelectedCodePoint);
                                        decResponse = await client.GetCodepointDec (SelectedCodePoint);
+                                       Application.Invoke (() => waitIndicator.RequestStop ());
                                    }
                                    }
                                    catch (HttpRequestException e)
                                    catch (HttpRequestException e)
                                    {
                                    {
+                                       getCodePointError = errorLabel.Text = e.Message;
                                        Application.Invoke (() => waitIndicator.RequestStop ());
                                        Application.Invoke (() => waitIndicator.RequestStop ());
                                    }
                                    }
-
                                };
                                };
         Application.Run (waitIndicator);
         Application.Run (waitIndicator);
         waitIndicator.Dispose ();
         waitIndicator.Dispose ();
@@ -939,46 +1053,46 @@ internal class CharMap : ScrollView
             var label = new Label { Text = "IsAscii: ", X = 0, Y = 0 };
             var label = new Label { Text = "IsAscii: ", X = 0, Y = 0 };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = $"{rune.IsAscii}", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = $"{rune.IsAscii}", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = ", Bmp: ", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = ", Bmp: ", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = $"{rune.IsBmp}", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = $"{rune.IsBmp}", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = ", CombiningMark: ", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = ", CombiningMark: ", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = $"{rune.IsCombiningMark ()}", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = $"{rune.IsCombiningMark ()}", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = ", SurrogatePair: ", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = ", SurrogatePair: ", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = $"{rune.IsSurrogatePair ()}", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = $"{rune.IsSurrogatePair ()}", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = ", Plane: ", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = ", Plane: ", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = $"{rune.Plane}", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = $"{rune.Plane}", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = "Columns: ", X = 0, Y = Pos.Bottom (label) };
+            label = new () { Text = "Columns: ", X = 0, Y = Pos.Bottom (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = $"{rune.GetColumns ()}", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = $"{rune.GetColumns ()}", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = ", Utf16SequenceLength: ", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = ", Utf16SequenceLength: ", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new() { Text = $"{rune.Utf16SequenceLength}", X = Pos.Right (label), Y = Pos.Top (label) };
+            label = new () { Text = $"{rune.Utf16SequenceLength}", X = Pos.Right (label), Y = Pos.Top (label) };
             dlg.Add (label);
             dlg.Add (label);
 
 
-            label = new()
+            label = new ()
             {
             {
                 Text =
                 Text =
                     $"Code Point Information from {UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint}:",
                     $"Code Point Information from {UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint}:",

+ 5 - 2
UICatalog/Scenarios/Clipping.cs

@@ -4,6 +4,8 @@ namespace UICatalog.Scenarios;
 
 
 [ScenarioMetadata ("Clipping", "Used to test that things clip correctly")]
 [ScenarioMetadata ("Clipping", "Used to test that things clip correctly")]
 [ScenarioCategory ("Tests")]
 [ScenarioCategory ("Tests")]
+[ScenarioCategory ("Drawing")]
+[ScenarioCategory ("Scrolling")]
 public class Clipping : Scenario
 public class Clipping : Scenario
 {
 {
     public override void Init ()
     public override void Init ()
@@ -30,8 +32,9 @@ public class Clipping : Scenario
         scrollView.ContentSize = new (200, 100);
         scrollView.ContentSize = new (200, 100);
 
 
         //ContentOffset = Point.Empty,
         //ContentOffset = Point.Empty,
-        //scrollView.ShowVerticalScrollIndicator = true;
-        //scrollView.ShowHorizontalScrollIndicator = true;
+        scrollView.AutoHideScrollBars = true;
+        scrollView.ShowVerticalScrollIndicator = true;
+        scrollView.ShowHorizontalScrollIndicator = true;
 
 
         var embedded1 = new View
         var embedded1 = new View
         {
         {

+ 2 - 2
UICatalog/Scenarios/ColorPicker.cs

@@ -44,8 +44,8 @@ public class ColorPickers : Scenario
         backgroundColorPicker = new ColorPicker
         backgroundColorPicker = new ColorPicker
         {
         {
             Title = "Background Color",
             Title = "Background Color",
-            Y = Pos.Center (),
-            X = Pos.Center (),
+            // TODO: Replace with Pos.AnchorEnd () when #2900 is done
+            X = Pos.AnchorEnd ((8 * 4) + 2), // 8 box * 4 width + 2 for border
             BoxHeight = 1,
             BoxHeight = 1,
             BoxWidth = 4,
             BoxWidth = 4,
             BorderStyle = LineStyle.Single
             BorderStyle = LineStyle.Single

+ 4 - 4
UICatalog/Scenarios/ComputedLayout.cs

@@ -57,12 +57,12 @@ public class ComputedLayout : Scenario
         Top.LayoutComplete += (s, a) =>
         Top.LayoutComplete += (s, a) =>
                                           {
                                           {
                                               horizontalRuler.Text =
                                               horizontalRuler.Text =
-                                                  rule.Repeat ((int)Math.Ceiling (horizontalRuler.Bounds.Width / (double)rule.Length)) [
-                                                   ..horizontalRuler.Bounds.Width];
+                                                  rule.Repeat ((int)Math.Ceiling (horizontalRuler.Viewport.Width / (double)rule.Length)) [
+                                                   ..horizontalRuler.Viewport.Width];
 
 
                                               verticalRuler.Text =
                                               verticalRuler.Text =
-                                                  vrule.Repeat ((int)Math.Ceiling (verticalRuler.Bounds.Height * 2 / (double)rule.Length))
-                                                      [..(verticalRuler.Bounds.Height * 2)];
+                                                  vrule.Repeat ((int)Math.Ceiling (verticalRuler.Viewport.Height * 2 / (double)rule.Length))
+                                                      [..(verticalRuler.Viewport.Height * 2)];
                                           };
                                           };
 
 
         Top.Add (verticalRuler);
         Top.Add (verticalRuler);

+ 402 - 0
UICatalog/Scenarios/ContentScrolling.cs

@@ -0,0 +1,402 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using Terminal.Gui;
+using static UICatalog.Scenarios.Adornments;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Content Scrolling", "Demonstrates using View.Viewport and View.ContentSize to scroll content.")]
+[ScenarioCategory ("Layout")]
+[ScenarioCategory ("Drawing")]
+[ScenarioCategory ("Scrolling")]
+public class ContentScrolling : Scenario
+{
+    private ViewDiagnosticFlags _diagnosticFlags;
+
+    public class ScrollingDemoView : FrameView
+    {
+        public ScrollingDemoView ()
+        {
+            Width = Dim.Fill ();
+            Height = Dim.Fill ();
+            ColorScheme = Colors.ColorSchemes ["Base"];
+            Text = "Text (ScrollingDemoView.Text). This is long text.\nThe second line.\n3\n4\n5th line\nLine 6. This is a longer line that should wrap automatically.";
+            CanFocus = true;
+            BorderStyle = LineStyle.Rounded;
+            Arrangement = ViewArrangement.Fixed;
+
+            ContentSize = new (60, 40);
+            ViewportSettings |= ViewportSettings.ClearContentOnly;
+            ViewportSettings |= ViewportSettings.ClipContentOnly;
+
+            // Things this view knows how to do
+            AddCommand (Command.ScrollDown, () => ScrollVertical (1));
+            AddCommand (Command.ScrollUp, () => ScrollVertical (-1));
+
+            AddCommand (Command.ScrollRight, () => ScrollHorizontal (1));
+            AddCommand (Command.ScrollLeft, () => ScrollHorizontal (-1));
+
+            // Default keybindings for all ListViews
+            KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
+            KeyBindings.Add (Key.CursorDown, Command.ScrollDown);
+            KeyBindings.Add (Key.CursorLeft, Command.ScrollLeft);
+            KeyBindings.Add (Key.CursorRight, Command.ScrollRight);
+
+            // Add a status label to the border that shows Viewport and ContentSize values. Bit of a hack.
+            // TODO: Move to Padding with controls
+            Border.Add (new Label { AutoSize = false, X = 20 });
+            LayoutComplete += VirtualDemoView_LayoutComplete;
+
+            MouseEvent += VirtualDemoView_MouseEvent;
+        }
+
+        private void VirtualDemoView_MouseEvent (object sender, MouseEventEventArgs e)
+        {
+            if (e.MouseEvent.Flags == MouseFlags.WheeledDown)
+            {
+                ScrollVertical (1);
+
+                return;
+            }
+
+            if (e.MouseEvent.Flags == MouseFlags.WheeledUp)
+            {
+                ScrollVertical (-1);
+
+                return;
+            }
+
+            if (e.MouseEvent.Flags == MouseFlags.WheeledRight)
+            {
+                ScrollHorizontal (1);
+
+                return;
+            }
+
+            if (e.MouseEvent.Flags == MouseFlags.WheeledLeft)
+            {
+                ScrollHorizontal (-1);
+            }
+        }
+
+        private void VirtualDemoView_LayoutComplete (object sender, LayoutEventArgs e)
+        {
+            Label status = Border.Subviews.OfType<Label> ().FirstOrDefault ();
+
+            if (status is { })
+            {
+                status.Title = $"Frame: {Frame}\n\nViewport: {Viewport}, ContentSize = {ContentSize}";
+                status.Width = Border.Frame.Width - status.Frame.X - Border.Thickness.Right;
+                status.Height = Border.Thickness.Top;
+            }
+
+            SetNeedsDisplay ();
+        }
+    }
+
+    public override void Main ()
+    {
+        Application.Init ();
+
+        _diagnosticFlags = View.Diagnostics;
+
+        Window app = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+        };
+
+        var editor = new AdornmentsEditor ();
+        app.Add (editor);
+
+        var view = new ScrollingDemoView
+        {
+            Title = "Demo View",
+            X = Pos.Right(editor),
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+        };
+        app.Add (view);
+
+        // Add Scroll Setting UI to Padding
+        view.Padding.Thickness = new (0, 3, 0, 0);
+        view.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
+
+        var cbAllowNegativeX = new CheckBox
+        {
+            Title = "Allow _X < 0",
+            Y = 0,
+            CanFocus = false
+        };
+        cbAllowNegativeX.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowNegativeX);
+        cbAllowNegativeX.Toggled += AllowNegativeX_Toggled;
+
+        void AllowNegativeX_Toggled (object sender, StateEventArgs<bool?> e)
+        {
+            if (e.NewValue == true)
+            {
+                view.ViewportSettings |= ViewportSettings.AllowNegativeX;
+            }
+            else
+            {
+                view.ViewportSettings &= ~ViewportSettings.AllowNegativeX;
+            }
+        }
+
+        view.Padding.Add (cbAllowNegativeX);
+
+        var cbAllowNegativeY = new CheckBox
+        {
+            Title = "Allow _Y < 0",
+            X = Pos.Right (cbAllowNegativeX) + 1,
+            Y = 0,
+            CanFocus = false
+        };
+        cbAllowNegativeY.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowNegativeY);
+        cbAllowNegativeY.Toggled += AllowNegativeY_Toggled;
+
+        void AllowNegativeY_Toggled (object sender, StateEventArgs<bool?> e)
+        {
+            if (e.NewValue == true)
+            {
+                view.ViewportSettings |= ViewportSettings.AllowNegativeY;
+            }
+            else
+            {
+                view.ViewportSettings &= ~ViewportSettings.AllowNegativeY;
+            }
+        }
+
+        view.Padding.Add (cbAllowNegativeY);
+
+        var cbAllowXGreaterThanContentWidth = new CheckBox
+        {
+            Title = "All_ow X > Content",
+            Y = Pos.Bottom (cbAllowNegativeX),
+            CanFocus = false
+        };
+        cbAllowXGreaterThanContentWidth.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth);
+        cbAllowXGreaterThanContentWidth.Toggled += AllowXGreaterThanContentWidth_Toggled;
+
+        void AllowXGreaterThanContentWidth_Toggled (object sender, StateEventArgs<bool?> e)
+        {
+            if (e.NewValue == true)
+            {
+                view.ViewportSettings |= ViewportSettings.AllowXGreaterThanContentWidth;
+            }
+            else
+            {
+                view.ViewportSettings &= ~ViewportSettings.AllowXGreaterThanContentWidth;
+            }
+        }
+
+        view.Padding.Add (cbAllowXGreaterThanContentWidth);
+
+        var cbAllowYGreaterThanContentHeight = new CheckBox
+        {
+            Title = "Allo_w Y > Content",
+            X = Pos.Right (cbAllowXGreaterThanContentWidth) + 1,
+            Y = Pos.Bottom (cbAllowNegativeX),
+            CanFocus = false
+        };
+        cbAllowYGreaterThanContentHeight.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowYGreaterThanContentHeight);
+        cbAllowYGreaterThanContentHeight.Toggled += AllowYGreaterThanContentHeight_Toggled;
+
+        void AllowYGreaterThanContentHeight_Toggled (object sender, StateEventArgs<bool?> e)
+        {
+            if (e.NewValue == true)
+            {
+                view.ViewportSettings |= ViewportSettings.AllowYGreaterThanContentHeight;
+            }
+            else
+            {
+                view.ViewportSettings &= ~ViewportSettings.AllowYGreaterThanContentHeight;
+            }
+        }
+
+        view.Padding.Add (cbAllowYGreaterThanContentHeight);
+
+        var labelContentSize = new Label
+        {
+            Title = "_ContentSize:",
+            Y = Pos.Bottom (cbAllowYGreaterThanContentHeight)
+        };
+
+        var contentSizeWidth = new Buttons.NumericUpDown<int>
+        {
+            Value = view.ContentSize.Width,
+            X = Pos.Right (labelContentSize) + 1,
+            Y = Pos.Top (labelContentSize)
+        };
+        contentSizeWidth.ValueChanging += ContentSizeWidth_ValueChanged;
+
+        void ContentSizeWidth_ValueChanged (object sender, StateEventArgs<int> e)
+        {
+            if (e.NewValue < 0)
+            {
+                e.Cancel = true;
+
+                return;
+            }
+
+            view.ContentSize = view.ContentSize with { Width = e.NewValue };
+        }
+
+        var labelComma = new Label
+        {
+            Title = ",",
+            X = Pos.Right (contentSizeWidth),
+            Y = Pos.Top (labelContentSize)
+        };
+
+        var contentSizeHeight = new Buttons.NumericUpDown<int>
+        {
+            Value = view.ContentSize.Height,
+            X = Pos.Right (labelComma) + 1,
+            Y = Pos.Top (labelContentSize),
+            CanFocus = false
+        };
+        contentSizeHeight.ValueChanging += ContentSizeHeight_ValueChanged;
+
+        void ContentSizeHeight_ValueChanged (object sender, StateEventArgs<int> e)
+        {
+            if (e.NewValue < 0)
+            {
+                e.Cancel = true;
+
+                return;
+            }
+
+            view.ContentSize = view.ContentSize with { Height = e.NewValue };
+        }
+
+        var cbClearOnlyVisible = new CheckBox
+        {
+            Title = "ClearContentOnly",
+            X = Pos.Right (contentSizeHeight) + 1,
+            Y = Pos.Top (labelContentSize),
+            CanFocus = false
+        };
+        cbClearOnlyVisible.Checked = view.ViewportSettings.HasFlag (ViewportSettings.ClearContentOnly);
+        cbClearOnlyVisible.Toggled += ClearVisibleContentOnly_Toggled;
+
+        void ClearVisibleContentOnly_Toggled (object sender, StateEventArgs<bool?> e)
+        {
+            if (e.NewValue == true)
+            {
+                view.ViewportSettings |= ViewportSettings.ClearContentOnly;
+            }
+            else
+            {
+                view.ViewportSettings &= ~ViewportSettings.ClearContentOnly;
+            }
+        }
+
+        var cbDoNotClipContent = new CheckBox
+        {
+            Title = "ClipContentOnly",
+            X = Pos.Right (cbClearOnlyVisible) + 1,
+            Y = Pos.Top (labelContentSize),
+            CanFocus = false
+        };
+        cbDoNotClipContent.Checked = view.ViewportSettings.HasFlag (ViewportSettings.ClipContentOnly);
+        cbDoNotClipContent.Toggled += ClipVisibleContentOnly_Toggled;
+
+        void ClipVisibleContentOnly_Toggled (object sender, StateEventArgs<bool?> e)
+        {
+            if (e.NewValue == true)
+            {
+                view.ViewportSettings |= ViewportSettings.ClipContentOnly;
+            }
+            else
+            {
+                view.ViewportSettings &= ~ViewportSettings.ClipContentOnly;
+            }
+        }
+
+        view.Padding.Add (labelContentSize, contentSizeWidth, labelComma, contentSizeHeight, cbClearOnlyVisible, cbDoNotClipContent);
+
+        // Add demo views to show that things work correctly
+        var textField = new TextField { X = 20, Y = 7, Width = 15, Text = "Test TextField" };
+
+        var colorPicker = new ColorPicker { Title = "BG", BoxHeight = 1, BoxWidth = 1, X = Pos.AnchorEnd (11), Y = 10 };
+        colorPicker.BorderStyle = LineStyle.RoundedDotted;
+
+        colorPicker.ColorChanged += (s, e) =>
+                                    {
+                                        colorPicker.SuperView.ColorScheme = new (colorPicker.SuperView.ColorScheme)
+                                        {
+                                            Normal = new (
+                                                          colorPicker.SuperView.ColorScheme.Normal.Foreground,
+                                                          e.Color
+                                                         )
+                                        };
+                                    };
+
+        var textView = new TextView
+        {
+            X = Pos.Center (),
+            Y = 10,
+            Title = "TextView",
+            Text = "I have a 3 row top border.\nMy border inherits from the SuperView.\nI have 3 lines of text with room for 2.",
+            AllowsTab = false,
+            Width = 30,
+            Height = 6 // TODO: Use Dim.Auto
+        };
+        textView.Border.Thickness = new (1, 3, 1, 1);
+
+        var charMap = new CharMap
+        {
+            X = Pos.Center (),
+            Y = Pos.Bottom (textView) + 1,
+            Width = 30,
+            Height = 10
+        };
+
+        charMap.Accept += (s, e) =>
+                              MessageBox.Query (20, 7, "Hi", $"Am I a {view.GetType ().Name}?", "Yes", "No");
+
+        var buttonAnchoredRight = new Button
+        {
+            X = Pos.AnchorEnd (10), Y = 0, Text = "Button"
+        };
+
+        var labelAnchoredBottomLeft = new Label
+        {
+            AutoSize = false,
+            Y = Pos.AnchorEnd (3),
+            Width = 25,
+            Height = Dim.Fill (),
+            Text = "Label\nY=AnchorEnd(3),Height=Dim.Fill()"
+        };
+
+        view.Margin.Data = "Margin";
+        view.Margin.Thickness = new (0);
+
+        view.Border.Data = "Border";
+        view.Border.Thickness = new (3);
+
+        view.Padding.Data = "Padding";
+
+        view.Add (buttonAnchoredRight, textField, colorPicker, charMap, textView, labelAnchoredBottomLeft);
+
+        var longLabel = new Label
+        {
+            Id = "label2",
+            X = 0,
+            Y = 30,
+            Text =
+                "This label is long. It should clip to the ContentArea if ClipContentOnly is set. This is a virtual scrolling demo. Use the arrow keys and/or mouse wheel to scroll the content."
+        };
+        longLabel.TextFormatter.WordWrap = true;
+        view.Add (longLabel);
+
+        editor.Initialized += (s, e) => { editor.ViewToEdit = view; };
+
+        app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags;
+
+        Application.Run (app);
+        app.Dispose ();
+        Application.Shutdown ();
+    }
+}

+ 2 - 2
UICatalog/Scenarios/Editor.cs

@@ -760,8 +760,8 @@ public class Editor : Scenario
         _winDialog = new Window
         _winDialog = new Window
         {
         {
             Title = isFind ? "Find" : "Replace",
             Title = isFind ? "Find" : "Replace",
-            X = Win.Bounds.Width / 2 - 30,
-            Y = Win.Bounds.Height / 2 - 10,
+            X = Win.Viewport.Width / 2 - 30,
+            Y = Win.Viewport.Height / 2 - 10,
             ColorScheme = Colors.ColorSchemes ["TopLevel"]
             ColorScheme = Colors.ColorSchemes ["TopLevel"]
         };
         };
 
 

+ 2 - 2
UICatalog/Scenarios/GraphViewExample.cs

@@ -254,7 +254,7 @@ public class GraphViewExample : Scenario
 
 
         _graphView.AxisY.Minimum = 0;
         _graphView.AxisY.Minimum = 0;
 
 
-        var legend = new LegendAnnotation (new Rectangle (_graphView.Bounds.Width - 20, 0, 20, 5));
+        var legend = new LegendAnnotation (new Rectangle (_graphView.Viewport.Width - 20, 0, 20, 5));
 
 
         legend.AddEntry (
         legend.AddEntry (
                          new GraphCellToRender (stiple, series.SubSeries.ElementAt (0).OverrideBarColor),
                          new GraphCellToRender (stiple, series.SubSeries.ElementAt (0).OverrideBarColor),
@@ -872,7 +872,7 @@ public class GraphViewExample : Scenario
         _graphView.Annotations.Add (new TextAnnotation { Text = "M", ScreenPosition = new Point (0, 10) });
         _graphView.Annotations.Add (new TextAnnotation { Text = "M", ScreenPosition = new Point (0, 10) });
 
 
         _graphView.Annotations.Add (
         _graphView.Annotations.Add (
-                                    new TextAnnotation { Text = "F", ScreenPosition = new Point (_graphView.Bounds.Width - 1, 10) }
+                                    new TextAnnotation { Text = "F", ScreenPosition = new Point (_graphView.Viewport.Width - 1, 10) }
                                    );
                                    );
 
 
         _graphView.SetNeedsDisplay ();
         _graphView.SetNeedsDisplay ();

+ 2 - 2
UICatalog/Scenarios/LineDrawing.cs

@@ -36,9 +36,9 @@ public class LineDrawing : Scenario
         public DrawingArea () { AddLayer (); }
         public DrawingArea () { AddLayer (); }
         public LineStyle LineStyle { get; set; }
         public LineStyle LineStyle { get; set; }
 
 
-        public override void OnDrawContentComplete (Rectangle contentArea)
+        public override void OnDrawContentComplete (Rectangle viewport)
         {
         {
-            base.OnDrawContentComplete (contentArea);
+            base.OnDrawContentComplete (viewport);
 
 
             foreach (LineCanvas canvas in _layers)
             foreach (LineCanvas canvas in _layers)
             {
             {

+ 2 - 0
UICatalog/Scenarios/ListColumns.cs

@@ -12,6 +12,8 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Dialogs")]
 [ScenarioCategory ("Dialogs")]
 [ScenarioCategory ("Text and Formatting")]
 [ScenarioCategory ("Text and Formatting")]
 [ScenarioCategory ("Top Level Windows")]
 [ScenarioCategory ("Top Level Windows")]
+[ScenarioCategory ("Scrolling")]
+
 public class ListColumns : Scenario
 public class ListColumns : Scenario
 {
 {
     private ColorScheme _alternatingColorScheme;
     private ColorScheme _alternatingColorScheme;

+ 1 - 1
UICatalog/Scenarios/Notepad.cs

@@ -362,7 +362,7 @@ public class Notepad : Scenario
                         );
                         );
         }
         }
 
 
-        Rectangle screen = ((View)sender).BoundsToScreen (new (e.MouseEvent.X, e.MouseEvent.Y, 0, 0));
+        Rectangle screen = ((View)sender).ViewportToScreen (new (e.MouseEvent.X, e.MouseEvent.Y, 0, 0));
 
 
         var contextMenu = new ContextMenu { Position = screen.Location, MenuItems = items };
         var contextMenu = new ContextMenu { Position = screen.Location, MenuItems = items };
 
 

+ 51 - 43
UICatalog/Scenarios/ProgressBarStyles.cs

@@ -20,20 +20,30 @@ public class ProgressBarStyles : Scenario
     private const uint _timerTick = 20;
     private const uint _timerTick = 20;
     private Timer _fractionTimer;
     private Timer _fractionTimer;
     private Timer _pulseTimer;
     private Timer _pulseTimer;
+    private ViewDiagnosticFlags _diagnosticFlags;
 
 
-    public override void Init ()
+    public override void Main ()
     {
     {
         Application.Init ();
         Application.Init ();
-        ConfigurationManager.Themes.Theme = Theme;
-        ConfigurationManager.Apply ();
 
 
-        Top = new ();
+        _diagnosticFlags = View.Diagnostics;
 
 
-        var editor = new AdornmentsEditor
+        Window app = new ()
         {
         {
-            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", BorderStyle = LineStyle.Single
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", BorderStyle = LineStyle.Single,
         };
         };
-        editor.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
+
+        var editor = new AdornmentsEditor ();
+        app.Add (editor);
+
+        View container = new ()
+        {
+            X = Pos.Right (editor),
+            Y = 0,
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+        };
+        app.Add (container);
 
 
         const float fractionStep = 0.01F;
         const float fractionStep = 0.01F;
 
 
@@ -47,16 +57,7 @@ public class ProgressBarStyles : Scenario
             BorderStyle = LineStyle.Single
             BorderStyle = LineStyle.Single
         };
         };
 
 
-        pbList.SelectedItemChanged += (sender, e) =>
-                                      {
-                                          editor.ViewToEdit = editor.Subviews.First (
-                                                                                     v =>
-                                                                                         v.GetType () == typeof (ProgressBar)
-                                                                                         && v.Title == (string)e.Value
-                                                                                    );
-                                      };
-        editor.Add (pbList);
-        pbList.SelectedItem = 0;
+        container.Add (pbList);
 
 
         #region ColorPicker
         #region ColorPicker
 
 
@@ -75,12 +76,12 @@ public class ProgressBarStyles : Scenario
 
 
             dialog.LayoutComplete += (sender, args) =>
             dialog.LayoutComplete += (sender, args) =>
                                     {
                                     {
-                                        dialog.Bounds = Rectangle.Empty with
+                                        dialog.Viewport = Rectangle.Empty with
                                         {
                                         {
                                             Width = colorPicker.Frame.Width,
                                             Width = colorPicker.Frame.Width,
                                             Height = colorPicker.Frame.Height
                                             Height = colorPicker.Frame.Height
                                         };
                                         };
-                                        Application.Top.LayoutSubviews();
+                                        Application.Top.LayoutSubviews ();
                                     };
                                     };
 
 
             dialog.Add (colorPicker);
             dialog.Add (colorPicker);
@@ -98,7 +99,7 @@ public class ProgressBarStyles : Scenario
         {
         {
             Text = "Foreground HotNormal Color", X = Pos.Center (), Y = Pos.Bottom (pbList)
             Text = "Foreground HotNormal Color", X = Pos.Center (), Y = Pos.Bottom (pbList)
         };
         };
-        editor.Add (fgColorPickerBtn);
+        container.Add (fgColorPickerBtn);
 
 
         fgColorPickerBtn.Accept += (s, e) =>
         fgColorPickerBtn.Accept += (s, e) =>
                                     {
                                     {
@@ -123,7 +124,7 @@ public class ProgressBarStyles : Scenario
         {
         {
             X = Pos.Center (), Y = Pos.Bottom (fgColorPickerBtn), Text = "Background HotNormal Color"
             X = Pos.Center (), Y = Pos.Bottom (fgColorPickerBtn), Text = "Background HotNormal Color"
         };
         };
-        editor.Add (bgColorPickerBtn);
+        container.Add (bgColorPickerBtn);
 
 
         bgColorPickerBtn.Accept += (s, e) =>
         bgColorPickerBtn.Accept += (s, e) =>
                                     {
                                     {
@@ -157,11 +158,10 @@ public class ProgressBarStyles : Scenario
             Y = Pos.Bottom (bgColorPickerBtn) + 1,
             Y = Pos.Bottom (bgColorPickerBtn) + 1,
             RadioLabels = pbFormatEnum.Select (e => e.ToString ()).ToArray ()
             RadioLabels = pbFormatEnum.Select (e => e.ToString ()).ToArray ()
         };
         };
-        editor.Add (rbPBFormat);
+        container.Add (rbPBFormat);
 
 
         var button = new Button { X = Pos.Center (), Y = Pos.Bottom (rbPBFormat) + 1, Text = "Start timer" };
         var button = new Button { X = Pos.Center (), Y = Pos.Bottom (rbPBFormat) + 1, Text = "Start timer" };
-
-        editor.Add (button);
+        container.Add (button);
 
 
         var blocksPB = new ProgressBar
         var blocksPB = new ProgressBar
         {
         {
@@ -172,7 +172,7 @@ public class ProgressBarStyles : Scenario
             BorderStyle = LineStyle.Single,
             BorderStyle = LineStyle.Single,
             CanFocus = true
             CanFocus = true
         };
         };
-        editor.Add (blocksPB);
+        container.Add (blocksPB);
 
 
         var continuousPB = new ProgressBar
         var continuousPB = new ProgressBar
         {
         {
@@ -184,7 +184,7 @@ public class ProgressBarStyles : Scenario
             BorderStyle = LineStyle.Single,
             BorderStyle = LineStyle.Single,
             CanFocus = true
             CanFocus = true
         };
         };
-        editor.Add (continuousPB);
+        container.Add (continuousPB);
 
 
         button.Accept += (s, e) =>
         button.Accept += (s, e) =>
                           {
                           {
@@ -222,7 +222,7 @@ public class ProgressBarStyles : Scenario
         {
         {
             X = Pos.Center (), Y = Pos.Bottom (continuousPB) + 1, Text = "BidirectionalMarquee", Checked = true
             X = Pos.Center (), Y = Pos.Bottom (continuousPB) + 1, Text = "BidirectionalMarquee", Checked = true
         };
         };
-        editor.Add (ckbBidirectional);
+        container.Add (ckbBidirectional);
 
 
         var marqueesBlocksPB = new ProgressBar
         var marqueesBlocksPB = new ProgressBar
         {
         {
@@ -234,7 +234,7 @@ public class ProgressBarStyles : Scenario
             BorderStyle = LineStyle.Single,
             BorderStyle = LineStyle.Single,
             CanFocus = true
             CanFocus = true
         };
         };
-        editor.Add (marqueesBlocksPB);
+        container.Add (marqueesBlocksPB);
 
 
         var marqueesContinuousPB = new ProgressBar
         var marqueesContinuousPB = new ProgressBar
         {
         {
@@ -246,13 +246,22 @@ public class ProgressBarStyles : Scenario
             BorderStyle = LineStyle.Single,
             BorderStyle = LineStyle.Single,
             CanFocus = true
             CanFocus = true
         };
         };
-        editor.Add (marqueesContinuousPB);
+        container.Add (marqueesContinuousPB);
 
 
         pbList.SetSource (
         pbList.SetSource (
-                          editor.Subviews.Where (v => v.GetType () == typeof (ProgressBar))
-                                .Select (v => v.Title)
-                                .ToList ()
+                          container.Subviews.Where (v => v.GetType () == typeof (ProgressBar))
+                                   .Select (v => v.Title)
+                                   .ToList ()
                          );
                          );
+
+        pbList.SelectedItemChanged += (sender, e) =>
+                                      {
+                                          editor.ViewToEdit = container.Subviews.First (
+                                                                                        v =>
+                                                                                            v.GetType () == typeof (ProgressBar)
+                                                                                            && v.Title == (string)e.Value
+                                                                                       );
+                                      };
         pbList.SelectedItem = 0;
         pbList.SelectedItem = 0;
 
 
         rbPBFormat.SelectedItemChanged += (s, e) =>
         rbPBFormat.SelectedItemChanged += (s, e) =>
@@ -272,8 +281,7 @@ public class ProgressBarStyles : Scenario
         _pulseTimer = new Timer (
         _pulseTimer = new Timer (
                                  _ =>
                                  _ =>
                                  {
                                  {
-                                     marqueesBlocksPB.Text =
-                                         marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString ();
+                                     marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString ();
                                      marqueesBlocksPB.Pulse ();
                                      marqueesBlocksPB.Pulse ();
                                      marqueesContinuousPB.Pulse ();
                                      marqueesContinuousPB.Pulse ();
                                      Application.Wakeup ();
                                      Application.Wakeup ();
@@ -283,9 +291,15 @@ public class ProgressBarStyles : Scenario
                                  300
                                  300
                                 );
                                 );
 
 
-        Top.Unloaded += Top_Unloaded;
+        app.Unloaded += App_Unloaded;
+
+        Application.Run (app);
+        app.Dispose ();
+        Application.Shutdown ();
+
+        return;
 
 
-        void Top_Unloaded (object sender, EventArgs args)
+        void App_Unloaded (object sender, EventArgs args)
         {
         {
             if (_fractionTimer != null)
             if (_fractionTimer != null)
             {
             {
@@ -299,13 +313,7 @@ public class ProgressBarStyles : Scenario
                 _pulseTimer = null;
                 _pulseTimer = null;
             }
             }
 
 
-            Top.Unloaded -= Top_Unloaded;
+            app.Unloaded -= App_Unloaded;
         }
         }
-
-        Application.Run (editor);
-        editor.Dispose ();
-        Application.Shutdown ();
     }
     }
-
-    public override void Run () { }
 }
 }

+ 72 - 77
UICatalog/Scenarios/Scrolling.cs

@@ -1,42 +1,51 @@
 using System;
 using System;
-using System.Text;
 using Terminal.Gui;
 using Terminal.Gui;
 
 
 namespace UICatalog.Scenarios;
 namespace UICatalog.Scenarios;
 
 
-[ScenarioMetadata ("Scrolling", "Demonstrates ScrollView etc...")]
+[ScenarioMetadata ("Scrolling", "Demonstrates scrolling etc...")]
 [ScenarioCategory ("Controls")]
 [ScenarioCategory ("Controls")]
-[ScenarioCategory ("ScrollView")]
+[ScenarioCategory ("Scrolling")]
 [ScenarioCategory ("Tests")]
 [ScenarioCategory ("Tests")]
 public class Scrolling : Scenario
 public class Scrolling : Scenario
 {
 {
-    public override void Setup ()
+    private ViewDiagnosticFlags _diagnosticFlags;
+
+    public override void Main ()
     {
     {
-        // Offset Win to stress clipping
-        Win.X = 1;
-        Win.Y = 1;
-        Win.Width = Dim.Fill (1);
-        Win.Height = Dim.Fill (1);
+        Application.Init ();
+        _diagnosticFlags = View.Diagnostics;
+        View.Diagnostics = ViewDiagnosticFlags.Ruler;
+        var app = new Window ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+            // Offset to stress clipping
+            X = 3,
+            Y = 3,
+            Width = Dim.Fill (3),
+            Height = Dim.Fill (3),
+        };
+
         var label = new Label { X = 0, Y = 0 };
         var label = new Label { X = 0, Y = 0 };
-        Win.Add (label);
+        app.Add (label);
 
 
         var scrollView = new ScrollView
         var scrollView = new ScrollView
         {
         {
             Id = "scrollView",
             Id = "scrollView",
             X = 2,
             X = 2,
             Y = Pos.Bottom (label) + 1,
             Y = Pos.Bottom (label) + 1,
-            Width = 50,
+            Width = 60,
             Height = 20,
             Height = 20,
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
-            ContentSize = new (200, 100),
+            ContentSize = new (120, 40),
 
 
             //ContentOffset = Point.Empty,
             //ContentOffset = Point.Empty,
             ShowVerticalScrollIndicator = true,
             ShowVerticalScrollIndicator = true,
             ShowHorizontalScrollIndicator = true
             ShowHorizontalScrollIndicator = true
         };
         };
+        scrollView.Padding.Thickness = new (1);
 
 
-        label.Text =
-            $"{scrollView}\nContentSize: {scrollView.ContentSize}\nContentOffset: {scrollView.ContentOffset}";
+        label.Text = $"{scrollView}\nContentSize: {scrollView.ContentSize}\nContentOffset: {scrollView.ContentOffset}";
 
 
         const string rule = "0123456789";
         const string rule = "0123456789";
 
 
@@ -64,25 +73,6 @@ public class Scrolling : Scenario
         };
         };
         scrollView.Add (verticalRuler);
         scrollView.Add (verticalRuler);
 
 
-        void Top_Loaded (object sender, EventArgs args)
-        {
-            horizontalRuler.Text =
-                rule.Repeat ((int)Math.Ceiling (horizontalRuler.Bounds.Width / (double)rule.Length)) [
-                                                                                                      ..horizontalRuler.Bounds.Width]
-                + "\n"
-                + "|         ".Repeat (
-                                       (int)Math.Ceiling (horizontalRuler.Bounds.Width / (double)rule.Length)
-                                      ) [
-                                         ..horizontalRuler.Bounds.Width];
-
-            verticalRuler.Text =
-                vrule.Repeat ((int)Math.Ceiling (verticalRuler.Bounds.Height * 2 / (double)rule.Length))
-                    [..(verticalRuler.Bounds.Height * 2)];
-            Top.Loaded -= Top_Loaded;
-        }
-
-        Top.Loaded += Top_Loaded;
-
         var pressMeButton = new Button { X = 3, Y = 3, Text = "Press me!" };
         var pressMeButton = new Button { X = 3, Y = 3, Text = "Press me!" };
         pressMeButton.Accept += (s, e) => MessageBox.Query (20, 7, "MessageBox", "Neat?", "Yes", "No");
         pressMeButton.Accept += (s, e) => MessageBox.Query (20, 7, "MessageBox", "Neat?", "Yes", "No");
         scrollView.Add (pressMeButton);
         scrollView.Add (pressMeButton);
@@ -105,7 +95,7 @@ public class Scrolling : Scenario
                             Y = 5,
                             Y = 5,
                             Width = 50,
                             Width = 50,
                             ColorScheme = Colors.ColorSchemes ["Dialog"],
                             ColorScheme = Colors.ColorSchemes ["Dialog"],
-                            Text = "This is a test of..."
+                            Text = "This is a test of...",
                         }
                         }
                        );
                        );
 
 
@@ -116,7 +106,7 @@ public class Scrolling : Scenario
                             Y = 10,
                             Y = 10,
                             Width = 50,
                             Width = 50,
                             ColorScheme = Colors.ColorSchemes ["Dialog"],
                             ColorScheme = Colors.ColorSchemes ["Dialog"],
-                            Text = "... the emergency broadcast system."
+                            Text = "... the emergency broadcast system.",
                         }
                         }
                        );
                        );
 
 
@@ -127,7 +117,7 @@ public class Scrolling : Scenario
                             Y = 99,
                             Y = 99,
                             Width = 50,
                             Width = 50,
                             ColorScheme = Colors.ColorSchemes ["Dialog"],
                             ColorScheme = Colors.ColorSchemes ["Dialog"],
-                            Text = "Last line"
+                            Text = "Last line",
                         }
                         }
                        );
                        );
 
 
@@ -138,17 +128,17 @@ public class Scrolling : Scenario
         anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
         anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
 
 
         anchorButton.Accept += (s, e) =>
         anchorButton.Accept += (s, e) =>
-                                {
-                                    // This demonstrates how to have a dynamically sized button
-                                    // Each time the button is clicked the button's text gets longer
-                                    // The call to Win.LayoutSubviews causes the Computed layout to
-                                    // get updated. 
-                                    anchorButton.Text += "!";
-                                    Win.LayoutSubviews ();
-                                };
+                               {
+                                   // This demonstrates how to have a dynamically sized button
+                                   // Each time the button is clicked the button's text gets longer
+                                   // The call to Win.LayoutSubviews causes the Computed layout to
+                                   // get updated. 
+                                   anchorButton.Text += "!";
+                                   app.LayoutSubviews ();
+                               };
         scrollView.Add (anchorButton);
         scrollView.Add (anchorButton);
 
 
-        Win.Add (scrollView);
+        app.Add (scrollView);
 
 
         var hCheckBox = new CheckBox
         var hCheckBox = new CheckBox
         {
         {
@@ -157,7 +147,7 @@ public class Scrolling : Scenario
             Text = "Horizontal Scrollbar",
             Text = "Horizontal Scrollbar",
             Checked = scrollView.ShowHorizontalScrollIndicator
             Checked = scrollView.ShowHorizontalScrollIndicator
         };
         };
-        Win.Add (hCheckBox);
+        app.Add (hCheckBox);
 
 
         var vCheckBox = new CheckBox
         var vCheckBox = new CheckBox
         {
         {
@@ -166,7 +156,7 @@ public class Scrolling : Scenario
             Text = "Vertical Scrollbar",
             Text = "Vertical Scrollbar",
             Checked = scrollView.ShowVerticalScrollIndicator
             Checked = scrollView.ShowVerticalScrollIndicator
         };
         };
-        Win.Add (vCheckBox);
+        app.Add (vCheckBox);
 
 
         var t = "Auto Hide Scrollbars";
         var t = "Auto Hide Scrollbars";
 
 
@@ -213,32 +203,10 @@ public class Scrolling : Scenario
                                   hCheckBox.Checked = true;
                                   hCheckBox.Checked = true;
                                   vCheckBox.Checked = true;
                                   vCheckBox.Checked = true;
                               };
                               };
-        Win.Add (ahCheckBox);
+        app.Add (ahCheckBox);
 
 
         keepCheckBox.Toggled += (s, e) => scrollView.KeepContentAlwaysInViewport = (bool)keepCheckBox.Checked;
         keepCheckBox.Toggled += (s, e) => scrollView.KeepContentAlwaysInViewport = (bool)keepCheckBox.Checked;
-        Win.Add (keepCheckBox);
-
-        //var scrollView2 = new ScrollView (new (55, 2, 20, 8)) {
-        //	ContentSize = new (20, 50),
-        //	//ContentOffset = Point.Empty,
-        //	ShowVerticalScrollIndicator = true,
-        //	ShowHorizontalScrollIndicator = true
-        //};
-        //var filler = new Filler (new (0, 0, 60, 40));
-        //scrollView2.Add (filler);
-        //scrollView2.DrawContent += (s,e) => {
-        //	scrollView2.ContentSize = filler.GetContentSize ();
-        //};
-        //Win.Add (scrollView2);
-
-        //// This is just to debug the visuals of the scrollview when small
-        //var scrollView3 = new ScrollView (new (55, 15, 3, 3)) {
-        //	ContentSize = new (100, 100),
-        //	ShowVerticalScrollIndicator = true,
-        //	ShowHorizontalScrollIndicator = true
-        //};
-        //scrollView3.Add (new Box10x (0, 0));
-        //Win.Add (scrollView3);
+        app.Add (keepCheckBox);
 
 
         var count = 0;
         var count = 0;
 
 
@@ -250,11 +218,13 @@ public class Scrolling : Scenario
             Width = 50,
             Width = 50,
             Text = "Mouse: "
             Text = "Mouse: "
         };
         };
-        Win.Add (mousePos);
+        app.Add (mousePos);
         Application.MouseEvent += (sender, a) => { mousePos.Text = $"Mouse: ({a.X},{a.Y}) - {a.Flags} {count++}"; };
         Application.MouseEvent += (sender, a) => { mousePos.Text = $"Mouse: ({a.X},{a.Y}) - {a.Flags} {count++}"; };
 
 
+        // Add a progress bar to cause constant redraws
         var progress = new ProgressBar { X = Pos.Right (scrollView) + 1, Y = Pos.AnchorEnd (2), Width = 50 };
         var progress = new ProgressBar { X = Pos.Right (scrollView) + 1, Y = Pos.AnchorEnd (2), Width = 50 };
-        Win.Add (progress);
+
+        app.Add (progress);
 
 
         var pulsing = true;
         var pulsing = true;
 
 
@@ -267,12 +237,37 @@ public class Scrolling : Scenario
 
 
         Application.AddTimeout (TimeSpan.FromMilliseconds (300), timer);
         Application.AddTimeout (TimeSpan.FromMilliseconds (300), timer);
 
 
-        void Top_Unloaded (object sender, EventArgs args)
+        app.Loaded += App_Loaded;
+        app.Unloaded += app_Unloaded;
+
+        Application.Run (app);
+        app.Loaded -= App_Loaded;
+        app.Unloaded -= app_Unloaded;
+        app.Dispose ();
+
+        return;
+
+        // Local functions
+        void App_Loaded (object sender, EventArgs args)
         {
         {
-            pulsing = false;
-            Top.Unloaded -= Top_Unloaded;
+            horizontalRuler.Text =
+                rule.Repeat ((int)Math.Ceiling (horizontalRuler.Viewport.Width / (double)rule.Length)) [
+                                                                                                        ..horizontalRuler.Viewport.Width]
+                + "\n"
+                + "|         ".Repeat (
+                                       (int)Math.Ceiling (horizontalRuler.Viewport.Width / (double)rule.Length)
+                                      ) [
+                                         ..horizontalRuler.Viewport.Width];
+
+            verticalRuler.Text =
+                vrule.Repeat ((int)Math.Ceiling (verticalRuler.Viewport.Height * 2 / (double)rule.Length))
+                    [..(verticalRuler.Viewport.Height * 2)];
         }
         }
 
 
-        Top.Unloaded += Top_Unloaded;
+        void app_Unloaded (object sender, EventArgs args)
+        {
+            View.Diagnostics = _diagnosticFlags;
+            pulsing = false;
+        }
     }
     }
 }
 }

+ 4 - 4
UICatalog/Scenarios/Snake.cs

@@ -309,12 +309,12 @@ public class Snake : Scenario
 
 
         public SnakeState State { get; }
         public SnakeState State { get; }
 
 
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
         {
-            base.OnDrawContent (contentArea);
+            base.OnDrawContent (viewport);
 
 
             Driver.SetAttribute (white);
             Driver.SetAttribute (white);
-            Clear (contentArea);
+            Clear ();
 
 
             var canvas = new LineCanvas ();
             var canvas = new LineCanvas ();
 
 
@@ -341,7 +341,7 @@ public class Snake : Scenario
                                );
                                );
             }
             }
 
 
-            foreach (KeyValuePair<Point, Rune> p in canvas.GetMap (Bounds))
+            foreach (KeyValuePair<Point, Rune> p in canvas.GetMap (Viewport))
             {
             {
                 AddRune (p.Key.X, p.Key.Y, p.Value);
                 AddRune (p.Key.X, p.Key.Y, p.Value);
             }
             }

+ 22 - 18
UICatalog/Scenarios/TextFormatterDemo.cs

@@ -10,18 +10,19 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Text and Formatting")]
 [ScenarioCategory ("Text and Formatting")]
 public class TextFormatterDemo : Scenario
 public class TextFormatterDemo : Scenario
 {
 {
-    public override void Setup ()
+    public override void Main ()
     {
     {
-        // TODO: Move this to another Scenario that specifically tests `Views` that have no subviews.
-        //Top.Text = "Press CTRL-Q to Quit. This is the Text for the TopLevel View. TextAlignment.Centered was specified. It is intentionally very long to illustrate word wrap.\n" +
-        //	"<-- There is a new line here to show a hard line break. You should see this text bleed underneath the subviews, which start at Y = 3.";
-        //Top.TextAlignment = TextAlignment.Centered;
-        //Top.ColorScheme = Colors.ColorSchemes ["Base"];
+        Application.Init ();
+
+        var app = new Window ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+        };
 
 
         // Make Win smaller so sizing the window horizontally will make the
         // Make Win smaller so sizing the window horizontally will make the
         // labels shrink to zero-width
         // labels shrink to zero-width
-        Win.X = 10;
-        Win.Width = Dim.Fill (10);
+        app.X = 10;
+        app.Width = Dim.Fill (10);
 
 
         var text = "Hello world, how are you today? Pretty neat!\nSecond line\n\nFourth Line.";
         var text = "Hello world, how are you today? Pretty neat!\nSecond line\n\nFourth Line.";
 
 
@@ -35,7 +36,7 @@ public class TextFormatterDemo : Scenario
             Y = 0,
             Y = 0,
             AutoSize = false,
             AutoSize = false,
             Height = 10,
             Height = 10,
-            Width = Dim.Fill ()
+            Width = Dim.Fill (),
         };
         };
 
 
         var block = new StringBuilder ();
         var block = new StringBuilder ();
@@ -50,17 +51,17 @@ public class TextFormatterDemo : Scenario
         block.AppendLine ("      ░    ░      ░    ░  ░ ░            ░  ");
         block.AppendLine ("      ░    ░      ░    ░  ░ ░            ░  ");
         block.AppendLine ("                       ░  ░                 ");
         block.AppendLine ("                       ░  ░                 ");
         blockText.Text = block.ToString (); // .Replace(" ", "\u00A0"); // \u00A0 is 'non-breaking space
         blockText.Text = block.ToString (); // .Replace(" ", "\u00A0"); // \u00A0 is 'non-breaking space
-        Win.Add (blockText);
+        app.Add (blockText);
 
 
         var unicodeCheckBox = new CheckBox
         var unicodeCheckBox = new CheckBox
         {
         {
             X = 0,
             X = 0,
             Y = Pos.Bottom (blockText) + 1,
             Y = Pos.Bottom (blockText) + 1,
             Text = "Unicode",
             Text = "Unicode",
-            Checked = Top.HotKeySpecifier == (Rune)' '
+            Checked = app.HotKeySpecifier == (Rune)' '
         };
         };
 
 
-        Win.Add (unicodeCheckBox);
+        app.Add (unicodeCheckBox);
 
 
         List<TextAlignment> alignments = Enum.GetValues (typeof (TextAlignment)).Cast<TextAlignment> ().ToList ();
         List<TextAlignment> alignments = Enum.GetValues (typeof (TextAlignment)).Cast<TextAlignment> ().ToList ();
         Label [] singleLines = new Label [alignments.Count];
         Label [] singleLines = new Label [alignments.Count];
@@ -97,26 +98,26 @@ public class TextFormatterDemo : Scenario
         {
         {
             Y = Pos.Bottom (unicodeCheckBox) + 1, Text = "Demonstrating multi-line and word wrap:"
             Y = Pos.Bottom (unicodeCheckBox) + 1, Text = "Demonstrating multi-line and word wrap:"
         };
         };
-        Win.Add (label);
+        app.Add (label);
 
 
         foreach (TextAlignment alignment in alignments)
         foreach (TextAlignment alignment in alignments)
         {
         {
             label = new Label { Y = Pos.Bottom (label), Text = $"{alignment}:" };
             label = new Label { Y = Pos.Bottom (label), Text = $"{alignment}:" };
-            Win.Add (label);
+            app.Add (label);
             singleLines [(int)alignment].Y = Pos.Bottom (label);
             singleLines [(int)alignment].Y = Pos.Bottom (label);
-            Win.Add (singleLines [(int)alignment]);
+            app.Add (singleLines [(int)alignment]);
             label = singleLines [(int)alignment];
             label = singleLines [(int)alignment];
         }
         }
 
 
         label = new Label { Y = Pos.Bottom (label), Text = "Demonstrating multi-line and word wrap:" };
         label = new Label { Y = Pos.Bottom (label), Text = "Demonstrating multi-line and word wrap:" };
-        Win.Add (label);
+        app.Add (label);
 
 
         foreach (TextAlignment alignment in alignments)
         foreach (TextAlignment alignment in alignments)
         {
         {
             label = new Label { Y = Pos.Bottom (label), Text = $"{alignment}:" };
             label = new Label { Y = Pos.Bottom (label), Text = $"{alignment}:" };
-            Win.Add (label);
+            app.Add (label);
             multipleLines [(int)alignment].Y = Pos.Bottom (label);
             multipleLines [(int)alignment].Y = Pos.Bottom (label);
-            Win.Add (multipleLines [(int)alignment]);
+            app.Add (multipleLines [(int)alignment]);
             label = multipleLines [(int)alignment];
             label = multipleLines [(int)alignment];
         }
         }
 
 
@@ -128,5 +129,8 @@ public class TextFormatterDemo : Scenario
                                            multipleLines [(int)alignment].Text = e.OldValue == true ? text : unicode;
                                            multipleLines [(int)alignment].Text = e.OldValue == true ? text : unicode;
                                        }
                                        }
                                    };
                                    };
+
+        Application.Run (app);
+        app.Dispose ();
     }
     }
 }
 }

+ 7 - 7
UICatalog/Scenarios/ViewExperiments.cs

@@ -219,17 +219,17 @@ public class ViewExperiments : Scenario
                                        $"Container.Frame: {
                                        $"Container.Frame: {
                                            Top.Frame
                                            Top.Frame
                                        } .Bounds: {
                                        } .Bounds: {
-                                           Top.Bounds
+                                           Top.Viewport
                                        }\nView.Frame: {
                                        }\nView.Frame: {
                                            view.Frame
                                            view.Frame
-                                       } .Bounds: {
-                                           view.Bounds
-                                       } .BoundsOffset: {
-                                           view.GetBoundsOffset ()
+                                       } .Viewport: {
+                                           view.Viewport
+                                       } .viewportOffset: {
+                                           view.GetViewportOffsetFromFrame ()
                                        }\n .Padding.Frame: {
                                        }\n .Padding.Frame: {
                                            view.Padding.Frame
                                            view.Padding.Frame
-                                       } .Padding.Bounds: {
-                                           view.Padding.Bounds
+                                       } .Padding.Viewport: {
+                                           view.Padding.Viewport
                                        }";
                                        }";
                                };
                                };
 
 

+ 6 - 6
UICatalog/Scenarios/VkeyPacketSimulator.cs

@@ -265,20 +265,20 @@ public class VkeyPacketSimulator : Scenario
             inputHorizontalRuler.Text = outputHorizontalRuler.Text =
             inputHorizontalRuler.Text = outputHorizontalRuler.Text =
                                             ruler.Repeat (
                                             ruler.Repeat (
                                                           (int)Math.Ceiling (
                                                           (int)Math.Ceiling (
-                                                                             inputHorizontalRuler.Bounds.Width
+                                                                             inputHorizontalRuler.Viewport.Width
                                                                              / (double)ruler.Length
                                                                              / (double)ruler.Length
                                                                             )
                                                                             )
                                                          ) [
                                                          ) [
-                                                            ..inputHorizontalRuler.Bounds.Width];
+                                                            ..inputHorizontalRuler.Viewport.Width];
             inputVerticalRuler.Height = tvInput.Frame.Height + 1;
             inputVerticalRuler.Height = tvInput.Frame.Height + 1;
 
 
             inputVerticalRuler.Text =
             inputVerticalRuler.Text =
-                ruler.Repeat ((int)Math.Ceiling (inputVerticalRuler.Bounds.Height / (double)ruler.Length)) [
-                     ..inputVerticalRuler.Bounds.Height];
+                ruler.Repeat ((int)Math.Ceiling (inputVerticalRuler.Viewport.Height / (double)ruler.Length)) [
+                     ..inputVerticalRuler.Viewport.Height];
 
 
             outputVerticalRuler.Text =
             outputVerticalRuler.Text =
-                ruler.Repeat ((int)Math.Ceiling (outputVerticalRuler.Bounds.Height / (double)ruler.Length)) [
-                     ..outputVerticalRuler.Bounds.Height];
+                ruler.Repeat ((int)Math.Ceiling (outputVerticalRuler.Viewport.Height / (double)ruler.Length)) [
+                     ..outputVerticalRuler.Viewport.Height];
         }
         }
 
 
         Win.LayoutComplete += Win_LayoutComplete;
         Win.LayoutComplete += Win_LayoutComplete;

+ 1 - 0
UICatalog/UICatalog.cs

@@ -345,6 +345,7 @@ internal class UICatalogApp
         // 'app' closed cleanly.
         // 'app' closed cleanly.
         foreach (Responder? inst in Responder.Instances)
         foreach (Responder? inst in Responder.Instances)
         {
         {
+            
             Debug.Assert (inst.WasDisposed);
             Debug.Assert (inst.WasDisposed);
         }
         }
 
 

+ 3 - 3
UICatalog/UICatalog.csproj

@@ -28,9 +28,9 @@
   <None Update="./Scenarios/Spinning_globe_dark_small.gif" CopyToOutputDirectory="PreserveNewest" />
   <None Update="./Scenarios/Spinning_globe_dark_small.gif" CopyToOutputDirectory="PreserveNewest" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
-    <PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" />
-    <PackageReference Include="CsvHelper" Version="31.0.2" />
+    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
+    <PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
+    <PackageReference Include="CsvHelper" Version="31.0.3" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
     <PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
     <PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
   </ItemGroup>
   </ItemGroup>

+ 7 - 2
UnitTests/Application/ApplicationTests.cs

@@ -172,7 +172,8 @@ public class ApplicationTests
 
 
             // Don't check Application.ForceDriver
             // Don't check Application.ForceDriver
             // Assert.Empty (Application.ForceDriver);
             // Assert.Empty (Application.ForceDriver);
-            Assert.False (Application.Force16Colors);
+            // Don't check Application.Force16Colors
+            //Assert.False (Application.Force16Colors);
             Assert.Null (Application.Driver);
             Assert.Null (Application.Driver);
             Assert.Null (Application.MainLoop);
             Assert.Null (Application.MainLoop);
             Assert.False (Application.EndAfterFirstIteration);
             Assert.False (Application.EndAfterFirstIteration);
@@ -808,7 +809,11 @@ public class ApplicationTests
         Init ();
         Init ();
 
 
         // Don't use Dialog here as it has more layout logic. Use Window instead.
         // Don't use Dialog here as it has more layout logic. Use Window instead.
-        var w = new Window { Width = 5, Height = 5 };
+        var w = new Window
+        {
+            Width = 5, Height = 5,
+            Arrangement = ViewArrangement.Movable
+        };
         ((FakeDriver)Application.Driver).SetBufferSize (10, 10);
         ((FakeDriver)Application.Driver).SetBufferSize (10, 10);
         RunState rs = Application.Begin (w);
         RunState rs = Application.Begin (w);
 
 

+ 3 - 3
UnitTests/Application/MouseTests.cs

@@ -60,7 +60,7 @@ public class MouseTests
 
 
     /// <summary>
     /// <summary>
     ///     Tests that the mouse coordinates passed to the focused view are correct when the mouse is clicked. No adornments;
     ///     Tests that the mouse coordinates passed to the focused view are correct when the mouse is clicked. No adornments;
-    ///     Frame == Bounds
+    ///     Frame == Viewport
     /// </summary>
     /// </summary>
     [Theory]
     [Theory]
     [AutoInitShutdown]
     [AutoInitShutdown]
@@ -134,7 +134,7 @@ public class MouseTests
 
 
     /// <summary>
     /// <summary>
     ///     Tests that the mouse coordinates passed to the focused view are correct when the mouse is clicked. With
     ///     Tests that the mouse coordinates passed to the focused view are correct when the mouse is clicked. With
-    ///     Frames; Frame != Bounds
+    ///     Frames; Frame != Viewport
     /// </summary>
     /// </summary>
     [AutoInitShutdown]
     [AutoInitShutdown]
     [Theory]
     [Theory]
@@ -207,7 +207,7 @@ public class MouseTests
 
 
         var view = new View { X = pos.X, Y = pos.Y, Width = size.Width, Height = size.Height };
         var view = new View { X = pos.X, Y = pos.Y, Width = size.Width, Height = size.Height };
 
 
-        // Give the view a border. With PR #2920, mouse clicks are only passed if they are inside the view's Bounds.
+        // Give the view a border. With PR #2920, mouse clicks are only passed if they are inside the view's Viewport.
         view.BorderStyle = LineStyle.Single;
         view.BorderStyle = LineStyle.Single;
         view.CanFocus = true;
         view.CanFocus = true;
 
 

+ 53 - 88
UnitTests/Dialogs/MessageBoxTests.cs

@@ -242,7 +242,7 @@ public class MessageBoxTests
     {
     {
         int iterations = -1;
         int iterations = -1;
         var top = new Toplevel ();
         var top = new Toplevel ();
-        top.BorderStyle = LineStyle.Double;
+        top.BorderStyle = LineStyle.None;
         ((FakeDriver)Application.Driver).SetBufferSize (20, 10);
         ((FakeDriver)Application.Driver).SetBufferSize (20, 10);
 
 
         var btn =
         var btn =
@@ -273,21 +273,15 @@ public class MessageBoxTests
 
 
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                                                                        @$"
                                                                                        @$"
-╔══════════════════╗
-║┌────────────────┐║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│       ff       │║
-║│                │║
-║│    {
-    btn
-}   │║
-║└────────────────┘║
-╚══════════════════╝",
+┌──────────────────┐
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│  ffffffffffffff  │
+│                  │
+│     {btn}    │
+└──────────────────┘",
                                                                                        _output
                                                                                        _output
                                                                                       );
                                                                                       );
-                                         Assert.Equal (new (20 - 2, 10 - 2), Application.Current.Frame.Size);
                                          Application.RequestStop ();
                                          Application.RequestStop ();
 
 
                                          // Really long text
                                          // Really long text
@@ -299,18 +293,16 @@ public class MessageBoxTests
 
 
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                                                                        @$"
                                                                                        @$"
-╔┌────────────────┐╗
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│    {
-    btn
-}   │║
-╚└────────────────┘╝",
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│     {btn}    │",
                                                                                        _output
                                                                                        _output
                                                                                       );
                                                                                       );
                                          Application.RequestStop ();
                                          Application.RequestStop ();
@@ -326,7 +318,7 @@ public class MessageBoxTests
     {
     {
         int iterations = -1;
         int iterations = -1;
         var top = new Toplevel ();
         var top = new Toplevel ();
-        top.BorderStyle = LineStyle.Double;
+        top.BorderStyle = LineStyle.None;
         ((FakeDriver)Application.Driver).SetBufferSize (20, 10);
         ((FakeDriver)Application.Driver).SetBufferSize (20, 10);
 
 
         var btn =
         var btn =
@@ -363,16 +355,12 @@ public class MessageBoxTests
 
 
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                                                                        @"
                                                                                        @"
-╔══════════════════╗
-║                  ║
 ────────────────────
 ────────────────────
 ff ff ff ff ff ff ff
 ff ff ff ff ff ff ff
                     
                     
       ⟦► btn ◄⟧     
       ⟦► btn ◄⟧     
 ────────────────────
 ────────────────────
-║                  ║
-║                  ║
-╚══════════════════╝",
+",
                                                                                        _output
                                                                                        _output
                                                                                       );
                                                                                       );
                                          Application.RequestStop ();
                                          Application.RequestStop ();
@@ -386,16 +374,12 @@ ff ff ff ff ff ff ff
 
 
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                                                                        @"
                                                                                        @"
-╔══════════════════╗
-║                  ║
 ────────────────────
 ────────────────────
 ffffffffffffffffffff
 ffffffffffffffffffff
                     
                     
       ⟦► btn ◄⟧     
       ⟦► btn ◄⟧     
 ────────────────────
 ────────────────────
-║                  ║
-║                  ║
-╚══════════════════╝",
+",
                                                                                        _output
                                                                                        _output
                                                                                       );
                                                                                       );
                                          Application.RequestStop ();
                                          Application.RequestStop ();
@@ -411,7 +395,7 @@ ffffffffffffffffffff
     {
     {
         int iterations = -1;
         int iterations = -1;
         var top = new Toplevel();
         var top = new Toplevel();
-        top.BorderStyle = LineStyle.Double;
+        top.BorderStyle = LineStyle.None;
         ((FakeDriver)Application.Driver).SetBufferSize (20, 10);
         ((FakeDriver)Application.Driver).SetBufferSize (20, 10);
 
 
         var btn =
         var btn =
@@ -446,20 +430,14 @@ ffffffffffffffffffff
                                      {
                                      {
                                          Application.Refresh ();
                                          Application.Refresh ();
 
 
-                                         TestHelpers.AssertDriverContentsWithFrameAre (
-                                                                                       @$"
-╔══════════════════╗
-║ ┌──────────────┐ ║
-║ │ff ff ff ff ff│ ║
-║ │ff ff ff ff ff│ ║
-║ │ff ff ff ff ff│ ║
-║ │    ff ff     │ ║
-║ │              │ ║
-║ │   {
-    btn
-}  │ ║
-║ └──────────────┘ ║
-╚══════════════════╝",
+                                         TestHelpers.AssertDriverContentsWithFrameAre (@$"
+┌─────────────────┐
+│ff ff ff ff ff ff│
+│ff ff ff ff ff ff│
+│ ff ff ff ff ff  │
+│                 │
+│    {btn}    │
+└─────────────────┘",
                                                                                        _output
                                                                                        _output
                                                                                       );
                                                                                       );
                                          Application.RequestStop ();
                                          Application.RequestStop ();
@@ -471,20 +449,17 @@ ffffffffffffffffffff
                                      {
                                      {
                                          Application.Refresh ();
                                          Application.Refresh ();
 
 
-                                         TestHelpers.AssertDriverContentsWithFrameAre (
-                                                                                       @$"
-╔┌────────────────┐╗
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│ffffffffffffffff│║
-║│    {
-    btn
-}   │║
-╚└────────────────┘╝",
+                                         TestHelpers.AssertDriverContentsWithFrameAre (@$"
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│ffffffffffffffffff│
+│     {btn}    │",
                                                                                        _output
                                                                                        _output
                                                                                       );
                                                                                       );
                                          Application.RequestStop ();
                                          Application.RequestStop ();
@@ -492,6 +467,7 @@ ffffffffffffffffffff
                                  };
                                  };
 
 
         Application.Run (top);
         Application.Run (top);
+        top.Dispose ();
     }
     }
 
 
     [Fact]
     [Fact]
@@ -500,7 +476,7 @@ ffffffffffffffffffff
     {
     {
         int iterations = -1;
         int iterations = -1;
         var top = new Toplevel();
         var top = new Toplevel();
-        top.BorderStyle = LineStyle.Double;
+        top.BorderStyle = LineStyle.None;
         ((FakeDriver)Application.Driver).SetBufferSize (20, 10);
         ((FakeDriver)Application.Driver).SetBufferSize (20, 10);
 
 
         var btn =
         var btn =
@@ -530,16 +506,12 @@ ffffffffffffffffffff
 
 
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                                                                        @"
                                                                                        @"
-╔══════════════════╗
-║                  ║
 ────────────────────
 ────────────────────
 ffffffffffffffffffff
 ffffffffffffffffffff
                     
                     
       ⟦► btn ◄⟧     
       ⟦► btn ◄⟧     
 ────────────────────
 ────────────────────
-║                  ║
-║                  ║
-╚══════════════════╝",
+",
                                                                                        _output
                                                                                        _output
                                                                                       );
                                                                                       );
 
 
@@ -554,16 +526,12 @@ ffffffffffffffffffff
 
 
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                                                                        @"
                                                                                        @"
-╔══════════════════╗
-║                  ║
 ────────────────────
 ────────────────────
 ffffffffffffffffffff
 ffffffffffffffffffff
                     
                     
       ⟦► btn ◄⟧     
       ⟦► btn ◄⟧     
 ────────────────────
 ────────────────────
-║                  ║
-║                  ║
-╚══════════════════╝",
+",
                                                                                        _output
                                                                                        _output
                                                                                       );
                                                                                       );
 
 
@@ -663,7 +631,7 @@ ffffffffffffffffffff
     public void Size_No_With_Button ()
     public void Size_No_With_Button ()
     {
     {
         var top = new Toplevel ();
         var top = new Toplevel ();
-        top.BorderStyle = LineStyle.Double;
+        top.BorderStyle = LineStyle.None;
         int iterations = -1;
         int iterations = -1;
 
 
         var aboutMessage = new StringBuilder ();
         var aboutMessage = new StringBuilder ();
@@ -700,16 +668,12 @@ ffffffffffffffffffff
 
 
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                          TestHelpers.AssertDriverContentsWithFrameAre (
                                                                                        @$"
                                                                                        @$"
-╔══════════════════════════════════════════╗
-║┌────────────────────────────────────────┐║
-║│0123456789012345678901234567890123456789│║
-║│ https://github.com/gui-cs/Terminal.Gui │║
-║│                                        │║
-║│                {
-    btn
-}                │║
-║└────────────────────────────────────────┘║
-╚══════════════════════════════════════════╝
+ ┌────────────────────────────────────────┐
+ │0123456789012345678901234567890123456789│
+ │ https://github.com/gui-cs/Terminal.Gui │
+ │                                        │
+ │                {btn}                │
+ └────────────────────────────────────────┘
 ",
 ",
                                                                                        _output
                                                                                        _output
                                                                                       );
                                                                                       );
@@ -719,6 +683,7 @@ ffffffffffffffffffff
                                  };
                                  };
 
 
         Application.Run (top);
         Application.Run (top);
+        top.Dispose ();
     }
     }
 
 
     [Fact]
     [Fact]

+ 26 - 26
UnitTests/Drawing/LineCanvasTests.cs

@@ -293,7 +293,7 @@ public class LineCanvasTests
         View v = GetCanvas (out LineCanvas lc);
         View v = GetCanvas (out LineCanvas lc);
         v.Width = 10;
         v.Width = 10;
         v.Height = 10;
         v.Height = 10;
-        v.Bounds = new Rectangle (0, 0, 10, 10);
+        v.Viewport = new Rectangle (0, 0, 10, 10);
 
 
         lc.AddLine (new Point (x1, y1), len1, o1, s1);
         lc.AddLine (new Point (x1, y1), len1, o1, s1);
         lc.AddLine (new Point (x2, y2), len2, o2, s2);
         lc.AddLine (new Point (x2, y2), len2, o2, s2);
@@ -366,7 +366,7 @@ public class LineCanvasTests
                 )]
                 )]
     [Theory]
     [Theory]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Bounds_H_And_V_Lines_Both_Positive (
+    public void Viewport_H_And_V_Lines_Both_Positive (
         int x,
         int x,
         int y,
         int y,
         int length,
         int length,
@@ -380,7 +380,7 @@ public class LineCanvasTests
         canvas.AddLine (new Point (x, y), length, Orientation.Horizontal, LineStyle.Single);
         canvas.AddLine (new Point (x, y), length, Orientation.Horizontal, LineStyle.Single);
         canvas.AddLine (new Point (x, y), length, Orientation.Vertical, LineStyle.Single);
         canvas.AddLine (new Point (x, y), length, Orientation.Vertical, LineStyle.Single);
 
 
-        Assert.Equal (new Rectangle (expectedX, expectedY, expectedWidth, expectedHeight), canvas.Bounds);
+        Assert.Equal (new Rectangle (expectedX, expectedY, expectedWidth, expectedHeight), canvas.Viewport);
     }
     }
 
 
     [InlineData (
     [InlineData (
@@ -448,7 +448,7 @@ public class LineCanvasTests
                 )]
                 )]
     [Theory]
     [Theory]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Bounds_H_Line (
+    public void Viewport_H_Line (
         int x,
         int x,
         int y,
         int y,
         int length,
         int length,
@@ -461,12 +461,12 @@ public class LineCanvasTests
         var canvas = new LineCanvas ();
         var canvas = new LineCanvas ();
         canvas.AddLine (new Point (x, y), length, Orientation.Horizontal, LineStyle.Single);
         canvas.AddLine (new Point (x, y), length, Orientation.Horizontal, LineStyle.Single);
 
 
-        Assert.Equal (new Rectangle (expectedX, expectedY, expectedWidth, expectedHeight), canvas.Bounds);
+        Assert.Equal (new Rectangle (expectedX, expectedY, expectedWidth, expectedHeight), canvas.Viewport);
     }
     }
 
 
     [Fact]
     [Fact]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Bounds_Specific ()
+    public void Viewport_Specific ()
     {
     {
         // Draw at 1,1 within client area of View (i.e. leave a top and left margin of 1)
         // Draw at 1,1 within client area of View (i.e. leave a top and left margin of 1)
         // This proves we aren't drawing excess above
         // This proves we aren't drawing excess above
@@ -483,27 +483,27 @@ public class LineCanvasTests
 
 
         // Add a short horiz line for ╔╡
         // Add a short horiz line for ╔╡
         lc.AddLine (new Point (x, y), 2, Orientation.Horizontal, LineStyle.Double);
         lc.AddLine (new Point (x, y), 2, Orientation.Horizontal, LineStyle.Double);
-        Assert.Equal (new Rectangle (x, y, 2, 1), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 2, 1), lc.Viewport);
 
 
         //LHS line down
         //LHS line down
         lc.AddLine (new Point (x, y), height, Orientation.Vertical, LineStyle.Double);
         lc.AddLine (new Point (x, y), height, Orientation.Vertical, LineStyle.Double);
-        Assert.Equal (new Rectangle (x, y, 2, 2), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 2, 2), lc.Viewport);
 
 
         //Vertical line before Title, results in a ╡
         //Vertical line before Title, results in a ╡
         lc.AddLine (new Point (x + 1, y), 0, Orientation.Vertical, LineStyle.Single);
         lc.AddLine (new Point (x + 1, y), 0, Orientation.Vertical, LineStyle.Single);
-        Assert.Equal (new Rectangle (x, y, 2, 2), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 2, 2), lc.Viewport);
 
 
         //Vertical line after Title, results in a ╞
         //Vertical line after Title, results in a ╞
         lc.AddLine (new Point (x + 2, y), 0, Orientation.Vertical, LineStyle.Single);
         lc.AddLine (new Point (x + 2, y), 0, Orientation.Vertical, LineStyle.Single);
-        Assert.Equal (new Rectangle (x, y, 3, 2), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 3, 2), lc.Viewport);
 
 
         // remainder of top line
         // remainder of top line
         lc.AddLine (new Point (x + 2, y), width - 1, Orientation.Horizontal, LineStyle.Double);
         lc.AddLine (new Point (x + 2, y), width - 1, Orientation.Horizontal, LineStyle.Double);
-        Assert.Equal (new Rectangle (x, y, 4, 2), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 4, 2), lc.Viewport);
 
 
         //RHS line down
         //RHS line down
         lc.AddLine (new Point (x + width, y), height, Orientation.Vertical, LineStyle.Double);
         lc.AddLine (new Point (x + width, y), height, Orientation.Vertical, LineStyle.Double);
-        Assert.Equal (new Rectangle (x, y, 4, 2), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 4, 2), lc.Viewport);
 
 
         TestHelpers.AssertEqual (
         TestHelpers.AssertEqual (
                                  output,
                                  output,
@@ -516,7 +516,7 @@ public class LineCanvasTests
 
 
     [Fact]
     [Fact]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Bounds_Specific_With_Ustring ()
+    public void Viewport_Specific_With_Ustring ()
     {
     {
         // Draw at 1,1 within client area of View (i.e. leave a top and left margin of 1)
         // Draw at 1,1 within client area of View (i.e. leave a top and left margin of 1)
         // This proves we aren't drawing excess above
         // This proves we aren't drawing excess above
@@ -533,27 +533,27 @@ public class LineCanvasTests
 
 
         // Add a short horiz line for ╔╡
         // Add a short horiz line for ╔╡
         lc.AddLine (new Point (x, y), 2, Orientation.Horizontal, LineStyle.Double);
         lc.AddLine (new Point (x, y), 2, Orientation.Horizontal, LineStyle.Double);
-        Assert.Equal (new Rectangle (x, y, 2, 1), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 2, 1), lc.Viewport);
 
 
         //LHS line down
         //LHS line down
         lc.AddLine (new Point (x, y), height, Orientation.Vertical, LineStyle.Double);
         lc.AddLine (new Point (x, y), height, Orientation.Vertical, LineStyle.Double);
-        Assert.Equal (new Rectangle (x, y, 2, 2), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 2, 2), lc.Viewport);
 
 
         //Vertical line before Title, results in a ╡
         //Vertical line before Title, results in a ╡
         lc.AddLine (new Point (x + 1, y), 0, Orientation.Vertical, LineStyle.Single);
         lc.AddLine (new Point (x + 1, y), 0, Orientation.Vertical, LineStyle.Single);
-        Assert.Equal (new Rectangle (x, y, 2, 2), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 2, 2), lc.Viewport);
 
 
         //Vertical line after Title, results in a ╞
         //Vertical line after Title, results in a ╞
         lc.AddLine (new Point (x + 2, y), 0, Orientation.Vertical, LineStyle.Single);
         lc.AddLine (new Point (x + 2, y), 0, Orientation.Vertical, LineStyle.Single);
-        Assert.Equal (new Rectangle (x, y, 3, 2), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 3, 2), lc.Viewport);
 
 
         // remainder of top line
         // remainder of top line
         lc.AddLine (new Point (x + 2, y), width - 1, Orientation.Horizontal, LineStyle.Double);
         lc.AddLine (new Point (x + 2, y), width - 1, Orientation.Horizontal, LineStyle.Double);
-        Assert.Equal (new Rectangle (x, y, 4, 2), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 4, 2), lc.Viewport);
 
 
         //RHS line down
         //RHS line down
         lc.AddLine (new Point (x + width, y), height, Orientation.Vertical, LineStyle.Double);
         lc.AddLine (new Point (x + width, y), height, Orientation.Vertical, LineStyle.Double);
-        Assert.Equal (new Rectangle (x, y, 4, 2), lc.Bounds);
+        Assert.Equal (new Rectangle (x, y, 4, 2), lc.Viewport);
 
 
         TestHelpers.AssertEqual (
         TestHelpers.AssertEqual (
                                  output,
                                  output,
@@ -570,13 +570,13 @@ public class LineCanvasTests
     {
     {
         var lc = new LineCanvas ();
         var lc = new LineCanvas ();
 
 
-        Assert.Equal (Rectangle.Empty, lc.Bounds);
+        Assert.Equal (Rectangle.Empty, lc.Viewport);
 
 
         lc.AddLine (Point.Empty, 2, Orientation.Horizontal, LineStyle.Double);
         lc.AddLine (Point.Empty, 2, Orientation.Horizontal, LineStyle.Double);
-        Assert.NotEqual (Rectangle.Empty, lc.Bounds);
+        Assert.NotEqual (Rectangle.Empty, lc.Viewport);
 
 
         lc.Clear ();
         lc.Clear ();
-        Assert.Equal (Rectangle.Empty, lc.Bounds);
+        Assert.Equal (Rectangle.Empty, lc.Viewport);
     }
     }
 
 
     [InlineData (0, 0, Orientation.Horizontal, "─")]
     [InlineData (0, 0, Orientation.Horizontal, "─")]
@@ -873,7 +873,7 @@ public class LineCanvasTests
         //// Left Up
         //// Left Up
         //canvas.AddLine (new Point (0, 3), -3, Orientation.Vertical, LineStyle.Single);
         //canvas.AddLine (new Point (0, 3), -3, Orientation.Vertical, LineStyle.Single);
 
 
-        Assert.Equal (new Rectangle (0, 0, 2, 2), canvas.Bounds);
+        Assert.Equal (new Rectangle (0, 0, 2, 2), canvas.Viewport);
 
 
         Dictionary<Point, Rune> map = canvas.GetMap ();
         Dictionary<Point, Rune> map = canvas.GetMap ();
         Assert.Equal (2, map.Count);
         Assert.Equal (2, map.Count);
@@ -990,7 +990,7 @@ public class LineCanvasTests
         View v = GetCanvas (out LineCanvas lc);
         View v = GetCanvas (out LineCanvas lc);
         v.Width = 10;
         v.Width = 10;
         v.Height = 10;
         v.Height = 10;
-        v.Bounds = new Rectangle (0, 0, 10, 10);
+        v.Viewport = new Rectangle (0, 0, 10, 10);
 
 
         lc.AddLine (new Point (x1, y1), length, o1, s1);
         lc.AddLine (new Point (x1, y1), length, o1, s1);
 
 
@@ -1304,7 +1304,7 @@ public class LineCanvasTests
     /// <returns></returns>
     /// <returns></returns>
     private View GetCanvas (out LineCanvas canvas, int offsetX = 0, int offsetY = 0)
     private View GetCanvas (out LineCanvas canvas, int offsetX = 0, int offsetY = 0)
     {
     {
-        var v = new View { Width = 10, Height = 5, Bounds = new Rectangle (0, 0, 10, 5) };
+        var v = new View { Width = 10, Height = 5, Viewport = new Rectangle (0, 0, 10, 5) };
         var top = new Toplevel ();
         var top = new Toplevel ();
         top.Add (v);
         top.Add (v);
         Application.Begin (top);
         Application.Begin (top);
@@ -1313,7 +1313,7 @@ public class LineCanvasTests
 
 
         v.DrawContentComplete += (s, e) =>
         v.DrawContentComplete += (s, e) =>
                                  {
                                  {
-                                     v.Clear (v.Bounds);
+                                     v.FillRect (v.Viewport);
 
 
                                      foreach (KeyValuePair<Point, Rune> p in canvasCopy.GetMap ())
                                      foreach (KeyValuePair<Point, Rune> p in canvasCopy.GetMap ())
                                      {
                                      {

+ 2 - 2
UnitTests/Drawing/StraightLineTests.cs

@@ -309,7 +309,7 @@ public class StraightLineTests
                 )]
                 )]
     [Theory]
     [Theory]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Bounds (
+    public void Viewport (
         Orientation orientation,
         Orientation orientation,
         int x,
         int x,
         int y,
         int y,
@@ -322,6 +322,6 @@ public class StraightLineTests
     {
     {
         var sl = new StraightLine (new Point (x, y), length, orientation, LineStyle.Single);
         var sl = new StraightLine (new Point (x, y), length, orientation, LineStyle.Single);
 
 
-        Assert.Equal (new Rectangle (expectedX, expectedY, expectedWidth, expectedHeight), sl.Bounds);
+        Assert.Equal (new Rectangle (expectedX, expectedY, expectedWidth, expectedHeight), sl.Viewport);
     }
     }
 }
 }

+ 21 - 0
UnitTests/TestHelpers.cs

@@ -3,6 +3,7 @@ using System.Globalization;
 using System.Reflection;
 using System.Reflection;
 using System.Text;
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
+using UICatalog;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 using Xunit.Sdk;
 using Xunit.Sdk;
 
 
@@ -123,6 +124,8 @@ public class TestRespondersDisposed : BeforeAfterTestAttribute
     public override void After (MethodInfo methodUnderTest)
     public override void After (MethodInfo methodUnderTest)
     {
     {
         Debug.WriteLine ($"After: {methodUnderTest.Name}");
         Debug.WriteLine ($"After: {methodUnderTest.Name}");
+        base.After (methodUnderTest);
+
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
         Assert.Empty (Responder.Instances);
         Assert.Empty (Responder.Instances);
 #endif
 #endif
@@ -131,6 +134,7 @@ public class TestRespondersDisposed : BeforeAfterTestAttribute
     public override void Before (MethodInfo methodUnderTest)
     public override void Before (MethodInfo methodUnderTest)
     {
     {
         Debug.WriteLine ($"Before: {methodUnderTest.Name}");
         Debug.WriteLine ($"Before: {methodUnderTest.Name}");
+        base.Before (methodUnderTest);
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
 
 
         // Clear out any lingering Responder instances from previous tests
         // Clear out any lingering Responder instances from previous tests
@@ -140,6 +144,7 @@ public class TestRespondersDisposed : BeforeAfterTestAttribute
     }
     }
 }
 }
 
 
+// TODO: Make this inherit from TestRespondersDisposed so that all tests that don't dispose Views correctly can be identified and fixed
 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
 public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
 public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
 {
 {
@@ -157,6 +162,7 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
         View.Diagnostics = ViewDiagnosticFlags.Off;
         View.Diagnostics = ViewDiagnosticFlags.Off;
 
 
         Application.Driver = null;
         Application.Driver = null;
+        base.After (methodUnderTest);
     }
     }
 
 
     public override void Before (MethodInfo methodUnderTest)
     public override void Before (MethodInfo methodUnderTest)
@@ -164,6 +170,7 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
         Debug.WriteLine ($"Before: {methodUnderTest.Name}");
         Debug.WriteLine ($"Before: {methodUnderTest.Name}");
         Assert.Null (Application.Driver);
         Assert.Null (Application.Driver);
         Application.Driver = new FakeDriver { Rows = 25, Cols = 25 };
         Application.Driver = new FakeDriver { Rows = 25, Cols = 25 };
+        base.Before (methodUnderTest);
     }
     }
 }
 }
 
 
@@ -559,6 +566,20 @@ internal partial class TestHelpers
     }
     }
 
 
 
 
+    public static TheoryData<Scenario, string> GetAllScenarioTheoryData ()
+    {
+        // TODO: Figure out how to simplify this. I couldn't figure out how to not have to iterate over ret.
+        var scenarios = Scenario.GetScenarios ();
+        (Scenario scenario, string name) [] ret = scenarios.Select (s => (scenario: s, name: s.GetName ())).ToArray();
+        TheoryData<Scenario, string> td = new ();
+        foreach ((Scenario scenario, string name) in ret)
+        {
+            td.Add (scenario, name);
+        }
+
+        return td;
+    }
+
     /// <summary>
     /// <summary>
     ///     Verifies the console used all the <paramref name="expectedColors"/> when rendering. If one or more of the
     ///     Verifies the console used all the <paramref name="expectedColors"/> when rendering. If one or more of the
     ///     expected colors are not used then the failure will output both the colors that were found to be used and which of
     ///     expected colors are not used then the failure will output both the colors that were found to be used and which of

+ 71 - 83
UnitTests/UICatalog/ScenarioTests.cs

@@ -15,85 +15,73 @@ public class ScenarioTests
         _output = output;
         _output = output;
     }
     }
 
 
+    public static TheoryData<Scenario, string> AllScenarios => TestHelpers.GetAllScenarioTheoryData ();
+
     /// <summary>
     /// <summary>
     ///     <para>This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run.</para>
     ///     <para>This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run.</para>
     ///     <para>Should find any Scenarios which crash on load or do not respond to <see cref="Application.RequestStop()"/>.</para>
     ///     <para>Should find any Scenarios which crash on load or do not respond to <see cref="Application.RequestStop()"/>.</para>
     /// </summary>
     /// </summary>
-    [Fact]
-    public void Run_All_Scenarios ()
+    [Theory]
+    [MemberData (nameof (AllScenarios))]
+    public void Run_All_Scenarios (Scenario scenario, string viewName)
     {
     {
-        List<Scenario> scenarios = Scenario.GetScenarios ();
-        Assert.NotEmpty (scenarios);
+        _output.WriteLine ($"Running Scenario '{scenario.GetName ()}'");
 
 
-        foreach (Scenario scenario in scenarios)
-        {
-            _output.WriteLine ($"Running Scenario '{scenario.GetName ()}'");
-
-            Application.Init (new FakeDriver ());
+        Application.Init (new FakeDriver ());
 
 
-            // Press QuitKey 
-            Assert.Empty (FakeConsole.MockKeyPresses);
+        // Press QuitKey 
+        Assert.Empty (FakeConsole.MockKeyPresses);
 
 
-            // BUGBUG: (#2474) For some reason ReadKey is not returning the QuitKey for some Scenarios
-            // by adding this Space it seems to work.
-            //FakeConsole.PushMockKeyPress (Key.Space);
-            FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey);
+        // BUGBUG: (#2474) For some reason ReadKey is not returning the QuitKey for some Scenarios
+        // by adding this Space it seems to work.
+        //FakeConsole.PushMockKeyPress (Key.Space);
+        FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey);
 
 
-            // The only key we care about is the QuitKey
-            Application.KeyDown += (sender, args) =>
-                                       {
-                                           _output.WriteLine ($"  Keypress: {args.KeyCode}");
+        // The only key we care about is the QuitKey
+        Application.KeyDown += (sender, args) =>
+                               {
+                                   _output.WriteLine ($"  Keypress: {args.KeyCode}");
 
 
-                                           // BUGBUG: (#2474) For some reason ReadKey is not returning the QuitKey for some Scenarios
-                                           // by adding this Space it seems to work.
-                                           // See #2474 for why this is commented out
-                                           Assert.Equal (Application.QuitKey.KeyCode, args.KeyCode);
-                                       };
+                                   // BUGBUG: (#2474) For some reason ReadKey is not returning the QuitKey for some Scenarios
+                                   // by adding this Space it seems to work.
+                                   // See #2474 for why this is commented out
+                                   Assert.Equal (Application.QuitKey.KeyCode, args.KeyCode);
+                               };
 
 
-            uint abortTime = 500;
+        uint abortTime = 500;
 
 
-            // If the scenario doesn't close within 500ms, this will force it to quit
-            bool ForceCloseCallback ()
+        // If the scenario doesn't close within 500ms, this will force it to quit
+        bool ForceCloseCallback ()
+        {
+            if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0)
             {
             {
-                if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0)
-                {
-                    Application.RequestStop ();
-
-                    // See #2474 for why this is commented out
-                    Assert.Fail (
-                                 $"'{
-                                     scenario.GetName ()
-                                 }' failed to Quit with {
-                                     Application.QuitKey
-                                 } after {
-                                     abortTime
-                                 }ms. Force quit.");
-                }
+                Application.RequestStop ();
 
 
-                return false;
+                // See #2474 for why this is commented out
+                Assert.Fail (
+                             $"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey} after {abortTime}ms. Force quit.");
             }
             }
 
 
-            //output.WriteLine ($"  Add timeout to force quit after {abortTime}ms");
-            _ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback);
+            return false;
+        }
+
+        //output.WriteLine ($"  Add timeout to force quit after {abortTime}ms");
+        _ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback);
 
 
-            Application.Iteration += (s, a) =>
+        Application.Iteration += (s, a) =>
+                                 {
+                                     //output.WriteLine ($"  iteration {++iterations}");
+                                     if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0)
                                      {
                                      {
-                                         //output.WriteLine ($"  iteration {++iterations}");
-                                         if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0)
-                                         {
-                                             Application.RequestStop ();
-                                             Assert.Fail ($"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey}. Force quit.");
-                                         }
-                                     };
+                                         Application.RequestStop ();
+                                         Assert.Fail ($"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey}. Force quit.");
+                                     }
+                                 };
 
 
-            scenario.Main ();
-            scenario.Dispose ();
+        scenario.Main ();
+        scenario.Dispose ();
 
 
-            Application.Shutdown ();
-#if DEBUG_IDISPOSABLE
-            Assert.Empty (Responder.Instances);
-#endif
-        }
+        Application.Shutdown ();
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
         Assert.Empty (Responder.Instances);
         Assert.Empty (Responder.Instances);
 #endif
 #endif
@@ -132,14 +120,14 @@ public class ScenarioTests
 
 
         Application.Init (new FakeDriver ());
         Application.Init (new FakeDriver ());
 
 
-        Toplevel top = new Toplevel ();
+        var top = new Toplevel ();
 
 
         _viewClasses = GetAllViewClassesCollection ()
         _viewClasses = GetAllViewClassesCollection ()
                        .OrderBy (t => t.Name)
                        .OrderBy (t => t.Name)
                        .Select (t => new KeyValuePair<string, Type> (t.Name, t))
                        .Select (t => new KeyValuePair<string, Type> (t.Name, t))
                        .ToDictionary (t => t.Key, t => t.Value);
                        .ToDictionary (t => t.Key, t => t.Value);
 
 
-        _leftPane = new Window
+        _leftPane = new()
         {
         {
             Title = "Classes",
             Title = "Classes",
             X = 0,
             X = 0,
@@ -150,7 +138,7 @@ public class ScenarioTests
             ColorScheme = Colors.ColorSchemes ["TopLevel"]
             ColorScheme = Colors.ColorSchemes ["TopLevel"]
         };
         };
 
 
-        _classListView = new ListView
+        _classListView = new()
         {
         {
             X = 0,
             X = 0,
             Y = 0,
             Y = 0,
@@ -162,7 +150,7 @@ public class ScenarioTests
         };
         };
         _leftPane.Add (_classListView);
         _leftPane.Add (_classListView);
 
 
-        _settingsPane = new FrameView
+        _settingsPane = new()
         {
         {
             X = Pos.Right (_leftPane),
             X = Pos.Right (_leftPane),
             Y = 0, // for menu
             Y = 0, // for menu
@@ -172,12 +160,12 @@ public class ScenarioTests
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
             Title = "Settings"
             Title = "Settings"
         };
         };
-        _computedCheckBox = new CheckBox { X = 0, Y = 0, Text = "Computed Layout", Checked = true };
+        _computedCheckBox = new() { X = 0, Y = 0, Text = "Computed Layout", Checked = true };
         _settingsPane.Add (_computedCheckBox);
         _settingsPane.Add (_computedCheckBox);
 
 
         var radioItems = new [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" };
         var radioItems = new [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" };
 
 
-        _locationFrame = new FrameView
+        _locationFrame = new()
         {
         {
             X = Pos.Left (_computedCheckBox),
             X = Pos.Left (_computedCheckBox),
             Y = Pos.Bottom (_computedCheckBox),
             Y = Pos.Bottom (_computedCheckBox),
@@ -189,21 +177,21 @@ public class ScenarioTests
 
 
         var label = new Label { X = 0, Y = 0, Text = "x:" };
         var label = new Label { X = 0, Y = 0, Text = "x:" };
         _locationFrame.Add (label);
         _locationFrame.Add (label);
-        _xRadioGroup = new RadioGroup { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
-        _xText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_xVal}" };
+        _xRadioGroup = new() { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
+        _xText = new() { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_xVal}" };
         _locationFrame.Add (_xText);
         _locationFrame.Add (_xText);
 
 
         _locationFrame.Add (_xRadioGroup);
         _locationFrame.Add (_xRadioGroup);
 
 
         radioItems = new [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" };
         radioItems = new [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" };
-        label = new Label { X = Pos.Right (_xRadioGroup) + 1, Y = 0, Text = "y:" };
+        label = new() { X = Pos.Right (_xRadioGroup) + 1, Y = 0, Text = "y:" };
         _locationFrame.Add (label);
         _locationFrame.Add (label);
-        _yText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_yVal}" };
+        _yText = new() { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_yVal}" };
         _locationFrame.Add (_yText);
         _locationFrame.Add (_yText);
-        _yRadioGroup = new RadioGroup { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
+        _yRadioGroup = new() { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
         _locationFrame.Add (_yRadioGroup);
         _locationFrame.Add (_yRadioGroup);
 
 
-        _sizeFrame = new FrameView
+        _sizeFrame = new()
         {
         {
             X = Pos.Right (_locationFrame),
             X = Pos.Right (_locationFrame),
             Y = Pos.Y (_locationFrame),
             Y = Pos.Y (_locationFrame),
@@ -213,25 +201,25 @@ public class ScenarioTests
         };
         };
 
 
         radioItems = new [] { "Percent(width)", "Fill(width)", "Sized(width)" };
         radioItems = new [] { "Percent(width)", "Fill(width)", "Sized(width)" };
-        label = new Label { X = 0, Y = 0, Text = "width:" };
+        label = new() { X = 0, Y = 0, Text = "width:" };
         _sizeFrame.Add (label);
         _sizeFrame.Add (label);
-        _wRadioGroup = new RadioGroup { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
-        _wText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_wVal}" };
+        _wRadioGroup = new() { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
+        _wText = new() { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_wVal}" };
         _sizeFrame.Add (_wText);
         _sizeFrame.Add (_wText);
         _sizeFrame.Add (_wRadioGroup);
         _sizeFrame.Add (_wRadioGroup);
 
 
         radioItems = new [] { "Percent(height)", "Fill(height)", "Sized(height)" };
         radioItems = new [] { "Percent(height)", "Fill(height)", "Sized(height)" };
-        label = new Label { X = Pos.Right (_wRadioGroup) + 1, Y = 0, Text = "height:" };
+        label = new() { X = Pos.Right (_wRadioGroup) + 1, Y = 0, Text = "height:" };
         _sizeFrame.Add (label);
         _sizeFrame.Add (label);
-        _hText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_hVal}" };
+        _hText = new() { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_hVal}" };
         _sizeFrame.Add (_hText);
         _sizeFrame.Add (_hText);
 
 
-        _hRadioGroup = new RadioGroup { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
+        _hRadioGroup = new() { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
         _sizeFrame.Add (_hRadioGroup);
         _sizeFrame.Add (_hRadioGroup);
 
 
         _settingsPane.Add (_sizeFrame);
         _settingsPane.Add (_sizeFrame);
 
 
-        _hostPane = new FrameView
+        _hostPane = new()
         {
         {
             X = Pos.Right (_leftPane),
             X = Pos.Right (_leftPane),
             Y = Pos.Bottom (_settingsPane),
             Y = Pos.Bottom (_settingsPane),
@@ -251,7 +239,7 @@ public class ScenarioTests
                                                       _hostPane.Remove (_curView);
                                                       _hostPane.Remove (_curView);
                                                       _curView.Dispose ();
                                                       _curView.Dispose ();
                                                       _curView = null;
                                                       _curView = null;
-                                                      _hostPane.Clear (_hostPane.Bounds);
+                                                      _hostPane.FillRect (_hostPane.Viewport);
                                                   }
                                                   }
 
 
                                                   _curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]);
                                                   _curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]);
@@ -623,10 +611,10 @@ public class ScenarioTests
                                  };
                                  };
 
 
         Application.KeyDown += (sender, args) =>
         Application.KeyDown += (sender, args) =>
-                                   {
-                                       // See #2474 for why this is commented out
-                                       Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, args.KeyCode);
-                                   };
+                               {
+                                   // See #2474 for why this is commented out
+                                   Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, args.KeyCode);
+                               };
 
 
         generic.Main ();
         generic.Main ();
 
 

+ 5 - 5
UnitTests/UnitTests.csproj

@@ -30,15 +30,15 @@
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
-    <PackageReference Include="ReportGenerator" Version="5.2.2" />
-    <PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="20.0.15" />
-    <PackageReference Include="xunit" Version="2.7.0" />
+    <PackageReference Include="ReportGenerator" Version="5.2.4" />
+    <PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="21.0.2" />
+    <PackageReference Include="xunit" Version="2.7.1" />
     <PackageReference Include="Xunit.Combinatorial" Version="1.6.24" />
     <PackageReference Include="Xunit.Combinatorial" Version="1.6.24" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>
-    <PackageReference Include="coverlet.collector" Version="6.0.1">
+    <PackageReference Include="coverlet.collector" Version="6.0.2">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>

+ 38 - 27
UnitTests/View/Adornment/AdornmentTests.cs

@@ -7,7 +7,7 @@ public class AdornmentTests (ITestOutputHelper output)
     private readonly ITestOutputHelper _output = output;
     private readonly ITestOutputHelper _output = output;
 
 
     [Fact]
     [Fact]
-    public void Bounds_Location_Always_Empty_Size_Correct ()
+    public void Viewport_Location_Always_Empty_Size_Correct ()
     {
     {
         var view = new View
         var view = new View
         {
         {
@@ -21,28 +21,28 @@ public class AdornmentTests (ITestOutputHelper output)
         view.EndInit ();
         view.EndInit ();
 
 
         Assert.Equal (new (1, 2, 20, 20), view.Frame);
         Assert.Equal (new (1, 2, 20, 20), view.Frame);
-        Assert.Equal (new (0, 0, 20, 20), view.Bounds);
+        Assert.Equal (new (0, 0, 20, 20), view.Viewport);
 
 
         var marginThickness = 1;
         var marginThickness = 1;
-        view.Margin.Thickness = new  (marginThickness);
-        Assert.Equal (new (0, 0, 18, 18), view.Bounds);
+        view.Margin.Thickness = new (marginThickness);
+        Assert.Equal (new (0, 0, 18, 18), view.Viewport);
 
 
         var borderThickness = 2;
         var borderThickness = 2;
         view.Border.Thickness = new (borderThickness);
         view.Border.Thickness = new (borderThickness);
-        Assert.Equal (new (0, 0, 14, 14), view.Bounds);
+        Assert.Equal (new (0, 0, 14, 14), view.Viewport);
 
 
         var paddingThickness = 3;
         var paddingThickness = 3;
         view.Padding.Thickness = new Thickness (paddingThickness);
         view.Padding.Thickness = new Thickness (paddingThickness);
-        Assert.Equal (new (0, 0, 8, 8), view.Bounds);
+        Assert.Equal (new (0, 0, 8, 8), view.Viewport);
 
 
-        Assert.Equal (new (0, 0, view.Margin.Frame.Width, view.Margin.Frame.Height), view.Margin.Bounds);
+        Assert.Equal (new (0, 0, view.Margin.Frame.Width, view.Margin.Frame.Height), view.Margin.Viewport);
 
 
-        Assert.Equal (new (0, 0, view.Border.Frame.Width, view.Border.Frame.Height), view.Border.Bounds);
+        Assert.Equal (new (0, 0, view.Border.Frame.Width, view.Border.Frame.Height), view.Border.Viewport);
 
 
-        Assert.Equal (new (0, 0, view.Padding.Frame.Width , view.Padding.Frame.Height), view.Padding.Bounds);
+        Assert.Equal (new (0, 0, view.Padding.Frame.Width, view.Padding.Frame.Height), view.Padding.Viewport);
     }
     }
 
 
-    // Test that Adornment.Bounds_get override returns Frame.Size minus Thickness
+    // Test that Adornment.Viewport_get override returns Frame.Size minus Thickness
     [Theory]
     [Theory]
     [InlineData (0, 0, 0, 0, 0)]
     [InlineData (0, 0, 0, 0, 0)]
     [InlineData (0, 0, 0, 1, 1)]
     [InlineData (0, 0, 0, 1, 1)]
@@ -79,7 +79,7 @@ public class AdornmentTests (ITestOutputHelper output)
     [InlineData (1, 1, 1, 4, 4)]
     [InlineData (1, 1, 1, 4, 4)]
     [InlineData (1, 1, 1, 4, 0)]
     [InlineData (1, 1, 1, 4, 0)]
     [InlineData (1, 1, 1, 0, 4)]
     [InlineData (1, 1, 1, 0, 4)]
-    public void Bounds_Width_Is_Frame_Width (int thickness, int x, int y, int w, int h)
+    public void Viewport_Width_Is_Frame_Width (int thickness, int x, int y, int w, int h)
     {
     {
         var adornment = new Adornment (null);
         var adornment = new Adornment (null);
         adornment.Thickness = new Thickness (thickness);
         adornment.Thickness = new Thickness (thickness);
@@ -87,10 +87,10 @@ public class AdornmentTests (ITestOutputHelper output)
         Assert.Equal (new Rectangle (x, y, w, h), adornment.Frame);
         Assert.Equal (new Rectangle (x, y, w, h), adornment.Frame);
 
 
         var expectedBounds = new Rectangle (0, 0, w, h);
         var expectedBounds = new Rectangle (0, 0, w, h);
-        Assert.Equal (expectedBounds, adornment.Bounds);
+        Assert.Equal (expectedBounds, adornment.Viewport);
     }
     }
 
 
-    // Test that Adornment.Bounds_get override uses Parent not SuperView
+    // Test that Adornment.Viewport_get override uses Parent not SuperView
     [Fact]
     [Fact]
     public void BoundsToScreen_Uses_Parent_Not_SuperView ()
     public void BoundsToScreen_Uses_Parent_Not_SuperView ()
     {
     {
@@ -100,12 +100,12 @@ public class AdornmentTests (ITestOutputHelper output)
         parent.EndInit ();
         parent.EndInit ();
 
 
         Assert.Equal (new Rectangle (1, 2, 10, 10), parent.Frame);
         Assert.Equal (new Rectangle (1, 2, 10, 10), parent.Frame);
-        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Bounds);
+        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Viewport);
         Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Margin.Frame);
         Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Margin.Frame);
-        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Margin.Bounds);
+        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Margin.Viewport);
 
 
         Assert.Null (parent.Margin.SuperView);
         Assert.Null (parent.Margin.SuperView);
-        Rectangle boundsAsScreen = parent.Margin.BoundsToScreen (new Rectangle (1, 2, 5, 5));
+        Rectangle boundsAsScreen = parent.Margin.ViewportToScreen (new Rectangle (1, 2, 5, 5));
         Assert.Equal (new Rectangle (2, 4, 5, 5), boundsAsScreen);
         Assert.Equal (new Rectangle (2, 4, 5, 5), boundsAsScreen);
     }
     }
 
 
@@ -133,7 +133,7 @@ public class AdornmentTests (ITestOutputHelper output)
         view.EndInit ();
         view.EndInit ();
 
 
         Assert.Equal (new Rectangle (1, 2, 20, 31), view.Frame);
         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, 8, 19), view.Viewport);
 
 
         // Margin.Frame is always the same as the view frame
         // Margin.Frame is always the same as the view frame
         Assert.Equal (new Rectangle (0, 0, 20, 31), view.Margin.Frame);
         Assert.Equal (new Rectangle (0, 0, 20, 31), view.Margin.Frame);
@@ -246,9 +246,9 @@ public class AdornmentTests (ITestOutputHelper output)
         parent.EndInit ();
         parent.EndInit ();
 
 
         Assert.Equal (new Rectangle (1, 2, 10, 10), parent.Frame);
         Assert.Equal (new Rectangle (1, 2, 10, 10), parent.Frame);
-        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Bounds);
+        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Viewport);
         Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Margin.Frame);
         Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Margin.Frame);
-        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Margin.Bounds);
+        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Margin.Viewport);
 
 
         Assert.Null (parent.Margin.SuperView);
         Assert.Null (parent.Margin.SuperView);
         Assert.Equal (new Rectangle (1, 2, 10, 10), parent.Margin.FrameToScreen ());
         Assert.Equal (new Rectangle (1, 2, 10, 10), parent.Margin.FrameToScreen ());
@@ -281,24 +281,24 @@ public class AdornmentTests (ITestOutputHelper output)
     }
     }
 
 
     [Fact]
     [Fact]
-    public void Setting_Bounds_Throws ()
+    public void Setting_Viewport_Throws ()
     {
     {
         var adornment = new Adornment (null);
         var adornment = new Adornment (null);
-        Assert.Throws<InvalidOperationException> (() => adornment.Bounds = new Rectangle (1, 2, 3, 4));
+        Assert.Throws<InvalidOperationException> (() => adornment.Viewport = new Rectangle (1, 2, 3, 4));
     }
     }
 
 
     [Fact]
     [Fact]
     public void Setting_SuperView_Throws ()
     public void Setting_SuperView_Throws ()
     {
     {
         var adornment = new Adornment (null);
         var adornment = new Adornment (null);
-        Assert.Throws<NotImplementedException> (() => adornment.SuperView = new View ());
+        Assert.Throws<InvalidOperationException> (() => adornment.SuperView = new View ());
     }
     }
 
 
     [Fact]
     [Fact]
     public void Setting_SuperViewRendersLineCanvas_Throws ()
     public void Setting_SuperViewRendersLineCanvas_Throws ()
     {
     {
         var adornment = new Adornment (null);
         var adornment = new Adornment (null);
-        Assert.Throws<NotImplementedException> (() => adornment.SuperViewRendersLineCanvas = true);
+        Assert.Throws<InvalidOperationException> (() => adornment.SuperViewRendersLineCanvas = true);
     }
     }
 
 
     [Fact]
     [Fact]
@@ -309,11 +309,11 @@ public class AdornmentTests (ITestOutputHelper output)
         parent.EndInit ();
         parent.EndInit ();
 
 
         Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Frame);
         Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Frame);
-        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Bounds);
+        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Viewport);
 
 
         parent.Margin.Thickness = new Thickness (1);
         parent.Margin.Thickness = new Thickness (1);
         Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Frame);
         Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Frame);
-        Assert.Equal (new Rectangle (0, 0, 8, 8), parent.Bounds);
+        Assert.Equal (new Rectangle (0, 0, 8, 8), parent.Viewport);
     }
     }
 
 
     [Fact]
     [Fact]
@@ -340,8 +340,8 @@ public class AdornmentTests (ITestOutputHelper output)
     {
     {
         var view = new View ();
         var view = new View ();
         var raised = false;
         var raised = false;
-        view.BeginInit();
-        view.EndInit();
+        view.BeginInit ();
+        view.EndInit ();
 
 
         view.LayoutStarted += LayoutStarted;
         view.LayoutStarted += LayoutStarted;
         view.Margin.Thickness = new Thickness (1, 2, 3, 4);
         view.Margin.Thickness = new Thickness (1, 2, 3, 4);
@@ -373,4 +373,15 @@ public class AdornmentTests (ITestOutputHelper output)
             raised = true;
             raised = true;
         }
         }
     }
     }
+
+    [Fact]
+    public void Set_Viewport_Throws ()
+    {
+        View view = new ();
+
+        view.BeginInit ();
+        view.EndInit ();
+        view.Padding.Thickness = new (2, 2, 2, 2);
+        Assert.Throws<InvalidOperationException> (() => view.Padding.Viewport = view.Padding.Viewport with { Location = new (1, 1) });
+    }
 }
 }

+ 9 - 3
UnitTests/View/Adornment/BorderTests.cs

@@ -11,7 +11,13 @@ public class BorderTests
     [SetupFakeDriver]
     [SetupFakeDriver]
     public void Border_Parent_HasFocus_Title_Uses_FocusAttribute ()
     public void Border_Parent_HasFocus_Title_Uses_FocusAttribute ()
     {
     {
+        var superView = new View { Width = 10, Height = 10, CanFocus = true };
+        var otherView = new View { Width = 0, Height = 0, CanFocus = true };
+        superView.Add (otherView);
+
         var view = new View { Title = "A", Height = 2, Width = 5 };
         var view = new View { Title = "A", Height = 2, Width = 5 };
+        superView.Add (view);
+
         view.Border.Thickness = new Thickness (0, 1, 0, 0);
         view.Border.Thickness = new Thickness (0, 1, 0, 0);
         view.Border.LineStyle = LineStyle.Single;
         view.Border.LineStyle = LineStyle.Single;
 
 
@@ -25,9 +31,9 @@ public class BorderTests
         Assert.Equal (ColorName.Green, view.Border.GetFocusColor ().Foreground.GetClosestNamedColor ());
         Assert.Equal (ColorName.Green, view.Border.GetFocusColor ().Foreground.GetClosestNamedColor ());
         Assert.Equal (view.GetFocusColor (), view.Border.GetFocusColor ());
         Assert.Equal (view.GetFocusColor (), view.Border.GetFocusColor ());
 
 
-        view.BeginInit ();
-        view.EndInit ();
-        view.Draw ();
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.Draw ();
 
 
         var expected = @"─┤A├─";
         var expected = @"─┤A├─";
         TestHelpers.AssertDriverContentsAre (expected, _output);
         TestHelpers.AssertDriverContentsAre (expected, _output);

+ 28 - 29
UnitTests/View/Adornment/ToScreenTests.cs

@@ -3,7 +3,7 @@
 namespace Terminal.Gui.ViewTests;
 namespace Terminal.Gui.ViewTests;
 
 
 /// <summary>
 /// <summary>
-/// Test the <see cref="Adornment.FrameToScreen"/> and <see cref="Adornment.BoundsToScreen"/> methods.
+/// Test the <see cref="Adornment.FrameToScreen"/> and <see cref="View.ViewportToScreen"/> methods.
 /// DOES NOT TEST View.xxxToScreen methods. Those are in ./View/Layout/ToScreenTests.cs
 /// DOES NOT TEST View.xxxToScreen methods. Those are in ./View/Layout/ToScreenTests.cs
 /// </summary>
 /// </summary>
 /// <param name="output"></param>
 /// <param name="output"></param>
@@ -234,14 +234,14 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         Assert.Equal (expectedX + view.Margin.Thickness.Left + view.Border.Thickness.Left, paddingScreen.X);
         Assert.Equal (expectedX + view.Margin.Thickness.Left + view.Border.Thickness.Left, paddingScreen.X);
     }
     }
 
 
-
+    // Adornment.ViewportToScreen tests ============================
 
 
     [Theory]
     [Theory]
     [InlineData (0, 0, 0)]
     [InlineData (0, 0, 0)]
     [InlineData (1, 0, 1)]
     [InlineData (1, 0, 1)]
     [InlineData (-1, 0, -1)]
     [InlineData (-1, 0, -1)]
     [InlineData (11, 0, 11)]
     [InlineData (11, 0, 11)]
-    public void BoundsToScreen_NoSuperView_WithoutAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_NoSuperView_WithoutAdornments (int frameX, int testX, int expectedX)
     {
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
         // Arrange
@@ -251,9 +251,9 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         view.Frame = frame;
         view.Frame = frame;
 
 
         // Act
         // Act
-        var marginScreen = view.Margin.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var borderScreen = view.Border.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var paddingScreen = view.Padding.BoundsToScreen (new (boundsX, 0, 0, 0));
+        var marginScreen = view.Margin.ViewportToScreen (new (testX, 0, 0, 0));
+        var borderScreen = view.Border.ViewportToScreen (new (testX, 0, 0, 0));
+        var paddingScreen = view.Padding.ViewportToScreen (new (testX, 0, 0, 0));
 
 
         // Assert
         // Assert
         Assert.Equal (expectedX, marginScreen.X);
         Assert.Equal (expectedX, marginScreen.X);
@@ -276,7 +276,7 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 0)]
     [InlineData (1, -1, 0)]
     [InlineData (-1, -1, -2)]
     [InlineData (-1, -1, -2)]
     [InlineData (11, -1, 10)]
     [InlineData (11, -1, 10)]
-    public void BoundsToScreen_NoSuperView_WithAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_NoSuperView_WithAdornments (int frameX, int testX, int expectedX)
     {
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
         // Arrange
@@ -286,15 +286,15 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         view.Margin.Thickness = new (1);
         view.Margin.Thickness = new (1);
         view.Border.Thickness = new (1);
         view.Border.Thickness = new (1);
         view.Padding.Thickness = new (1);
         view.Padding.Thickness = new (1);
-        // Total thickness is 3 (view.Bounds will be Frame.Width - 6)
+        // Total thickness is 3 (view.Viewport will be Frame.Width - 6)
         view.Frame = frame;
         view.Frame = frame;
 
 
-        Assert.Equal(4, view.Bounds.Width);
+        Assert.Equal(4, view.Viewport.Width);
 
 
         // Act
         // Act
-        var marginScreen = view.Margin.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var borderScreen = view.Border.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var paddingScreen = view.Padding.BoundsToScreen (new (boundsX, 0, 0, 0));
+        var marginScreen = view.Margin.ViewportToScreen (new (testX, 0, 0, 0));
+        var borderScreen = view.Border.ViewportToScreen (new (testX, 0, 0, 0));
+        var paddingScreen = view.Padding.ViewportToScreen (new (testX, 0, 0, 0));
 
 
         // Assert
         // Assert
         Assert.Equal (expectedX, marginScreen.X);
         Assert.Equal (expectedX, marginScreen.X);
@@ -317,7 +317,7 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 0)]
     [InlineData (1, -1, 0)]
     [InlineData (-1, -1, -2)]
     [InlineData (-1, -1, -2)]
     [InlineData (11, -1, 10)]
     [InlineData (11, -1, 10)]
-    public void BoundsToScreen_SuperView_WithoutAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_SuperView_WithoutAdornments (int frameX, int testX, int expectedX)
     {
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
         // Arrange
@@ -338,9 +338,9 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
         superView.LayoutSubviews ();
 
 
         // Act
         // Act
-        var marginScreen = view.Margin.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var borderScreen = view.Border.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var paddingScreen = view.Padding.BoundsToScreen (new (boundsX, 0, 0, 0));
+        var marginScreen = view.Margin.ViewportToScreen (new (testX, 0, 0, 0));
+        var borderScreen = view.Border.ViewportToScreen (new (testX, 0, 0, 0));
+        var paddingScreen = view.Padding.ViewportToScreen (new (testX, 0, 0, 0));
 
 
         // Assert
         // Assert
         Assert.Equal (expectedX, marginScreen.X);
         Assert.Equal (expectedX, marginScreen.X);
@@ -363,7 +363,7 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 1)]
     [InlineData (1, -1, 1)]
     [InlineData (-1, -1, -1)]
     [InlineData (-1, -1, -1)]
     [InlineData (11, -1, 11)]
     [InlineData (11, -1, 11)]
-    public void BoundsToScreen_SuperView_WithAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_SuperView_WithAdornments (int frameX, int testX, int expectedX)
     {
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
         // Arrange
@@ -385,9 +385,9 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
         superView.LayoutSubviews ();
 
 
         // Act
         // Act
-        var marginScreen = view.Margin.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var borderScreen = view.Border.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var paddingScreen = view.Padding.BoundsToScreen (new (boundsX, 0, 0, 0));
+        var marginScreen = view.Margin.ViewportToScreen (new (testX, 0, 0, 0));
+        var borderScreen = view.Border.ViewportToScreen (new (testX, 0, 0, 0));
+        var paddingScreen = view.Padding.ViewportToScreen (new (testX, 0, 0, 0));
 
 
         // Assert
         // Assert
         Assert.Equal (expectedX, marginScreen.X);
         Assert.Equal (expectedX, marginScreen.X);
@@ -410,7 +410,7 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 0)]
     [InlineData (1, -1, 0)]
     [InlineData (-1, -1, -2)]
     [InlineData (-1, -1, -2)]
     [InlineData (11, -1, 10)]
     [InlineData (11, -1, 10)]
-    public void BoundsToScreen_NestedSuperView_WithoutAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_NestedSuperView_WithoutAdornments (int frameX, int testX, int expectedX)
     {
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
         // Arrange
@@ -441,9 +441,9 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
         superView.LayoutSubviews ();
 
 
         // Act
         // Act
-        var marginScreen = view.Margin.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var borderScreen = view.Border.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var paddingScreen = view.Padding.BoundsToScreen (new (boundsX, 0, 0, 0));
+        var marginScreen = view.Margin.ViewportToScreen (new (testX, 0, 0, 0));
+        var borderScreen = view.Border.ViewportToScreen (new (testX, 0, 0, 0));
+        var paddingScreen = view.Padding.ViewportToScreen (new (testX, 0, 0, 0));
 
 
         // Assert
         // Assert
         Assert.Equal (expectedX, marginScreen.X);
         Assert.Equal (expectedX, marginScreen.X);
@@ -466,7 +466,7 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 6)]
     [InlineData (1, -1, 6)]
     [InlineData (-1, -1, 4)]
     [InlineData (-1, -1, 4)]
     [InlineData (11, -1, 16)]
     [InlineData (11, -1, 16)]
-    public void BoundsToScreen_NestedSuperView_WithAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_NestedSuperView_WithAdornments (int frameX, int testX, int expectedX)
     {
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
         // Arrange
@@ -502,14 +502,13 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
         superView.LayoutSubviews ();
 
 
         // Act
         // Act
-        var marginScreen = view.Margin.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var borderScreen = view.Border.BoundsToScreen (new (boundsX, 0, 0, 0));
-        var paddingScreen = view.Padding.BoundsToScreen (new (boundsX, 0, 0, 0));
+        var marginScreen = view.Margin.ViewportToScreen (new (testX, 0, 0, 0));
+        var borderScreen = view.Border.ViewportToScreen (new (testX, 0, 0, 0));
+        var paddingScreen = view.Padding.ViewportToScreen (new (testX, 0, 0, 0));
 
 
         // Assert
         // Assert
         Assert.Equal (expectedX, marginScreen.X);
         Assert.Equal (expectedX, marginScreen.X);
         Assert.Equal (expectedX + view.Margin.Thickness.Left, borderScreen.X);
         Assert.Equal (expectedX + view.Margin.Thickness.Left, borderScreen.X);
         Assert.Equal (expectedX + view.Margin.Thickness.Left + view.Border.Thickness.Left, paddingScreen.X);
         Assert.Equal (expectedX + view.Margin.Thickness.Left + view.Border.Thickness.Left, paddingScreen.X);
     }
     }
-
 }
 }

+ 316 - 109
UnitTests/View/DrawTests.cs

@@ -1,21 +1,74 @@
 #nullable enable
 #nullable enable
 using System.Text;
 using System.Text;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
+using static System.Net.Mime.MediaTypeNames;
 
 
 namespace Terminal.Gui.ViewTests;
 namespace Terminal.Gui.ViewTests;
 
 
-[Trait("Category","Output")]
-public class DrawTests
+[Trait ("Category", "Output")]
+public class DrawTests (ITestOutputHelper output)
 {
 {
-    private readonly ITestOutputHelper _output;
-    public DrawTests (ITestOutputHelper output) { _output = output; }
+    [Fact]
+    [SetupFakeDriver]
+    public void Move_Is_Constrained_To_Viewport ()
+    {
+        var view = new View
+        {
+            X = 1,
+            Y = 1,
+            Width = 3, Height = 3
+        };
+        view.Margin.Thickness = new Thickness (1);
+
+        // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen)
+
+        view.Move (0, 0);
+        Assert.Equal (new Point (2, 2), new Point (Application.Driver.Col, Application.Driver.Row));
+
+        view.Move (-1, -1);
+        Assert.Equal (new Point (2, 2), new Point (Application.Driver.Col, Application.Driver.Row));
+
+        view.Move (1, 1);
+        Assert.Equal (new Point (2, 2), new Point (Application.Driver.Col, Application.Driver.Row));
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void AddRune_Is_Constrained_To_Viewport ()
+    {
+        var view = new View
+        {
+            X = 1,
+            Y = 1,
+            Width = 3, Height = 3
+        };
+        view.Margin.Thickness = new Thickness (1);
+        View.Diagnostics = ViewDiagnosticFlags.Padding;
+        view.BeginInit ();
+        view.EndInit ();
+        view.Draw ();
+
+        // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen)
+        Assert.Equal ((Rune)' ', Application.Driver.Contents [2, 2].Rune);
+
+        view.AddRune (0, 0, Rune.ReplacementChar);
+        Assert.Equal (Rune.ReplacementChar, Application.Driver.Contents [2, 2].Rune);
+
+        view.AddRune (-1, -1, Rune.ReplacementChar);
+        Assert.Equal ((Rune)'M', Application.Driver.Contents [1, 1].Rune);
+
+        view.AddRune (1, 1, Rune.ReplacementChar);
+        Assert.Equal ((Rune)'M', Application.Driver.Contents [3, 3].Rune);
+
+        View.Diagnostics = ViewDiagnosticFlags.Off;
+    }
 
 
     [Theory]
     [Theory]
     [InlineData (0, 0, 1, 1)]
     [InlineData (0, 0, 1, 1)]
     [InlineData (0, 0, 2, 2)]
     [InlineData (0, 0, 2, 2)]
     [InlineData (-1, -1, 2, 2)]
     [InlineData (-1, -1, 2, 2)]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Clear_Bounds_Clears_Only_Bounds (int x, int y, int width, int height)
+    public void FillRect_Fills_HonorsClip (int x, int y, int width, int height)
     {
     {
         var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
         var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
 
 
@@ -32,28 +85,158 @@ public class DrawTests
         superView.LayoutSubviews ();
         superView.LayoutSubviews ();
 
 
         superView.Draw ();
         superView.Draw ();
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      output);
+
+        Rectangle toFill = new (x, y, width, height);
+        view.FillRect (toFill);
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │ │
+ └─┘",
+                                                      output);
 
 
+        // Now try to clear beyond Viewport (invalid; clipping should prevent)
+        superView.SetNeedsDisplay ();
+        superView.Draw ();
         TestHelpers.AssertDriverContentsWithFrameAre (
         TestHelpers.AssertDriverContentsWithFrameAre (
                                                       @"
                                                       @"
  ┌─┐
  ┌─┐
  │X│
  │X│
  └─┘",
  └─┘",
-                                                      _output);
+                                                      output);
+        toFill = new (-width, -height, width, height);
+        view.FillRect (toFill);
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      output);
 
 
-        Rectangle boundsToClear = new (x, y, width, height);
-        view.Clear (boundsToClear);
+        // Now try to clear beyond Viewport (valid)
+        superView.SetNeedsDisplay ();
+        superView.Draw ();
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      output);
+        toFill = new (-1, -1, width + 1, height + 1);
+        view.FillRect (toFill);
         TestHelpers.AssertDriverContentsWithFrameAre (
         TestHelpers.AssertDriverContentsWithFrameAre (
                                                       @"
                                                       @"
  ┌─┐
  ┌─┐
  │ │
  │ │
  └─┘",
  └─┘",
-                                                      _output);
+                                                      output);
 
 
+        // Now clear too much size
+        superView.SetNeedsDisplay ();
+        superView.Draw ();
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      output);
+        toFill = new (0, 0, width * 2, height * 2);
+        view.FillRect (toFill);
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │ │
+ └─┘",
+                                                      output);
     }
     }
 
 
+    [Theory]
+    [InlineData (0, 0, 1, 1)]
+    [InlineData (0, 0, 2, 2)]
+    [InlineData (-1, -1, 2, 2)]
+    [SetupFakeDriver]
+    public void Clear_ClearsEntireViewport (int x, int y, int width, int height)
+    {
+        var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        var view = new View
+        {
+            Text = "X",
+            X = 1, Y = 1,
+            Width = 3, Height = 3,
+            BorderStyle = LineStyle.Single
+        };
+        superView.Add (view);
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.LayoutSubviews ();
+
+        superView.Draw ();
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      output);
+
+        view.Clear ();
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │ │
+ └─┘",
+                                                      output);
+    }
+
+    [Theory]
+    [InlineData (0, 0, 1, 1)]
+    [InlineData (0, 0, 2, 2)]
+    [InlineData (-1, -1, 2, 2)]
+    [SetupFakeDriver]
+    public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly (int x, int y, int width, int height)
+    {
+        var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        var view = new View
+        {
+            Text = "X",
+            X = 1, Y = 1,
+            Width = 3, Height = 3,
+            BorderStyle = LineStyle.Single,
+            ViewportSettings = ViewportSettings.ClearContentOnly
+        };
+        superView.Add (view);
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.LayoutSubviews ();
+
+        superView.Draw ();
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      output);
+
+        view.Clear ();
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │ │
+ └─┘",
+                                                      output);
+    }
+
+
     [Fact]
     [Fact]
     [AutoInitShutdown]
     [AutoInitShutdown]
-    [Trait("Category","Unicode")]
+    [Trait ("Category", "Unicode")]
     public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
     public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
     {
     {
         const string us = "\U0000f900";
         const string us = "\U0000f900";
@@ -67,7 +250,7 @@ public class DrawTests
         Assert.Equal (2, r.GetColumns ());
         Assert.Equal (2, r.GetColumns ());
 
 
         var win = new Window { Title = us };
         var win = new Window { Title = us };
-        var view = new View { Text = r.ToString (), Height = Dim.Fill (), Width = Dim.Fill () };
+        var view = new View { Text = r.ToString (), Height = Dim.Fill (), Width = Dim.Fill ()};
         var tf = new TextField { Text = us, Y = 1, Width = 3 };
         var tf = new TextField { Text = us, Y = 1, Width = 3 };
         win.Add (view, tf);
         win.Add (view, tf);
         Toplevel top = new ();
         Toplevel top = new ();
@@ -83,39 +266,17 @@ public class DrawTests
                                       │豈      │
                                       │豈      │
                                       └────────┘
                                       └────────┘
                                       """;
                                       """;
-        TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
-
-        TestHelpers.AssertDriverContentsAre (expectedOutput, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, output);
 
 
-        Attribute [] expectedColors =
-        {
-            // 0
-            Colors.ColorSchemes ["Base"].Normal,
+        TestHelpers.AssertDriverContentsAre (expectedOutput, output);
 
 
-            // 1
-            Colors.ColorSchemes ["Base"].Focus,
-
-            // 2
-            Colors.ColorSchemes ["Base"].HotNormal
-        };
-
-        TestHelpers.AssertDriverAttributesAre (
-                                               """
-
-                                               0011000000
-                                               0000000000
-                                               0111000000
-                                               0000000000
-                                               """,
-                                               Application.Driver,
-                                               expectedColors
-                                              );
+        // This test has nothing to do with color - removing as it is not relevant and fragile
     }
     }
 
 
     // TODO: Refactor this test to not depend on TextView etc... Make it as primitive as possible
     // TODO: Refactor this test to not depend on TextView etc... Make it as primitive as possible
     [Fact]
     [Fact]
     [AutoInitShutdown]
     [AutoInitShutdown]
-    [Trait("Category","Unicode")]
+    [Trait ("Category", "Unicode")]
     public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
     public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
     {
     {
         var tv = new TextView
         var tv = new TextView
@@ -162,7 +323,7 @@ public class DrawTests
                                       └────────────────────────────┘
                                       └────────────────────────────┘
                                       """;
                                       """;
 
 
-        Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
+        Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, output);
         Assert.Equal (new Rectangle (0, 0, 30, 10), pos);
         Assert.Equal (new Rectangle (0, 0, 30, 10), pos);
 
 
         Application.End (rsDiag);
         Application.End (rsDiag);
@@ -171,7 +332,7 @@ public class DrawTests
 
 
     [Fact]
     [Fact]
     [AutoInitShutdown]
     [AutoInitShutdown]
-    [Trait("Category","Output")]
+    [Trait ("Category", "Output")]
     public void Colors_On_TextAlignment_Right_And_Bottom ()
     public void Colors_On_TextAlignment_Right_And_Bottom ()
     {
     {
         var viewRight = new View
         var viewRight = new View
@@ -210,7 +371,7 @@ public class DrawTests
                                                       s     
                                                       s     
                                                       t     
                                                       t     
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         TestHelpers.AssertDriverAttributesAre (
         TestHelpers.AssertDriverAttributesAre (
@@ -231,15 +392,15 @@ public class DrawTests
 
 
     [Fact]
     [Fact]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Draw_Minimum_Full_Border_With_Empty_Bounds ()
+    public void Draw_Minimum_Full_Border_With_Empty_Viewport ()
     {
     {
         var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single };
         var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single };
         view.BeginInit ();
         view.BeginInit ();
         view.EndInit ();
         view.EndInit ();
-        view.SetRelativeLayout (Application.Driver.Bounds);
+        view.SetRelativeLayout (Application.Driver.Screen.Size);
 
 
-        Assert.Equal (new (0,0,2,2), view.Frame);
-        Assert.Equal (Rectangle.Empty, view.Bounds);
+        Assert.Equal (new (0, 0, 2, 2), view.Frame);
+        Assert.Equal (Rectangle.Empty, view.Viewport);
 
 
         view.Draw ();
         view.Draw ();
 
 
@@ -249,40 +410,40 @@ public class DrawTests
                                                       ┌┐
                                                       ┌┐
                                                       └┘
                                                       └┘
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
     }
     }
 
 
     [Fact]
     [Fact]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Draw_Minimum_Full_Border_With_Empty_Bounds_Without_Bottom ()
+    public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Bottom ()
     {
     {
         var view = new View { Width = 2, Height = 1, BorderStyle = LineStyle.Single };
         var view = new View { Width = 2, Height = 1, BorderStyle = LineStyle.Single };
         view.Border.Thickness = new Thickness (1, 1, 1, 0);
         view.Border.Thickness = new Thickness (1, 1, 1, 0);
         view.BeginInit ();
         view.BeginInit ();
         view.EndInit ();
         view.EndInit ();
-        view.SetRelativeLayout (Application.Driver.Bounds);
+        view.SetRelativeLayout (Application.Driver.Screen.Size);
 
 
-        Assert.Equal (new (0,0,2,1), view.Frame);
-        Assert.Equal (Rectangle.Empty, view.Bounds);
+        Assert.Equal (new (0, 0, 2, 1), view.Frame);
+        Assert.Equal (Rectangle.Empty, view.Viewport);
 
 
         view.Draw ();
         view.Draw ();
 
 
-        TestHelpers.AssertDriverContentsWithFrameAre (string.Empty, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (string.Empty, output);
     }
     }
 
 
     [Fact]
     [Fact]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Draw_Minimum_Full_Border_With_Empty_Bounds_Without_Left ()
+    public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left ()
     {
     {
         var view = new View { Width = 1, Height = 2, BorderStyle = LineStyle.Single };
         var view = new View { Width = 1, Height = 2, BorderStyle = LineStyle.Single };
         view.Border.Thickness = new Thickness (0, 1, 1, 1);
         view.Border.Thickness = new Thickness (0, 1, 1, 1);
         view.BeginInit ();
         view.BeginInit ();
         view.EndInit ();
         view.EndInit ();
-        view.SetRelativeLayout (Application.Driver.Bounds);
+        view.SetRelativeLayout (Application.Driver.Screen.Size);
 
 
-        Assert.Equal (new (0,0,1,2), view.Frame);
-        Assert.Equal (Rectangle.Empty, view.Bounds);
+        Assert.Equal (new (0, 0, 1, 2), view.Frame);
+        Assert.Equal (Rectangle.Empty, view.Viewport);
 
 
         view.Draw ();
         view.Draw ();
 
 
@@ -292,22 +453,22 @@ public class DrawTests
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
     }
     }
 
 
     [Fact]
     [Fact]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Draw_Minimum_Full_Border_With_Empty_Bounds_Without_Right ()
+    public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right ()
     {
     {
         var view = new View { Width = 1, Height = 2, BorderStyle = LineStyle.Single };
         var view = new View { Width = 1, Height = 2, BorderStyle = LineStyle.Single };
         view.Border.Thickness = new Thickness (1, 1, 0, 1);
         view.Border.Thickness = new Thickness (1, 1, 0, 1);
         view.BeginInit ();
         view.BeginInit ();
         view.EndInit ();
         view.EndInit ();
-        view.SetRelativeLayout (Application.Driver.Bounds);
+        view.SetRelativeLayout (Application.Driver.Screen.Size);
 
 
-        Assert.Equal (new (0,0,1,2), view.Frame);
-        Assert.Equal (Rectangle.Empty, view.Bounds);
+        Assert.Equal (new (0, 0, 1, 2), view.Frame);
+        Assert.Equal (Rectangle.Empty, view.Viewport);
 
 
         view.Draw ();
         view.Draw ();
 
 
@@ -317,23 +478,23 @@ public class DrawTests
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
     }
     }
 
 
     [Fact]
     [Fact]
     [SetupFakeDriver]
     [SetupFakeDriver]
-    public void Draw_Minimum_Full_Border_With_Empty_Bounds_Without_Top ()
+    public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top ()
     {
     {
         var view = new View { Width = 2, Height = 1, BorderStyle = LineStyle.Single };
         var view = new View { Width = 2, Height = 1, BorderStyle = LineStyle.Single };
         view.Border.Thickness = new Thickness (1, 0, 1, 1);
         view.Border.Thickness = new Thickness (1, 0, 1, 1);
 
 
         view.BeginInit ();
         view.BeginInit ();
         view.EndInit ();
         view.EndInit ();
-        view.SetRelativeLayout (Application.Driver.Bounds);
+        view.SetRelativeLayout (Application.Driver.Screen.Size);
 
 
-        Assert.Equal (new (0,0,2,1), view.Frame);
-        Assert.Equal (Rectangle.Empty, view.Bounds);
+        Assert.Equal (new (0, 0, 2, 1), view.Frame);
+        Assert.Equal (Rectangle.Empty, view.Viewport);
 
 
         view.Draw ();
         view.Draw ();
 
 
@@ -343,13 +504,13 @@ public class DrawTests
 
 
                                                       ┌┐
                                                       ┌┐
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
     }
     }
 
 
     [Fact]
     [Fact]
     [AutoInitShutdown]
     [AutoInitShutdown]
-    public void Draw_Negative_Bounds_Horizontal_With_New_Lines ()
+    public void Draw_Negative_Viewport_Horizontal_With_New_Lines ()
     {
     {
         var subView = new View
         var subView = new View
         {
         {
@@ -420,7 +581,7 @@ public class DrawTests
                                                        3V
                                                        3V
                                                        4i
                                                        4i
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.X = -1;
         content.X = -1;
@@ -435,12 +596,12 @@ public class DrawTests
                                                        V
                                                        V
                                                        i
                                                        i
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.X = -2;
         content.X = -2;
         Application.Refresh ();
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre (@"", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (@"", output);
 
 
         content.X = 0;
         content.X = 0;
         content.Y = -1;
         content.Y = -1;
@@ -455,7 +616,7 @@ public class DrawTests
                                                        4i
                                                        4i
                                                        5e
                                                        5e
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.Y = -6;
         content.Y = -6;
@@ -470,7 +631,7 @@ public class DrawTests
                                                        9 
                                                        9 
                                                        0 
                                                        0 
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.Y = -19;
         content.Y = -19;
@@ -481,22 +642,22 @@ public class DrawTests
 
 
                                                        9
                                                        9
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.Y = -20;
         content.Y = -20;
         Application.Refresh ();
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
 
 
         content.X = -2;
         content.X = -2;
         content.Y = 0;
         content.Y = 0;
         Application.Refresh ();
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
     }
     }
 
 
     [Fact]
     [Fact]
     [AutoInitShutdown]
     [AutoInitShutdown]
-    public void Draw_Negative_Bounds_Horizontal_Without_New_Lines ()
+    public void Draw_Negative_Viewport_Horizontal_Without_New_Lines ()
     {
     {
         // BUGBUG: This previously assumed the default height of a View was 1.
         // BUGBUG: This previously assumed the default height of a View was 1.
         var subView = new View
         var subView = new View
@@ -537,7 +698,7 @@ public class DrawTests
                                                        01234
                                                        01234
                                                        subVi
                                                        subVi
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.X = -1;
         content.X = -1;
@@ -549,7 +710,7 @@ public class DrawTests
                                                        12345
                                                        12345
                                                        ubVie
                                                        ubVie
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.Y = -1;
         content.Y = -1;
@@ -560,22 +721,22 @@ public class DrawTests
 
 
                                                        ubVie
                                                        ubVie
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.Y = -2;
         content.Y = -2;
         Application.Refresh ();
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
 
 
         content.X = -20;
         content.X = -20;
         content.Y = 0;
         content.Y = 0;
         Application.Refresh ();
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
     }
     }
 
 
     [Fact]
     [Fact]
     [AutoInitShutdown]
     [AutoInitShutdown]
-    public void Draw_Negative_Bounds_Vertical ()
+    public void Draw_Negative_Viewport_Vertical ()
     {
     {
         var subView = new View
         var subView = new View
         {
         {
@@ -622,7 +783,7 @@ public class DrawTests
                                                        3V
                                                        3V
                                                        4i
                                                        4i
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.X = -1;
         content.X = -1;
@@ -637,12 +798,12 @@ public class DrawTests
                                                        V
                                                        V
                                                        i
                                                        i
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.X = -2;
         content.X = -2;
         Application.Refresh ();
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre (@"", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (@"", output);
 
 
         content.X = 0;
         content.X = 0;
         content.Y = -1;
         content.Y = -1;
@@ -657,7 +818,7 @@ public class DrawTests
                                                        4i
                                                        4i
                                                        5e
                                                        5e
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.Y = -6;
         content.Y = -6;
@@ -672,7 +833,7 @@ public class DrawTests
                                                        9 
                                                        9 
                                                        0 
                                                        0 
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.Y = -19;
         content.Y = -19;
@@ -683,17 +844,17 @@ public class DrawTests
 
 
                                                        9
                                                        9
                                                       """,
                                                       """,
-                                                      _output
+                                                      output
                                                      );
                                                      );
 
 
         content.Y = -20;
         content.Y = -20;
         Application.Refresh ();
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
 
 
         content.X = -2;
         content.X = -2;
         content.Y = 0;
         content.Y = 0;
         Application.Refresh ();
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
     }
     }
 
 
     [Theory]
     [Theory]
@@ -705,7 +866,7 @@ public class DrawTests
         var view = new View { Width = 10, Height = 1 };
         var view = new View { Width = 10, Height = 1 };
         view.DrawHotString (expected, Attribute.Default, Attribute.Default);
         view.DrawHotString (expected, Attribute.Default, Attribute.Default);
 
 
-        TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
     }
     }
 
 
     // TODO: The tests below that use Label should use View instead.
     // TODO: The tests below that use Label should use View instead.
@@ -740,32 +901,78 @@ public class DrawTests
             │𝔹       │
             │𝔹       │
             └────────┘
             └────────┘
             """;
             """;
-        TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+
+        TestHelpers.AssertDriverContentsAre (expected, output);
 
 
-        TestHelpers.AssertDriverContentsAre (expected, _output);
+        // This test has nothing to do with color - removing as it is not relevant and fragile
+    }
 
 
-        Attribute [] expectedColors =
+    [Fact]
+    [SetupFakeDriver]
+    public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped ()
+    {
+        // Screen is 25x25
+        // View is 25x25
+        // Viewport is (0, 0, 23, 23)
+        // ContentSize is (10, 10)
+        // ViewportToScreen is (1, 1, 23, 23)
+        // Visible content is (1, 1, 10, 10)
+        // Expected clip is (1, 1, 10, 10) - same as visible content
+        Rectangle expectedClip = new (1, 1, 10, 10);
+        // Arrange
+        var view = new View ()
         {
         {
-            // 0
-            Colors.ColorSchemes ["Base"].Normal,
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            ContentSize = new Size (10, 10),
+            ViewportSettings = ViewportSettings.ClipContentOnly
+        };
+        view.Border.Thickness = new Thickness (1);
+        view.BeginInit ();
+        view.EndInit ();
+        Assert.Equal (view.Frame, Application.Driver.Clip);
+
+        // Act
+        view.SetClip ();
 
 
-            // 1
-            Colors.ColorSchemes ["Base"].Focus,
+        // Assert
+        Assert.Equal (expectedClip, Application.Driver.Clip);
+        view.Dispose ();
+    }
 
 
-            // 2
-            Colors.ColorSchemes ["Base"].HotNormal
+    [Fact]
+    [SetupFakeDriver]
+    public void SetClip_Default_ClipsToViewport ()
+    {
+        // Screen is 25x25
+        // View is 25x25
+        // Viewport is (0, 0, 23, 23)
+        // ContentSize is (10, 10)
+        // ViewportToScreen is (1, 1, 23, 23)
+        // Visible content is (1, 1, 10, 10)
+        // Expected clip is (1, 1, 23, 23) - same as Viewport
+        Rectangle expectedClip = new (1, 1, 23, 23);
+        // Arrange
+        var view = new View ()
+        {
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            ContentSize = new Size (10, 10),
         };
         };
+        view.Border.Thickness = new Thickness (1);
+        view.BeginInit ();
+        view.EndInit ();
+        Assert.Equal (view.Frame, Application.Driver.Clip);
+        view.Viewport = view.Viewport with { X = 1, Y = 1 };
 
 
-        TestHelpers.AssertDriverAttributesAre (
-                                               """
+        // Act
+        view.SetClip ();
 
 
-                                               0010000000
-                                               0000000000
-                                               0111000000
-                                               0000000000
-                                               """,
-                                               Application.Driver,
-                                               expectedColors
-                                              );
+        // Assert
+        Assert.Equal (expectedClip, Application.Driver.Clip);
+        view.Dispose ();
     }
     }
+
+
 }
 }

+ 161 - 2
UnitTests/View/FindDeepestViewTests.cs

@@ -68,7 +68,7 @@ public class FindDeepestViewTests (ITestOutputHelper output)
     [InlineData (1, 1, 1, 1, 1, 8, 8, typeof (Padding))]
     [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, 9, 9, typeof (Border))]
     [InlineData (1, 1, 1, 1, 1, 10, 10, typeof (Margin))]
     [InlineData (1, 1, 1, 1, 1, 10, 10, typeof (Margin))]
-    public void Contains (int frameX, int frameY, int marginThickness, int borderThickness, int paddingThinkcness, 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 ()
         {
         {
@@ -77,7 +77,7 @@ public class FindDeepestViewTests (ITestOutputHelper output)
         };
         };
         view.Margin.Thickness = new Thickness (marginThickness);
         view.Margin.Thickness = new Thickness (marginThickness);
         view.Border.Thickness = new Thickness (borderThickness);
         view.Border.Thickness = new Thickness (borderThickness);
-        view.Padding.Thickness = new Thickness (paddingThinkcness);
+        view.Padding.Thickness = new Thickness (paddingThickness);
 
 
         Type? containedType = null;
         Type? containedType = null;
         if (view.Contains (testX, testY))
         if (view.Contains (testX, testY))
@@ -271,6 +271,71 @@ public class FindDeepestViewTests (ITestOutputHelper output)
         Assert.Equal (expectedSubViewFound, found == subview);
         Assert.Equal (expectedSubViewFound, found == subview);
     }
     }
 
 
+    // Test that FindDeepestView works if the start view has offset Viewport location
+    [Theory]
+    [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)]
+    [InlineData (-1, 0, 0, false)]
+    public void Returns_Correct_If_Start_Has_Offset_Viewport (int offset, int testX, int testY, bool expectedSubViewFound)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+            ViewportSettings = ViewportSettings.AllowNegativeLocation
+        };
+        start.Viewport = new (offset, offset, 10, 10);
+
+        var subview = new View ()
+        {
+            X = 1, Y = 1,
+            Width = 2, Height = 2,
+        };
+        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, true)]
+    [InlineData (10, 10, false)]
+    [InlineData (7, 8, false)]
+    [InlineData (1, 2, false)]
+    [InlineData (2, 3, false)]
+    [InlineData (5, 6, false)]
+    [InlineData (2, 3, false)]
+    [InlineData (6, 7, false)]
+    public void Returns_Correct_If_Start_Has_Adornment_WithSubview (int testX, int testY, bool expectedSubViewFound)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+        };
+        start.Padding.Thickness = new Thickness (1);
+
+        var subview = new View ()
+        {
+            X = Pos.AnchorEnd(1), Y = Pos.AnchorEnd(1),
+            Width = 1, Height = 1,
+        };
+        start.Padding.Add (subview);
+        start.BeginInit();
+        start.EndInit();
+
+        var found = View.FindDeepestView (start, testX, testY);
+
+        Assert.Equal (expectedSubViewFound, found == subview);
+    }
+
+
     [Theory]
     [Theory]
     [InlineData (0, 0, typeof (Margin))]
     [InlineData (0, 0, typeof (Margin))]
     [InlineData (9, 9, typeof (Margin))]
     [InlineData (9, 9, typeof (Margin))]
@@ -336,6 +401,100 @@ public class FindDeepestViewTests (ITestOutputHelper output)
         Assert.Equal (expectedSubViewFound, found == subview);
         Assert.Equal (expectedSubViewFound, found == subview);
     }
     }
 
 
+    [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, false)]
+    [InlineData (5, 6, false)]
+    [InlineData (6, 5, false)]
+    [InlineData (5, 5, true)]
+    public void Returns_Correct_If_SubView_Has_Adornment_WithSubview (int testX, int testY, bool expectedSubViewFound)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+        };
+
+        // A subview with + Padding
+        var subview = new View ()
+        {
+            X = 1, Y = 1,
+            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 ()
+        {
+            X = Pos.AnchorEnd (1),
+            Y = Pos.AnchorEnd (1),
+            Width = 1,
+            Height = 1,
+        };
+        subview.Padding.Add (paddingSubview);
+        start.Add (subview);
+        start.BeginInit();
+        start.EndInit();
+
+        var found = View.FindDeepestView (start, testX, testY);
+
+        Assert.Equal (expectedSubViewFound, found == paddingSubview);
+    }
+
+    [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, false)]
+    [InlineData (5, 6, false)]
+    [InlineData (6, 5, false)]
+    [InlineData (5, 5, true)]
+    public void Returns_Correct_If_SubView_Is_Scrolled_And_Has_Adornment_WithSubview (int testX, int testY, bool expectedSubViewFound)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+        };
+
+        // A subview with + Padding
+        var subview = new View ()
+        {
+            X = 1, Y = 1,
+            Width = 5, Height = 5,
+        };
+        subview.Padding.Thickness = new (1);
+
+        // Scroll the subview
+        subview.ContentSize = new Size (10, 10);
+        subview.Viewport = subview.Viewport with { Location = new (1, 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 ()
+        {
+            X = Pos.AnchorEnd (1),
+            Y = Pos.AnchorEnd (1),
+            Width = 1,
+            Height = 1,
+        };
+        subview.Padding.Add (paddingSubview);
+        start.Add (subview);
+        start.BeginInit ();
+        start.EndInit ();
+
+        var found = View.FindDeepestView (start, testX, testY);
+
+        Assert.Equal (expectedSubViewFound, found == paddingSubview);
+    }
+
     // Test that FindDeepestView works with nested subviews
     // Test that FindDeepestView works with nested subviews
     [Theory]
     [Theory]
     [InlineData (0, 0, -1)]
     [InlineData (0, 0, -1)]

+ 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);
-    }
-}

+ 13 - 155
UnitTests/View/Layout/AbsoluteLayoutTests.cs

@@ -11,80 +11,6 @@ public class AbsoluteLayoutTests
     private readonly ITestOutputHelper _output;
     private readonly ITestOutputHelper _output;
     public AbsoluteLayoutTests (ITestOutputHelper output) { _output = 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.Bounds
-                     ); // With Absolute Bounds *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.Bounds
-                     ); // With Absolute Bounds *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.Bounds
-                     ); // With Absolute Bounds *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.Bounds
-                     ); // With Absolute Bounds *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]
     [Fact]
     [TestRespondersDisposed]
     [TestRespondersDisposed]
@@ -101,8 +27,8 @@ public class AbsoluteLayoutTests
 
 
         Assert.Equal (
         Assert.Equal (
                       new Rectangle (0, 0, newFrame.Width, newFrame.Height),
                       new Rectangle (0, 0, newFrame.Width, newFrame.Height),
-                      v.Bounds
-                     ); // With Absolute Bounds *is* deterministic before Layout
+                      v.Viewport
+                     ); // With Absolute Viewport *is* deterministic before Layout
         Assert.Equal (Pos.At (1), v.X);
         Assert.Equal (Pos.At (1), v.X);
         Assert.Equal (Pos.At (2), v.Y);
         Assert.Equal (Pos.At (2), v.Y);
         Assert.Equal ($"Absolute({newFrame.Height})", v.Height.ToString ());
         Assert.Equal ($"Absolute({newFrame.Height})", v.Height.ToString ());
@@ -136,8 +62,8 @@ public class AbsoluteLayoutTests
 
 
         Assert.Equal (
         Assert.Equal (
                       new Rectangle (0, 0, newFrame.Width, newFrame.Height),
                       new Rectangle (0, 0, newFrame.Width, newFrame.Height),
-                      v.Bounds
-                     ); // With Absolute Bounds *is* deterministic before Layout
+                      v.Viewport
+                     ); // With Absolute Viewport *is* deterministic before Layout
         Assert.Equal ($"Absolute({newFrame.X})", v.X.ToString ());
         Assert.Equal ($"Absolute({newFrame.X})", v.X.ToString ());
         Assert.Equal ($"Absolute({newFrame.Y})", v.Y.ToString ());
         Assert.Equal ($"Absolute({newFrame.Y})", v.Y.ToString ());
         Assert.Equal (Dim.Sized (3), v.Width);
         Assert.Equal (Dim.Sized (3), v.Width);
@@ -250,8 +176,8 @@ public class AbsoluteLayoutTests
 
 
         Assert.Equal (
         Assert.Equal (
                       new Rectangle (0, 0, frame.Width, frame.Height),
                       new Rectangle (0, 0, frame.Width, frame.Height),
-                      v.Bounds
-                     ); // With Absolute Bounds *is* deterministic before Layout
+                      v.Viewport
+                     ); // With Absolute Viewport *is* deterministic before Layout
         Assert.Equal (Pos.At (0), v.X);
         Assert.Equal (Pos.At (0), v.X);
         Assert.Equal (Pos.At (0), v.Y);
         Assert.Equal (Pos.At (0), v.Y);
         Assert.Equal (Dim.Sized (0), v.Width);
         Assert.Equal (Dim.Sized (0), v.Width);
@@ -265,8 +191,8 @@ public class AbsoluteLayoutTests
 
 
         Assert.Equal (
         Assert.Equal (
                       new Rectangle (0, 0, frame.Width, frame.Height),
                       new Rectangle (0, 0, frame.Width, frame.Height),
-                      v.Bounds
-                     ); // With Absolute Bounds *is* deterministic before Layout
+                      v.Viewport
+                     ); // With Absolute Viewport *is* deterministic before Layout
         Assert.Equal (Pos.At (1), v.X);
         Assert.Equal (Pos.At (1), v.X);
         Assert.Equal (Pos.At (2), v.Y);
         Assert.Equal (Pos.At (2), v.Y);
         Assert.Equal (Dim.Sized (3), v.Width);
         Assert.Equal (Dim.Sized (3), v.Width);
@@ -279,8 +205,8 @@ public class AbsoluteLayoutTests
 
 
         Assert.Equal (
         Assert.Equal (
                       new Rectangle (0, 0, frame.Width, frame.Height),
                       new Rectangle (0, 0, frame.Width, frame.Height),
-                      v.Bounds
-                     ); // With Absolute Bounds *is* deterministic before Layout
+                      v.Viewport
+                     ); // With Absolute Viewport *is* deterministic before Layout
         Assert.Equal (Pos.At (1), v.X);
         Assert.Equal (Pos.At (1), v.X);
         Assert.Equal (Pos.At (2), v.Y);
         Assert.Equal (Pos.At (2), v.Y);
         Assert.Equal (Dim.Sized (3), v.Width);
         Assert.Equal (Dim.Sized (3), v.Width);
@@ -294,7 +220,7 @@ public class AbsoluteLayoutTests
         // That is correct it should be 0,0 because AutoSize is false
         // That is correct it should be 0,0 because AutoSize is false
         // and the size wasn't set on the initializer
         // and the size wasn't set on the initializer
         Assert.Equal (new Rectangle (frame.X, frame.Y, 0, 0), v.Frame);
         Assert.Equal (new Rectangle (frame.X, frame.Y, 0, 0), v.Frame);
-        Assert.Equal (new Rectangle (0, 0, 0, 0), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (new Rectangle (0, 0, 0, 0), v.Viewport); // With Absolute Viewport *is* deterministic before Layout
         Assert.Equal (Pos.At (1), v.X);
         Assert.Equal (Pos.At (1), v.X);
         Assert.Equal (Pos.At (2), v.Y);
         Assert.Equal (Pos.At (2), v.Y);
         Assert.Equal (Dim.Sized (0), v.Width);
         Assert.Equal (Dim.Sized (0), v.Width);
@@ -304,7 +230,7 @@ public class AbsoluteLayoutTests
         v = new View ();
         v = new View ();
         Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
         Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
         Assert.Equal (new Rectangle (0, 0, 0, 0), v.Frame);
         Assert.Equal (new Rectangle (0, 0, 0, 0), v.Frame);
-        Assert.Equal (new Rectangle (0, 0, 0, 0), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (new Rectangle (0, 0, 0, 0), v.Viewport); // With Absolute Viewport *is* deterministic before Layout
         Assert.Equal (Pos.At (0), v.X);
         Assert.Equal (Pos.At (0), v.X);
         Assert.Equal (Pos.At (0), v.Y);
         Assert.Equal (Pos.At (0), v.Y);
         Assert.Equal (Dim.Sized (0), v.Width);
         Assert.Equal (Dim.Sized (0), v.Width);
@@ -314,7 +240,7 @@ public class AbsoluteLayoutTests
         v = new View { X = frame.X, Y = frame.Y, Width = frame.Width, Height = frame.Height };
         v = new View { X = frame.X, Y = frame.Y, Width = frame.Width, Height = frame.Height };
         Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
         Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
         Assert.Equal (new Rectangle (frame.X, frame.Y, 3, 4), v.Frame);
         Assert.Equal (new Rectangle (frame.X, frame.Y, 3, 4), v.Frame);
-        Assert.Equal (new Rectangle (0, 0, 3, 4), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (new Rectangle (0, 0, 3, 4), v.Viewport); // With Absolute Viewport *is* deterministic before Layout
         Assert.Equal (Pos.At (1), v.X);
         Assert.Equal (Pos.At (1), v.X);
         Assert.Equal (Pos.At (2), v.Y);
         Assert.Equal (Pos.At (2), v.Y);
         Assert.Equal (Dim.Sized (3), v.Width);
         Assert.Equal (Dim.Sized (3), v.Width);
@@ -344,72 +270,4 @@ public class AbsoluteLayoutTests
         Assert.Equal (new Rectangle (10, 10, 10, 10), v2.Frame);
         Assert.Equal (new Rectangle (10, 10, 10, 10), v2.Frame);
         super.Dispose ();
         super.Dispose ();
     }
     }
-
-    [Fact]
-    public void AbsoluteLayout_Setting_Bounds_Location_NotEmpty ()
-    {
-        // TODO: Should we enforce Bounds.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.Bounds = newBounds;
-        Assert.Equal (new Rectangle (0, 0, 30, 40), view.Bounds);
-        Assert.Equal (new Rectangle (1, 2, 30, 40), view.Frame);
-    }
-
-    [Fact]
-    public void AbsoluteLayout_Setting_Bounds_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.Bounds = newBounds;
-        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-        Assert.Equal (newBounds, v.Bounds);
-        Assert.Equal (new Rectangle (1, 2, newBounds.Width, newBounds.Height), v.Frame);
-        Assert.Equal (new Rectangle (0, 0, newBounds.Width, newBounds.Height), v.Bounds);
-        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.Bounds = newBounds;
-        Assert.Equal (newBounds, v.Bounds);
-        Assert.Equal (new Rectangle (1, 2, newBounds.Width, newBounds.Height), v.Frame);
-        Assert.Equal (new Rectangle (0, 0, newBounds.Width, newBounds.Height), v.Bounds);
-        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;
-
-        // Bounds should shrink
-        Assert.Equal (new Rectangle (0, 0, 1, 2), v.Bounds);
-
-        // 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.Bounds = newBounds;
-        Assert.Equal (newBounds, v.Bounds);
-
-        // 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.Bounds);
-        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);
-    }
 }
 }

+ 0 - 152
UnitTests/View/Layout/BoundsTests.cs

@@ -1,152 +0,0 @@
-using Xunit.Abstractions;
-
-namespace Terminal.Gui.ViewTests;
-
-/// <summary>
-/// Test the <see cref="View.Bounds"/>.
-/// DOES NOT TEST Adornment.Bounds methods. Those are in ./Adornment/BoundsTests.cs
-/// </summary>
-/// <param name="output"></param>
-public class BoundsTests (ITestOutputHelper output)
-{
-    private readonly ITestOutputHelper _output = output;
-
-    [Theory]
-    [InlineData (0, 10)]
-    [InlineData (1, 10)]
-    [InlineData (-1, 10)]
-    [InlineData (11, 10)]
-    public void Get_Bounds_NoSuperView_WithoutAdornments (int x, int expectedW)
-    {
-        // We test with only X because Y is equivalent. Height/Width are irrelevant.
-        // Arrange
-        var frame = new Rectangle (x, 0, 10, 10);
-
-        var view = new View ();
-        view.Frame = frame;
-        view.BeginInit();
-        view.EndInit();
-
-        // Act
-        var bounds = view.Bounds;
-
-        // Assert
-        Assert.Equal(expectedW, bounds.Width);
-    }
-    
-    [Theory]
-    [InlineData (0, 0, 10)]
-    [InlineData (1, 0, 9)]
-    [InlineData (-1, 0, 11)]
-    [InlineData (10, 0, 0)]
-    [InlineData (11, 0, 0)]
-
-    [InlineData (0, 1, 6)]
-    [InlineData (1, 1, 5)]
-    [InlineData (-1, 1, 7)]
-    [InlineData (10, 1, 0)]
-    [InlineData (11, 1, 0)]
-
-    public void Get_Bounds_NestedSuperView_WithAdornments (int frameX, int borderThickness, int expectedW)
-    {
-        // We test with only X because Y is equivalent. Height/Width are irrelevant.
-        // Arrange
-        var superSuperView = new View ()
-        {
-            X = 0,
-            Y = 0,
-            Height = 10,
-            Width = 10,
-        };
-        superSuperView.Border.Thickness = new Thickness (borderThickness);
-
-        var superView = new View ()
-        {
-            X = 0,
-            Y = 0,
-            Height = Dim.Fill (),
-            Width = Dim.Fill ()
-        };
-        superView.Border.Thickness = new Thickness (borderThickness);
-
-        superSuperView.Add (superView);
-
-        var view = new View ()
-        {
-            X = frameX,
-            Y = 0,
-            Height = Dim.Fill (),
-            Width = Dim.Fill ()
-        };
-
-        superView.Add (view);
-        superSuperView.BeginInit ();
-        superSuperView.EndInit ();
-        superSuperView.LayoutSubviews ();
-
-        // Act
-        var bounds = view.Bounds;
-
-        // Assert
-        Assert.Equal (expectedW, bounds.Width);
-    }
-
-
-
-    [Theory]
-    [InlineData (0, 0, 10)]
-    [InlineData (1, 0, 9)]
-    [InlineData (-1, 0, 11)]
-    [InlineData (10, 0, 0)]
-    [InlineData (11, 0, 0)]
-
-    [InlineData (0, 1, 4)]
-    [InlineData (1, 1, 3)]
-    [InlineData (-1, 1, 5)]
-    [InlineData (10, 1, 0)]
-    [InlineData (11, 1, 0)]
-    public void Get_Bounds_NestedSuperView_WithAdornments_WithBorder (int frameX, int borderThickness, int expectedW)
-    {
-        // We test with only X because Y is equivalent. Height/Width are irrelevant.
-        // Arrange
-        var superSuperView = new View ()
-        {
-            X = 0,
-            Y = 0,
-            Height = 10,
-            Width = 10,
-        };
-        superSuperView.Border.Thickness = new Thickness (borderThickness);
-
-        var superView = new View ()
-        {
-            X = 0,
-            Y = 0,
-            Height = Dim.Fill (),
-            Width = Dim.Fill ()
-        };
-        superView.Border.Thickness = new Thickness (borderThickness);
-
-        superSuperView.Add (superView);
-
-        var view = new View ()
-        {
-            X = frameX,
-            Y = 0,
-            Height = Dim.Fill (),
-            Width = Dim.Fill ()
-        };
-        view.Border.Thickness = new Thickness (borderThickness);
-
-        superView.Add (view);
-        superSuperView.BeginInit ();
-        superSuperView.EndInit ();
-        superSuperView.LayoutSubviews ();
-
-        // Act
-        var bounds = view.Bounds;
-
-        // Assert
-        Assert.Equal (expectedW, bounds.Width);
-    }
-}

Some files were not shown because too many files changed in this diff