2
0
Эх сурвалжийг харах

Merge pull request #3323 from tig/v2_3269_Bounds-ContentArea

Fixes #3169. `Bounds` -> `Viewport`: Content Scrolling in `View`
Tig 1 жил өмнө
parent
commit
56922b4357
100 өөрчлөгдсөн 3846 нэмэгдсэн , 2560 устгасан
  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)
 ![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)
@@ -31,8 +31,10 @@ dotnet run
 
 ## 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)
+* [Documentation Home](https://gui-cs.github.io/Terminal.GuiV2Docs)
 
 ## Showcase & Examples
 

+ 1 - 1
ReactiveExample/ReactiveExample.csproj

@@ -12,7 +12,7 @@
   </PropertyGroup>
   <ItemGroup>
     <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" />
   </ItemGroup>
   <ItemGroup>

+ 51 - 21
Terminal.Gui/Application.cs

@@ -88,7 +88,7 @@ public static partial class Application
 #if DEBUG_IDISPOSABLE
 
             // Don't dispose the toplevels. It's up to caller dispose them
-            Debug.Assert (t.WasDisposed);
+            //Debug.Assert (t.WasDisposed);
 #endif
         }
 
@@ -99,6 +99,7 @@ public static partial class Application
         // Don't dispose the Top. It's up to caller dispose it
         if (Top is { })
         {
+
             Debug.Assert (Top.WasDisposed);
 
             // 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.
         //ForceDriver = string.Empty;
-        Force16Colors = false;
+        //Force16Colors = false;
         _forceFakeConsole = false;
 
         // Run State stuff
@@ -525,7 +526,10 @@ public static partial class Application
             MoveCurrent (Current);
         }
 
-        toplevel.SetRelativeLayout (Driver.Bounds);
+        //if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
+        toplevel.SetRelativeLayout (Driver.Screen.Size);
+
+        //}
 
         // BUGBUG: This call is likely not needed.
         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)
     {
         ArgumentNullException.ThrowIfNull (view);
-        
+
         if (_initialized)
         {
             if (Driver is null)
@@ -878,7 +882,7 @@ public static partial class Application
         }
         else
         {
-            Driver.UpdateCursor ();
+            //Driver.UpdateCursor ();
         }
 
         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)
         {
-            t.SetRelativeLayout (Rectangle.Empty with { Size = args.Size });
+            t.SetRelativeLayout (args.Size);
             t.LayoutSubviews ();
             t.PositionToplevels ();
             t.OnSizeChanging (new (args.Size));
@@ -1437,15 +1441,15 @@ public static partial class Application
     /// <remarks>
     ///     <para>
     ///         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>The <see cref="MouseEvent.View"/> will contain the <see cref="View"/> that contains the mouse coordinates.</para>
     /// </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>
     /// <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)
     {
         if (IsMouseDisabled)
@@ -1453,7 +1457,6 @@ public static partial class Application
             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);
 
         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.
             // 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
             {
-                X = boundsLoc.X,
-                Y = boundsLoc.Y,
+                X = frameLoc.X,
+                Y = frameLoc.Y,
                 Flags = mouseEvent.Flags,
                 ScreenPosition = new (mouseEvent.X, mouseEvent.Y),
                 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
                 _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent);
@@ -1546,14 +1549,14 @@ public static partial class Application
                 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 ()
             {
-                X = boundsPoint.X,
-                Y = boundsPoint.Y,
+                X = viewportLocation.X,
+                Y = viewportLocation.Y,
                 Flags = mouseEvent.Flags,
                 ScreenPosition = new (mouseEvent.X, mouseEvent.Y),
                 View = view
@@ -1586,10 +1589,37 @@ public static partial class Application
 
         //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 ();

+ 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.
     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>
     ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject
     ///     to.
     /// </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>
     public IClipboard Clipboard { get; internal set; }
@@ -278,16 +294,6 @@ public abstract class ConsoleDriver
 
         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]);
         }
     }
@@ -295,31 +301,27 @@ public abstract class ConsoleDriver
     /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>
     public void ClearContents ()
     {
-        // TODO: This method is really "Clear Contents" now and should not be abstract (or virtual)
         Contents = new Cell [Rows, Cols];
         //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];
 
         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>
     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)
     {
-        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="row">The row.</param>
     /// <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.
     /// </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>
     ///     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>
     public virtual bool SupportsTrueColor => true;
 
+    // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
     /// <summary>
     ///     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"/>.
@@ -466,6 +482,7 @@ public abstract class ConsoleDriver
         get => _currentAttribute;
         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 { })
             {
                 _currentAttribute = new Attribute (value.Foreground, value.Background);

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

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

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

@@ -18,6 +18,7 @@
 using System.ComponentModel;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
+using System.Text;
 using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 
 namespace Terminal.Gui;
@@ -110,6 +111,7 @@ internal class WindowsConsole
             }
 
             _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
+            _stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
 
             var s = _stringBuilder.ToString ();
 
@@ -129,6 +131,11 @@ internal class WindowsConsole
         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)
     {
         _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 ()
     {
@@ -577,14 +587,14 @@ internal class WindowsConsole
         public readonly override string ToString ()
         {
             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 CursorVisibility _cachedCursorVisibility;
     private WindowsConsole.SmallRect _damageRegion;
     private bool _isButtonDoubleClicked;
     private bool _isButtonPressed;
@@ -1023,9 +1032,6 @@ internal class WindowsDriver : ConsoleDriver
 
     public WindowsConsole WinConsole { get; private set; }
 
-    /// <inheritdoc/>
-    public override bool EnsureCursorVisibility () { return WinConsole is null || WinConsole.EnsureCursorVisibility (); }
-
     public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
     {
         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 void Refresh ()
     {
         UpdateScreen ();
-        WinConsole?.SetInitialCursorVisibility ();
+        //WinConsole?.SetInitialCursorVisibility ();
         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
 
@@ -1194,9 +1180,13 @@ internal class WindowsDriver : ConsoleDriver
         return new WindowsConsole.ConsoleKeyInfoEx (cki, capslock, numlock, scrolllock);
     }
 
+    #region Cursor Handling
+
+    private CursorVisibility? _cachedCursorVisibility;
+
     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);
             _cachedCursorVisibility = cursorVisibility;
@@ -1205,16 +1195,90 @@ internal class WindowsDriver : ConsoleDriver
             return;
         }
 
-        SetCursorVisibility (_cachedCursorVisibility);
-
         var position = new WindowsConsole.Coord
         {
             X = (short)Col,
             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 ()
     {
         Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (Cols, Rows);
@@ -1226,8 +1290,8 @@ internal class WindowsDriver : ConsoleDriver
 
         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++)
@@ -1574,13 +1638,13 @@ internal class WindowsDriver : ConsoleDriver
                         // returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important
                         // for key persistence ("Ctrl++" vs. "Ctrl+=").
                         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
@@ -1745,7 +1809,10 @@ internal class WindowsDriver : ConsoleDriver
                 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;
             }
@@ -1759,6 +1826,7 @@ internal class WindowsDriver : ConsoleDriver
             //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}");
             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));
             }
         }
@@ -1813,6 +1881,7 @@ internal class WindowsDriver : ConsoleDriver
 
         if (_isButtonDoubleClicked || _isOneFingerDoubleClicked)
         {
+            // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
             Application.MainLoop.AddIdle (
                                           () =>
                                           {
@@ -1884,6 +1953,7 @@ internal class WindowsDriver : ConsoleDriver
 
             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 (
                                               () =>
                                               {

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

@@ -48,7 +48,7 @@ public class LineCanvas : IDisposable
         // TODO: Add other resolvers
     };
 
-    private Rectangle _cachedBounds;
+    private Rectangle _cachedViewport;
 
     /// <summary>Creates a new instance.</summary>
     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
     ///     furthest left/top and Size is defined by the line that extends the furthest right/bottom.
     /// </summary>
-    public Rectangle Bounds
+    public Rectangle Viewport
     {
         get
         {
-            if (_cachedBounds.IsEmpty)
+            if (_cachedViewport.IsEmpty)
             {
                 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++)
                 {
-                    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
     )
     {
-        _cachedBounds = Rectangle.Empty;
+        _cachedViewport = Rectangle.Empty;
         _lines.Add (new StraightLine (start, length, orientation, style, attribute));
     }
 
@@ -142,14 +142,14 @@ public class LineCanvas : IDisposable
     /// <param name="line"></param>
     public void AddLine (StraightLine line)
     {
-        _cachedBounds = Rectangle.Empty;
+        _cachedViewport = Rectangle.Empty;
         _lines.Add (line);
     }
 
     /// <summary>Clears all lines from the LineCanvas.</summary>
     public void Clear ()
     {
-        _cachedBounds = Rectangle.Empty;
+        _cachedViewport = Rectangle.Empty;
         _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
     ///     added.
     /// </summary>
-    public void ClearCache () { _cachedBounds = Rectangle.Empty; }
+    public void ClearCache () { _cachedViewport = Rectangle.Empty; }
 
     /// <summary>
     ///     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 ();
 
         // 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
                                                         .Select (l => l.Intersects (x, y))
@@ -232,7 +232,7 @@ public class LineCanvas : IDisposable
     ///     intersection symbols.
     /// </summary>
     /// <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>
     /// <param name="lineCanvas"></param>
@@ -260,13 +260,13 @@ public class LineCanvas : IDisposable
 
     /// <summary>
     ///     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.
     /// </summary>
     /// <returns>The canvas rendered to a string.</returns>
     public override string ToString ()
     {
-        if (Bounds.IsEmpty)
+        if (Viewport.IsEmpty)
         {
             return string.Empty;
         }
@@ -275,13 +275,13 @@ public class LineCanvas : IDisposable
         Dictionary<Point, Rune> runeMap = GetMap ();
 
         // 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
         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;
         }
 

+ 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
     ///     furthest left/top and Size is defined by the line that extends the furthest right/bottom.
     /// </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
         {

+ 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);
         }
 
+        // Draw the Left side
         // Draw the Left side
         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>
     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; }
 
-    /// <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; }
 
     /// <summary>
@@ -133,12 +133,12 @@ public class MouseEvent
     /// </summary>
     /// <remarks>
     ///     <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
     ///         mouse has moved.
     ///     </para>
     ///     <para>
-    ///         Calculated and processed in <see cref="Application.OnMouseEvent(MouseEventEventArgs)"/>.
+    ///         Calculated and processed in <see cref="Application.OnMouseEvent(MouseEvent)"/>.
     ///     </para>
     /// </remarks>
     public Point ScreenPosition { get; set; }

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

@@ -48,8 +48,8 @@
     <PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
     <!-- Enable Nuget Source Link for github -->
     <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" />
   </ItemGroup>
   <!-- =================================================================== -->

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

@@ -117,7 +117,7 @@ public class AppendAutocomplete : AutocompleteBase
         Suggestion suggestion = Suggestions.ElementAt (SelectedIdx);
         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 ());
 
         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); }
 
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
             if (!_autoComplete.LastPopupPos.HasValue)
             {

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

@@ -271,13 +271,13 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         if (PopupInsideContainer)
         {
             // 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
-            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
-                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);
                     height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), LastPopupPos.Value.Y - 1);
@@ -287,13 +287,13 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         else
         {
             // 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
             if (height < Suggestions.Count && HostControl.Frame.Y - top.Frame.Y >= height)
             {
                 // 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);
                     height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), HostControl.Frame.Y);
@@ -323,34 +323,34 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         if (PopupInsideContainer)
         {
             // 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
-                if (renderAt.X > HostControl.Bounds.Width - renderAt.X)
+                if (renderAt.X > HostControl.Viewport.Width - renderAt.X)
                 {
                     renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
                     width = Math.Min (width, LastPopupPos.Value.X);
                 }
                 else
                 {
-                    width = Math.Min (width, HostControl.Bounds.Width - renderAt.X);
+                    width = Math.Min (width, HostControl.Viewport.Width - renderAt.X);
                 }
             }
         }
         else
         {
             // 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
-                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);
                     width = Math.Min (width, LastPopupPos.Value.X);
                 }
                 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>
     /// <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>
     ///         AutoSize is ignored if <see cref="TextAlignment.Justified"/> and
     ///         <see cref="VerticalTextAlignment.Justified"/> are used.
@@ -73,8 +73,8 @@ public class TextFormatter
     }
 
     /// <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.
     /// </summary>
     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
     ///     <c>false</c>.
     /// </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="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>
     /// <exception cref="ArgumentOutOfRangeException"></exception>
     public void Draw (
-        Rectangle bounds,
+        Rectangle screen,
         Attribute normalColor,
         Attribute hotColor,
-        Rectangle containerBounds = default,
+        Rectangle maximum = default,
         ConsoleDriver driver = null
     )
     {
@@ -240,46 +240,46 @@ public class TextFormatter
         }
 
         bool isVertical = IsVerticalDirection (Direction);
-        Rectangle maxBounds = bounds;
+        Rectangle maxScreen = screen;
 
         if (driver is { })
         {
             // INTENT: What, exactly, is the intent of this?
-            maxBounds = containerBounds == default (Rectangle)
-                            ? bounds
+            maxScreen = maximum == default (Rectangle)
+                            ? screen
                             : 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.Min (containerBounds.Width, containerBounds.Right - bounds.Left),
+                                             Math.Min (maximum.Width, maximum.Right - screen.Left),
                                              0
                                             ),
                                    Math.Max (
                                              Math.Min (
-                                                       containerBounds.Height,
-                                                       containerBounds.Bottom - bounds.Top
+                                                       maximum.Height,
+                                                       maximum.Bottom - screen.Top
                                                       ),
                                              0
                                             )
                                   );
         }
 
-        if (maxBounds.Width == 0 || maxBounds.Height == 0)
+        if (maxScreen.Width == 0 || maxScreen.Height == 0)
         {
             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++)
         {
-            if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height))
+            if ((isVertical && line > screen.Width) || (!isVertical && line > screen.Height))
             {
                 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;
             }
@@ -305,14 +305,14 @@ public class TextFormatter
                 if (isVertical)
                 {
                     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
                 {
                     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)
@@ -322,11 +322,11 @@ public class TextFormatter
                     int runesWidth = line > 0
                                          ? GetWidestLineLength (linesFormatted, 0, line, TabWidth)
                                          : 0;
-                    x = bounds.Left + runesWidth;
+                    x = screen.Left + runesWidth;
                 }
                 else
                 {
-                    x = bounds.Left;
+                    x = screen.Left;
                 }
 
                 CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0;
@@ -336,16 +336,16 @@ public class TextFormatter
                 if (isVertical)
                 {
                     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
                 {
                     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
@@ -358,35 +358,35 @@ public class TextFormatter
             {
                 if (isVertical)
                 {
-                    y = bounds.Bottom - runes.Length;
+                    y = screen.Bottom - runes.Length;
                 }
                 else
                 {
-                    y = bounds.Bottom - linesFormatted.Count + line;
+                    y = screen.Bottom - linesFormatted.Count + line;
                 }
             }
             else if (VerticalAlignment is VerticalTextAlignment.Top or VerticalTextAlignment.Justified)
             {
                 if (isVertical)
                 {
-                    y = bounds.Top;
+                    y = screen.Top;
                 }
                 else
                 {
-                    y = bounds.Top + line;
+                    y = screen.Top + line;
                 }
             }
             else if (VerticalAlignment == VerticalTextAlignment.Middle)
             {
                 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
                 {
-                    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
@@ -394,9 +394,9 @@ public class TextFormatter
                 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;
             List<Point?> lastZeroWidthPos = null;
             Rune rune = default;
@@ -422,15 +422,15 @@ public class TextFormatter
                         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;
                     }
                 }
 
-                //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;
 

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

@@ -1,14 +1,14 @@
 namespace Terminal.Gui;
 
 /// <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="Thickness"/> class, which specifies the thickness of the sides of a rectangle.
 /// </summary>
 /// <remarsk>
 ///     <para>
 ///         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.
 ///     </para>
 /// </remarsk>
@@ -89,7 +89,7 @@ public class Adornment : View
     public override View SuperView
     {
         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)
@@ -107,10 +107,14 @@ public class Adornment : View
     ///     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"/>.
     /// </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 };
-        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/>
@@ -123,10 +127,11 @@ public class Adornment : View
 
         // 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
-        // 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/>
@@ -137,19 +142,21 @@ public class Adornment : View
     public override bool OnDrawAdornments () { return false; }
 
     /// <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)
         {
             return;
         }
 
-        Rectangle screenBounds = BoundsToScreen (contentArea);
+        Rectangle prevClip = SetClip ();
+
+        Rectangle screen = ViewportToScreen (viewport);
         Attribute normalAttr = GetNormalColor ();
         Driver.SetAttribute (normalAttr);
 
         // This just draws/clears the thickness, not the insides.
-        Thickness.Draw (screenBounds, ToString ());
+        Thickness.Draw (screen, ToString ());
 
         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)
         {
-            base.OnDrawContent (contentArea);
+            base.OnDrawContent (viewport);
+        }
+
+        if (Driver is { })
+        {
+           Driver.Clip = prevClip;
         }
 
         ClearLayoutNeeded ();
@@ -181,8 +193,8 @@ public class Adornment : View
     /// </summary>
     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
@@ -205,6 +217,7 @@ public class Adornment : View
         {
             return false;
         }
+
         Rectangle frame = Frame;
         frame.Offset (Parent.Frame.Location);
 
@@ -227,43 +240,6 @@ public class Adornment : View
         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/>
     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 (
-                                      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 (
                                                 0,
-                                                screenBounds.Width
+                                                screenRect.Width
                                                 - Math.Max (
                                                             0,
                                                             Math.Max (0, Thickness.Left - 1)
@@ -165,7 +165,7 @@ public class Border : Adornment
                                                ),
                                       Math.Max (
                                                 0,
-                                                screenBounds.Height
+                                                screenRect.Height
                                                 - Math.Max (
                                                             0,
                                                             Math.Max (0, Thickness.Top - 1)
@@ -204,6 +204,7 @@ public class Border : Adornment
     {
         if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable))
         {
+            e.Cancel = true;
             return;
         }
 
@@ -300,7 +301,7 @@ public class Border : Adornment
 
                 _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 (
                                      Parent,
@@ -360,9 +361,9 @@ public class Border : Adornment
 #endregion Mouse Support
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
         if (Thickness == Thickness.Empty)
         {
@@ -370,7 +371,7 @@ public class Border : Adornment
         }
 
         //Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal);
-        Rectangle screenBounds = BoundsToScreen (contentArea);
+        Rectangle screenBounds = ViewportToScreen (viewport);
 
         //OnDrawSubviews (bounds); 
 
@@ -381,7 +382,7 @@ public class Border : Adornment
         // ...thickness extends outward (border/title is always as far in as possible)
         // PERF: How about a call to Rectangle.Offset?
 
-        var borderBounds = GetBorderBounds (screenBounds);
+        var borderBounds = GetBorderRectangle (screenBounds);
         int topTitleLineY = borderBounds.Y;
         int titleY = borderBounds.Y;
         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))
         {
+            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 (
                                             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)
@@ -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>
     /// <remarks>
     /// <para>
-    /// The coordinates are relative to <see cref="View.Bounds"/>.
+    /// The coordinates are relative to <see cref="View.Viewport"/>.
     /// </para>
     /// <para>
     /// 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.IO.Compression;
 
 namespace Terminal.Gui;
 
@@ -42,18 +43,18 @@ public partial class View
     /// <summary>Gets or sets the absolute location and dimension of the view.</summary>
     /// <value>
     ///     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>
     /// <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>
     ///         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.
+    ///         This causes <see cref="LayoutStyle"/> to be <see cref="LayoutStyle.Absolute"/>.
     ///     </para>
-    ///     <para>This causes <see cref="LayoutStyle"/> to be <see cref="LayoutStyle.Absolute"/>.</para>
     ///     <para>
     ///         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)"/>
     ///         methods to be called.
     ///     </para>
@@ -63,7 +64,12 @@ public partial class View
         get => _frame;
         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
             // set all Pos/Dim to Absolute values.
@@ -73,62 +79,83 @@ public partial class View
             _height = _frame.Height;
 
             // 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>
     /// <returns>The location and size of the view in screen-relative coordinates.</returns>
     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
-                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>
     ///     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>
-    /// <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="y">Screen-relative row.</param>
     public virtual Point ScreenToFrame (int x, int y)
     {
-        Point superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty;
         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;
     }
 
@@ -138,13 +165,16 @@ public partial class View
     /// <value>The <see cref="Pos"/> object representing the X position.</value>
     /// <remarks>
     ///     <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
-    ///         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.
     ///     </para>
     ///     <para>
     ///         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>
     ///         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));
         set
         {
+            if (Equals (_x, value))
+            {
+                return;
+            }
+
             _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null");
+
             OnResizeNeeded ();
         }
     }
@@ -168,13 +204,16 @@ public partial class View
     /// <value>The <see cref="Pos"/> object representing the Y position.</value>
     /// <remarks>
     ///     <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
-    ///         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.
     ///     </para>
     ///     <para>
     ///         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>
     ///         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));
         set
         {
+            if (Equals (_y, value))
+            {
+                return;
+            }
+
             _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null");
             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>
     /// <remarks>
     ///     <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
-    ///         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.
     ///     </para>
     ///     <para>
     ///         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>
     ///         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));
         set
         {
+            if (Equals (_height, value))
+            {
+                return;
+            }
+
             _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null");
 
             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>
     /// <remarks>
     ///     <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
-    ///         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.
     ///     </para>
     ///     <para>
     ///         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>
     ///         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));
         set
         {
+            if (Equals (_width, value))
+            {
+                return;
+            }
+
             _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null");
 
             if (AutoSize)
@@ -288,123 +348,13 @@ public partial class View
 
     #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
 
     private bool _autoSize;
 
     /// <summary>
     ///     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>
     ///         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
@@ -462,7 +412,7 @@ public partial class View
             if (ValidatePosDim)
             {
                 // BUGBUG: This ain't right, obviously.  We need to figure out how to handle this.
-                boundsChanged = ResizeBoundsToFit (newFrameSize);
+                boundsChanged = ResizeViewportToFit (newFrameSize);
             }
             else
             {
@@ -577,35 +527,35 @@ public partial class View
     /// <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>
     /// <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) {
-        //	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 canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH);
 
         if (canSizeW)
         {
-            boundsChanged = true;
+            changed = true;
             _width = rW;
         }
 
         if (canSizeH)
         {
-            boundsChanged = true;
+            changed = true;
             _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
@@ -651,25 +601,20 @@ public partial class View
 
     #endregion Layout Engine
 
-    internal bool LayoutNeeded { get; private set; } = true;
-
     /// <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>
     /// <param name="x">SuperView-relative X 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>
-    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
     /// <summary>Finds the first Subview of <paramref name="start"/> that is visible at the provided location.</summary>
     /// <remarks>
-    /// <para>
-    ///     Used to determine what view the mouse is over.
-    /// </para>
+    ///     <para>
+    ///         Used to determine what view the mouse is over.
+    ///     </para>
     /// </remarks>
     /// <param name="start">The view to scope the search by.</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.
     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--)
             {
-                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
 
     /// <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.
     /// </summary>
     /// <remarks>
@@ -761,6 +715,7 @@ public partial class View
     {
         int maxDimension;
         View superView;
+        statusBar = null;
 
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         {
@@ -769,8 +724,8 @@ public partial class View
         }
         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;
         }
 
@@ -795,7 +750,8 @@ public partial class View
         }
 
         //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)
         {
@@ -805,12 +761,15 @@ public partial class View
         {
             View t = viewToMove.SuperView;
 
-            while (t is not Toplevel)
+            while (t is { } and not Toplevel)
             {
                 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)
@@ -833,13 +792,16 @@ public partial class View
         {
             View t = viewToMove.SuperView;
 
-            while (t is not Toplevel)
+            while (t is { } and not Toplevel)
             {
                 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)
@@ -848,7 +810,7 @@ public partial class View
         }
         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)
@@ -916,8 +878,7 @@ public partial class View
 
         LayoutAdornments ();
 
-        Rectangle oldBounds = Bounds;
-        OnLayoutStarted (new () { OldBounds = oldBounds });
+        OnLayoutStarted (new (ContentSize));
 
         SetTextFormatterSize ();
 
@@ -929,7 +890,7 @@ public partial class View
 
         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.
@@ -938,13 +899,19 @@ public partial class View
         {
             foreach ((View from, View to) in edges)
             {
-                LayoutSubview (to, from.Frame);
+                LayoutSubview (to, from.ContentSize);
             }
         }
 
         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>
@@ -969,19 +936,19 @@ public partial class View
     /// <remarks>
     ///     <para>
     ///         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>
     /// </remarks>
     internal void OnResizeNeeded ()
     {
         // TODO: Identify a real-world use-case where this API should be 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).
-        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.
         if (IsInitialized)
@@ -997,6 +964,7 @@ public partial class View
             SetNeedsLayout ();
         }
     }
+    internal bool LayoutNeeded { get; private set; } = true;
 
     /// <summary>
     ///     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>
-    ///     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>
-    /// <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>
-    internal void SetRelativeLayout (Rectangle superviewBounds)
+    internal void SetRelativeLayout (Size superviewContentSize)
     {
         Debug.Assert (_x is { });
         Debug.Assert (_y is { });
@@ -1051,13 +1026,13 @@ public partial class View
         // TODO: View.AutoSize stuff is removed.
 
         // 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 Dim (View.Width or View.Height)
         // This method is called recursively if pos is Pos.PosCombine
         (int newLocation, int newDimension) GetNewLocationAndDimension (
             bool width,
-            Rectangle superviewBounds,
+            Size superviewContentSize,
             Pos pos,
             Dim dim,
             int autosizeDimension
@@ -1119,7 +1094,7 @@ public partial class View
             }
 
             int newDimension, newLocation;
-            int superviewDimension = width ? superviewBounds.Width : superviewBounds.Height;
+            int superviewDimension = width ? superviewContentSize.Width : superviewContentSize.Height;
 
             // Determine new location
             switch (pos)
@@ -1143,7 +1118,7 @@ public partial class View
 
                     (left, newDimension) = GetNewLocationAndDimension (
                                                                        width,
-                                                                       superviewBounds,
+                                                                       superviewContentSize,
                                                                        combine._left,
                                                                        dim,
                                                                        autosizeDimension
@@ -1151,7 +1126,7 @@ public partial class View
 
                     (right, newDimension) = GetNewLocationAndDimension (
                                                                         width,
-                                                                        superviewBounds,
+                                                                        superviewContentSize,
                                                                         combine._right,
                                                                         dim,
                                                                         autosizeDimension
@@ -1193,10 +1168,10 @@ public partial class View
         }
 
         // horizontal/width
-        (newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width);
+        (newX, newW) = GetNewLocationAndDimension (true, superviewContentSize, _x, _width, autosize.Width);
 
         // 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);
 
@@ -1204,7 +1179,7 @@ public partial class View
         {
             // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making
             // the view LayoutStyle.Absolute.
-            _frame = r;
+            SetFrame (r);
 
             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
                 // the view LayoutStyle.Absolute.
-                _frame = _frame with { Size = autosize };
+                SetFrame (_frame with { Size = autosize });
 
                 if (autosize.Width == 0)
                 {
@@ -1417,17 +1392,6 @@ public partial class View
         return result;
     } // TopologicalSort
 
-    private void LayoutSubview (View v, Rectangle contentArea)
-    {
-        //if (v.LayoutStyle == LayoutStyle.Computed) {
-        v.SetRelativeLayout (contentArea);
-
-        //}
-
-        v.LayoutSubviews ();
-        v.LayoutNeeded = false;
-    }
-
     #region Diagnostics
 
     // 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; }
 
     #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
 ///         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
-///         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.
 ///     </para>
 ///     <para>
@@ -73,7 +73,7 @@ namespace Terminal.Gui;
 ///         a View can be accessed with the <see cref="SuperView"/> property.
 ///     </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()"/>.
 ///     </para>
@@ -234,7 +234,7 @@ public partial class View : Responder, ISupportInitializeNotification
 
         // TODO: Move these into ViewText.cs as EndInit_Text() to consolodate.
         // 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);
         UpdateTextFormatterText ();
         OnResizeNeeded ();

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

@@ -39,7 +39,7 @@ public partial class View
 
     /// <summary>
     ///     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>
     /// <remarks>
     ///     <para>
@@ -55,7 +55,7 @@ public partial class View
     public Margin Margin { get; private set; }
 
     /// <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
     ///     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.
@@ -116,7 +116,7 @@ public partial class View
     }
 
     /// <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"/>.
     /// </summary>
     /// <remarks>
@@ -151,7 +151,7 @@ public partial class View
 
         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.Y = 0;
             Margin.Width = Frame.Size.Width;
@@ -170,7 +170,7 @@ public partial class View
 
         if (border != Border.Frame)
         {
-            Border._frame = border;
+            Border.SetFrame (border);
             Border.X = border.Location.X;
             Border.Y = border.Location.Y;
             Border.Width = border.Size.Width;
@@ -189,7 +189,7 @@ public partial class View
 
         if (padding != Padding.Frame)
         {
-            Padding._frame = padding;
+            Padding.SetFrame (padding);
             Padding.X = padding.Location.X;
             Padding.Y = padding.Location.Y;
             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
 {
@@ -54,67 +56,108 @@ public partial class View
     public bool SubViewNeedsDisplay { get; private set; }
 
     /// <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
     ///     called to render the borders.
     /// </summary>
     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;
         }
 
-        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)
         {
             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.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>
     ///     The current screen-relative clip region, which can be then re-applied by setting
     ///     <see cref="ConsoleDriver.Clip"/>.
     /// </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)
         {
@@ -122,7 +165,18 @@ public partial class View
         }
 
         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;
     }
@@ -133,7 +187,7 @@ public partial class View
     /// </summary>
     /// <remarks>
     ///     <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).
     ///     </para>
     ///     <para>
@@ -142,7 +196,8 @@ public partial class View
     ///     </para>
     ///     <para>
     ///         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>
     /// </remarks>
     public void Draw ()
@@ -154,21 +209,24 @@ public partial class View
 
         OnDrawAdornments ();
 
-        Rectangle prevClip = ClipToBounds ();
-
         if (ColorScheme is { })
         {
             //Driver.SetAttribute (HasFocus ? GetFocusColor () : 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
-        var dev = new DrawEventArgs (Bounds);
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
         DrawContent?.Invoke (this, dev);
 
         if (!dev.Cancel)
         {
-            OnDrawContent (Bounds);
+            OnDrawContent (Viewport);
         }
 
         if (Driver is { })
@@ -179,7 +237,7 @@ public partial class View
         OnRenderLineCanvas ();
 
         // 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.
         ClearLayoutNeeded ();
@@ -264,14 +322,13 @@ public partial class View
 
     /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
     /// <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.
     /// </returns>
     public virtual Attribute GetFocusColor ()
     {
         ColorScheme cs = ColorScheme;
-
         if (ColorScheme is null)
         {
             cs = new ();
@@ -316,19 +373,33 @@ public partial class View
         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)
         {
-            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);
+
+        return true;
     }
 
     // TODO: Make this cancelable
@@ -347,26 +418,51 @@ public partial class View
 
         // Each of these renders lines to either this View's LineCanvas 
         // 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;
     }
 
-    /// <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>
-    /// <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 (SuperView is { })
             {
-                Clear (contentArea);
+                Clear ();
             }
 
             if (!string.IsNullOrEmpty (TextFormatter.Text))
@@ -378,8 +474,11 @@ public partial class View
             }
 
             // 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 (
-                                 BoundsToScreen (contentArea),
+                                 drawRect,
                                  HasFocus ? GetFocusColor () : GetNormalColor (),
                                  HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
                                  Rectangle.Empty
@@ -387,6 +486,7 @@ public partial class View
             SetSubViewNeedsDisplay ();
         }
 
+        // TODO: Move drawing of subviews to a separate OnDrawSubviews virtual method
         // Draw subviews
         // TODO: Implement OnDrawSubviews (cancelable);
         if (_subviews is { } && SubViewNeedsDisplay)
@@ -395,39 +495,25 @@ public partial class View
                                                                      view => view.Visible
                                                                              && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
                                                                     );
-
             foreach (View view in subviewsNeedingDraw)
             {
-                //view.Frame.IntersectsWith (bounds)) {
-                // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
                 if (view.LayoutNeeded)
                 {
                     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 ();
-
-                //}
             }
         }
     }
 
     /// <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>
-    /// <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"/>
     /// </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
     /// <summary>
@@ -438,13 +524,13 @@ public partial class View
     /// <returns></returns>
     public virtual bool OnRenderLineCanvas ()
     {
-        if (!IsInitialized)
+        if (!IsInitialized || Driver is null)
         {
             return false;
         }
 
         // 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 ())
             {
@@ -484,7 +570,7 @@ public partial class View
         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>
     ///     If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), this method
     ///     does nothing.
@@ -493,21 +579,28 @@ public partial class View
     {
         if (IsInitialized)
         {
-            SetNeedsDisplay (Bounds);
+            SetNeedsDisplay (Viewport);
         }
     }
 
     /// <summary>Expands the area of this view needing to be redrawn to include <paramref name="region"/>.</summary>
     /// <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>
-    /// <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)
     {
         if (!IsInitialized)
         {
             _needsDisplayRect = region;
+
             return;
         }
 
@@ -572,25 +665,7 @@ public partial class View
 
         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>
 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; }
 
     /// <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>
 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>
 public class DrawEventArgs : EventArgs
 {
     /// <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"/>.
     /// </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>
     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>

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

@@ -21,12 +21,12 @@ public enum HighlightStyle
 #endif
 
     /// <summary>
-    /// The mouse is pressed within the <see cref="View.Bounds"/>.
+    /// The mouse is pressed within the <see cref="View.Viewport"/>.
     /// </summary>
     Pressed = 2,
 
     /// <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>
     PressedOutside = 4
 }
@@ -36,6 +36,10 @@ public enum HighlightStyle
 /// </summary>
 public class HighlightEventArgs : CancelEventArgs
 {
+    /// <summary>
+    /// Constructs a new instance of <see cref="HighlightEventArgs"/>.
+    /// </summary>
+    /// <param name="style"></param>
     public HighlightEventArgs (HighlightStyle style)
     {
         HighlightStyle = style;
@@ -63,7 +67,7 @@ public partial class View
     public virtual bool WantMousePositionReports { get; set; }
 
     /// <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
     ///     the view.
     /// </summary>
@@ -110,7 +114,7 @@ public partial class View
     }
 
     /// <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
     ///     the view.
     /// </summary>
@@ -119,7 +123,7 @@ public partial class View
     /// Override this method or subscribe to <see cref="MouseEnter"/> to change the default enter behavior.
     /// </para>
     /// <para>
-    ///     The coordinates are relative to <see cref="View.Bounds"/>.
+    ///     The coordinates are relative to <see cref="View.Viewport"/>.
     /// </para>
     /// </remarks>
     /// <param name="mouseEvent"></param>
@@ -133,12 +137,12 @@ public partial class View
         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;
 
 
     /// <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.
     /// </summary>
     /// <remarks>
@@ -180,15 +184,15 @@ public partial class View
         return false;
     }
     /// <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.
     /// </summary>
     /// <remarks>
     /// <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>
-    ///     The coordinates are relative to <see cref="View.Bounds"/>.
+    ///     The coordinates are relative to <see cref="View.Viewport"/>.
     /// </para>
     /// </remarks>
     /// <param name="mouseEvent"></param>
@@ -211,7 +215,7 @@ public partial class View
         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;
 
     /// <summary>
@@ -227,7 +231,7 @@ public partial class View
     ///         mouse buttons was clicked, it calls <see cref="OnMouseClick"/> to process the click.
     ///     </para>
     ///     <para>
-    ///         See <see cref="SetHighlight"/> and <see cref="DisableHighlight"/> for more information.
+    ///         See <see cref="SetHighlight"/> for more information.
     ///     </para>
     ///     <para>
     ///         If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, the <see cref="OnMouseClick"/> event
@@ -307,7 +311,7 @@ public partial class View
     ///     </para>
     /// </remarks>
     /// <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)
     {
         if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
@@ -325,9 +329,11 @@ public partial class View
                     // Set the focus, but don't invoke Accept
                     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)
                 {
@@ -408,7 +414,7 @@ public partial class View
             }
 
             // 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));
             }
@@ -489,9 +495,9 @@ public partial class View
                     };
                     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;
     }
 
-    /// <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>
     ///     <para>
-    ///         The coordinates are relative to <see cref="View.Bounds"/>.
+    ///         The coordinates are relative to <see cref="View.Viewport"/>.
     ///     </para>
     /// </remarks>
     /// <param name="mouseEvent"></param>
@@ -546,7 +552,7 @@ public partial class View
     /// <summary>Event fired when a mouse event occurs.</summary>
     /// <remarks>
     ///     <para>
-    ///         The coordinates are relative to <see cref="View.Bounds"/>.
+    ///         The coordinates are relative to <see cref="View.Viewport"/>.
     ///     </para>
     /// </remarks>
     public event EventHandler<MouseEventEventArgs> MouseEvent;
@@ -564,9 +570,7 @@ public partial class View
         if (!Enabled)
         {
             // QUESTION: Is this right? Should a disabled view eat mouse clicks?
-            args.Handled = true;
-
-            return true;
+            return args.Handled = true;
         }
 
         MouseClick?.Invoke (this, args);
@@ -592,7 +596,7 @@ public partial class View
     ///         <see cref="MouseEvent.Flags"/> to see which button was clicked.
     ///     </para>
     ///     <para>
-    ///         The coordinates are relative to <see cref="View.Bounds"/>.
+    ///         The coordinates are relative to <see cref="View.Viewport"/>.
     ///     </para>
     /// </remarks>
     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 ()
     {
         if (_subviews is null)
@@ -485,7 +493,7 @@ public partial class View
         {
             return true;
         }
-
+        
         return false;
     }
 
@@ -855,16 +863,13 @@ public partial class View
             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 { })
         {
             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 ();
         }

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

@@ -30,10 +30,10 @@ public partial class View
     ///         <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
     ///     </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.
     ///     </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>
     /// </remarks>
     public virtual string Text
@@ -80,7 +80,7 @@ public partial class View
     ///     redisplay the <see cref="View"/>.
     /// </summary>
     /// <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>
     /// <value>The text alignment.</value>
     public virtual TextAlignment TextAlignment
@@ -99,7 +99,7 @@ public partial class View
     ///     <see cref="View"/>.
     /// </summary>
     /// <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>
     /// <value>The text alignment.</value>
     public virtual TextDirection TextDirection
@@ -120,7 +120,7 @@ public partial class View
     ///     redisplay the <see cref="View"/>.
     /// </summary>
     /// <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>
     /// <value>The text alignment.</value>
     public virtual VerticalTextAlignment VerticalTextAlignment
@@ -134,7 +134,7 @@ public partial class View
     }
 
     /// <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="HotKeySpecifier"/> characters.
     /// </summary>
@@ -146,8 +146,8 @@ public partial class View
 
         if (IsInitialized)
         {
-            x = Bounds.X;
-            y = Bounds.Y;
+            x = Viewport.X;
+            y = Viewport.Y;
         }
 
         Rectangle rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
@@ -225,7 +225,7 @@ public partial class View
     }
 
     /// <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"/>.
     /// </summary>
     /// <remarks>
@@ -244,14 +244,14 @@ public partial class View
 
         if (string.IsNullOrEmpty (TextFormatter.Text))
         {
-            TextFormatter.Size = Bounds.Size;
+            TextFormatter.Size = ContentSize;
 
             return;
         }
 
         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");
         }
 
-        // 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>
         // Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
         // </summary>
         // <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>
         // 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.
@@ -324,7 +324,7 @@ public partial class View
                 return false;
             }
 
-            sizeRequired = Bounds.Size;
+            sizeRequired = ContentSize;
 
             if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text))
             {
@@ -336,11 +336,11 @@ public partial class View
                 case true:
                     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
-                        && (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;
                     }
@@ -349,7 +349,7 @@ public partial class View
                 default:
                     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;
                     }
@@ -365,7 +365,7 @@ public partial class View
             // TODO: This is a hack.
             //_width  = size.Width;
             //_height = size.Height;
-            _frame = new (_frame.Location, size);
+            SetFrame (new (_frame.Location, size));
 
             //throw new InvalidOperationException ("This is a hack.");
             return true;
@@ -392,7 +392,7 @@ public partial class View
         }
         else if (AutoSize && directionChanged && IsAdded)
         {
-            ResizeBoundsToFit (Bounds.Size);
+            ResizeViewportToFit (Viewport.Size);
         }
 
         SetTextFormatterSize ();

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

@@ -33,9 +33,6 @@ public class Button : View
     private readonly Rune _rightDefault;
     private bool _isDefault;
 
-    /// <inheritdoc />
-    private bool _wantContinuousButtonPressed;
-
     /// <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>
     public Button ()
@@ -59,10 +56,10 @@ public class Button : View
 #endif
         // Override default behavior of View
         AddCommand (Command.HotKey, () =>
-                                     {
-                                         SetFocus ();
-                                         return !OnAccept ();
-                                     });
+        {
+            SetFocus ();
+            return !OnAccept ();
+        });
 
         KeyBindings.Add (Key.Space, Command.HotKey);
         KeyBindings.Add (Key.Enter, Command.HotKey);
@@ -71,6 +68,8 @@ public class Button : View
         MouseClick += Button_MouseClick;
     }
 
+    private bool _wantContinuousButtonPressed;
+
     /// <inheritdoc />
     public override bool WantContinuousButtonPressed
     {
@@ -97,7 +96,7 @@ public class Button : View
 
     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)
@@ -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;
                              Height = _rows * BoxHeight + thickness.Horizontal;
                          };
-//        MouseEvent += ColorPicker_MouseEvent;
+
         MouseClick += ColorPicker_MouseClick;
     }
 
     // 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)
     {
@@ -181,16 +168,16 @@ public class ColorPicker : View
     }
 
     ///<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 ());
         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;
                 Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex));

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

@@ -245,8 +245,8 @@ public class ComboBox : View
     /// <inheritdoc/>
     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
             && _autoHide)
         {
@@ -284,9 +284,9 @@ public class ComboBox : View
     public virtual void OnCollapsed () { Collapsed?.Invoke (this, EventArgs.Empty); }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
         if (!_autoHide)
         {
@@ -294,7 +294,7 @@ public class ComboBox : View
         }
 
         Driver.SetAttribute (ColorScheme.Focus);
-        Move (Bounds.Right - 1, 0);
+        Move (Viewport.Right - 1, 0);
         Driver.AddRune (Glyphs.DownArrow);
     }
 
@@ -401,15 +401,15 @@ public class ComboBox : View
     /// <returns></returns>
     private int CalculatetHeight ()
     {
-        if (!IsInitialized || Bounds.Height == 0)
+        if (!IsInitialized || Viewport.Height == 0)
         {
             return 0;
         }
 
         return Math.Min (
-                         Math.Max (Bounds.Height - 1, _minimumHeight - 1),
+                         Math.Max (Viewport.Height - 1, _minimumHeight - 1),
                          _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);
-        _listview.Clear (_listview.IsInitialized ? _listview.Bounds : Rectangle.Empty);
+        _listview.Clear ();
         _listview.TabStop = false;
         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);
         OnCollapsed ();
     }
@@ -607,18 +607,18 @@ public class ComboBox : View
 
     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;
         }
 
-        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 ();
-            _search.SetRelativeLayout (Bounds);
-            _listview.SetRelativeLayout (Bounds);
+            _search.SetRelativeLayout (ContentSize);
+            _listview.SetRelativeLayout (ContentSize);
         }
     }
 
@@ -761,7 +761,7 @@ public class ComboBox : View
     private void ShowList ()
     {
         _listview.SetSource (_searchset);
-        _listview.Clear (Bounds); // Ensure list shrinks in Dialog as you type
+        _listview.Clear (); 
         _listview.Height = CalculatetHeight ();
         SuperView?.BringSubviewToFront (this);
     }
@@ -839,7 +839,7 @@ public class ComboBox : View
             return res;
         }
 
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
             Attribute current = ColorScheme.Focus;
             Driver.SetAttribute (current);

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

@@ -56,6 +56,7 @@ public class Dialog : Window
     /// </remarks>
     public Dialog ()
     {
+        Arrangement = ViewArrangement.Movable;
         X = Pos.Center ();
         Y = Pos.Center ();
         ValidatePosDim = true;
@@ -201,7 +202,7 @@ public class Dialog : Window
         {
             case ButtonAlignments.Center:
                 // 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--)
                 {
@@ -214,7 +215,7 @@ public class Dialog : Window
                     }
                     else
                     {
-                        button.X = Bounds.Width - shiftLeft;
+                        button.X = Viewport.Width - shiftLeft;
                     }
 
                     button.Y = Pos.AnchorEnd (1);
@@ -226,7 +227,7 @@ public class Dialog : Window
                 // Justify Buttons
                 // 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--)
                 {
@@ -242,7 +243,7 @@ public class Dialog : Window
                         if (i == 0)
                         {
                             // first (leftmost) button 
-                            int left = Bounds.Width;
+                            int left = Viewport.Width;
                             button.X = Pos.AnchorEnd (left);
                         }
                         else

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

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

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

@@ -18,7 +18,7 @@ public interface IAnnotation
 
     /// <summary>
     ///     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>
     /// <param name="graph"></param>
     void Render (GraphView graph);
@@ -62,7 +62,7 @@ public class TextAnnotation : IAnnotation
 
     /// <summary>
     ///     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>
     /// <param name="graph"></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)
     {
         // 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;
         }
@@ -83,7 +83,7 @@ public class TextAnnotation : IAnnotation
 
         graph.Move (x, y);
 
-        int availableWidth = graph.Bounds.Width - x;
+        int availableWidth = graph.Viewport.Width - x;
 
         if (availableWidth <= 0)
         {
@@ -127,7 +127,7 @@ public class LegendAnnotation : View, IAnnotation
     /// <summary>Returns false i.e. Legends render after series</summary>
     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>
     public void Render (GraphView graph)
     {
@@ -165,13 +165,13 @@ public class LegendAnnotation : View, IAnnotation
             // add the text
             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);
 
             linesDrawn++;
 
             // Legend has run out of space
-            if (linesDrawn >= Bounds.Height)
+            if (linesDrawn >= Viewport.Height)
             {
                 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="text">
     ///     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>
     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;
 
             // 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!
             if (xSpaceAvailable <= 0)
@@ -127,7 +127,7 @@ public class HorizontalAxis : Axis
                 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);
         }
     }
@@ -140,9 +140,9 @@ public class HorizontalAxis : Axis
             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)
         {
@@ -155,12 +155,12 @@ public class HorizontalAxis : Axis
             string toRender = Text;
 
             // 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);
         }
     }
@@ -174,7 +174,7 @@ public class HorizontalAxis : Axis
             return;
         }
 
-        Rectangle bounds = graph.Bounds;
+        Rectangle bounds = graph.Viewport;
 
         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
         // 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>
@@ -225,7 +225,7 @@ public class HorizontalAxis : Axis
         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 (Increment == 0)
@@ -237,7 +237,7 @@ public class HorizontalAxis : Axis
         int y = GetAxisYPosition (graph);
 
         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
         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;
 
             current.X += Increment;
@@ -317,7 +317,7 @@ public class VerticalAxis : Axis
             return;
         }
 
-        Rectangle bounds = graph.Bounds;
+        Rectangle bounds = graph.Viewport;
         IEnumerable<AxisIncrementToRender> labels = GetLabels (graph, bounds);
 
         foreach (AxisIncrementToRender label in labels)
@@ -331,13 +331,13 @@ public class VerticalAxis : Axis
             string toRender = Text;
 
             // 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
-            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++)
             {
@@ -356,7 +356,7 @@ public class VerticalAxis : Axis
             return;
         }
 
-        Rectangle bounds = graph.Bounds;
+        Rectangle bounds = graph.Viewport;
 
         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
         // 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>
@@ -409,7 +409,7 @@ public class VerticalAxis : Axis
             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)

+ 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,
 
                           // 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/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         if (CellSize.X == 0 || CellSize.Y == 0)
         {
@@ -209,10 +209,10 @@ public class GraphView : View
         Move (0, 0);
 
         // clear all old content
-        for (var i = 0; i < Bounds.Height; i++)
+        for (var i = 0; i < Viewport.Height; 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
@@ -222,8 +222,8 @@ public class GraphView : View
         }
 
         // 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 (graphScreenWidth < 0 || graphScreenHeight < 0)
@@ -287,10 +287,10 @@ public class GraphView : View
     }
 
     /// <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>
-    public void PageUp () { Scroll (0, CellSize.Y * Bounds.Height); }
+    public void PageUp () { Scroll (0, CellSize.Y * Viewport.Height); }
 
     /// <summary>
     ///     Clears all settings configured on the graph and resets all properties to default values (
@@ -316,7 +316,7 @@ public class GraphView : View
     {
         return new (
                     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.Y
                    );

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

@@ -192,7 +192,7 @@ public class BarSeries : ISeries
                 screenStart.X = graph.AxisY.GetAxisXPosition (graph);
 
                 // 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 (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/>
     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)
             && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
@@ -351,14 +351,14 @@ public class HexView : View
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         Attribute currentAttribute;
         Attribute current = ColorScheme.Focus;
         Driver.SetAttribute (current);
         Move (0, 0);
 
-        // BUGBUG: Bounds!!!!
+        // BUGBUG: Viewport!!!!
         Rectangle frame = Frame;
 
         int nblocks = bytesPerLine / bsize;
@@ -373,7 +373,7 @@ public class HexView : View
         {
             Rectangle lineRect = new (0, line, frame.Width, 1);
 
-            if (!Bounds.Contains (lineRect))
+            if (!Viewport.Contains (lineRect))
             {
                 continue;
             }
@@ -611,15 +611,15 @@ public class HexView : View
         // Small buffers will just show the position, with the bsize field value (4 bytes)
         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)
     {
-        // BUGBUG: Bounds!
+        // BUGBUG: Viewport!
         RedisplayLine (position);
 
         if (position + bytes < source.Length)
@@ -657,7 +657,7 @@ public class HexView : View
     {
         position = source.Length;
 
-        // BUGBUG: Bounds!
+        // BUGBUG: Viewport!
         if (position >= DisplayStart + bytesPerLine * Frame.Height)
         {
             SetDisplayStart (position);
@@ -744,7 +744,7 @@ public class HexView : View
             position++;
         }
 
-        // BUGBUG: Bounds!
+        // BUGBUG: Viewport!
         if (position >= DisplayStart + bytesPerLine * Frame.Height)
         {
             SetDisplayStart (DisplayStart + bytesPerLine);
@@ -793,7 +793,7 @@ public class HexView : View
         var delta = (int)(pos - DisplayStart);
         int line = delta / bytesPerLine;
 
-        // BUGBUG: Bounds!
+        // BUGBUG: Viewport!
         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; }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         LineCanvas lc = LineCanvas;
 
@@ -26,7 +26,7 @@ public class Line : View
             lc = adornment.Parent.LineCanvas;
         }
         lc.AddLine (
-                    BoundsToScreen (contentArea).Location,
+                    ViewportToScreen (viewport).Location,
                     Orientation == Orientation.Horizontal ? Frame.Width : Frame.Height,
                     Orientation,
                     BorderStyle

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

@@ -54,16 +54,16 @@ public class LineView : View
     public Rune? StartingAnchor { get; set; }
 
     /// <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);
         Driver.SetAttribute (GetNormalColor ());
 
         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)
         {

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

@@ -1,4 +1,5 @@
 using System.Collections;
+using static Terminal.Gui.SpinnerStyle;
 
 namespace Terminal.Gui;
 
@@ -94,7 +95,10 @@ public class ListView : View
     private int _lastSelectedItem = -1;
     private int _selected = -1;
     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>
     ///     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
         AddCommand (Command.LineUp, () => MoveUp ());
         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.PageDown, () => MovePageDown ());
         AddCommand (Command.TopHome, () => MoveHome ());
@@ -117,6 +121,9 @@ public class ListView : View
         AddCommand (Command.OpenSelectedItem, () => OnOpenSelectedItem ());
         AddCommand (Command.Select, () => MarkUnmarkRow ());
 
+        AddCommand (Command.ScrollLeft, () => ScrollHorizontal (-1));
+        AddCommand (Command.ScrollRight, () => ScrollHorizontal (1));
+
         // Default keybindings for all ListViews
         KeyBindings.Add (Key.CursorUp, Command.LineUp);
         KeyBindings.Add (Key.P.WithCtrl, Command.LineUp);
@@ -199,7 +206,7 @@ public class ListView : View
     /// <value>The left position.</value>
     public int LeftItem
     {
-        get => _left;
+        get => Viewport.X;
         set
         {
             if (_source is null)
@@ -212,7 +219,7 @@ public class ListView : View
                 throw new ArgumentException ("value");
             }
 
-            _left = value;
+            Viewport = Viewport with { X = value };
             SetNeedsDisplay ();
         }
     }
@@ -250,20 +257,34 @@ public class ListView : View
         get => _source;
         set
         {
+            if (_source == value)
+
+            {
+                return;
+            }
             _source = value;
+
+            ContentSize = new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width);
+            if (IsInitialized)
+            {
+                Viewport = Viewport with { Y = 0 };
+            }
+
             KeystrokeNavigator.Collection = _source?.ToList ();
-            _top = 0;
             _selected = -1;
             _lastSelectedItem = -1;
             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>
     public int TopItem
     {
-        get => _top;
+        get => Viewport.Y;
         set
         {
             if (_source is null)
@@ -271,13 +292,7 @@ public class ListView : View
                 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 (_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;
@@ -347,7 +363,7 @@ public class ListView : View
     }
 
     /// <inheritdoc/>
-    protected internal override bool OnMouseEvent  (MouseEvent me)
+    protected internal override bool OnMouseEvent (MouseEvent me)
     {
         if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)
             && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
@@ -371,40 +387,40 @@ public class ListView : View
 
         if (me.Flags == MouseFlags.WheeledDown)
         {
-            ScrollDown (1);
+            ScrollVertical (1);
 
             return true;
         }
 
         if (me.Flags == MouseFlags.WheeledUp)
         {
-            ScrollUp (1);
+            ScrollVertical (-1);
 
             return true;
         }
 
         if (me.Flags == MouseFlags.WheeledRight)
         {
-            ScrollRight (1);
+            ScrollHorizontal (1);
 
             return true;
         }
 
         if (me.Flags == MouseFlags.WheeledLeft)
         {
-            ScrollLeft (1);
+            ScrollHorizontal(-1);
 
             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;
         }
 
-        _selected = _top + me.Y;
+        _selected = Viewport.Y + me.Y;
 
         if (AllowsAll ())
         {
@@ -449,13 +465,13 @@ public class ListView : View
             //can move by down by one.
             _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 ();
@@ -466,9 +482,9 @@ public class ListView : View
             OnSelectedChanged ();
             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 ();
         }
 
@@ -483,9 +499,9 @@ public class ListView : View
         {
             _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 ();
@@ -502,7 +518,7 @@ public class ListView : View
         if (_selected != 0)
         {
             _selected = 0;
-            _top = Math.Max (_selected, 0);
+            Viewport = Viewport with { Y = _selected };
             OnSelectedChanged ();
             SetNeedsDisplay ();
         }
@@ -522,7 +538,7 @@ public class ListView : View
             return true;
         }
 
-        int n = _selected + Bounds.Height;
+        int n = _selected + Viewport.Height;
 
         if (n >= _source.Count)
         {
@@ -533,13 +549,13 @@ public class ListView : View
         {
             _selected = n;
 
-            if (_source.Count >= Bounds.Height)
+            if (_source.Count >= Viewport.Height)
             {
-                _top = Math.Max (_selected, 0);
+                Viewport = Viewport with { Y = _selected };
             }
             else
             {
-                _top = 0;
+                Viewport = Viewport with { Y = 0 };
             }
 
             OnSelectedChanged ();
@@ -553,7 +569,7 @@ public class ListView : View
     /// <returns></returns>
     public virtual bool MovePageUp ()
     {
-        int n = _selected - Bounds.Height;
+        int n = _selected - Viewport.Height;
 
         if (n < 0)
         {
@@ -563,7 +579,7 @@ public class ListView : View
         if (n != _selected)
         {
             _selected = n;
-            _top = Math.Max (_selected, 0);
+            Viewport = Viewport with { Y = _selected };
             OnSelectedChanged ();
             SetNeedsDisplay ();
         }
@@ -599,21 +615,21 @@ public class ListView : View
                 _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 ();
             SetNeedsDisplay ();
         }
-        else if (_selected < _top)
+        else if (_selected < Viewport.Y)
         {
-            _top = Math.Max (_selected, 0);
+            Viewport = Viewport with { Y = _selected };
             SetNeedsDisplay ();
         }
 
@@ -621,18 +637,18 @@ public class ListView : View
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
         Attribute current = ColorScheme.Focus;
         Driver.SetAttribute (current);
         Move (0, 0);
-        Rectangle f = Bounds;
-        int item = _top;
+        Rectangle f = Viewport;
+        int item = Viewport.Y;
         bool focused = HasFocus;
         int col = _allowsMarking ? 2 : 0;
-        int start = _left;
+        int start = Viewport.X;
 
         for (var row = 0; row < f.Height; row++, item++)
         {
@@ -769,57 +785,17 @@ public class ListView : View
     {
         if (_allowsMarking)
         {
-            Move (0, _selected - _top);
+            Move (0, _selected - Viewport.Y);
         }
         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>
     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>
     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.Closing += Container_Closing;
         _container.Deactivate += Container_Deactivate;
-        Rectangle frame = Application.Driver.Bounds;
+        Rectangle frame = Application.Driver.Screen;
         Point position = Position;
 
         if (Host is { })
         {
-            Point pos = Host.BoundsToScreen (frame).Location;
+            Point pos = Host.ViewportToScreen (frame).Location;
             pos.Y += Host.Frame.Height - 1;
 
             if (position != pos)
@@ -186,7 +186,7 @@ public sealed class ContextMenu : IDisposable
                 }
                 else
                 {
-                    Point pos = Host.BoundsToScreen (frame).Location;
+                    Point pos = Host.ViewportToScreen (frame).Location;
                     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;
 
-        Point boundsPoint = view.ScreenToBounds (a.X, a.Y);
+        Point boundsPoint = view.ScreenToViewport (a.X, a.Y);
         var me = new MouseEvent
         {
             X = boundsPoint.X,
@@ -757,7 +757,7 @@ internal sealed class Menu : View
         return !item.IsEnabled () ? ColorScheme.Disabled : GetNormalColor ();
     }
 
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         if (_barItems.Children is null)
         {
@@ -771,14 +771,14 @@ internal sealed class Menu : View
         OnDrawAdornments ();
         OnRenderLineCanvas ();
 
-        for (int i = Bounds.Y; i < _barItems.Children.Length; i++)
+        for (int i = Viewport.Y; i < _barItems.Children.Length; i++)
         {
             if (i < 0)
             {
                 continue;
             }
 
-            if (BoundsToScreen (Bounds).Y + i >= Driver.Rows)
+            if (ViewportToScreen (Viewport).Y + i >= Driver.Rows)
             {
                 break;
             }
@@ -792,7 +792,8 @@ internal sealed class Menu : View
 
             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);
             }
             else if (Frame.X < Driver.Cols)
@@ -802,7 +803,7 @@ internal sealed class Menu : View
 
             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
                 if (p < 0)
@@ -810,7 +811,7 @@ internal sealed class Menu : View
                     continue;
                 }
 
-                if (BoundsToScreen (Bounds).X + p >= Driver.Cols)
+                if (ViewportToScreen (Viewport).X + p >= Driver.Cols)
                 {
                     break;
                 }
@@ -839,7 +840,8 @@ internal sealed class Menu : View
             {
                 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);
                 }
 
@@ -875,7 +877,7 @@ internal sealed class Menu : View
                 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)
             {
                 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)
                     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.HotFocus : ColorScheme.HotNormal,
-                             SuperView?.BoundsToScreen (SuperView.Bounds) ?? Rectangle.Empty
+                             SuperView?.ViewportToScreen (SuperView.Viewport) ?? Rectangle.Empty
                             );
                 }
                 else
@@ -913,7 +915,7 @@ internal sealed class Menu : View
                             ? item.Help.GetColumns ()
                             : item.Help.GetColumns () + item.ShortcutTag.GetColumns () + 2;
                 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)
                 {
@@ -939,7 +941,7 @@ internal sealed class Menu : View
     {
         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;
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
-        Move (0, 0);
         Driver.SetAttribute (GetNormalColor ());
 
-        for (var i = 0; i < Frame.Width; i++)
-        {
-            Driver.AddRune ((Rune)' ');
-        }
+        Clear ();
 
         var pos = 0;
 
@@ -824,13 +820,13 @@ public class MenuBar : View
             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;
-        Point boundsOffset = sv.GetBoundsOffset ();
+        Point viewportOffset = sv.GetViewportOffsetFromFrame ();
 
         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>
     internal Point GetScreenOffsetFromCurrent ()
     {
-        Rectangle screen = Driver.Bounds;
+        Rectangle screen = Driver.Screen;
         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)
@@ -1319,7 +1315,7 @@ public class MenuBar : View
 
         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 };
             menu.Run (mi.Action);
             menu.Dispose ();
@@ -1689,7 +1685,7 @@ public class MenuBar : View
                     {
                         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] };
                             menu.Run (Menus [i].Action);
                             menu.Dispose ();

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

@@ -422,7 +422,7 @@ public static class MessageBox
             }
 
             // 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 ();
 
@@ -467,7 +467,7 @@ public static class MessageBox
                                      + adornmentsThickness.Vertical);
             }
 
-            d.SetRelativeLayout (d.SuperView?.Frame ?? Application.Top.Frame);
+            d.SetRelativeLayout (d.SuperView?.ContentSize ?? Application.Top.ContentSize);
             d.LayoutSubviews ();
         }
     }

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

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

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

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

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

@@ -152,14 +152,14 @@ public class ScrollBarView : View
                 _keepContentAlwaysInViewport = value;
                 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)
@@ -204,13 +204,12 @@ public class ScrollBarView : View
         get => _position;
         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)
             {
-                SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+                SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size);
                 ShowHideScrollBars (false);
                 SetNeedsDisplay ();
             }
@@ -275,7 +274,7 @@ public class ScrollBarView : View
     public event EventHandler ChangedPosition;
 
     /// <inheritdoc/>
-    protected internal override bool OnMouseEvent  (MouseEvent mouseEvent)
+    protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
     {
         if (mouseEvent.Flags != MouseFlags.Button1Pressed
             && mouseEvent.Flags != MouseFlags.Button1DoubleClicked
@@ -301,7 +300,7 @@ public class ScrollBarView : View
         }
 
         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 posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1;
         barsize -= 2;
@@ -449,7 +448,7 @@ public class ScrollBarView : View
     public virtual void OnChangedPosition () { ChangedPosition?.Invoke (this, EventArgs.Empty); }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         if (ColorScheme is null || ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible))
         {
@@ -461,7 +460,7 @@ public class ScrollBarView : View
             return;
         }
 
-        if (Size == 0 || (_vertical && Bounds.Height == 0) || (!_vertical && Bounds.Width == 0))
+        if (Size == 0 || (_vertical && Viewport.Height == 0) || (!_vertical && Viewport.Width == 0))
         {
             return;
         }
@@ -470,13 +469,13 @@ public class ScrollBarView : View
 
         if (_vertical)
         {
-            if (Bounds.Right < Bounds.Width - 1)
+            if (Viewport.Right < Viewport.Width - 1)
             {
                 return;
             }
 
-            int col = Bounds.Width - 1;
-            int bh = Bounds.Height;
+            int col = Viewport.Width - 1;
+            int bh = Viewport.Height;
             Rune special;
 
             if (bh < 4)
@@ -486,7 +485,7 @@ public class ScrollBarView : View
 
                 Move (col, 0);
 
-                if (Bounds.Height == 1)
+                if (Viewport.Height == 1)
                 {
                     Driver.AddRune (Glyphs.Diamond);
                 }
@@ -495,15 +494,15 @@ public class ScrollBarView : View
                     Driver.AddRune (Glyphs.UpArrow);
                 }
 
-                if (Bounds.Height == 3)
+                if (Viewport.Height == 3)
                 {
                     Move (col, 1);
                     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);
                 }
             }
@@ -524,8 +523,7 @@ public class ScrollBarView : View
                     by1 = Math.Max (by1 - 1, 0);
                 }
 
-                Move (col, 0);
-                Driver.AddRune (Glyphs.UpArrow);
+                AddRune (col, 0, Glyphs.UpArrow);
 
                 var hasTopTee = false;
                 var hasDiamond = false;
@@ -533,7 +531,6 @@ public class ScrollBarView : View
 
                 for (var y = 0; y < bh; y++)
                 {
-                    Move (col, y + 1);
 
                     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)
                 {
-                    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
         {
-            if (Bounds.Bottom < Bounds.Height - 1)
+            if (Viewport.Bottom < Viewport.Height - 1)
             {
                 return;
             }
 
-            int row = Bounds.Height - 1;
-            int bw = Bounds.Width;
+            int row = Viewport.Height - 1;
+            int bw = Viewport.Width;
             Rune special;
 
             if (bw < 4)
@@ -663,7 +658,7 @@ public class ScrollBarView : View
 
                 if (!hasLeftTee)
                 {
-                    Move (Bounds.Width - 2, row);
+                    Move (Viewport.Width - 2, row);
                     Driver.AddRune (Glyphs.LeftTee);
                 }
 
@@ -685,7 +680,7 @@ public class ScrollBarView : View
 
     internal bool CanScroll (int n, out int max, bool isVertical = false)
     {
-        if (Host?.Bounds.IsEmpty != false)
+        if (Host?.Viewport.IsEmpty != false)
         {
             max = 0;
 
@@ -706,7 +701,7 @@ public class ScrollBarView : View
 
     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)
         {
@@ -845,15 +840,15 @@ public class ScrollBarView : View
 
     private int GetBarsize (bool isVertical)
     {
-        if (Host?.Bounds.IsEmpty != false)
+        if (Host?.Viewport.IsEmpty != false)
         {
             return 0;
         }
 
         return isVertical ? KeepContentAlwaysInViewport
-                                ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1)
+                                ? Host.Viewport.Height + (_showBothScrollIndicator ? -2 : -1)
                                 : 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)
@@ -890,7 +885,7 @@ public class ScrollBarView : View
     private void ScrollBarView_Initialized (object sender, EventArgs e)
     {
         SetWidthHeight ();
-        SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame);
+        SetRelativeLayout (SuperView?.Frame.Size ?? Host?.Frame.Size ?? Frame.Size);
 
         if (OtherScrollBarView is null)
         {
@@ -904,7 +899,22 @@ public class ScrollBarView : View
     // Helper to assist Initialized event handler
     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)
             {
@@ -995,11 +1005,11 @@ public class ScrollBarView : View
         }
 
         SetWidthHeight ();
-        SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+        SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size);
 
         if (_otherScrollBarView is { })
         {
-            OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+            OtherScrollBarView.SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size);
         }
 
         if (_showBothScrollIndicator)

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

@@ -21,7 +21,7 @@ namespace Terminal.Gui;
 ///     <para>
 ///         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="ContentSize"/>.
+///         <see cref="View.ContentSize"/>.
 ///     </para>
 ///     <para>Use the</para>
 /// </remarks>
@@ -33,7 +33,6 @@ public class ScrollView : View
     private bool _autoHideScrollBars = true;
     private View _contentBottomRightCorner;
     private Point _contentOffset;
-    private Size _contentSize;
     private bool _keepContentAlwaysInViewport = true;
     private bool _showHorizontalScrollIndicator;
     private bool _showVerticalScrollIndicator;
@@ -85,14 +84,14 @@ public class ScrollView : View
         AddCommand (Command.ScrollDown, () => ScrollDown (1));
         AddCommand (Command.ScrollLeft, () => ScrollLeft (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
         KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
@@ -134,6 +133,14 @@ public class ScrollView : View
                            _vertical.ChangedPosition += delegate { ContentOffset = new Point (ContentOffset.X, _vertical.Position); };
                            _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)
@@ -194,7 +201,6 @@ public class ScrollView : View
             {
                 // 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));
-                ;
 
                 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>
     public bool KeepContentAlwaysInViewport
@@ -234,26 +240,26 @@ public class ScrollView : View
                 _horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value;
                 Point p = default;
 
-                if (value && -_contentOffset.X + Bounds.Width > _contentSize.Width)
+                if (value && -_contentOffset.X + Viewport.Width > ContentSize.Width)
                 {
                     p = new Point (
-                                   _contentSize.Width - Bounds.Width + (_showVerticalScrollIndicator ? 1 : 0),
+                                   ContentSize.Width - Viewport.Width + (_showVerticalScrollIndicator ? 1 : 0),
                                    -_contentOffset.Y
                                   );
                 }
 
-                if (value && -_contentOffset.Y + Bounds.Height > _contentSize.Height)
+                if (value && -_contentOffset.Y + Viewport.Height > ContentSize.Height)
                 {
                     if (p == default (Point))
                     {
                         p = new Point (
                                        -_contentOffset.X,
-                                       _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0)
+                                       ContentSize.Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0)
                                       );
                     }
                     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/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         SetViewsNeedsDisplay ();
 
-        Rectangle savedClip = ClipToBounds ();
-
         // 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)
         {
@@ -374,8 +378,6 @@ public class ScrollView : View
         }
 
         DrawScrollBars ();
-
-        Driver.Clip = savedClip;
     }
 
     /// <inheritdoc/>
@@ -613,7 +615,7 @@ public class ScrollView : View
     {
         // INTENT: Unclear intent. How about a call to Offset?
         _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);
 
         if (_vertical.Position != p)
@@ -644,7 +646,7 @@ public class ScrollView : View
         bool v = false, h = false;
         var p = false;
 
-        if (Bounds.Height == 0 || Bounds.Height > _contentSize.Height)
+        if (Viewport.Height == 0 || Viewport.Height > ContentSize.Height)
         {
             if (ShowVerticalScrollIndicator)
             {
@@ -653,7 +655,7 @@ public class ScrollView : View
 
             v = false;
         }
-        else if (Bounds.Height > 0 && Bounds.Height == _contentSize.Height)
+        else if (Viewport.Height > 0 && Viewport.Height == ContentSize.Height)
         {
             p = true;
         }
@@ -667,7 +669,7 @@ public class ScrollView : View
             v = true;
         }
 
-        if (Bounds.Width == 0 || Bounds.Width > _contentSize.Width)
+        if (Viewport.Width == 0 || Viewport.Width > ContentSize.Width)
         {
             if (ShowHorizontalScrollIndicator)
             {
@@ -676,7 +678,7 @@ public class ScrollView : View
 
             h = false;
         }
-        else if (Bounds.Width > 0 && Bounds.Width == _contentSize.Width && p)
+        else if (Viewport.Width > 0 && Viewport.Width == ContentSize.Width && p)
         {
             if (ShowHorizontalScrollIndicator)
             {
@@ -728,13 +730,13 @@ public class ScrollView : View
 
         if (v)
         {
-            _vertical.SetRelativeLayout (Bounds);
+            _vertical.SetRelativeLayout (Viewport.Size);
             _vertical.Draw ();
         }
 
         if (h)
         {
-            _horizontal.SetRelativeLayout (Bounds);
+            _horizontal.SetRelativeLayout (Viewport.Size);
             _horizontal.Draw ();
         }
 
@@ -742,7 +744,7 @@ public class ScrollView : View
 
         if (v && h)
         {
-            _contentBottomRightCorner.SetRelativeLayout (Bounds);
+            _contentBottomRightCorner.SetRelativeLayout (Viewport.Size);
             _contentBottomRightCorner.Draw ();
         }
     }

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

@@ -374,7 +374,7 @@ public class Slider<T> : View
     }
 
     /// <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).
     /// </summary>
     /// <remarks>
@@ -663,17 +663,17 @@ public class Slider<T> : View
 
         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 { })
             {
-                // 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)
                 {
-                    size = int.Min (SuperView.Bounds.Width, CalcBestLength ());
+                    size = int.Min (SuperView.Viewport.Width, CalcBestLength ());
                 }
                 else
                 {
-                    size = int.Min (SuperView.Bounds.Height, CalcBestLength ());
+                    size = int.Min (SuperView.Viewport.Height, CalcBestLength ());
                 }
             }
             else
@@ -686,14 +686,14 @@ public class Slider<T> : View
         }
         else
         {
-            // Fit Slider to the Bounds
+            // Fit Slider to the Viewport
             if (_config._sliderOrientation == Orientation.Horizontal)
             {
-                size = Bounds.Width;
+                size = Viewport.Width;
             }
             else
             {
-                size = Bounds.Height;
+                size = Viewport.Height;
             }
         }
 
@@ -777,15 +777,15 @@ public class Slider<T> : View
 
         if (_config._sliderOrientation == Orientation.Horizontal)
         {
-            Bounds = new (
-                          Bounds.Location,
+            Viewport = new (
+                          Viewport.Location,
                           new (
                                int.Min (
-                                        SuperView.Bounds.Width - adornmentsThickness.Horizontal,
+                                        SuperView.Viewport.Width - adornmentsThickness.Horizontal,
                                         CalcBestLength ()
                                        ),
                                int.Min (
-                                        SuperView.Bounds.Height - adornmentsThickness.Vertical,
+                                        SuperView.Viewport.Height - adornmentsThickness.Vertical,
                                         CalcThickness ()
                                        )
                               )
@@ -793,15 +793,15 @@ public class Slider<T> : View
         }
         else
         {
-            Bounds = new (
-                          Bounds.Location,
+            Viewport = new (
+                          Viewport.Location,
                           new (
                                int.Min (
-                                        SuperView.Bounds.Width - adornmentsThickness.Horizontal,
+                                        SuperView.Viewport.Width - adornmentsThickness.Horizontal,
                                         CalcThickness ()
                                        ),
                                int.Min (
-                                        SuperView.Bounds.Height - adornmentsThickness.Vertical,
+                                        SuperView.Viewport.Height - adornmentsThickness.Vertical,
                                         CalcBestLength ()
                                        )
                               )
@@ -988,7 +988,7 @@ public class Slider<T> : View
 
         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);
             }
@@ -996,7 +996,7 @@ public class Slider<T> : View
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         // TODO: make this more surgical to reduce repaint
 
@@ -1009,9 +1009,9 @@ public class Slider<T> : View
 #if (DEBUG)
         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, '·');
             }
@@ -1073,7 +1073,7 @@ public class Slider<T> : View
     private void DrawSlider ()
     {
         // TODO: be more surgical on clear
-        Clear (Bounds);
+        Clear ();
 
         // 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
         if (_config._showEndSpacing)

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

@@ -172,7 +172,7 @@ public class StatusBar : View
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         Move (0, 0);
         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 (!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
             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); }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         Driver.SetAttribute (GetNormalColor ());
 
         if (Tabs.Any ())
         {
-            Rectangle savedClip = ClipToBounds ();
-            _tabsBar.OnDrawContent (contentArea);
+            Rectangle savedClip = SetClip ();
+            _tabsBar.OnDrawContent (viewport);
             _contentView.SetNeedsDisplay ();
             _contentView.Draw ();
             Driver.Clip = savedClip;
@@ -326,7 +326,7 @@ public class TabView : View
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContentComplete (Rectangle contentArea) { _tabsBar.OnDrawContentComplete (contentArea); }
+    public override void OnDrawContentComplete (Rectangle viewport) { _tabsBar.OnDrawContentComplete (viewport); }
 
     /// <summary>
     ///     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;
         }
 
-        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 (contentArea);
+            Clear ();
 
             RenderTabLine ();
 
@@ -670,7 +670,7 @@ public class TabView : View
             Driver.SetAttribute (GetNormalColor ());
         }
 
-        public override void OnDrawContentComplete (Rectangle contentArea)
+        public override void OnDrawContentComplete (Rectangle viewport)
         {
             if (_host._tabLocations is null)
             {
@@ -683,7 +683,7 @@ public class TabView : View
             for (var i = 0; i < tabLocations.Length; i++)
             {
                 View tab = tabLocations [i].Tab;
-                Rectangle vts = tab.BoundsToScreen (tab.Bounds);
+                Rectangle vts = tab.ViewportToScreen (tab.Viewport);
                 var lc = new LineCanvas ();
                 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 :
                                           _host.Style.TabsOnBottom ? 1 : 0;
-                    Rectangle tabsBarVts = BoundsToScreen (Bounds);
+                    Rectangle tabsBarVts = ViewportToScreen (Viewport);
                     int lineLength = tabsBarVts.Right - vts.Right;
 
                     // Right horizontal line
@@ -1238,7 +1238,7 @@ public class TabView : View
 
             View selected = null;
             int topLine = _host.Style.ShowTopLine ? 1 : 0;
-            int width = Bounds.Width;
+            int width = Viewport.Width;
 
             foreach (TabToRender toRender in tabLocations)
             {
@@ -1314,7 +1314,7 @@ public class TabView : View
                 }
 
                 tab.TextFormatter.Draw (
-                                        tab.BoundsToScreen (tab.Bounds),
+                                        tab.ViewportToScreen (tab.Viewport),
                                         prevAttr,
                                         ColorScheme.HotNormal
                                        );
@@ -1360,7 +1360,7 @@ public class TabView : View
             // if there are more tabs to the right not visible
             if (ShouldDrawRightScrollIndicator ())
             {
-                _rightScrollIndicator.X = Bounds.Width - 1;
+                _rightScrollIndicator.X = Viewport.Width - 1;
                 _rightScrollIndicator.Y = y;
 
                 // 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)
         {
-            float f = (float)_tableView.Bounds.Height - _tableView.GetHeaderHeight ();
+            float f = (float)_tableView.Viewport.Height - _tableView.GetHeaderHeight ();
             cols = (int)Math.Ceiling (Count / f);
         }
         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;
@@ -179,7 +179,7 @@ public class ListTableSource : ITableSource
 
     private void TableView_DrawContent (object sender, DrawEventArgs e)
     {
-        if (!_tableView.Bounds.Equals (_lastBounds)
+        if (!_tableView.Viewport.Equals (_lastBounds)
             || _tableView.MaxCellWidth != _lastMaxCellWidth
             || _tableView.MinCellWidth != _lastMinCellWidth
             || Style != _lastStyle
@@ -188,7 +188,7 @@ public class ListTableSource : ITableSource
             DataTable = CreateTable (CalculateColumns ());
         }
 
-        _lastBounds = _tableView.Bounds;
+        _lastBounds = _tableView.Viewport;
         _lastMinCellWidth = _tableView.MaxCellWidth;
         _lastMaxCellWidth = _tableView.MaxCellWidth;
         _lastStyle = Style;

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

@@ -483,7 +483,7 @@ public class TableView : View
             return null;
         }
 
-        IEnumerable<ColumnToRender> viewPort = CalculateViewport (Bounds);
+        IEnumerable<ColumnToRender> viewPort = CalculateViewport (Viewport);
 
         int headerHeight = GetHeaderHeightIfAny ();
 
@@ -502,7 +502,7 @@ public class TableView : View
         }
 
         // 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;
         }
@@ -577,7 +577,7 @@ public class TableView : View
             return;
         }
 
-        ColumnToRender [] columnsToRender = CalculateViewport (Bounds).ToArray ();
+        ColumnToRender [] columnsToRender = CalculateViewport (Viewport).ToArray ();
         int headerHeight = GetHeaderHeightIfAny ();
 
         //if we have scrolled too far to the left 
@@ -595,7 +595,7 @@ public class TableView : View
                 while (SelectedColumn > columnsToRender.Max (r => r.Column))
                 {
                     ColumnOffset++;
-                    columnsToRender = CalculateViewport (Bounds).ToArray ();
+                    columnsToRender = CalculateViewport (Viewport).ToArray ();
 
                     // if we are already scrolled to the last column then break
                     // this will prevent any theoretical infinite loop
@@ -612,9 +612,9 @@ public class TableView : View
         }
 
         //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
@@ -896,9 +896,9 @@ public class TableView : View
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
-        base.OnDrawContent (contentArea);
+        base.OnDrawContent (viewport);
 
         Move (0, 0);
 
@@ -906,12 +906,12 @@ public class TableView : View
         scrollLeftPoint = null;
 
         // What columns to render at what X offset in viewport
-        ColumnToRender [] columnsToRender = CalculateViewport (Bounds).ToArray ();
+        ColumnToRender [] columnsToRender = CalculateViewport (Viewport).ToArray ();
 
         Driver.SetAttribute (GetNormalColor ());
 
         //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;
 
@@ -925,7 +925,7 @@ public class TableView : View
             */
             if (Style.ShowHorizontalHeaderOverline)
             {
-                RenderHeaderOverline (line, Bounds.Width, columnsToRender);
+                RenderHeaderOverline (line, Viewport.Width, columnsToRender);
                 line++;
             }
 
@@ -937,7 +937,7 @@ public class TableView : View
 
             if (Style.ShowHorizontalHeaderUnderline)
             {
-                RenderHeaderUnderline (line, Bounds.Width, columnsToRender);
+                RenderHeaderUnderline (line, Viewport.Width, columnsToRender);
                 line++;
             }
         }
@@ -945,9 +945,9 @@ public class TableView : View
         int headerLinesConsumed = line;
 
         //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
             int rowToRender = RowOffset + (line - headerLinesConsumed);
@@ -963,7 +963,7 @@ public class TableView : View
             {
                 if (rowToRender == Table.Rows && Style.ShowHorizontalBottomline)
                 {
-                    RenderBottomLine (line, Bounds.Width, columnsToRender);
+                    RenderBottomLine (line, Viewport.Width, columnsToRender);
                 }
 
                 continue;
@@ -1001,7 +1001,7 @@ public class TableView : View
     /// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
     public void PageDown (bool extend)
     {
-        ChangeSelectionByOffset (0, Bounds.Height - GetHeaderHeightIfAny (), extend);
+        ChangeSelectionByOffset (0, Viewport.Height - GetHeaderHeightIfAny (), extend);
         Update ();
     }
 
@@ -1009,7 +1009,7 @@ public class TableView : View
     /// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
     public void PageUp (bool extend)
     {
-        ChangeSelectionByOffset (0, -(Bounds.Height - GetHeaderHeightIfAny ()), extend);
+        ChangeSelectionByOffset (0, -(Viewport.Height - GetHeaderHeightIfAny ()), extend);
         Update ();
     }
 
@@ -1073,7 +1073,7 @@ public class TableView : View
             return null;
         }
 
-        IEnumerable<ColumnToRender> viewPort = CalculateViewport (Bounds);
+        IEnumerable<ColumnToRender> viewPort = CalculateViewport (Viewport);
 
         int headerHeight = GetHeaderHeightIfAny ();
 
@@ -1658,7 +1658,7 @@ public class TableView : View
         // Renders something like:
         // │ArithmeticComparator│chi       │Healthboard│Interpretation│Labnumber│
 
-        ClearLine (row, Bounds.Width);
+        ClearLine (row, Viewport.Width);
 
         //render start of line
         if (style.ShowVerticalHeaderLines)
@@ -1688,7 +1688,7 @@ public class TableView : View
         //render end of line
         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.AddStr (new string (' ', Bounds.Width));
+        Driver.AddStr (new string (' ', Viewport.Width));
 
         // Render cells for each visible header for the current row
         for (var i = 0; i < columnsToRender.Length; i++)
@@ -1955,7 +1955,7 @@ public class TableView : View
 
             //render start and end of line
             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/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         _isDrawing = true;
 
@@ -1196,8 +1196,8 @@ public class TextField : View
 
         int pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
         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
             && 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 ()
     {
         var offB = 0;
@@ -1878,9 +1878,9 @@ public class TextField : View
         Move (0, 0);
         string render = Caption;
 
-        if (render.GetColumns () > Bounds.Width)
+        if (render.GetColumns () > Viewport.Width)
         {
-            render = render [..Bounds.Width];
+            render = render [..Viewport.Width];
         }
 
         Driver.AddStr (render);
@@ -1941,9 +1941,9 @@ public class TextField : View
     {
         _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;

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

@@ -537,7 +537,7 @@ namespace Terminal.Gui
         {
             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)
                 {
@@ -555,7 +555,7 @@ namespace Terminal.Gui
         }
 
         /// <inheritdoc/>
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
             if (_provider is null)
             {
@@ -568,7 +568,7 @@ namespace Terminal.Gui
             Color bgcolor = !IsValid ? new Color (Color.BrightRed) : ColorScheme.Focus.Background;
             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);
 
@@ -642,7 +642,7 @@ namespace Terminal.Gui
         /// <inheritdoc/>
         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 = false, is for normal input.
@@ -660,7 +660,7 @@ namespace Terminal.Gui
                 Move (curPos, 0);
             }
 
-            if (curPos < 0 || curPos >= Bounds.Width)
+            if (curPos < 0 || curPos >= Viewport.Width)
             {
                 Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
             }

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

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

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

@@ -156,19 +156,19 @@ public class TileView : View
             return;
         }
 
-        Rectangle contentArea = Bounds;
+        Rectangle viewport = Viewport;
 
         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 ();
     }
 
@@ -179,12 +179,13 @@ public class TileView : View
     public override bool OnDrawAdornments () { return false; }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         Driver.SetAttribute (ColorScheme.Normal);
-        Clear (contentArea);
 
-        base.OnDrawContent (contentArea);
+        Clear ();
+
+        base.OnDrawContent (viewport);
 
         var lc = new LineCanvas ();
 
@@ -195,19 +196,19 @@ public class TileView : View
         {
             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 (
-                            new Point (Bounds.Width - 1, Bounds.Height - 1),
-                            -Bounds.Width,
+                            new Point (Viewport.Width - 1, Viewport.Height - 1),
+                            -Viewport.Width,
                             Orientation.Horizontal,
                             LineStyle
                            );
 
                 lc.AddLine (
-                            new Point (Bounds.Width - 1, Bounds.Height - 1),
-                            -Bounds.Height,
+                            new Point (Viewport.Width - 1, Viewport.Height - 1),
+                            -Viewport.Height,
                             Orientation.Vertical,
                             LineStyle
                            );
@@ -217,7 +218,7 @@ public class TileView : View
             {
                 bool isRoot = _splitterLines.Contains (line);
 
-                Rectangle screen = line.BoundsToScreen (Rectangle.Empty);
+                Rectangle screen = line.ViewportToScreen (Rectangle.Empty);
                 Point origin = ScreenToFrame (screen.X, screen.Y);
                 int length = line.Orientation == Orientation.Horizontal ? line.Frame.Width : line.Frame.Height;
 
@@ -241,7 +242,7 @@ public class TileView : View
 
         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);
         }
@@ -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))
         {
@@ -758,9 +759,9 @@ public class TileView : View
         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;
         }
@@ -803,18 +804,20 @@ public class TileView : View
 
             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
             {
-                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>
         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);
         }
 
@@ -845,7 +848,7 @@ public class TileView : View
         {
             Dim spaceDim = Tile.ContentView.Width;
 
-            int spaceAbs = spaceDim.Anchor (Parent.Bounds.Width);
+            int spaceAbs = spaceDim.Anchor (Parent.Viewport.Width);
 
             var title = $" {Tile.Title} ";
 
@@ -893,13 +896,13 @@ public class TileView : View
         {
             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);
             }
         }
 
-        protected internal override bool OnMouseEvent  (MouseEvent mouseEvent)
+        protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
         {
             if (!dragPosition.HasValue && mouseEvent.Flags == MouseFlags.Button1Pressed)
             {
@@ -919,7 +922,7 @@ public class TileView : View
                     {
                         moveRuneRenderLocation = new Point (
                                                             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;
                     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 ();
@@ -969,9 +972,9 @@ public class TileView : View
             return false;
         }
 
-        public override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
-            base.OnDrawContent (contentArea);
+            base.OnDrawContent (viewport);
 
             DrawSplitterSymbol ();
         }
@@ -988,7 +991,7 @@ public class TileView : View
         {
             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);
         }
 
@@ -1032,10 +1035,10 @@ public class TileView : View
             {
                 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);
@@ -1071,8 +1074,8 @@ public class TileView : View
         {
             int posAbsolute = pos.Anchor (
                                           Orientation == Orientation.Horizontal
-                                              ? Parent.Bounds.Height
-                                              : Parent.Bounds.Width
+                                              ? Parent.Viewport.Height
+                                              : Parent.Viewport.Width
                                          );
 
             return posAbsolute + delta;

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

@@ -29,7 +29,7 @@ public partial class Toplevel : View
     /// </summary>
     public Toplevel ()
     {
-        Arrangement = ViewArrangement.Movable;
+        Arrangement = ViewArrangement.Fixed;
         Width = Dim.Fill ();
         Height = Dim.Fill ();
 
@@ -249,7 +249,7 @@ public partial class Toplevel : View
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         if (!Visible)
         {
@@ -268,12 +268,12 @@ public partial class Toplevel : View
             {
                 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)
                         {
                             top.SetNeedsLayout ();
-                            top.SetNeedsDisplay (top.Bounds);
+                            top.SetNeedsDisplay (top.Viewport);
                             top.Draw ();
                             top.OnRenderLineCanvas ();
                         }
@@ -284,20 +284,20 @@ public partial class Toplevel : View
             // This should not be here, but in base
             foreach (View view in Subviews)
             {
-                if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this))
+                if (view.Frame.IntersectsWith (Viewport) && !OutsideTopFrame (this))
                 {
                     //view.SetNeedsLayout ();
-                    view.SetNeedsDisplay (view.Bounds);
+                    view.SetNeedsDisplay ();
                     view.SetSubViewNeedsDisplay ();
                 }
             }
 
-            base.OnDrawContent (contentArea);
+            base.OnDrawContent (viewport);
 
             // This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true
             //if (this.MenuBar is { } && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu is { }) {
             //	// 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>
     public virtual void PositionToplevel (Toplevel top)
     {
+
         View superView = GetLocationEnsuringFullVisibility (
                                               top,
                                               top.Frame.X,
@@ -547,7 +548,7 @@ public partial class Toplevel : View
     ///     to dispose objects after calling <see cref="Application.End(RunState)"/>.
     /// </summary>
     public event EventHandler Unloaded;
-    
+
     internal void AddMenuStatusBar (View view)
     {
         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
             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
-            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 (Bounds.Height == 0)
+            if (Viewport.Height == 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));
@@ -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
     ///     the <see cref="TreeView{T}"/>.
     /// </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>
     public T GetObjectOnRow (int row) { return HitTest (row)?.Model; }
 
     /// <summary>
     ///     <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).
     ///     </para>
     ///     <para>
@@ -967,7 +967,7 @@ public class TreeView<T> : View, ITreeView where T : class
     public void GoToEnd ()
     {
         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;
 
         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>
     /// <param name="expandSelection">True if the navigation should add the covered nodes to the selected current selection.</param>
     /// <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>
     /// <param name="expandSelection">True if the navigation should add the covered nodes to the selected current selection.</param>
     /// <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>
     ///     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;
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle contentArea)
+    public override void OnDrawContent (Rectangle viewport)
     {
         if (roots is null)
         {
@@ -1160,7 +1160,7 @@ public class TreeView<T> : View, ITreeView where T : class
 
         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;
 
@@ -1168,14 +1168,14 @@ public class TreeView<T> : View, ITreeView where T : class
             if (idxToRender < map.Count)
             {
                 // Render the line
-                map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Bounds.Width);
+                map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Viewport.Width);
             }
             else
             {
                 // Else clear the line to prevent stale symbols due to scrolling etc
                 Move (0, line);
                 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));
 
             // 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);
             }

+ 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/=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/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_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_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: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>

+ 2 - 2
UICatalog/Scenarios/ASCIICustomButton.cs

@@ -86,14 +86,14 @@ public class ASCIICustomButtonTest : Scenario
 
             var fillText = new StringBuilder ();
 
-            for (var i = 0; i < Bounds.Height; i++)
+            for (var i = 0; i < Viewport.Height; i++)
             {
                 if (i > 0)
                 {
                     fillText.AppendLine ("");
                 }
 
-                for (var j = 0; j < Bounds.Width; j++)
+                for (var j = 0; j < Viewport.Width; j++)
                 {
                     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;
 
-    public override void Init ()
+    public override void Main ()
     {
         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 color = new ColorPicker { Title = "BG", BoxHeight = 1, BoxWidth = 1, X = Pos.AnchorEnd (11) };
         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!" };
 
         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
         {
@@ -64,45 +79,39 @@ public class Adornments : Scenario
             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" };
                                 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" };
                                 btnButtonInPadding.Accept += (s, e) => MessageBox.Query (20, 7, "Hi", "Button in Padding Pressed!", "Ok");
                                 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
                                 btnButtonInPadding.Border.CloseButton.Visible = true;
@@ -118,16 +127,17 @@ public class Adornments : Scenario
 #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 ();
     }
 
-    public override void Run () { }
-
+    /// <summary>
+    /// Provides a composable UI for editing the settings of an Adornment.
+    /// </summary>
     public class AdornmentEditor : View
     {
         private readonly ColorPicker _backgroundColorPicker = new ()
@@ -148,12 +158,12 @@ public class Adornments : Scenario
             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 TextField _topEdit;
+        private bool _isUpdating;
 
         public AdornmentEditor ()
         {
@@ -161,7 +171,6 @@ public class Adornments : Scenario
             BorderStyle = LineStyle.Double;
             Initialized += AdornmentEditor_Initialized;
         }
-
         public Attribute Color
         {
             get => new (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor);
@@ -183,31 +192,15 @@ public class Adornments : Scenario
                 }
 
                 _thickness = value;
-                ThicknessChanged?.Invoke (this, new() { Thickness = Thickness });
+                ThicknessChanged?.Invoke (this, new () { Thickness = Thickness });
 
                 if (IsInitialized)
                 {
                     _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;
                 }
@@ -219,49 +212,44 @@ public class Adornments : Scenario
 
         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);
 
-            _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);
 
-            _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);
 
-            _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);
 
-            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) =>
                               {
                                   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);
 
             // Foreground ColorPicker.
             _foregroundColorPicker.X = -1;
-            _foregroundColorPicker.Y = Pos.Bottom (copyTop) + 1;
+            _foregroundColorPicker.Y = Pos.Bottom (copyTop);
             _foregroundColorPicker.SelectedColor = Color.Foreground.GetClosestNamedColor ();
 
             _foregroundColorPicker.ColorChanged += (o, a) =>
@@ -289,29 +277,69 @@ public class Adornments : Scenario
                                                                                 );
             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 ();
-            Height = GetAdornmentsThickness ().Vertical + 4 + 4;
+            Height = GetAdornmentsThickness ().Vertical + 4 + 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 CheckBox _diagCheckBox;
@@ -320,6 +348,15 @@ public class Adornments : Scenario
         private AdornmentEditor _paddingEditor;
         private View _viewToEdit;
 
+        public AdornmentsEditor ()
+        {
+            ColorScheme = Colors.ColorSchemes ["Dialog"];
+
+            // TOOD: Use Dim.Auto
+            Width = 36;
+            Height = Dim.Fill ();
+        }
+
         public View ViewToEdit
         {
             get => _viewToEdit;
@@ -328,26 +365,26 @@ public class Adornments : Scenario
                 _origTitle = value.Title;
                 _viewToEdit = value;
 
-                _marginEditor = new()
+                _marginEditor = new ()
                 {
                     X = 0,
                     Y = 0,
                     Title = "_Margin",
                     Thickness = _viewToEdit.Margin.Thickness,
-                    Color = new (_viewToEdit.Margin.ColorScheme.Normal),
+                    Color = new (_viewToEdit.Margin.ColorScheme?.Normal ?? ColorScheme.Normal),
                     SuperViewRendersLineCanvas = true
                 };
                 _marginEditor.ThicknessChanged += Editor_ThicknessChanged;
                 _marginEditor.AttributeChanged += Editor_AttributeChanged;
                 Add (_marginEditor);
 
-                _borderEditor = new()
+                _borderEditor = new ()
                 {
                     X = Pos.Left (_marginEditor),
                     Y = Pos.Bottom (_marginEditor),
                     Title = "B_order",
                     Thickness = _viewToEdit.Border.Thickness,
-                    Color = new (_viewToEdit.Border.ColorScheme.Normal),
+                    Color = new (_viewToEdit.Border.ColorScheme?.Normal ?? ColorScheme.Normal),
                     SuperViewRendersLineCanvas = true
                 };
                 _borderEditor.ThicknessChanged += Editor_ThicknessChanged;
@@ -411,7 +448,7 @@ public class Adornments : Scenario
                                     {
                                         if (ckbTitle.Checked == true)
                                         {
-                                            _viewToEdit.Title = _origTitle;
+                                            //_viewToEdit.Title = _origTitle;
                                         }
                                         else
                                         {
@@ -420,13 +457,13 @@ public class Adornments : Scenario
                                     };
                 Add (ckbTitle);
 
-                _paddingEditor = new()
+                _paddingEditor = new ()
                 {
                     X = Pos.Left (_borderEditor),
                     Y = Pos.Bottom (rbBorderStyle),
                     Title = "_Padding",
                     Thickness = _viewToEdit.Padding.Thickness,
-                    Color = new (_viewToEdit.Padding.ColorScheme.Normal),
+                    Color = new (_viewToEdit.Padding.ColorScheme?.Normal ?? ColorScheme.Normal),
                     SuperViewRendersLineCanvas = true
                 };
                 _paddingEditor.ThicknessChanged += Editor_ThicknessChanged;
@@ -450,7 +487,6 @@ public class Adornments : Scenario
                                          };
 
                 Add (_diagCheckBox);
-                Add (_viewToEdit);
 
                 _viewToEdit.LayoutComplete += (s, e) =>
                                               {

+ 5 - 5
UICatalog/Scenarios/Animation.cs

@@ -176,16 +176,16 @@ public class Animation : Scenario
         private Rectangle oldSize = Rectangle.Empty;
         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
                 matchSizes = new Image<Rgba32> [frameCount];
                 brailleCache = new string [frameCount];
-                oldSize = Bounds;
+                oldSize = Viewport;
             }
 
             Image<Rgba32> imgScaled = matchSizes [currentFrame];
@@ -196,7 +196,7 @@ public class Animation : Scenario
                 Image<Rgba32> imgFull = fullResImages [currentFrame];
 
                 // keep aspect ratio
-                int newSize = Math.Min (Bounds.Width, Bounds.Height);
+                int newSize = Math.Min (Viewport.Width, Viewport.Height);
 
                 // generate one
                 matchSizes [currentFrame] = imgScaled = imgFull.Clone (

+ 6 - 1
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -36,6 +36,7 @@ public class BackgroundWorkerCollection : Scenario
 
         public OverlappedMain ()
         {
+            Arrangement = ViewArrangement.Movable;
             Data = "OverlappedMain";
 
             IsOverlappedContainer = true;
@@ -258,6 +259,8 @@ public class BackgroundWorkerCollection : Scenario
 
         public StagingUIController ()
         {
+            Arrangement = ViewArrangement.Movable;
+
             X = Pos.Center ();
             Y = Pos.Center ();
             Width = Dim.Percent (85);
@@ -303,7 +306,7 @@ public class BackgroundWorkerCollection : Scenario
             LayoutStarted += (s, e) =>
                              {
                                  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;
                                  _close.X = Pos.AnchorEnd (shiftLeft);
@@ -338,6 +341,8 @@ public class BackgroundWorkerCollection : Scenario
 
         public WorkerApp ()
         {
+            Arrangement = ViewArrangement.Movable;
+
             Data = "WorkerApp";
             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 JetBrains.Annotations;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
@@ -13,8 +16,9 @@ public class Buttons : Scenario
     {
         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
         var editLabel = new Label { X = 0, Y = 0, TabStop = true, Text = "TextField (to demo IsDefault):" };
         main.Add (editLabel);
@@ -32,19 +36,19 @@ public class Buttons : Scenario
         var swapButton = new Button { X = 50, Text = "S_wap Default (Absolute Layout)" };
 
         swapButton.Accept += (s, e) =>
-                              {
-                                  defaultButton.IsDefault = !defaultButton.IsDefault;
-                                  swapButton.IsDefault = !swapButton.IsDefault;
-                              };
+                             {
+                                 defaultButton.IsDefault = !defaultButton.IsDefault;
+                                 swapButton.IsDefault = !swapButton.IsDefault;
+                             };
         main.Add (swapButton);
 
         static void DoMessage (Button button, string txt)
         {
             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:" };
@@ -75,20 +79,20 @@ public class Buttons : Scenario
         Button button;
 
         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);
 
         // Note the 'N' in 'Newline' will be the hotkey
         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");
 
         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 += "!";
 
         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
         {
@@ -112,12 +116,12 @@ public class Buttons : Scenario
 
         // This in interesting test case because `moveBtn` and below are laid out relative to this one!
         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
         {
@@ -142,12 +146,12 @@ public class Buttons : Scenario
         };
 
         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);
 
         // Demonstrates how changing the View.Frame property can SIZE Views (#583)
@@ -163,11 +167,11 @@ public class Buttons : Scenario
         };
 
         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);
 
         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" };
 
         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);
 
         // Demonstrates how changing the View.Frame property can SIZE Views (#583)
@@ -201,14 +205,14 @@ public class Buttons : Scenario
         };
 
         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);
 
         var label = new Label
@@ -289,154 +293,273 @@ public class Buttons : Scenario
         main.Add (moveUnicodeHotKeyBtn);
 
         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,
             Y = Pos.Bottom (moveUnicodeHotKeyBtn) + 1,
             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,
-            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,
-            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,
             Y = Pos.Top (label),
             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,
             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,
             Y = Pos.Top (label),
             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,
             Y = Pos.Top (repeatButton),
             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 ();
         Application.Run (main);
         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.Collections.Generic;
 using System.ComponentModel;
@@ -17,12 +19,15 @@ namespace UICatalog.Scenarios;
 /// <summary>
 ///     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 -
-///     Illustrates how to use ScrollView to do infinite scrolling
+///     Illustrates how to do infinite scrolling
 /// </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 ("Drawing")]
 [ScenarioCategory ("Controls")]
-[ScenarioCategory ("ScrollView")]
+[ScenarioCategory ("Layout")]
+[ScenarioCategory ("Scrolling")]
+
 public class CharacterMap : Scenario
 {
     public Label _errorLabel;
@@ -30,17 +35,26 @@ public class CharacterMap : Scenario
     private CharMap _charMap;
 
     // Don't create a Window, just return the top-level view
-    public override void Init ()
+    public override void Main ()
     {
         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
         {
@@ -49,19 +63,19 @@ public class CharacterMap : Scenario
             HotKeySpecifier = (Rune)'_',
             Text = "_Jump To Code Point:"
         };
-        Top.Add (jumpLabel);
+        top.Add (jumpLabel);
 
         var jumpEdit = new TextField
         {
             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"
         };
-        Top.Add (_errorLabel);
+        top.Add (_errorLabel);
 
 #if TEXT_CHANGED_TO_JUMP
         jumpEdit.TextChanged += JumpEdit_TextChanged;
@@ -76,8 +90,7 @@ public class CharacterMap : Scenario
             e.Cancel = true;
         }
 #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;
 
         //jumpList.Style.ShowHeaders = false;
@@ -120,10 +133,10 @@ public class CharacterMap : Scenario
 
         _categoryList.Style.ColumnStyles.Add (
                                               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;
 
@@ -133,10 +146,7 @@ public class CharacterMap : Scenario
                                                  _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
         _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; }
@@ -203,7 +220,7 @@ public class CharacterMap : Scenario
 
         return new (
                     sortedRanges,
-                    new()
+                    new ()
                     {
                         { $"Category{categorySort}", s => s.Category },
                         { $"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 int COLUMN_WIDTH = 3;
@@ -311,10 +328,7 @@ internal class CharMap : ScrollView
         ColorScheme = Colors.ColorSchemes ["Dialog"];
         CanFocus = true;
 
-        ContentSize = new (
-                           RowWidth,
-                           (MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)) * _rowHeight
-                          );
+        ContentSize = new (RowWidth, (MaxCodePoint / 16 + 2) * _rowHeight);
 
         AddCommand (
                     Command.ScrollUp,
@@ -325,6 +339,8 @@ internal class CharMap : ScrollView
                             SelectedCodePoint -= 16;
                         }
 
+                        ScrollVertical (-_rowHeight);
+
                         return true;
                     }
                    );
@@ -333,11 +349,16 @@ internal class CharMap : ScrollView
                     Command.ScrollDown,
                     () =>
                     {
-                        if (SelectedCodePoint < MaxCodePoint - 16)
+                        if (SelectedCodePoint <= MaxCodePoint - 16)
                         {
                             SelectedCodePoint += 16;
                         }
 
+                        if (Cursor.Y >= Viewport.Height)
+                        {
+                            ScrollVertical (_rowHeight);
+                        }
+
                         return true;
                     }
                    );
@@ -351,6 +372,11 @@ internal class CharMap : ScrollView
                             SelectedCodePoint--;
                         }
 
+                        if (Cursor.X > RowLabelWidth + 1)
+                        {
+                            ScrollHorizontal (-COLUMN_WIDTH);
+                        }
+
                         return true;
                     }
                    );
@@ -364,6 +390,11 @@ internal class CharMap : ScrollView
                             SelectedCodePoint++;
                         }
 
+                        if (Cursor.X >= Viewport.Width)
+                        {
+                            ScrollHorizontal (COLUMN_WIDTH);
+                        }
+
                         return true;
                     }
                    );
@@ -372,8 +403,9 @@ internal class CharMap : ScrollView
                     Command.PageUp,
                     () =>
                     {
-                        int page = (Bounds.Height / _rowHeight - 1) * 16;
+                        int page = (Viewport.Height - 1 / _rowHeight) * 16;
                         SelectedCodePoint -= Math.Min (page, SelectedCodePoint);
+                        Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight };
 
                         return true;
                     }
@@ -383,8 +415,9 @@ internal class CharMap : ScrollView
                     Command.PageDown,
                     () =>
                     {
-                        int page = (Bounds.Height / _rowHeight - 1) * 16;
+                        int page = (Viewport.Height - 1 / _rowHeight) * 16;
                         SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint);
+                        Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight };
 
                         return true;
                     }
@@ -405,11 +438,11 @@ internal class CharMap : ScrollView
                     () =>
                     {
                         SelectedCodePoint = MaxCodePoint;
+                        Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight };
 
                         return true;
                     }
                    );
-        KeyBindings.Add (Key.Enter, Command.Accept);
 
         AddCommand (
                     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;
+        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>
@@ -429,16 +571,16 @@ internal class CharMap : ScrollView
     {
         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);
         }
         set => throw new NotImplementedException ();
     }
 
-    public static int MaxCodePoint => 0x10FFFF;
+    public static int MaxCodePoint = UnicodeRange.Ranges.Max (r => r.End);
 
     /// <summary>
     ///     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;
         set
         {
+            if (_selected == value)
+            {
+                return;
+            }
+
             _selected = value;
 
             if (IsInitialized)
@@ -456,36 +603,28 @@ internal class CharMap : ScrollView
                 int row = SelectedCodePoint / 16 * _rowHeight;
                 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.
-                    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.
-                    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.
-                    ContentOffset = new (col, ContentOffset.Y);
+                    Viewport = Viewport with { X = col };
                 }
-                else if (col + ContentOffset.X >= width)
+                else if (col - Viewport.X >= width)
                 {
                     // 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;
             SelectedCodePoint = value;
+            Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight };
             SetNeedsDisplay ();
         }
     }
@@ -523,98 +663,71 @@ internal class CharMap : ScrollView
     private static int RowWidth => RowLabelWidth + COLUMN_WIDTH * 16;
     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;
         }
 
-        // 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 ());
         Move (0, 0);
         Driver.AddStr (new (' ', RowLabelWidth + 1));
 
+        int firstColumnX = RowLabelWidth - Viewport.X;
+
+        // Header
         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)
             {
                 Move (x, 0);
                 Driver.SetAttribute (GetHotNormalColor ());
                 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.SetAttribute (GetHotNormalColor ());
                 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
         // 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?
-            int row = (y - ContentOffset.Y - 1) / _rowHeight;
+            int row = (y + Viewport.Y - 1) / _rowHeight;
 
             int val = row * 16;
 
             if (val > MaxCodePoint)
             {
-                continue;
+                break;
             }
 
             Move (firstColumnX + COLUMN_WIDTH, y);
             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++)
             {
                 int x = firstColumnX + COLUMN_WIDTH * col + 1;
 
+                if (x < 0 || x > Viewport.Width - 1)
+                {
+                    continue;
+                }
+
                 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 ());
                 }
@@ -629,9 +742,9 @@ internal class CharMap : ScrollView
 
                 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)
                     {
                         Driver.AddRune (rune);
@@ -666,25 +779,24 @@ internal class CharMap : ScrollView
                 }
                 else
                 {
+                    // Draw the width of the rune
                     Driver.SetAttribute (ColorScheme.HotNormal);
                     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 ());
                 }
             }
 
+            // Draw row label (U+XXXX_)
             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}_ ");
             }
@@ -693,8 +805,6 @@ internal class CharMap : ScrollView
                 Driver.AddStr (new (' ', RowLabelWidth));
             }
         }
-
-        Driver.Clip = oldClip;
     }
 
     public override bool OnEnter (View view)
@@ -718,9 +828,9 @@ internal class CharMap : ScrollView
     {
         if (HasFocus
             && Cursor.X >= RowLabelWidth
-            && Cursor.X < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0)
+            && Cursor.X < Viewport.Width
             && Cursor.Y > 0
-            && Cursor.Y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0))
+            && Cursor.Y < Viewport.Height)
         {
             Driver.SetCursorVisibility (_cursor);
             Move (Cursor.X, Cursor.Y);
@@ -760,6 +870,8 @@ internal class CharMap : ScrollView
             return;
         }
 
+        args.Handled = true;
+
         if (me.Y == 0)
         {
             me.Y = Cursor.Y;
@@ -773,8 +885,8 @@ internal class CharMap : ScrollView
             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)
         {
@@ -812,7 +924,7 @@ internal class CharMap : ScrollView
         {
             SelectedCodePoint = val;
 
-            _contextMenu = new()
+            _contextMenu = new ()
             {
                 Position = new (me.X + 1, me.Y + 1),
                 MenuItems = new (
@@ -846,6 +958,7 @@ internal class CharMap : ScrollView
     {
         var client = new UcdApiClient ();
         var decResponse = string.Empty;
+        var getCodePointError = string.Empty;
 
         var waitIndicator = new Dialog
         {
@@ -854,7 +967,7 @@ internal class CharMap : ScrollView
             Y = Pos.Center (),
             Height = 7,
             Width = 50,
-            Buttons = [new() { Text = "Cancel" }]
+            Buttons = [new () { Text = "Cancel" }]
         };
 
         var errorLabel = new Label
@@ -877,12 +990,13 @@ internal class CharMap : ScrollView
                                    try
                                    {
                                        decResponse = await client.GetCodepointDec (SelectedCodePoint);
+                                       Application.Invoke (() => waitIndicator.RequestStop ());
                                    }
                                    catch (HttpRequestException e)
                                    {
+                                       getCodePointError = errorLabel.Text = e.Message;
                                        Application.Invoke (() => waitIndicator.RequestStop ());
                                    }
-
                                };
         Application.Run (waitIndicator);
         waitIndicator.Dispose ();
@@ -939,46 +1053,46 @@ internal class CharMap : ScrollView
             var label = new Label { Text = "IsAscii: ", X = 0, Y = 0 };
             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);
 
-            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);
 
-            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);
 
-            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);
 
-            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);
 
-            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);
 
-            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);
 
-            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);
 
-            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);
 
-            label = new() { Text = "Columns: ", X = 0, Y = Pos.Bottom (label) };
+            label = new () { Text = "Columns: ", X = 0, Y = Pos.Bottom (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);
 
-            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);
 
-            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);
 
-            label = new()
+            label = new ()
             {
                 Text =
                     $"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")]
 [ScenarioCategory ("Tests")]
+[ScenarioCategory ("Drawing")]
+[ScenarioCategory ("Scrolling")]
 public class Clipping : Scenario
 {
     public override void Init ()
@@ -30,8 +32,9 @@ public class Clipping : Scenario
         scrollView.ContentSize = new (200, 100);
 
         //ContentOffset = Point.Empty,
-        //scrollView.ShowVerticalScrollIndicator = true;
-        //scrollView.ShowHorizontalScrollIndicator = true;
+        scrollView.AutoHideScrollBars = true;
+        scrollView.ShowVerticalScrollIndicator = true;
+        scrollView.ShowHorizontalScrollIndicator = true;
 
         var embedded1 = new View
         {

+ 2 - 2
UICatalog/Scenarios/ColorPicker.cs

@@ -44,8 +44,8 @@ public class ColorPickers : Scenario
         backgroundColorPicker = new ColorPicker
         {
             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,
             BoxWidth = 4,
             BorderStyle = LineStyle.Single

+ 4 - 4
UICatalog/Scenarios/ComputedLayout.cs

@@ -57,12 +57,12 @@ public class ComputedLayout : Scenario
         Top.LayoutComplete += (s, a) =>
                                           {
                                               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 =
-                                                  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);

+ 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
         {
             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"]
         };
 

+ 2 - 2
UICatalog/Scenarios/GraphViewExample.cs

@@ -254,7 +254,7 @@ public class GraphViewExample : Scenario
 
         _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 (
                          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 = "F", ScreenPosition = new Point (_graphView.Bounds.Width - 1, 10) }
+                                    new TextAnnotation { Text = "F", ScreenPosition = new Point (_graphView.Viewport.Width - 1, 10) }
                                    );
 
         _graphView.SetNeedsDisplay ();

+ 2 - 2
UICatalog/Scenarios/LineDrawing.cs

@@ -36,9 +36,9 @@ public class LineDrawing : Scenario
         public DrawingArea () { AddLayer (); }
         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)
             {

+ 2 - 0
UICatalog/Scenarios/ListColumns.cs

@@ -12,6 +12,8 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Dialogs")]
 [ScenarioCategory ("Text and Formatting")]
 [ScenarioCategory ("Top Level Windows")]
+[ScenarioCategory ("Scrolling")]
+
 public class ListColumns : Scenario
 {
     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 };
 

+ 51 - 43
UICatalog/Scenarios/ProgressBarStyles.cs

@@ -20,20 +20,30 @@ public class ProgressBarStyles : Scenario
     private const uint _timerTick = 20;
     private Timer _fractionTimer;
     private Timer _pulseTimer;
+    private ViewDiagnosticFlags _diagnosticFlags;
 
-    public override void Init ()
+    public override void Main ()
     {
         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;
 
@@ -47,16 +57,7 @@ public class ProgressBarStyles : Scenario
             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
 
@@ -75,12 +76,12 @@ public class ProgressBarStyles : Scenario
 
             dialog.LayoutComplete += (sender, args) =>
                                     {
-                                        dialog.Bounds = Rectangle.Empty with
+                                        dialog.Viewport = Rectangle.Empty with
                                         {
                                             Width = colorPicker.Frame.Width,
                                             Height = colorPicker.Frame.Height
                                         };
-                                        Application.Top.LayoutSubviews();
+                                        Application.Top.LayoutSubviews ();
                                     };
 
             dialog.Add (colorPicker);
@@ -98,7 +99,7 @@ public class ProgressBarStyles : Scenario
         {
             Text = "Foreground HotNormal Color", X = Pos.Center (), Y = Pos.Bottom (pbList)
         };
-        editor.Add (fgColorPickerBtn);
+        container.Add (fgColorPickerBtn);
 
         fgColorPickerBtn.Accept += (s, e) =>
                                     {
@@ -123,7 +124,7 @@ public class ProgressBarStyles : Scenario
         {
             X = Pos.Center (), Y = Pos.Bottom (fgColorPickerBtn), Text = "Background HotNormal Color"
         };
-        editor.Add (bgColorPickerBtn);
+        container.Add (bgColorPickerBtn);
 
         bgColorPickerBtn.Accept += (s, e) =>
                                     {
@@ -157,11 +158,10 @@ public class ProgressBarStyles : Scenario
             Y = Pos.Bottom (bgColorPickerBtn) + 1,
             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" };
-
-        editor.Add (button);
+        container.Add (button);
 
         var blocksPB = new ProgressBar
         {
@@ -172,7 +172,7 @@ public class ProgressBarStyles : Scenario
             BorderStyle = LineStyle.Single,
             CanFocus = true
         };
-        editor.Add (blocksPB);
+        container.Add (blocksPB);
 
         var continuousPB = new ProgressBar
         {
@@ -184,7 +184,7 @@ public class ProgressBarStyles : Scenario
             BorderStyle = LineStyle.Single,
             CanFocus = true
         };
-        editor.Add (continuousPB);
+        container.Add (continuousPB);
 
         button.Accept += (s, e) =>
                           {
@@ -222,7 +222,7 @@ public class ProgressBarStyles : Scenario
         {
             X = Pos.Center (), Y = Pos.Bottom (continuousPB) + 1, Text = "BidirectionalMarquee", Checked = true
         };
-        editor.Add (ckbBidirectional);
+        container.Add (ckbBidirectional);
 
         var marqueesBlocksPB = new ProgressBar
         {
@@ -234,7 +234,7 @@ public class ProgressBarStyles : Scenario
             BorderStyle = LineStyle.Single,
             CanFocus = true
         };
-        editor.Add (marqueesBlocksPB);
+        container.Add (marqueesBlocksPB);
 
         var marqueesContinuousPB = new ProgressBar
         {
@@ -246,13 +246,22 @@ public class ProgressBarStyles : Scenario
             BorderStyle = LineStyle.Single,
             CanFocus = true
         };
-        editor.Add (marqueesContinuousPB);
+        container.Add (marqueesContinuousPB);
 
         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;
 
         rbPBFormat.SelectedItemChanged += (s, e) =>
@@ -272,8 +281,7 @@ public class ProgressBarStyles : Scenario
         _pulseTimer = new Timer (
                                  _ =>
                                  {
-                                     marqueesBlocksPB.Text =
-                                         marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString ();
+                                     marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString ();
                                      marqueesBlocksPB.Pulse ();
                                      marqueesContinuousPB.Pulse ();
                                      Application.Wakeup ();
@@ -283,9 +291,15 @@ public class ProgressBarStyles : Scenario
                                  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)
             {
@@ -299,13 +313,7 @@ public class ProgressBarStyles : Scenario
                 _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.Text;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
 
-[ScenarioMetadata ("Scrolling", "Demonstrates ScrollView etc...")]
+[ScenarioMetadata ("Scrolling", "Demonstrates scrolling etc...")]
 [ScenarioCategory ("Controls")]
-[ScenarioCategory ("ScrollView")]
+[ScenarioCategory ("Scrolling")]
 [ScenarioCategory ("Tests")]
 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 };
-        Win.Add (label);
+        app.Add (label);
 
         var scrollView = new ScrollView
         {
             Id = "scrollView",
             X = 2,
             Y = Pos.Bottom (label) + 1,
-            Width = 50,
+            Width = 60,
             Height = 20,
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
-            ContentSize = new (200, 100),
+            ContentSize = new (120, 40),
 
             //ContentOffset = Point.Empty,
             ShowVerticalScrollIndicator = 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";
 
@@ -64,25 +73,6 @@ public class Scrolling : Scenario
         };
         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!" };
         pressMeButton.Accept += (s, e) => MessageBox.Query (20, 7, "MessageBox", "Neat?", "Yes", "No");
         scrollView.Add (pressMeButton);
@@ -105,7 +95,7 @@ public class Scrolling : Scenario
                             Y = 5,
                             Width = 50,
                             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,
                             Width = 50,
                             ColorScheme = Colors.ColorSchemes ["Dialog"],
-                            Text = "... the emergency broadcast system."
+                            Text = "... the emergency broadcast system.",
                         }
                        );
 
@@ -127,7 +117,7 @@ public class Scrolling : Scenario
                             Y = 99,
                             Width = 50,
                             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.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);
 
-        Win.Add (scrollView);
+        app.Add (scrollView);
 
         var hCheckBox = new CheckBox
         {
@@ -157,7 +147,7 @@ public class Scrolling : Scenario
             Text = "Horizontal Scrollbar",
             Checked = scrollView.ShowHorizontalScrollIndicator
         };
-        Win.Add (hCheckBox);
+        app.Add (hCheckBox);
 
         var vCheckBox = new CheckBox
         {
@@ -166,7 +156,7 @@ public class Scrolling : Scenario
             Text = "Vertical Scrollbar",
             Checked = scrollView.ShowVerticalScrollIndicator
         };
-        Win.Add (vCheckBox);
+        app.Add (vCheckBox);
 
         var t = "Auto Hide Scrollbars";
 
@@ -213,32 +203,10 @@ public class Scrolling : Scenario
                                   hCheckBox.Checked = true;
                                   vCheckBox.Checked = true;
                               };
-        Win.Add (ahCheckBox);
+        app.Add (ahCheckBox);
 
         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;
 
@@ -250,11 +218,13 @@ public class Scrolling : Scenario
             Width = 50,
             Text = "Mouse: "
         };
-        Win.Add (mousePos);
+        app.Add (mousePos);
         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 };
-        Win.Add (progress);
+
+        app.Add (progress);
 
         var pulsing = true;
 
@@ -267,12 +237,37 @@ public class Scrolling : Scenario
 
         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 override void OnDrawContent (Rectangle contentArea)
+        public override void OnDrawContent (Rectangle viewport)
         {
-            base.OnDrawContent (contentArea);
+            base.OnDrawContent (viewport);
 
             Driver.SetAttribute (white);
-            Clear (contentArea);
+            Clear ();
 
             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);
             }

+ 22 - 18
UICatalog/Scenarios/TextFormatterDemo.cs

@@ -10,18 +10,19 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Text and Formatting")]
 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
         // 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.";
 
@@ -35,7 +36,7 @@ public class TextFormatterDemo : Scenario
             Y = 0,
             AutoSize = false,
             Height = 10,
-            Width = Dim.Fill ()
+            Width = Dim.Fill (),
         };
 
         var block = new StringBuilder ();
@@ -50,17 +51,17 @@ public class TextFormatterDemo : Scenario
         block.AppendLine ("      ░    ░      ░    ░  ░ ░            ░  ");
         block.AppendLine ("                       ░  ░                 ");
         blockText.Text = block.ToString (); // .Replace(" ", "\u00A0"); // \u00A0 is 'non-breaking space
-        Win.Add (blockText);
+        app.Add (blockText);
 
         var unicodeCheckBox = new CheckBox
         {
             X = 0,
             Y = Pos.Bottom (blockText) + 1,
             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 ();
         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:"
         };
-        Win.Add (label);
+        app.Add (label);
 
         foreach (TextAlignment alignment in alignments)
         {
             label = new Label { Y = Pos.Bottom (label), Text = $"{alignment}:" };
-            Win.Add (label);
+            app.Add (label);
             singleLines [(int)alignment].Y = Pos.Bottom (label);
-            Win.Add (singleLines [(int)alignment]);
+            app.Add (singleLines [(int)alignment]);
             label = singleLines [(int)alignment];
         }
 
         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)
         {
             label = new Label { Y = Pos.Bottom (label), Text = $"{alignment}:" };
-            Win.Add (label);
+            app.Add (label);
             multipleLines [(int)alignment].Y = Pos.Bottom (label);
-            Win.Add (multipleLines [(int)alignment]);
+            app.Add (multipleLines [(int)alignment]);
             label = multipleLines [(int)alignment];
         }
 
@@ -128,5 +129,8 @@ public class TextFormatterDemo : Scenario
                                            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: {
                                            Top.Frame
                                        } .Bounds: {
-                                           Top.Bounds
+                                           Top.Viewport
                                        }\nView.Frame: {
                                            view.Frame
-                                       } .Bounds: {
-                                           view.Bounds
-                                       } .BoundsOffset: {
-                                           view.GetBoundsOffset ()
+                                       } .Viewport: {
+                                           view.Viewport
+                                       } .viewportOffset: {
+                                           view.GetViewportOffsetFromFrame ()
                                        }\n .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 =
                                             ruler.Repeat (
                                                           (int)Math.Ceiling (
-                                                                             inputHorizontalRuler.Bounds.Width
+                                                                             inputHorizontalRuler.Viewport.Width
                                                                              / (double)ruler.Length
                                                                             )
                                                          ) [
-                                                            ..inputHorizontalRuler.Bounds.Width];
+                                                            ..inputHorizontalRuler.Viewport.Width];
             inputVerticalRuler.Height = tvInput.Frame.Height + 1;
 
             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 =
-                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;

+ 1 - 0
UICatalog/UICatalog.cs

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

+ 3 - 3
UICatalog/UICatalog.csproj

@@ -28,9 +28,9 @@
   <None Update="./Scenarios/Spinning_globe_dark_small.gif" CopyToOutputDirectory="PreserveNewest" />
   </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="System.CommandLine" Version="2.0.0-beta4.22272.1" />
   </ItemGroup>

+ 7 - 2
UnitTests/Application/ApplicationTests.cs

@@ -172,7 +172,8 @@ public class ApplicationTests
 
             // Don't check 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.MainLoop);
             Assert.False (Application.EndAfterFirstIteration);
@@ -808,7 +809,11 @@ public class ApplicationTests
         Init ();
 
         // 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);
         RunState rs = Application.Begin (w);
 

+ 3 - 3
UnitTests/Application/MouseTests.cs

@@ -60,7 +60,7 @@ public class MouseTests
 
     /// <summary>
     ///     Tests that the mouse coordinates passed to the focused view are correct when the mouse is clicked. No adornments;
-    ///     Frame == Bounds
+    ///     Frame == Viewport
     /// </summary>
     [Theory]
     [AutoInitShutdown]
@@ -134,7 +134,7 @@ public class MouseTests
 
     /// <summary>
     ///     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>
     [AutoInitShutdown]
     [Theory]
@@ -207,7 +207,7 @@ public class MouseTests
 
         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.CanFocus = true;
 

+ 53 - 88
UnitTests/Dialogs/MessageBoxTests.cs

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

+ 26 - 26
UnitTests/Drawing/LineCanvasTests.cs

@@ -293,7 +293,7 @@ public class LineCanvasTests
         View v = GetCanvas (out LineCanvas lc);
         v.Width = 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 (x2, y2), len2, o2, s2);
@@ -366,7 +366,7 @@ public class LineCanvasTests
                 )]
     [Theory]
     [SetupFakeDriver]
-    public void Bounds_H_And_V_Lines_Both_Positive (
+    public void Viewport_H_And_V_Lines_Both_Positive (
         int x,
         int y,
         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.Vertical, LineStyle.Single);
 
-        Assert.Equal (new Rectangle (expectedX, expectedY, expectedWidth, expectedHeight), canvas.Bounds);
+        Assert.Equal (new Rectangle (expectedX, expectedY, expectedWidth, expectedHeight), canvas.Viewport);
     }
 
     [InlineData (
@@ -448,7 +448,7 @@ public class LineCanvasTests
                 )]
     [Theory]
     [SetupFakeDriver]
-    public void Bounds_H_Line (
+    public void Viewport_H_Line (
         int x,
         int y,
         int length,
@@ -461,12 +461,12 @@ public class LineCanvasTests
         var canvas = new LineCanvas ();
         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]
     [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)
         // This proves we aren't drawing excess above
@@ -483,27 +483,27 @@ public class LineCanvasTests
 
         // Add a short horiz line for ╔╡
         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
         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 ╡
         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 ╞
         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
         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
         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 (
                                  output,
@@ -516,7 +516,7 @@ public class LineCanvasTests
 
     [Fact]
     [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)
         // This proves we aren't drawing excess above
@@ -533,27 +533,27 @@ public class LineCanvasTests
 
         // Add a short horiz line for ╔╡
         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
         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 ╡
         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 ╞
         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
         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
         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 (
                                  output,
@@ -570,13 +570,13 @@ public class LineCanvasTests
     {
         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);
-        Assert.NotEqual (Rectangle.Empty, lc.Bounds);
+        Assert.NotEqual (Rectangle.Empty, lc.Viewport);
 
         lc.Clear ();
-        Assert.Equal (Rectangle.Empty, lc.Bounds);
+        Assert.Equal (Rectangle.Empty, lc.Viewport);
     }
 
     [InlineData (0, 0, Orientation.Horizontal, "─")]
@@ -873,7 +873,7 @@ public class LineCanvasTests
         //// Left Up
         //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 ();
         Assert.Equal (2, map.Count);
@@ -990,7 +990,7 @@ public class LineCanvasTests
         View v = GetCanvas (out LineCanvas lc);
         v.Width = 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);
 
@@ -1304,7 +1304,7 @@ public class LineCanvasTests
     /// <returns></returns>
     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 ();
         top.Add (v);
         Application.Begin (top);
@@ -1313,7 +1313,7 @@ public class LineCanvasTests
 
         v.DrawContentComplete += (s, e) =>
                                  {
-                                     v.Clear (v.Bounds);
+                                     v.FillRect (v.Viewport);
 
                                      foreach (KeyValuePair<Point, Rune> p in canvasCopy.GetMap ())
                                      {

+ 2 - 2
UnitTests/Drawing/StraightLineTests.cs

@@ -309,7 +309,7 @@ public class StraightLineTests
                 )]
     [Theory]
     [SetupFakeDriver]
-    public void Bounds (
+    public void Viewport (
         Orientation orientation,
         int x,
         int y,
@@ -322,6 +322,6 @@ public class StraightLineTests
     {
         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.Text;
 using System.Text.RegularExpressions;
+using UICatalog;
 using Xunit.Abstractions;
 using Xunit.Sdk;
 
@@ -123,6 +124,8 @@ public class TestRespondersDisposed : BeforeAfterTestAttribute
     public override void After (MethodInfo methodUnderTest)
     {
         Debug.WriteLine ($"After: {methodUnderTest.Name}");
+        base.After (methodUnderTest);
+
 #if DEBUG_IDISPOSABLE
         Assert.Empty (Responder.Instances);
 #endif
@@ -131,6 +134,7 @@ public class TestRespondersDisposed : BeforeAfterTestAttribute
     public override void Before (MethodInfo methodUnderTest)
     {
         Debug.WriteLine ($"Before: {methodUnderTest.Name}");
+        base.Before (methodUnderTest);
 #if DEBUG_IDISPOSABLE
 
         // 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)]
 public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
 {
@@ -157,6 +162,7 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
         View.Diagnostics = ViewDiagnosticFlags.Off;
 
         Application.Driver = null;
+        base.After (methodUnderTest);
     }
 
     public override void Before (MethodInfo methodUnderTest)
@@ -164,6 +170,7 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
         Debug.WriteLine ($"Before: {methodUnderTest.Name}");
         Assert.Null (Application.Driver);
         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>
     ///     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

+ 71 - 83
UnitTests/UICatalog/ScenarioTests.cs

@@ -15,85 +15,73 @@ public class ScenarioTests
         _output = output;
     }
 
+    public static TheoryData<Scenario, string> AllScenarios => TestHelpers.GetAllScenarioTheoryData ();
+
     /// <summary>
     ///     <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>
     /// </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
         Assert.Empty (Responder.Instances);
 #endif
@@ -132,14 +120,14 @@ public class ScenarioTests
 
         Application.Init (new FakeDriver ());
 
-        Toplevel top = new Toplevel ();
+        var top = new Toplevel ();
 
         _viewClasses = GetAllViewClassesCollection ()
                        .OrderBy (t => t.Name)
                        .Select (t => new KeyValuePair<string, Type> (t.Name, t))
                        .ToDictionary (t => t.Key, t => t.Value);
 
-        _leftPane = new Window
+        _leftPane = new()
         {
             Title = "Classes",
             X = 0,
@@ -150,7 +138,7 @@ public class ScenarioTests
             ColorScheme = Colors.ColorSchemes ["TopLevel"]
         };
 
-        _classListView = new ListView
+        _classListView = new()
         {
             X = 0,
             Y = 0,
@@ -162,7 +150,7 @@ public class ScenarioTests
         };
         _leftPane.Add (_classListView);
 
-        _settingsPane = new FrameView
+        _settingsPane = new()
         {
             X = Pos.Right (_leftPane),
             Y = 0, // for menu
@@ -172,12 +160,12 @@ public class ScenarioTests
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
             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);
 
         var radioItems = new [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" };
 
-        _locationFrame = new FrameView
+        _locationFrame = new()
         {
             X = Pos.Left (_computedCheckBox),
             Y = Pos.Bottom (_computedCheckBox),
@@ -189,21 +177,21 @@ public class ScenarioTests
 
         var label = new Label { X = 0, Y = 0, Text = "x:" };
         _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 (_xRadioGroup);
 
         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);
-        _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);
-        _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);
 
-        _sizeFrame = new FrameView
+        _sizeFrame = new()
         {
             X = Pos.Right (_locationFrame),
             Y = Pos.Y (_locationFrame),
@@ -213,25 +201,25 @@ public class ScenarioTests
         };
 
         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);
-        _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 (_wRadioGroup);
 
         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);
-        _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);
 
-        _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);
 
         _settingsPane.Add (_sizeFrame);
 
-        _hostPane = new FrameView
+        _hostPane = new()
         {
             X = Pos.Right (_leftPane),
             Y = Pos.Bottom (_settingsPane),
@@ -251,7 +239,7 @@ public class ScenarioTests
                                                       _hostPane.Remove (_curView);
                                                       _curView.Dispose ();
                                                       _curView = null;
-                                                      _hostPane.Clear (_hostPane.Bounds);
+                                                      _hostPane.FillRect (_hostPane.Viewport);
                                                   }
 
                                                   _curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]);
@@ -623,10 +611,10 @@ public class ScenarioTests
                                  };
 
         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 ();
 

+ 5 - 5
UnitTests/UnitTests.csproj

@@ -30,15 +30,15 @@
   </PropertyGroup>
   <ItemGroup>
     <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.runner.visualstudio" Version="2.5.7">
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="coverlet.collector" Version="6.0.1">
+    <PackageReference Include="coverlet.collector" Version="6.0.2">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>

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

@@ -7,7 +7,7 @@ public class AdornmentTests (ITestOutputHelper output)
     private readonly ITestOutputHelper _output = output;
 
     [Fact]
-    public void Bounds_Location_Always_Empty_Size_Correct ()
+    public void Viewport_Location_Always_Empty_Size_Correct ()
     {
         var view = new View
         {
@@ -21,28 +21,28 @@ public class AdornmentTests (ITestOutputHelper output)
         view.EndInit ();
 
         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;
-        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;
         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;
         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]
     [InlineData (0, 0, 0, 0, 0)]
     [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, 0)]
     [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);
         adornment.Thickness = new Thickness (thickness);
@@ -87,10 +87,10 @@ public class AdornmentTests (ITestOutputHelper output)
         Assert.Equal (new Rectangle (x, y, w, h), adornment.Frame);
 
         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]
     public void BoundsToScreen_Uses_Parent_Not_SuperView ()
     {
@@ -100,12 +100,12 @@ public class AdornmentTests (ITestOutputHelper output)
         parent.EndInit ();
 
         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.Bounds);
+        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Margin.Viewport);
 
         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);
     }
 
@@ -133,7 +133,7 @@ public class AdornmentTests (ITestOutputHelper output)
         view.EndInit ();
 
         Assert.Equal (new Rectangle (1, 2, 20, 31), view.Frame);
-        Assert.Equal (new Rectangle (0, 0, 8, 19), view.Bounds);
+        Assert.Equal (new Rectangle (0, 0, 8, 19), view.Viewport);
 
         // Margin.Frame is always the same as the view frame
         Assert.Equal (new Rectangle (0, 0, 20, 31), view.Margin.Frame);
@@ -246,9 +246,9 @@ public class AdornmentTests (ITestOutputHelper output)
         parent.EndInit ();
 
         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.Bounds);
+        Assert.Equal (new Rectangle (0, 0, 10, 10), parent.Margin.Viewport);
 
         Assert.Null (parent.Margin.SuperView);
         Assert.Equal (new Rectangle (1, 2, 10, 10), parent.Margin.FrameToScreen ());
@@ -281,24 +281,24 @@ public class AdornmentTests (ITestOutputHelper output)
     }
 
     [Fact]
-    public void Setting_Bounds_Throws ()
+    public void Setting_Viewport_Throws ()
     {
         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]
     public void Setting_SuperView_Throws ()
     {
         var adornment = new Adornment (null);
-        Assert.Throws<NotImplementedException> (() => adornment.SuperView = new View ());
+        Assert.Throws<InvalidOperationException> (() => adornment.SuperView = new View ());
     }
 
     [Fact]
     public void Setting_SuperViewRendersLineCanvas_Throws ()
     {
         var adornment = new Adornment (null);
-        Assert.Throws<NotImplementedException> (() => adornment.SuperViewRendersLineCanvas = true);
+        Assert.Throws<InvalidOperationException> (() => adornment.SuperViewRendersLineCanvas = true);
     }
 
     [Fact]
@@ -309,11 +309,11 @@ public class AdornmentTests (ITestOutputHelper output)
         parent.EndInit ();
 
         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);
         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]
@@ -340,8 +340,8 @@ public class AdornmentTests (ITestOutputHelper output)
     {
         var view = new View ();
         var raised = false;
-        view.BeginInit();
-        view.EndInit();
+        view.BeginInit ();
+        view.EndInit ();
 
         view.LayoutStarted += LayoutStarted;
         view.Margin.Thickness = new Thickness (1, 2, 3, 4);
@@ -373,4 +373,15 @@ public class AdornmentTests (ITestOutputHelper output)
             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]
     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 };
+        superView.Add (view);
+
         view.Border.Thickness = new Thickness (0, 1, 0, 0);
         view.Border.LineStyle = LineStyle.Single;
 
@@ -25,9 +31,9 @@ public class BorderTests
         Assert.Equal (ColorName.Green, view.Border.GetFocusColor ().Foreground.GetClosestNamedColor ());
         Assert.Equal (view.GetFocusColor (), view.Border.GetFocusColor ());
 
-        view.BeginInit ();
-        view.EndInit ();
-        view.Draw ();
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.Draw ();
 
         var expected = @"─┤A├─";
         TestHelpers.AssertDriverContentsAre (expected, _output);

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

@@ -3,7 +3,7 @@
 namespace Terminal.Gui.ViewTests;
 
 /// <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
 /// </summary>
 /// <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);
     }
 
-
+    // Adornment.ViewportToScreen tests ============================
 
     [Theory]
     [InlineData (0, 0, 0)]
     [InlineData (1, 0, 1)]
     [InlineData (-1, 0, -1)]
     [InlineData (11, 0, 11)]
-    public void BoundsToScreen_NoSuperView_WithoutAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_NoSuperView_WithoutAdornments (int frameX, int testX, int expectedX)
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
@@ -251,9 +251,9 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         view.Frame = frame;
 
         // 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.Equal (expectedX, marginScreen.X);
@@ -276,7 +276,7 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 0)]
     [InlineData (-1, -1, -2)]
     [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.
         // Arrange
@@ -286,15 +286,15 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         view.Margin.Thickness = new (1);
         view.Border.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;
 
-        Assert.Equal(4, view.Bounds.Width);
+        Assert.Equal(4, view.Viewport.Width);
 
         // 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.Equal (expectedX, marginScreen.X);
@@ -317,7 +317,7 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 0)]
     [InlineData (-1, -1, -2)]
     [InlineData (11, -1, 10)]
-    public void BoundsToScreen_SuperView_WithoutAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_SuperView_WithoutAdornments (int frameX, int testX, int expectedX)
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
@@ -338,9 +338,9 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
 
         // 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.Equal (expectedX, marginScreen.X);
@@ -363,7 +363,7 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 1)]
     [InlineData (-1, -1, -1)]
     [InlineData (11, -1, 11)]
-    public void BoundsToScreen_SuperView_WithAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_SuperView_WithAdornments (int frameX, int testX, int expectedX)
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
@@ -385,9 +385,9 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
 
         // 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.Equal (expectedX, marginScreen.X);
@@ -410,7 +410,7 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 0)]
     [InlineData (-1, -1, -2)]
     [InlineData (11, -1, 10)]
-    public void BoundsToScreen_NestedSuperView_WithoutAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_NestedSuperView_WithoutAdornments (int frameX, int testX, int expectedX)
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
@@ -441,9 +441,9 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
 
         // 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.Equal (expectedX, marginScreen.X);
@@ -466,7 +466,7 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 6)]
     [InlineData (-1, -1, 4)]
     [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.
         // Arrange
@@ -502,14 +502,13 @@ public class AdornmentToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
 
         // 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.Equal (expectedX, marginScreen.X);
         Assert.Equal (expectedX + view.Margin.Thickness.Left, borderScreen.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
 using System.Text;
 using Xunit.Abstractions;
+using static System.Net.Mime.MediaTypeNames;
 
 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]
     [InlineData (0, 0, 1, 1)]
     [InlineData (0, 0, 2, 2)]
     [InlineData (-1, -1, 2, 2)]
     [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 () };
 
@@ -32,28 +85,158 @@ public class DrawTests
         superView.LayoutSubviews ();
 
         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 (
                                                       @"
  ┌─┐
  │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 (
                                                       @"
  ┌─┐
  │ │
  └─┘",
-                                                      _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]
     [AutoInitShutdown]
-    [Trait("Category","Unicode")]
+    [Trait ("Category", "Unicode")]
     public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
     {
         const string us = "\U0000f900";
@@ -67,7 +250,7 @@ public class DrawTests
         Assert.Equal (2, r.GetColumns ());
 
         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 };
         win.Add (view, tf);
         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
     [Fact]
     [AutoInitShutdown]
-    [Trait("Category","Unicode")]
+    [Trait ("Category", "Unicode")]
     public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
     {
         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);
 
         Application.End (rsDiag);
@@ -171,7 +332,7 @@ public class DrawTests
 
     [Fact]
     [AutoInitShutdown]
-    [Trait("Category","Output")]
+    [Trait ("Category", "Output")]
     public void Colors_On_TextAlignment_Right_And_Bottom ()
     {
         var viewRight = new View
@@ -210,7 +371,7 @@ public class DrawTests
                                                       s     
                                                       t     
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         TestHelpers.AssertDriverAttributesAre (
@@ -231,15 +392,15 @@ public class DrawTests
 
     [Fact]
     [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 };
         view.BeginInit ();
         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 ();
 
@@ -249,40 +410,40 @@ public class DrawTests
                                                       ┌┐
                                                       └┘
                                                       """,
-                                                      _output
+                                                      output
                                                      );
     }
 
     [Fact]
     [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 };
         view.Border.Thickness = new Thickness (1, 1, 1, 0);
         view.BeginInit ();
         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 ();
 
-        TestHelpers.AssertDriverContentsWithFrameAre (string.Empty, _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (string.Empty, output);
     }
 
     [Fact]
     [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 };
         view.Border.Thickness = new Thickness (0, 1, 1, 1);
         view.BeginInit ();
         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 ();
 
@@ -292,22 +453,22 @@ public class DrawTests
                                                       """,
-                                                      _output
+                                                      output
                                                      );
     }
 
     [Fact]
     [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 };
         view.Border.Thickness = new Thickness (1, 1, 0, 1);
         view.BeginInit ();
         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 ();
 
@@ -317,23 +478,23 @@ public class DrawTests
                                                       """,
-                                                      _output
+                                                      output
                                                      );
     }
 
     [Fact]
     [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 };
         view.Border.Thickness = new Thickness (1, 0, 1, 1);
 
         view.BeginInit ();
         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 ();
 
@@ -343,13 +504,13 @@ public class DrawTests
 
                                                       ┌┐
                                                       """,
-                                                      _output
+                                                      output
                                                      );
     }
 
     [Fact]
     [AutoInitShutdown]
-    public void Draw_Negative_Bounds_Horizontal_With_New_Lines ()
+    public void Draw_Negative_Viewport_Horizontal_With_New_Lines ()
     {
         var subView = new View
         {
@@ -420,7 +581,7 @@ public class DrawTests
                                                        3V
                                                        4i
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.X = -1;
@@ -435,12 +596,12 @@ public class DrawTests
                                                        V
                                                        i
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.X = -2;
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre (@"", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (@"", output);
 
         content.X = 0;
         content.Y = -1;
@@ -455,7 +616,7 @@ public class DrawTests
                                                        4i
                                                        5e
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.Y = -6;
@@ -470,7 +631,7 @@ public class DrawTests
                                                        9 
                                                        0 
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.Y = -19;
@@ -481,22 +642,22 @@ public class DrawTests
 
                                                        9
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.Y = -20;
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
 
         content.X = -2;
         content.Y = 0;
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
     }
 
     [Fact]
     [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.
         var subView = new View
@@ -537,7 +698,7 @@ public class DrawTests
                                                        01234
                                                        subVi
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.X = -1;
@@ -549,7 +710,7 @@ public class DrawTests
                                                        12345
                                                        ubVie
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.Y = -1;
@@ -560,22 +721,22 @@ public class DrawTests
 
                                                        ubVie
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.Y = -2;
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
 
         content.X = -20;
         content.Y = 0;
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
     }
 
     [Fact]
     [AutoInitShutdown]
-    public void Draw_Negative_Bounds_Vertical ()
+    public void Draw_Negative_Viewport_Vertical ()
     {
         var subView = new View
         {
@@ -622,7 +783,7 @@ public class DrawTests
                                                        3V
                                                        4i
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.X = -1;
@@ -637,12 +798,12 @@ public class DrawTests
                                                        V
                                                        i
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.X = -2;
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre (@"", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre (@"", output);
 
         content.X = 0;
         content.Y = -1;
@@ -657,7 +818,7 @@ public class DrawTests
                                                        4i
                                                        5e
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.Y = -6;
@@ -672,7 +833,7 @@ public class DrawTests
                                                        9 
                                                        0 
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.Y = -19;
@@ -683,17 +844,17 @@ public class DrawTests
 
                                                        9
                                                       """,
-                                                      _output
+                                                      output
                                                      );
 
         content.Y = -20;
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
 
         content.X = -2;
         content.Y = 0;
         Application.Refresh ();
-        TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+        TestHelpers.AssertDriverContentsWithFrameAre ("", output);
     }
 
     [Theory]
@@ -705,7 +866,7 @@ public class DrawTests
         var view = new View { Width = 10, Height = 1 };
         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.
@@ -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, 9, 9, typeof (Border))]
     [InlineData (1, 1, 1, 1, 1, 10, 10, typeof (Margin))]
-    public void Contains (int frameX, int frameY, int marginThickness, int borderThickness, int 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 ()
         {
@@ -77,7 +77,7 @@ public class FindDeepestViewTests (ITestOutputHelper output)
         };
         view.Margin.Thickness = new Thickness (marginThickness);
         view.Border.Thickness = new Thickness (borderThickness);
-        view.Padding.Thickness = new Thickness (paddingThinkcness);
+        view.Padding.Thickness = new Thickness (paddingThickness);
 
         Type? containedType = null;
         if (view.Contains (testX, testY))
@@ -271,6 +271,71 @@ public class FindDeepestViewTests (ITestOutputHelper output)
         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]
     [InlineData (0, 0, typeof (Margin))]
     [InlineData (9, 9, typeof (Margin))]
@@ -336,6 +401,100 @@ public class FindDeepestViewTests (ITestOutputHelper output)
         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
     [Theory]
     [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;
     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]
     [TestRespondersDisposed]
@@ -101,8 +27,8 @@ public class AbsoluteLayoutTests
 
         Assert.Equal (
                       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 (2), v.Y);
         Assert.Equal ($"Absolute({newFrame.Height})", v.Height.ToString ());
@@ -136,8 +62,8 @@ public class AbsoluteLayoutTests
 
         Assert.Equal (
                       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.Y})", v.Y.ToString ());
         Assert.Equal (Dim.Sized (3), v.Width);
@@ -250,8 +176,8 @@ public class AbsoluteLayoutTests
 
         Assert.Equal (
                       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.Y);
         Assert.Equal (Dim.Sized (0), v.Width);
@@ -265,8 +191,8 @@ public class AbsoluteLayoutTests
 
         Assert.Equal (
                       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 (2), v.Y);
         Assert.Equal (Dim.Sized (3), v.Width);
@@ -279,8 +205,8 @@ public class AbsoluteLayoutTests
 
         Assert.Equal (
                       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 (2), v.Y);
         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
         // 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 (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 (2), v.Y);
         Assert.Equal (Dim.Sized (0), v.Width);
@@ -304,7 +230,7 @@ public class AbsoluteLayoutTests
         v = new View ();
         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.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.Y);
         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 };
         Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
         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 (2), v.Y);
         Assert.Equal (Dim.Sized (3), v.Width);
@@ -344,72 +270,4 @@ public class AbsoluteLayoutTests
         Assert.Equal (new Rectangle (10, 10, 10, 10), v2.Frame);
         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);
-    }
-}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно