فهرست منبع

Refactoring... WIP

Tig 9 ماه پیش
والد
کامیت
127bfd50a2
100فایلهای تغییر یافته به همراه4510 افزوده شده و 3086 حذف شده
  1. 1 1
      CommunityToolkitExample/LoginView.cs
  2. 15 6
      Terminal.Gui/Application/Application.Initialization.cs
  3. 2 2
      Terminal.Gui/Application/Application.Keyboard.cs
  4. 1 1
      Terminal.Gui/Application/Application.Mouse.cs
  5. 62 45
      Terminal.Gui/Application/Application.Run.cs
  6. 28 5
      Terminal.Gui/Application/Application.Screen.cs
  7. 3 1
      Terminal.Gui/Application/Application.cs
  8. 0 9
      Terminal.Gui/Application/ApplicationNavigation.cs
  9. 1 1
      Terminal.Gui/Clipboard/Clipboard.cs
  10. 82 34
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  11. 16 16
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  12. 7 7
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  13. 11 13
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  14. 23 14
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  15. 143 88
      Terminal.Gui/Drawing/LineCanvas.cs
  16. 283 0
      Terminal.Gui/Drawing/Region.cs
  17. 5 4
      Terminal.Gui/Drawing/Ruler.cs
  18. 8 8
      Terminal.Gui/Drawing/StraightLine.cs
  19. 24 24
      Terminal.Gui/Drawing/Thickness.cs
  20. 5 7
      Terminal.Gui/Terminal.Gui.csproj
  21. 8 8
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs
  22. 4 2
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.PopUp.cs
  23. 9 9
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  24. 4 1
      Terminal.Gui/Text/TextFormatter.cs
  25. 92 107
      Terminal.Gui/View/Adornment/Adornment.cs
  26. 100 64
      Terminal.Gui/View/Adornment/Border.cs
  27. 168 109
      Terminal.Gui/View/Adornment/Margin.cs
  28. 6 13
      Terminal.Gui/View/Adornment/Padding.cs
  29. 58 37
      Terminal.Gui/View/Adornment/ShadowView.cs
  30. 4 5
      Terminal.Gui/View/DrawEventArgs.cs
  31. 10 2
      Terminal.Gui/View/Layout/Dim.cs
  32. 69 67
      Terminal.Gui/View/Layout/DimAuto.cs
  33. 1 1
      Terminal.Gui/View/Layout/DimFunc.cs
  34. 1 1
      Terminal.Gui/View/Layout/LayoutEventArgs.cs
  35. 30 0
      Terminal.Gui/View/Layout/LayoutException.cs
  36. 8 8
      Terminal.Gui/View/Layout/Pos.cs
  37. 30 43
      Terminal.Gui/View/Layout/PosAlign.cs
  38. 1 1
      Terminal.Gui/View/Layout/PosAnchorEnd.cs
  39. 1 1
      Terminal.Gui/View/Layout/PosFunc.cs
  40. 23 5
      Terminal.Gui/View/Layout/PosView.cs
  41. 74 90
      Terminal.Gui/View/View.Adornments.cs
  42. 2 1
      Terminal.Gui/View/View.Arrangement.cs
  43. 121 0
      Terminal.Gui/View/View.Attribute.cs
  44. 8 6
      Terminal.Gui/View/View.Content.cs
  45. 19 6
      Terminal.Gui/View/View.Diagnostics.cs
  46. 167 0
      Terminal.Gui/View/View.Drawing.Clipping.cs
  47. 172 0
      Terminal.Gui/View/View.Drawing.Primitives.cs
  48. 381 432
      Terminal.Gui/View/View.Drawing.cs
  49. 16 10
      Terminal.Gui/View/View.Hierarchy.cs
  50. 389 346
      Terminal.Gui/View/View.Layout.cs
  51. 13 10
      Terminal.Gui/View/View.Mouse.cs
  52. 9 9
      Terminal.Gui/View/View.Navigation.cs
  53. 0 4
      Terminal.Gui/View/View.ScrollBars.cs
  54. 13 8
      Terminal.Gui/View/View.Text.cs
  55. 24 23
      Terminal.Gui/View/View.cs
  56. 1 1
      Terminal.Gui/View/ViewportSettings.cs
  57. 67 51
      Terminal.Gui/Views/Bar.cs
  58. 1 1
      Terminal.Gui/Views/Button.cs
  59. 1 1
      Terminal.Gui/Views/CheckBox.cs
  60. 10 10
      Terminal.Gui/Views/ColorBar.cs
  61. 46 30
      Terminal.Gui/Views/ColorPicker.16.cs
  62. 7 7
      Terminal.Gui/Views/ColorPicker.cs
  63. 31 24
      Terminal.Gui/Views/ComboBox.cs
  64. 6 2
      Terminal.Gui/Views/DatePicker.cs
  65. 2 2
      Terminal.Gui/Views/Dialog.cs
  66. 50 23
      Terminal.Gui/Views/FileDialog.cs
  67. 15 4
      Terminal.Gui/Views/GraphView/Annotations.cs
  68. 53 8
      Terminal.Gui/Views/GraphView/GraphView.cs
  69. 2 2
      Terminal.Gui/Views/GraphView/Series.cs
  70. 42 31
      Terminal.Gui/Views/HexView.cs
  71. 8 7
      Terminal.Gui/Views/Line.cs
  72. 4 5
      Terminal.Gui/Views/LineView.cs
  73. 77 60
      Terminal.Gui/Views/ListView.cs
  74. 39 31
      Terminal.Gui/Views/Menu/Menu.cs
  75. 18 20
      Terminal.Gui/Views/Menu/MenuBar.cs
  76. 1 1
      Terminal.Gui/Views/MenuBarv2.cs
  77. 3 2
      Terminal.Gui/Views/Menuv2.cs
  78. 17 3
      Terminal.Gui/Views/NumericUpDown.cs
  79. 39 61
      Terminal.Gui/Views/ProgressBar.cs
  80. 17 18
      Terminal.Gui/Views/RadioGroup.cs
  81. 146 176
      Terminal.Gui/Views/Scroll/Scroll.cs
  82. 185 154
      Terminal.Gui/Views/Scroll/ScrollBar.cs
  83. 210 166
      Terminal.Gui/Views/Scroll/ScrollSlider.cs
  84. 17 13
      Terminal.Gui/Views/ScrollBarView.cs
  85. 19 9
      Terminal.Gui/Views/ScrollView.cs
  86. 140 142
      Terminal.Gui/Views/Shortcut.cs
  87. 24 22
      Terminal.Gui/Views/Slider.cs
  88. 1 1
      Terminal.Gui/Views/SpinnerView/SpinnerStyle.cs
  89. 43 10
      Terminal.Gui/Views/SpinnerView/SpinnerView.cs
  90. 1 1
      Terminal.Gui/Views/StatusBar.cs
  91. 5 4
      Terminal.Gui/Views/Tab.cs
  92. 130 92
      Terminal.Gui/Views/TabView.cs
  93. 3 3
      Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs
  94. 1 1
      Terminal.Gui/Views/TableView/ListTableSource.cs
  95. 138 43
      Terminal.Gui/Views/TableView/TableView.cs
  96. 2 2
      Terminal.Gui/Views/TableView/TreeTableSource.cs
  97. 23 21
      Terminal.Gui/Views/TextField.cs
  98. 19 17
      Terminal.Gui/Views/TextValidateField.cs
  99. 90 88
      Terminal.Gui/Views/TextView.cs
  100. 1 1
      Terminal.Gui/Views/Tile.cs

+ 1 - 1
CommunityToolkitExample/LoginView.cs

@@ -59,7 +59,7 @@ internal partial class LoginView : IRecipient<Message<LoginActions>>
                 }
         }
         SetText();
-        Application.Refresh ();
+        Application.LayoutAndDraw ();
     }
 
     private void SetText ()

+ 15 - 6
Terminal.Gui/Application/Application.Initialization.cs

@@ -39,7 +39,6 @@ public static partial class Application // Initialization (Init/Shutdown)
     [RequiresDynamicCode ("AOT")]
     public static void Init (ConsoleDriver? driver = null, string? driverName = null) { InternalInit (driver, driverName); }
 
-    internal static bool IsInitialized { get; set; }
     internal static int MainThreadId { get; set; } = -1;
 
     // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop.
@@ -59,12 +58,12 @@ public static partial class Application // Initialization (Init/Shutdown)
         bool calledViaRunT = false
     )
     {
-        if (IsInitialized && driver is null)
+        if (Initialized && driver is null)
         {
             return;
         }
 
-        if (IsInitialized)
+        if (Initialized)
         {
             throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown.");
         }
@@ -173,7 +172,7 @@ public static partial class Application // Initialization (Init/Shutdown)
 
         SupportedCultures = GetSupportedCultures ();
         MainThreadId = Thread.CurrentThread.ManagedThreadId;
-        bool init = IsInitialized = true;
+        bool init = Initialized = true;
         InitializedChanged?.Invoke (null, new (init));
     }
 
@@ -215,17 +214,27 @@ public static partial class Application // Initialization (Init/Shutdown)
     {
         // TODO: Throw an exception if Init hasn't been called.
 
-        bool wasInitialized = IsInitialized;
+        bool wasInitialized = Initialized;
         ResetState ();
         PrintJsonErrors ();
 
         if (wasInitialized)
         {
-            bool init = IsInitialized;
+            bool init = Initialized;
             InitializedChanged?.Invoke (null, new (in init));
         }
     }
 
+    /// <summary>
+    ///     Gets whether the application has been initialized with <see cref="Init"/> and not yet shutdown with <see cref="Shutdown"/>.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    ///     The <see cref="InitializedChanged"/> event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
+    /// </para>
+    /// </remarks>
+    public static bool Initialized { get; internal set; }
+
     /// <summary>
     ///     This event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
     /// </summary>

+ 2 - 2
Terminal.Gui/Application/Application.Keyboard.cs

@@ -120,7 +120,7 @@ public static partial class Application // Keyboard handling
     /// <returns><see langword="true"/> if the key was handled.</returns>
     public static bool RaiseKeyUpEvent (Key key)
     {
-        if (!IsInitialized)
+        if (!Initialized)
         {
             return true;
         }
@@ -200,7 +200,7 @@ public static partial class Application // Keyboard handling
                     Command.Refresh,
                     static () =>
                     {
-                        Refresh ();
+                        LayoutAndDraw ();
 
                         return true;
                     }

+ 1 - 1
Terminal.Gui/Application/Application.Mouse.cs

@@ -227,7 +227,7 @@ public static partial class Application // Mouse handling
         {
             if (deepestViewUnderMouse is Adornment adornmentView)
             {
-                deepestViewUnderMouse = adornmentView.Parent!.SuperView;
+                deepestViewUnderMouse = adornmentView.Parent?.SuperView;
             }
             else
             {

+ 62 - 45
Terminal.Gui/Application/Application.Run.cs

@@ -1,6 +1,7 @@
 #nullable enable
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
 
 namespace Terminal.Gui;
 
@@ -80,27 +81,20 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     {
         ArgumentNullException.ThrowIfNull (toplevel);
 
-//#if DEBUG_IDISPOSABLE
-//        Debug.Assert (!toplevel.WasDisposed);
+        //#if DEBUG_IDISPOSABLE
+        //        Debug.Assert (!toplevel.WasDisposed);
 
-//        if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel)
-//        {
-//            Debug.Assert (_cachedRunStateToplevel.WasDisposed);
-//        }
-//#endif
+        //        if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel)
+        //        {
+        //            Debug.Assert (_cachedRunStateToplevel.WasDisposed);
+        //        }
+        //#endif
 
         // Ensure the mouse is ungrabbed.
         MouseGrabView = null;
 
         var rs = new RunState (toplevel);
 
-        // View implements ISupportInitializeNotification which is derived from ISupportInitialize
-        if (!toplevel.IsInitialized)
-        {
-            toplevel.BeginInit ();
-            toplevel.EndInit ();
-        }
-
 #if DEBUG_IDISPOSABLE
         if (Top is { } && toplevel != Top && !TopLevels.Contains (Top))
         {
@@ -176,16 +170,26 @@ public static partial class Application // Run (Begin, Run, End, Stop)
                     Top.HasFocus = false;
                 }
 
+                // Force leave events for any entered views in the old Top
+                if (GetLastMousePosition () is { })
+                {
+                    RaiseMouseEnterLeaveEvents (GetLastMousePosition ()!.Value, new List<View?> ());
+                }
+
                 Top?.OnDeactivate (toplevel);
-                Toplevel previousCurrent = Top!;
+                Toplevel previousTop = Top!;
 
                 Top = toplevel;
-                Top.OnActivate (previousCurrent);
+                Top.OnActivate (previousTop);
             }
         }
 
-        toplevel.SetRelativeLayout (Driver!.Screen.Size);
-        toplevel.LayoutSubviews ();
+        // View implements ISupportInitializeNotification which is derived from ISupportInitialize
+        if (!toplevel.IsInitialized)
+        {
+            toplevel.BeginInit ();
+            toplevel.EndInit (); // Calls Layout
+        }
 
         // Try to set initial focus to any TabStop
         if (!toplevel.HasFocus)
@@ -195,15 +199,16 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
         toplevel.OnLoaded ();
 
-        Refresh ();
-
         if (PositionCursor ())
         {
-            Driver.UpdateCursor ();
+            Driver?.UpdateCursor ();
         }
 
         NotifyNewRunState?.Invoke (toplevel, new (rs));
 
+        // Force an Idle event so that an Iteration (and Refresh) happen.
+        Application.Invoke (() => { });
+
         return rs;
     }
 
@@ -225,11 +230,12 @@ public static partial class Application // Run (Begin, Run, End, Stop)
         // If the view is not visible or enabled, don't position the cursor
         if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
         {
-            Driver!.GetCursorVisibility (out CursorVisibility current);
+            CursorVisibility current = CursorVisibility.Invisible;
+            Driver?.GetCursorVisibility (out current);
 
             if (current != CursorVisibility.Invisible)
             {
-                Driver.SetCursorVisibility (CursorVisibility.Invisible);
+                Driver?.SetCursorVisibility (CursorVisibility.Invisible);
             }
 
             return false;
@@ -326,7 +332,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     public static T Run<T> (Func<Exception, bool>? errorHandler = null, ConsoleDriver? driver = null)
         where T : Toplevel, new()
     {
-        if (!IsInitialized)
+        if (!Initialized)
         {
             // Init() has NOT been called.
             InternalInit (driver, null, true);
@@ -381,7 +387,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     {
         ArgumentNullException.ThrowIfNull (view);
 
-        if (IsInitialized)
+        if (Initialized)
         {
             if (Driver is null)
             {
@@ -452,7 +458,10 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
     ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
     /// </remarks>
-    public static object AddTimeout (TimeSpan time, Func<bool> callback) { return MainLoop!.AddTimeout (time, callback); }
+    public static object? AddTimeout (TimeSpan time, Func<bool> callback)
+    {
+        return MainLoop?.AddTimeout (time, callback) ?? null;
+    }
 
     /// <summary>Removes a previously scheduled timeout</summary>
     /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
@@ -486,20 +495,25 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     /// <summary>Wakes up the running application that might be waiting on input.</summary>
     public static void Wakeup () { MainLoop?.Wakeup (); }
 
-    /// <summary>Triggers a refresh of the entire display.</summary>
-    public static void Refresh ()
+    /// <summary>
+    /// Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that need to be laid out (see <see cref="View.NeedsLayout"/>) will be laid out.
+    /// Only Views that need to be drawn (see <see cref="View.NeedsDraw"/>) will be drawn.
+    /// </summary>
+    /// <param name="forceDraw">If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and should only be overriden for testing.</param>
+    public static void LayoutAndDraw (bool forceDraw = false)
     {
-        foreach (Toplevel tl in TopLevels.Reverse ())
-        {
-            if (tl.LayoutNeeded)
-            {
-                tl.LayoutSubviews ();
-            }
+        bool neededLayout = View.Layout (TopLevels.Reverse (), Screen.Size);
 
-            tl.Draw ();
+        if (forceDraw)
+        {
+            Driver?.ClearContents ();
         }
 
-        Driver!.Refresh ();
+        View.SetClipToScreen ();
+        View.Draw (TopLevels, neededLayout || forceDraw);
+        View.SetClipToScreen ();
+
+        Driver?.Refresh ();
     }
 
     /// <summary>This event is raised on each iteration of the main loop.</summary>
@@ -534,24 +548,25 @@ public static partial class Application // Run (Begin, Run, End, Stop)
                 return;
             }
 
-            RunIteration (ref state, ref firstIteration);
+            firstIteration = RunIteration (ref state, firstIteration);
         }
 
         MainLoop!.Running = false;
 
         // Run one last iteration to consume any outstanding input events from Driver
         // This is important for remaining OnKeyUp events.
-        RunIteration (ref state, ref firstIteration);
+        RunIteration (ref state, firstIteration);
     }
 
     /// <summary>Run one application iteration.</summary>
     /// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
     /// <param name="firstIteration">
-    ///     Set to <see langword="true"/> if this is the first run loop iteration. Upon return, it
-    ///     will be set to <see langword="false"/> if at least one iteration happened.
+    ///     Set to <see langword="true"/> if this is the first run loop iteration.
     /// </param>
-    public static void RunIteration (ref RunState state, ref bool firstIteration)
+    /// <returns><see langword="false"/> if at least one iteration happened.</returns>
+    public static bool RunIteration (ref RunState state, bool firstIteration = false)
     {
+        // If the driver has events pending do an iteration of the driver MainLoop
         if (MainLoop!.Running && MainLoop.EventsPending ())
         {
             // Notify Toplevel it's ready
@@ -561,6 +576,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
             }
 
             MainLoop.RunIteration ();
+
             Iteration?.Invoke (null, new ());
         }
 
@@ -568,16 +584,17 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
         if (Top is null)
         {
-            return;
+            return firstIteration;
         }
 
-        Refresh ();
+        LayoutAndDraw ();
 
         if (PositionCursor ())
         {
             Driver!.UpdateCursor ();
         }
 
+        return firstIteration;
     }
 
     /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
@@ -652,7 +669,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
         if (TopLevels.Count > 0)
         {
             Top = TopLevels.Peek ();
-            Top.SetNeedsDisplay ();
+            Top.SetNeedsDraw ();
         }
 
         if (runState.Toplevel is { HasFocus: true })
@@ -670,6 +687,6 @@ public static partial class Application // Run (Begin, Run, End, Stop)
         runState.Toplevel = null;
         runState.Dispose ();
 
-        Refresh ();
+        LayoutAndDraw ();
     }
 }

+ 28 - 5
Terminal.Gui/Application/Application.Screen.cs

@@ -3,13 +3,35 @@ namespace Terminal.Gui;
 
 public static partial class Application // Screen related stuff
 {
+    private static Rectangle? _screen;
+
     /// <summary>
-    ///     Gets the size of the screen. This is the size of the screen as reported by the <see cref="ConsoleDriver"/>.
+    ///     Gets or sets the size of the screen. By default, this is the size of the screen as reported by the <see cref="ConsoleDriver"/>.
     /// </summary>
     /// <remarks>
+    /// <para>
     ///     If the <see cref="ConsoleDriver"/> has not been initialized, this will return a default size of 2048x2048; useful for unit tests.
+    /// </para>
     /// </remarks>
-    public static Rectangle Screen => Driver?.Screen ?? new (0, 0, 2048, 2048);
+    public static Rectangle Screen
+    {
+        get
+        {
+            if (_screen == null)
+            {
+                _screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
+            }
+            return _screen.Value;
+        }
+        set
+        {
+            if (value is {} && (value.X != 0 || value.Y != 0))
+            {
+                throw new NotImplementedException ($"Screen locations other than 0, 0 are not yet supported");
+            }
+            _screen = value;
+        }
+    }
 
     /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
     /// <remarks>
@@ -33,14 +55,15 @@ public static partial class Application // Screen related stuff
             return false;
         }
 
+        Screen = new (Point.Empty, args.Size.Value);
+
         foreach (Toplevel t in TopLevels)
         {
-            t.SetRelativeLayout (args.Size.Value);
-            t.LayoutSubviews ();
             t.OnSizeChanging (new (args.Size));
+            t.SetNeedsLayout ();
         }
 
-        Refresh ();
+        LayoutAndDraw ();
 
         return true;
     }

+ 3 - 1
Terminal.Gui/Application/Application.cs

@@ -185,6 +185,8 @@ public static partial class Application
             Driver = null;
         }
 
+        _screen = null;
+
         // Don't reset ForceDriver; it needs to be set before Init is called.
         //ForceDriver = string.Empty;
         //Force16Colors = false;
@@ -194,7 +196,7 @@ public static partial class Application
         NotifyNewRunState = null;
         NotifyStopRunState = null;
         MouseGrabView = null;
-        IsInitialized = false;
+        Initialized = false;
 
         // Mouse
         _lastMousePosition = null;

+ 0 - 9
Terminal.Gui/Application/ApplicationNavigation.cs

@@ -30,15 +30,6 @@ public class ApplicationNavigation
     public View? GetFocused ()
     {
         return _focused;
-
-        if (_focused is { CanFocus: true, HasFocus: true })
-        {
-            return _focused;
-        }
-
-        _focused = null;
-
-        return null;
     }
 
     /// <summary>

+ 1 - 1
Terminal.Gui/Clipboard/Clipboard.cs

@@ -148,7 +148,7 @@ internal static class ClipboardProcessRunner
         bool waitForOutput = true
     )
     {
-        var output = string.Empty;
+            var output = string.Empty;
 
         using (var process = new Process
                {

+ 82 - 34
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -23,14 +23,14 @@ public abstract class ConsoleDriver
     /// <summary>Gets the location and size of the terminal screen.</summary>
     internal Rectangle Screen => new (0, 0, Cols, Rows);
 
-    private Rectangle _clip;
+    private Region? _clip = null;
 
     /// <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 of <see cref="Clip"/> region.</value>
-    public Rectangle Clip
+    internal Region? Clip
     {
         get => _clip;
         set
@@ -40,8 +40,13 @@ public abstract class ConsoleDriver
                 return;
             }
 
+            _clip = value;
+
             // Don't ever let Clip be bigger than Screen
-            _clip = Rectangle.Intersect (Screen, value);
+            if (_clip is { })
+            {
+                _clip.Intersect (Screen);
+            }
         }
     }
 
@@ -52,13 +57,13 @@ public abstract class ConsoleDriver
     ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
     ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
     /// </summary>
-    public int Col { get; internal set; }
+    internal int Col { get; private set; }
 
     /// <summary>The number of columns visible in the terminal.</summary>
-    public virtual int Cols
+    internal virtual int Cols
     {
         get => _cols;
-        internal set
+        set
         {
             _cols = value;
             ClearContents ();
@@ -70,22 +75,22 @@ public abstract class ConsoleDriver
     ///     <see cref="UpdateScreen"/> is called.
     ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
     /// </summary>
-    public Cell [,]? Contents { get; internal set; }
+    internal Cell [,]? Contents { get; set; }
 
     /// <summary>The leftmost column in the terminal.</summary>
-    public virtual int Left { get; internal set; } = 0;
+    internal virtual int Left { get; set; } = 0;
 
     /// <summary>
     ///     Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
     ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
     /// </summary>
-    public int Row { get; internal set; }
+    internal int Row { get; private set; }
 
     /// <summary>The number of rows visible in the terminal.</summary>
-    public virtual int Rows
+    internal virtual int Rows
     {
         get => _rows;
-        internal set
+        set
         {
             _rows = value;
             ClearContents ();
@@ -93,7 +98,7 @@ public abstract class ConsoleDriver
     }
 
     /// <summary>The topmost row in the terminal.</summary>
-    public virtual int Top { get; internal set; } = 0;
+    internal virtual int Top { get; set; } = 0;
 
     /// <summary>
     ///     Set this to true in any unit tests that attempt to test drivers other than FakeDriver.
@@ -120,16 +125,18 @@ public abstract class ConsoleDriver
     ///     </para>
     /// </remarks>
     /// <param name="rune">Rune to add.</param>
-    public void AddRune (Rune rune)
+    internal void AddRune (Rune rune)
     {
         int runeWidth = -1;
-        bool validLocation = IsValidLocation (Col, Row);
+        bool validLocation = IsValidLocation (rune, Col, Row);
 
         if (Contents is null)
         {
             return;
         }
 
+        Rectangle clipRect = Clip!.GetBounds ();
+
         if (validLocation)
         {
             rune = rune.MakePrintable ();
@@ -217,24 +224,29 @@ public abstract class ConsoleDriver
                     {
                         Contents [Row, Col].Rune = rune;
 
-                        if (Col < Clip.Right - 1)
+                        if (Col < clipRect.Right - 1)
                         {
                             Contents [Row, Col + 1].IsDirty = true;
                         }
                     }
                     else if (runeWidth == 2)
                     {
-                        if (Col == Clip.Right - 1)
+                        if (!Clip.Contains (Col + 1, Row))
                         {
                             // We're at the right edge of the clip, so we can't display a wide character.
                             // TODO: Figure out if it is better to show a replacement character or ' '
                             Contents [Row, Col].Rune = Rune.ReplacementChar;
                         }
+                        else if (!Clip.Contains (Col, Row))
+                        {
+                            // Our 1st column is outside the clip, so we can't display a wide character.
+                            Contents [Row, Col+1].Rune = Rune.ReplacementChar;
+                        }
                         else
                         {
                             Contents [Row, Col].Rune = rune;
 
-                            if (Col < Clip.Right - 1)
+                            if (Col < clipRect.Right - 1)
                             {
                                 // Invalidate cell to right so that it doesn't get drawn
                                 // TODO: Figure out if it is better to show a replacement character or ' '
@@ -264,7 +276,7 @@ public abstract class ConsoleDriver
         {
             Debug.Assert (runeWidth <= 2);
 
-            if (validLocation && Col < Clip.Right)
+            if (validLocation && Col < clipRect.Right)
             {
                 lock (Contents!)
                 {
@@ -288,7 +300,7 @@ public abstract class ConsoleDriver
     ///     convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
     /// </summary>
     /// <param name="c">Character to add.</param>
-    public void AddRune (char c) { AddRune (new Rune (c)); }
+    internal void AddRune (char c) { AddRune (new Rune (c)); }
 
     /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
     /// <remarks>
@@ -300,7 +312,7 @@ public abstract class ConsoleDriver
     ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
     /// </remarks>
     /// <param name="str">String.</param>
-    public void AddStr (string str)
+    internal void AddStr (string str)
     {
         List<Rune> runes = str.EnumerateRunes ().ToList ();
 
@@ -311,12 +323,14 @@ public abstract class ConsoleDriver
     }
 
     /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>
-    public void ClearContents ()
+    internal void ClearContents ()
     {
         Contents = new Cell [Rows, Cols];
+
         //CONCURRENCY: Unsynchronized access to Clip isn't safe.
         // TODO: ClearContents should not clear the clip; it should only clear the contents. Move clearing it elsewhere.
-        Clip = Screen;
+        Clip = new (Screen);
+
         _dirtyLines = new bool [Rows];
 
         lock (Contents)
@@ -335,13 +349,20 @@ public abstract class ConsoleDriver
                 _dirtyLines [row] = true;
             }
         }
+
+        ClearedContents?.Invoke (this, EventArgs.Empty);
     }
 
+    /// <summary>
+    ///     Raised each time <see cref="ClearContents"/> is called. For benchmarking.
+    /// </summary>
+    public event EventHandler<EventArgs>? ClearedContents;
+
     /// <summary>
     /// Sets <see cref="Contents"/> as dirty for situations where views
     /// don't need layout and redrawing, but just refresh the screen.
     /// </summary>
-    public void SetContentsAsDirty ()
+    internal void SetContentsAsDirty ()
     {
         lock (Contents!)
         {
@@ -366,15 +387,20 @@ public abstract class ConsoleDriver
     /// </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)
+    internal void FillRect (Rectangle rect, Rune rune = default)
     {
-        rect = Rectangle.Intersect (rect, Clip);
+        // BUGBUG: This should be a method on Region
+        rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen);
         lock (Contents!)
         {
             for (int r = rect.Y; r < rect.Y + rect.Height; r++)
             {
                 for (int c = rect.X; c < rect.X + rect.Width; c++)
                 {
+                    if (!IsValidLocation (rune, c, r))
+                    {
+                        continue;
+                    }
                     Contents [r, c] = new Cell
                     {
                         Rune = (rune != default ? rune : (Rune)' '),
@@ -392,7 +418,7 @@ public abstract class ConsoleDriver
     /// </summary>
     /// <param name="rect"></param>
     /// <param name="c"></param>
-    public void FillRect (Rectangle rect, char c) { FillRect (rect, new Rune (c)); }
+    internal void FillRect (Rectangle rect, char c) { FillRect (rect, new Rune (c)); }
 
     /// <summary>Gets the terminal cursor visibility.</summary>
     /// <param name="visibility">The current <see cref="CursorVisibility"/></param>
@@ -411,18 +437,28 @@ public abstract class ConsoleDriver
     /// </returns>
     public virtual bool IsRuneSupported (Rune rune) { return Rune.IsValid (rune.Value); }
 
-    /// <summary>Tests whether the specified coordinate are valid for drawing.</summary>
+    /// <summary>Tests whether the specified coordinate are valid for drawing the specified Rune.</summary>
+    /// <param name="rune">Used to determine if one or two columns are required.</param>
     /// <param name="col">The column.</param>
     /// <param name="row">The row.</param>
     /// <returns>
     ///     <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)
+    internal bool IsValidLocation (Rune rune, int col, int row)
     {
-        return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row);
+        if (rune.GetColumns () < 2)
+        {
+            return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip!.Contains (col, row);
+        }
+        else
+        {
+
+            return Clip!.Contains (col, row) || Clip!.Contains (col + 1, row);
+        }
     }
 
+    // TODO: Make internal once Menu is upgraded
     /// <summary>
     ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
     ///     Used by <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
@@ -445,10 +481,21 @@ public abstract class ConsoleDriver
 
     /// <summary>Called when the terminal size changes. Fires the <see cref="SizeChanged"/> event.</summary>
     /// <param name="args"></param>
-    public void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); }
+    internal void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); }
 
     /// <summary>Updates the screen to reflect all the changes that have been done to the display buffer</summary>
-    public abstract void Refresh ();
+    internal void Refresh ()
+    {
+        bool updated = UpdateScreen ();
+        UpdateCursor ();
+
+        Refreshed?.Invoke (this, new EventArgs<bool> (in updated));
+    }
+
+    /// <summary>
+    ///     Raised each time <see cref="Refresh"/> is called. For benchmarking.
+    /// </summary>
+    public event EventHandler<EventArgs<bool>>? Refreshed;
 
     /// <summary>Sets the terminal cursor visibility.</summary>
     /// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
@@ -466,7 +513,8 @@ public abstract class ConsoleDriver
     public abstract void UpdateCursor ();
 
     /// <summary>Redraws the physical screen with the contents that have been queued up via any of the printing commands.</summary>
-    public abstract void UpdateScreen ();
+    /// <returns><see langword="true"/> if any updates to the screen were made.</returns>
+    public abstract bool UpdateScreen ();
 
     #region Setup & Teardown
 
@@ -530,7 +578,7 @@ public abstract class ConsoleDriver
     /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
     /// <remarks>Implementations should call <c>base.SetAttribute(c)</c>.</remarks>
     /// <param name="c">C.</param>
-    public Attribute SetAttribute (Attribute c)
+    internal Attribute SetAttribute (Attribute c)
     {
         Attribute prevAttribute = CurrentAttribute;
         CurrentAttribute = c;
@@ -540,7 +588,7 @@ public abstract class ConsoleDriver
 
     /// <summary>Gets the current <see cref="Attribute"/>.</summary>
     /// <returns>The current attribute.</returns>
-    public Attribute GetAttribute () { return CurrentAttribute; }
+    internal Attribute GetAttribute () { return CurrentAttribute; }
 
     // TODO: This is only overridden by CursesDriver. Once CursesDriver supports 24-bit color, this virtual method can be
     // removed (and Attribute can lose the platformColor property).

+ 16 - 16
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -19,20 +19,20 @@ internal class CursesDriver : ConsoleDriver
     private UnixMainLoop _mainLoopDriver;
     private object _processInputToken;
 
-    public override int Cols
+    internal override int Cols
     {
         get => Curses.Cols;
-        internal set
+        set
         {
             Curses.Cols = value;
             ClearContents ();
         }
     }
 
-    public override int Rows
+    internal override int Rows
     {
         get => Curses.Lines;
-        internal set
+        set
         {
             Curses.Lines = value;
             ClearContents ();
@@ -93,7 +93,7 @@ internal class CursesDriver : ConsoleDriver
             return;
         }
 
-        if (IsValidLocation (col, row))
+        if (IsValidLocation (default, col, row))
         {
             Curses.move (row, col);
         }
@@ -101,16 +101,12 @@ internal class CursesDriver : ConsoleDriver
         {
             // Not a valid location (outside screen or clip region)
             // Move within the clip region, then AddRune will actually move to Col, Row
-            Curses.move (Clip.Y, Clip.X);
+            Rectangle clipRect = Clip.GetBounds ();
+            Curses.move (clipRect.Y, clipRect.X);
         }
     }
 
-    public override void Refresh ()
-    {
-        UpdateScreen ();
-        UpdateCursor ();
-    }
-
+    
     public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
     {
         KeyCode key;
@@ -228,8 +224,9 @@ internal class CursesDriver : ConsoleDriver
         }
     }
 
-    public override void UpdateScreen ()
+    public override bool UpdateScreen ()
     {
+        bool updated = false;
         if (Force16Colors)
         {
             for (var row = 0; row < Rows; row++)
@@ -297,7 +294,7 @@ internal class CursesDriver : ConsoleDriver
                 || Contents.Length != Rows * Cols
                 || Rows != Console.WindowHeight)
             {
-                return;
+                return updated;
             }
 
             var top = 0;
@@ -315,7 +312,7 @@ internal class CursesDriver : ConsoleDriver
             {
                 if (Console.WindowHeight < 1)
                 {
-                    return;
+                    return updated;
                 }
 
                 if (!_dirtyLines [row])
@@ -325,7 +322,7 @@ internal class CursesDriver : ConsoleDriver
 
                 if (!SetCursorPosition (0, row))
                 {
-                    return;
+                    return updated;
                 }
 
                 _dirtyLines [row] = false;
@@ -338,6 +335,7 @@ internal class CursesDriver : ConsoleDriver
 
                     for (; col < cols; col++)
                     {
+                        updated = true;
                         if (!Contents [row, col].IsDirty)
                         {
                             if (output.Length > 0)
@@ -440,6 +438,8 @@ internal class CursesDriver : ConsoleDriver
                 outputWidth = 0;
             }
         }
+
+        return updated;
     }
 
     private bool SetCursorPosition (int col, int row)

+ 7 - 7
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -101,8 +101,10 @@ public class FakeDriver : ConsoleDriver
         return new MainLoop (_mainLoopDriver);
     }
 
-    public override void UpdateScreen ()
+    public override bool UpdateScreen ()
     {
+        bool updated = false;
+
         int savedRow = FakeConsole.CursorTop;
         int savedCol = FakeConsole.CursorLeft;
         bool savedCursorVisible = FakeConsole.CursorVisible;
@@ -122,6 +124,8 @@ public class FakeDriver : ConsoleDriver
                 continue;
             }
 
+            updated = true;
+
             FakeConsole.CursorTop = row;
             FakeConsole.CursorLeft = 0;
 
@@ -218,13 +222,9 @@ public class FakeDriver : ConsoleDriver
         FakeConsole.CursorTop = savedRow;
         FakeConsole.CursorLeft = savedCol;
         FakeConsole.CursorVisible = savedCursorVisible;
+        return updated;
     }
 
-    public override void Refresh ()
-    {
-        UpdateScreen ();
-        UpdateCursor ();
-    }
 
     #region Color Handling
 
@@ -456,7 +456,7 @@ public class FakeDriver : ConsoleDriver
         }
 
         // CONCURRENCY: Unsynchronized access to Clip is not safe.
-        Clip = new (0, 0, Cols, Rows);
+        Clip = new (Screen);
     }
 
     public override void UpdateCursor ()

+ 11 - 13
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -823,12 +823,6 @@ internal class NetDriver : ConsoleDriver
     public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix
                                               || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931);
 
-    public override void Refresh ()
-    {
-        UpdateScreen ();
-        UpdateCursor ();
-    }
-
     public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
     {
         var input = new InputResult
@@ -876,15 +870,16 @@ internal class NetDriver : ConsoleDriver
         StartReportingMouseMoves ();
     }
 
-    public override void UpdateScreen ()
+    public override bool UpdateScreen ()
     {
+        bool updated = false;
         if (RunningUnitTests
             || _winSizeChanging
             || Console.WindowHeight < 1
             || Contents.Length != Rows * Cols
             || Rows != Console.WindowHeight)
         {
-            return;
+            return updated;
         }
 
         var top = 0;
@@ -902,7 +897,7 @@ internal class NetDriver : ConsoleDriver
         {
             if (Console.WindowHeight < 1)
             {
-                return;
+                return updated;
             }
 
             if (!_dirtyLines [row])
@@ -912,9 +907,10 @@ internal class NetDriver : ConsoleDriver
 
             if (!SetCursorPosition (0, row))
             {
-                return;
+                return updated;
             }
 
+            updated = true;
             _dirtyLines [row] = false;
             output.Clear ();
 
@@ -1043,6 +1039,8 @@ internal class NetDriver : ConsoleDriver
             lastCol += outputWidth;
             outputWidth = 0;
         }
+
+        return updated;
     }
 
     internal override void End ()
@@ -1239,12 +1237,12 @@ internal class NetDriver : ConsoleDriver
             catch (IOException)
             {
                 // CONCURRENCY: Unsynchronized access to Clip is not safe.
-                Clip = new (0, 0, Cols, Rows);
+                Clip = new (Screen);
             }
             catch (ArgumentOutOfRangeException)
             {
                 // CONCURRENCY: Unsynchronized access to Clip is not safe.
-                Clip = new (0, 0, Cols, Rows);
+                Clip = new (Screen);
             }
         }
         else
@@ -1253,7 +1251,7 @@ internal class NetDriver : ConsoleDriver
         }
 
         // CONCURRENCY: Unsynchronized access to Clip is not safe.
-        Clip = new (0, 0, Cols, Rows);
+        Clip = new (Screen);
     }
 
     #endregion

+ 23 - 14
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1100,13 +1100,6 @@ internal class WindowsDriver : ConsoleDriver
 
     public override bool IsRuneSupported (Rune rune) { return base.IsRuneSupported (rune) && rune.IsBmp; }
 
-    public override void Refresh ()
-    {
-        UpdateScreen ();
-        //WinConsole?.SetInitialCursorVisibility ();
-        UpdateCursor ();
-    }
-
     public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
     {
         var input = new WindowsConsole.InputRecord
@@ -1299,13 +1292,14 @@ internal class WindowsDriver : ConsoleDriver
 
     #endregion Cursor Handling
 
-    public override void UpdateScreen ()
+    public override bool UpdateScreen ()
     {
+        bool updated = false;
         Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (Cols, Rows);
 
         if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows))
         {
-            return;
+            return updated;
         }
 
         var bufferCoords = new WindowsConsole.Coord
@@ -1322,6 +1316,7 @@ internal class WindowsDriver : ConsoleDriver
             }
 
             _dirtyLines [row] = false;
+            updated = true;
 
             for (var col = 0; col < Cols; col++)
             {
@@ -1380,6 +1375,8 @@ internal class WindowsDriver : ConsoleDriver
         }
 
         WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion);
+
+        return updated;
     }
 
     internal override void End ()
@@ -1419,6 +1416,7 @@ internal class WindowsDriver : ConsoleDriver
                     Size winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
                     Cols = winSize.Width;
                     Rows = winSize.Height;
+                    OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows)));
                 }
 
                 WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion);
@@ -1441,7 +1439,7 @@ internal class WindowsDriver : ConsoleDriver
 
         _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols];
         // CONCURRENCY: Unsynchronized access to Clip is not safe.
-        Clip = new (0, 0, Cols, Rows);
+        Clip = new (Screen);
 
         _damageRegion = new WindowsConsole.SmallRect
         {
@@ -1861,7 +1859,7 @@ internal class WindowsDriver : ConsoleDriver
     {
         _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols];
         // CONCURRENCY: Unsynchronized access to Clip is not safe.
-        Clip = new (0, 0, Cols, Rows);
+        Clip = new (Screen);
 
         _damageRegion = new WindowsConsole.SmallRect
         {
@@ -2210,15 +2208,18 @@ internal class WindowsMainLoop : IMainLoopDriver
                 // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there
                 // are no timers, but there IS an idle handler waiting.
                 _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token);
+                //
             }
+            _eventReady.Reset ();
         }
         catch (OperationCanceledException)
         {
+            _eventReady.Reset ();
             return true;
         }
         finally
         {
-            _eventReady.Reset ();
+            //_eventReady.Reset ();
         }
 
         if (!_eventReadyTokenSource.IsCancellationRequested)
@@ -2316,10 +2317,18 @@ internal class WindowsMainLoop : IMainLoopDriver
 
             if (_resultQueue?.Count == 0)
             {
-                _resultQueue.Enqueue (_winConsole.ReadConsoleInput ());
+                var input = _winConsole.ReadConsoleInput ();
+
+                //if (input [0].EventType != WindowsConsole.EventType.Focus)
+                {
+                    _resultQueue.Enqueue (input);
+                }
             }
 
-            _eventReady.Set ();
+            if (_resultQueue?.Count > 0)
+            {
+                _eventReady.Set ();
+            }
         }
     }
 

+ 143 - 88
Terminal.Gui/Drawing/LineCanvas.cs

@@ -1,62 +1,9 @@
 #nullable enable
 namespace Terminal.Gui;
 
-/// <summary>Facilitates box drawing and line intersection detection and rendering.  Does not support diagonal lines.</summary>
+/// <summary>Facilitates box drawing and line intersection detection and rendering. Does not support diagonal lines.</summary>
 public class LineCanvas : IDisposable
 {
-    /// <summary>
-    ///     Optional <see cref="FillPair"/> which when present overrides the <see cref="StraightLine.Attribute"/>
-    ///     (colors) of lines in the canvas. This can be used e.g. to apply a global <see cref="GradientFill"/>
-    ///     across all lines.
-    /// </summary>
-    public FillPair? Fill { get; set; }
-
-    private readonly List<StraightLine> _lines = [];
-
-    private readonly Dictionary<IntersectionRuneType, IntersectionRuneResolver> _runeResolvers = new ()
-    {
-        {
-            IntersectionRuneType.ULCorner,
-            new ULIntersectionRuneResolver ()
-        },
-        {
-            IntersectionRuneType.URCorner,
-            new URIntersectionRuneResolver ()
-        },
-        {
-            IntersectionRuneType.LLCorner,
-            new LLIntersectionRuneResolver ()
-        },
-        {
-            IntersectionRuneType.LRCorner,
-            new LRIntersectionRuneResolver ()
-        },
-        {
-            IntersectionRuneType.TopTee,
-            new TopTeeIntersectionRuneResolver ()
-        },
-        {
-            IntersectionRuneType.LeftTee,
-            new LeftTeeIntersectionRuneResolver ()
-        },
-        {
-            IntersectionRuneType.RightTee,
-            new RightTeeIntersectionRuneResolver ()
-        },
-        {
-            IntersectionRuneType.BottomTee,
-            new BottomTeeIntersectionRuneResolver ()
-        },
-        {
-            IntersectionRuneType.Cross,
-            new CrossIntersectionRuneResolver ()
-        }
-
-        // TODO: Add other resolvers
-    };
-
-    private Rectangle _cachedViewport;
-
     /// <summary>Creates a new instance.</summary>
     public LineCanvas ()
     {
@@ -66,54 +13,62 @@ public class LineCanvas : IDisposable
         Applied += ConfigurationManager_Applied;
     }
 
+    private readonly List<StraightLine> _lines = [];
+
     /// <summary>Creates a new instance with the given <paramref name="lines"/>.</summary>
     /// <param name="lines">Initial lines for the canvas.</param>
     public LineCanvas (IEnumerable<StraightLine> lines) : this () { _lines = lines.ToList (); }
 
+    /// <summary>
+    ///     Optional <see cref="FillPair"/> which when present overrides the <see cref="StraightLine.Attribute"/>
+    ///     (colors) of lines in the canvas. This can be used e.g. to apply a global <see cref="GradientFill"/>
+    ///     across all lines.
+    /// </summary>
+    public FillPair? Fill { get; set; }
+
+    private Rectangle _cachedBounds;
+
     /// <summary>
     ///     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.
+    ///     the furthest left/top and Size is defined by the line that extends the furthest right/bottom.
     /// </summary>
-    public Rectangle Viewport
+    public Rectangle Bounds
     {
         get
         {
-            if (_cachedViewport.IsEmpty)
+            if (_cachedBounds.IsEmpty)
             {
                 if (_lines.Count == 0)
                 {
-                    return _cachedViewport;
+                    return _cachedBounds;
                 }
 
-                Rectangle viewport = _lines [0].Viewport;
+                Rectangle bounds = _lines [0].Bounds;
 
                 for (var i = 1; i < _lines.Count; i++)
                 {
-                    viewport = Rectangle.Union (viewport, _lines [i].Viewport);
+                    bounds = Rectangle.Union (bounds, _lines [i].Bounds);
                 }
 
-                if (viewport is { Width: 0 } or { Height: 0 })
+                if (bounds is { Width: 0 } or { Height: 0 })
                 {
-                    viewport = viewport with
+                    bounds = bounds with
                     {
-                        Width = Math.Clamp (viewport.Width, 1, short.MaxValue),
-                        Height = Math.Clamp (viewport.Height, 1, short.MaxValue)
+                        Width = Math.Clamp (bounds.Width, 1, short.MaxValue),
+                        Height = Math.Clamp (bounds.Height, 1, short.MaxValue)
                     };
                 }
 
-                _cachedViewport = viewport;
+                _cachedBounds = bounds;
             }
 
-            return _cachedViewport;
+            return _cachedBounds;
         }
     }
 
     /// <summary>Gets the lines in the canvas.</summary>
     public IReadOnlyCollection<StraightLine> Lines => _lines.AsReadOnly ();
 
-    /// <inheritdoc/>
-    public void Dispose () { Applied -= ConfigurationManager_Applied; }
-
     /// <summary>
     ///     <para>Adds a new <paramref name="length"/> long line to the canvas starting at <paramref name="start"/>.</para>
     ///     <para>
@@ -141,7 +96,7 @@ public class LineCanvas : IDisposable
         Attribute? attribute = null
     )
     {
-        _cachedViewport = Rectangle.Empty;
+        _cachedBounds = Rectangle.Empty;
         _lines.Add (new (start, length, orientation, style, attribute));
     }
 
@@ -149,37 +104,67 @@ public class LineCanvas : IDisposable
     /// <param name="line"></param>
     public void AddLine (StraightLine line)
     {
-        _cachedViewport = Rectangle.Empty;
+        _cachedBounds = Rectangle.Empty;
         _lines.Add (line);
     }
 
+    private Region? _exclusionRegion;
+
+    /// <summary>
+    ///     Causes the provided region to be excluded from <see cref="GetCellMap"/> and <see cref="GetMap()"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Each call to this method will add to the exclusion region. To clear the exclusion region, call
+    ///         <see cref="ClearCache"/>.
+    ///     </para>
+    /// </remarks>
+    public void Exclude (Region region)
+    {
+        _exclusionRegion ??= new ();
+        _exclusionRegion.Union (region);
+    }
+
+    /// <summary>
+    ///     Clears the exclusion region. After calling this method, <see cref="GetCellMap"/> and <see cref="GetMap()"/> will
+    ///     return all points in the canvas.
+    /// </summary>
+    public void ClearExclusions () { _exclusionRegion = null; }
+
     /// <summary>Clears all lines from the LineCanvas.</summary>
     public void Clear ()
     {
-        _cachedViewport = Rectangle.Empty;
+        _cachedBounds = Rectangle.Empty;
         _lines.Clear ();
+        ClearExclusions ();
     }
 
     /// <summary>
-    ///     Clears any cached states from the canvas Call this method if you make changes to lines that have already been
+    ///     Clears any cached states from the canvas. Call this method if you make changes to lines that have already been
     ///     added.
     /// </summary>
-    public void ClearCache () { _cachedViewport = Rectangle.Empty; }
+    public void ClearCache () { _cachedBounds = Rectangle.Empty; }
 
     /// <summary>
     ///     Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their
     ///     locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate
     ///     intersection symbols.
     /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Only the points within the <see cref="Bounds"/> of the canvas that are not in the exclusion region will be
+    ///         returned. To exclude points from the map, use <see cref="Exclude"/>.
+    ///     </para>
+    /// </remarks>
     /// <returns>A map of all the points within the canvas.</returns>
     public Dictionary<Point, Cell?> GetCellMap ()
     {
         Dictionary<Point, Cell?> map = new ();
 
         // walk through each pixel of the bitmap
-        for (int y = Viewport.Y; y < Viewport.Y + Viewport.Height; y++)
+        for (int y = Bounds.Y; y < Bounds.Y + Bounds.Height; y++)
         {
-            for (int x = Viewport.X; x < Viewport.X + Viewport.Width; x++)
+            for (int x = Bounds.X; x < Bounds.X + Bounds.Width; x++)
             {
                 IntersectionDefinition? [] intersects = _lines
                                                         .Select (l => l.Intersects (x, y))
@@ -188,7 +173,7 @@ public class LineCanvas : IDisposable
 
                 Cell? cell = GetCellForIntersects (Application.Driver, intersects);
 
-                if (cell is { })
+                if (cell is { } && _exclusionRegion?.Contains (x, y) is null or false)
                 {
                     map.Add (new (x, y), cell);
                 }
@@ -205,6 +190,12 @@ public class LineCanvas : IDisposable
     ///     locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate
     ///     intersection symbols.
     /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Only the points within the <paramref name="inArea"/> of the canvas that are not in the exclusion region will be
+    ///         returned. To exclude points from the map, use <see cref="Exclude"/>.
+    ///     </para>
+    /// </remarks>
     /// <param name="inArea">A rectangle to constrain the search by.</param>
     /// <returns>A map of the points within the canvas that intersect with <paramref name="inArea"/>.</returns>
     public Dictionary<Point, Rune> GetMap (Rectangle inArea)
@@ -223,7 +214,7 @@ public class LineCanvas : IDisposable
 
                 Rune? rune = GetRuneForIntersects (Application.Driver, intersects);
 
-                if (rune is { })
+                if (rune is { } && _exclusionRegion?.Contains (x, y) is null or false)
                 {
                     map.Add (new (x, y), rune.Value);
                 }
@@ -238,8 +229,14 @@ public class LineCanvas : IDisposable
     ///     locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate
     ///     intersection symbols.
     /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Only the points within the <see cref="Bounds"/> of the canvas that are not in the exclusion region will be
+    ///         returned. To exclude points from the map, use <see cref="Exclude"/>.
+    ///     </para>
+    /// </remarks>
     /// <returns>A map of all the points within the canvas.</returns>
-    public Dictionary<Point, Rune> GetMap () { return GetMap (Viewport); }
+    public Dictionary<Point, Rune> GetMap () { return GetMap (Bounds); }
 
     /// <summary>Merges one line canvas into this one.</summary>
     /// <param name="lineCanvas"></param>
@@ -249,6 +246,12 @@ public class LineCanvas : IDisposable
         {
             AddLine (line);
         }
+
+        if (lineCanvas._exclusionRegion is { })
+        {
+            _exclusionRegion ??= new ();
+            _exclusionRegion.Union (lineCanvas._exclusionRegion);
+        }
     }
 
     /// <summary>Removes the last line added to the canvas</summary>
@@ -267,13 +270,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="Viewport"/> has negative coordinates. For example, if the canvas contains a single line that
+    ///     even if <see cref="Bounds"/> 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 (Viewport.IsEmpty)
+        if (Bounds.IsEmpty)
         {
             return string.Empty;
         }
@@ -282,13 +285,13 @@ public class LineCanvas : IDisposable
         Dictionary<Point, Rune> runeMap = GetMap ();
 
         // Create the rune canvas
-        Rune [,] canvas = new Rune [Viewport.Height, Viewport.Width];
+        Rune [,] canvas = new Rune [Bounds.Height, Bounds.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 - Viewport.X;
-            int y = kvp.Key.Y - Viewport.Y;
+            int x = kvp.Key.X - Bounds.X;
+            int y = kvp.Key.Y - Bounds.Y;
             canvas [y, x] = kvp.Value;
         }
 
@@ -312,7 +315,10 @@ public class LineCanvas : IDisposable
         return sb.ToString ();
     }
 
-    private bool All (IntersectionDefinition? [] intersects, Orientation orientation) { return intersects.All (i => i!.Line.Orientation == orientation); }
+    private static bool All (IntersectionDefinition? [] intersects, Orientation orientation)
+    {
+        return intersects.All (i => i!.Line.Orientation == orientation);
+    }
 
     private void ConfigurationManager_Applied (object? sender, ConfigurationManagerEventArgs e)
     {
@@ -329,13 +335,55 @@ public class LineCanvas : IDisposable
     /// <param name="intersects"></param>
     /// <param name="types"></param>
     /// <returns></returns>
-    private bool Exactly (HashSet<IntersectionType> intersects, params IntersectionType [] types) { return intersects.SetEquals (types); }
+    private static bool Exactly (HashSet<IntersectionType> intersects, params IntersectionType [] types) { return intersects.SetEquals (types); }
 
     private Attribute? GetAttributeForIntersects (IntersectionDefinition? [] intersects)
     {
-        return Fill != null ? Fill.GetAttribute (intersects [0]!.Point) : intersects [0]!.Line.Attribute;
+        return Fill?.GetAttribute (intersects [0]!.Point) ?? intersects [0]!.Line.Attribute;
     }
 
+    private readonly Dictionary<IntersectionRuneType, IntersectionRuneResolver> _runeResolvers = new ()
+    {
+        {
+            IntersectionRuneType.ULCorner,
+            new ULIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.URCorner,
+            new URIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.LLCorner,
+            new LLIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.LRCorner,
+            new LRIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.TopTee,
+            new TopTeeIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.LeftTee,
+            new LeftTeeIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.RightTee,
+            new RightTeeIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.BottomTee,
+            new BottomTeeIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.Cross,
+            new CrossIntersectionRuneResolver ()
+        }
+
+        // TODO: Add other resolvers
+    };
+
     private Cell? GetCellForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
     {
         if (!intersects.Any ())
@@ -677,7 +725,7 @@ public class LineCanvas : IDisposable
         internal Rune _thickBoth;
         internal Rune _thickH;
         internal Rune _thickV;
-        public IntersectionRuneResolver () { SetGlyphs (); }
+        protected IntersectionRuneResolver () { SetGlyphs (); }
 
         public Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
         {
@@ -853,4 +901,11 @@ public class LineCanvas : IDisposable
             _normal = Glyphs.URCorner;
         }
     }
+
+    /// <inheritdoc/>
+    public void Dispose ()
+    {
+        Applied -= ConfigurationManager_Applied;
+        GC.SuppressFinalize (this);
+    }
 }

+ 283 - 0
Terminal.Gui/Drawing/Region.cs

@@ -0,0 +1,283 @@
+/// <summary>
+///     Represents a region composed of one or more rectangles, providing methods for union, intersection, exclusion, and
+///     complement operations.
+/// </summary>
+public class Region : IDisposable
+{
+    private List<Rectangle> _rectangles;
+
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="Region"/> class.
+    /// </summary>
+    public Region () { _rectangles = new (); }
+
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="Region"/> class with the specified rectangle.
+    /// </summary>
+    /// <param name="rectangle">The initial rectangle for the region.</param>
+    public Region (Rectangle rectangle) { _rectangles = new () { rectangle }; }
+
+    /// <summary>
+    ///     Adds the specified rectangle to the region.
+    /// </summary>
+    /// <param name="rectangle">The rectangle to add to the region.</param>
+    public void Union (Rectangle rectangle)
+    {
+        _rectangles.Add (rectangle);
+        _rectangles = MergeRectangles (_rectangles);
+    }
+
+    /// <summary>
+    ///     Adds the specified region to this region.
+    /// </summary>
+    /// <param name="region">The region to add to this region.</param>
+    public void Union (Region region)
+    {
+        _rectangles.AddRange (region._rectangles);
+        _rectangles = MergeRectangles (_rectangles);
+    }
+
+    /// <summary>
+    ///     Updates the region to be the intersection of itself with the specified rectangle.
+    /// </summary>
+    /// <param name="rectangle">The rectangle to intersect with the region.</param>
+    public void Intersect (Rectangle rectangle)
+    {
+        _rectangles = _rectangles.Select (r => Rectangle.Intersect (r, rectangle)).Where (r => !r.IsEmpty).ToList ();
+    }
+
+    /// <summary>
+    ///     Updates the region to be the intersection of itself with the specified region.
+    /// </summary>
+    /// <param name="region">The region to intersect with this region.</param>
+    public void Intersect (Region region)
+    {
+        List<Rectangle> intersections = new List<Rectangle> ();
+
+        foreach (Rectangle rect1 in _rectangles)
+        {
+            foreach (Rectangle rect2 in region._rectangles)
+            {
+                Rectangle intersected = Rectangle.Intersect (rect1, rect2);
+
+                if (!intersected.IsEmpty)
+                {
+                    intersections.Add (intersected);
+                }
+            }
+        }
+
+        _rectangles = intersections;
+    }
+
+    /// <summary>
+    ///     Removes the specified rectangle from the region.
+    /// </summary>
+    /// <param name="rectangle">The rectangle to exclude from the region.</param>
+    public void Exclude (Rectangle rectangle) { _rectangles = _rectangles.SelectMany (r => SubtractRectangle (r, rectangle)).ToList (); }
+
+    /// <summary>
+    ///     Removes the portion of the specified region from this region.
+    /// </summary>
+    /// <param name="region">The region to exclude from this region.</param>
+    public void Exclude (Region region)
+    {
+        foreach (Rectangle rect in region._rectangles)
+        {
+            _rectangles = _rectangles.SelectMany (r => SubtractRectangle (r, rect)).ToList ();
+        }
+    }
+
+    /// <summary>
+    ///     Updates the region to be the complement of itself within the specified bounds.
+    /// </summary>
+    /// <param name="bounds">The bounding rectangle to use for complementing the region.</param>
+    public void Complement (Rectangle bounds)
+    {
+        if (bounds.IsEmpty || _rectangles.Count == 0)
+        {
+            _rectangles.Clear ();
+
+            return;
+        }
+
+        List<Rectangle> complementRectangles = new List<Rectangle> { bounds };
+
+        foreach (Rectangle rect in _rectangles)
+        {
+            complementRectangles = complementRectangles.SelectMany (r => SubtractRectangle (r, rect)).ToList ();
+        }
+
+        _rectangles = complementRectangles;
+    }
+
+    /// <summary>
+    ///     Creates an exact copy of the region.
+    /// </summary>
+    /// <returns>A new <see cref="Region"/> that is a copy of this instance.</returns>
+    public Region Clone ()
+    {
+        var clone = new Region ();
+        clone._rectangles = new (_rectangles);
+
+        return clone;
+    }
+
+    /// <summary>
+    ///     Gets a bounding rectangle for the entire region.
+    /// </summary>
+    /// <returns>A <see cref="Rectangle"/> that bounds the region.</returns>
+    public Rectangle GetBounds ()
+    {
+        if (_rectangles.Count == 0)
+        {
+            return Rectangle.Empty;
+        }
+
+        int left = _rectangles.Min (r => r.Left);
+        int top = _rectangles.Min (r => r.Top);
+        int right = _rectangles.Max (r => r.Right);
+        int bottom = _rectangles.Max (r => r.Bottom);
+
+        return new (left, top, right - left, bottom - top);
+    }
+
+    /// <summary>
+    ///     Determines whether the region is empty.
+    /// </summary>
+    /// <returns><c>true</c> if the region is empty; otherwise, <c>false</c>.</returns>
+    public bool IsEmpty () { return !_rectangles.Any (); }
+
+    /// <summary>
+    ///     Determines whether the specified point is contained within the region.
+    /// </summary>
+    /// <param name="x">The x-coordinate of the point.</param>
+    /// <param name="y">The y-coordinate of the point.</param>
+    /// <returns><c>true</c> if the point is contained within the region; otherwise, <c>false</c>.</returns>
+    public bool Contains (int x, int y) { return _rectangles.Any (r => r.Contains (x, y)); }
+
+    /// <summary>
+    ///     Determines whether the specified rectangle is contained within the region.
+    /// </summary>
+    /// <param name="rectangle">The rectangle to check for containment.</param>
+    /// <returns><c>true</c> if the rectangle is contained within the region; otherwise, <c>false</c>.</returns>
+    public bool Contains (Rectangle rectangle) { return _rectangles.Any (r => r.Contains (rectangle)); }
+
+    /// <summary>
+    ///     Returns an array of rectangles that represent the region.
+    /// </summary>
+    /// <returns>An array of <see cref="Rectangle"/> objects that make up the region.</returns>
+    public Rectangle [] GetRegionScans () { return _rectangles.ToArray (); }
+
+    /// <summary>
+    ///     Offsets all rectangles in the region by the specified amounts.
+    /// </summary>
+    /// <param name="offsetX">The amount to offset along the x-axis.</param>
+    /// <param name="offsetY">The amount to offset along the y-axis.</param>
+    public void Offset (int offsetX, int offsetY)
+    {
+        for (int i = 0; i < _rectangles.Count; i++)
+        {
+            var rect = _rectangles [i];
+            _rectangles [i] = new Rectangle (rect.Left + offsetX, rect.Top + offsetY, rect.Width, rect.Height);
+        }
+    }
+
+    /// <summary>
+    ///     Merges overlapping rectangles into a minimal set of non-overlapping rectangles.
+    /// </summary>
+    /// <param name="rectangles">The list of rectangles to merge.</param>
+    /// <returns>A list of merged rectangles.</returns>
+    private List<Rectangle> MergeRectangles (List<Rectangle> rectangles)
+    {
+        // Simplified merging logic: this does not handle all edge cases for merging overlapping rectangles.
+        // For a full implementation, a plane sweep algorithm or similar would be needed.
+        List<Rectangle> merged = new List<Rectangle> (rectangles);
+        bool mergedAny;
+
+        do
+        {
+            mergedAny = false;
+
+            for (var i = 0; i < merged.Count; i++)
+            {
+                for (int j = i + 1; j < merged.Count; j++)
+                {
+                    if (merged [i].IntersectsWith (merged [j]))
+                    {
+                        merged [i] = Rectangle.Union (merged [i], merged [j]);
+                        merged.RemoveAt (j);
+                        mergedAny = true;
+
+                        break;
+                    }
+                }
+
+                if (mergedAny)
+                {
+                    break;
+                }
+            }
+        }
+        while (mergedAny);
+
+        return merged;
+    }
+
+    /// <summary>
+    ///     Subtracts the specified rectangle from the original rectangle, returning the resulting rectangles.
+    /// </summary>
+    /// <param name="original">The original rectangle.</param>
+    /// <param name="subtract">The rectangle to subtract from the original.</param>
+    /// <returns>An enumerable collection of resulting rectangles after subtraction.</returns>
+    private IEnumerable<Rectangle> SubtractRectangle (Rectangle original, Rectangle subtract)
+    {
+        if (!original.IntersectsWith (subtract))
+        {
+            yield return original;
+
+            yield break;
+        }
+
+        // Top segment
+        if (original.Top < subtract.Top)
+        {
+            yield return new (original.Left, original.Top, original.Width, subtract.Top - original.Top);
+        }
+
+        // Bottom segment
+        if (original.Bottom > subtract.Bottom)
+        {
+            yield return new (original.Left, subtract.Bottom, original.Width, original.Bottom - subtract.Bottom);
+        }
+
+        // Left segment
+        if (original.Left < subtract.Left)
+        {
+            int top = Math.Max (original.Top, subtract.Top);
+            int bottom = Math.Min (original.Bottom, subtract.Bottom);
+
+            if (bottom > top)
+            {
+                yield return new (original.Left, top, subtract.Left - original.Left, bottom - top);
+            }
+        }
+
+        // Right segment
+        if (original.Right > subtract.Right)
+        {
+            int top = Math.Max (original.Top, subtract.Top);
+            int bottom = Math.Min (original.Bottom, subtract.Bottom);
+
+            if (bottom > top)
+            {
+                yield return new (subtract.Right, top, original.Right - subtract.Right, bottom - top);
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Releases all resources used by the <see cref="Region"/>.
+    /// </summary>
+    public void Dispose () { _rectangles.Clear (); }
+}

+ 5 - 4
Terminal.Gui/Drawing/Ruler.cs

@@ -1,10 +1,11 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 /// <summary>Draws a ruler on the screen.</summary>
 /// <remarks>
 ///     <para></para>
 /// </remarks>
-public class Ruler
+internal class Ruler
 {
     /// <summary>Gets or sets the foreground and background color to use.</summary>
     public Attribute Attribute { get; set; } = new ();
@@ -36,7 +37,7 @@ public class Ruler
         if (Orientation == Orientation.Horizontal)
         {
             string hrule =
-                _hTemplate.Repeat ((int)Math.Ceiling (Length + 2 / (double)_hTemplate.Length)) [start..(Length + start)];
+                _hTemplate.Repeat ((int)Math.Ceiling (Length + 2 / (double)_hTemplate.Length))! [start..(Length + start)];
 
             // Top
             Application.Driver?.Move (location.X, location.Y);
@@ -45,7 +46,7 @@ public class Ruler
         else
         {
             string vrule =
-                _vTemplate.Repeat ((int)Math.Ceiling ((Length + 2) / (double)_vTemplate.Length))
+                _vTemplate.Repeat ((int)Math.Ceiling ((Length + 2) / (double)_vTemplate.Length))!
                     [start..(Length + start)];
 
             for (int r = location.Y; r < location.Y + Length; r++)

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

@@ -6,11 +6,11 @@
 public class StraightLine
 {
     /// <summary>Creates a new instance of the <see cref="StraightLine"/> class.</summary>
-    /// <param name="start"></param>
-    /// <param name="length"></param>
-    /// <param name="orientation"></param>
-    /// <param name="style"></param>
-    /// <param name="attribute"></param>
+    /// <param name="start">The start location.</param>
+    /// <param name="length">The length of the line.</param>
+    /// <param name="orientation">The orientation of the line.</param>
+    /// <param name="style">The line style.</param>
+    /// <param name="attribute">The attribute to be used for rendering the line.</param>
     public StraightLine (
         Point start,
         int length,
@@ -43,11 +43,11 @@ public class StraightLine
 
     /// <summary>
     ///     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.
+    ///     the 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 Viewport.
-    internal Rectangle Viewport
+    // PERF: Probably better to store the rectangle rather than make a new one on every single access to Bounds.
+    internal Rectangle Bounds
     {
         get
         {

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

@@ -1,4 +1,5 @@
-using System.Numerics;
+#nullable enable
+using System.Numerics;
 using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
@@ -15,10 +16,7 @@ namespace Terminal.Gui;
 ///         with the thickness widths subtracted.
 ///     </para>
 ///     <para>
-///         Use the helper API (<see cref="Draw(Rectangle, string)"/> to draw the frame with the specified thickness.
-///     </para>
-///     <para>
-///         Thickness uses <see langword="float"/> intenrally. As a result, there is a potential precision loss for very
+///         Thickness uses <see langword="float"/> internally. As a result, there is a potential precision loss for very
 ///         large numbers. This is typically not an issue for UI dimensions but could be relevant in other contexts.
 ///     </para>
 /// </remarks>
@@ -82,15 +80,16 @@ public record struct Thickness
     /// <summary>Draws the <see cref="Thickness"/> rectangle with an optional diagnostics label.</summary>
     /// <remarks>
     ///     If <see cref="ViewDiagnosticFlags"/> is set to
-    ///     <see cref="ViewDiagnosticFlags.Padding"/> then 'T', 'L', 'R', and 'B' glyphs will be used instead of
+    ///     <see cref="ViewDiagnosticFlags.Thickness"/> then 'T', 'L', 'R', and 'B' glyphs will be used instead of
     ///     space. If <see cref="ViewDiagnosticFlags"/> is set to
     ///     <see cref="ViewDiagnosticFlags.Ruler"/> then a ruler will be drawn on the outer edge of the
     ///     Thickness.
     /// </remarks>
     /// <param name="rect">The location and size of the rectangle that bounds the thickness rectangle, in screen coordinates.</param>
+    /// <param name="diagnosticFlags"></param>
     /// <param name="label">The diagnostics label to draw on the bottom of the <see cref="Bottom"/>.</param>
     /// <returns>The inner rectangle remaining to be drawn.</returns>
-    public Rectangle Draw (Rectangle rect, string label = null)
+    public Rectangle Draw (Rectangle rect, ViewDiagnosticFlags diagnosticFlags = ViewDiagnosticFlags.Off, string? label = null)
     {
         if (rect.Size.Width < 1 || rect.Size.Height < 1)
         {
@@ -103,7 +102,7 @@ public record struct Thickness
         Rune topChar = clearChar;
         Rune bottomChar = clearChar;
 
-        if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.Padding))
+        if (diagnosticFlags.HasFlag (ViewDiagnosticFlags.Thickness))
         {
             leftChar = (Rune)'L';
             rightChar = (Rune)'R';
@@ -133,29 +132,29 @@ public record struct Thickness
         if (Right > 0)
         {
             Application.Driver?.FillRect (
-                                         rect with
-                                         {
-                                             X = Math.Max (0, rect.X + rect.Width - Right),
-                                             Width = Math.Min (rect.Width, Right)
-                                         },
-                                         rightChar
-                                        );
+                                          rect with
+                                          {
+                                              X = Math.Max (0, rect.X + rect.Width - Right),
+                                              Width = Math.Min (rect.Width, Right)
+                                          },
+                                          rightChar
+                                         );
         }
 
         // Draw the Bottom side
         if (Bottom > 0)
         {
             Application.Driver?.FillRect (
-                                         rect with
-                                         {
-                                             Y = rect.Y + Math.Max (0, rect.Height - Bottom),
-                                             Height = Bottom
-                                         },
-                                         bottomChar
-                                        );
+                                          rect with
+                                          {
+                                              Y = rect.Y + Math.Max (0, rect.Height - Bottom),
+                                              Height = Bottom
+                                          },
+                                          bottomChar
+                                         );
         }
 
-        if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.Ruler))
+        if (diagnosticFlags.HasFlag (ViewDiagnosticFlags.Ruler))
         {
             // PERF: This can almost certainly be simplified down to a single point offset and fewer calls to Draw
             // Top
@@ -187,10 +186,11 @@ public record struct Thickness
             }
         }
 
-        if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.Padding))
+        if (diagnosticFlags.HasFlag (ViewDiagnosticFlags.Thickness))
         {
             // Draw the diagnostics label on the bottom
             string text = label is null ? string.Empty : $"{label} {this}";
+
             var tf = new TextFormatter
             {
                 Text = text,

+ 5 - 7
Terminal.Gui/Terminal.Gui.csproj

@@ -109,6 +109,9 @@
       <LastGenOutput>Strings.Designer.cs</LastGenOutput>
     </EmbeddedResource>
   </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Views\Scroll\" />
+  </ItemGroup>
   <!-- =================================================================== -->
   <!-- Nuget  -->
   <!-- =================================================================== -->
@@ -143,9 +146,7 @@
   </PropertyGroup>
   <ProjectExtensions><VisualStudio><UserProperties resources_4config_1json__JsonSchema="../../docfx/schemas/tui-config-schema.json" /></VisualStudio></ProjectExtensions>
 
-  <Target Name="CopyNuGetPackagesToLocalPackagesFolder"
-          AfterTargets="Pack"
-          Condition="'$(Configuration)' == 'Release'">
+  <Target Name="CopyNuGetPackagesToLocalPackagesFolder" AfterTargets="Pack" Condition="'$(Configuration)' == 'Release'">
       <PropertyGroup>
           <!-- Define the path for local_packages relative to the project directory -->
           <LocalPackagesPath>$(MSBuildThisFileDirectory)..\local_packages\</LocalPackagesPath>
@@ -166,10 +167,7 @@
       <Message Text="Found packages: @(NuGetPackages)" Importance="high" />
 
       <!-- Copy files only if found -->
-      <Copy SourceFiles="@(NuGetPackages)"
-            DestinationFolder="$(LocalPackagesPath)"
-            SkipUnchangedFiles="false"
-            Condition="@(NuGetPackages) != ''" />
+      <Copy SourceFiles="@(NuGetPackages)" DestinationFolder="$(LocalPackagesPath)" SkipUnchangedFiles="false" Condition="@(NuGetPackages) != ''" />
 
       <!-- Log success -->
       <Message Text="Copy completed successfully." Importance="high" />

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

@@ -42,7 +42,7 @@ public class AppendAutocomplete : AutocompleteBase
     public override void ClearSuggestions ()
     {
         base.ClearSuggestions ();
-        textField.SetNeedsDisplay ();
+        textField.SetNeedsDraw ();
     }
 
     /// <inheritdoc/>
@@ -106,12 +106,12 @@ public class AppendAutocomplete : AutocompleteBase
         }
 
         // draw it like it's selected, even though it's not
-        Application.Driver?.SetAttribute (
-                                         new Attribute (
-                                                        ColorScheme.Normal.Foreground,
-                                                        textField.ColorScheme.Focus.Background
-                                                       )
-                                        );
+        textField.SetAttribute (
+                               new Attribute (
+                                              ColorScheme.Normal.Foreground,
+                                              textField.ColorScheme.Focus.Background
+                                             )
+                              );
         textField.Move (textField.Text.Length, 0);
 
         Suggestion suggestion = Suggestions.ElementAt (SelectedIdx);
@@ -183,7 +183,7 @@ public class AppendAutocomplete : AutocompleteBase
             SelectedIdx = Suggestions.Count () - 1;
         }
 
-        textField.SetNeedsDisplay ();
+        textField.SetNeedsDraw ();
 
         return true;
     }

+ 4 - 2
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.PopUp.cs

@@ -15,14 +15,16 @@ public abstract partial class PopupAutocomplete
 
         private readonly PopupAutocomplete _autoComplete;
 
-        public override void OnDrawContent (Rectangle viewport)
+        protected override bool OnDrawingContent ()
         {
             if (!_autoComplete.LastPopupPos.HasValue)
             {
-                return;
+                return true;
             }
 
             _autoComplete.RenderOverlay (_autoComplete.LastPopupPos.Value);
+
+            return true;
         }
 
         protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { return _autoComplete.OnMouseEvent (mouseEvent); }

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

@@ -120,7 +120,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
             if (Visible && Suggestions.Count == 0)
             {
                 Visible = false;
-                HostControl?.SetNeedsDisplay ();
+                HostControl?.SetNeedsDraw ();
 
                 return true;
             }
@@ -128,7 +128,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
             if (!Visible && Suggestions.Count > 0)
             {
                 Visible = true;
-                HostControl?.SetNeedsDisplay ();
+                HostControl?.SetNeedsDraw ();
                 Application.UngrabMouse ();
 
                 return false;
@@ -141,7 +141,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
                 _closed = false;
             }
 
-            HostControl?.SetNeedsDisplay ();
+            HostControl?.SetNeedsDraw ();
 
             return false;
         }
@@ -395,11 +395,11 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         {
             if (i == SelectedIdx - ScrollOffset)
             {
-                Application.Driver?.SetAttribute (ColorScheme.Focus);
+                _popup.SetAttribute (ColorScheme.Focus);
             }
             else
             {
-                Application.Driver?.SetAttribute (ColorScheme.Normal);
+                _popup.SetAttribute (ColorScheme.Normal);
             }
 
             _popup.Move (0, i);
@@ -424,7 +424,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         ClearSuggestions ();
         Visible = false;
         _closed = true;
-        HostControl?.SetNeedsDisplay ();
+        HostControl?.SetNeedsDraw ();
         //RemovePopupFromTop ();
     }
 
@@ -469,7 +469,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         }
 
         EnsureSelectedIdxIsValid ();
-        HostControl?.SetNeedsDisplay ();
+        HostControl?.SetNeedsDraw ();
     }
 
     /// <summary>Moves the selection in the Autocomplete context menu up one</summary>
@@ -483,7 +483,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         }
 
         EnsureSelectedIdxIsValid ();
-        HostControl?.SetNeedsDisplay ();
+        HostControl?.SetNeedsDraw ();
     }
 
     /// <summary>Render the current selection in the Autocomplete context menu by the mouse reporting.</summary>
@@ -512,7 +512,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         {
             Visible = true;
             _closed = false;
-            HostControl?.SetNeedsDisplay ();
+            HostControl?.SetNeedsDraw ();
 
             return true;
         }

+ 4 - 1
Terminal.Gui/Text/TextFormatter.cs

@@ -68,7 +68,10 @@ public class TextFormatter
             return;
         }
 
-        driver ??= Application.Driver;
+        if (driver is null)
+        {
+            driver = Application.Driver;
+        }
 
         driver?.SetAttribute (normalColor);
 

+ 92 - 107
Terminal.Gui/View/Adornment/Adornment.cs

@@ -1,5 +1,4 @@
 #nullable enable
-using System.ComponentModel;
 using Terminal.Gui;
 using Attribute = Terminal.Gui.Attribute;
 
@@ -15,7 +14,7 @@ using Attribute = Terminal.Gui.Attribute;
 ///         mouse input. Each can be customized by manipulating their Subviews.
 ///     </para>
 /// </remarsk>
-public class Adornment : View
+public class Adornment : View, IDesignable
 {
     /// <inheritdoc/>
     public Adornment ()
@@ -27,7 +26,7 @@ public class Adornment : View
     /// <param name="parent"></param>
     public Adornment (View parent)
     {
-        // By default Adornments can't get focus; has to be enabled specifically.
+        // By default, Adornments can't get focus; has to be enabled specifically.
         CanFocus = false;
         TabStop = TabBehavior.NoStop;
         Parent = parent;
@@ -42,6 +41,15 @@ public class Adornment : View
 
     #region Thickness
 
+    /// <summary>
+    ///     Gets or sets whether the Adornment will draw diagnostic information. This is a bit-field of
+    ///     <see cref="ViewDiagnosticFlags"/>.
+    /// </summary>
+    /// <remarks>
+    ///     The <see cref="View.Diagnostics"/> static property is used as the default value for this property.
+    /// </remarks>
+    public new ViewDiagnosticFlags Diagnostics { get; set; } = View.Diagnostics;
+
     private Thickness _thickness = Thickness.Empty;
 
     /// <summary>Defines the rectangle that the <see cref="Adornment"/> will use to draw its content.</summary>
@@ -51,20 +59,14 @@ public class Adornment : View
         set
         {
             Thickness current = _thickness;
+
             _thickness = value;
 
             if (current != _thickness)
             {
-                if (Parent?.IsInitialized == false)
-                {
-                    // When initialized Parent.LayoutSubViews will cause a LayoutAdornments
-                    Parent?.LayoutAdornments ();
-                }
-                else
-                {
-                    Parent?.SetNeedsLayout ();
-                    Parent?.LayoutSubviews ();
-                }
+                Parent?.SetAdornmentFrames ();
+                SetNeedsLayout ();
+                SetNeedsDraw ();
 
                 OnThicknessChanged ();
             }
@@ -72,14 +74,10 @@ public class Adornment : View
     }
 
     /// <summary>Fired whenever the <see cref="Thickness"/> property changes.</summary>
-    [CanBeNull]
     public event EventHandler? ThicknessChanged;
 
     /// <summary>Called whenever the <see cref="Thickness"/> property changes.</summary>
-    public void OnThicknessChanged ()
-    {
-        ThicknessChanged?.Invoke (this, EventArgs.Empty);
-    }
+    public void OnThicknessChanged () { ThicknessChanged?.Invoke (this, EventArgs.Empty); }
 
     #endregion Thickness
 
@@ -89,23 +87,16 @@ public class Adornment : View
     ///     Adornments cannot be used as sub-views (see <see cref="Parent"/>); setting this property will throw
     ///     <see cref="InvalidOperationException"/>.
     /// </summary>
+    /// <remarks>
+    ///     While there are no real use cases for an Adornment being a subview, it is not explicitly dis-allowed to support
+    ///     testing. E.g. in AllViewsTester.
+    /// </remarks>
     public override View? SuperView
     {
-        get => null!;
+        get => base.SuperView!;
         set => throw new InvalidOperationException (@"Adornments can not be Subviews or have SuperViews. Use Parent instead.");
     }
 
-    //internal override Adornment CreateAdornment (Type adornmentType)
-    //{
-    //    /* Do nothing - Adornments do not have Adornments */
-    //    return null;
-    //}
-
-    internal override void LayoutAdornments ()
-    {
-        /* Do nothing - Adornments do not have Adornments */
-    }
-
     /// <summary>
     ///     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"/>.
@@ -116,7 +107,7 @@ public class Adornment : View
     /// </remarks>
     public override Rectangle Viewport
     {
-        get => Frame with { Location = Point.Empty };
+        get => base.Viewport;
         set => throw new InvalidOperationException (@"The Viewport of an Adornment cannot be modified.");
     }
 
@@ -125,6 +116,15 @@ public class Adornment : View
     {
         if (Parent is null)
         {
+            // While there are no real use cases for an Adornment being a subview, we support it for
+            // testing. E.g. in AllViewsTester.
+            if (SuperView is { })
+            {
+                Point super = SuperView.ViewportToScreen (Frame.Location);
+
+                return new (super, Frame.Size);
+            }
+
             return Frame;
         }
 
@@ -140,58 +140,51 @@ public class Adornment : View
     /// <inheritdoc/>
     public override Point ScreenToFrame (in Point location)
     {
-        return Parent!.ScreenToFrame (new (location.X - Frame.X, location.Y - Frame.Y));
-    }
-
-    /// <summary>Does nothing for Adornment</summary>
-    /// <returns></returns>
-    public override bool OnDrawAdornments () { return false; }
+        View? parentOrSuperView = Parent;
 
-    /// <summary>Redraws the Adornments that comprise the <see cref="Adornment"/>.</summary>
-    public override void OnDrawContent (Rectangle viewport)
-    {
-        if (Thickness == Thickness.Empty)
+        if (parentOrSuperView is null)
         {
-            return;
-        }
-
-        Rectangle prevClip = SetClip ();
-
-        Rectangle screen = ViewportToScreen (viewport);
-        Attribute normalAttr = GetNormalColor ();
-        Driver.SetAttribute (normalAttr);
-
-        // This just draws/clears the thickness, not the insides.
-        Thickness.Draw (screen, ToString ());
+            // While there are no real use cases for an Adornment being a subview, we support it for
+            // testing. E.g. in AllViewsTester.
+            parentOrSuperView = SuperView;
 
-        if (!string.IsNullOrEmpty (TextFormatter.Text))
-        {
-            if (TextFormatter is { })
+            if (parentOrSuperView is null)
             {
-                TextFormatter.ConstrainToSize = Frame.Size;
-                TextFormatter.NeedsFormat = true;
+                return Point.Empty;
             }
         }
 
-        TextFormatter?.Draw (screen, normalAttr, normalAttr, Rectangle.Empty);
+        return parentOrSuperView.ScreenToFrame (new (location.X - Frame.X, location.Y - Frame.Y));
+    }
 
-        if (Subviews.Count > 0)
+    /// <summary>
+    ///     Called when the <see cref="Thickness"/> of the Adornment is to be cleared.
+    /// </summary>
+    /// <returns><see langword="true"/> to stop further clearing.</returns>
+    protected override bool OnClearingViewport ()
+    {
+        if (Thickness == Thickness.Empty)
         {
-            base.OnDrawContent (viewport);
+            return true;
         }
 
-        if (Driver is { })
-        {
-           Driver.Clip = prevClip;
-        }
+        // This just draws/clears the thickness, not the insides.
+        Thickness.Draw (ViewportToScreen (Viewport), Diagnostics, ToString ());
+
+        NeedsDraw = true;
 
-        ClearLayoutNeeded ();
-        ClearNeedsDisplay ();
+        return true;
     }
 
+    /// <inheritdoc/>
+    protected override bool OnDrawingText () { return Thickness == Thickness.Empty; }
+
+    /// <inheritdoc/>
+    protected override bool OnDrawingSubviews () { return Thickness == Thickness.Empty; }
+
     /// <summary>Does nothing for Adornment</summary>
     /// <returns></returns>
-    public override bool OnRenderLineCanvas () { return false; }
+    protected override bool OnRenderingLineCanvas () { return true; }
 
     /// <summary>
     ///     Adornments only render to their <see cref="Parent"/>'s or Parent's SuperView's LineCanvas, so setting this
@@ -199,64 +192,56 @@ public class Adornment : View
     /// </summary>
     public override bool SuperViewRendersLineCanvas
     {
-        get => false; 
+        get => false;
         set => throw new InvalidOperationException (@"Adornment can only render to their Parent or Parent's Superview.");
     }
 
-    #endregion View Overrides
-
-    #region Mouse Support
-
+    /// <inheritdoc/>
+    protected override void OnDrawComplete () { }
 
     /// <summary>
-    /// Indicates whether the specified Parent's SuperView-relative coordinates are within the Adornment's Thickness.
+    ///     Indicates whether the specified Parent's SuperView-relative coordinates are within the Adornment's Thickness.
     /// </summary>
     /// <remarks>
     ///     The <paramref name="location"/> is relative to the PARENT's SuperView.
     /// </remarks>
     /// <param name="location"></param>
-    /// <returns><see langword="true"/> if the specified Parent's SuperView-relative coordinates are within the Adornment's Thickness. </returns>
+    /// <returns>
+    ///     <see langword="true"/> if the specified Parent's SuperView-relative coordinates are within the Adornment's
+    ///     Thickness.
+    /// </returns>
     public override bool Contains (in Point location)
     {
-        if (Parent is null)
+        View? parentOrSuperView = Parent;
+
+        if (parentOrSuperView is null)
         {
-            return false;
+            // While there are no real use cases for an Adornment being a subview, we support it for
+            // testing. E.g. in AllViewsTester.
+            parentOrSuperView = SuperView;
+
+            if (parentOrSuperView is null)
+            {
+                return false;
+            }
         }
 
         Rectangle outside = Frame;
-        outside.Offset (Parent.Frame.Location);
+        outside.Offset (parentOrSuperView.Frame.Location);
 
         return Thickness.Contains (outside, location);
     }
 
-    ///// <inheritdoc/>
-    //protected override bool OnMouseEnter (CancelEventArgs mouseEvent)
-    //{
-    //    // Invert Normal
-    //    if (Diagnostics.HasFlag (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
-    //    {
-    //        var cs = new ColorScheme (ColorScheme)
-    //        {
-    //            Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
-    //        };
-    //        ColorScheme = cs;
-    //    }
-
-    //    return false;
-    //}
-
-    ///// <inheritdoc/>   
-    //protected override void OnMouseLeave ()
-    //{
-    //    // Invert Normal
-    //    if (Diagnostics.FastHasFlags (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
-    //    {
-    //        var cs = new ColorScheme (ColorScheme)
-    //        {
-    //            Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
-    //        };
-    //        ColorScheme = cs;
-    //    }
-    //}
-    #endregion Mouse Support
+    #endregion View Overrides
+
+    /// <inheritdoc/>
+    bool IDesignable.EnableForDesign ()
+    {
+        // This enables AllViewsTester to show something useful.
+        Thickness = new (3);
+        Frame = new (0, 0, 10, 10);
+        Diagnostics = ViewDiagnosticFlags.Thickness;
+
+        return true;
+    }
 }

+ 100 - 64
Terminal.Gui/View/Adornment/Border.cs

@@ -64,6 +64,51 @@ public class Border : Adornment
 
         HighlightStyle |= HighlightStyle.Pressed;
         Highlight += Border_Highlight;
+
+        ThicknessChanged += OnThicknessChanged;
+    }
+
+    // TODO: Move DrawIndicator out of Border and into View
+
+    private void OnThicknessChanged (object? sender, EventArgs e)
+    {
+        if (IsInitialized)
+        {
+            ShowHideDrawIndicator ();
+        }
+    }
+    private void ShowHideDrawIndicator ()
+    {
+        if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.DrawIndicator) && Thickness != Thickness.Empty)
+        {
+            if (DrawIndicator is null)
+            {
+                DrawIndicator = new SpinnerView ()
+                {
+                    Id = "DrawIndicator",
+                    X = 1,
+                    Style = new SpinnerStyle.Dots2 (),
+                    SpinDelay = 0,
+                    Visible = false
+                };
+                Add (DrawIndicator);
+            }
+        }
+        else if (DrawIndicator is { })
+        {
+            Remove (DrawIndicator);
+            DrawIndicator!.Dispose ();
+            DrawIndicator = null;
+        }
+    }
+
+    internal void AdvanceDrawIndicator ()
+    {
+        if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.DrawIndicator) && DrawIndicator is { })
+        {
+            DrawIndicator.AdvanceAnimation (false);
+            DrawIndicator.Render ();
+        }
     }
 
 #if SUBVIEW_BASED_BORDER
@@ -80,6 +125,7 @@ public class Border : Adornment
     {
         base.BeginInit ();
 
+        ShowHideDrawIndicator ();
 #if SUBVIEW_BASED_BORDER
         if (Parent is { })
         {
@@ -130,19 +176,11 @@ public class Border : Adornment
     /// </summary>
     public override ColorScheme? ColorScheme
     {
-        get
-        {
-            if (base.ColorScheme is { })
-            {
-                return base.ColorScheme;
-            }
-
-            return Parent?.ColorScheme;
-        }
+        get => base.ColorScheme ?? Parent?.ColorScheme;
         set
         {
             base.ColorScheme = value;
-            Parent?.SetNeedsDisplay ();
+            Parent?.SetNeedsDraw ();
         }
     }
 
@@ -191,7 +229,7 @@ public class Border : Adornment
             // TODO: Make Border.LineStyle inherit from the SuperView hierarchy
             // TODO: Right now, Window and FrameView use CM to set BorderStyle, which negates
             // TODO: all this.
-            return Parent!.SuperView?.BorderStyle ?? LineStyle.None;
+            return Parent?.SuperView?.BorderStyle ?? LineStyle.None;
         }
         set => _lineStyle = value;
     }
@@ -213,7 +251,7 @@ public class Border : Adornment
 
             _settings = value;
 
-            Parent?.SetNeedsDisplay ();
+            Parent?.SetNeedsDraw ();
         }
     }
 
@@ -254,7 +292,7 @@ public class Border : Adornment
             ColorScheme = cs;
         }
 
-        Parent?.SetNeedsDisplay ();
+        Parent?.SetNeedsDraw ();
         e.Cancel = true;
     }
 
@@ -266,9 +304,9 @@ public class Border : Adornment
     {
         // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/3312
         if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
-                                    // HACK: Prevents Window from being draggable if it's Top
-                                    //&& Parent is Toplevel { Modal: true }
-                                    )
+                                // HACK: Prevents Window from being draggable if it's Top
+                                //&& Parent is Toplevel { Modal: true }
+                                )
         {
             Parent!.SetFocus ();
 
@@ -437,11 +475,11 @@ public class Border : Adornment
                 if (Parent!.SuperView is null)
                 {
                     // Redraw the entire app window.
-                    Application.Top!.SetNeedsDisplay ();
+                    Application.Top!.SetNeedsDraw ();
                 }
                 else
                 {
-                    Parent.SuperView.SetNeedsDisplay ();
+                    Parent.SuperView.SetNeedsDraw ();
                 }
 
                 _dragPosition = mouseEvent.Position;
@@ -449,8 +487,8 @@ public class Border : Adornment
                 Point parentLoc = Parent.SuperView?.ScreenToViewport (new (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y))
                                   ?? mouseEvent.ScreenPosition;
 
-                int minHeight = Thickness.Vertical + Parent!.Margin.Thickness.Bottom;
-                int minWidth = Thickness.Horizontal + Parent!.Margin.Thickness.Right;
+                int minHeight = Thickness.Vertical + Parent!.Margin!.Thickness.Bottom;
+                int minWidth = Thickness.Horizontal + Parent!.Margin!.Thickness.Right;
 
                 // TODO: This code can be refactored to be more readable and maintainable.
                 switch (_arranging)
@@ -565,7 +603,6 @@ public class Border : Adornment
 
                         break;
                 }
-                Application.Refresh ();
 
                 return true;
             }
@@ -604,19 +641,14 @@ public class Border : Adornment
     #endregion Mouse Support
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        base.OnDrawContent (viewport);
-
         if (Thickness == Thickness.Empty)
         {
-            return;
+            return true;
         }
 
-        //Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal);
-        Rectangle screenBounds = ViewportToScreen (viewport);
-
-        //OnDrawSubviews (bounds); 
+        Rectangle screenBounds = ViewportToScreen (Viewport);
 
         // TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title)
 
@@ -633,12 +665,15 @@ public class Border : Adornment
         int maxTitleWidth = Math.Max (
                                       0,
                                       Math.Min (
-                                                Parent!.TitleTextFormatter.FormatAndGetSize ().Width,
+                                                Parent?.TitleTextFormatter.FormatAndGetSize ().Width ?? 0,
                                                 Math.Min (screenBounds.Width - 4, borderBounds.Width - 4)
                                                )
                                      );
 
-        Parent.TitleTextFormatter.ConstrainToSize = new (maxTitleWidth, 1);
+        if (Parent is { })
+        {
+            Parent.TitleTextFormatter.ConstrainToSize = new (maxTitleWidth, 1);
+        }
 
         int sideLineLength = borderBounds.Height;
         bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 };
@@ -677,20 +712,22 @@ public class Border : Adornment
             }
         }
 
-        if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title) && !string.IsNullOrEmpty (Parent?.Title))
+        if (Parent is { } && canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title) && !string.IsNullOrEmpty (Parent?.Title))
         {
             Attribute 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 ();
+                focus = GetFocusColor ();
             }
 
-            Parent.TitleTextFormatter.Draw (
-                                            new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
-                                            Parent.HasFocus ? focus : Parent.GetNormalColor (),
-                                            Parent.HasFocus ? focus : Parent.GetHotNormalColor ());
+            Rectangle titleRect = new (borderBounds.X + 2, titleY, maxTitleWidth, 1);
+            Parent.TitleTextFormatter.Draw (titleRect
+                                           ,
+                                            Parent.HasFocus ? focus : GetNormalColor (),
+                                            Parent.HasFocus ? focus : GetHotNormalColor ());
+            Parent?.LineCanvas.Exclude(new(titleRect));
         }
 
         if (canDrawBorder && LineStyle != LineStyle.None)
@@ -702,15 +739,15 @@ public class Border : Adornment
             bool drawBottom = Thickness.Bottom > 0 && Frame.Width > 1 && Frame.Height > 1;
             bool drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0);
 
-            Attribute prevAttr = Driver.GetAttribute ();
+            Attribute prevAttr = Driver?.GetAttribute () ?? Attribute.Default;
 
             if (ColorScheme is { })
             {
-                Driver.SetAttribute (GetNormalColor ());
+                SetAttribute (GetNormalColor ());
             }
             else
             {
-                Driver.SetAttribute (Parent!.GetNormalColor ());
+                SetAttribute (Parent!.GetNormalColor ());
             }
 
             if (drawTop)
@@ -725,7 +762,7 @@ public class Border : Adornment
                                  borderBounds.Width,
                                  Orientation.Horizontal,
                                  lineStyle,
-                                 Driver.GetAttribute ()
+                                 Driver?.GetAttribute ()
                                 );
                 }
                 else
@@ -740,7 +777,7 @@ public class Border : Adornment
                                      Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
                                      Orientation.Horizontal,
                                      lineStyle,
-                                     Driver.GetAttribute ()
+                                     Driver?.GetAttribute ()
                                     );
                     }
 
@@ -754,7 +791,7 @@ public class Border : Adornment
                                      Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
                                      Orientation.Horizontal,
                                      lineStyle,
-                                     Driver.GetAttribute ()
+                                     Driver?.GetAttribute ()
                                     );
 
                         lc?.AddLine (
@@ -762,7 +799,7 @@ public class Border : Adornment
                                      Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
                                      Orientation.Horizontal,
                                      lineStyle,
-                                     Driver.GetAttribute ()
+                                     Driver?.GetAttribute ()
                                     );
                     }
 
@@ -773,7 +810,7 @@ public class Border : Adornment
                                  2,
                                  Orientation.Horizontal,
                                  lineStyle,
-                                 Driver.GetAttribute ()
+                                 Driver?.GetAttribute ()
                                 );
 
                     // Add a vert line for ╔╡
@@ -782,7 +819,7 @@ public class Border : Adornment
                                  titleBarsLength,
                                  Orientation.Vertical,
                                  LineStyle.Single,
-                                 Driver.GetAttribute ()
+                                 Driver?.GetAttribute ()
                                 );
 
                     // Add a vert line for ╞
@@ -797,7 +834,7 @@ public class Border : Adornment
                                  titleBarsLength,
                                  Orientation.Vertical,
                                  LineStyle.Single,
-                                 Driver.GetAttribute ()
+                                 Driver?.GetAttribute ()
                                 );
 
                     // Add the right hand line for ╞═════╗
@@ -812,7 +849,7 @@ public class Border : Adornment
                                  borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
                                  Orientation.Horizontal,
                                  lineStyle,
-                                 Driver.GetAttribute ()
+                                 Driver?.GetAttribute ()
                                 );
                 }
             }
@@ -826,7 +863,7 @@ public class Border : Adornment
                              sideLineLength,
                              Orientation.Vertical,
                              lineStyle,
-                             Driver.GetAttribute ()
+                             Driver?.GetAttribute ()
                             );
             }
 #endif
@@ -838,7 +875,7 @@ public class Border : Adornment
                              borderBounds.Width,
                              Orientation.Horizontal,
                              lineStyle,
-                             Driver.GetAttribute ()
+                             Driver?.GetAttribute ()
                             );
             }
 
@@ -849,11 +886,11 @@ public class Border : Adornment
                              sideLineLength,
                              Orientation.Vertical,
                              lineStyle,
-                             Driver.GetAttribute ()
+                             Driver?.GetAttribute ()
                             );
             }
 
-            Driver.SetAttribute (prevAttr);
+            SetAttribute (prevAttr);
 
             // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
             if (Diagnostics.HasFlag (ViewDiagnosticFlags.Ruler))
@@ -906,8 +943,15 @@ public class Border : Adornment
                 lc!.Fill = null;
             }
         }
+
+        return true; ;
     }
 
+    /// <summary>
+    ///     Gets the subview used to render <see cref="ViewDiagnosticFlags.DrawIndicator"/>.
+    /// </summary>
+    public SpinnerView? DrawIndicator { get; private set; } = null;
+
     private void SetupGradientLineCanvas (LineCanvas lc, Rectangle rect)
     {
         GetAppealingGradientColors (out List<Color> stops, out List<int> steps);
@@ -1034,7 +1078,7 @@ public class Border : Adornment
                 NoPadding = true,
                 ShadowStyle = ShadowStyle.None,
                 Text = $"{Glyphs.SizeVertical}",
-                X = Pos.Center () + Parent!.Margin.Thickness.Horizontal,
+                X = Pos.Center () + Parent!.Margin!.Thickness.Horizontal,
                 Y = 0,
                 Visible = false,
                 Data = ViewArrangement.TopResizable
@@ -1057,7 +1101,7 @@ public class Border : Adornment
                 ShadowStyle = ShadowStyle.None,
                 Text = $"{Glyphs.SizeHorizontal}",
                 X = Pos.AnchorEnd (),
-                Y = Pos.Center () + Parent!.Margin.Thickness.Vertical / 2,
+                Y = Pos.Center () + Parent!.Margin!.Thickness.Vertical / 2,
                 Visible = false,
                 Data = ViewArrangement.RightResizable
             };
@@ -1079,7 +1123,7 @@ public class Border : Adornment
                 ShadowStyle = ShadowStyle.None,
                 Text = $"{Glyphs.SizeHorizontal}",
                 X = 0,
-                Y = Pos.Center () + Parent!.Margin.Thickness.Vertical / 2,
+                Y = Pos.Center () + Parent!.Margin!.Thickness.Vertical / 2,
                 Visible = false,
                 Data = ViewArrangement.LeftResizable
             };
@@ -1100,7 +1144,7 @@ public class Border : Adornment
                 NoPadding = true,
                 ShadowStyle = ShadowStyle.None,
                 Text = $"{Glyphs.SizeVertical}",
-                X = Pos.Center () + Parent!.Margin.Thickness.Horizontal / 2,
+                X = Pos.Center () + Parent!.Margin!.Thickness.Horizontal / 2,
                 Y = Pos.AnchorEnd (),
                 Visible = false,
                 Data = ViewArrangement.BottomResizable
@@ -1249,8 +1293,6 @@ public class Border : Adornment
                             }
                         }
 
-                        Application.Refresh ();
-
                         return true;
                     });
 
@@ -1273,8 +1315,6 @@ public class Border : Adornment
                             Parent!.Height = Parent.Height! + 1;
                         }
 
-                        Application.Refresh ();
-
                         return true;
                     });
 
@@ -1300,8 +1340,6 @@ public class Border : Adornment
                             }
                         }
 
-                        Application.Refresh ();
-
                         return true;
                     });
 
@@ -1324,8 +1362,6 @@ public class Border : Adornment
                             Parent!.Width = Parent.Width! + 1;
                         }
 
-                        Application.Refresh ();
-
                         return true;
                     });
 

+ 168 - 109
Terminal.Gui/View/Adornment/Margin.cs

@@ -4,10 +4,21 @@ namespace Terminal.Gui;
 
 /// <summary>The Margin for a <see cref="View"/>. Accessed via <see cref="View.Margin"/></summary>
 /// <remarks>
+///     <para>
+///         The Margin is transparent by default. This can be overriden by explicitly setting <see cref="ColorScheme"/>.
+///     </para>
+///     <para>
+///         Margins are drawn after all other Views in the application View hierarchy are drawn.
+///     </para>
 ///     <para>See the <see cref="Adornment"/> class.</para>
 /// </remarks>
 public class Margin : Adornment
 {
+    private const int SHADOW_WIDTH = 1;
+    private const int SHADOW_HEIGHT = 1;
+    private const int PRESS_MOVE_HORIZONTAL = 1;
+    private const int PRESS_MOVE_VERTICAL = 0;
+
     /// <inheritdoc/>
     public Margin ()
     { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */
@@ -19,18 +30,66 @@ public class Margin : Adornment
         /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */
 
         // BUGBUG: We should not set HighlightStyle.Pressed here, but wherever it is actually needed
-       // HighlightStyle |= HighlightStyle.Pressed;
+        // HighlightStyle |= HighlightStyle.Pressed;
         Highlight += Margin_Highlight;
-        LayoutStarted += Margin_LayoutStarted;
+        SubviewLayout += Margin_LayoutStarted;
 
         // Margin should not be focusable
         CanFocus = false;
     }
 
-    private bool _pressed;
+    // When the Parent is drawn, we cache the clip region so we can draw the Margin after all other Views
+    // QUESTION: Why can't this just be the NeedsDisplay region?
+    private Region? _cachedClip;
 
-    private ShadowView? _bottomShadow;
-    private ShadowView? _rightShadow;
+    internal Region? GetCachedClip () { return _cachedClip; }
+
+    internal void ClearCachedClip () { _cachedClip = null; }
+
+    internal void CacheClip ()
+    {
+        if (Thickness != Thickness.Empty)
+        {
+            // PERFORMANCE: How expensive are these clones?
+            _cachedClip = GetClip ()?.Clone ();
+        }
+    }
+
+    // PERFORMANCE: Margins are ALWAYS drawn. This may be an issue for apps that have a large number of views with shadows.
+    /// <summary>
+    ///     INTERNAL API - Draws the margins for the specified views. This is called by the <see cref="Application"/> on each
+    ///     iteration of the main loop after all Views have been drawn.
+    /// </summary>
+    /// <param name="margins"></param>
+    /// <returns><see langword="true"/></returns>
+    internal static bool DrawMargins (IEnumerable<View> margins)
+    {
+        Stack<View> stack = new (margins);
+
+        while (stack.Count > 0)
+        {
+            var view = stack.Pop ();
+
+            if (view.Margin?.GetCachedClip() != null)
+            {
+                view.Margin.NeedsDraw = true;
+                Region? saved = GetClip ();
+                View.SetClip (view.Margin.GetCachedClip ());
+                view.Margin.Draw ();
+                View.SetClip (saved);
+                view.Margin.ClearCachedClip ();
+            }
+
+            view.NeedsDraw = false;
+
+            foreach (var subview in view.Subviews)
+            {
+                stack.Push (subview);
+            }
+        }
+
+        return true;
+    }
 
     /// <inheritdoc/>
     public override void BeginInit ()
@@ -43,32 +102,10 @@ public class Margin : Adornment
         }
 
         ShadowStyle = base.ShadowStyle;
-
-        Add (
-             _rightShadow = new ()
-             {
-                 X = Pos.AnchorEnd (1),
-                 Y = 0,
-                 Width = 1,
-                 Height = Dim.Fill (),
-                 ShadowStyle = ShadowStyle,
-                 Orientation = Orientation.Vertical
-             },
-             _bottomShadow = new ()
-             {
-                 X = 0,
-                 Y = Pos.AnchorEnd (1),
-                 Width = Dim.Fill (),
-                 Height = 1,
-                 ShadowStyle = ShadowStyle,
-                 Orientation = Orientation.Horizontal
-             }
-            );
     }
 
     /// <summary>
-    ///     The color scheme for the Margin. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/>'s
-    ///     <see cref="View.SuperView"/> scheme. color scheme.
+    ///     The color scheme for the Margin. If set to <see langword="null"/> (the default), the margin will be transparent.
     /// </summary>
     public override ColorScheme? ColorScheme
     {
@@ -84,86 +121,95 @@ public class Margin : Adornment
         set
         {
             base.ColorScheme = value;
-            Parent?.SetNeedsDisplay ();
+            Parent?.SetNeedsDraw ();
         }
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnClearingViewport ()
     {
-        if (!NeedsDisplay)
+        if (Thickness == Thickness.Empty)
         {
-            return;
+            return true;
         }
 
-        Rectangle screen = ViewportToScreen (viewport);
-        Attribute normalAttr = GetNormalColor ();
+        Rectangle screen = ViewportToScreen (Viewport);
 
-        Driver?.SetAttribute (normalAttr);
-
-        if (ShadowStyle != ShadowStyle.None)
+        // This just draws/clears the thickness, not the insides.
+        if (Diagnostics.HasFlag (ViewDiagnosticFlags.Thickness) || base.ColorScheme is { })
         {
-            screen = Rectangle.Inflate (screen, -1, -1);
+            Thickness.Draw (screen, Diagnostics, ToString ());
         }
 
-        // This just draws/clears the thickness, not the insides.
-        Thickness.Draw (screen, ToString ());
-
-        if (Subviews.Count > 0)
+        if (ShadowStyle != ShadowStyle.None)
         {
-            // Draw subviews
-            // TODO: Implement OnDrawSubviews (cancelable);
-            if (Subviews is { } && SubViewNeedsDisplay)
-            {
-                IEnumerable<View> subviewsNeedingDraw = Subviews.Where (
-                                                                        view => view.Visible
-                                                                                && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
-                                                                       );
-
-                foreach (View view in subviewsNeedingDraw)
-                {
-                    if (view.LayoutNeeded)
-                    {
-                        view.LayoutSubviews ();
-                    }
-
-                    view.Draw ();
-                }
-            }
+            // Don't clear where the shadow goes
+            screen = Rectangle.Inflate (screen, -SHADOW_WIDTH, -SHADOW_HEIGHT);
         }
+
+        return true;
     }
 
+    #region Shadow
+
+    private bool _pressed;
+    private ShadowView? _bottomShadow;
+    private ShadowView? _rightShadow;
+
     /// <summary>
     ///     Sets whether the Margin includes a shadow effect. The shadow is drawn on the right and bottom sides of the
     ///     Margin.
     /// </summary>
     public ShadowStyle SetShadow (ShadowStyle style)
     {
-        if (ShadowStyle == style)
+        if (_rightShadow is { })
+        {
+            Remove (_rightShadow);
+            _rightShadow.Dispose ();
+            _rightShadow = null;
+        }
+
+        if (_bottomShadow is { })
         {
-            // return style;
+            Remove (_bottomShadow);
+            _bottomShadow.Dispose ();
+            _bottomShadow = null;
         }
 
         if (ShadowStyle != ShadowStyle.None)
         {
             // Turn off shadow
-            Thickness = new (Thickness.Left, Thickness.Top, Thickness.Right - 1, Thickness.Bottom - 1);
+            Thickness = new (Thickness.Left, Thickness.Top, Thickness.Right - SHADOW_WIDTH, Thickness.Bottom - SHADOW_HEIGHT);
         }
 
         if (style != ShadowStyle.None)
         {
             // Turn on shadow
-            Thickness = new (Thickness.Left, Thickness.Top, Thickness.Right + 1, Thickness.Bottom + 1);
+            Thickness = new (Thickness.Left, Thickness.Top, Thickness.Right + SHADOW_WIDTH, Thickness.Bottom + SHADOW_HEIGHT);
         }
 
-        if (_rightShadow is { })
-        {
-            _rightShadow.ShadowStyle = style;
-        }
-
-        if (_bottomShadow is { })
+        if (style != ShadowStyle.None)
         {
-            _bottomShadow.ShadowStyle = style;
+            _rightShadow = new ()
+            {
+                X = Pos.AnchorEnd (SHADOW_WIDTH),
+                Y = 0,
+                Width = SHADOW_WIDTH,
+                Height = Dim.Fill (),
+                ShadowStyle = style,
+                Orientation = Orientation.Vertical
+            };
+
+            _bottomShadow = new ()
+            {
+                X = 0,
+                Y = Pos.AnchorEnd (SHADOW_HEIGHT),
+                Width = Dim.Fill (),
+                Height = SHADOW_HEIGHT,
+                ShadowStyle = style,
+                Orientation = Orientation.Horizontal
+            };
+            Add (_rightShadow, _bottomShadow);
         }
 
         return style;
@@ -176,52 +222,59 @@ public class Margin : Adornment
         set => base.ShadowStyle = SetShadow (value);
     }
 
-    private const int PRESS_MOVE_HORIZONTAL = 1;
-    private const int PRESS_MOVE_VERTICAL = 0;
-
     private void Margin_Highlight (object? sender, CancelEventArgs<HighlightStyle> e)
     {
-        if (ShadowStyle != ShadowStyle.None)
+        if (Thickness == Thickness.Empty || ShadowStyle == ShadowStyle.None)
+        {
+            return;
+        }
+
+        if (_pressed && e.NewValue == HighlightStyle.None)
         {
-            if (_pressed && e.NewValue == HighlightStyle.None)
+            // If the view is pressed and the highlight is being removed, move the shadow back.
+            // Note, for visual effects reasons, we only move horizontally.
+            // TODO: Add a setting or flag that lets the view move vertically as well.
+            Thickness = new (
+                             Thickness.Left - PRESS_MOVE_HORIZONTAL,
+                             Thickness.Top - PRESS_MOVE_VERTICAL,
+                             Thickness.Right + PRESS_MOVE_HORIZONTAL,
+                             Thickness.Bottom + PRESS_MOVE_VERTICAL);
+
+            if (_rightShadow is { })
             {
-                // If the view is pressed and the highlight is being removed, move the shadow back.
-                // Note, for visual effects reasons, we only move horizontally.
-                // TODO: Add a setting or flag that lets the view move vertically as well.
-                Thickness = new (Thickness.Left - PRESS_MOVE_HORIZONTAL, Thickness.Top - PRESS_MOVE_VERTICAL, Thickness.Right + PRESS_MOVE_HORIZONTAL, Thickness.Bottom + PRESS_MOVE_VERTICAL);
+                _rightShadow.Visible = true;
+            }
 
-                if (_rightShadow is { })
-                {
-                    _rightShadow.Visible = true;
-                }
+            if (_bottomShadow is { })
+            {
+                _bottomShadow.Visible = true;
+            }
 
-                if (_bottomShadow is { })
-                {
-                    _bottomShadow.Visible = true;
-                }
+            _pressed = false;
 
-                _pressed = false;
+            return;
+        }
 
-                return;
+        if (!_pressed && e.NewValue.HasFlag (HighlightStyle.Pressed))
+        {
+            // If the view is not pressed and we want highlight move the shadow
+            // Note, for visual effects reasons, we only move horizontally.
+            // TODO: Add a setting or flag that lets the view move vertically as well.
+            Thickness = new (
+                             Thickness.Left + PRESS_MOVE_HORIZONTAL,
+                             Thickness.Top + PRESS_MOVE_VERTICAL,
+                             Thickness.Right - PRESS_MOVE_HORIZONTAL,
+                             Thickness.Bottom - PRESS_MOVE_VERTICAL);
+            _pressed = true;
+
+            if (_rightShadow is { })
+            {
+                _rightShadow.Visible = false;
             }
 
-            if (!_pressed && e.NewValue.HasFlag (HighlightStyle.Pressed))
+            if (_bottomShadow is { })
             {
-                // If the view is not pressed and we want highlight move the shadow
-                // Note, for visual effects reasons, we only move horizontally.
-                // TODO: Add a setting or flag that lets the view move vertically as well.
-                Thickness = new (Thickness.Left + PRESS_MOVE_HORIZONTAL, Thickness.Top+ PRESS_MOVE_VERTICAL, Thickness.Right - PRESS_MOVE_HORIZONTAL, Thickness.Bottom - PRESS_MOVE_VERTICAL);
-                _pressed = true;
-
-                if (_rightShadow is { })
-                {
-                    _rightShadow.Visible = false;
-                }
-
-                if (_bottomShadow is { })
-                {
-                    _bottomShadow.Visible = false;
-                }
+                _bottomShadow.Visible = false;
             }
         }
     }
@@ -235,21 +288,27 @@ public class Margin : Adornment
             {
                 case ShadowStyle.Transparent:
                     // BUGBUG: This doesn't work right for all Border.Top sizes - Need an API on Border that gives top-right location of line corner.
-                    _rightShadow.Y = Parent!.Border.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
+                    _rightShadow.Y = Parent!.Border!.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
+
                     break;
 
                 case ShadowStyle.Opaque:
                     // BUGBUG: This doesn't work right for all Border.Top sizes - Need an API on Border that gives top-right location of line corner.
-                    _rightShadow.Y = Parent!.Border.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
+                    _rightShadow.Y = Parent!.Border!.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
                     _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).X + 1 : 0;
+
                     break;
 
                 case ShadowStyle.None:
                 default:
                     _rightShadow.Y = 0;
                     _bottomShadow.X = 0;
+
                     break;
             }
         }
     }
+
+    #endregion Shadow
+
 }

+ 6 - 13
Terminal.Gui/View/Adornment/Padding.cs

@@ -1,4 +1,5 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 /// <summary>The Padding for a <see cref="View"/>. Accessed via <see cref="View.Padding"/></summary>
 /// <remarks>
@@ -21,21 +22,13 @@ public class Padding : Adornment
     ///     The color scheme for the Padding. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/>
     ///     scheme. color scheme.
     /// </summary>
-    public override ColorScheme ColorScheme
+    public override ColorScheme? ColorScheme
     {
-        get
-        {
-            if (base.ColorScheme is { })
-            {
-                return base.ColorScheme;
-            }
-
-            return Parent?.ColorScheme;
-        }
+        get => base.ColorScheme ?? Parent?.ColorScheme;
         set
         {
             base.ColorScheme = value;
-            Parent?.SetNeedsDisplay ();
+            Parent?.SetNeedsDraw ();
         }
     }
 
@@ -62,7 +55,7 @@ public class Padding : Adornment
             if (Parent.CanFocus && !Parent.HasFocus)
             {
                 Parent.SetFocus ();
-                Parent.SetNeedsDisplay ();
+                Parent.SetNeedsDraw ();
                 return mouseEvent.Handled = true;
             }
         }

+ 58 - 37
Terminal.Gui/View/Adornment/ShadowView.cs

@@ -14,42 +14,56 @@ internal class ShadowView : View
     /// <inheritdoc/>
     public override Attribute GetNormalColor ()
     {
-        if (SuperView is Adornment adornment)
+        if (SuperView is not Adornment adornment)
         {
-            var attr = Attribute.Default;
+            return base.GetNormalColor ();
+        }
 
-            if (adornment.Parent?.SuperView is { })
-            {
-                attr = adornment.Parent.SuperView.GetNormalColor ();
-            }
-            else if (Application.Top is { })
-            {
-                attr = Application.Top.GetNormalColor ();
-            }
+        var attr = Attribute.Default;
 
-            return new (
-                        new Attribute (
-                                       ShadowStyle == ShadowStyle.Opaque ? Color.Black : attr.Foreground.GetDarkerColor (),
-                                       ShadowStyle == ShadowStyle.Opaque ? attr.Background : attr.Background.GetDarkerColor ()));
+        if (adornment.Parent?.SuperView is { })
+        {
+            attr = adornment.Parent.SuperView.GetNormalColor ();
         }
+        else if (Application.Top is { })
+        {
+            attr = Application.Top.GetNormalColor ();
+        }
+
+        return new (
+                    new Attribute (
+                                   ShadowStyle == ShadowStyle.Opaque ? Color.Black : attr.Foreground.GetDarkerColor (),
+                                   ShadowStyle == ShadowStyle.Opaque ? attr.Background : attr.Background.GetDarkerColor ()));
+
+    }
+
+    /// <inheritdoc />
+    /// <inheritdoc />
+    protected override bool OnDrawingText ()
+    {
+        return true;
+    }
 
-        return base.GetNormalColor ();
+    /// <inheritdoc />
+    protected override bool OnClearingViewport ()
+    {
+        // Prevent clearing (so we can have transparency)
+        return true;
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        //base.OnDrawContent (viewport);
         switch (ShadowStyle)
         {
             case ShadowStyle.Opaque:
                 if (Orientation == Orientation.Vertical)
                 {
-                    DrawVerticalShadowOpaque (viewport);
+                    DrawVerticalShadowOpaque (Viewport);
                 }
                 else
                 {
-                    DrawHorizontalShadowOpaque (viewport);
+                    DrawHorizontalShadowOpaque (Viewport);
                 }
 
                 break;
@@ -57,21 +71,23 @@ internal class ShadowView : View
             case ShadowStyle.Transparent:
                 //Attribute prevAttr = Driver.GetAttribute ();
                 //var attr = new Attribute (prevAttr.Foreground, prevAttr.Background);
-                //Driver.SetAttribute (attr);
+                //SetAttribute (attr);
 
                 if (Orientation == Orientation.Vertical)
                 {
-                    DrawVerticalShadowTransparent (viewport);
+                    DrawVerticalShadowTransparent (Viewport);
                 }
                 else
                 {
-                    DrawHorizontalShadowTransparent (viewport);
+                    DrawHorizontalShadowTransparent (Viewport);
                 }
 
-                //Driver.SetAttribute (prevAttr);
+                //SetAttribute (prevAttr);
 
                 break;
         }
+
+        return true;
     }
 
     /// <summary>
@@ -106,16 +122,18 @@ internal class ShadowView : View
 
     private void DrawHorizontalShadowTransparent (Rectangle viewport)
     {
-        Rectangle screen = ViewportToScreen (viewport);
+        Rectangle screen = ViewportToScreen (Viewport);
 
-        // Fill the rest of the rectangle - note we skip the last since vertical will draw it
-        for (int i = Math.Max(0, screen.X + 1); i < screen.X + screen.Width - 1; i++)
+        for (int r = Math.Max (0, screen.Y); r < screen.Y + screen.Height; r++)
         {
-            Driver.Move (i, screen.Y);
-
-            if (i < Driver.Contents!.GetLength (1) && screen.Y < Driver.Contents.GetLength (0))
+            for (int c = Math.Max (0, screen.X + 1); c < screen.X + screen.Width; c++)
             {
-                Driver.AddRune (Driver.Contents [screen.Y, i].Rune);
+                Driver?.Move (c, r);
+
+                if (c < Driver?.Contents!.GetLength (1) && r < Driver?.Contents?.GetLength (0))
+                {
+                    Driver.AddRune (Driver.Contents [r, c].Rune);
+                }
             }
         }
     }
@@ -126,7 +144,7 @@ internal class ShadowView : View
         AddRune (0, 0, Glyphs.ShadowVerticalStart);
 
         // Fill the rest of the rectangle with the glyph
-        for (var i = 1; i < viewport.Height; i++)
+        for (var i = 1; i < viewport.Height - 1; i++)
         {
             AddRune (0, i, Glyphs.ShadowVertical);
         }
@@ -134,16 +152,19 @@ internal class ShadowView : View
 
     private void DrawVerticalShadowTransparent (Rectangle viewport)
     {
-        Rectangle screen = ViewportToScreen (viewport);
+        Rectangle screen = ViewportToScreen (Viewport);
 
         // Fill the rest of the rectangle
-        for (int i = Math.Max (0, screen.Y); i < screen.Y + viewport.Height; i++)
+        for (int c = Math.Max (0, screen.X); c < screen.X + screen.Width; c++)
         {
-            Driver.Move (screen.X, i);
-
-            if (Driver.Contents is { } && screen.X < Driver.Contents.GetLength (1) && i < Driver.Contents.GetLength (0))
+            for (int r = Math.Max (0, screen.Y); r < screen.Y + viewport.Height; r++)
             {
-                Driver.AddRune (Driver.Contents [i, screen.X].Rune);
+                Driver?.Move (c, r);
+
+                if (Driver?.Contents is { } && screen.X < Driver.Contents.GetLength (1) && r < Driver.Contents.GetLength (0))
+                {
+                    Driver.AddRune (Driver.Contents [r, c].Rune);
+                }
             }
         }
     }

+ 4 - 5
Terminal.Gui/View/DrawEventArgs.cs

@@ -1,7 +1,9 @@
-namespace Terminal.Gui;
+using System.ComponentModel;
+
+namespace Terminal.Gui;
 
 /// <summary>Event args for draw events</summary>
-public class DrawEventArgs : EventArgs
+public class DrawEventArgs : CancelEventArgs
 {
     /// <summary>Creates a new instance of the <see cref="DrawEventArgs"/> class.</summary>
     /// <param name="newViewport">
@@ -18,9 +20,6 @@ public class DrawEventArgs : EventArgs
         OldViewport = oldViewport;
     }
 
-    /// <summary>If set to true, the draw operation will be canceled, if applicable.</summary>
-    public bool Cancel { get; set; }
-
     /// <summary>Gets the Content-relative rectangle describing the old visible viewport into the <see cref="View"/>.</summary>
     public Rectangle OldViewport { get; }
 

+ 10 - 2
Terminal.Gui/View/Layout/Dim.cs

@@ -22,6 +22,14 @@ using System.Numerics;
 ///             </listheader>
 ///             <item>
 ///                 <term>
+///                     <see cref="Dim.Absolute"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Dim"/> that is a fixed size.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
 ///                     <see cref="Dim.Auto"/>
 ///                 </term>
 ///                 <description>
@@ -182,9 +190,9 @@ public abstract record Dim : IEqualityOperators<Dim, Dim, bool>
     /// </summary>
     /// <param name="dim">A reference to this <see cref="Dim"/> instance.</param>
     /// <returns></returns>
-    public bool Has<T> (out Dim dim) where T : Dim
+    public bool Has<T> (out T dim) where T : Dim
     {
-        dim = this;
+        dim = (this as T)!;
 
         return this switch
                {

+ 69 - 67
Terminal.Gui/View/Layout/DimAuto.cs

@@ -70,15 +70,15 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
 
         if (Style.FastHasFlags (DimAutoStyle.Content))
         {
-            if (!us.ContentSizeTracksViewport)
+            maxCalculatedSize = textSize;
+
+            if (us is { ContentSizeTracksViewport: false, Subviews.Count: 0 })
             {
                 // ContentSize was explicitly set. Use `us.ContentSize` to determine size.
                 maxCalculatedSize = dimension == Dimension.Width ? us.GetContentSize ().Width : us.GetContentSize ().Height;
             }
             else
             {
-                maxCalculatedSize = textSize;
-
                 // TOOD: All the below is a naive implementation. It may be possible to optimize this.
 
                 List<View> includedSubviews = us.Subviews.ToList ();
@@ -130,7 +130,10 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
                 {
                     notDependentSubViews = includedSubviews.Where (
                                                                    v => v.Width is { }
-                                                                        && (v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) // BUGBUG: We should use v.X.Has and v.Width.Has?
+                                                                        && (v.X is PosAbsolute or PosFunc
+                                                                            || v.Width is DimAuto
+                                                                                          or DimAbsolute
+                                                                                          or DimFunc) // BUGBUG: We should use v.X.Has and v.Width.Has?
                                                                         && !v.X.Has<PosAnchorEnd> (out _)
                                                                         && !v.X.Has<PosAlign> (out _)
                                                                         && !v.X.Has<PosCenter> (out _)
@@ -143,7 +146,10 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
                 {
                     notDependentSubViews = includedSubviews.Where (
                                                                    v => v.Height is { }
-                                                                        && (v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) // BUGBUG: We should use v.Y.Has and v.Height.Has?
+                                                                        && (v.Y is PosAbsolute or PosFunc
+                                                                            || v.Height is DimAuto
+                                                                                           or DimAbsolute
+                                                                                           or DimFunc) // BUGBUG: We should use v.Y.Has and v.Height.Has?
                                                                         && !v.Y.Has<PosAnchorEnd> (out _)
                                                                         && !v.Y.Has<PosAlign> (out _)
                                                                         && !v.Y.Has<PosCenter> (out _)
@@ -168,6 +174,7 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
                     {
                         int width = v.Width!.Calculate (0, superviewContentSize, v, dimension);
                         size = v.X.GetAnchor (0) + width;
+
                     }
                     else
                     {
@@ -237,22 +244,14 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
                 List<int> groupIds = includedSubviews.Select (
                                                               v =>
                                                               {
-                                                                  if (dimension == Dimension.Width)
+                                                                  return dimension switch
                                                                   {
-                                                                      if (v.X.Has<PosAlign> (out Pos posAlign))
-                                                                      {
-                                                                          return ((PosAlign)posAlign).GroupId;
-                                                                      }
-                                                                  }
-                                                                  else
-                                                                  {
-                                                                      if (v.Y.Has<PosAlign> (out Pos posAlign))
-                                                                      {
-                                                                          return ((PosAlign)posAlign).GroupId;
-                                                                      }
-                                                                  }
-
-                                                                  return -1;
+                                                                      Dimension.Width when v.X.Has<PosAlign> (out PosAlign posAlign) =>
+                                                                              ((PosAlign)posAlign).GroupId,
+                                                                      Dimension.Height when v.Y.Has<PosAlign> (out PosAlign posAlign) =>
+                                                                              ((PosAlign)posAlign).GroupId,
+                                                                      _ => -1
+                                                                  };
                                                               })
                                                      .Distinct ()
                                                      .ToList ();
@@ -260,18 +259,7 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
                 foreach (int groupId in groupIds.Where (g => g != -1))
                 {
                     // PERF: If this proves a perf issue, consider caching a ref to this list in each item
-                    List<PosAlign?> posAlignsInGroup = includedSubviews.Where (
-                                                                               v =>
-                                                                               {
-                                                                                   return dimension switch
-                                                                                   {
-                                                                                       Dimension.Width when v.X is PosAlign alignX => alignX.GroupId
-                                                                                           == groupId,
-                                                                                       Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId
-                                                                                           == groupId,
-                                                                                       _ => false
-                                                                                   };
-                                                                               })
+                    List<PosAlign?> posAlignsInGroup = includedSubviews.Where (v => PosAlign.HasGroupId (v, dimension, groupId))
                                                                        .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign)
                                                                        .ToList ();
 
@@ -349,16 +337,9 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
 
                     // BUGBUG: The order may not be correct. May need to call TopologicalSort?
                     // TODO: Figure out a way to not have Calculate change the state of subviews (calling SRL).
-                    if (dimension == Dimension.Width)
-                    {
-                        v.SetRelativeLayout (new (maxCalculatedSize, 0));
-                    }
-                    else
-                    {
-                        v.SetRelativeLayout (new (0, maxCalculatedSize));
-                    }
-
-                    int maxPosView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
+                    int maxPosView = dimension == Dimension.Width
+                                         ? v.Frame.X + v.Width!.Calculate (0, maxCalculatedSize, v, dimension)
+                                         : v.Frame.Y + v.Height!.Calculate (0, maxCalculatedSize, v, dimension);
 
                     if (maxPosView > maxCalculatedSize)
                     {
@@ -390,16 +371,9 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
 
                     // BUGBUG: The order may not be correct. May need to call TopologicalSort?
                     // TODO: Figure out a way to not have Calculate change the state of subviews (calling SRL).
-                    if (dimension == Dimension.Width)
-                    {
-                        v.SetRelativeLayout (new (maxCalculatedSize, 0));
-                    }
-                    else
-                    {
-                        v.SetRelativeLayout (new (0, maxCalculatedSize));
-                    }
-
-                    int maxDimView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
+                    int maxDimView = dimension == Dimension.Width
+                                         ? v.Frame.X + v.Width!.Calculate (0, maxCalculatedSize, v, dimension)
+                                         : v.Frame.Y + v.Height!.Calculate (0, maxCalculatedSize, v, dimension);
 
                     if (maxDimView > maxCalculatedSize)
                     {
@@ -410,15 +384,11 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
                 #endregion DimView
 
                 #region DimAuto
+
                 // [ ] DimAuto      - Dimension is internally calculated
 
                 List<View> dimAutoSubViews;
 
-                if (dimension == Dimension.Width && us.GetType ().Name == "Bar" && us.Subviews.Count == 3)
-                {
-
-                }
-
                 if (dimension == Dimension.Width)
                 {
                     dimAutoSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has<DimAuto> (out _)).ToList ();
@@ -432,16 +402,9 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
                 {
                     View v = dimAutoSubViews [i];
 
-                    if (dimension == Dimension.Width)
-                    {
-                        v.SetRelativeLayout (new (maxCalculatedSize, 0));
-                    }
-                    else
-                    {
-                        v.SetRelativeLayout (new (0, maxCalculatedSize));
-                    }
-
-                    int maxDimAuto= dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
+                    int maxDimAuto = dimension == Dimension.Width
+                                         ? v.Frame.X + v.Width!.Calculate (0, maxCalculatedSize, v, dimension)
+                                         : v.Frame.Y + v.Height!.Calculate (0, maxCalculatedSize, v, dimension);
 
                     if (maxDimAuto > maxCalculatedSize)
                     {
@@ -450,6 +413,45 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
                 }
 
                 #endregion
+
+
+                #region DimFill
+
+                //// [ ] DimFill      - Dimension is internally calculated
+
+                //List<View> DimFillSubViews;
+
+                //if (dimension == Dimension.Width)
+                //{
+                //    DimFillSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has<DimFill> (out _)).ToList ();
+                //}
+                //else
+                //{
+                //    DimFillSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has<DimFill> (out _)).ToList ();
+                //}
+
+                //for (var i = 0; i < DimFillSubViews.Count; i++)
+                //{
+                //    View v = DimFillSubViews [i];
+
+                //    if (dimension == Dimension.Width)
+                //    {
+                //        v.SetRelativeLayout (new (maxCalculatedSize, 0));
+                //    }
+                //    else
+                //    {
+                //        v.SetRelativeLayout (new (0, maxCalculatedSize));
+                //    }
+
+                //    int maxDimFill = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
+
+                //    if (maxDimFill > maxCalculatedSize)
+                //    {
+                //        maxCalculatedSize = maxDimFill;
+                //    }
+                //}
+
+                #endregion
             }
         }
 

+ 1 - 1
Terminal.Gui/View/Layout/DimFunc.cs

@@ -8,7 +8,7 @@ namespace Terminal.Gui;
 ///     This is a low-level API that is typically used internally by the layout system. Use the various static
 ///     methods on the <see cref="Gui.Dim"/> class to create <see cref="Gui.Dim"/> objects instead.
 /// </remarks>
-/// <param name="Fn">The function that computes the dimension.</param>
+/// <param name="Fn">The function that computes the dimension. If this function throws <see cref="LayoutException"/>... </param>
 public record DimFunc (Func<int> Fn) : Dim
 {
     /// <summary>

+ 1 - 1
Terminal.Gui/View/Layout/LayoutEventArgs.cs

@@ -1,6 +1,6 @@
 namespace Terminal.Gui;
 
-/// <summary>Event arguments for the <see cref="View.LayoutComplete"/> event.</summary>
+/// <summary>Event arguments for the <see cref="View.SubviewsLaidOut"/> event.</summary>
 public class LayoutEventArgs : EventArgs
 {
     /// <summary>Creates a new instance of the <see cref="Terminal.Gui.LayoutEventArgs"/> class.</summary>

+ 30 - 0
Terminal.Gui/View/Layout/LayoutException.cs

@@ -0,0 +1,30 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+///     Represents an exception that is thrown when a layout operation fails.
+/// </summary>
+[Serializable]
+public class LayoutException : Exception
+{
+
+    /// <summary>
+    ///     Creates a new instance of <see cref="LayoutException"/>.
+    /// </summary>
+    public LayoutException () { }
+
+    /// <summary>
+    ///     Creates a new instance of <see cref="LayoutException"/>.
+    /// </summary>
+    /// <param name="message"></param>
+    public LayoutException (string? message) : base (message) { }
+
+    /// <summary>
+    ///     Creates a new instance of <see cref="LayoutException"/>.
+    /// </summary>
+    /// <param name="message"></param>
+    /// <param name="innerException"></param>
+    public LayoutException (string? message, Exception? innerException)
+        : base (message, innerException)
+    { }
+}

+ 8 - 8
Terminal.Gui/View/Layout/Pos.cs

@@ -251,22 +251,22 @@ public abstract record Pos
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Top (View? view) { return new PosView (view, Side.Top); }
+    public static Pos Top (View view) { return new PosView (view, Side.Top); }
 
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Y (View? view) { return new PosView (view, Side.Top); }
+    public static Pos Y (View view) { return new PosView (view, Side.Top); }
 
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Left (View? view) { return new PosView (view, Side.Left); }
+    public static Pos Left (View view) { return new PosView (view, Side.Left); }
 
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos X (View? view) { return new PosView (view, Side.Left); }
+    public static Pos X (View view) { return new PosView (view, Side.Left); }
 
     /// <summary>
     ///     Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the specified
@@ -274,7 +274,7 @@ public abstract record Pos
     /// </summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Bottom (View? view) { return new PosView (view, Side.Bottom); }
+    public static Pos Bottom (View view) { return new PosView (view, Side.Bottom); }
 
     /// <summary>
     ///     Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the specified
@@ -282,7 +282,7 @@ public abstract record Pos
     /// </summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Right (View? view) { return new PosView (view, Side.Right); }
+    public static Pos Right (View view) { return new PosView (view, Side.Right); }
 
     #endregion static Pos creation methods
 
@@ -335,9 +335,9 @@ public abstract record Pos
     /// </summary>
     /// <param name="pos">A reference to this <see cref="Pos"/> instance.</param>
     /// <returns></returns>
-    public bool Has<T> (out Pos pos) where T : Pos
+    public bool Has<T> (out T pos) where T : Pos
     {
-        pos = this;
+        pos = (this as T)!;
 
         return this switch
                {

+ 30 - 43
Terminal.Gui/View/Layout/PosAlign.cs

@@ -1,6 +1,7 @@
 #nullable enable
 
 using System.ComponentModel;
+using System.Text.RegularExpressions;
 
 namespace Terminal.Gui;
 
@@ -10,7 +11,7 @@ namespace Terminal.Gui;
 /// <remarks>
 ///     <para>
 ///         Updating the properties of <see cref="Aligner"/> is supported, but will not automatically cause re-layout to
-///         happen. <see cref="View.LayoutSubviews"/>
+///         happen. <see cref="View.Layout()"/>
 ///         must be called on the SuperView.
 ///     </para>
 ///     <para>
@@ -28,7 +29,7 @@ public record PosAlign : Pos
     /// <summary>
     ///     The cached location. Used to store the calculated location to minimize recalculating it.
     /// </summary>
-    public int? _cachedLocation;
+    internal int? _cachedLocation;
 
     private readonly Aligner? _aligner;
 
@@ -63,17 +64,7 @@ public record PosAlign : Pos
         List<int> dimensionsList = new ();
 
         // PERF: If this proves a perf issue, consider caching a ref to this list in each item
-        List<View> viewsInGroup = views.Where (
-                                               v =>
-                                               {
-                                                   return dimension switch
-                                                   {
-                                                       Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId,
-                                                       Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId,
-                                                       _ => false
-                                                   };
-                                               })
-                                       .ToList ();
+        List<View> viewsInGroup = views.Where (v => HasGroupId (v, dimension, groupId)).ToList ();
 
         if (viewsInGroup.Count == 0)
         {
@@ -99,6 +90,16 @@ public record PosAlign : Pos
         return dimensionsList.Sum ();
     }
 
+    internal static bool HasGroupId (View v, Dimension dimension, int groupId)
+    {
+        return dimension switch
+        {
+            Dimension.Width when v.X.Has<PosAlign> (out PosAlign pos) => pos.GroupId == groupId,
+            Dimension.Height when v.Y.Has<PosAlign> (out PosAlign pos) => pos.GroupId == groupId,
+            _ => false
+        };
+    }
+
     /// <summary>
     ///     Gets the identifier of a set of views that should be aligned together. When only a single
     ///     set of views in a SuperView is aligned, setting <see cref="GroupId"/> is not needed because it defaults to 0.
@@ -110,17 +111,23 @@ public record PosAlign : Pos
 
     internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension)
     {
-        if (_cachedLocation.HasValue && Aligner.ContainerSize == superviewDimension)
+        if (_cachedLocation.HasValue && Aligner.ContainerSize == superviewDimension && !us.NeedsLayout)
         {
             return _cachedLocation.Value;
         }
 
-        if (us?.SuperView is null)
+        IList<View>? groupViews;
+        if (us.SuperView is null)
         {
-            return 0;
+            groupViews = new List<View> ();
+            groupViews.Add (us);
+        }
+        else
+        {
+            groupViews = us.SuperView!.Subviews.Where (v => HasGroupId (v, dimension, GroupId)).ToList ();
         }
 
-        AlignAndUpdateGroup (GroupId, us.SuperView.Subviews, dimension, superviewDimension);
+        AlignAndUpdateGroup (GroupId, groupViews, dimension, superviewDimension);
 
         if (_cachedLocation.HasValue)
         {
@@ -145,31 +152,9 @@ public record PosAlign : Pos
         List<int> dimensionsList = new ();
 
         // PERF: If this proves a perf issue, consider caching a ref to this list in each item
-        List<PosAlign?> posAligns = views.Select (
-                                                  v =>
-                                                  {
-                                                      switch (dimension)
-                                                      {
-                                                          case Dimension.Width when v.X.Has<PosAlign> (out Pos pos):
-
-                                                              if (pos is PosAlign posAlignX && posAlignX.GroupId == groupId)
-                                                              {
-                                                                  return posAlignX;
-                                                              }
-
-                                                              break;
-                                                          case Dimension.Height when v.Y.Has<PosAlign> (out Pos pos):
-                                                              if (pos is PosAlign posAlignY && posAlignY.GroupId == groupId)
-                                                              {
-                                                                  return posAlignY;
-                                                              }
-
-                                                              break;
-                                                      }
-
-                                                      return null;
-                                                  })
-                                         .ToList ();
+        List<PosAlign?> posAligns = views.Where (v => PosAlign.HasGroupId (v, dimension, groupId))
+                                        .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign)
+                                        .ToList ();
 
         // PERF: We iterate over viewsInGroup multiple times here.
 
@@ -185,7 +170,9 @@ public record PosAlign : Pos
                     firstInGroup = posAligns [index]!.Aligner;
                 }
 
-                dimensionsList.Add (dimension == Dimension.Width ? views [index].Frame.Width : views [index].Frame.Height);
+                dimensionsList.Add (dimension == Dimension.Width 
+                                        ? views [index].Width!.Calculate(0, size, views [index], dimension) 
+                                        : views [index].Height!.Calculate (0, size, views [index], dimension));
             }
         }
 

+ 1 - 1
Terminal.Gui/View/Layout/PosAnchorEnd.cs

@@ -36,7 +36,7 @@ public record PosAnchorEnd : Pos
     public bool UseDimForOffset { get; }
 
     /// <inheritdoc/>
-    public override string ToString () { return UseDimForOffset ? "AnchorEnd()" : $"AnchorEnd({Offset})"; }
+    public override string ToString () { return UseDimForOffset ? "AnchorEnd" : $"AnchorEnd({Offset})"; }
 
     internal override int GetAnchor (int size)
     {

+ 1 - 1
Terminal.Gui/View/Layout/PosFunc.cs

@@ -4,7 +4,7 @@ namespace Terminal.Gui;
 /// <summary>
 ///     Represents a position that is computed by executing a function that returns an integer position.
 /// </summary>
-/// <param name="Fn">The function that computes the position.</param>
+/// <param name="Fn">The function that computes the dimension. If this function throws <see cref="LayoutException"/>... </param>
 public record PosFunc (Func<int> Fn) : Pos
 {
     /// <inheritdoc/>

+ 23 - 5
Terminal.Gui/View/Layout/PosView.cs

@@ -1,4 +1,6 @@
 #nullable enable
+using System.Diagnostics;
+
 namespace Terminal.Gui;
 
 /// <summary>
@@ -10,19 +12,35 @@ namespace Terminal.Gui;
 ///         methods on the <see cref="Pos"/> class to create <see cref="Pos"/> objects instead.
 ///     </para>
 /// </remarks>
-/// <param name="View">The View the position is anchored to.</param>
-/// <param name="Side">The side of the View the position is anchored to.</param>
-public record PosView (View? View, Side Side) : Pos
+public record PosView : Pos
 {
+    /// <summary>
+    ///     Represents a position that is anchored to the side of another view.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This is a low-level API that is typically used internally by the layout system. Use the various static
+    ///         methods on the <see cref="Pos"/> class to create <see cref="Pos"/> objects instead.
+    ///     </para>
+    /// </remarks>
+    /// <param name="view">The View the position is anchored to.</param>
+    /// <param name="side">The side of the View the position is anchored to.</param>
+    public PosView (View view, Side side)
+    {
+        ArgumentNullException.ThrowIfNull (view);
+        Target = view;
+        Side = side;
+    }
+
     /// <summary>
     ///     Gets the View the position is anchored to.
     /// </summary>
-    public View? Target { get; } = View;
+    public View Target { get; }
 
     /// <summary>
     ///     Gets the side of the View the position is anchored to.
     /// </summary>
-    public Side Side { get; } = Side;
+    public Side Side { get; }
 
     /// <inheritdoc/>
     public override string ToString ()

+ 74 - 90
Terminal.Gui/View/View.Adornments.cs

@@ -1,4 +1,5 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 public partial class View // Adornments
 {
@@ -7,9 +8,10 @@ public partial class View // Adornments
     /// </summary>
     private void SetupAdornments ()
     {
-        //// TODO: Move this to Adornment as a static factory method
+        // TODO: Move this to Adornment as a static factory method
         if (this is not Adornment)
         {
+            // TODO: Make the Adornments Lazy and only create them when needed
             Margin = new (this);
             Border = new (this);
             Padding = new (this);
@@ -46,6 +48,9 @@ public partial class View // Adornments
     /// </summary>
     /// <remarks>
     ///     <para>
+    ///         The margin is typically transparent. This can be overriden by explicitly setting <see cref="ColorScheme"/>.
+    ///     </para>
+    ///     <para>
     ///         Enabling <see cref="ShadowStyle"/> will change the Thickness of the Margin to include the shadow.
     ///     </para>
     ///     <para>
@@ -54,11 +59,11 @@ public partial class View // Adornments
     ///     </para>
     ///     <para>
     ///         Changing the size of an adornment (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>) will
-    ///         change the size of <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
+    ///         change the size of <see cref="Frame"/> which will call <see cref="SetNeedsLayout"/> to update the layout of the
     ///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
     ///     </para>
     /// </remarks>
-    public Margin Margin { get; private set; }
+    public Margin? Margin { get; private set; }
 
     private ShadowStyle _shadowStyle;
 
@@ -109,12 +114,12 @@ public partial class View // Adornments
     ///         View's content and are not clipped by the View's Clip Area.
     ///     </para>
     ///     <para>
-    ///         Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>) will
-    ///         change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
+    ///         Changing the size of an adornment (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>) will
+    ///         change the size of <see cref="Frame"/> which will call <see cref="SetNeedsLayout"/> to update the layout of the
     ///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
     ///     </para>
     /// </remarks>
-    public Border Border { get; private set; }
+    public Border? Border { get; private set; }
 
     /// <summary>Gets or sets whether the view has a one row/col thick border.</summary>
     /// <remarks>
@@ -127,6 +132,10 @@ public partial class View // Adornments
     ///         Setting this property to <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s
     ///         <see cref="Adornment.Thickness"/> to `0` and <see cref="BorderStyle"/> to <see cref="LineStyle.None"/>.
     ///     </para>
+    ///     <para>
+    ///         Calls <see cref="OnBorderStyleChanging"/> and raises <see cref="BorderStyleChanging"/>, which allows change
+    ///         to be cancelled.
+    ///     </para>
     ///     <para>For more advanced customization of the view's border, manipulate see <see cref="Border"/> directly.</para>
     /// </remarks>
     public LineStyle BorderStyle
@@ -134,38 +143,49 @@ public partial class View // Adornments
         get => Border?.LineStyle ?? LineStyle.Single;
         set
         {
+            if (Border is null)
+            {
+                return;
+            }
+
             LineStyle old = Border?.LineStyle ?? LineStyle.None;
+
+            // It's tempting to try to optimize this by checking that old != value and returning.
+            // Do not.
+
             CancelEventArgs<LineStyle> e = new (ref old, ref value);
-            OnBorderStyleChanging (e);
+
+            if (OnBorderStyleChanging (e) || e.Cancel)
+            {
+                return;
+            }
+
+            BorderStyleChanging?.Invoke (this, e);
+
+            if (e.Cancel)
+            {
+                return;
+            }
+
+            SetBorderStyle (e.NewValue);
+            SetAdornmentFrames ();
+            SetNeedsLayout ();
         }
     }
 
     /// <summary>
-    ///     Called when the <see cref="BorderStyle"/> is changing. Invokes <see cref="BorderStyleChanging"/>, which allows the
-    ///     event to be cancelled.
+    ///     Called when the <see cref="BorderStyle"/> is changing.
     /// </summary>
     /// <remarks>
-    ///     Override <see cref="SetBorderStyle"/> to prevent the <see cref="BorderStyle"/> from changing.
+    ///     Set e.Cancel to true to prevent the <see cref="BorderStyle"/> from changing.
     /// </remarks>
     /// <param name="e"></param>
-    protected void OnBorderStyleChanging (CancelEventArgs<LineStyle> e)
-    {
-        if (Border is null)
-        {
-            return;
-        }
-
-        BorderStyleChanging?.Invoke (this, e);
+    protected virtual bool OnBorderStyleChanging (CancelEventArgs<LineStyle> e) { return false; }
 
-        if (e.Cancel)
-        {
-            return;
-        }
-
-        SetBorderStyle (e.NewValue);
-        LayoutAdornments ();
-        SetNeedsLayout ();
-    }
+    /// <summary>
+    ///     Fired when the <see cref="BorderStyle"/> is changing. Allows the event to be cancelled.
+    /// </summary>
+    public event EventHandler<CancelEventArgs<LineStyle>>? BorderStyleChanging;
 
     /// <summary>
     ///     Sets the <see cref="BorderStyle"/> of the view to the specified value.
@@ -188,25 +208,19 @@ public partial class View // Adornments
     {
         if (value != LineStyle.None)
         {
-            if (Border.Thickness == Thickness.Empty)
+            if (Border!.Thickness == Thickness.Empty)
             {
                 Border.Thickness = new (1);
             }
         }
         else
         {
-            Border.Thickness = new (0);
+            Border!.Thickness = new (0);
         }
 
         Border.LineStyle = value;
     }
 
-    /// <summary>
-    ///     Fired when the <see cref="BorderStyle"/> is changing. Allows the event to be cancelled.
-    /// </summary>
-    [CanBeNull]
-    public event EventHandler<CancelEventArgs<LineStyle>> BorderStyleChanging;
-
     /// <summary>
     ///     The <see cref="Adornment"/> inside of the view that offsets the <see cref="Viewport"/>
     ///     from the <see cref="Border"/>.
@@ -217,12 +231,12 @@ public partial class View // Adornments
     ///         View's content and are not clipped by the View's Clip Area.
     ///     </para>
     ///     <para>
-    ///         Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>) will
-    ///         change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
+    ///         Changing the size of an adornment (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>) will
+    ///         change the size of <see cref="Frame"/> which will call <see cref="SetNeedsLayout"/> to update the layout of the
     ///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
     ///     </para>
     /// </remarks>
-    public Padding Padding { get; private set; }
+    public Padding? Padding { get; private set; }
 
     /// <summary>
     ///     <para>Gets the thickness describing the sum of the Adornments' thicknesses.</para>
@@ -235,78 +249,48 @@ public partial class View // Adornments
     /// <returns>A thickness that describes the sum of the Adornments' thicknesses.</returns>
     public Thickness GetAdornmentsThickness ()
     {
-        if (Margin is null)
-        {
-            return Thickness.Empty;
-        }
-
-        return Margin.Thickness + Border.Thickness + Padding.Thickness;
-    }
+        Thickness result = Thickness.Empty;
 
-    /// <summary>Lays out the Adornments of the View.</summary>
-    /// <remarks>
-    ///     Overriden by <see cref="Adornment"/> to do nothing, as <see cref="Adornment"/> does not have adornments.
-    /// </remarks>
-    internal virtual void LayoutAdornments ()
-    {
-        if (Margin is null)
+        if (Margin is { })
         {
-            return; // CreateAdornments () has not been called yet
+            result += Margin.Thickness;
         }
 
-        if (Margin.Frame.Size != Frame.Size)
+        if (Border is { })
         {
-            Margin.SetFrame (Rectangle.Empty with { Size = Frame.Size });
-            Margin.X = 0;
-            Margin.Y = 0;
-            Margin.Width = Frame.Size.Width;
-            Margin.Height = Frame.Size.Height;
+            result += Border.Thickness;
         }
 
-        Margin.SetNeedsLayout ();
-        Margin.SetNeedsDisplay ();
-
-        if (IsInitialized)
+        if (Padding is { })
         {
-            Margin.LayoutSubviews ();
+            result += Padding.Thickness;
         }
 
-        Rectangle border = Margin.Thickness.GetInside (Margin.Frame);
+        return result;
+    }
 
-        if (border != Border.Frame)
+    /// <summary>Sets the Frame's of the Margin, Border, and Padding.</summary>
+    internal void SetAdornmentFrames ()
+    {
+        if (this is Adornment)
         {
-            Border.SetFrame (border);
-            Border.X = border.Location.X;
-            Border.Y = border.Location.Y;
-            Border.Width = border.Size.Width;
-            Border.Height = border.Size.Height;
+            // Adornments do not have Adornments
+            return;
         }
 
-        Border.SetNeedsLayout ();
-        Border.SetNeedsDisplay ();
-
-        if (IsInitialized)
+        if (Margin is { })
         {
-            Border.LayoutSubviews ();
+            Margin!.Frame = Rectangle.Empty with { Size = Frame.Size };
         }
 
-        Rectangle padding = Border.Thickness.GetInside (Border.Frame);
-
-        if (padding != Padding.Frame)
+        if (Border is { } && Margin is { })
         {
-            Padding.SetFrame (padding);
-            Padding.X = padding.Location.X;
-            Padding.Y = padding.Location.Y;
-            Padding.Width = padding.Size.Width;
-            Padding.Height = padding.Size.Height;
+            Border!.Frame = Margin!.Thickness.GetInside (Margin!.Frame);
         }
 
-        Padding.SetNeedsLayout ();
-        Padding.SetNeedsDisplay ();
-
-        if (IsInitialized)
+        if (Padding is { } && Border is { })
         {
-            Padding.LayoutSubviews ();
+            Padding!.Frame = Border!.Thickness.GetInside (Border!.Frame);
         }
     }
 }

+ 2 - 1
Terminal.Gui/View/View.Arrangement.cs

@@ -1,4 +1,5 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 public partial class View
 {

+ 121 - 0
Terminal.Gui/View/View.Attribute.cs

@@ -0,0 +1,121 @@
+#nullable enable
+namespace Terminal.Gui;
+
+public partial class View
+{
+    // TODO: Rename "Color"->"Attribute" given we'll soon have non-color information in Attributes?
+    // TODO: See https://github.com/gui-cs/Terminal.Gui/issues/457
+
+    #region ColorScheme
+
+    private ColorScheme? _colorScheme;
+
+    /// <summary>The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s color scheme.</summary>
+    public virtual ColorScheme? ColorScheme
+    {
+        get => _colorScheme ?? SuperView?.ColorScheme;
+        set
+        {
+            if (_colorScheme == value)
+            {
+                return;
+            }
+
+            _colorScheme = value;
+
+            // BUGBUG: This should be in Border.cs somehow
+            if (Border is { } && Border.LineStyle != LineStyle.None && Border.ColorScheme is { })
+            {
+                Border.ColorScheme = _colorScheme;
+            }
+
+            SetNeedsDraw ();
+        }
+    }
+
+    /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
+    /// <returns>
+    ///     <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 ?? new ();
+
+        return Enabled ? GetColor (cs.Focus) : cs.Disabled;
+    }
+
+    /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
+    /// <returns>
+    ///     <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 GetHotFocusColor ()
+    {
+        ColorScheme? cs = ColorScheme ?? new ();
+
+        return Enabled ? GetColor (cs.HotFocus) : cs.Disabled;
+    }
+
+    /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
+    /// <returns>
+    ///     <see cref="ColorScheme.HotNormal"/> 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 GetHotNormalColor ()
+    {
+        ColorScheme? cs = ColorScheme ?? new ();
+
+        return Enabled ? GetColor (cs.HotNormal) : cs.Disabled;
+    }
+
+    /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
+    /// <returns>
+    ///     <see cref="ColorScheme.Normal"/> 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 GetNormalColor ()
+    {
+        ColorScheme? cs = ColorScheme ?? new ();
+
+        Attribute disabled = new (cs.Disabled.Foreground, cs.Disabled.Background);
+
+        if (Diagnostics.HasFlag (ViewDiagnosticFlags.Hover) && _hovering)
+        {
+            disabled = new (disabled.Foreground.GetDarkerColor (), disabled.Background.GetDarkerColor ());
+        }
+
+        return Enabled ? GetColor (cs.Normal) : disabled;
+    }
+
+    private Attribute GetColor (Attribute inputAttribute)
+    {
+        Attribute attr = inputAttribute;
+
+        if (Diagnostics.HasFlag (ViewDiagnosticFlags.Hover) && _hovering)
+        {
+            attr = new (attr.Foreground.GetDarkerColor (), attr.Background.GetDarkerColor ());
+        }
+
+        return attr;
+    }
+
+    #endregion ColorScheme
+
+    #region Attribute
+
+    /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
+    /// <remarks></remarks>
+    /// <param name="attribute">THe Attribute to set.</param>
+    public Attribute SetAttribute (Attribute attribute) { return Driver?.SetAttribute (attribute) ?? Attribute.Default; }
+
+    /// <summary>Gets the current <see cref="Attribute"/>.</summary>
+    /// <returns>The current attribute.</returns>
+    public Attribute GetAttribute () { return Driver?.GetAttribute () ?? Attribute.Default; }
+
+    #endregion Attribute
+}

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

@@ -151,10 +151,7 @@ public partial class View
 
         if (e.Cancel != true)
         {
-            OnResizeNeeded ();
-
-            //SetNeedsLayout ();
-            //SetNeedsDisplay ();
+            SetNeedsLayout ();
         }
 
         return e.Cancel;
@@ -173,7 +170,6 @@ public partial class View
     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);
 
@@ -269,7 +265,7 @@ public partial class View
     ///     </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.
+    ///         <see cref="Layout()"/> and <see cref="OnDrawingContent"/> methods to be called.
     ///     </para>
     /// </remarks>
     public virtual Rectangle Viewport
@@ -313,6 +309,8 @@ public partial class View
             {
                 _viewportLocation = viewport.Location;
                 SetNeedsLayout ();
+                //SetNeedsDraw();
+                //SetSubViewNeedsDraw();
             }
 
             OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport));
@@ -328,6 +326,10 @@ public partial class View
             Size = newSize
         };
 
+        OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport));
+
+        return;
+
         void ApplySettings (ref Rectangle newViewport)
         {
             if (!ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth))

+ 19 - 6
Terminal.Gui/View/View.Diagnostics.cs

@@ -9,24 +9,37 @@ public enum ViewDiagnosticFlags : uint
     Off = 0b_0000_0000,
 
     /// <summary>
-    ///     When enabled, <see cref="View.OnDrawAdornments"/> will draw a ruler in the Thickness.
+    ///     When enabled, <see cref="Adornment"/> will draw a ruler in the Thickness. See <see cref="Adornment.Diagnostics"/>.
     /// </summary>
     Ruler = 0b_0000_0001,
 
     /// <summary>
-    ///     When enabled, <see cref="View.OnDrawAdornments"/> will draw the first letter of the Adornment name ('M', 'B', or 'P')
-    ///     in the Thickness.
+    ///     When enabled, <see cref="Adornment"/> will draw the first letter of the Adornment name ('M', 'B', or 'P')
+    ///     in the Thickness. See <see cref="Adornment.Diagnostics"/>.
     /// </summary>
-    Padding = 0b_0000_0010,
+    Thickness = 0b_0000_0010,
 
     /// <summary>
     ///     When enabled the View's colors will be darker when the mouse is hovering over the View (See <see cref="View.MouseEnter"/> and <see cref="View.MouseLeave"/>.
     /// </summary>
-    Hover = 0b_0000_00100
+    Hover = 0b_0000_00100,
+
+    /// <summary>
+    ///     When enabled a draw indicator will be shown; the indicator will change each time the View's Draw method is called with NeedsDraw set to true.
+    /// </summary>
+    DrawIndicator = 0b_0000_01000,
 }
 
 public partial class View
 {
-    /// <summary>Flags to enable/disable <see cref="View"/> diagnostics.</summary>
+    /// <summary>Gets or sets whether diagnostic information will be drawn. This is a bit-field of <see cref="ViewDiagnosticFlags"/>.e <see cref="View"/> diagnostics.</summary>
+    /// <remarks>
+    /// <para>
+    ///     <see cref="Adornment.Diagnostics"/> gets set to this property by default, enabling <see cref="ViewDiagnosticFlags.Ruler"/> and <see cref="ViewDiagnosticFlags.Thickness"/>.
+    /// </para>
+    /// <para>
+    ///     <see cref="ViewDiagnosticFlags.Hover"/> and <see cref="ViewDiagnosticFlags.DrawIndicator"/> are enabled for all Views independently of Adornments.
+    /// </para>
+    /// </remarks>
     public static ViewDiagnosticFlags Diagnostics { get; set; }
 }

+ 167 - 0
Terminal.Gui/View/View.Drawing.Clipping.cs

@@ -0,0 +1,167 @@
+#nullable enable
+namespace Terminal.Gui;
+
+public partial class View
+{
+    /// <summary>
+    ///     Gets the current Clip region.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         There is a single clip region for the entire application.
+    ///     </para>
+    ///     <para>
+    ///         This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is
+    ///         recommended to clone it first.
+    ///     </para>
+    /// </remarks>
+    /// <returns>The current Clip.</returns>
+    public static Region? GetClip () { return Application.Driver?.Clip; }
+
+    /// <summary>
+    ///     Sets the Clip to the specified region.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         There is a single clip region for the entire application. This method sets the clip region to the specified
+    ///         region.
+    ///     </para>
+    /// </remarks>
+    /// <param name="region"></param>
+    public static void SetClip (Region? region)
+    {
+        if (Driver is { } && region is { })
+        {
+            Driver.Clip = region;
+        }
+    }
+
+    /// <summary>
+    ///     Sets the Clip to be the rectangle of the screen.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         There is a single clip region for the entire application. This method sets the clip region to the screen.
+    ///     </para>
+    ///     <para>
+    ///         This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is
+    ///         recommended to clone it first.
+    ///     </para>
+    /// </remarks>
+    /// <returns>
+    ///     The current Clip, which can be then re-applied <see cref="View.SetClip"/>
+    /// </returns>
+    public static Region? SetClipToScreen ()
+    {
+        Region? previous = GetClip ();
+
+        if (Driver is { })
+        {
+            Driver.Clip = new (Application.Screen);
+        }
+
+        return previous;
+    }
+
+    /// <summary>
+    ///     Removes the specified rectangle from the Clip.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         There is a single clip region for the entire application.
+    ///     </para>
+    /// </remarks>
+    /// <param name="rectangle"></param>
+    public static void ExcludeFromClip (Rectangle rectangle) { Driver?.Clip?.Exclude (rectangle); }
+
+    /// <summary>
+    ///     Changes the Clip to the intersection of the current Clip and the <see cref="Frame"/> of this View.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is
+    ///         recommended to clone it first.
+    ///     </para>
+    /// </remarks>
+    /// <returns>
+    ///     The current Clip, which can be then re-applied <see cref="View.SetClip"/>
+    /// </returns>
+    internal Region? ClipFrame ()
+    {
+        if (Driver is null)
+        {
+            return null;
+        }
+
+        Region previous = GetClip () ?? new (Application.Screen);
+
+        Region frameRegion = previous.Clone ();
+
+        // Translate viewportRegion to screen-relative coords
+        Rectangle screenRect = FrameToScreen ();
+        frameRegion.Intersect (screenRect);
+
+        if (this is Adornment adornment && adornment.Thickness != Thickness.Empty)
+        {
+            // Ensure adornments can't draw outside their thickness
+            frameRegion.Exclude (adornment.Thickness.GetInside (Frame));
+        }
+
+        SetClip (frameRegion);
+
+        return previous;
+    }
+
+    /// <summary>Changes the Clip to the intersection of the current Clip and the <see cref="Viewport"/> of this View.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         By default, sets the Clip 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>
+    ///         <para>
+    ///             This method returns the current clip region, not a clone. If there is a need to modify the clip region, it
+    ///             is recommended to clone it first.
+    ///         </para>
+    ///     </remarks>
+    /// </remarks>
+    /// <returns>
+    ///     The current Clip, which can be then re-applied <see cref="View.SetClip"/>
+    /// </returns>
+    public Region? ClipViewport ()
+    {
+        if (Driver is null)
+        {
+            return null;
+        }
+
+        Region previous = GetClip () ?? new (Application.Screen);
+
+        Region viewportRegion = previous.Clone ();
+
+        Rectangle viewport = ViewportToScreen (new Rectangle (Point.Empty, Viewport.Size));
+        viewportRegion?.Intersect (viewport);
+
+        if (ViewportSettings.HasFlag (ViewportSettings.ClipContentOnly))
+        {
+            // Clamp the Clip to the just content area that is within the viewport
+            Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), GetContentSize ()));
+            viewportRegion?.Intersect (visibleContent);
+        }
+
+        if (this is Adornment adornment && adornment.Thickness != Thickness.Empty)
+        {
+            // Ensure adornments can't draw outside their thickness
+            viewportRegion?.Exclude (adornment.Thickness.GetInside (viewport));
+        }
+
+        SetClip (viewportRegion);
+
+        return previous;
+    }
+}

+ 172 - 0
Terminal.Gui/View/View.Drawing.Primitives.cs

@@ -0,0 +1,172 @@
+using static Terminal.Gui.SpinnerStyle;
+
+namespace Terminal.Gui;
+
+public partial class View
+{
+    /// <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 false;
+        }
+
+        if (col < 0 || row < 0 || col >= Viewport.Width || row >= Viewport.Height)
+        {
+            return false;
+        }
+
+        Point screen = ViewportToScreen (new Point (col, row));
+        Driver?.Move (screen.X, screen.Y);
+
+        return true;
+    }
+
+    /// <summary>Draws the specified character at the current draw position.</summary>
+    /// <param name="rune">The Rune.</param>
+    public void AddRune (Rune rune)
+    {
+        Driver?.AddRune (rune);
+    }
+
+
+    /// <summary>
+    ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
+    ///     convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
+    /// </summary>
+    /// <param name="c">Character to add.</param>
+    public void AddRune (char c) { AddRune (new Rune (c)); }
+
+    /// <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 (Move (col, row))
+        {
+            Driver?.AddRune (rune);
+        }
+    }
+
+
+    /// <summary>Adds the <paramref name="str"/> to the display at the current draw position.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         When the method returns, the draw position will be incremented by the number of columns
+    ///         <paramref name="str"/> required, unless the new column value is outside the <see cref="GetClip()"/> or <see cref="Application.Screen"/>.
+    ///     </para>
+    ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
+    /// </remarks>
+    /// <param name="str">String.</param>
+    public void AddStr (string str)
+    {
+        Driver?.AddStr (str);
+    }
+    /// <summary>Utility function to draw strings that contain a hotkey.</summary>
+    /// <param name="text">String to display, the hotkey specifier before a letter flags the next letter as the hotkey.</param>
+    /// <param name="hotColor">Hot color.</param>
+    /// <param name="normalColor">Normal color.</param>
+    /// <remarks>
+    ///     <para>
+    ///         The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by
+    ///         default.
+    ///     </para>
+    ///     <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
+    /// </remarks>
+    public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
+    {
+        Rune hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
+        SetAttribute (normalColor);
+
+        foreach (Rune rune in text.EnumerateRunes ())
+        {
+            if (rune == new Rune (hotkeySpec.Value))
+            {
+                SetAttribute (hotColor);
+
+                continue;
+            }
+
+            AddRune (rune);
+            SetAttribute (normalColor);
+        }
+    }
+
+    /// <summary>
+    ///     Utility function to draw strings that contains a hotkey using a <see cref="ColorScheme"/> and the "focused"
+    ///     state.
+    /// </summary>
+    /// <param name="text">String to display, the underscore before a letter flags the next letter as the hotkey.</param>
+    /// <param name="focused">
+    ///     If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise
+    ///     the regular ones.
+    /// </param>
+    public void DrawHotString (string text, bool focused)
+    {
+        if (focused)
+        {
+            DrawHotString (text, GetHotFocusColor (), GetFocusColor ());
+        }
+        else
+        {
+            DrawHotString (
+                           text,
+                           Enabled ? GetHotNormalColor () : ColorScheme!.Disabled,
+                           Enabled ? GetNormalColor () : ColorScheme!.Disabled
+                          );
+        }
+    }
+
+    /// <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;
+        }
+
+        Region prevClip = ClipViewport ();
+        Rectangle toClear = ViewportToScreen (rect);
+        Attribute prev = SetAttribute (new (color ?? GetNormalColor ().Background));
+        Driver.FillRect (toClear);
+        SetAttribute (prev);
+        SetClip (prevClip);
+    }
+
+    /// <summary>Fills the specified <see cref="Viewport"/>-relative rectangle.</summary>
+    /// <param name="rect">The Viewport-relative rectangle to clear.</param>
+    /// <param name="rune">The Rune to fill with.</param>
+    public void FillRect (Rectangle rect, Rune rune)
+    {
+        if (Driver is null)
+        {
+            return;
+        }
+
+        Region prevClip = ClipViewport ();
+        Rectangle toClear = ViewportToScreen (rect);
+        Driver.FillRect (toClear, rune);
+        SetClip (prevClip);
+    }
+
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 381 - 432
Terminal.Gui/View/View.Drawing.cs


+ 16 - 10
Terminal.Gui/View/View.Hierarchy.cs

@@ -47,8 +47,12 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
     /// </remarks>
     /// <param name="view">The view to add.</param>
     /// <returns>The view that was added.</returns>
-    public virtual View Add (View view)
+    public virtual View? Add (View? view)
     {
+        if (view is null)
+        {
+            return null;
+        }
         if (_subviews is null)
         {
             _subviews = [];
@@ -73,7 +77,6 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
 
         if (view.Enabled && !Enabled)
         {
-            view._oldEnabled = true;
             view.Enabled = false;
         }
 
@@ -85,9 +88,8 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
             view.EndInit ();
         }
 
-        CheckDimAuto ();
+        SetNeedsDraw ();
         SetNeedsLayout ();
-        SetNeedsDisplay ();
 
         return view;
     }
@@ -126,7 +128,6 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
     {
         View view = e.SubView;
         view.IsAdded = true;
-        view.OnResizeNeeded ();
         view.Added?.Invoke (this, e);
     }
 
@@ -150,8 +151,13 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
     /// <returns>
     ///     The removed View. <see langword="null"/> if the View could not be removed.
     /// </returns>
-    public virtual View? Remove (View view)
+    public virtual View? Remove (View? view)
     {
+        if (view is null)
+        {
+            return null;
+        }
+
         if (_subviews is null)
         {
             return view;
@@ -179,13 +185,13 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
         view._superView = null;
 
         SetNeedsLayout ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         foreach (View v in _subviews)
         {
             if (v.Frame.IntersectsWith (touched))
             {
-                view.SetNeedsDisplay ();
+                view.SetNeedsDraw ();
             }
         }
 
@@ -339,8 +345,8 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
         }
 
         // BUGBUG: this is odd. Why is this needed?
-        SetNeedsDisplay ();
-        subview.SetNeedsDisplay ();
+        SetNeedsDraw ();
+        subview.SetNeedsDraw ();
     }
 
     #endregion SubViewOrdering

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 389 - 346
Terminal.Gui/View/View.Layout.cs


+ 13 - 10
Terminal.Gui/View/View.Mouse.cs

@@ -664,17 +664,20 @@ public partial class View // Mouse APIs
 
             Adornment? found = null;
 
-            if (start.Margin.Contains (currentLocation))
+            if (start is not Adornment)
             {
-                found = start.Margin;
-            }
-            else if (start.Border.Contains (currentLocation))
-            {
-                found = start.Border;
-            }
-            else if (start.Padding.Contains (currentLocation))
-            {
-                found = start.Padding;
+                if (start.Margin is {} && start.Margin.Contains (currentLocation))
+                {
+                    found = start.Margin;
+                }
+                else if (start.Border is {} && start.Border.Contains (currentLocation))
+                {
+                    found = start.Border;
+                }
+                else if (start.Padding is { } && start.Padding.Contains(currentLocation))
+                {
+                    found = start.Padding;
+                }
             }
 
             Point viewportOffset = start.GetViewportOffsetFromFrame ();

+ 9 - 9
Terminal.Gui/View/View.Navigation.cs

@@ -594,7 +594,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
         // Focus work is done. Notify.
         RaiseFocusChanged (HasFocus, currentFocusedView, this);
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         // Post-conditions - prove correctness
         if (HasFocus == previousValue)
@@ -847,21 +847,21 @@ public partial class View // Focus and cross-view navigation management (TabStop
             throw new InvalidOperationException ("SetHasFocusFalse and the HasFocus value did not change.");
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
-    private void RaiseFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
+    private void RaiseFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
     {
-        if (newHasFocus && focusedVew?.Focused is null)
+        if (newHasFocus && focusedView?.Focused is null)
         {
-            Application.Navigation?.SetFocused (focusedVew);
+            Application.Navigation?.SetFocused (focusedView);
         }
 
         // Call the virtual method
-        OnHasFocusChanged (newHasFocus, previousFocusedView, focusedVew);
+        OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView);
 
         // Raise the event
-        var args = new HasFocusEventArgs (newHasFocus, newHasFocus, previousFocusedView, focusedVew);
+        var args = new HasFocusEventArgs (newHasFocus, newHasFocus, previousFocusedView, focusedView);
         HasFocusChanged?.Invoke (this, args);
     }
 
@@ -876,8 +876,8 @@ public partial class View // Focus and cross-view navigation management (TabStop
     /// </remarks>
     /// <param name="newHasFocus">The new value of <see cref="View.HasFocus"/>.</param>
     /// <param name="previousFocusedView"></param>
-    /// <param name="focusedVew">The view that is now focused. May be <see langword="null"/></param>
-    protected virtual void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew) { }
+    /// <param name="focusedView">The view that is now focused. May be <see langword="null"/></param>
+    protected virtual void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView) { }
 
     /// <summary>Raised after <see cref="HasFocus"/> has changed.</summary>
     /// <remarks>

+ 0 - 4
Terminal.Gui/View/View.ScrollBars.cs

@@ -178,19 +178,15 @@ public partial class View
         else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeX))
         {
             _horizontalScrollBar.Value.AutoHide = false;
-            _horizontalScrollBar.Value.ShowScrollIndicator = false;
         }
         else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeY))
         {
             _verticalScrollBar.Value.AutoHide = false;
-            _verticalScrollBar.Value.ShowScrollIndicator = false;
         }
         else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeLocation))
         {
             _horizontalScrollBar.Value.AutoHide = false;
-            _horizontalScrollBar.Value.ShowScrollIndicator = false;
             _verticalScrollBar.Value.AutoHide = false;
-            _verticalScrollBar.Value.ShowScrollIndicator = false;
         }
         else if (viewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth))
         {

+ 13 - 8
Terminal.Gui/View/View.Text.cs

@@ -4,21 +4,20 @@ namespace Terminal.Gui;
 
 public partial class View // Text Property APIs
 {
-    private string _text = null!;
+    private string _text = string.Empty;
 
     /// <summary>
     ///     Called when the <see cref="Text"/> has changed. Fires the <see cref="TextChanged"/> event.
     /// </summary>
     public void OnTextChanged () { TextChanged?.Invoke (this, EventArgs.Empty); }
 
-    // TODO: Make this non-virtual. Nobody overrides it.
     /// <summary>
     ///     Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
     ///     or not when <see cref="TextFormatter.WordWrap"/> is enabled.
     ///     If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when
     ///     <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
     /// </summary>
-    public virtual bool PreserveTrailingSpaces
+    public bool PreserveTrailingSpaces
     {
         get => TextFormatter.PreserveTrailingSpaces;
         set
@@ -27,6 +26,7 @@ public partial class View // Text Property APIs
             {
                 TextFormatter.PreserveTrailingSpaces = value;
                 TextFormatter.NeedsFormat = true;
+                SetNeedsLayout ();
             }
         }
     }
@@ -58,11 +58,16 @@ public partial class View // Text Property APIs
         get => _text;
         set
         {
+            if (_text == value)
+            {
+                return;
+            }
+
             string old = _text;
             _text = value;
 
             UpdateTextFormatterText ();
-            OnResizeNeeded ();
+            SetNeedsLayout ();
 #if DEBUG
             if (_text is { } && string.IsNullOrEmpty (Id))
             {
@@ -92,7 +97,7 @@ public partial class View // Text Property APIs
         {
             TextFormatter.Alignment = value;
             UpdateTextFormatterText ();
-            OnResizeNeeded ();
+            SetNeedsLayout ();
         }
     }
 
@@ -143,7 +148,7 @@ public partial class View // Text Property APIs
         set
         {
             TextFormatter.VerticalAlignment = value;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -229,9 +234,9 @@ public partial class View // Text Property APIs
         {
             TextFormatter.ConstrainToWidth = null;
             TextFormatter.ConstrainToHeight = null;
-            OnResizeNeeded ();
+            SetNeedsLayout ();
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 }

+ 24 - 23
Terminal.Gui/View/View.cs

@@ -69,12 +69,14 @@ namespace Terminal.Gui;
 ///     </para>
 ///     <para>
 ///         To flag a region of the View's <see cref="Viewport"/> to be redrawn call
-///         <see cref="SetNeedsDisplay(Rectangle)"/>
+///         <see cref="SetNeedsDraw(System.Drawing.Rectangle)"/>
 ///         .
-///         To flag the entire view for redraw call <see cref="SetNeedsDisplay()"/>.
+///         To flag the entire view for redraw call <see cref="SetNeedsDraw()"/>.
 ///     </para>
 ///     <para>
-///         The <see cref="LayoutSubviews"/> method is invoked when the size or layout of a view has changed.
+///         The <see cref="SetNeedsLayout"/> method is called when the size or layout of a view has changed. The <see cref="MainLoop"/> will
+///         cause <see cref="Layout()"/> to be called on the next <see cref="Application.Iteration"/> so there is normally no reason to direclty call
+///         see <see cref="Layout()"/>.
 ///     </para>
 ///     <para>
 ///         Views have a <see cref="ColorScheme"/> property that defines the default colors that subviews should use for
@@ -122,7 +124,7 @@ public partial class View : Responder, ISupportInitializeNotification
     ///     Points to the current driver in use by the view, it is a convenience property for simplifying the development
     ///     of new views.
     /// </summary>
-    public static ConsoleDriver Driver => Application.Driver!;
+    public static ConsoleDriver? Driver => Application.Driver;
 
     /// <summary>Initializes a new instance of <see cref="View"/>.</summary>
     /// <remarks>
@@ -143,6 +145,7 @@ public partial class View : Responder, ISupportInitializeNotification
 
         SetupText ();
 
+        SetupScrollBars ();
     }
 
     /// <summary>
@@ -229,7 +232,6 @@ public partial class View : Responder, ISupportInitializeNotification
         // These calls were moved from BeginInit as they access Viewport which is indeterminate until EndInit is called.
         UpdateTextDirection (TextDirection);
         UpdateTextFormatterText ();
-        OnResizeNeeded ();
 
         if (_subviews is { })
         {
@@ -242,6 +244,10 @@ public partial class View : Responder, ISupportInitializeNotification
             }
         }
 
+        // TODO: Figure out how to move this out of here and just depend on LayoutNeeded in Mainloop
+        Layout (); // the EventLog in AllViewsTester fails to layout correctly if this is not here (convoluted Dim.Fill(Func)).
+        SetNeedsLayout ();
+
         Initialized?.Invoke (this, EventArgs.Empty);
     }
 
@@ -251,9 +257,6 @@ public partial class View : Responder, ISupportInitializeNotification
 
     private bool _enabled = true;
 
-    // This is a cache of the Enabled property so that we can restore it when the superview is re-enabled.
-    private bool _oldEnabled;
-
     /// <summary>Gets or sets a value indicating whether this <see cref="Responder"/> can respond to user interaction.</summary>
     public bool Enabled
     {
@@ -282,7 +285,12 @@ public partial class View : Responder, ISupportInitializeNotification
             }
 
             OnEnabledChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
+
+            if (Border is { })
+            {
+                Border.Enabled = _enabled;
+            }
 
             if (_subviews is null)
             {
@@ -291,18 +299,7 @@ public partial class View : Responder, ISupportInitializeNotification
 
             foreach (View view in _subviews)
             {
-                if (!_enabled)
-                {
-                    view._oldEnabled = view.Enabled;
-                    view.Enabled = _enabled;
-                }
-                else
-                {
-                    view.Enabled = view._oldEnabled;
-#if AUTO_CANFOCUS
-                    view._addingViewSoCanFocusAlsoUpdatesSuperView = _enabled;
-#endif
-                }
+                view.Enabled = Enabled;
             }
         }
     }
@@ -363,7 +360,10 @@ public partial class View : Responder, ISupportInitializeNotification
             OnVisibleChanged ();
             VisibleChanged?.Invoke (this, EventArgs.Empty);
 
-            SetNeedsDisplay ();
+            SetNeedsLayout ();
+            SuperView?.SetNeedsLayout ();
+            SetNeedsDraw ();
+            SuperView?.SetNeedsDraw ();
         }
     }
 
@@ -469,7 +469,7 @@ public partial class View : Responder, ISupportInitializeNotification
 
                 SetTitleTextFormatterSize ();
                 SetHotKeyFromTitle ();
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
 #if DEBUG
                 if (string.IsNullOrEmpty (Id))
                 {
@@ -531,6 +531,7 @@ public partial class View : Responder, ISupportInitializeNotification
 
         DisposeKeyboard ();
         DisposeAdornments ();
+        DisposeScrollBars ();
 
         for (int i = InternalSubviews.Count - 1; i >= 0; i--)
         {

+ 1 - 1
Terminal.Gui/View/ViewportSettings.cs

@@ -94,7 +94,7 @@ public enum ViewportSettings
     ClipContentOnly = 16,
 
     /// <summary>
-    ///     If set <see cref="View.Clear()"/> will clear only the portion of the content
+    ///     If set <see cref="View.ClearViewport"/> 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.
     ///     <see cref="ClipContentOnly"/> must be set for this setting to work (clipping beyond the visible area must be

+ 67 - 51
Terminal.Gui/Views/Bar.cs

@@ -31,7 +31,7 @@ public class Bar : View, IOrientation, IDesignable
         _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
         _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
 
-        Initialized += Bar_Initialized;
+        // Initialized += Bar_Initialized;
         MouseEvent += OnMouseEvent;
 
         if (shortcuts is null)
@@ -77,17 +77,21 @@ public class Bar : View, IOrientation, IDesignable
         }
     }
 
-    private void Bar_Initialized (object? sender, EventArgs e)
+    /// <inheritdoc />
+    public override void EndInit ()
     {
+        base.EndInit ();
         ColorScheme = Colors.ColorSchemes ["Menu"];
-        LayoutBarItems (GetContentSize ());
     }
 
     /// <inheritdoc/>
     public override void SetBorderStyle (LineStyle value)
     {
-        // The default changes the thickness. We don't want that. We just set the style.
-        Border.LineStyle = value;
+        if (Border is { })
+        {
+            // The default changes the thickness. We don't want that. We just set the style.
+            Border.LineStyle = value;
+        }
     }
 
     #region IOrientation members
@@ -119,7 +123,8 @@ public class Bar : View, IOrientation, IDesignable
     /// <param name="newOrientation"></param>
     public void OnOrientationChanged (Orientation newOrientation)
     {
-        SetNeedsLayout ();
+        // BUGBUG: this should not be SuperView.GetContentSize
+        LayoutBarItems (SuperView?.GetContentSize () ?? Application.Screen.Size);
     }
     #endregion
 
@@ -135,6 +140,7 @@ public class Bar : View, IOrientation, IDesignable
         set
         {
             _alignmentModes = value;
+            //SetNeedsDraw ();
             SetNeedsLayout ();
         }
     }
@@ -162,7 +168,8 @@ public class Bar : View, IOrientation, IDesignable
             }
         }
 
-        SetNeedsDisplay ();
+        //SetNeedsDraw ();
+        SetNeedsLayout ();
     }
 
     // TODO: Move this to View
@@ -185,17 +192,16 @@ public class Bar : View, IOrientation, IDesignable
         if (toRemove is { })
         {
             Remove (toRemove);
-            SetNeedsDisplay ();
+            //SetNeedsDraw ();
+            SetNeedsLayout ();
         }
 
         return toRemove as Shortcut;
     }
 
     /// <inheritdoc />
-    internal override void OnLayoutStarted (LayoutEventArgs args)
+    protected override void OnSubviewLayout (LayoutEventArgs args)
     {
-        base.OnLayoutStarted (args);
-
         LayoutBarItems (args.OldContentSize);
     }
 
@@ -217,62 +223,72 @@ public class Bar : View, IOrientation, IDesignable
                 break;
 
             case Orientation.Vertical:
-                // Set the overall size of the Bar and arrange the views vertically
-
-                var minKeyWidth = 0;
-
-                List<Shortcut> shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast<Shortcut> ().ToList ();
-                foreach (Shortcut shortcut in shortcuts)
+                if (Width!.Has<DimAuto> (out _))
                 {
-                    // Let DimAuto do its thing to get the minimum width of each CommandView and HelpView
-                    //shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue));
-                    minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ());
-                }
+                    // Set the overall size of the Bar and arrange the views vertically
 
-                var maxBarItemWidth = 0;
-                var totalHeight = 0;
-
-                for (var index = 0; index < Subviews.Count; index++)
-                {
-                    View barItem = Subviews [index];
+                    var minKeyWidth = 0;
 
-                    barItem.ColorScheme = ColorScheme;
+                    List<Shortcut> shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast<Shortcut> ().ToList ();
 
-                    if (!barItem.Visible)
+                    foreach (Shortcut shortcut in shortcuts)
                     {
-                        continue;
+                        // Get the largest width of all KeyView's
+                        minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ());
                     }
 
-                    if (barItem is Shortcut scBarItem)
-                    {
-                        scBarItem.MinimumKeyTextSize = minKeyWidth;
-                        maxBarItemWidth = Math.Max (maxBarItemWidth, scBarItem.Frame.Width);
-                    }
+                    var _maxBarItemWidth = 0;
 
-                    if (prevBarItem == null)
+                    for (var index = 0; index < Subviews.Count; index++)
                     {
-                        barItem.Y = 0;
+                        View barItem = Subviews [index];
+
+                        barItem.X = 0;
+
+                        barItem.ColorScheme = ColorScheme;
+
+                        if (!barItem.Visible)
+                        {
+                            continue;
+                        }
+
+                        if (barItem is Shortcut scBarItem)
+                        {
+                            scBarItem.MinimumKeyTextSize = minKeyWidth;
+                            scBarItem.Width = scBarItem.GetWidthDimAuto ();
+                            barItem.Layout (Application.Screen.Size);
+                            _maxBarItemWidth = Math.Max (_maxBarItemWidth, barItem.Frame.Width);
+                        }
+
+                        if (prevBarItem == null)
+                        {
+                            // TODO: Just use Pos.Align!
+                            barItem.Y = 0;
+                        }
+                        else
+                        {
+                            // TODO: Just use Pos.Align!
+                            // Align the view to the bottom of the previous view
+                            barItem.Y = Pos.Bottom (prevBarItem);
+                        }
+
+                        prevBarItem = barItem;
+
                     }
-                    else
+
+                    foreach (var subView in Subviews)
                     {
-                        // Align the view to the bottom of the previous view
-                        barItem.Y = Pos.Bottom (prevBarItem);
+                        subView.Width = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: _maxBarItemWidth);
                     }
-
-                    prevBarItem = barItem;
-
-                    barItem.X = 0;
-                    totalHeight += barItem.Frame.Height;
                 }
-
-
-                foreach (View barItem in Subviews)
+                else
                 {
-                    barItem.Width = maxBarItemWidth;
+                    foreach (var subView in Subviews)
+                    {
+                        subView.Width = Dim.Fill();
+                    }
                 }
 
-                Height = Dim.Auto (DimAutoStyle.Content, totalHeight);
-
                 break;
         }
     }

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

@@ -191,7 +191,7 @@ public class Button : View, IDesignable
             _isDefault = value;
 
             UpdateTextFormatterText ();
-            OnResizeNeeded ();
+            SetNeedsLayout ();
         }
     }
 

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

@@ -153,7 +153,7 @@ public class CheckBox : View
 
         _checkedState = value;
         UpdateTextFormatterText ();
-        OnResizeNeeded ();
+        SetNeedsLayout ();
 
         EventArgs<CheckState> args = new (in _checkedState);
         OnCheckedStateChanged (args);

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

@@ -78,30 +78,30 @@ internal abstract class ColorBar : View, IColorBar
     void IColorBar.SetValueWithoutRaisingEvent (int v)
     {
         _value = v;
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        base.OnDrawContent (viewport);
-
         var xOffset = 0;
 
         if (!string.IsNullOrWhiteSpace (Text))
         {
             Move (0, 0);
-            Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
-            Driver.AddStr (Text);
+            SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
+            Driver?.AddStr (Text);
 
             // TODO: is there a better method than this? this is what it is in TableView
             xOffset = Text.EnumerateRunes ().Sum (c => c.GetColumns ());
         }
 
-        _barWidth = viewport.Width - xOffset;
+        _barWidth = Viewport.Width - xOffset;
         _barStartsAt = xOffset;
 
         DrawBar (xOffset, 0, _barWidth);
+
+        return true;
     }
 
     /// <summary>
@@ -198,7 +198,7 @@ internal abstract class ColorBar : View, IColorBar
             if (isSelectedCell)
             {
                 // Draw the triangle at the closest position
-                Application.Driver?.SetAttribute (new (triangleColor, color));
+                SetAttribute (new (triangleColor, color));
                 AddRune (x + xOffset, yOffset, new ('▲'));
 
                 // Record for tests
@@ -206,7 +206,7 @@ internal abstract class ColorBar : View, IColorBar
             }
             else
             {
-                Application.Driver?.SetAttribute (new (color, color));
+                SetAttribute (new (color, color));
                 AddRune (x + xOffset, yOffset, new ('█'));
             }
         }
@@ -215,7 +215,7 @@ internal abstract class ColorBar : View, IColorBar
     private void OnValueChanged ()
     {
         ValueChanged?.Invoke (this, new (in _value));
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     private bool? SetMax ()

+ 46 - 30
Terminal.Gui/Views/ColorPicker.16.cs

@@ -67,8 +67,12 @@ public class ColorPicker16 : View
 
     /// <summary>Moves the selected item index to the next row.</summary>
     /// <returns></returns>
-    public virtual bool MoveDown ()
+    private bool MoveDown (CommandContext ctx)
     {
+        if (RaiseSelecting (ctx) == true)
+        {
+            return true;
+        }
         if (Cursor.Y < _rows - 1)
         {
             SelectedColor += _cols;
@@ -79,8 +83,13 @@ public class ColorPicker16 : View
 
     /// <summary>Moves the selected item index to the previous column.</summary>
     /// <returns></returns>
-    public virtual bool MoveLeft ()
+    private bool MoveLeft (CommandContext ctx)
     {
+        if (RaiseSelecting (ctx) == true)
+        {
+            return true;
+        }
+
         if (Cursor.X > 0)
         {
             SelectedColor--;
@@ -91,8 +100,12 @@ public class ColorPicker16 : View
 
     /// <summary>Moves the selected item index to the next column.</summary>
     /// <returns></returns>
-    public virtual bool MoveRight ()
+    private bool MoveRight (CommandContext ctx)
     {
+        if (RaiseSelecting (ctx) == true)
+        {
+            return true;
+        }
         if (Cursor.X < _cols - 1)
         {
             SelectedColor++;
@@ -103,8 +116,12 @@ public class ColorPicker16 : View
 
     /// <summary>Moves the selected item index to the previous row.</summary>
     /// <returns></returns>
-    public virtual bool MoveUp ()
+    private bool MoveUp (CommandContext ctx)
     {
+        if (RaiseSelecting (ctx) == true)
+        {
+            return true;
+        }
         if (Cursor.Y > 0)
         {
             SelectedColor -= _cols;
@@ -114,16 +131,14 @@ public class ColorPicker16 : View
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        base.OnDrawContent (viewport);
-
-        Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
+        SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
         var colorIndex = 0;
 
-        for (var y = 0; y < Math.Max (2, viewport.Height / BoxHeight); y++)
+        for (var y = 0; y < Math.Max (2, Viewport.Height / BoxHeight); y++)
         {
-            for (var x = 0; x < Math.Max (8, viewport.Width / BoxWidth); x++)
+            for (var x = 0; x < Math.Max (8, Viewport.Width / BoxWidth); x++)
             {
                 int foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
 
@@ -132,12 +147,14 @@ public class ColorPicker16 : View
                     continue;
                 }
 
-                Driver.SetAttribute (new ((ColorName16)foregroundColorIndex, (ColorName16)colorIndex));
+                SetAttribute (new ((ColorName16)foregroundColorIndex, (ColorName16)colorIndex));
                 bool selected = x == Cursor.X && y == Cursor.Y;
                 DrawColorBox (x, y, selected);
                 colorIndex++;
             }
         }
+
+        return true;
     }
 
     /// <summary>Selected color.</summary>
@@ -157,20 +174,31 @@ public class ColorPicker16 : View
                                   this,
                                   new (value)
                                  );
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
     /// <summary>Add the commands.</summary>
     private void AddCommands ()
     {
-        AddCommand (Command.Left, () => MoveLeft ());
-        AddCommand (Command.Right, () => MoveRight ());
-        AddCommand (Command.Up, () => MoveUp ());
-        AddCommand (Command.Down, () => MoveDown ());
+        AddCommand (Command.Left, (ctx) => MoveLeft (ctx));
+        AddCommand (Command.Right, (ctx) => MoveRight (ctx));
+        AddCommand (Command.Up, (ctx) => MoveUp (ctx));
+        AddCommand (Command.Down, (ctx) => MoveDown (ctx));
+
+        AddCommand (Command.Select, (ctx) =>
+                                    {
+                                        bool set = false;
+                                        if (ctx.Data is MouseEventArgs me)
+                                        {
+                                            Cursor = new (me.Position.X / _boxWidth, me.Position.Y / _boxHeight);
+                                            set = true;
+                                        }
+                                        return RaiseAccepting (ctx) == true || set;
+                                    });
     }
 
-    /// <summary>Add the KeyBindinds.</summary>
+    /// <summary>Add the KeyBindings.</summary>
     private void AddKeyBindings ()
     {
         KeyBindings.Add (Key.CursorLeft, Command.Left);
@@ -181,15 +209,6 @@ public class ColorPicker16 : View
 
     // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
 
-    private void ColorPicker_MouseClick (object sender, MouseEventArgs me)
-    {
-        // if (CanFocus)
-        {
-            Cursor = new (me.Position.X / _boxWidth, me.Position.Y / _boxHeight);
-            SetFocus ();
-            me.Handled = true;
-        }
-    }
 
     /// <summary>Draw a box for one color.</summary>
     /// <param name="x">X location.</param>
@@ -203,8 +222,7 @@ public class ColorPicker16 : View
         {
             for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++)
             {
-                Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY);
-                Driver.AddRune ((Rune)' ');
+                AddRune (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY, (Rune)' ');
                 index++;
             }
         }
@@ -265,7 +283,5 @@ public class ColorPicker16 : View
         Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
         Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
         SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
-
-        MouseClick += ColorPicker_MouseClick;
     }
 }

+ 7 - 7
Terminal.Gui/Views/ColorPicker.cs

@@ -90,10 +90,7 @@ public partial class ColorPicker : View
         CreateTextField ();
         SelectedColor = oldValue;
 
-        if (IsInitialized)
-        {
-            LayoutSubviews ();
-        }
+        SetNeedsLayout ();
     }
 
     /// <summary>
@@ -102,13 +99,14 @@ public partial class ColorPicker : View
     public event EventHandler<ColorEventArgs>? ColorChanged;
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        base.OnDrawContent (viewport);
         Attribute normal = GetNormalColor ();
-        Driver.SetAttribute (new (SelectedColor, normal.Background));
+        SetAttribute (new (SelectedColor, normal.Background));
         int y = _bars.Count + (Style.ShowColorName ? 1 : 0);
         AddRune (13, y, (Rune)'■');
+
+        return true;
     }
 
     /// <summary>
@@ -275,6 +273,8 @@ public partial class ColorPicker : View
         {
             _tfHex.Text = colorHex;
         }
+
+        SetNeedsLayout ();
     }
 
     private void UpdateSingleBarValueFromTextField (object? sender, HasFocusEventArgs e)

+ 31 - 24
Terminal.Gui/Views/ComboBox.cs

@@ -57,7 +57,7 @@ public class ComboBox : View, IDesignable
         Initialized += (s, e) => ProcessLayout ();
 
         // On resize
-        LayoutComplete += (sender, a) => ProcessLayout ();
+        SubviewsLaidOut += (sender, a) => ProcessLayout ();
 
         Added += (s, e) =>
                  {
@@ -73,7 +73,7 @@ public class ComboBox : View, IDesignable
                      }
 
                      SetNeedsLayout ();
-                     SetNeedsDisplay ();
+                     SetNeedsDraw ();
                      ShowHideList (Text);
                  };
 
@@ -118,7 +118,7 @@ public class ComboBox : View, IDesignable
         {
             _listview.ColorScheme = value;
             base.ColorScheme = value;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -198,7 +198,7 @@ public class ComboBox : View, IDesignable
             if (SuperView is { } && SuperView.Subviews.Contains (this))
             {
                 Text = string.Empty;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
         }
     }
@@ -294,18 +294,21 @@ public class ComboBox : View, IDesignable
     public virtual void OnCollapsed () { Collapsed?.Invoke (this, EventArgs.Empty); }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        base.OnDrawContent (viewport);
 
         if (!_autoHide)
         {
-            return;
+            return true;
+        }
+
+        if (ColorScheme != null)
+        {
+            SetAttribute (ColorScheme.Focus);
         }
+        AddRune (Viewport.Right - 1, 0, Glyphs.DownArrow);
 
-        Driver.SetAttribute (ColorScheme.Focus);
-        Move (Viewport.Right - 1, 0);
-        Driver.AddRune (Glyphs.DownArrow);
+        return true;
     }
 
 
@@ -504,11 +507,13 @@ public class ComboBox : View, IDesignable
         }
 
         Reset (true);
-        _listview.Clear ();
+        _listview.ClearViewport ();
         _listview.TabStop = TabBehavior.NoStop;
         SuperView?.MoveSubviewToStart (this);
+
+        // BUGBUG: SetNeedsDraw takes Viewport relative coordinates, not Screen
         Rectangle rect = _listview.ViewportToScreen (_listview.IsInitialized ? _listview.Viewport : Rectangle.Empty);
-        SuperView?.SetNeedsDisplay (rect);
+        SuperView?.SetNeedsDraw (rect);
         OnCollapsed ();
     }
 
@@ -803,7 +808,7 @@ public class ComboBox : View, IDesignable
         _listview.SetSource (_searchSet);
         _listview.ResumeSuspendCollectionChangedEvent ();
 
-        _listview.Clear ();
+        _listview.ClearViewport ();
         _listview.Height = CalculateHeight ();
         SuperView?.MoveSubviewToStart (this);
     }
@@ -872,7 +877,7 @@ public class ComboBox : View, IDesignable
                 if (isMousePositionValid)
                 {
                     _highlighted = Math.Min (TopItem + me.Position.Y, Source.Count);
-                    SetNeedsDisplay ();
+                    SetNeedsDraw ();
                 }
 
                 _isFocusing = false;
@@ -883,10 +888,10 @@ public class ComboBox : View, IDesignable
             return res;
         }
 
-        public override void OnDrawContent (Rectangle viewport)
+        protected override bool OnDrawingContent ()
         {
-            Attribute current = ColorScheme.Focus;
-            Driver.SetAttribute (current);
+            Attribute current = ColorScheme?.Focus ?? Attribute.Default;
+            SetAttribute (current);
             Move (0, 0);
             Rectangle f = Frame;
             int item = TopItem;
@@ -916,7 +921,7 @@ public class ComboBox : View, IDesignable
 
                 if (newcolor != current)
                 {
-                    Driver.SetAttribute (newcolor);
+                    SetAttribute (newcolor);
                     current = newcolor;
                 }
 
@@ -926,7 +931,7 @@ public class ComboBox : View, IDesignable
                 {
                     for (var c = 0; c < f.Width; c++)
                     {
-                        Driver.AddRune ((Rune)' ');
+                        AddRune (0, row, (Rune)' ');
                     }
                 }
                 else
@@ -937,24 +942,26 @@ public class ComboBox : View, IDesignable
                     if (rowEventArgs.RowAttribute is { } && current != rowEventArgs.RowAttribute)
                     {
                         current = (Attribute)rowEventArgs.RowAttribute;
-                        Driver.SetAttribute (current);
+                        SetAttribute (current);
                     }
 
                     if (AllowsMarking)
                     {
-                        Driver.AddRune (
+                        AddRune (
                                         Source.IsMarked (item) ? AllowsMultipleSelection ? Glyphs.CheckStateChecked : Glyphs.Selected :
                                         AllowsMultipleSelection ? Glyphs.CheckStateUnChecked : Glyphs.UnSelected
                                        );
-                        Driver.AddRune ((Rune)' ');
+                        AddRune ((Rune)' ');
                     }
 
-                    Source.Render (this, Driver, isSelected, item, col, row, f.Width - col, start);
+                    Source.Render (this, isSelected, item, col, row, f.Width - col, start);
                 }
             }
+
+            return true;
         }
 
-        protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew)
+        protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedView)
         {
             if (newHasFocus)
             {

+ 6 - 2
Terminal.Gui/Views/DatePicker.cs

@@ -200,8 +200,9 @@ public class DatePicker : View
                 ShowHeaders = true,
                 ShowHorizontalBottomline = true,
                 ShowVerticalCellLines = true,
-                ExpandLastColumn = true
-            }
+                ExpandLastColumn = true,
+            },
+            MultiSelect = false
         };
 
         _dateField = new DateField (DateTime.Now)
@@ -286,6 +287,9 @@ public class DatePicker : View
         Add (_dateLabel, _dateField, _calendar, _previousMonthButton, _nextMonthButton);
     }
 
+    /// <inheritdoc />
+    protected override bool OnDrawingText () { return true; }
+
     private static string StandardizeDateFormat (string format)
     {
         return format switch

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

@@ -89,13 +89,13 @@ public class Dialog : Window
     /// <inheritdoc />
     public override Attribute GetNormalColor ()
     {
-        return ColorScheme.Normal;
+        return ColorScheme!.Normal;
     }
 
     /// <inheritdoc />
     public override Attribute GetFocusColor ()
     {
-        return ColorScheme.Normal;
+        return ColorScheme!.Normal;
     }
 
     private bool _canceled;

+ 50 - 23
Terminal.Gui/Views/FileDialog.cs

@@ -8,7 +8,7 @@ namespace Terminal.Gui;
 ///     Modal dialog for selecting files/directories. Has auto-complete and expandable navigation pane (Recent, Root
 ///     drives etc).
 /// </summary>
-public class FileDialog : Dialog
+public class FileDialog : Dialog, IDesignable
 {
     private const int alignmentGroupInput = 32;
     private const int alignmentGroupComplete = 55;
@@ -78,20 +78,34 @@ public class FileDialog : Dialog
             Y = Pos.AnchorEnd (),
             IsDefault = true, Text = Style.OkButtonText
         };
-        _btnOk.Accepting += (s, e) => Accept (true);
+        _btnOk.Accepting += (s, e) =>
+                            {
+                                if (e.Cancel)
+                                {
+                                    return;
+                                }
+
+                                Accept (true);
+                            };
 
 
         _btnCancel = new Button
         {
             X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, alignmentGroupComplete),
-            Y = Pos.AnchorEnd(),
+            Y = Pos.AnchorEnd (),
             Text = Strings.btnCancel
         };
 
         _btnCancel.Accepting += (s, e) =>
         {
-            Canceled = true;
-            Application.RequestStop ();
+            if (e.Cancel)
+            {
+                return;
+            }
+            if (Modal)
+            {
+                Application.RequestStop ();
+            }
         };
 
         _btnUp = new Button { X = 0, Y = 1, NoPadding = true };
@@ -163,7 +177,7 @@ public class FileDialog : Dialog
         ColumnStyle typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3);
         typeStyle.MinWidth = 6;
         typeStyle.ColorGetter = ColorGetter;
-        
+
         _treeView = new TreeView<IFileSystemInfo> { Width = Dim.Fill (), Height = Dim.Fill () };
 
         var fileDialogTreeBuilder = new FileSystemTreeBuilder ();
@@ -189,12 +203,12 @@ public class FileDialog : Dialog
                                                   bool newState = !tile.ContentView.Visible;
                                                   tile.ContentView.Visible = newState;
                                                   _btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState);
-                                                  LayoutSubviews ();
+                                                  SetNeedsLayout ();
                                               };
 
         _tbFind = new TextField
         {
-            X = Pos.Align (Alignment.Start,AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput),
+            X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput),
             CaptionColor = new Color (Color.Black),
             Width = 30,
             Y = Pos.Top (_btnToggleSplitterCollapse),
@@ -240,7 +254,7 @@ public class FileDialog : Dialog
         _tableView.KeyBindings.ReplaceCommands (Key.End, Command.End);
         _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.StartExtend);
         _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.EndExtend);
-        
+
         AllowsMultipleSelection = false;
 
         UpdateNavigationVisibility ();
@@ -254,8 +268,8 @@ public class FileDialog : Dialog
         Add (_tbFind);
         Add (_spinnerView);
 
-        Add(_btnOk);
-        Add(_btnCancel);
+        Add (_btnOk);
+        Add (_btnCancel);
     }
 
     /// <summary>
@@ -368,10 +382,8 @@ public class FileDialog : Dialog
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        base.OnDrawContent (viewport);
-
         if (!string.IsNullOrWhiteSpace (_feedback))
         {
             int feedbackWidth = _feedback.EnumerateRunes ().Sum (c => c.GetColumns ());
@@ -386,11 +398,13 @@ public class FileDialog : Dialog
 
             Move (0, Viewport.Height / 2);
 
-            Driver.SetAttribute (new Attribute (Color.Red, ColorScheme.Normal.Background));
+            SetAttribute (new Attribute (Color.Red, ColorScheme.Normal.Background));
             Driver.AddStr (new string (' ', feedbackPadLeft));
             Driver.AddStr (_feedback);
             Driver.AddStr (new string (' ', feedbackPadRight));
         }
+
+        return true;
     }
 
     /// <inheritdoc/>
@@ -461,7 +475,7 @@ public class FileDialog : Dialog
             AllowedTypeMenuClicked (0);
 
             // TODO: Using v1's menu bar here is a hack. Need to upgrade this.
-            _allowedTypeMenuBar.DrawContentComplete += (s, e) =>
+            _allowedTypeMenuBar.DrawingContent += (s, e) =>
                                                        {
                                                            _allowedTypeMenuBar.Move (e.NewViewport.Width - 1, 0);
                                                            Driver.AddRune (Glyphs.DownArrow);
@@ -493,7 +507,9 @@ public class FileDialog : Dialog
             _btnOk.X = Pos.Right (_btnCancel) + 1;
             MoveSubviewTowardsStart (_btnCancel);
         }
-        LayoutSubviews ();
+
+        SetNeedsDraw ();
+        SetNeedsLayout ();
     }
 
     /// <inheritdoc/>
@@ -634,7 +650,7 @@ public class FileDialog : Dialog
         if (!IsCompatibleWithOpenMode (f.FullName, out string reason))
         {
             _feedback = reason;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return;
         }
@@ -661,7 +677,7 @@ public class FileDialog : Dialog
             if (reason is { })
             {
                 _feedback = reason;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             return;
@@ -827,7 +843,11 @@ public class FileDialog : Dialog
         }
 
         Canceled = false;
-        Application.RequestStop ();
+
+        if (Modal)
+        {
+            Application.RequestStop ();
+        }
     }
 
     private string GetBackButtonText () { return Glyphs.LeftArrow + "-"; }
@@ -1115,7 +1135,7 @@ public class FileDialog : Dialog
             _tableView.RowOffset = 0;
             _tableView.SelectedRow = 0;
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
             UpdateNavigationVisibility ();
         }
         finally
@@ -1400,7 +1420,7 @@ public class FileDialog : Dialog
         if (reason is { })
         {
             _feedback = reason;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return false;
@@ -1588,9 +1608,16 @@ public class FileDialog : Dialog
                                     Parent.WriteStateToTableView ();
 
                                     Parent._spinnerView.Visible = true;
-                                    Parent._spinnerView.SetNeedsDisplay ();
+                                    Parent._spinnerView.SetNeedsDraw ();
                                 }
                                );
         }
     }
+
+    bool IDesignable.EnableForDesign ()
+    {
+        Modal = false;
+        OnLoaded ();
+        return true;
+    }
 }

+ 15 - 4
Terminal.Gui/Views/GraphView/Annotations.cs

@@ -127,6 +127,17 @@ public class LegendAnnotation : View, IAnnotation
     /// <summary>Returns false i.e. Legends render after series</summary>
     public bool BeforeSeries => false;
 
+    // BUGBUG: Legend annotations are subviews. But for some reason the are rendered directly in OnDrawContent 
+    // BUGBUG: instead of just being normal subviews. They get rendered as blank rects and thus we disable subview drawing.
+    /// <inheritdoc />
+    protected override bool OnDrawingText () { return true; }
+
+    // BUGBUG: Legend annotations are subviews. But for some reason the are rendered directly in OnDrawContent 
+    // BUGBUG: instead of just being normal subviews. They get rendered as blank rects and thus we disable subview drawing.
+    /// <inheritdoc />
+    protected override bool OnClearingViewport () { return true; }
+
+
     /// <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)
@@ -139,8 +150,8 @@ public class LegendAnnotation : View, IAnnotation
 
         if (BorderStyle != LineStyle.None)
         {
-            OnDrawAdornments ();
-            OnRenderLineCanvas ();
+            DrawBorderAndPadding ();
+            RenderLineCanvas ();
         }
 
         var linesDrawn = 0;
@@ -149,7 +160,7 @@ public class LegendAnnotation : View, IAnnotation
         {
             if (entry.Item1.Color.HasValue)
             {
-                Application.Driver?.SetAttribute (entry.Item1.Color.Value);
+                SetAttribute (entry.Item1.Color.Value);
             }
             else
             {
@@ -206,7 +217,7 @@ public class PathAnnotation : IAnnotation
     /// <param name="graph"></param>
     public void Render (GraphView graph)
     {
-        View.Driver.SetAttribute (LineColor ?? graph.ColorScheme.Normal);
+        graph.SetAttribute (LineColor ?? graph.ColorScheme.Normal);
 
         foreach (LineF line in PointsToLines ())
         {

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

@@ -2,7 +2,7 @@
 namespace Terminal.Gui;
 
 /// <summary>View for rendering graphs (bar, scatter, etc...).</summary>
-public class GraphView : View
+public class GraphView : View, IDesignable
 {
     /// <summary>Creates a new graph with a 1 to 1 graph space with absolute layout.</summary>
     public GraphView ()
@@ -197,7 +197,7 @@ public class GraphView : View
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
         if (CellSize.X == 0 || CellSize.Y == 0)
         {
@@ -212,13 +212,13 @@ public class GraphView : View
         for (var i = 0; i < Viewport.Height; i++)
         {
             Move (0, i);
-            Driver.AddStr (new string (' ', Viewport.Width));
+            Driver?.AddStr (new string (' ', Viewport.Width));
         }
 
         // If there is no data do not display a graph
         if (!Series.Any () && !Annotations.Any ())
         {
-            return;
+            return true;
         }
 
         // The drawable area of the graph (anything that isn't in the margins)
@@ -228,7 +228,7 @@ public class GraphView : View
         // if the margins take up the full draw bounds don't render
         if (graphScreenWidth < 0 || graphScreenHeight < 0)
         {
-            return;
+            return true;
         }
 
         // Draw 'before' annotations
@@ -275,6 +275,7 @@ public class GraphView : View
         {
             a.Render (this);
         }
+        return true;
     }
 
     /// <summary>Scrolls the graph down 1 page.</summary>
@@ -296,7 +297,7 @@ public class GraphView : View
         Series.Clear ();
         Annotations.Clear ();
         GraphColor = null;
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Returns the section of the graph that is represented by the given screen position.</summary>
@@ -337,12 +338,56 @@ public class GraphView : View
                             ScrollOffset.Y + offsetY
                            );
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
     ///     Sets the color attribute of <see cref="Application.Driver"/> to the <see cref="GraphColor"/> (if defined) or
     ///     <see cref="ColorScheme"/> otherwise.
     /// </summary>
-    public void SetDriverColorToGraphColor () { Driver.SetAttribute (GraphColor ?? GetNormalColor ()); }
+    public void SetDriverColorToGraphColor () { SetAttribute (GraphColor ?? GetNormalColor ()); }
+
+    bool IDesignable.EnableForDesign ()
+    {
+        Title = "Sine Wave";
+
+        var points = new ScatterSeries ();
+        var line = new PathAnnotation ();
+
+        // Draw line first so it does not draw over top of points or axis labels
+        line.BeforeSeries = true;
+
+        // Generate line graph with 2,000 points
+        for (float x = -500; x < 500; x += 0.5f)
+        {
+            points.Points.Add (new (x, (float)Math.Sin (x)));
+            line.Points.Add (new (x, (float)Math.Sin (x)));
+        }
+
+        Series.Add (points);
+        Annotations.Add (line);
+
+        // How much graph space each cell of the console depicts
+        CellSize = new (0.1f, 0.1f);
+
+        // leave space for axis labels
+        MarginBottom = 2;
+        MarginLeft = 3;
+
+        // One axis tick/label per
+        AxisX.Increment = 0.5f;
+        AxisX.ShowLabelsEvery = 2;
+        AxisX.Text = "X →";
+        AxisX.LabelGetter = v => v.Value.ToString ("N2");
+
+        AxisY.Increment = 0.2f;
+        AxisY.ShowLabelsEvery = 2;
+        AxisY.Text = "↑Y";
+        AxisY.LabelGetter = v => v.Value.ToString ("N2");
+
+        ScrollOffset = new (-2.5f, -1);
+
+        return true;
+    }
+
 }

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

@@ -33,7 +33,7 @@ public class ScatterSeries : ISeries
     {
         if (Fill.Color.HasValue)
         {
-            Application.Driver?.SetAttribute (Fill.Color.Value);
+            graph.SetAttribute (Fill.Color.Value);
         }
 
         foreach (PointF p in Points.Where (p => graphBounds.Contains (p)))
@@ -261,7 +261,7 @@ public class BarSeries : ISeries
 
         if (adjusted.Color.HasValue)
         {
-            Application.Driver?.SetAttribute (adjusted.Color.Value);
+            graph.SetAttribute (adjusted.Color.Value);
         }
 
         graph.DrawLine (start, end, adjusted.Rune);

+ 42 - 31
Terminal.Gui/Views/HexView.cs

@@ -104,7 +104,7 @@ public class HexView : View, IDesignable
         KeyBindings.Remove (Key.Space);
         KeyBindings.Remove (Key.Enter);
 
-        LayoutComplete += HexView_LayoutComplete;
+        SubviewsLaidOut += HexView_LayoutComplete;
     }
 
     /// <summary>Initializes a <see cref="HexView"/> class.</summary>
@@ -198,7 +198,8 @@ public class HexView : View, IDesignable
                 Address = 0;
             }
 
-            SetNeedsDisplay ();
+            SetNeedsLayout ();
+            SetNeedsDraw ();
         }
     }
 
@@ -270,7 +271,8 @@ public class HexView : View, IDesignable
             }
 
             _addressWidth = value;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
+            SetNeedsLayout ();
         }
     }
 
@@ -291,7 +293,7 @@ public class HexView : View, IDesignable
             _displayStart = value;
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
@@ -317,7 +319,7 @@ public class HexView : View, IDesignable
         }
 
         _edits = new ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
@@ -413,35 +415,35 @@ public class HexView : View, IDesignable
             }
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         return true;
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
         if (Source is null)
         {
-            return;
+            return true;
         }
 
-        Attribute currentAttribute;
+        Attribute currentAttribute = Attribute.Default;
         Attribute current = GetFocusColor ();
-        Driver.SetAttribute (current);
+        SetAttribute (current);
         Move (0, 0);
 
         int nBlocks = BytesPerLine / NUM_BYTES_PER_HEX_COLUMN;
-        var data = new byte [nBlocks * NUM_BYTES_PER_HEX_COLUMN * viewport.Height];
+        var data = new byte [nBlocks * NUM_BYTES_PER_HEX_COLUMN * Viewport.Height];
         Source.Position = _displayStart;
         int n = _source!.Read (data, 0, data.Length);
 
         Attribute selectedAttribute = GetHotNormalColor ();
         Attribute editedAttribute = new Attribute (GetNormalColor ().Foreground.GetHighlightColor (), GetNormalColor ().Background);
         Attribute editingAttribute = new Attribute (GetFocusColor ().Background, GetFocusColor ().Foreground);
-        for (var line = 0; line < viewport.Height; line++)
+        for (var line = 0; line < Viewport.Height; line++)
         {
-            Rectangle lineRect = new (0, line, viewport.Width, 1);
+            Rectangle lineRect = new (0, line, Viewport.Width, 1);
 
             if (!Viewport.Contains (lineRect))
             {
@@ -450,13 +452,13 @@ public class HexView : View, IDesignable
 
             Move (0, line);
             currentAttribute = new Attribute (GetNormalColor ().Foreground.GetHighlightColor (), GetNormalColor ().Background);
-            Driver.SetAttribute (currentAttribute);
+            SetAttribute (currentAttribute);
             var address = $"{_displayStart + line * nBlocks * NUM_BYTES_PER_HEX_COLUMN:x8}";
-            Driver.AddStr ($"{address.Substring (8 - AddressWidth)}");
+            Driver?.AddStr ($"{address.Substring (8 - AddressWidth)}");
 
             if (AddressWidth > 0)
             {
-                Driver.AddStr (" ");
+                Driver?.AddStr (" ");
             }
 
             SetAttribute (GetNormalColor ());
@@ -478,12 +480,12 @@ public class HexView : View, IDesignable
                         SetAttribute (edited ? editedAttribute : GetNormalColor ());
                     }
 
-                    Driver.AddStr (offset >= n && !edited ? "  " : $"{value:x2}");
+                    Driver?.AddStr (offset >= n && !edited ? "  " : $"{value:x2}");
                     SetAttribute (GetNormalColor ());
-                    Driver.AddRune (_spaceCharRune);
+                    Driver?.AddRune (_spaceCharRune);
                 }
 
-                Driver.AddStr (block + 1 == nBlocks ? " " : $"{_columnSeparatorRune} ");
+                Driver?.AddStr (block + 1 == nBlocks ? " " : $"{_columnSeparatorRune} ");
             }
 
             for (var byteIndex = 0; byteIndex < nBlocks * NUM_BYTES_PER_HEX_COLUMN; byteIndex++)
@@ -536,22 +538,24 @@ public class HexView : View, IDesignable
                     SetAttribute (edited ? editedAttribute : GetNormalColor ());
                 }
 
-                Driver.AddRune (c);
+                Driver?.AddRune (c);
 
                 for (var i = 1; i < utf8BytesConsumed; i++)
                 {
                     byteIndex++;
-                    Driver.AddRune (_periodCharRune);
+                    Driver?.AddRune (_periodCharRune);
                 }
             }
         }
 
+        return true;
+
         void SetAttribute (Attribute attribute)
         {
             if (currentAttribute != attribute)
             {
                 currentAttribute = attribute;
-                Driver.SetAttribute (attribute);
+                SetAttribute (attribute);
             }
         }
     }
@@ -577,6 +581,8 @@ public class HexView : View, IDesignable
     /// </summary>
     protected void RaisePositionChanged ()
     {
+        SetNeedsDraw ();
+
         HexViewEventArgs args = new (Address, Position, BytesPerLine);
         OnPositionChanged (args);
         PositionChanged?.Invoke (this, args);
@@ -598,6 +604,11 @@ public class HexView : View, IDesignable
             return false;
         }
 
+        if (keyEvent.IsAlt)
+        {
+            return false;
+        }
+
         if (_leftSideHasFocus)
         {
             int value;
@@ -775,7 +786,7 @@ public class HexView : View, IDesignable
         if (Address >= DisplayStart + BytesPerLine * Viewport.Height)
         {
             SetDisplayStart (DisplayStart + bytes);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else
         {
@@ -793,7 +804,7 @@ public class HexView : View, IDesignable
         if (Address >= DisplayStart + BytesPerLine * Viewport.Height)
         {
             SetDisplayStart (Address);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else
         {
@@ -807,7 +818,7 @@ public class HexView : View, IDesignable
     {
         // This lets address go past the end of the stream one, enabling adding to the stream.
         Address = Math.Min (Address / BytesPerLine * BytesPerLine + BytesPerLine - 1, GetEditedSize ());
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         return true;
     }
@@ -815,7 +826,7 @@ public class HexView : View, IDesignable
     private bool MoveHome ()
     {
         DisplayStart = 0;
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         return true;
     }
@@ -844,7 +855,7 @@ public class HexView : View, IDesignable
         if (Address - 1 < DisplayStart)
         {
             SetDisplayStart (_displayStart - BytesPerLine);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else
         {
@@ -881,7 +892,7 @@ public class HexView : View, IDesignable
         if (Address >= DisplayStart + BytesPerLine * Viewport.Height)
         {
             SetDisplayStart (DisplayStart + BytesPerLine);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else
         {
@@ -906,7 +917,7 @@ public class HexView : View, IDesignable
     private bool MoveLeftStart ()
     {
         Address = Address / BytesPerLine * BytesPerLine;
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         return true;
     }
@@ -923,7 +934,7 @@ public class HexView : View, IDesignable
         if (Address < DisplayStart)
         {
             SetDisplayStart (DisplayStart - bytes);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else
         {
@@ -943,7 +954,7 @@ public class HexView : View, IDesignable
         var delta = (int)(pos - DisplayStart);
         int line = delta / BytesPerLine;
 
-        SetNeedsDisplay (new (0, line, Viewport.Width, 1));
+        SetNeedsDraw (new (0, line, Viewport.Width, 1));
     }
 
     /// <inheritdoc />

+ 8 - 7
Terminal.Gui/Views/Line.cs

@@ -64,39 +64,40 @@ public class Line : View, IOrientation
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
         LineCanvas lc = LineCanvas;
 
         if (SuperViewRendersLineCanvas)
         {
-            lc = SuperView.LineCanvas;
+            lc = SuperView?.LineCanvas;
         }
 
         if (SuperView is Adornment adornment)
         {
-            lc = adornment.Parent.LineCanvas;
+            lc = adornment.Parent?.LineCanvas;
         }
 
-        Point pos = ViewportToScreen (viewport).Location;
+        Point pos = ViewportToScreen (Viewport).Location;
         int length = Orientation == Orientation.Horizontal ? Frame.Width : Frame.Height;
 
-        if (SuperViewRendersLineCanvas && Orientation == Orientation.Horizontal)
+        if (SuperView is {} && SuperViewRendersLineCanvas && Orientation == Orientation.Horizontal)
         {
             pos.Offset (-SuperView.Border.Thickness.Left, 0);
             length += SuperView.Border.Thickness.Horizontal;
         }
 
-        if (SuperViewRendersLineCanvas && Orientation == Orientation.Vertical)
+        if (SuperView is { } && SuperViewRendersLineCanvas && Orientation == Orientation.Vertical)
         {
             pos.Offset (0, -SuperView.Border.Thickness.Top);
             length += SuperView.Border.Thickness.Vertical;
         }
-        lc.AddLine (
+        lc?.AddLine (
                     pos,
                     length,
                     Orientation,
                     BorderStyle
                    );
+        return true;
     }
 }

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

@@ -54,12 +54,10 @@ public class LineView : View
     public Rune? StartingAnchor { get; set; }
 
     /// <summary>Draws the line including any starting/ending anchors</summary>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        base.OnDrawContent (viewport);
-
         Move (0, 0);
-        Driver.SetAttribute (GetNormalColor ());
+        SetAttribute (GetNormalColor ());
 
         int hLineWidth = Math.Max (1, Glyphs.HLine.GetColumns ());
 
@@ -87,7 +85,8 @@ public class LineView : View
                 rune = EndingAnchor ?? LineRune;
             }
 
-            Driver.AddRune (rune);
+            Driver?.AddRune (rune);
         }
+        return true;
     }
 }

+ 77 - 60
Terminal.Gui/Views/ListView.cs

@@ -32,8 +32,7 @@ public interface IListDataSource : IDisposable
 
     /// <summary>This method is invoked to render a specified item, the method should cover the entire provided width.</summary>
     /// <returns>The render.</returns>
-    /// <param name="container">The list view to render.</param>
-    /// <param name="driver">The console driver to render.</param>
+    /// <param name="listView">The list view to render.</param>
     /// <param name="selected">Describes whether the item being rendered is currently selected by the user.</param>
     /// <param name="item">The index of the item to render, zero for the first item and so on.</param>
     /// <param name="col">The column where the rendering will start</param>
@@ -45,8 +44,7 @@ public interface IListDataSource : IDisposable
     ///     or not.
     /// </remarks>
     void Render (
-        ListView container,
-        ConsoleDriver driver,
+        ListView listView,
         bool selected,
         int item,
         int col,
@@ -88,7 +86,7 @@ public interface IListDataSource : IDisposable
 ///     </para>
 ///     <para>
 ///         To change the contents of the ListView, set the <see cref="Source"/> property (when providing custom
-///         rendering via <see cref="IListDataSource"/>) or call <see cref="SetSource"/> an <see cref="IList"/> is being
+///         rendering via <see cref="IListDataSource"/>) or call <see cref="SetSource{T}"/> an <see cref="IList"/> is being
 ///         used.
 ///     </para>
 ///     <para>
@@ -123,11 +121,24 @@ public class ListView : View, IDesignable
 
         // Things this view knows how to do
         // 
-        // BUGBUG: Should return false if selection doesn't change (to support nav to next view)
-        AddCommand (Command.Up, () => MoveUp ());
-        // BUGBUG: Should return false if selection doesn't change (to support nav to next view)
-        AddCommand (Command.Down, () => MoveDown ());
+        AddCommand (Command.Up, (ctx) =>
+                                {
+                                    if (RaiseSelecting (ctx) == true)
+                                    {
+                                        return true;
+                                    }
+                                    return MoveUp ();
+                                });
+        AddCommand (Command.Down, (ctx) =>
+                                  {
+                                      if (RaiseSelecting (ctx) == true)
+                                      {
+                                          return true;
+                                      }
+                                      return MoveDown ();
+                                  });
 
+        // TODO: add RaiseSelecting to all of these
         AddCommand (Command.ScrollUp, () => ScrollVertical (-1));
         AddCommand (Command.ScrollDown, () => ScrollVertical (1));
         AddCommand (Command.PageUp, () => MovePageUp ());
@@ -213,6 +224,13 @@ public class ListView : View, IDesignable
         // Use the form of Add that lets us pass context to the handler
         KeyBindings.Add (Key.A.WithCtrl, new KeyBinding ([Command.SelectAll], KeyBindingScope.Focused, true));
         KeyBindings.Add (Key.U.WithCtrl, new KeyBinding ([Command.SelectAll], KeyBindingScope.Focused, false));
+
+        SubviewsLaidOut += ListView_LayoutComplete;
+    }
+
+    private void ListView_LayoutComplete (object sender, LayoutEventArgs e)
+    {
+        SetContentSize (new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width));
     }
 
     /// <summary>Gets or sets whether this <see cref="ListView"/> allows items to be marked.</summary>
@@ -227,7 +245,7 @@ public class ListView : View, IDesignable
         set
         {
             _allowsMarking = value;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -254,7 +272,7 @@ public class ListView : View, IDesignable
                 }
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -282,7 +300,7 @@ public class ListView : View, IDesignable
             }
 
             Viewport = Viewport with { X = value };
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -313,7 +331,7 @@ public class ListView : View, IDesignable
 
     /// <summary>Gets or sets the <see cref="IListDataSource"/> backing this <see cref="ListView"/>, enabling custom rendering.</summary>
     /// <value>The source.</value>
-    /// <remarks>Use <see cref="SetSource"/> to set a new <see cref="IList"/> source.</remarks>
+    /// <remarks>Use <see cref="SetSource{T}"/> to set a new <see cref="IList"/> source.</remarks>
     public IListDataSource Source
     {
         get => _source;
@@ -335,16 +353,17 @@ public class ListView : View, IDesignable
             SetContentSize (new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width));
             if (IsInitialized)
             {
-                Viewport = Viewport with { Y = 0 };
+               // Viewport = Viewport with { Y = 0 };
             }
 
             KeystrokeNavigator.Collection = _source?.ToList ();
             _selected = -1;
             _lastSelectedItem = -1;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
+
     private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
     {
         SetContentSize (new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width));
@@ -354,7 +373,7 @@ public class ListView : View, IDesignable
             SelectedItem = Source.Count - 1;
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         OnCollectionChanged (e);
     }
@@ -446,11 +465,11 @@ public class ListView : View, IDesignable
                 Viewport = Viewport with { Y = _selected - Viewport.Height + 1 };
             }
 
-            LayoutStarted -= ListView_LayoutStarted;
+            SubviewLayout -= ListView_LayoutStarted;
         }
         else
         {
-            LayoutStarted += ListView_LayoutStarted;
+            SubviewLayout += ListView_LayoutStarted;
         }
     }
 
@@ -461,7 +480,7 @@ public class ListView : View, IDesignable
         if (UnmarkAllButSelected ())
         {
             Source.SetMark (SelectedItem, !Source.IsMarked (SelectedItem));
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return Source.IsMarked (SelectedItem);
         }
@@ -537,7 +556,7 @@ public class ListView : View, IDesignable
         }
 
         OnSelectedChanged ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         if (me.Flags == MouseFlags.Button1DoubleClicked)
         {
@@ -564,7 +583,7 @@ public class ListView : View, IDesignable
             // This can occur if the backing data source changes.
             _selected = _source.Count - 1;
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else if (_selected + 1 < _source.Count)
         {
@@ -581,17 +600,17 @@ public class ListView : View, IDesignable
             }
 
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else if (_selected == 0)
         {
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else if (_selected >= Viewport.Y + Viewport.Height)
         {
             Viewport = Viewport with { Y = _source.Count - Viewport.Height };
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return true;
@@ -616,7 +635,7 @@ public class ListView : View, IDesignable
             }
 
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return true;
@@ -631,7 +650,7 @@ public class ListView : View, IDesignable
             _selected = 0;
             Viewport = Viewport with { Y = _selected };
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return true;
@@ -670,7 +689,7 @@ public class ListView : View, IDesignable
             }
 
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return true;
@@ -692,7 +711,7 @@ public class ListView : View, IDesignable
             _selected = n;
             Viewport = Viewport with { Y = _selected };
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return true;
@@ -715,7 +734,7 @@ public class ListView : View, IDesignable
             // This can occur if the backing data source changes.
             _selected = _source.Count - 1;
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else if (_selected > 0)
         {
@@ -736,24 +755,22 @@ public class ListView : View, IDesignable
             }
 
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else if (_selected < Viewport.Y)
         {
             Viewport = Viewport with { Y = _selected };
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return true;
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        base.OnDrawContent (viewport);
-
-        Attribute current = ColorScheme.Focus;
-        Driver.SetAttribute (current);
+        Attribute current = ColorScheme?.Focus ?? Attribute.Default;
+        SetAttribute (current);
         Move (0, 0);
         Rectangle f = Viewport;
         int item = Viewport.Y;
@@ -770,7 +787,7 @@ public class ListView : View, IDesignable
 
             if (newcolor != current)
             {
-                Driver.SetAttribute (newcolor);
+                SetAttribute (newcolor);
                 current = newcolor;
             }
 
@@ -780,7 +797,7 @@ public class ListView : View, IDesignable
             {
                 for (var c = 0; c < f.Width; c++)
                 {
-                    Driver.AddRune ((Rune)' ');
+                    Driver?.AddRune ((Rune)' ');
                 }
             }
             else
@@ -791,21 +808,22 @@ public class ListView : View, IDesignable
                 if (rowEventArgs.RowAttribute is { } && current != rowEventArgs.RowAttribute)
                 {
                     current = (Attribute)rowEventArgs.RowAttribute;
-                    Driver.SetAttribute (current);
+                    SetAttribute (current);
                 }
 
                 if (_allowsMarking)
                 {
-                    Driver.AddRune (
+                    Driver?.AddRune (
                                     _source.IsMarked (item) ? AllowsMultipleSelection ? Glyphs.CheckStateChecked : Glyphs.Selected :
                                     AllowsMultipleSelection ? Glyphs.CheckStateUnChecked : Glyphs.UnSelected
                                    );
-                    Driver.AddRune ((Rune)' ');
+                    Driver?.AddRune ((Rune)' ');
                 }
 
-                Source.Render (this, Driver, isSelected, item, col, row, f.Width - col, start);
+                Source.Render (this, isSelected, item, col, row, f.Width - col, start);
             }
         }
+        return true;
     }
 
     /// <inheritdoc/>
@@ -866,7 +884,7 @@ public class ListView : View, IDesignable
             {
                 SelectedItem = (int)newItem;
                 EnsureSelectedItemVisible ();
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
 
                 return true;
             }
@@ -900,20 +918,20 @@ public class ListView : View, IDesignable
     /// <summary>This event is raised when the user Double Clicks on an item or presses ENTER to open the selected item.</summary>
     public event EventHandler<ListViewItemEventArgs> OpenSelectedItem;
 
-    /// <inheritdoc/>
-    public override Point? PositionCursor ()
-    {
-        int x = 0;
-        int y = _selected - Viewport.Y;
-        if (!_allowsMarking)
-        {
-            x = Viewport.Width - 1;
-        }
+    ///// <inheritdoc/>
+    //public override Point? PositionCursor ()
+    //{
+    //    int x = 0;
+    //    int y = _selected - Viewport.Y;
+    //    if (!_allowsMarking)
+    //    {
+    //        x = Viewport.Width - 1;
+    //    }
 
-        Move (x, y);
+    //    Move (x, y);
 
-        return null; // Don't show the cursor
-    }
+    //    return null; // Don't show the cursor
+    //}
 
     /// <summary>This event is invoked when this <see cref="ListView"/> is being drawn before rendering.</summary>
     public event EventHandler<ListViewRowEventArgs> RowRender;
@@ -1096,7 +1114,6 @@ public class ListWrapper<T> : IListDataSource, IDisposable
     /// <inheritdoc/>
     public void Render (
         ListView container,
-        ConsoleDriver driver,
         bool marked,
         int item,
         int col,
@@ -1113,17 +1130,17 @@ public class ListWrapper<T> : IListDataSource, IDisposable
 
             if (t is null)
             {
-                RenderUstr (driver, "", col, line, width);
+                RenderUstr (container, "", col, line, width);
             }
             else
             {
                 if (t is string s)
                 {
-                    RenderUstr (driver, s, col, line, width, start);
+                    RenderUstr (container, s, col, line, width, start);
                 }
                 else
                 {
-                    RenderUstr (driver, t.ToString (), col, line, width, start);
+                    RenderUstr (container, t.ToString (), col, line, width, start);
                 }
             }
         }
@@ -1219,7 +1236,7 @@ public class ListWrapper<T> : IListDataSource, IDisposable
         return maxLength;
     }
 
-    private void RenderUstr (ConsoleDriver driver, string ustr, int col, int line, int width, int start = 0)
+    private void RenderUstr (View driver, string ustr, int col, int line, int width, int start = 0)
     {
         string str = start > ustr.GetColumns () ? string.Empty : ustr.Substring (Math.Min (start, ustr.ToRunes ().Length - 1));
         string u = TextFormatter.ClipAndJustify (str, width, Alignment.Start);

+ 39 - 31
Terminal.Gui/Views/Menu/Menu.cs

@@ -70,7 +70,18 @@ internal sealed class Menu : View
     {
         base.BeginInit ();
 
-        Frame = MakeFrame (Frame.X, Frame.Y, _barItems!.Children!, Parent);
+        var frame = MakeFrame (Frame.X, Frame.Y, _barItems!.Children!, Parent);
+
+        if (Frame.X != frame.X)
+        {
+            X = frame.X;
+        }
+        if (Frame.Y != frame.Y)
+        {
+            Y = frame.Y;
+        }
+        Width = frame.Width;
+        Height = frame.Height;
 
         if (_barItems.Children is { })
         {
@@ -148,7 +159,7 @@ internal sealed class Menu : View
     {
         if (Application.Top is { })
         {
-            Application.Top.DrawContentComplete += Current_DrawContentComplete;
+            Application.Top.DrawComplete += Top_DrawComplete;
             Application.Top.SizeChanging += Current_TerminalResized;
         }
 
@@ -279,7 +290,7 @@ internal sealed class Menu : View
 
                 if (!disabled && (_host.UseSubMenusSingleFrame || !CheckSubMenu ()))
                 {
-                    SetNeedsDisplay ();
+                    SetNeedsDraw ();
                     SetParentSetNeedsDisplay ();
 
                     return true;
@@ -382,19 +393,26 @@ internal sealed class Menu : View
         return !item.IsEnabled () ? ColorScheme!.Disabled : GetNormalColor ();
     }
 
-    public override void OnDrawContent (Rectangle viewport)
+    // By doing this we draw last, over everything else.
+    private void Top_DrawComplete (object? sender, DrawEventArgs e)
     {
+        if (!Visible)
+        {
+            return;
+        }
+
         if (_barItems!.Children is null)
         {
             return;
         }
 
-        Rectangle savedClip = Driver.Clip;
-        Driver.Clip = new (0, 0, Driver.Cols, Driver.Rows);
-        Driver.SetAttribute (GetNormalColor ());
+        DrawBorderAndPadding ();
+        RenderLineCanvas ();
+
+        // BUGBUG: Views should not change the clip. Doing so is an indcation of poor design or a bug in the framework.
+        Region? savedClip = View.SetClipToScreen ();
 
-        OnDrawAdornments ();
-        OnRenderLineCanvas ();
+        SetAttribute (GetNormalColor ());
 
         for (int i = Viewport.Y; i < _barItems!.Children.Length; i++)
         {
@@ -410,7 +428,7 @@ internal sealed class Menu : View
 
             MenuItem? item = _barItems.Children [i];
 
-            Driver.SetAttribute (
+            SetAttribute (
                                  // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
                                  item is null ? GetNormalColor () :
                                  i == _currentChild ? GetFocusColor () : GetNormalColor ()
@@ -427,7 +445,7 @@ internal sealed class Menu : View
                 Move (0, i);
             }
 
-            Driver.SetAttribute (DetermineColorSchemeFor (item, i));
+            SetAttribute (DetermineColorSchemeFor (item, i));
 
             for (int p = Viewport.X; p < Frame.Width - 2; p++)
             {
@@ -561,17 +579,7 @@ internal sealed class Menu : View
             }
         }
 
-        Driver.Clip = savedClip;
-
-        // PositionCursor ();
-    }
-
-    private void Current_DrawContentComplete (object? sender, DrawEventArgs e)
-    {
-        if (Visible)
-        {
-            OnDrawContent (Viewport);
-        }
+        View.SetClip (savedClip);
     }
 
     public override Point? PositionCursor ()
@@ -601,7 +609,7 @@ internal sealed class Menu : View
         Application.UngrabMouse ();
         _host.CloseAllMenus ();
         Application.Driver!.ClearContents ();
-        Application.Refresh ();
+        Application.LayoutAndDraw ();
 
         _host.Run (action);
     }
@@ -702,7 +710,7 @@ internal sealed class Menu : View
         }
         while (_barItems?.Children? [_currentChild] is null || disabled);
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         SetParentSetNeedsDisplay ();
 
         if (!_host.UseSubMenusSingleFrame)
@@ -783,7 +791,7 @@ internal sealed class Menu : View
         }
         while (_barItems.Children [_currentChild] is null || disabled);
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         SetParentSetNeedsDisplay ();
 
         if (!_host.UseSubMenusSingleFrame)
@@ -800,12 +808,12 @@ internal sealed class Menu : View
         {
             foreach (Menu menu in _host._openSubMenu)
             {
-                menu.SetNeedsDisplay ();
+                menu.SetNeedsDraw ();
             }
         }
 
-        _host._openMenu?.SetNeedsDisplay ();
-        _host.SetNeedsDisplay ();
+        _host._openMenu?.SetNeedsDraw ();
+        _host.SetNeedsDraw ();
     }
 
     protected override bool OnMouseEvent (MouseEventArgs me)
@@ -888,7 +896,7 @@ internal sealed class Menu : View
 
             if (_host.UseSubMenusSingleFrame || !CheckSubMenu ())
             {
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
                 SetParentSetNeedsDisplay ();
 
                 return me.Handled = true;
@@ -935,7 +943,7 @@ internal sealed class Menu : View
         }
         else
         {
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
             SetParentSetNeedsDisplay ();
         }
 
@@ -948,7 +956,7 @@ internal sealed class Menu : View
 
         if (Application.Top is { })
         {
-            Application.Top.DrawContentComplete -= Current_DrawContentComplete;
+            Application.Top.DrawComplete -= Top_DrawComplete;
             Application.Top.SizeChanging -= Current_TerminalResized;
         }
 

+ 18 - 20
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -244,7 +244,7 @@ public class MenuBar : View, IDesignable
             if (value && UseKeysUpDownAsKeysLeftRight)
             {
                 _useKeysUpDownAsKeysLeftRight = false;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
         }
     }
@@ -297,12 +297,8 @@ public class MenuBar : View, IDesignable
     public event EventHandler<MenuOpeningEventArgs>? MenuOpening;
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        Driver.SetAttribute (GetNormalColor ());
-
-        Clear ();
-
         var pos = 0;
 
         for (var i = 0; i < Menus.Length; i++)
@@ -338,6 +334,7 @@ public class MenuBar : View, IDesignable
         }
 
         //PositionCursor ();
+        return true;
     }
 
     /// <summary>Virtual method that will invoke the <see cref="MenuAllClosed"/>.</summary>
@@ -381,7 +378,7 @@ public class MenuBar : View, IDesignable
                 mi = parent?.Children?.Length > 0 ? parent.Children [_openMenu!._currentChild] : null;
             }
         }
-
+        
         MenuOpened?.Invoke (this, new (parent, mi));
     }
 
@@ -411,7 +408,7 @@ public class MenuBar : View, IDesignable
         }
 
         _selected = 0;
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         _previousFocused = (SuperView is null ? Application.Top?.Focused : SuperView.Focused)!;
         OpenMenu (_selected);
@@ -478,7 +475,7 @@ public class MenuBar : View, IDesignable
         }
 
         OpenMenu (idx, sIdx, subMenu);
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     internal void CleanUp ()
@@ -500,7 +497,7 @@ public class MenuBar : View, IDesignable
             _lastFocused.SetFocus ();
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         if (Application.MouseGrabView is { } && Application.MouseGrabView is MenuBar && Application.MouseGrabView != this)
         {
@@ -593,7 +590,7 @@ public class MenuBar : View, IDesignable
                     Application.Top?.Remove (_openMenu);
                 }
 
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
 
                 if (_previousFocused is Menu && _openMenu is { } && _previousFocused.ToString () != OpenCurrentMenu!.ToString ())
                 {
@@ -655,7 +652,7 @@ public class MenuBar : View, IDesignable
 
             case true:
                 _selectedSub = -1;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
                 RemoveAllOpensSubMenus ();
                 OpenCurrentMenu!._previousSubFocused!.SetFocus ();
                 _openSubMenu = null;
@@ -773,7 +770,7 @@ public class MenuBar : View, IDesignable
                         return;
                     }
 
-                    SetNeedsDisplay ();
+                    SetNeedsDraw ();
 
                     if (UseKeysUpDownAsKeysLeftRight)
                     {
@@ -861,6 +858,7 @@ public class MenuBar : View, IDesignable
                 if (Application.Top is { })
                 {
                     Application.Top.Add (_openMenu);
+                   // _openMenu.SetRelativeLayout (Application.Screen.Size);
                 }
                 else
                 {
@@ -991,7 +989,7 @@ public class MenuBar : View, IDesignable
                 {
                     _selectedSub--;
                     RemoveSubMenu (_selectedSub, ignoreUseSubMenusSingleFrame);
-                    SetNeedsDisplay ();
+                    SetNeedsDraw ();
                 }
                 else
                 {
@@ -1119,7 +1117,7 @@ public class MenuBar : View, IDesignable
 
         Application.UngrabMouse ();
         CloseAllMenus ();
-        Application.Refresh ();
+        Application.LayoutAndDraw ();
         _openedByAltKey = true;
 
         return Run (item.Action);
@@ -1138,7 +1136,7 @@ public class MenuBar : View, IDesignable
             LastFocused?.SetFocus ();
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     private Point GetLocationOffset ()
@@ -1167,14 +1165,14 @@ public class MenuBar : View, IDesignable
         }
 
         OpenMenu (_selected);
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     private void MoveRight ()
     {
         _selected = (_selected + 1) % Menus.Length;
         OpenMenu (_selected);
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     private bool ProcessMenu (int i, MenuBarItem mi)
@@ -1217,7 +1215,7 @@ public class MenuBar : View, IDesignable
             }
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         return true;
     }
@@ -1324,7 +1322,7 @@ public class MenuBar : View, IDesignable
             if (value && UseSubMenusSingleFrame)
             {
                 UseSubMenusSingleFrame = false;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
         }
     }

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

@@ -22,7 +22,7 @@ public class MenuBarv2 : Bar
         ColorScheme = Colors.ColorSchemes ["Menu"];
         Orientation = Orientation.Horizontal;
 
-        LayoutStarted += MenuBarv2_LayoutStarted;
+        SubviewLayout += MenuBarv2_LayoutStarted;
     }
 
     // MenuBarv2 arranges the items horizontally.

+ 3 - 2
Terminal.Gui/Views/Menuv2.cs

@@ -46,7 +46,8 @@ public class Menuv2 : Bar
     // Menuv2 arranges the items horizontally.
     // The first item has no left border, the last item has no right border.
     // The Shortcuts are configured with the command, help, and key views aligned in reverse order (EndToStart).
-    internal override void OnLayoutStarted (LayoutEventArgs args)
+    /// <inheritdoc />
+    protected override void OnSubviewLayout (LayoutEventArgs args)
     {
         for (int index = 0; index < Subviews.Count; index++)
         {
@@ -58,7 +59,7 @@ public class Menuv2 : Bar
             }
 
         }
-        base.OnLayoutStarted (args);
+        base.OnSubviewLayout (args);
     }
 
     /// <inheritdoc/>

+ 17 - 3
Terminal.Gui/Views/NumericUpDown.cs

@@ -64,7 +64,7 @@ public class NumericUpDown<T> : View where T : notnull
             Text = Value?.ToString () ?? "Err",
             X = Pos.Right (_down),
             Y = Pos.Top (_down),
-            Width = Dim.Auto (minimumContentDim: Dim.Func (() => string.Format (Format, Value).Length)),
+            Width = Dim.Auto (minimumContentDim: Dim.Func (() => string.Format (Format, Value).GetColumns())),
             Height = 1,
             TextAlignment = Alignment.Center,
             CanFocus = true,
@@ -93,13 +93,18 @@ public class NumericUpDown<T> : View where T : notnull
 
         AddCommand (
                     Command.ScrollUp,
-                    () =>
+                    (ctx) =>
                     {
                         if (type == typeof (object))
                         {
                             return false;
                         }
 
+                        if (RaiseSelecting (ctx) is true)
+                        {
+                            return true;
+                        }
+
                         if (Value is { } && Increment is { })
                         {
                             Value = (dynamic)Value + (dynamic)Increment;
@@ -110,13 +115,18 @@ public class NumericUpDown<T> : View where T : notnull
 
         AddCommand (
                     Command.ScrollDown,
-                    () =>
+                    (ctx) =>
                     {
                         if (type == typeof (object))
                         {
                             return false;
                         }
 
+                        if (RaiseSelecting (ctx) is true)
+                        {
+                            return true;
+                        }
+
                         if (Value is { } && Increment is { })
                         {
                             Value = (dynamic)Value - (dynamic)Increment;
@@ -251,6 +261,10 @@ public class NumericUpDown<T> : View where T : notnull
     ///     Raised when <see cref="Increment"/> has changed.
     /// </summary>
     public event EventHandler<EventArgs<T>>? IncrementChanged;
+
+    // Prevent the drawing of Text
+    /// <inheritdoc />
+    protected override bool OnDrawingText () { return true; }
 }
 
 /// <summary>

+ 39 - 61
Terminal.Gui/Views/ProgressBar.cs

@@ -1,4 +1,5 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 /// <summary>Specifies the style that a <see cref="ProgressBar"/> uses to indicate the progress of an operation.</summary>
 public enum ProgressBarStyle
@@ -37,30 +38,29 @@ public enum ProgressBarFormat
 /// </remarks>
 public class ProgressBar : View, IDesignable
 {
-    private int [] _activityPos;
-    private bool _bidirectionalMarquee = true;
+    private int []? _activityPos;
     private int _delta;
     private float _fraction;
     private bool _isActivity;
     private ProgressBarStyle _progressBarStyle = ProgressBarStyle.Blocks;
-    private ProgressBarFormat _progressBarFormat = ProgressBarFormat.Simple;
-    private Rune _segmentCharacter = Glyphs.BlocksMeterSegment;
 
     /// <summary>
     ///     Initializes a new instance of the <see cref="ProgressBar"/> class, starts in percentage mode and uses relative
     ///     layout.
     /// </summary>
-    public ProgressBar () { SetInitialProperties (); }
+    public ProgressBar ()
+    {
+        Width = Dim.Auto (DimAutoStyle.Content);
+        Height = Dim.Auto (DimAutoStyle.Content, 1);
+        CanFocus = false;
+        _fraction = 0;
+    }
 
     /// <summary>
     ///     Specifies if the <see cref="ProgressBarStyle.MarqueeBlocks"/> or the
     ///     <see cref="ProgressBarStyle.MarqueeContinuous"/> styles is unidirectional or bidirectional.
     /// </summary>
-    public bool BidirectionalMarquee
-    {
-        get => _bidirectionalMarquee;
-        set => _bidirectionalMarquee = value;
-    }
+    public bool BidirectionalMarquee { get; set; } = true;
 
     /// <summary>Gets or sets the <see cref="ProgressBar"/> fraction to display, must be a value between 0 and 1.</summary>
     /// <value>The fraction representing the progress.</value>
@@ -71,16 +71,12 @@ public class ProgressBar : View, IDesignable
         {
             _fraction = Math.Min (value, 1);
             _isActivity = false;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
     /// <summary>Specifies the format that a <see cref="ProgressBar"/> uses to indicate the visual presentation.</summary>
-    public ProgressBarFormat ProgressBarFormat
-    {
-        get => _progressBarFormat;
-        set => _progressBarFormat = value;
-    }
+    public ProgressBarFormat ProgressBarFormat { get; set; } = ProgressBarFormat.Simple;
 
     /// <summary>Gets/Sets the progress bar style based on the <see cref="Terminal.Gui.ProgressBarStyle"/></summary>
     public ProgressBarStyle ProgressBarStyle
@@ -109,16 +105,13 @@ public class ProgressBar : View, IDesignable
 
                     break;
             }
-            SetNeedsDisplay ();
+
+            SetNeedsDraw ();
         }
     }
 
     /// <summary>Segment indicator for meter views.</summary>
-    public Rune SegmentCharacter
-    {
-        get => _segmentCharacter;
-        set => _segmentCharacter = value;
-    }
+    public Rune SegmentCharacter { get; set; } = Glyphs.BlocksMeterSegment;
 
     /// <summary>
     ///     Gets or sets the text displayed on the progress bar. If set to an empty string and
@@ -130,8 +123,7 @@ public class ProgressBar : View, IDesignable
         get => string.IsNullOrEmpty (base.Text) ? $"{_fraction * 100:F0}%" : base.Text;
         set
         {
-            if (ProgressBarStyle == ProgressBarStyle.MarqueeBlocks
-                || ProgressBarStyle == ProgressBarStyle.MarqueeContinuous)
+            if (ProgressBarStyle is ProgressBarStyle.MarqueeBlocks or ProgressBarStyle.MarqueeContinuous)
             {
                 base.Text = value;
             }
@@ -139,9 +131,9 @@ public class ProgressBar : View, IDesignable
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        Driver.SetAttribute (GetHotNormalColor ());
+        SetAttribute (GetHotNormalColor ());
 
         Move (0, 0);
 
@@ -149,13 +141,13 @@ public class ProgressBar : View, IDesignable
         {
             for (var i = 0; i < Viewport.Width; i++)
             {
-                if (Array.IndexOf (_activityPos, i) != -1)
+                if (Array.IndexOf (_activityPos!, i) != -1)
                 {
-                    Driver.AddRune (SegmentCharacter);
+                    Driver?.AddRune (SegmentCharacter);
                 }
                 else
                 {
-                    Driver.AddRune ((Rune)' ');
+                    Driver?.AddRune ((Rune)' ');
                 }
             }
         }
@@ -166,32 +158,34 @@ public class ProgressBar : View, IDesignable
 
             for (i = 0; (i < mid) & (i < Viewport.Width); i++)
             {
-                Driver.AddRune (SegmentCharacter);
+                Driver?.AddRune (SegmentCharacter);
             }
 
             for (; i < Viewport.Width; i++)
             {
-                Driver.AddRune ((Rune)' ');
+                Driver?.AddRune ((Rune)' ');
             }
         }
 
         if (ProgressBarFormat != ProgressBarFormat.Simple && !_isActivity)
         {
             var tf = new TextFormatter { Alignment = Alignment.Center, Text = Text };
-            var attr = new Attribute (ColorScheme.HotNormal.Foreground, ColorScheme.HotNormal.Background);
+            var attr = new Attribute (ColorScheme!.HotNormal.Foreground, ColorScheme.HotNormal.Background);
 
             if (_fraction > .5)
             {
-                attr = new Attribute (ColorScheme.HotNormal.Background, ColorScheme.HotNormal.Foreground);
+                attr = new (ColorScheme.HotNormal.Background, ColorScheme.HotNormal.Foreground);
             }
 
-            tf?.Draw (
-                      ViewportToScreen (Viewport),
-                      attr,
-                      ColorScheme.Normal,
-                      SuperView?.ViewportToScreen (SuperView.Viewport) ?? default (Rectangle)
-                     );
+            tf.Draw (
+                     ViewportToScreen (Viewport),
+                     attr,
+                     ColorScheme.Normal,
+                     SuperView?.ViewportToScreen (SuperView.Viewport) ?? default (Rectangle)
+                    );
         }
+
+        return true;
     }
 
     /// <summary>Notifies the <see cref="ProgressBar"/> that some progress has taken place.</summary>
@@ -234,7 +228,7 @@ public class ProgressBar : View, IDesignable
             }
             else if (_activityPos [0] >= Viewport.Width)
             {
-                if (_bidirectionalMarquee)
+                if (BidirectionalMarquee)
                 {
                     for (var i = 0; i < _activityPos.Length; i++)
                     {
@@ -250,7 +244,7 @@ public class ProgressBar : View, IDesignable
             }
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     private void PopulateActivityPos ()
@@ -263,29 +257,13 @@ public class ProgressBar : View, IDesignable
         }
     }
 
-    private void ProgressBar_Initialized (object sender, EventArgs e)
-    {
-        //ColorScheme = new ColorScheme (ColorScheme ?? SuperView?.ColorScheme ?? Colors.ColorSchemes ["Base"])
-        //{
-        //    HotNormal = new Attribute (Color.BrightGreen, Color.Gray)
-        //};
-    }
-
-    private void SetInitialProperties ()
-    {
-        Width = Dim.Auto (DimAutoStyle.Content);
-        Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 1);
-        CanFocus = false;
-        _fraction = 0;
-        Initialized += ProgressBar_Initialized;
-    }
-
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public bool EnableForDesign ()
     {
         Width = Dim.Fill ();
-        Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1);
+        Height = Dim.Auto (DimAutoStyle.Text, 1);
         Fraction = 0.75f;
+
         return true;
     }
 }

+ 17 - 18
Terminal.Gui/Views/RadioGroup.cs

@@ -52,9 +52,9 @@ public class RadioGroup : View, IDesignable, IOrientation
         // Accept (Enter key) - Raise Accept event - DO NOT advance state
         AddCommand (Command.Accept, RaiseAccepting);
 
-        // Hotkey - ctx may indicate a radio item hotkey was pressed. Beahvior depends on HasFocus
+        // Hotkey - ctx may indicate a radio item hotkey was pressed. Behavior depends on HasFocus
         //          If HasFocus and it's this.HotKey invoke Select command - DO NOT raise Accept
-        //          If it's a radio item HotKey select that item and raise Seelcted event - DO NOT raise Accept
+        //          If it's a radio item HotKey select that item and raise Selected event - DO NOT raise Accept
         //          If nothing is selected, select first and raise Selected event - DO NOT raise Accept
         AddCommand (Command.HotKey,
                     ctx =>
@@ -175,7 +175,7 @@ public class RadioGroup : View, IDesignable, IOrientation
 
         SetupKeyBindings ();
 
-        LayoutStarted += RadioGroup_LayoutStarted;
+        SubviewLayout += RadioGroup_LayoutStarted;
 
         HighlightStyle = HighlightStyle.PressedOutside | HighlightStyle.Pressed;
 
@@ -354,17 +354,15 @@ public class RadioGroup : View, IDesignable, IOrientation
         OnSelectedItemChanged (value, SelectedItem);
         SelectedItemChanged?.Invoke (this, new (SelectedItem, savedSelected));
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         return true;
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        base.OnDrawContent (viewport);
-
-        Driver.SetAttribute (GetNormalColor ());
+        SetAttribute (GetNormalColor ());
 
         for (var i = 0; i < _radioLabels.Count; i++)
         {
@@ -381,8 +379,8 @@ public class RadioGroup : View, IDesignable, IOrientation
             }
 
             string rl = _radioLabels [i];
-            Driver.SetAttribute (GetNormalColor ());
-            Driver.AddStr ($"{(i == _selected ? Glyphs.Selected : Glyphs.UnSelected)} ");
+            SetAttribute (GetNormalColor ());
+            Driver?.AddStr ($"{(i == _selected ? Glyphs.Selected : Glyphs.UnSelected)} ");
             TextFormatter.FindHotKey (rl, HotKeySpecifier, out int hotPos, out Key hotKey);
 
             if (hotPos != -1 && hotKey != Key.Empty)
@@ -395,7 +393,7 @@ public class RadioGroup : View, IDesignable, IOrientation
 
                     if (j == hotPos && i == Cursor)
                     {
-                        Application.Driver?.SetAttribute (
+                        SetAttribute (
                                                           HasFocus
                                                               ? ColorScheme!.HotFocus
                                                               : GetHotNormalColor ()
@@ -403,11 +401,11 @@ public class RadioGroup : View, IDesignable, IOrientation
                     }
                     else if (j == hotPos && i != Cursor)
                     {
-                        Application.Driver?.SetAttribute (GetHotNormalColor ());
+                        SetAttribute (GetHotNormalColor ());
                     }
                     else if (HasFocus && i == Cursor)
                     {
-                        Application.Driver?.SetAttribute (GetFocusColor ());
+                        SetAttribute (GetFocusColor ());
                     }
 
                     if (rune == HotKeySpecifier && j + 1 < rlRunes.Length)
@@ -417,7 +415,7 @@ public class RadioGroup : View, IDesignable, IOrientation
 
                         if (i == Cursor)
                         {
-                            Application.Driver?.SetAttribute (
+                            SetAttribute (
                                                               HasFocus
                                                                   ? ColorScheme!.HotFocus
                                                                   : GetHotNormalColor ()
@@ -425,12 +423,12 @@ public class RadioGroup : View, IDesignable, IOrientation
                         }
                         else if (i != Cursor)
                         {
-                            Application.Driver?.SetAttribute (GetHotNormalColor ());
+                            SetAttribute (GetHotNormalColor ());
                         }
                     }
 
                     Application.Driver?.AddRune (rune);
-                    Driver.SetAttribute (GetNormalColor ());
+                    SetAttribute (GetNormalColor ());
                 }
             }
             else
@@ -438,6 +436,7 @@ public class RadioGroup : View, IDesignable, IOrientation
                 DrawHotString (rl, HasFocus && i == Cursor);
             }
         }
+        return true;
     }
 
     #region IOrientation
@@ -524,7 +523,7 @@ public class RadioGroup : View, IDesignable, IOrientation
         if (Cursor + 1 < _radioLabels.Count)
         {
             Cursor++;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -552,7 +551,7 @@ public class RadioGroup : View, IDesignable, IOrientation
         if (Cursor > 0)
         {
             Cursor--;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }

+ 146 - 176
Terminal.Gui/Views/Scroll/Scroll.cs

@@ -1,6 +1,7 @@
 #nullable enable
 
 using System.ComponentModel;
+using System.Drawing;
 
 namespace Terminal.Gui;
 
@@ -16,137 +17,99 @@ namespace Terminal.Gui;
 ///         By default, this view cannot be focused and does not support keyboard.
 ///     </para>
 /// </remarks>
-public class Scroll : View
+public class Scroll : View, IOrientation, IDesignable
 {
+    internal readonly ScrollSlider _slider;
+
     /// <inheritdoc/>
     public Scroll ()
     {
         _slider = new ();
         Add (_slider);
+        _slider.FrameChanged += OnSliderOnFrameChanged;
 
-        WantContinuousButtonPressed = true;
         CanFocus = false;
-        Orientation = Orientation.Vertical;
-
-        Width = Dim.Auto (DimAutoStyle.Content, 1);
-        Height = Dim.Auto (DimAutoStyle.Content, 1);
-    }
 
-    internal readonly ScrollSlider _slider;
-    private Orientation _orientation;
-    private int _position;
-    private int _size;
-    private bool _keepContentInAllViewport;
+        _orientationHelper = new (this); // Do not use object initializer!
+        _orientationHelper.Orientation = Orientation.Vertical;
+        _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+        _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
 
-    /// <inheritdoc/>
-    public override void EndInit ()
-    {
-        base.EndInit ();
-
-        AdjustScroll ();
+        // This sets the width/height etc...
+        OnOrientationChanged (Orientation);
     }
 
-    /// <summary>Get or sets if the view-port is kept in all visible area of this <see cref="Scroll"/></summary>
-    public bool KeepContentInAllViewport
+
+    /// <inheritdoc />
+    protected override void OnSubviewLayout (LayoutEventArgs args)
     {
-        get => _keepContentInAllViewport;
-        set
+        if (ViewportDimension < 1)
         {
-            if (_keepContentInAllViewport != value)
-            {
-                _keepContentInAllViewport = value;
-                var pos = 0;
-
-                if (value
-                    && Orientation == Orientation.Horizontal
-                    && _position + (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.GetContentSize ().Width : GetContentSize ().Width) > Size)
-                {
-                    pos = Size - (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.GetContentSize ().Width : GetContentSize ().Width);
-                }
-
-                if (value
-                    && Orientation == Orientation.Vertical
-                    && _position + (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.GetContentSize ().Height : GetContentSize ().Height) > Size)
-                {
-                    pos = _size - (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.GetContentSize ().Height : GetContentSize ().Height);
-                }
-
-                if (pos != 0)
-                {
-                    Position = pos;
-                }
-
-                SetNeedsDisplay ();
-                AdjustScroll ();
-            }
+            _slider.Size = 1;
+
+            return;
         }
+        _slider.Size = (int)Math.Clamp (Math.Floor ((double)ViewportDimension * ViewportDimension / (Size)), 1, ViewportDimension);
     }
 
-    /// <summary>
-    ///     Gets or sets if the Scroll is oriented vertically or horizontally.
-    /// </summary>
+    #region IOrientation members
+    private readonly OrientationHelper _orientationHelper;
+
+    /// <inheritdoc/>
     public Orientation Orientation
     {
-        get => _orientation;
-        set
-        {
-            _orientation = value;
-            AdjustScroll ();
-        }
+        get => _orientationHelper.Orientation;
+        set => _orientationHelper.Orientation = value;
     }
 
-    /// <summary>
-    ///     Gets or sets the position of the start of the Scroll slider, relative to <see cref="Size"/>.
-    /// </summary>
-    public int Position
-    {
-        get => _position;
-        set
-        {
-            if (value == _position || value < 0)
-            {
-                return;
-            }
-
-            if (SuperViewAsScrollBar is { IsInitialized: false })
-            {
-                // Ensures a more exactly calculation
-                SetRelativeLayout (SuperViewAsScrollBar.Frame.Size);
-            }
-
-            int pos = SetPosition (value);
-
-            if (pos == _position)
-            {
-                return;
-            }
-
-            CancelEventArgs<int> args = OnPositionChanging (_position, pos);
+    /// <inheritdoc/>
+    public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
 
-            if (args.Cancel)
-            {
-                return;
-            }
+    /// <inheritdoc/>
+    public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
 
-            _position = pos;
+    /// <inheritdoc/>
+    public void OnOrientationChanged (Orientation newOrientation)
+    {
+        TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
+        TextAlignment = Alignment.Center;
+        VerticalTextAlignment = Alignment.Center;
 
-            AdjustScroll ();
+        X = 0;
+        Y = 0;
 
-            OnPositionChanged (_position);
+        if (Orientation == Orientation.Vertical)
+        {
+            Width = 1;
+            Height = Dim.Fill ();
+        }
+        else
+        {
+            Width = Dim.Fill ();
+            Height = 1;
         }
+
+        _slider.Orientation = newOrientation;
     }
 
-    /// <summary>Raised when the <see cref="Position"/> has changed.</summary>
-    public event EventHandler<EventArgs<int>>? PositionChanged;
+    #endregion
 
     /// <summary>
-    ///     Raised when the <see cref="Position"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
-    ///     <see langword="true"/> to prevent the position from being changed.
+    ///     Gets or sets whether the Scroll will show the percentage the slider
+    ///     takes up within the <see cref="Size"/>.
     /// </summary>
-    public event EventHandler<CancelEventArgs<int>>? PositionChanging;
+    public bool ShowPercent
+    {
+        get => _slider.ShowPercent;
+        set => _slider.ShowPercent = value;
+    }
+
+    private int ViewportDimension => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
+
+    private int _size;
 
     /// <summary>
-    ///     Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through.
+    ///     Gets or sets the total size of the content that can be scrolled.
     /// </summary>
     public int Size
     {
@@ -160,116 +123,123 @@ public class Scroll : View
 
             _size = value;
             OnSizeChanged (_size);
-            AdjustScroll ();
+            SizeChanged?.Invoke (this, new (in _size));
         }
     }
 
+    /// <summary>Called when <see cref="Size"/> has changed. </summary>
+    protected virtual void OnSizeChanged (int size) { }
+
     /// <summary>Raised when <see cref="Size"/> has changed.</summary>
     public event EventHandler<EventArgs<int>>? SizeChanged;
 
-    /// <inheritdoc/>
-    protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
-    {
-        int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X;
-        int barSize = BarSize;
-
-        (int start, int end) sliderPos = _orientation == Orientation.Vertical
-                                             ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1)
-                                             : new (_slider.Frame.X, _slider.Frame.Right - 1);
+    private int _position;
 
-        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location < sliderPos.start)
-        {
-            int distance = sliderPos.start - location;
-            int scrollAmount = (int)((double)distance / barSize * (Size - barSize));
-            Position = Math.Max (Position - scrollAmount, 0);
-        }
-        else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.end)
-        {
-            Position = Math.Min (Position + barSize, Size - barSize + (KeepContentInAllViewport ? 0 : barSize));
-        }
-        else if ((mouseEvent.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical)
-                 || (mouseEvent.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal))
-        {
-            Position = Math.Min (Position + 1, Size - barSize + (KeepContentInAllViewport ? 0 : barSize));
-        }
-        else if ((mouseEvent.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical)
-                 || (mouseEvent.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal))
+    private void OnSliderOnFrameChanged (object? sender, EventArgs<Rectangle> args)
+    {
+        if (ViewportDimension == 0)
         {
-            Position = Math.Max (Position - 1, 0);
+            return;
         }
-        else if (mouseEvent.Flags == MouseFlags.Button1Clicked)
-        {
-            if (_slider.Frame.Contains (mouseEvent.Position))
-            {
-                //return _slider.OnMouseEvent (mouseEvent);
-            }
-        }
-
-        return false;
+        int framePos = Orientation == Orientation.Vertical ? args.CurrentValue.Y : args.CurrentValue.X;
+        double pos = ((double)ViewportDimension * ViewportDimension / (Size)) * framePos;
+        RaisePositionChangeEvents (_position, (int)pos);
     }
 
-    /// <summary>Virtual method called when <see cref="Position"/> has changed. Raises <see cref="PositionChanged"/>.</summary>
-    protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (in position)); }
-
     /// <summary>
-    ///     Virtual method called when <see cref="Position"/> is changing. Raises <see cref="PositionChanging"/>, which is
-    ///     cancelable.
+    ///     Gets or sets the position of the start of the Scroll slider, relative to <see cref="Size"/>.
     /// </summary>
-    protected virtual CancelEventArgs<int> OnPositionChanging (int currentPos, int newPos)
+    public int Position
+    {
+        get => _position;
+        set => RaisePositionChangeEvents (_position, value);
+    }
+
+    private void RaisePositionChangeEvents (int current, int value)
     {
-        CancelEventArgs<int> args = new (ref currentPos, ref newPos);
+        if (OnPositionChanging (current, value))
+        {
+            _slider.Position = current;
+            return;
+        }
+
+        CancelEventArgs<int> args = new (ref current, ref value);
         PositionChanging?.Invoke (this, args);
 
-        return args;
-    }
+        if (args.Cancel)
+        {
+            _slider.Position = current;
+            return;
+        }
 
-    /// <summary>Called when <see cref="Size"/> has changed. Raises <see cref="SizeChanged"/>.</summary>
-    protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); }
+        // This sets the slider position and clamps the value
+        _slider.Position = value;
 
-    internal void AdjustScroll ()
-    {
-        if (SuperViewAsScrollBar is { })
+        if (_slider.Position == _position)
         {
-            X = Orientation == Orientation.Vertical ? 0 : 1;
-            Y = Orientation == Orientation.Vertical ? 1 : 0;
-            Width = Orientation == Orientation.Vertical ? Dim.Fill () : Dim.Fill (1);
-            Height = Orientation == Orientation.Vertical ? Dim.Fill (1) : Dim.Fill ();
+            return;
         }
 
-        _slider.AdjustSlider ();
-        SetScrollText ();
+        _position = value;
+
+        OnPositionChanged (_position);
+        PositionChanged?.Invoke (this, new (in value));
     }
 
-    /// <inheritdoc/>
-    internal override void OnLayoutStarted (LayoutEventArgs args)
+    /// <summary>
+    ///     Called when <see cref="Position"/> is changing. Return true to cancel the change.
+    /// </summary>
+    protected virtual bool OnPositionChanging (int currentPos, int newPos)
     {
-        AdjustScroll ();
+        return false;
     }
 
-    internal ScrollBar? SuperViewAsScrollBar => SuperView as ScrollBar;
+    /// <summary>
+    ///     Raised when the <see cref="Position"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
+    ///     <see langword="true"/> to prevent the position from being changed.
+    /// </summary>
+    public event EventHandler<CancelEventArgs<int>>? PositionChanging;
 
-    private int BarSize => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
+    /// <summary>Called when <see cref="Position"/> has changed.</summary>
+    protected virtual void OnPositionChanged (int position) { }
 
-    private int SetPosition (int position)
-    {
-        int barSize = BarSize;
+    /// <summary>Raised when the <see cref="Position"/> has changed.</summary>
+    public event EventHandler<EventArgs<int>>? PositionChanged;
 
-        if (position + barSize > Size + (KeepContentInAllViewport ? 0 : barSize) - (SuperViewAsScrollBar is { } ? 2 : 0))
-        {
-            return KeepContentInAllViewport ? Math.Max (Size - barSize - (SuperViewAsScrollBar is { } ? 2 : 0), 0) : Math.Max (Size - 1, 0);
-        }
 
-        return position;
+    /// <inheritdoc/>
+    protected override bool OnClearingViewport ()
+    {
+        FillRect (Viewport, Glyphs.Stipple);
+
+        return true;
     }
 
-    private void SetScrollText ()
-    {
-        TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
 
-        // QUESTION: Should these Glyphs be configurable via CM?
-        Text = string.Concat (
-                              Enumerable.Repeat (
-                                                 Glyphs.Stipple.ToString (),
-                                                 GetContentSize ().Width * GetContentSize ().Height));
+    /// <inheritdoc />
+    public bool EnableForDesign ()
+    {
+        OrientationChanged += (sender, args) =>
+                              {
+                                  if (args.CurrentValue == Orientation.Vertical)
+                                  {
+                                      Width = 1;
+                                      Height = Dim.Fill ();
+                                  }
+                                  else
+                                  {
+                                      Width = Dim.Fill ();
+                                      Height = 1;
+                                  }
+                              };
+
+        Width = 1;
+        Height = Dim.Fill ();
+        Size = 1000;
+        Position = 10;
+
+
+
+        return true;
     }
 }

+ 185 - 154
Terminal.Gui/Views/Scroll/ScrollBar.cs

@@ -6,50 +6,116 @@ namespace Terminal.Gui;
 
 /// <summary>
 ///     Provides a visual indicator that content can be scrolled. ScrollBars consist of two buttons, one each for scrolling
-///     forward or backwards, a Scroll that can be clicked to scroll large amounts, and a ScrollSlider that can be dragged
+///     forward or backwards, a <see cref="Scroll"/> that can be dragged
 ///     to scroll continuously. ScrollBars can be oriented either horizontally or vertically and support the user dragging
 ///     and clicking with the mouse to scroll.
 /// </summary>
 /// <remarks>
 ///     <para>
-///         <see cref="Position"/> indicates the current location between zero and <see cref="Size"/>.
+///         <see cref="Position"/> indicates the number of rows or columns the Scroll has moved from 0.
 ///     </para>
-///     <para>If the scrollbar is larger than three cells, arrow indicators are drawn.</para>
 /// </remarks>
-public class ScrollBar : View
+public class ScrollBar : View, IOrientation, IDesignable
 {
+    private readonly Scroll _scroll;
+    private readonly Button _decreaseButton;
+    private readonly Button _increaseButton;
+
     /// <inheritdoc/>
     public ScrollBar ()
     {
+        CanFocus = false;
+
         _scroll = new ();
-        _decrease = new ()
+        _scroll.PositionChanging += OnScrollOnPositionChanging;
+        _scroll.PositionChanged += OnScrollOnPositionChanged;
+        _scroll.SizeChanged += OnScrollOnSizeChanged;
+
+        _decreaseButton = new ()
         {
+            CanFocus = false,
             NoDecorations = true,
             NoPadding = true,
+            ShadowStyle = ShadowStyle.None,
+            WantContinuousButtonPressed = true
         };
-        _increase = new ()
+        _decreaseButton.Accepting += OnDecreaseButtonOnAccept;
+
+        _increaseButton = new ()
         {
+            CanFocus = false,
             NoDecorations = true,
             NoPadding = true,
+            ShadowStyle = ShadowStyle.None,
+            WantContinuousButtonPressed = true
         };
-        Add (_scroll, _decrease, _increase);
+        _increaseButton.Accepting += OnIncreaseButtonOnAccept;
+        Add (_decreaseButton, _scroll, _increaseButton);
 
-        CanFocus = false;
-        Orientation = Orientation.Vertical;
-        Width = Dim.Auto (DimAutoStyle.Content, 1);
-        Height = Dim.Auto (DimAutoStyle.Content, 1);
+        _orientationHelper = new (this); // Do not use object initializer!
+        _orientationHelper.Orientation = Orientation.Vertical;
+        _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+        _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
+
+        // This sets the width/height etc...
+        OnOrientationChanged (Orientation);
 
-        _scroll.PositionChanging += Scroll_PositionChanging;
-        _scroll.PositionChanged += Scroll_PositionChanged;
-        _scroll.SizeChanged += _scroll_SizeChanged;
+        return;
+
+        void OnDecreaseButtonOnAccept (object? s, CommandEventArgs e)
+        {
+            _scroll.Position--;
+            e.Cancel = true;
+        }
+
+        void OnIncreaseButtonOnAccept (object? s, CommandEventArgs e)
+        {
+            _scroll.Position++;
+            e.Cancel = true;
+        }
     }
 
-    private readonly Scroll _scroll;
-    private readonly Button _decrease;
-    private readonly Button _increase;
+    #region IOrientation members
+
+    private readonly OrientationHelper _orientationHelper;
+
+    /// <inheritdoc/>
+    public Orientation Orientation
+    {
+        get => _orientationHelper.Orientation;
+        set => _orientationHelper.Orientation = value;
+    }
+
+    /// <inheritdoc/>
+    public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
+
+    /// <inheritdoc/>
+    public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
+
+    /// <inheritdoc/>
+    public void OnOrientationChanged (Orientation newOrientation)
+    {
+        TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
+        TextAlignment = Alignment.Center;
+        VerticalTextAlignment = Alignment.Center;
+
+        if (Orientation == Orientation.Vertical)
+        {
+            Width = 1;
+            Height = Dim.Fill ();
+        }
+        else
+        {
+            Width = Dim.Fill ();
+            Height = 1;
+        }
+
+        _scroll.Orientation = newOrientation;
+    }
+
+    #endregion
 
     private bool _autoHide = true;
-    private bool _showScrollIndicator = true;
 
     /// <summary>
     ///     Gets or sets whether <see cref="View.Visible"/> will be set to <see langword="false"/> if the dimension of the
@@ -63,27 +129,55 @@ public class ScrollBar : View
             if (_autoHide != value)
             {
                 _autoHide = value;
-                AdjustAll ();
+
+                if (!AutoHide)
+                {
+                    Visible = true;
+                }
+
+                SetNeedsLayout ();
             }
         }
     }
 
-    /// <summary>Get or sets if the view-port is kept in all visible area of this <see cref="ScrollBar"/>.</summary>
-    public bool KeepContentInAllViewport
-    {
-        get => _scroll.KeepContentInAllViewport;
-        set => _scroll.KeepContentInAllViewport = value;
-    }
+    /// <inheritdoc/>
+    protected override void OnFrameChanged (in Rectangle frame) { ShowHide (); }
 
-    /// <summary>Gets or sets if a scrollbar is vertical or horizontal.</summary>
-    public Orientation Orientation
+    private void ShowHide ()
     {
-        get => _scroll.Orientation;
-        set
+        if (!AutoHide || !IsInitialized)
+        {
+            return;
+        }
+
+        if (Orientation == Orientation.Vertical)
         {
-            Resize (value);
-            _scroll.Orientation = value;
+            Visible = Frame.Height - (_decreaseButton.Frame.Height + _increaseButton.Frame.Height) < Size;
         }
+        else
+        {
+            Visible = Frame.Width - (_decreaseButton.Frame.Width + _increaseButton.Frame.Width) < Size;
+        }
+    }
+
+    /// <summary>
+    ///     Gets or sets whether the Scroll will show the percentage the slider
+    ///     takes up within the <see cref="Size"/>.
+    /// </summary>
+    public bool ShowPercent
+    {
+        get => _scroll.ShowPercent;
+        set => _scroll.ShowPercent = value;
+    }
+
+
+    /// <summary>Get or sets if the view-port is kept in all visible area of this <see cref="ScrollBar"/>.</summary>
+    public bool KeepContentInAllViewport
+    {
+        //get => _scroll.KeepContentInAllViewport;
+        //set => _scroll.KeepContentInAllViewport = value;
+        get;
+        set;
     }
 
     /// <summary>Gets or sets the position, relative to <see cref="Size"/>, to set the scrollbar at.</summary>
@@ -91,13 +185,12 @@ public class ScrollBar : View
     public int Position
     {
         get => _scroll.Position;
-        set
-        {
-            _scroll.Position = value;
-            AdjustAll ();
-        }
+        set => _scroll.Position = value;
     }
 
+    private void OnScrollOnPositionChanged (object? sender, EventArgs<int> e) { PositionChanged?.Invoke (this, e); }
+    private void OnScrollOnPositionChanging (object? sender, CancelEventArgs<int> e) { PositionChanging?.Invoke (this, e); }
+
     /// <summary>Raised when the <see cref="Position"/> has changed.</summary>
     public event EventHandler<EventArgs<int>>? PositionChanged;
 
@@ -107,149 +200,87 @@ public class ScrollBar : View
     /// </summary>
     public event EventHandler<CancelEventArgs<int>>? PositionChanging;
 
-    /// <summary>Gets or sets the visibility for the vertical or horizontal scroll indicator.</summary>
-    /// <value><c>true</c> if show vertical or horizontal scroll indicator; otherwise, <c>false</c>.</value>
-    public bool ShowScrollIndicator
-    {
-        get => Visible;
-        set
-        {
-            if (value == _showScrollIndicator)
-            {
-                return;
-            }
-
-            _showScrollIndicator = value;
-
-            if (IsInitialized)
-            {
-                SetNeedsLayout ();
-
-                if (value)
-                {
-                    Visible = true;
-                }
-                else
-                {
-                    Visible = false;
-                    Position = 0;
-                }
-
-                AdjustAll ();
-            }
-        }
-    }
-
     /// <summary>
     ///     Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through.
     /// </summary>
     public int Size
     {
         get => _scroll.Size;
-        set
-        {
-            _scroll.Size = value;
-            AdjustAll ();
-        }
+        set => _scroll.Size = value;
     }
 
     /// <summary>Raised when <see cref="Size"/> has changed.</summary>
     public event EventHandler<EventArgs<int>>? SizeChanged;
 
-    /// <inheritdoc/>
-    /// <inheritdoc />
-    internal override void OnLayoutStarted (LayoutEventArgs args) 
+    private void OnScrollOnSizeChanged (object? sender, EventArgs<int> e)
     {
-        AdjustAll ();
+        ShowHide ();
+        SizeChanged?.Invoke (this, e);
     }
 
-    private void _scroll_SizeChanged (object? sender, EventArgs<int> e) { SizeChanged?.Invoke (this, e); }
+    /// <inheritdoc/>
+    protected override void OnSubviewLayout (LayoutEventArgs args) { PositionSubviews (); }
 
-    private void AdjustAll ()
+    private void PositionSubviews ()
     {
-        CheckVisibility ();
-        _scroll.AdjustScroll ();
-
         if (Orientation == Orientation.Vertical)
         {
-            _decrease.Y = 0;
-            _decrease.X = 0;
-            _decrease.Width = Dim.Fill ();
-            _decrease.Height = 1;
-            _decrease.Text = Glyphs.DownArrow.ToString ();
-            _increase.Y = Pos.Bottom (_scroll);
-            _increase.X = 0;
-            _increase.Width = Dim.Fill ();
-            _increase.Height = 1;
-            _increase.Text = Glyphs.UpArrow.ToString ();
+            _decreaseButton.Y = 0;
+            _decreaseButton.X = 0;
+            _decreaseButton.Width = Dim.Fill ();
+            _decreaseButton.Height = 1;
+            _decreaseButton.Title = Glyphs.UpArrow.ToString ();
+            _increaseButton.Y = Pos.Bottom (_scroll);
+            _increaseButton.X = 0;
+            _increaseButton.Width = Dim.Fill ();
+            _increaseButton.Height = 1;
+            _increaseButton.Title = Glyphs.DownArrow.ToString ();
+            _scroll.X = 0;
+            _scroll.Y = Pos.Bottom (_decreaseButton);
+            _scroll.Height = Dim.Fill (1);
+            _scroll.Width = Dim.Fill ();
         }
         else
         {
-            _decrease.Y = 0;
-            _decrease.X = 0;
-            _decrease.Width = 1;
-            _decrease.Height = Dim.Fill ();
-            _decrease.Text = Glyphs.LeftArrow.ToString ();
-            _increase.Y = 0;
-            _increase.X = Pos.Right (_scroll);
-            _increase.Width = 1;
-            _increase.Height = Dim.Fill ();
-            _increase.Text = Glyphs.RightArrow.ToString ();
+            _decreaseButton.Y = 0;
+            _decreaseButton.X = 0;
+            _decreaseButton.Width = 1;
+            _decreaseButton.Height = Dim.Fill ();
+            _decreaseButton.Title = Glyphs.LeftArrow.ToString ();
+            _increaseButton.Y = 0;
+            _increaseButton.X = Pos.Right (_scroll);
+            _increaseButton.Width = 1;
+            _increaseButton.Height = Dim.Fill ();
+            _increaseButton.Title = Glyphs.RightArrow.ToString ();
+            _scroll.Y = 0;
+            _scroll.X = Pos.Bottom (_decreaseButton);
+            _scroll.Width = Dim.Fill (1);
+            _scroll.Height = Dim.Fill ();
         }
     }
 
-    private bool CheckVisibility ()
+    /// <inheritdoc/>
+    public bool EnableForDesign ()
     {
-        if (!AutoHide)
-        {
-            if (Visible != _showScrollIndicator)
-            {
-                Visible = _showScrollIndicator;
-                SetNeedsDisplay ();
-            }
-
-            return _showScrollIndicator;
-        }
-
-        int barSize = Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
-
-        if (barSize == 0 || barSize >= Size)
-        {
-            if (Visible)
-            {
-                Visible = false;
-                SetNeedsDisplay ();
-
-                return false;
-            }
-        }
-        else
-        {
-            if (!Visible)
-            {
-                Visible = true;
-                SetNeedsDisplay ();
-            }
-        }
-
+        OrientationChanged += (sender, args) =>
+                              {
+                                  if (args.CurrentValue == Orientation.Vertical)
+                                  {
+                                      Width = 1;
+                                      Height = Dim.Fill ();
+                                  }
+                                  else
+                                  {
+                                      Width = Dim.Fill ();
+                                      Height = 1;
+                                  }
+                              };
+
+        Width = 1;
+        Height = Dim.Fill ();
+        Size = 200;
+        Position = 10;
+        //ShowPercent = true;
         return true;
     }
-
-    private void Resize (Orientation orientation)
-    {
-        switch (orientation)
-        {
-            case Orientation.Horizontal:
-
-                break;
-            case Orientation.Vertical:
-                break;
-            default:
-                throw new ArgumentOutOfRangeException (nameof (orientation), orientation, null);
-        }
-    }
-
-    private void Scroll_PositionChanged (object? sender, EventArgs<int> e) { PositionChanged?.Invoke (this, e); }
-
-    private void Scroll_PositionChanging (object? sender, CancelEventArgs<int> e) { PositionChanging?.Invoke (this, e); }
 }

+ 210 - 166
Terminal.Gui/Views/Scroll/ScrollSlider.cs

@@ -1,249 +1,293 @@
 #nullable enable
 
-using System.ComponentModel;
+using System.Diagnostics;
 
 namespace Terminal.Gui;
 
-internal class ScrollSlider : View
+/// <summary>
+///     The ScrollSlider can be dragged with the mouse, constrained by the size of the Viewport of it's superview. The ScrollSlider can be
+///     oriented either vertically or horizontally.
+/// </summary>
+/// <remarks>
+///     <para>
+///         If <see cref="View.Text"/> is set, it will be displayed centered within the slider. Set
+///         <see cref="ShowPercent"/> to automatically have the Text
+///         be show what percent the slider is to the Superview's Viewport size.
+///     </para>
+///     <para>
+///        Used to represent the proportion of the visible content to the Viewport in a <see cref="Scroll"/>.
+///     </para>
+/// </remarks>
+public class ScrollSlider : View, IOrientation, IDesignable
 {
+    /// <summary>
+    ///     Initializes a new instance.
+    /// </summary>
     public ScrollSlider ()
     {
         Id = "scrollSlider";
-        Width = Dim.Auto (DimAutoStyle.Content);
-        Height = Dim.Auto (DimAutoStyle.Content);
         WantMousePositionReports = true;
+
+        _orientationHelper = new (this); // Do not use object initializer!
+        _orientationHelper.Orientation = Orientation.Vertical;
+        _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+        _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
+
+        OnOrientationChanged (Orientation);
+
+        HighlightStyle = HighlightStyle.Hover;
+
+        // Default size is 1
+        Size = 1;
     }
 
-    private int _lastLocation = -1;
-    private ColorScheme? _savedColorScheme;
+    #region IOrientation members
+    private readonly OrientationHelper _orientationHelper;
 
-    public void AdjustSlider ()
+    /// <inheritdoc/>
+    public Orientation Orientation
     {
-        if (!IsInitialized)
-        {
-            return;
-        }
-
-        (int Location, int Dimension) sliderLocationAndDimension = GetSliderLocationDimensionFromPosition ();
-        X = SuperViewAsScroll.Orientation == Orientation.Vertical ? 0 : sliderLocationAndDimension.Location;
-        Y = SuperViewAsScroll.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Location : 0;
-
-        SetContentSize (
-                        new (
-                             SuperViewAsScroll.Orientation == Orientation.Vertical
-                                 ? SuperViewAsScroll.GetContentSize ().Width
-                                 : sliderLocationAndDimension.Dimension,
-                             SuperViewAsScroll.Orientation == Orientation.Vertical
-                                 ? sliderLocationAndDimension.Dimension
-                                 : SuperViewAsScroll.GetContentSize ().Height
-                            ));
-        SetSliderText ();
+        get => _orientationHelper.Orientation;
+        set => _orientationHelper.Orientation = value;
     }
 
     /// <inheritdoc/>
-    public override Attribute GetNormalColor ()
+    public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
+
+    /// <inheritdoc/>
+    public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
+
+    /// <inheritdoc/>
+    public void OnOrientationChanged (Orientation newOrientation)
     {
-        if (_savedColorScheme is null)
+        TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
+        TextAlignment = Alignment.Center;
+        VerticalTextAlignment = Alignment.Center;
+
+        // Reset Position to 0 when changing orientation
+        X = 0;
+        Y = 0;
+
+        // Reset Size to 1 when changing orientation
+        if (Orientation == Orientation.Vertical)
         {
-            ColorScheme = new () { Normal = new (SuperViewAsScroll.ColorScheme.HotNormal.Foreground, SuperViewAsScroll.ColorScheme.HotNormal.Foreground) };
+            Width = Dim.Fill ();
+            Height = 1;
         }
         else
         {
-            ColorScheme = new () { Normal = new (SuperViewAsScroll.ColorScheme.Normal.Foreground, SuperViewAsScroll.ColorScheme.Normal.Foreground) };
+            Width = 1;
+            Height = Dim.Fill ();
         }
-
-        return base.GetNormalColor ();
     }
 
+    #endregion
+
     /// <inheritdoc/>
-    /// <inheritdoc />
-    protected override bool OnMouseEnter (CancelEventArgs eventArgs)
+    protected override bool OnClearingViewport ()
     {
-        _savedColorScheme ??= SuperViewAsScroll.ColorScheme;
+        FillRect (Viewport, Glyphs.ContinuousMeterSegment);
 
-        ColorScheme = new ()
-        {
-            Normal = new (_savedColorScheme.HotNormal.Foreground, _savedColorScheme.HotNormal.Foreground),
-            Focus = new (_savedColorScheme.Focus.Foreground, _savedColorScheme.Focus.Foreground),
-            HotNormal = new (_savedColorScheme.Normal.Foreground, _savedColorScheme.Normal.Foreground),
-            HotFocus = new (_savedColorScheme.HotFocus.Foreground, _savedColorScheme.HotFocus.Foreground),
-            Disabled = new (_savedColorScheme.Disabled.Foreground, _savedColorScheme.Disabled.Foreground)
-        };
         return true;
     }
 
-    /// <inheritdoc/>
-    protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
+    /// <summary>
+    ///     Gets or sets whether the ScrollSlider will set <see cref="View.Text"/> to show the percentage the slider
+    ///     takes up within the <see cref="View.SuperView"/>'s Viewport.
+    /// </summary>
+    public bool ShowPercent { get; set; }
+
+    /// <summary>
+    ///     Gets or sets the size of the ScrollSlider. This is a helper that simply gets or sets the Width or Height depending on the
+    ///     <see cref="Orientation"/>. The size will be constrained such that the ScrollSlider will not go outside the Viewport of
+    ///     the <see cref="View.SuperView"/>. The size will never be less than 1.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         The dimension of the ScrollSlider that is perpendicular to the <see cref="Orientation"/> will be set to <see cref="Dim.Fill()"/>
+    ///     </para>
+    /// </remarks>
+    public int Size
     {
-        int location = SuperViewAsScroll.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X;
-        int offset = _lastLocation > -1 ? location - _lastLocation : 0;
-        int barSize = SuperViewAsScroll.Orientation == Orientation.Vertical ? SuperViewAsScroll.Viewport.Height : SuperViewAsScroll.Viewport.Width;
-
-        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1)
+        get
         {
-            if (Application.MouseGrabView != this)
+            if (Orientation == Orientation.Vertical)
             {
-                Application.GrabMouse (this);
-                _lastLocation = location;
+                return Frame.Height;
+            }
+            else
+            {
+                return Frame.Width;
             }
         }
-        else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
+        set
         {
-            if (SuperViewAsScroll.Orientation == Orientation.Vertical)
+            if (Orientation == Orientation.Vertical)
             {
-                Y = Frame.Y + offset < 0
-                        ? 0
-                        : Frame.Y + offset + Frame.Height > barSize
-                            ? Math.Max (barSize - Frame.Height, 0)
-                            : Frame.Y + offset;
-
-                SuperViewAsScroll.Position = GetPositionFromSliderLocation (Frame.Y);
+                Width = Dim.Fill ();
+                int viewport = Math.Max (1, SuperView?.Viewport.Height ?? 1);
+                Height = Math.Clamp (value, 1, viewport);
             }
             else
             {
-                X = Frame.X + offset < 0
-                        ? 0
-                        : Frame.X + offset + Frame.Width > barSize
-                            ? Math.Max (barSize - Frame.Width, 0)
-                            : Frame.X + offset;
-
-                SuperViewAsScroll.Position = GetPositionFromSliderLocation (Frame.X);
+                int viewport = Math.Max (1, SuperView?.Viewport.Width ?? 1);
+                Width = Math.Clamp (value, 1, viewport);
+                Height = Dim.Fill ();
             }
         }
-        else if (mouseEvent.Flags == MouseFlags.Button1Released)
-        {
-            _lastLocation = -1;
+    }
 
-            if (Application.MouseGrabView == this)
+    /// <summary>
+    ///     Gets or sets the position of the ScrollSlider. This is a helper that simply gets or sets the X or Y depending on the
+    ///     <see cref="Orientation"/>. The position will be constrained such that the ScrollSlider will not go outside the Viewport of
+    ///     the <see cref="View.SuperView"/>.
+    /// </summary>
+    public int Position
+    {
+        get
+        {
+            if (Orientation == Orientation.Vertical)
             {
-                Application.UngrabMouse ();
+                return Frame.Y;
+            }
+            else
+            {
+                return Frame.X;
             }
         }
-        else if ((mouseEvent.Flags == MouseFlags.WheeledDown && SuperViewAsScroll.Orientation == Orientation.Vertical)
-                 || (mouseEvent.Flags == MouseFlags.WheeledRight && SuperViewAsScroll.Orientation == Orientation.Horizontal))
-        {
-            SuperViewAsScroll.Position = Math.Min (
-                                                   SuperViewAsScroll.Position + 1,
-                                                   SuperViewAsScroll.KeepContentInAllViewport ? SuperViewAsScroll.Size - barSize : SuperViewAsScroll.Size - 1);
-        }
-        else if ((mouseEvent.Flags == MouseFlags.WheeledUp && SuperViewAsScroll.Orientation == Orientation.Vertical)
-                 || (mouseEvent.Flags == MouseFlags.WheeledLeft && SuperViewAsScroll.Orientation == Orientation.Horizontal))
-        {
-            SuperViewAsScroll.Position = Math.Max (SuperViewAsScroll.Position - 1, 0);
-        }
-        else if (mouseEvent.Flags != MouseFlags.ReportMousePosition)
+        set
         {
-            return base.OnMouseEvent (mouseEvent);
+            if (Orientation == Orientation.Vertical)
+            {
+                int viewport = Math.Max (1, SuperView?.Viewport.Height ?? 1);
+                Y = Math.Clamp (value, 0, viewport - Frame.Height);
+            }
+            else
+            {
+                int viewport = Math.Max (1, SuperView?.Viewport.Width ?? 1);
+                X = Math.Clamp (value, 0, viewport - Frame.Width);
+            }
         }
-
-        return true;
     }
 
     /// <inheritdoc/>
-    /// <inheritdoc />
-    protected override void OnMouseLeave ()
+    protected override bool OnDrawingText ()
     {
-        if (_savedColorScheme is { } /*&& !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)*/)
+        if (!ShowPercent)
         {
-            ColorScheme = _savedColorScheme;
-            _savedColorScheme = null;
+            return false;
         }
-    }
 
-    internal int GetPositionFromSliderLocation (int location)
-    {
-        if (SuperViewAsScroll.GetContentSize ().Height == 0 || SuperViewAsScroll.GetContentSize ().Width == 0)
+        if (Orientation == Orientation.Vertical)
         {
-            return 0;
+            Text = $"{(int)Math.Round ((double)Viewport.Height / SuperView!.GetContentSize ().Height * 100)}%";
         }
-
-        int scrollSize = SuperViewAsScroll.Orientation == Orientation.Vertical
-                             ? SuperViewAsScroll.GetContentSize ().Height
-                             : SuperViewAsScroll.GetContentSize ().Width;
-
-        // Ensure the Position is valid if the slider is at end
-        // We use Frame here instead of ContentSize because even if the slider has a margin or border, Frame indicates the actual size
-        if ((SuperViewAsScroll.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize)
-            || (SuperViewAsScroll.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize))
+        else
         {
-            return SuperViewAsScroll.Size - scrollSize + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize);
+            Text = $"{(int)Math.Round ((double)Viewport.Width / SuperView!.GetContentSize ().Width * 100)}%";
         }
 
-        return (int)Math.Min (
-                              Math.Round (
-                                          (double)(location * (SuperViewAsScroll.Size + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize))
-                                                   + location)
-                                          / scrollSize),
-                              SuperViewAsScroll.Size - scrollSize + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize));
+        return false;
     }
 
-    internal (int Location, int Dimension) GetSliderLocationDimensionFromPosition ()
+    /// <inheritdoc/>
+    public override Attribute GetNormalColor () { return base.GetHotNormalColor (); }
+
+    ///// <inheritdoc/>
+    private int _lastLocation = -1;
+
+    /// <inheritdoc/>
+    protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
     {
-        if (SuperViewAsScroll.GetContentSize ().Height == 0 || SuperViewAsScroll.GetContentSize ().Width == 0)
+        if (SuperView is null)
         {
-            return new (0, 0);
+            return false;
         }
 
-        int scrollSize = SuperViewAsScroll.Orientation == Orientation.Vertical
-                             ? SuperViewAsScroll.GetContentSize ().Height
-                             : SuperViewAsScroll.GetContentSize ().Width;
-        int location;
-        int dimension;
+        int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X;
+        int offset = _lastLocation > -1 ? location - _lastLocation : 0;
+        int superViewDimension = Orientation == Orientation.Vertical ? SuperView!.Viewport.Height : SuperView!.Viewport.Width;
 
-        if (SuperViewAsScroll.Size > 0)
+        if (mouseEvent.IsPressed || mouseEvent.IsReleased)
         {
-            dimension = (int)Math.Min (
-                                       Math.Max (
-                                                 Math.Ceiling (
-                                                               (double)scrollSize
-                                                               * scrollSize
-                                                               / (SuperViewAsScroll.Size + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize))),
-                                                 1),
-                                       scrollSize);
-
-            // Ensure the Position is valid
-            if (SuperViewAsScroll.Position > 0
-                && SuperViewAsScroll.Position + scrollSize > SuperViewAsScroll.Size + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize))
+            if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1)
             {
-                SuperViewAsScroll.Position = SuperViewAsScroll.KeepContentInAllViewport ? SuperViewAsScroll.Size - scrollSize : SuperViewAsScroll.Size - 1;
+                if (Application.MouseGrabView != this)
+                {
+                    Application.GrabMouse (this);
+                    _lastLocation = location;
+                }
             }
-
-            location = (int)Math.Min (
-                                      Math.Round (
-                                                  (double)SuperViewAsScroll.Position
-                                                  * scrollSize
-                                                  / (SuperViewAsScroll.Size + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize))),
-                                      scrollSize - dimension);
-
-            if (SuperViewAsScroll.Position == SuperViewAsScroll.Size - scrollSize + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize)
-                && location + dimension < scrollSize)
+            else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
             {
-                location = scrollSize - dimension;
+                if (Orientation == Orientation.Vertical)
+                {
+                    Y = Frame.Y + offset < 0
+                            ? 0
+                            : Frame.Y + offset + Frame.Height > superViewDimension
+                                ? Math.Max (superViewDimension - Frame.Height, 0)
+                                : Frame.Y + offset;
+                }
+                else
+                {
+                    X = Frame.X + offset < 0
+                            ? 0
+                            : Frame.X + offset + Frame.Width > superViewDimension
+                                ? Math.Max (superViewDimension - Frame.Width, 0)
+                                : Frame.X + offset;
+                }
+            }
+            else if (mouseEvent.Flags == MouseFlags.Button1Released)
+            {
+                _lastLocation = -1;
+
+                if (Application.MouseGrabView == this)
+                {
+                    Application.UngrabMouse ();
+                }
             }
         }
-        else
+
+        if (mouseEvent.IsWheel)
         {
-            location = 0;
-            dimension = scrollSize;
+            if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown) || mouseEvent.Flags.HasFlag (MouseFlags.WheeledRight))
+            {
+                offset = 1;
+            }
+            else if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown) || mouseEvent.Flags.HasFlag (MouseFlags.WheeledLeft))
+            {
+                offset = -1;
+            }
+
+            if (Orientation == Orientation.Vertical)
+            {
+                Y = Frame.Y + offset < 0
+                        ? 0
+                        : Frame.Y + offset + Frame.Height > superViewDimension
+                            ? Math.Max (superViewDimension - Frame.Height, 0)
+                            : Frame.Y + offset;
+            }
+            else
+            {
+                X = Frame.X + offset < 0
+                        ? 0
+                        : Frame.X + offset + Frame.Width > superViewDimension
+                            ? Math.Max (superViewDimension - Frame.Width, 0)
+                            : Frame.X + offset;
+            }
         }
 
-        return new (location, dimension);
+        return true;
     }
 
-    // TODO: I think you should create a new `internal` view named "ScrollSlider" with an `Orientation` property. It should inherit from View and override GetNormalColor and the mouse events
-    // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation.
-    // This will really simplify a lot of this.
-
-    private void SetSliderText ()
+    /// <inheritdoc />
+    public bool EnableForDesign ()
     {
-        TextDirection = SuperViewAsScroll.Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
+        Orientation = Orientation.Vertical;
+        Width = 1;
+        Height = 10;
+        ShowPercent = true;
 
-        // QUESTION: Should these Glyphs be configurable via CM?
-        Text = string.Concat (
-                              Enumerable.Repeat (
-                                                 Glyphs.ContinuousMeterSegment.ToString (),
-                                                 SuperViewAsScroll.GetContentSize ().Width * SuperViewAsScroll.GetContentSize ().Height));
+        return true;
     }
-
-    private Scroll SuperViewAsScroll => (SuperView as Scroll)!;
 }

+ 17 - 13
Terminal.Gui/Views/ScrollBarView.cs

@@ -5,6 +5,8 @@
 //   Miguel de Icaza ([email protected])
 //
 
+using System.Diagnostics;
+
 namespace Terminal.Gui;
 
 /// <summary>ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical</summary>
@@ -113,7 +115,7 @@ public class ScrollBarView : View
             if (_autoHideScrollBars != value)
             {
                 _autoHideScrollBars = value;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
         }
     }
@@ -259,7 +261,7 @@ public class ScrollBarView : View
             {
                 SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size);
                 ShowHideScrollBars (false);
-                SetNeedsDisplay ();
+                SetNeedsLayout ();
             }
         }
     }
@@ -444,7 +446,7 @@ public class ScrollBarView : View
     public virtual void OnChangedPosition () { ChangedPosition?.Invoke (this, EventArgs.Empty); }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
         if (ColorScheme is null || ((!ShowScrollIndicator || Size == 0) && AutoHideScrollBars && Visible))
         {
@@ -453,21 +455,21 @@ public class ScrollBarView : View
                 ShowHideScrollBars (false);
             }
 
-            return;
+            return false;
         }
 
         if (Size == 0 || (_vertical && Viewport.Height == 0) || (!_vertical && Viewport.Width == 0))
         {
-            return;
+            return false;
         }
 
-        Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
+        SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
 
         if (_vertical)
         {
             if (Viewport.Right < Viewport.Width - 1)
             {
-                return;
+                return true;
             }
 
             int col = Viewport.Width - 1;
@@ -575,7 +577,7 @@ public class ScrollBarView : View
         {
             if (Viewport.Bottom < Viewport.Height - 1)
             {
-                return;
+                return true;
             }
 
             int row = Viewport.Height - 1;
@@ -661,6 +663,8 @@ public class ScrollBarView : View
                 Driver.AddRune (Glyphs.RightArrow);
             }
         }
+
+        return false;
     }
 
 
@@ -761,11 +765,11 @@ public class ScrollBarView : View
 
     private void ContentBottomRightCorner_DrawContent (object sender, DrawEventArgs e)
     {
-        Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
+        SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
 
         // I'm forced to do this here because the Clear method is
         // changing the color attribute and is different of this one
-        Driver.FillRect (Driver.Clip);
+        Driver.FillRect (Driver.Clip.GetBounds());
         e.Cancel = true;
     }
 
@@ -823,7 +827,7 @@ public class ScrollBarView : View
             _contentBottomRightCorner.Width = 1;
             _contentBottomRightCorner.Height = 1;
             _contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick;
-            _contentBottomRightCorner.DrawContent += ContentBottomRightCorner_DrawContent;
+            _contentBottomRightCorner.DrawingContent += ContentBottomRightCorner_DrawContent;
         }
     }
 
@@ -899,7 +903,7 @@ public class ScrollBarView : View
         if (newPosition < 0)
         {
             _position = 0;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return;
         }
@@ -924,7 +928,7 @@ public class ScrollBarView : View
         }
 
         OnChangedPosition ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     // BUGBUG: v2 - rationalize this with View.SetMinWidthHeight

+ 19 - 9
Terminal.Gui/Views/ScrollView.cs

@@ -191,7 +191,7 @@ public class ScrollView : View
                     _horizontal.AutoHideScrollBars = value;
                 }
 
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
         }
     }
@@ -228,7 +228,7 @@ public class ScrollView : View
     //            _contentView.Frame = new Rectangle (_contentOffset, value);
     //            _vertical.Size = GetContentSize ().Height;
     //            _horizontal.Size = GetContentSize ().Width;
-    //            SetNeedsDisplay ();
+    //            SetNeedsDraw ();
     //        }
     //    }
     //}
@@ -372,19 +372,23 @@ public class ScrollView : View
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        SetViewsNeedsDisplay ();
+        SetViewsNeedsDraw ();
 
         // TODO: It's bad practice for views to always clear a view. It negates clipping.
-        Clear ();
+        ClearViewport ();
 
         if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0)
         {
+            Region? saved = ClipFrame();
             _contentView.Draw ();
+            View.SetClip (saved);
         }
 
         DrawScrollBars ();
+
+        return true;
     }
 
     /// <inheritdoc/>
@@ -467,7 +471,7 @@ public class ScrollView : View
             return view;
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         View container = view?.SuperView;
 
         if (container == this)
@@ -580,18 +584,24 @@ public class ScrollView : View
         {
             if (ShowVerticalScrollIndicator)
             {
+                Region? saved = View.SetClipToScreen ();
                 _vertical.Draw ();
+                View.SetClip (saved);
             }
 
             if (ShowHorizontalScrollIndicator)
             {
+                Region? saved = View.SetClipToScreen ();
                 _horizontal.Draw ();
+                View.SetClip (saved);
             }
 
             if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator)
             {
                 SetContentBottomRightCornerVisibility ();
+                Region? saved = View.SetClipToScreen ();
                 _contentBottomRightCorner.Draw ();
+                View.SetClip (saved);
             }
         }
     }
@@ -626,14 +636,14 @@ public class ScrollView : View
         {
             _horizontal.Position = Math.Max (0, -_contentOffset.X);
         }
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
-    private void SetViewsNeedsDisplay ()
+    private void SetViewsNeedsDraw ()
     {
         foreach (View view in _contentView.Subviews)
         {
-            view.SetNeedsDisplay ();
+            view.SetNeedsDraw ();
         }
     }
 

+ 140 - 142
Terminal.Gui/Views/Shortcut.cs

@@ -96,6 +96,12 @@ public class Shortcut : View, IOrientation, IDesignable
 
         HighlightStyle = HighlightStyle.None;
         CanFocus = true;
+
+        if (Border is { })
+        {
+            Border.Settings &= ~BorderSettings.Title;
+        }
+
         Width = GetWidthDimAuto ();
         Height = Dim.Auto (DimAutoStyle.Content, 1);
 
@@ -109,64 +115,43 @@ public class Shortcut : View, IOrientation, IDesignable
 
         CommandView = new ()
         {
+            Id = "CommandView",
             Width = Dim.Auto (),
-            Height = Dim.Auto (DimAutoStyle.Auto, 1)
+            Height = Dim.Fill()
         };
+        Title = commandText ?? string.Empty;
 
         HelpView.Id = "_helpView";
         HelpView.CanFocus = false;
         HelpView.Text = helpText ?? string.Empty;
-        Add (HelpView);
 
         KeyView.Id = "_keyView";
         KeyView.CanFocus = false;
-        Add (KeyView);
-
-        LayoutStarted += OnLayoutStarted;
-        Initialized += OnInitialized;
-
         key ??= Key.Empty;
         Key = key;
-        Title = commandText ?? string.Empty;
-        Action = action;
 
-        return;
-
-        void OnInitialized (object? sender, EventArgs e)
-        {
-            SuperViewRendersLineCanvas = true;
-            Border.Settings &= ~BorderSettings.Title;
-
-            ShowHide ();
-
-            // Force Width to DimAuto to calculate natural width and then set it back
-            Dim savedDim = Width;
-            Width = GetWidthDimAuto ();
-            _minimumDimAutoWidth = Frame.Width;
-            Width = savedDim;
-
-            SetCommandViewDefaultLayout ();
-            SetHelpViewDefaultLayout ();
-            SetKeyViewDefaultLayout ();
+        Action = action;
 
-            SetColors ();
-        }
+        SubviewLayout += OnLayoutStarted;
 
-        // Helper to set Width consistently
-        Dim GetWidthDimAuto ()
-        {
-            // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this.
-            return Dim.Auto (
-                             DimAutoStyle.Content,
-                             Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width)),
-                             Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width)))!;
-        }
+        ShowHide ();
     }
 
+    // Helper to set Width consistently
+    internal Dim GetWidthDimAuto ()
+    {
+        return Dim.Auto (
+                         DimAutoStyle.Content,
+                         minimumContentDim: Dim.Func (() => _minimumNaturalWidth ?? 0),
+                         maximumContentDim: Dim.Func (() => _minimumNaturalWidth ?? 0))!;
+}
+
     private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast;
 
-    // This is used to calculate the minimum width of the Shortcut when the width is NOT Dim.Auto
-    private int? _minimumDimAutoWidth;
+    // This is used to calculate the minimum width of the Shortcut when Width is NOT Dim.Auto
+    // It is calculated by setting Width to DimAuto temporarily and forcing layout.
+    // Once Frame.Width gets below this value, LayoutStarted makes HelpView an KeyView smaller.
+    private int? _minimumNaturalWidth;
 
     /// <inheritdoc/>
     protected override bool OnHighlight (CancelEventArgs<HighlightStyle> args)
@@ -210,101 +195,84 @@ public class Shortcut : View, IOrientation, IDesignable
         if (CommandView.Visible)
         {
             Add (CommandView);
+            SetCommandViewDefaultLayout ();
         }
 
         if (HelpView.Visible && !string.IsNullOrEmpty (HelpView.Text))
         {
             Add (HelpView);
+            SetHelpViewDefaultLayout ();
         }
 
         if (KeyView.Visible && Key != Key.Empty)
         {
             Add (KeyView);
+            SetKeyViewDefaultLayout ();
         }
+
+        SetColors ();
     }
 
-    private Thickness GetMarginThickness ()
+    // Force Width to DimAuto to calculate natural width and then set it back
+    private void ForceCalculateNaturalWidth ()
     {
-        if (Orientation == Orientation.Vertical)
-        {
-            return new (1, 0, 1, 0);
-        }
+        // Get the natural size of each subview
+        CommandView.SetRelativeLayout (Application.Screen.Size);
+        HelpView.SetRelativeLayout (Application.Screen.Size);
+        KeyView.SetRelativeLayout (Application.Screen.Size);
 
+        _minimumNaturalWidth = PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width);
+
+        // Reset our relative layout
+        SetRelativeLayout (SuperView?.GetContentSize() ?? Application.Screen.Size);
+    }
+
+    // TODO: Enable setting of the margin thickness
+    private Thickness GetMarginThickness ()
+    {
         return new (1, 0, 1, 0);
     }
 
     // When layout starts, we need to adjust the layout of the HelpView and KeyView
     private void OnLayoutStarted (object? sender, LayoutEventArgs e)
     {
-        if (Width is DimAuto widthAuto)
+        ShowHide ();
+        ForceCalculateNaturalWidth ();
+
+        if (Width is DimAuto widthAuto || HelpView!.Margin is null)
         {
-            _minimumDimAutoWidth = Frame.Width;
+            return;
         }
-        else
-        {
-            if (string.IsNullOrEmpty (HelpView.Text))
-            {
-                return;
-            }
-
-            int currentWidth = Frame.Width;
-
-            // If our width is smaller than the natural width then reduce width of HelpView first.
-            // Then KeyView.
-            // Don't ever reduce CommandView (it should spill).
-            // When Horizontal, Key is first, then Help, then Command.
-            // When Vertical, Command is first, then Help, then Key.
-            // BUGBUG: This does not do what the above says.
-            // TODO: Add Unit tests for this.
-            if (currentWidth < _minimumDimAutoWidth)
-            {
-                int delta = _minimumDimAutoWidth.Value - currentWidth;
-                int maxHelpWidth = int.Max (0, HelpView.Text.GetColumns () + Margin.Thickness.Horizontal - delta);
-
-                switch (maxHelpWidth)
-                {
-                    case 0:
-                        // Hide HelpView
-                        HelpView.Visible = false;
-                        HelpView.X = 0;
-
-                        break;
 
-                    case 1:
-                        // Scrunch it by removing margins
-                        HelpView.Margin.Thickness = new (0, 0, 0, 0);
+        // Frame.Width is smaller than the natural width. Reduce width of HelpView.
+        _maxHelpWidth = int.Max (0, GetContentSize ().Width - CommandView.Frame.Width - KeyView.Frame.Width);
 
-                        break;
+        if (_maxHelpWidth < 3)
+        {
+            Thickness t = GetMarginThickness ();
 
-                    case 2:
-                        // Scrunch just the right margin
-                        Thickness t = GetMarginThickness ();
-                        HelpView.Margin.Thickness = new (t.Right, t.Top, t.Left - 1, t.Bottom);
+            switch (_maxHelpWidth)
+            {
+                case 0:
+                case 1:
+                    // Scrunch it by removing both margins
+                    HelpView.Margin.Thickness = new (t.Right - 1, t.Top, t.Left - 1, t.Bottom);
 
-                        break;
+                    break;
 
-                    default:
-                        // Default margin
-                        HelpView.Margin.Thickness = GetMarginThickness ();
+                case 2:
 
-                        break;
-                }
+                    // Scrunch just the right margin
+                    HelpView.Margin.Thickness = new (t.Right, t.Top, t.Left - 1, t.Bottom);
 
-                if (maxHelpWidth > 0)
-                {
-                    HelpView.X = Pos.Align (Alignment.End, AlignmentModes);
-
-                    // Leverage Dim.Auto's max:
-                    HelpView.Width = Dim.Auto (DimAutoStyle.Text, maximumContentDim: maxHelpWidth);
-                    HelpView.Visible = true;
-                }
-            }
-            else
-            {
-                // Reset to default
-                SetHelpViewDefaultLayout ();
+                    break;
             }
         }
+        else
+        {
+            // Reset to default
+            HelpView.Margin.Thickness = GetMarginThickness ();
+        }
     }
 
 
@@ -387,14 +355,7 @@ public class Shortcut : View, IOrientation, IDesignable
     ///     <see cref="Orientation.Horizontal"/>.
     /// </summary>
     /// <remarks>
-    ///     <para>
-    ///         Horizontal orientation arranges the command, help, and key parts of each <see cref="Shortcut"/>s from right to
-    ///         left
-    ///         Vertical orientation arranges the command, help, and key parts of each <see cref="Shortcut"/>s from left to
-    ///         right.
-    ///     </para>
     /// </remarks>
-
     public Orientation Orientation
     {
         get => _orientationHelper.Orientation;
@@ -504,13 +465,9 @@ public class Shortcut : View, IOrientation, IDesignable
             Title = _commandView.Text;
 
             _commandView.Selecting += CommandViewOnSelecting;
-
             _commandView.Accepting += CommandViewOnAccepted;
 
-            SetCommandViewDefaultLayout ();
-            SetHelpViewDefaultLayout ();
-            SetKeyViewDefaultLayout ();
-            ShowHide ();
+            //ShowHide ();
             UpdateKeyBindings (Key.Empty);
 
             return;
@@ -536,10 +493,17 @@ public class Shortcut : View, IOrientation, IDesignable
 
     private void SetCommandViewDefaultLayout ()
     {
-        CommandView.Margin.Thickness = GetMarginThickness ();
+        if (CommandView.Margin is { })
+        {
+            CommandView.Margin.Thickness = GetMarginThickness ();
+        }
+
         CommandView.X = Pos.Align (Alignment.End, AlignmentModes);
-        CommandView.Y = 0; //Pos.Center ();
-        HelpView.HighlightStyle = HighlightStyle.None;
+
+        CommandView.VerticalTextAlignment = Alignment.Center;
+        CommandView.TextAlignment = Alignment.Start;
+        CommandView.TextFormatter.WordWrap = false;
+        CommandView.HighlightStyle = HighlightStyle.None;
     }
 
     private void Shortcut_TitleChanged (object? sender, EventArgs<string> e)
@@ -554,21 +518,30 @@ public class Shortcut : View, IOrientation, IDesignable
 
     #region Help
 
+    // The maximum width of the HelpView. Calculated in OnLayoutStarted and used in HelpView.Width (Dim.Auto/Func).
+    private int _maxHelpWidth = 0;
+
     /// <summary>
     ///     The subview that displays the help text for the command. Internal for unit testing.
     /// </summary>
-    internal View HelpView { get; } = new ();
+    public View HelpView { get; } = new ();
 
     private void SetHelpViewDefaultLayout ()
     {
-        HelpView.Margin.Thickness = GetMarginThickness ();
+        if (HelpView.Margin is { })
+        {
+            HelpView.Margin.Thickness = GetMarginThickness ();
+        }
+
         HelpView.X = Pos.Align (Alignment.End, AlignmentModes);
-        HelpView.Y = 0; //Pos.Center ();
-        HelpView.Width = Dim.Auto (DimAutoStyle.Text);
-        HelpView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
+        _maxHelpWidth = HelpView.Text.GetColumns ();
+        HelpView.Width = Dim.Auto (DimAutoStyle.Text, maximumContentDim: Dim.Func ((() => _maxHelpWidth)));
+        HelpView.Height = Dim.Fill ();
 
         HelpView.Visible = true;
         HelpView.VerticalTextAlignment = Alignment.Center;
+        HelpView.TextAlignment = Alignment.Start;
+        HelpView.TextFormatter.WordWrap = false;
         HelpView.HighlightStyle = HighlightStyle.None;
     }
 
@@ -660,7 +633,7 @@ public class Shortcut : View, IOrientation, IDesignable
     ///     Gets the subview that displays the key. Internal for unit testing.
     /// </summary>
 
-    internal View KeyView { get; } = new ();
+    public View KeyView { get; } = new ();
 
     private int _minimumKeyTextSize;
 
@@ -679,22 +652,26 @@ public class Shortcut : View, IOrientation, IDesignable
 
             _minimumKeyTextSize = value;
             SetKeyViewDefaultLayout ();
-            CommandView.SetNeedsLayout ();
-            HelpView.SetNeedsLayout ();
-            KeyView.SetNeedsLayout ();
-            SetSubViewNeedsDisplay ();
+
+            //// TODO: Prob not needed
+            //CommandView.SetNeedsLayout ();
+            //HelpView.SetNeedsLayout ();
+            //KeyView.SetNeedsLayout ();
+            //SetSubViewNeedsDraw ();
         }
     }
 
-    private int GetMinimumKeyViewSize () { return MinimumKeyTextSize; }
 
     private void SetKeyViewDefaultLayout ()
     {
-        KeyView.Margin.Thickness = GetMarginThickness ();
+        if (KeyView.Margin is { })
+        {
+            KeyView.Margin.Thickness = GetMarginThickness ();
+        }
+
         KeyView.X = Pos.Align (Alignment.End, AlignmentModes);
-        KeyView.Y = 0;
-        KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize));
-        KeyView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
+        KeyView.Width = Dim.Auto (DimAutoStyle.Text, minimumContentDim: Dim.Func (() => MinimumKeyTextSize));
+        KeyView.Height = Dim.Fill ();
 
         KeyView.Visible = true;
 
@@ -742,23 +719,23 @@ public class Shortcut : View, IOrientation, IDesignable
         get => base.ColorScheme;
         set
         {
-            base.ColorScheme = value;
+            base.ColorScheme = _nonFocusColorScheme = value;
             SetColors ();
         }
     }
 
+    private ColorScheme? _nonFocusColorScheme;
     /// <summary>
     /// </summary>
     internal void SetColors (bool highlight = false)
     {
-        // Border should match superview.
-        if (Border is { })
-        {
-            Border.ColorScheme = SuperView?.ColorScheme;
-        }
-
         if (HasFocus || highlight)
         {
+            if (_nonFocusColorScheme is null)
+            {
+                _nonFocusColorScheme = base.ColorScheme;
+            }
+
             base.ColorScheme ??= new (Attribute.Default);
 
             // When we have focus, we invert the colors
@@ -772,7 +749,15 @@ public class Shortcut : View, IOrientation, IDesignable
         }
         else
         {
-            base.ColorScheme = SuperView?.ColorScheme ?? base.ColorScheme;
+            if (_nonFocusColorScheme is { })
+            {
+                base.ColorScheme = _nonFocusColorScheme;
+               //_nonFocusColorScheme = null;
+            }
+            else
+            {
+                base.ColorScheme = SuperView?.ColorScheme ?? base.ColorScheme;
+            }
         }
 
         // Set KeyView's colors to show "hot"
@@ -785,6 +770,20 @@ public class Shortcut : View, IOrientation, IDesignable
             };
             KeyView.ColorScheme = cs;
         }
+
+        if (CommandView.Margin is { })
+        {
+            CommandView.Margin.ColorScheme = base.ColorScheme;
+        }
+        if (HelpView.Margin is { })
+        {
+            HelpView.Margin.ColorScheme = base.ColorScheme;
+        }
+
+        if (KeyView.Margin is { })
+        {
+            KeyView.Margin.ColorScheme = base.ColorScheme;
+        }
     }
 
     /// <inheritdoc/>
@@ -802,7 +801,6 @@ public class Shortcut : View, IOrientation, IDesignable
         return true;
     }
 
-
     /// <inheritdoc/>
     protected override void Dispose (bool disposing)
     {

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

@@ -47,7 +47,7 @@ public class Slider<T> : View, IOrientation
 
         _options = options ?? new List<SliderOption<T>> ();
 
-        _orientationHelper = new (this);
+        _orientationHelper = new (this); // Do not use object initializer!
         _orientationHelper.Orientation = _config._sliderOrientation = orientation;
         _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
         _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
@@ -59,7 +59,7 @@ public class Slider<T> : View, IOrientation
         // BUGBUG: This should not be needed - Need to ensure SetRelativeLayout gets called during EndInit
         Initialized += (s, e) => { SetContentSize (); };
 
-        LayoutStarted += (s, e) => { SetContentSize (); };
+        SubviewLayout += (s, e) => { SetContentSize (); };
     }
 
     // TODO: Make configurable via ConfigurationManager
@@ -222,7 +222,7 @@ public class Slider<T> : View, IOrientation
 
             // Todo: Custom logic to preserve options.
             _setOptions.Clear ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -353,7 +353,7 @@ public class Slider<T> : View, IOrientation
     public virtual void OnOptionsChanged ()
     {
         OptionsChanged?.Invoke (this, new (GetSetOptionDictionary ()));
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Event raised When the option is hovered with the keys or the mouse.</summary>
@@ -775,13 +775,13 @@ public class Slider<T> : View, IOrientation
     #region Drawing
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
         // TODO: make this more surgical to reduce repaint
 
         if (_options is null || _options.Count == 0)
         {
-            return;
+            return true;
         }
 
         // Draw Slider
@@ -797,6 +797,8 @@ public class Slider<T> : View, IOrientation
         {
             AddRune (_moveRenderPosition.Value.X, _moveRenderPosition.Value.Y, Style.DragChar.Rune);
         }
+
+        return true;
     }
 
     private string AlignText (string text, int width, Alignment alignment)
@@ -839,7 +841,7 @@ public class Slider<T> : View, IOrientation
     private void DrawSlider ()
     {
         // TODO: be more surgical on clear
-        Clear ();
+        ClearViewport ();
 
         // Attributes
 
@@ -848,8 +850,8 @@ public class Slider<T> : View, IOrientation
 
         if (IsInitialized)
         {
-            normalAttr = ColorScheme?.Normal ?? Application.Top.ColorScheme.Normal;
-            setAttr = Style.SetChar.Attribute ?? ColorScheme!.HotNormal;
+            normalAttr = GetNormalColor();
+            setAttr = Style.SetChar.Attribute ?? GetHotNormalColor ();
         }
 
         bool isVertical = _config._sliderOrientation == Orientation.Vertical;
@@ -864,7 +866,7 @@ public class Slider<T> : View, IOrientation
         // Left Spacing
         if (_config._showEndSpacing && _config._startSpacing > 0)
         {
-            Driver?.SetAttribute (
+            SetAttribute (
                                   isSet && _config._type == SliderType.LeftRange
                                       ? Style.RangeChar.Attribute ?? normalAttr
                                       : Style.SpaceChar.Attribute ?? normalAttr
@@ -887,7 +889,7 @@ public class Slider<T> : View, IOrientation
         }
         else
         {
-            Driver?.SetAttribute (Style.EmptyChar.Attribute ?? normalAttr);
+            SetAttribute (Style.EmptyChar.Attribute ?? normalAttr);
 
             for (var i = 0; i < _config._startSpacing; i++)
             {
@@ -940,7 +942,7 @@ public class Slider<T> : View, IOrientation
                 }
 
                 // Draw Option
-                Driver?.SetAttribute (
+                SetAttribute (
                                       isSet && _setOptions.Contains (i) ? Style.SetChar.Attribute ?? setAttr :
                                       drawRange ? Style.RangeChar.Attribute ?? setAttr : Style.OptionChar.Attribute ?? normalAttr
                                      );
@@ -978,7 +980,7 @@ public class Slider<T> : View, IOrientation
                 if (_config._showEndSpacing || i < _options.Count - 1)
                 {
                     // Skip if is the Last Spacing.
-                    Driver?.SetAttribute (
+                    SetAttribute (
                                           drawRange && isSet
                                               ? Style.RangeChar.Attribute ?? setAttr
                                               : Style.SpaceChar.Attribute ?? normalAttr
@@ -1006,7 +1008,7 @@ public class Slider<T> : View, IOrientation
         // Right Spacing
         if (_config._showEndSpacing)
         {
-            Driver?.SetAttribute (
+            SetAttribute (
                                   isSet && _config._type == SliderType.RightRange
                                       ? Style.RangeChar.Attribute ?? normalAttr
                                       : Style.SpaceChar.Attribute ?? normalAttr
@@ -1029,7 +1031,7 @@ public class Slider<T> : View, IOrientation
         }
         else
         {
-            Driver?.SetAttribute (Style.EmptyChar.Attribute ?? normalAttr);
+            SetAttribute (Style.EmptyChar.Attribute ?? normalAttr);
 
             for (var i = 0; i < remaining; i++)
             {
@@ -1056,8 +1058,8 @@ public class Slider<T> : View, IOrientation
 
         if (IsInitialized)
         {
-            normalAttr = Style.LegendAttributes.NormalAttribute ?? ColorScheme?.Normal ?? ColorScheme.Disabled;
-            setAttr = Style.LegendAttributes.SetAttribute ?? ColorScheme?.HotNormal ?? ColorScheme.Normal;
+            normalAttr = Style.LegendAttributes.NormalAttribute ?? GetNormalColor ();
+            setAttr = Style.LegendAttributes.SetAttribute ?? GetHotNormalColor ();
             spaceAttr = Style.LegendAttributes.EmptyAttribute ?? normalAttr;
         }
 
@@ -1221,7 +1223,7 @@ public class Slider<T> : View, IOrientation
             }
 
             // Legend
-            Driver?.SetAttribute (isOptionSet ? setAttr : normalAttr);
+            SetAttribute (isOptionSet ? setAttr : normalAttr);
 
             foreach (Rune c in text.EnumerateRunes ())
             {
@@ -1247,7 +1249,7 @@ public class Slider<T> : View, IOrientation
             }
 
             // Option Right Spacing of Option
-            Driver?.SetAttribute (spaceAttr);
+            SetAttribute (spaceAttr);
 
             if (isTextVertical)
             {
@@ -1309,7 +1311,7 @@ public class Slider<T> : View, IOrientation
                 Application.GrabMouse (this);
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -1343,7 +1345,7 @@ public class Slider<T> : View, IOrientation
                 }
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -1377,7 +1379,7 @@ public class Slider<T> : View, IOrientation
                 }
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             mouseEvent.Handled = true;
 

+ 1 - 1
Terminal.Gui/Views/SpinnerView/SpinnerStyle.cs

@@ -54,7 +54,7 @@ public abstract class SpinnerStyle
     /// </summary>
     /// <remarks>
     ///     This is the maximum speed the spinner will rotate at.  You still need to call
-    ///     <see cref="View.SetNeedsDisplay()"/> or <see cref="SpinnerView.AutoSpin"/> to advance/start animation.
+    ///     <see cref="View.SetNeedsDraw()"/> or <see cref="SpinnerView.AutoSpin"/> to advance/start animation.
     /// </remarks>
     public abstract int SpinDelay { get; }
 

+ 43 - 10
Terminal.Gui/Views/SpinnerView/SpinnerView.cs

@@ -4,14 +4,16 @@
 // <https://spectreconsole.net/best-practices>.
 //------------------------------------------------------------------------------
 
+using System.Diagnostics;
+
 namespace Terminal.Gui;
 
 /// <summary>A <see cref="View"/> which displays (by default) a spinning line character.</summary>
 /// <remarks>
-///     By default animation only occurs when you call <see cref="SpinnerView.AdvanceAnimation()"/>. Use
-///     <see cref="AutoSpin"/> to make the automate calls to <see cref="SpinnerView.AdvanceAnimation()"/>.
+///     By default animation only occurs when you call <see cref="SpinnerView.AdvanceAnimation(bool)"/>. Use
+///     <see cref="AutoSpin"/> to make the automate calls to <see cref="SpinnerView.AdvanceAnimation(bool)"/>.
 /// </remarks>
-public class SpinnerView : View
+public class SpinnerView : View, IDesignable
 {
     private const int DEFAULT_DELAY = 130;
     private static readonly SpinnerStyle DEFAULT_STYLE = new SpinnerStyle.Line ();
@@ -87,7 +89,7 @@ public class SpinnerView : View
     /// <summary>Gets or sets the number of milliseconds to wait between characters in the animation.</summary>
     /// <remarks>
     ///     This is the maximum speed the spinner will rotate at.  You still need to call
-    ///     <see cref="SpinnerView.AdvanceAnimation()"/> or <see cref="SpinnerView.AutoSpin"/> to advance/start animation.
+    ///     <see cref="SpinnerView.AdvanceAnimation(bool)"/> or <see cref="SpinnerView.AutoSpin"/> to advance/start animation.
     /// </remarks>
     public int SpinDelay
     {
@@ -113,11 +115,11 @@ public class SpinnerView : View
     ///     ignored based on <see cref="SpinDelay"/>.
     /// </summary>
     /// <remarks>Ensure this method is called on the main UI thread e.g. via <see cref="Application.Invoke"/></remarks>
-    public void AdvanceAnimation ()
+    public void AdvanceAnimation (bool setNeedsDraw = true)
     {
         if (DateTime.Now - _lastRender > TimeSpan.FromMilliseconds (SpinDelay))
         {
-            if (Sequence is { } && Sequence.Length > 1)
+            if (Sequence is { Length: > 1 })
             {
                 var d = 1;
 
@@ -169,14 +171,37 @@ public class SpinnerView : View
                         _currentIdx = Sequence.Length - 1;
                     }
                 }
-
-                Text = "" + Sequence [_currentIdx]; //.EnumerateRunes;
             }
 
             _lastRender = DateTime.Now;
         }
 
-        SetNeedsDisplay ();
+        if (setNeedsDraw)
+        {
+            SetNeedsDraw ();
+        }
+    }
+
+    /// <inheritdoc />
+    protected override bool OnClearingViewport () { return true; }
+
+    /// <inheritdoc />
+    protected override bool OnDrawingContent ()
+    {
+        Render ();
+        return true;
+    }
+
+    /// <summary>
+    ///    Renders the current frame of the spinner.
+    /// </summary>
+    public void Render ()
+    {
+        if (Sequence is { Length: > 0 } && _currentIdx < Sequence.Length)
+        {
+            Move (Viewport.X, Viewport.Y);
+            View.Driver?.AddStr (Sequence [_currentIdx]);
+        }
     }
 
     /// <inheritdoc/>
@@ -198,7 +223,7 @@ public class SpinnerView : View
                                            TimeSpan.FromMilliseconds (SpinDelay),
                                            () =>
                                            {
-                                               Application.Invoke (AdvanceAnimation);
+                                               Application.Invoke (() => AdvanceAnimation());
 
                                                return true;
                                            }
@@ -289,4 +314,12 @@ public class SpinnerView : View
             Width = GetSpinnerWidth ();
         }
     }
+
+    bool IDesignable.EnableForDesign ()
+    {
+        Style = new SpinnerStyle.Points ();
+        SpinReverse = true;
+        AutoSpin = true;
+        return true;
+    }
 }

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

@@ -26,7 +26,7 @@ public class StatusBar : Bar, IDesignable
         BorderStyle = LineStyle.Dashed;
         ColorScheme = Colors.ColorSchemes ["Menu"];
 
-        LayoutStarted += StatusBar_LayoutStarted;
+        SubviewLayout += StatusBar_LayoutStarted;
     }
 
     // StatusBar arranges the items horizontally.

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

@@ -1,9 +1,10 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 /// <summary>A single tab in a <see cref="TabView"/>.</summary>
 public class Tab : View
 {
-    private string _displayText;
+    private string? _displayText;
 
     /// <summary>Creates a new unnamed tab with no controls inside.</summary>
     public Tab ()
@@ -21,11 +22,11 @@ public class Tab : View
         set
         {
             _displayText = value;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
     /// <summary>The control to display when the tab is selected.</summary>
     /// <value></value>
-    public View View { get; set; }
+    public View? View { get; set; }
 }

+ 130 - 92
Terminal.Gui/Views/TabView.cs

@@ -1,5 +1,4 @@
-using System.Diagnostics;
-
+#nullable enable
 namespace Terminal.Gui;
 
 /// <summary>Control that hosts multiple sub views, presenting a single one at once.</summary>
@@ -19,8 +18,8 @@ public class TabView : View
     /// <summary>This sub view is the 2 or 3 line control that represents the actual tabs themselves.</summary>
     private readonly TabRowView _tabsBar;
 
-    private Tab _selectedTab;
-    private TabToRender [] _tabLocations;
+    private Tab? _selectedTab;
+    private TabToRender []? _tabLocations;
     private int _tabScrollOffset;
 
     /// <summary>Initializes a <see cref="TabView"/> class.</summary>
@@ -48,7 +47,7 @@ public class TabView : View
                     () =>
                     {
                         TabScrollOffset = 0;
-                        SelectedTab = Tabs.FirstOrDefault ();
+                        SelectedTab = Tabs.FirstOrDefault ()!;
 
                         return true;
                     }
@@ -59,7 +58,7 @@ public class TabView : View
                     () =>
                     {
                         TabScrollOffset = Tabs.Count - 1;
-                        SelectedTab = Tabs.LastOrDefault ();
+                        SelectedTab = Tabs.LastOrDefault ()!;
 
                         return true;
                     }
@@ -69,7 +68,7 @@ public class TabView : View
                     Command.PageDown,
                     () =>
                     {
-                        TabScrollOffset += _tabLocations.Length;
+                        TabScrollOffset += _tabLocations!.Length;
                         SelectedTab = Tabs.ElementAt (TabScrollOffset);
 
                         return true;
@@ -80,7 +79,7 @@ public class TabView : View
                     Command.PageUp,
                     () =>
                     {
-                        TabScrollOffset -= _tabLocations.Length;
+                        TabScrollOffset -= _tabLocations!.Length;
                         SelectedTab = Tabs.ElementAt (TabScrollOffset);
 
                         return true;
@@ -104,19 +103,20 @@ public class TabView : View
 
     /// <summary>The currently selected member of <see cref="Tabs"/> chosen by the user.</summary>
     /// <value></value>
-    public Tab SelectedTab
+    public Tab? SelectedTab
     {
         get => _selectedTab;
         set
         {
             UnSetCurrentTabs ();
 
-            Tab old = _selectedTab;
+            Tab? old = _selectedTab;
 
             if (_selectedTab is { })
             {
                 if (_selectedTab.View is { })
                 {
+                    _selectedTab.View.CanFocusChanged -= ContentViewCanFocus!;
                     // remove old content
                     _contentView.Remove (_selectedTab.View);
                 }
@@ -124,35 +124,53 @@ public class TabView : View
 
             _selectedTab = value;
 
-            if (value is { })
+            // add new content
+            if (_selectedTab?.View != null)
             {
-                // add new content
-                if (_selectedTab.View is { })
-                {
-                    _contentView.Add (_selectedTab.View);
-                   // _contentView.Id = $"_contentView for {_selectedTab.DisplayText}";
-                }
+                _selectedTab.View.CanFocusChanged += ContentViewCanFocus!;
+                _contentView.Add (_selectedTab.View);
+                // _contentView.Id = $"_contentView for {_selectedTab.DisplayText}";
             }
 
-            _contentView.CanFocus = _contentView.Subviews.Count (v => v.CanFocus) > 0;
+            ContentViewCanFocus (null!, null!);
 
             EnsureSelectedTabIsVisible ();
 
-            if (old != value)
+            if (old != _selectedTab)
             {
                 if (old?.HasFocus == true)
                 {
                     SelectedTab?.SetFocus ();
                 }
 
-                OnSelectedTabChanged (old, value);
+                OnSelectedTabChanged (old!, _selectedTab!);
             }
+            SetNeedsLayout ();
         }
     }
 
+    private void ContentViewCanFocus (object sender, EventArgs eventArgs)
+    {
+        _contentView.CanFocus = _contentView.Subviews.Count (v => v.CanFocus) > 0;
+    }
+
+    private TabStyle _style = new ();
+
     /// <summary>Render choices for how to display tabs.  After making changes, call <see cref="ApplyStyleChanges()"/>.</summary>
     /// <value></value>
-    public TabStyle Style { get; set; } = new ();
+    public TabStyle Style
+    {
+        get => _style;
+        set
+        {
+            if (_style == value)
+            {
+                return;
+            }
+            _style = value;
+            SetNeedsLayout ();
+        }
+    }
 
     /// <summary>All tabs currently hosted by the control.</summary>
     /// <value></value>
@@ -163,7 +181,11 @@ public class TabView : View
     public int TabScrollOffset
     {
         get => _tabScrollOffset;
-        set => _tabScrollOffset = EnsureValidScrollOffsets (value);
+        set
+        {
+            _tabScrollOffset = EnsureValidScrollOffsets (value);
+            SetNeedsLayout ();
+        }
     }
 
     /// <summary>Adds the given <paramref name="tab"/> to <see cref="Tabs"/>.</summary>
@@ -188,13 +210,13 @@ public class TabView : View
             tab.View?.SetFocus ();
         }
 
-        SetNeedsDisplay ();
+        SetNeedsLayout ();
     }
 
     /// <summary>
     ///     Updates the control to use the latest state settings in <see cref="Style"/>. This can change the size of the
     ///     client area of the tab (for rendering the selected tab's content).  This method includes a call to
-    ///     <see cref="View.SetNeedsDisplay()"/>.
+    ///     <see cref="View.SetNeedsDraw()"/>.
     /// </summary>
     public void ApplyStyleChanges ()
     {
@@ -233,7 +255,7 @@ public class TabView : View
             int tabHeight = GetTabHeight (true);
 
             //move content down to make space for tabs
-            _contentView.Y = Pos.Bottom (_tabsBar) ;
+            _contentView.Y = Pos.Bottom (_tabsBar);
 
             // Fill client area leaving space at bottom for border
             _contentView.Height = Dim.Fill ();
@@ -245,12 +267,7 @@ public class TabView : View
             // Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0
         }
 
-        if (IsInitialized)
-        {
-            LayoutSubviews ();
-        }
-
-        SetNeedsDisplay ();
+        SetNeedsLayout ();
     }
 
     /// <summary>Updates <see cref="TabScrollOffset"/> to ensure that <see cref="SelectedTab"/> is visible.</summary>
@@ -271,34 +288,48 @@ public class TabView : View
 
     /// <summary>Updates <see cref="TabScrollOffset"/> to be a valid index of <see cref="Tabs"/>.</summary>
     /// <param name="value">The value to validate.</param>
-    /// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDisplay()"/>.</remarks>
+    /// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw()"/>.</remarks>
     /// <returns>The valid <see cref="TabScrollOffset"/> for the given value.</returns>
     public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); }
 
-    /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    /// <inheritdoc />
+    protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
     {
-        Driver.SetAttribute (GetNormalColor ());
+        if (SelectedTab is { } && !_contentView.CanFocus && focusedView == this)
+        {
+            SelectedTab?.SetFocus ();
 
+            return;
+        }
+
+        base.OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView);
+    }
+
+    /// <inheritdoc/>
+    protected override bool OnDrawingContent ()
+    {
         if (Tabs.Any ())
         {
-            Rectangle savedClip = SetClip ();
-            _tabsBar.OnDrawContent (viewport);
-            _contentView.SetNeedsDisplay ();
+            // Region savedClip = SetClip ();
+            _tabsBar.Draw ();
+            _contentView.SetNeedsDraw ();
             _contentView.Draw ();
-            Driver.Clip = savedClip;
+
+            //if (Driver is { })
+            //{
+            //    Driver.Clip = savedClip;
+            //}
         }
-    }
 
-    /// <inheritdoc/>
-    public override void OnDrawContentComplete (Rectangle viewport) { _tabsBar.OnDrawContentComplete (viewport); }
+        return true;
+    }
 
     /// <summary>
     ///     Removes the given <paramref name="tab"/> from <see cref="Tabs"/>. Caller is responsible for disposing the
     ///     tab's hosted <see cref="Tab.View"/> if appropriate.
     /// </summary>
     /// <param name="tab"></param>
-    public void RemoveTab (Tab tab)
+    public void RemoveTab (Tab? tab)
     {
         if (tab is null || !_tabs.Contains (tab))
         {
@@ -327,11 +358,11 @@ public class TabView : View
         }
 
         EnsureSelectedTabIsVisible ();
-        SetNeedsDisplay ();
+        SetNeedsLayout ();
     }
 
     /// <summary>Event for when <see cref="SelectedTab"/> changes.</summary>
-    public event EventHandler<TabChangedEventArgs> SelectedTabChanged;
+    public event EventHandler<TabChangedEventArgs>? SelectedTabChanged;
 
     /// <summary>
     ///     Changes the <see cref="SelectedTab"/> by the given <paramref name="amount"/>. Positive for right, negative for
@@ -349,7 +380,6 @@ public class TabView : View
         if (Tabs.Count == 1 || SelectedTab is null)
         {
             SelectedTab = Tabs.ElementAt (0);
-            SetNeedsDisplay ();
 
             return SelectedTab is { };
         }
@@ -360,8 +390,6 @@ public class TabView : View
         if (currentIdx == -1)
         {
             SelectedTab = Tabs.ElementAt (0);
-            SetNeedsDisplay ();
-
             return true;
         }
 
@@ -373,7 +401,6 @@ public class TabView : View
         }
 
         SelectedTab = _tabs [newIdx];
-        SetNeedsDisplay ();
 
         EnsureSelectedTabIsVisible ();
 
@@ -384,7 +411,7 @@ public class TabView : View
     ///     Event fired when a <see cref="Tab"/> is clicked.  Can be used to cancel navigation, show context menu (e.g. on
     ///     right click) etc.
     /// </summary>
-    public event EventHandler<TabMouseEventArgs> TabClicked;
+    public event EventHandler<TabMouseEventArgs>? TabClicked;
 
     /// <summary>Disposes the control and all <see cref="Tabs"/>.</summary>
     /// <param name="disposing"></param>
@@ -417,7 +444,7 @@ public class TabView : View
         UnSetCurrentTabs ();
 
         var i = 1;
-        View prevTab = null;
+        View? prevTab = null;
 
         // Starting at the first or scrolled to tab
         foreach (Tab tab in Tabs.Skip (TabScrollOffset))
@@ -452,9 +479,9 @@ public class TabView : View
             if (maxWidth == 0)
             {
                 tab.Visible = true;
-                tab.MouseClick += Tab_MouseClick;
+                tab.MouseClick += Tab_MouseClick!;
 
-                yield return new TabToRender (i, tab, string.Empty, Equals (SelectedTab, tab), 0);
+                yield return new TabToRender (tab, string.Empty, Equals (SelectedTab, tab));
 
                 break;
             }
@@ -478,9 +505,9 @@ public class TabView : View
 
             // there is enough space!
             tab.Visible = true;
-            tab.MouseClick += Tab_MouseClick;
+            tab.MouseClick += Tab_MouseClick!;
 
-            yield return new TabToRender (i, tab, text, Equals (SelectedTab, tab), tabTextWidth);
+            yield return new TabToRender (tab, text, Equals (SelectedTab, tab));
 
             i += tabTextWidth + 1;
         }
@@ -519,7 +546,7 @@ public class TabView : View
         {
             foreach (TabToRender tabToRender in _tabLocations)
             {
-                tabToRender.Tab.MouseClick -= Tab_MouseClick;
+                tabToRender.Tab.MouseClick -= Tab_MouseClick!;
                 tabToRender.Tab.Visible = false;
             }
 
@@ -554,7 +581,7 @@ public class TabView : View
                 Visible = false,
                 Text = Glyphs.RightArrow.ToString ()
             };
-            _rightScrollIndicator.MouseClick += _host.Tab_MouseClick;
+            _rightScrollIndicator.MouseClick += _host.Tab_MouseClick!;
 
             _leftScrollIndicator = new View
             {
@@ -564,14 +591,14 @@ public class TabView : View
                 Visible = false,
                 Text = Glyphs.LeftArrow.ToString ()
             };
-            _leftScrollIndicator.MouseClick += _host.Tab_MouseClick;
+            _leftScrollIndicator.MouseClick += _host.Tab_MouseClick!;
 
             Add (_rightScrollIndicator, _leftScrollIndicator);
         }
 
         protected override bool OnMouseEvent (MouseEventArgs me)
         {
-            Tab hit = me.View is Tab ? (Tab)me.View : null;
+            Tab? hit = me.View as Tab;
 
             if (me.IsSingleClicked)
             {
@@ -611,7 +638,7 @@ public class TabView : View
                 {
                     _host.SwitchTabBy (scrollIndicatorHit);
 
-                    SetNeedsDisplay ();
+                    SetNeedsLayout ();
 
                     return true;
                 }
@@ -619,7 +646,7 @@ public class TabView : View
                 if (hit is { })
                 {
                     _host.SelectedTab = hit;
-                    SetNeedsDisplay ();
+                    SetNeedsLayout ();
 
                     return true;
                 }
@@ -628,20 +655,37 @@ public class TabView : View
             return false;
         }
 
-        public override void OnDrawContent (Rectangle viewport)
+        /// <inheritdoc />
+        protected override bool OnClearingViewport ()
         {
-            _host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
-
             // clear any old text
-            Clear ();
+            ClearViewport ();
+
+            return true;
+        }
+
+        protected override bool OnDrawingContent ()
+        {
+            _host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
 
             RenderTabLine ();
 
             RenderUnderline ();
-            Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
+
+            SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
+
+            return true;
+        }
+
+        /// <inheritdoc />
+        protected override bool OnDrawingSubviews ()
+        {
+           // RenderTabLine ();
+
+            return false;
         }
 
-        public override void OnDrawContentComplete (Rectangle viewport)
+        protected override void OnDrawComplete ()
         {
             if (_host._tabLocations is null)
             {
@@ -1171,7 +1215,9 @@ public class TabView : View
                 }
 
                 tab.LineCanvas.Merge (lc);
-                tab.OnRenderLineCanvas ();
+                tab.RenderLineCanvas ();
+
+               // RenderUnderline ();
             }
         }
 
@@ -1188,21 +1234,15 @@ public class TabView : View
         /// <summary>Renders the line with the tab names in it.</summary>
         private void RenderTabLine ()
         {
-            TabToRender [] tabLocations = _host._tabLocations;
-            int y;
+            TabToRender []? tabLocations = _host._tabLocations;
 
-            if (_host.Style.TabsOnBottom)
+            if (tabLocations is null)
             {
-                y = 1;
-            }
-            else
-            {
-                y = _host.Style.ShowTopLine ? 1 : 0;
+                return;
             }
 
-            View selected = null;
+            View? selected = null;
             int topLine = _host.Style.ShowTopLine ? 1 : 0;
-            int width = Viewport.Width;
 
             foreach (TabToRender toRender in tabLocations)
             {
@@ -1236,7 +1276,7 @@ public class TabView : View
                         tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
                     }
 
-                    tab.Width = Math.Max (tab.Width.GetAnchor (0) - 1, 1);
+                    tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1);
                 }
                 else
                 {
@@ -1251,16 +1291,17 @@ public class TabView : View
                         tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
                     }
 
-                    tab.Width = Math.Max (tab.Width.GetAnchor (0) - 1, 1);
+                    tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1);
                 }
 
                 tab.Text = toRender.TextToRender;
 
-                LayoutSubviews ();
+                // BUGBUG: Layout should only be called from Mainloop iteration!
+                Layout ();
 
-                tab.OnDrawAdornments ();
+                tab.DrawBorderAndPadding ();
 
-                Attribute prevAttr = Driver.GetAttribute ();
+                Attribute prevAttr = Driver?.GetAttribute () ?? Attribute.Default;
 
                 // if tab is the selected one and focus is inside this control
                 if (toRender.IsSelected && _host.HasFocus)
@@ -1283,9 +1324,10 @@ public class TabView : View
                                         ColorScheme.HotNormal
                                        );
 
-                tab.OnRenderLineCanvas ();
+                tab.DrawBorderAndPadding ();
+
 
-                Driver.SetAttribute (GetNormalColor ());
+                SetAttribute (GetNormalColor ());
             }
         }
 
@@ -1294,7 +1336,7 @@ public class TabView : View
         {
             int y = GetUnderlineYPosition ();
 
-            TabToRender selected = _host._tabLocations.FirstOrDefault (t => t.IsSelected);
+            TabToRender? selected = _host._tabLocations?.FirstOrDefault (t => t.IsSelected);
 
             if (selected is null)
             {
@@ -1340,17 +1382,15 @@ public class TabView : View
             }
         }
 
-        private bool ShouldDrawRightScrollIndicator () { return _host._tabLocations.LastOrDefault ()?.Tab != _host.Tabs.LastOrDefault (); }
+        private bool ShouldDrawRightScrollIndicator () { return _host._tabLocations!.LastOrDefault ()?.Tab != _host.Tabs.LastOrDefault (); }
     }
 
     private class TabToRender
     {
-        public TabToRender (int x, Tab tab, string textToRender, bool isSelected, int width)
+        public TabToRender (Tab tab, string textToRender, bool isSelected)
         {
-            X = x;
             Tab = tab;
             IsSelected = isSelected;
-            Width = width;
             TextToRender = textToRender;
         }
 
@@ -1360,7 +1400,5 @@ public class TabView : View
 
         public Tab Tab { get; }
         public string TextToRender { get; }
-        public int Width { get; }
-        public int X { get; set; }
     }
 }

+ 3 - 3
Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs

@@ -147,7 +147,7 @@ public abstract class CheckBoxTableSourceWrapperBase : ITableSource
         }
 
         e.Cancel = true;
-        tableView.SetNeedsDisplay ();
+        tableView.SetNeedsDraw ();
     }
 
     private void TableView_MouseClick (object sender, MouseEventArgs e)
@@ -171,7 +171,7 @@ public abstract class CheckBoxTableSourceWrapperBase : ITableSource
             // otherwise it ticks all rows
             ToggleAllRows ();
             e.Handled = true;
-            tableView.SetNeedsDisplay ();
+            tableView.SetNeedsDraw ();
         }
         else if (hit.HasValue && hit.Value.X == 0)
         {
@@ -186,7 +186,7 @@ public abstract class CheckBoxTableSourceWrapperBase : ITableSource
             }
 
             e.Handled = true;
-            tableView.SetNeedsDisplay ();
+            tableView.SetNeedsDraw ();
         }
     }
 }

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

@@ -38,7 +38,7 @@ public class ListTableSource : ITableSource
         DataTable = CreateTable (CalculateColumns ());
 
         // TODO: Determine the best event for this
-        tableView.DrawContent += TableView_DrawContent;
+        tableView.DrawingContent += TableView_DrawContent;
     }
 
     /// <inheritdoc/>

+ 138 - 43
Terminal.Gui/Views/TableView/TableView.cs

@@ -1,4 +1,5 @@
 using System.Data;
+using System.Globalization;
 
 namespace Terminal.Gui;
 
@@ -16,7 +17,7 @@ public delegate ColorScheme RowColorGetterDelegate (RowColorGetterArgs args);
 ///     View for tabular data based on a <see cref="ITableSource"/>.
 ///     <a href="../docs/tableview.md">See TableView Deep Dive for more information</a>.
 /// </summary>
-public class TableView : View
+public class TableView : View, IDesignable
 {
     /// <summary>
     ///     The default maximum cell width for <see cref="TableView.MaxCellWidth"/> and <see cref="ColumnStyle.MaxWidth"/>
@@ -319,8 +320,13 @@ public class TableView : View
         //try to prevent this being set to an out of bounds column
         set
         {
+            int prev = columnOffset;
             columnOffset = TableIsNullOrInvisible () ? 0 : Math.Max (0, Math.Min (Table.Columns - 1, value));
-            SetNeedsDisplay ();
+
+            if (prev != columnOffset)
+            {
+                SetNeedsDraw ();
+            }
         }
     }
 
@@ -357,7 +363,16 @@ public class TableView : View
     public int RowOffset
     {
         get => rowOffset;
-        set => rowOffset = TableIsNullOrInvisible () ? 0 : Math.Max (0, Math.Min (Table.Rows - 1, value));
+        set
+        {
+            int prev = rowOffset;
+            rowOffset = TableIsNullOrInvisible () ? 0 : Math.Max (0, Math.Min (Table.Rows - 1, value));
+
+            if (rowOffset != prev)
+            {
+                SetNeedsDraw ();
+            }
+        }
     }
 
     /// <summary>The index of <see cref="DataTable.Columns"/> in <see cref="Table"/> that the user has currently selected</summary>
@@ -582,7 +597,7 @@ public class TableView : View
     ///     not been set.
     /// </summary>
     /// <remarks>
-    ///     Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDisplay()"/>
+    ///     Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw()"/>
     /// </remarks>
     public void EnsureSelectedCellIsVisible ()
     {
@@ -643,7 +658,7 @@ public class TableView : View
     ///     (by adjusting them to the nearest existing cell).  Has no effect if <see cref="Table"/> has not been set.
     /// </summary>
     /// <remarks>
-    ///     Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDisplay()"/>
+    ///     Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw()"/>
     /// </remarks>
     public void EnsureValidScrollOffsets ()
     {
@@ -662,7 +677,7 @@ public class TableView : View
     ///     <see cref="Table"/> has not been set.
     /// </summary>
     /// <remarks>
-    ///     Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDisplay()"/>
+    ///     Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw()"/>
     /// </remarks>
     public void EnsureValidSelection ()
     {
@@ -829,28 +844,28 @@ public class TableView : View
             case MouseFlags.WheeledDown:
                 RowOffset++;
                 EnsureValidScrollOffsets ();
-                SetNeedsDisplay ();
+                //SetNeedsDraw ();
 
                 return true;
 
             case MouseFlags.WheeledUp:
                 RowOffset--;
                 EnsureValidScrollOffsets ();
-                SetNeedsDisplay ();
+                //SetNeedsDraw ();
 
                 return true;
 
             case MouseFlags.WheeledRight:
                 ColumnOffset++;
                 EnsureValidScrollOffsets ();
-                SetNeedsDisplay ();
+                //SetNeedsDraw ();
 
                 return true;
 
             case MouseFlags.WheeledLeft:
                 ColumnOffset--;
                 EnsureValidScrollOffsets ();
-                SetNeedsDisplay ();
+                //SetNeedsDraw ();
 
                 return true;
         }
@@ -866,7 +881,7 @@ public class TableView : View
             {
                 ColumnOffset--;
                 EnsureValidScrollOffsets ();
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             if (scrollRightPoint != null
@@ -875,7 +890,7 @@ public class TableView : View
             {
                 ColumnOffset++;
                 EnsureValidScrollOffsets ();
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             Point? hit = ScreenToCell (boundsX, boundsY);
@@ -910,10 +925,8 @@ public class TableView : View
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
-        base.OnDrawContent (viewport);
-
         Move (0, 0);
 
         scrollRightPoint = null;
@@ -922,7 +935,7 @@ public class TableView : View
         // What columns to render at what X offset in viewport
         ColumnToRender [] columnsToRender = CalculateViewport (Viewport).ToArray ();
 
-        Driver?.SetAttribute (GetNormalColor ());
+        SetAttribute (GetNormalColor ());
 
         //invalidate current row (prevents scrolling around leaving old characters in the frame
         Driver?.AddStr (new string (' ', Viewport.Width));
@@ -985,6 +998,8 @@ public class TableView : View
 
             RenderRow (line, rowToRender, columnsToRender);
         }
+
+        return true;
     }
 
     /// <inheritdoc/>
@@ -1226,12 +1241,12 @@ public class TableView : View
     ///     Updates the view to reflect changes to <see cref="Table"/> and to (<see cref="ColumnOffset"/> /
     ///     <see cref="RowOffset"/>) etc
     /// </summary>
-    /// <remarks>This always calls <see cref="View.SetNeedsDisplay()"/></remarks>
+    /// <remarks>This always calls <see cref="View.SetNeedsDraw()"/></remarks>
     public void Update ()
     {
         if (!IsInitialized || TableIsNullOrInvisible ())
         {
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return;
         }
@@ -1241,7 +1256,7 @@ public class TableView : View
 
         EnsureSelectedCellIsVisible ();
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Invokes the <see cref="CellActivated"/> event</summary>
@@ -1280,20 +1295,20 @@ public class TableView : View
             if (render.Length > 0)
             {
                 // invert the color of the current cell for the first character
-                Driver.SetAttribute (new Attribute (cellColor.Background, cellColor.Foreground));
-                Driver.AddRune ((Rune)render [0]);
+                SetAttribute (new Attribute (cellColor.Background, cellColor.Foreground));
+                Driver?.AddRune ((Rune)render [0]);
 
                 if (render.Length > 1)
                 {
-                    Driver.SetAttribute (cellColor);
-                    Driver.AddStr (render.Substring (1));
+                    SetAttribute (cellColor);
+                    Driver?.AddStr (render.Substring (1));
                 }
             }
         }
         else
         {
-            Driver.SetAttribute (cellColor);
-            Driver.AddStr (render);
+            SetAttribute (cellColor);
+            Driver?.AddStr (render);
         }
     }
 
@@ -1323,7 +1338,7 @@ public class TableView : View
     private void AddRuneAt (ConsoleDriver d, int col, int row, Rune ch)
     {
         Move (col, row);
-        d.AddRune (ch);
+        d?.AddRune (ch);
     }
 
     /// <summary>
@@ -1505,8 +1520,12 @@ public class TableView : View
     /// <param name="width"></param>
     private void ClearLine (int row, int width)
     {
+        if (Driver is null)
+        {
+            return;
+        }
         Move (0, row);
-        Driver.SetAttribute (GetNormalColor ());
+        SetAttribute (GetNormalColor ());
         Driver.AddStr (new string (' ', width));
     }
 
@@ -1580,7 +1599,7 @@ public class TableView : View
             SelectedRow = match;
             EnsureValidSelection ();
             EnsureSelectedCellIsVisible ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -1728,7 +1747,7 @@ public class TableView : View
 
             Move (current.X, row);
 
-            Driver.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle));
+            Driver?.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle));
 
             if (Style.ExpandLastColumn == false && current.IsVeryLast)
             {
@@ -1776,7 +1795,10 @@ public class TableView : View
                 }
             }
 
-            AddRuneAt (Driver, c, row, rune);
+            if (Driver is { })
+            {
+                AddRuneAt (Driver, c, row, rune);
+            }
         }
     }
 
@@ -1885,19 +1907,22 @@ public class TableView : View
         //start by clearing the entire line
         Move (0, row);
 
-        Attribute color;
+        Attribute? color;
 
         if (FullRowSelect && IsSelected (0, rowToRender))
         {
-            color = focused ? rowScheme.Focus : rowScheme.HotNormal;
+            color = focused ? rowScheme?.Focus : rowScheme?.HotNormal;
         }
         else
         {
-            color = Enabled ? rowScheme.Normal : rowScheme.Disabled;
+            color = Enabled ? rowScheme?.Normal : rowScheme?.Disabled;
         }
 
-        Driver.SetAttribute (color);
-        Driver.AddStr (new string (' ', Viewport.Width));
+        if (color is { })
+        {
+            SetAttribute (color.Value);
+        }
+        Driver?.AddStr (new string (' ', Viewport.Width));
 
         // Render cells for each visible header for the current row
         for (var i = 0; i < columnsToRender.Length; i++)
@@ -1948,15 +1973,15 @@ public class TableView : View
                 scheme = rowScheme;
             }
 
-            Attribute cellColor;
+            Attribute? cellColor;
 
             if (isSelectedCell)
             {
-                cellColor = focused ? scheme.Focus : scheme.HotNormal;
+                cellColor = focused ? scheme?.Focus : scheme?.HotNormal;
             }
             else
             {
-                cellColor = Enabled ? scheme.Normal : scheme.Disabled;
+                cellColor = Enabled ? scheme?.Normal : scheme?.Disabled;
             }
 
             string render = TruncateOrPad (val, representation, current.Width, colStyle);
@@ -1964,7 +1989,10 @@ public class TableView : View
             // While many cells can be selected (see MultiSelectedRegions) only one cell is the primary (drives navigation etc)
             bool isPrimaryCell = current.Column == selectedColumn && rowToRender == selectedRow;
 
-            RenderCell (cellColor, render, isPrimaryCell);
+            if (cellColor.HasValue)
+            {
+                RenderCell (cellColor.Value, render, isPrimaryCell);
+            }
 
             // Reset color scheme to normal for drawing separators if we drew text with custom scheme
             if (scheme != rowScheme)
@@ -1978,18 +2006,24 @@ public class TableView : View
                     color = Enabled ? rowScheme.Normal : rowScheme.Disabled;
                 }
 
-                Driver.SetAttribute (color);
+                SetAttribute (color.Value);
             }
 
             // If not in full row select mode always, reset color scheme to normal and render the vertical line (or space) at the end of the cell
             if (!FullRowSelect)
             {
-                Driver.SetAttribute (Enabled ? rowScheme.Normal : rowScheme.Disabled);
+                if (rowScheme is { })
+                {
+                    SetAttribute (Enabled ? rowScheme.Normal : rowScheme.Disabled);
+                }
             }
 
             if (style.AlwaysUseNormalColorForVerticalCellLines && style.ShowVerticalCellLines)
             {
-                Driver.SetAttribute (rowScheme.Normal);
+                if (rowScheme is { })
+                {
+                    SetAttribute (rowScheme.Normal);
+                }
             }
 
             RenderSeparator (current.X - 1, row, false);
@@ -2002,7 +2036,10 @@ public class TableView : View
 
         if (style.ShowVerticalCellLines)
         {
-            Driver.SetAttribute (rowScheme.Normal);
+            if (rowScheme is { })
+            {
+                SetAttribute (rowScheme.Normal);
+            }
 
             //render start and end of line
             AddRune (0, row, Glyphs.VLine);
@@ -2297,4 +2334,62 @@ public class TableView : View
         /// <summary>The horizontal position to begin rendering the column at</summary>
         public int X { get; set; }
     }
+
+    bool IDesignable.EnableForDesign ()
+    {
+        var dt = BuildDemoDataTable (5, 5);
+        Table = new DataTableSource (dt);
+        return true;
+    }
+
+    /// <summary>
+    ///     Generates a new demo <see cref="DataTable"/> with the given number of <paramref name="cols"/> (min 5) and
+    ///     <paramref name="rows"/>
+    /// </summary>
+    /// <param name="cols"></param>
+    /// <param name="rows"></param>
+    /// <returns></returns>
+    public static DataTable BuildDemoDataTable (int cols, int rows)
+    {
+        var dt = new DataTable ();
+
+        var explicitCols = 6;
+        dt.Columns.Add (new DataColumn ("StrCol", typeof (string)));
+        dt.Columns.Add (new DataColumn ("DateCol", typeof (DateTime)));
+        dt.Columns.Add (new DataColumn ("IntCol", typeof (int)));
+        dt.Columns.Add (new DataColumn ("DoubleCol", typeof (double)));
+        dt.Columns.Add (new DataColumn ("NullsCol", typeof (string)));
+        dt.Columns.Add (new DataColumn ("Unicode", typeof (string)));
+
+        for (var i = 0; i < cols - explicitCols; i++)
+        {
+            dt.Columns.Add ("Column" + (i + explicitCols));
+        }
+
+        var r = new Random (100);
+
+        for (var i = 0; i < rows; i++)
+        {
+            List<object> row = new ()
+            {
+                $"Demo text in row {i}",
+                new DateTime (2000 + i, 12, 25),
+                r.Next (i),
+                r.NextDouble () * i - 0.5 /*add some negatives to demo styles*/,
+                DBNull.Value,
+                "Les Mise"
+                + char.ConvertFromUtf32 (int.Parse ("0301", NumberStyles.HexNumber))
+                + "rables"
+            };
+
+            for (var j = 0; j < cols - explicitCols; j++)
+            {
+                row.Add ("SomeValue" + r.Next (100));
+            }
+
+            dt.Rows.Add (row.ToArray ());
+        }
+
+        return dt;
+    }
 }

+ 2 - 2
Terminal.Gui/Views/TableView/TreeTableSource.cs

@@ -162,7 +162,7 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
         if (e.Handled)
         {
             _tree.InvalidateLineMap ();
-            _tableView.SetNeedsDisplay ();
+            _tableView.SetNeedsDraw ();
         }
     }
 
@@ -197,7 +197,7 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
         if (e.Handled)
         {
             _tree.InvalidateLineMap ();
-            _tableView.SetNeedsDisplay ();
+            _tableView.SetNeedsDraw ();
         }
     }
 }

+ 23 - 21
Terminal.Gui/Views/TextField.cs

@@ -562,7 +562,7 @@ public class TextField : View
             }
 
             Adjust ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -585,7 +585,7 @@ public class TextField : View
         _selectedText = null;
         _start = 0;
         SelectedLength = 0;
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Allows clearing the <see cref="HistoryText.HistoryTextItemEventArgs"/> items updating the original text.</summary>
@@ -627,7 +627,7 @@ public class TextField : View
         _selectedStart = 0;
         MoveEndExtend ();
         DeleteCharLeft (false);
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Deletes the character to the left.</summary>
@@ -909,7 +909,7 @@ public class TextField : View
             ShowContextMenu ();
         }
 
-        //SetNeedsDisplay ();
+        //SetNeedsDraw ();
 
         return true;
 
@@ -931,14 +931,14 @@ public class TextField : View
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
         _isDrawing = true;
 
         var selColor = new Attribute (GetFocusColor ().Background, GetFocusColor ().Foreground);
         SetSelectedStartSelectedLength ();
 
-        Driver?.SetAttribute (GetNormalColor ());
+        SetAttribute (GetNormalColor ());
         Move (0, 0);
 
         int p = ScrollOffset;
@@ -954,11 +954,11 @@ public class TextField : View
 
             if (idx == _cursorPosition && HasFocus && !Used && SelectedLength == 0 && !ReadOnly)
             {
-                Driver?.SetAttribute (selColor);
+                SetAttribute (selColor);
             }
             else if (ReadOnly)
             {
-                Driver?.SetAttribute (
+                SetAttribute (
                                       idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength
                                           ? selColor
                                           : roc
@@ -966,15 +966,15 @@ public class TextField : View
             }
             else if (!HasFocus && Enabled)
             {
-                Driver?.SetAttribute (GetFocusColor ());
+                SetAttribute (GetFocusColor ());
             }
             else if (!Enabled)
             {
-                Driver?.SetAttribute (roc);
+                SetAttribute (roc);
             }
             else
             {
-                Driver?.SetAttribute (
+                SetAttribute (
                                       idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength
                                           ? selColor
                                           : ColorScheme.Focus
@@ -997,11 +997,11 @@ public class TextField : View
             }
         }
 
-        Driver.SetAttribute (GetFocusColor ());
+        SetAttribute (GetFocusColor ());
 
         for (int i = col; i < width; i++)
         {
-            Driver.AddRune ((Rune)' ');
+            Driver?.AddRune ((Rune)' ');
         }
 
         PositionCursor ();
@@ -1010,6 +1010,8 @@ public class TextField : View
 
         DrawAutocomplete ();
         _isDrawing = false;
+
+        return true;
     }
 
     /// <inheritdoc/>
@@ -1093,7 +1095,7 @@ public class TextField : View
 
         _cursorPosition = Math.Min (selStart + cbTxt.GetRuneCount (), _text.Count);
         ClearAllSelection ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         Adjust ();
     }
 
@@ -1142,7 +1144,7 @@ public class TextField : View
 
         _selectedStart = 0;
         MoveEndExtend ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     ///// <summary>
@@ -1191,7 +1193,7 @@ public class TextField : View
         //SetContentSize(new (TextModel.DisplaySize (_text).size, 1));
 
         int offB = OffSetBackground ();
-        bool need = NeedsDisplay || !Used;
+        bool need = NeedsDraw || !Used;
 
         if (_cursorPosition < ScrollOffset)
         {
@@ -1216,7 +1218,7 @@ public class TextField : View
 
         if (need)
         {
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else
         {
@@ -1711,7 +1713,7 @@ public class TextField : View
                 _selectedText = null;
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else if (SelectedLength > 0 || _selectedText is { })
         {
@@ -1767,7 +1769,7 @@ public class TextField : View
         }
 
         var color = new Attribute (CaptionColor, GetNormalColor ().Background);
-        Driver.SetAttribute (color);
+        SetAttribute (color);
 
         Move (0, 0);
         string render = Caption;
@@ -1777,7 +1779,7 @@ public class TextField : View
             render = render [..Viewport.Width];
         }
 
-        Driver.AddStr (render);
+        Driver?.AddStr (render);
     }
 
     private void SetClipboard (IEnumerable<Rune> text)
@@ -1791,7 +1793,7 @@ public class TextField : View
     private void SetOverwrite (bool overwrite)
     {
         Used = overwrite;
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     private void SetSelectedStartSelectedLength ()

+ 19 - 17
Terminal.Gui/Views/TextValidateField.cs

@@ -526,7 +526,7 @@ namespace Terminal.Gui
 
                 _provider.Text = value;
 
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
         }
 
@@ -544,7 +544,7 @@ namespace Terminal.Gui
 
                 _cursorPosition = c;
                 SetFocus ();
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
 
                 return true;
             }
@@ -553,14 +553,14 @@ namespace Terminal.Gui
         }
 
         /// <inheritdoc/>
-        public override void OnDrawContent (Rectangle viewport)
+        protected override bool OnDrawingContent ()
         {
             if (_provider is null)
             {
                 Move (0, 0);
-                Driver.AddStr ("Error: ITextValidateProvider not set!");
+                Driver?.AddStr ("Error: ITextValidateProvider not set!");
 
-                return;
+                return true;
             }
 
             Color bgcolor = !IsValid ? new Color (Color.BrightRed) : ColorScheme.Focus.Background;
@@ -571,29 +571,31 @@ namespace Terminal.Gui
             Move (0, 0);
 
             // Left Margin
-            Driver.SetAttribute (textColor);
+            SetAttribute (textColor);
 
             for (var i = 0; i < margin_left; i++)
             {
-                Driver.AddRune ((Rune)' ');
+                Driver?.AddRune ((Rune)' ');
             }
 
             // Content
-            Driver.SetAttribute (textColor);
+            SetAttribute (textColor);
 
             // Content
             for (var i = 0; i < _provider.DisplayText.Length; i++)
             {
-                Driver.AddRune ((Rune)_provider.DisplayText [i]);
+                Driver?.AddRune ((Rune)_provider.DisplayText [i]);
             }
 
             // Right Margin
-            Driver.SetAttribute (textColor);
+            SetAttribute (textColor);
 
             for (var i = 0; i < margin_right; i++)
             {
-                Driver.AddRune ((Rune)' ');
+                Driver?.AddRune ((Rune)' ');
             }
+
+            return true;
         }
 
         /// <inheritdoc/>
@@ -655,7 +657,7 @@ namespace Terminal.Gui
 
             _cursorPosition = _provider.CursorLeft (_cursorPosition);
             _provider.Delete (_cursorPosition);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -671,7 +673,7 @@ namespace Terminal.Gui
 
             int current = _cursorPosition;
             _cursorPosition = _provider.CursorLeft (_cursorPosition);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return current != _cursorPosition;
         }
@@ -687,7 +689,7 @@ namespace Terminal.Gui
 
             int current = _cursorPosition;
             _cursorPosition = _provider.CursorRight (_cursorPosition);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return current != _cursorPosition;
         }
@@ -702,7 +704,7 @@ namespace Terminal.Gui
             }
 
             _provider.Delete (_cursorPosition);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -712,7 +714,7 @@ namespace Terminal.Gui
         private bool EndKeyHandler ()
         {
             _cursorPosition = _provider.CursorEnd ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -743,7 +745,7 @@ namespace Terminal.Gui
         private bool HomeKeyHandler ()
         {
             _cursorPosition = _provider.CursorStart ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }

+ 90 - 88
Terminal.Gui/Views/TextView.cs

@@ -1901,7 +1901,7 @@ public class TextView : View
 
         Added += TextView_Added!;
 
-        LayoutComplete += TextView_LayoutComplete;
+        SubviewsLaidOut += TextView_LayoutComplete;
 
         // Things this view knows how to do
 
@@ -2457,7 +2457,7 @@ public class TextView : View
                 AllowsTab = false;
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -2489,7 +2489,7 @@ public class TextView : View
                 _tabWidth = 0;
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -2522,7 +2522,7 @@ public class TextView : View
 
             CurrentRow = value.Y < 0 ? 0 :
                          value.Y > _model.Count - 1 ? Math.Max (_model.Count - 1, 0) : value.Y;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
             Adjust ();
         }
     }
@@ -2605,12 +2605,12 @@ public class TextView : View
                     _model.LoadString (Text);
                 }
 
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
             else if (_multiline && _savedHeight is { })
             {
                 Height = _savedHeight;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             KeyBindings.Remove (Key.Enter);
@@ -2629,7 +2629,7 @@ public class TextView : View
             {
                 _isReadOnly = value;
 
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
                 WrapTextModel ();
                 Adjust ();
             }
@@ -2683,7 +2683,7 @@ public class TextView : View
             _selectionStartColumn = value < 0 ? 0 :
                                     value > line.Count ? line.Count : value;
             IsSelecting = true;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
             Adjust ();
         }
     }
@@ -2697,7 +2697,7 @@ public class TextView : View
             _selectionStartRow = value < 0 ? 0 :
                                  value > _model.Count - 1 ? Math.Max (_model.Count - 1, 0) : value;
             IsSelecting = true;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
             Adjust ();
         }
     }
@@ -2715,7 +2715,7 @@ public class TextView : View
                 AllowsTab = true;
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -2747,7 +2747,7 @@ public class TextView : View
             }
 
             OnTextChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             _historyText.Clear (_model.GetAllLines ());
         }
@@ -2795,7 +2795,7 @@ public class TextView : View
                 _model = _wrapManager.Model;
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -2809,7 +2809,7 @@ public class TextView : View
         SetWrapModel ();
         bool res = _model.CloseFile ();
         ResetPosition ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         UpdateWrapModel ();
 
         return res;
@@ -2978,7 +2978,7 @@ public class TextView : View
         _selectionStartRow = 0;
         MoveBottomEndExtend ();
         DeleteCharLeft ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Deletes all the selected or a single character at left from the position of the cursor.</summary>
@@ -3196,7 +3196,7 @@ public class TextView : View
 
             InsertText (key);
 
-            if (NeedsDisplay)
+            if (NeedsDraw)
             {
                 Adjust ();
             }
@@ -3225,7 +3225,7 @@ public class TextView : View
         finally
         {
             UpdateWrapModel ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
             Adjust ();
         }
 
@@ -3243,7 +3243,7 @@ public class TextView : View
         _model.LoadStream (stream);
         _historyText.Clear (_model.GetAllLines ());
         ResetPosition ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         UpdateWrapModel ();
     }
 
@@ -3255,7 +3255,7 @@ public class TextView : View
         _model.LoadCells (cells, ColorScheme?.Focus);
         _historyText.Clear (_model.GetAllLines ());
         ResetPosition ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         UpdateWrapModel ();
         InheritsPreviousAttribute = true;
     }
@@ -3269,7 +3269,7 @@ public class TextView : View
         _model.LoadListCells (cellsList, ColorScheme?.Focus);
         _historyText.Clear (_model.GetAllLines ());
         ResetPosition ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         UpdateWrapModel ();
     }
 
@@ -3325,7 +3325,7 @@ public class TextView : View
             }
             else
             {
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             _lastWasKill = false;
@@ -3534,7 +3534,7 @@ public class TextView : View
         _leftColumn = 0;
         TrackColumn ();
         PositionCursor ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
@@ -3550,7 +3550,7 @@ public class TextView : View
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent ()
     {
         _isDrawing = true;
 
@@ -3616,7 +3616,7 @@ public class TextView : View
                     cols = Math.Max (cols, 1);
                 }
 
-                if (!TextModel.SetCol (ref col, viewport.Right, cols))
+                if (!TextModel.SetCol (ref col, Viewport.Right, cols))
                 {
                     break;
                 }
@@ -3639,12 +3639,14 @@ public class TextView : View
         if (row < bottom)
         {
             SetNormalColor ();
-            ClearRegion (viewport.Left, row, right, bottom);
+            ClearRegion (Viewport.Left, row, right, bottom);
         }
 
         //PositionCursor ();
 
         _isDrawing = false;
+
+        return false;
     }
 
     /// <inheritdoc/>
@@ -3756,7 +3758,7 @@ public class TextView : View
                               HistoryText.LineStatus.Replaced
                              );
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
             OnContentsChanged ();
         }
         else
@@ -3778,7 +3780,7 @@ public class TextView : View
                                          );
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         UpdateWrapModel ();
@@ -3801,8 +3803,8 @@ public class TextView : View
             // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
             //var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Viewport.Height);
             //var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Viewport.Height);
-            //SetNeedsDisplay (new (0, minRow, Viewport.Width, maxRow));
-            SetNeedsDisplay ();
+            //SetNeedsDraw (new (0, minRow, Viewport.Width, maxRow));
+            SetNeedsDraw ();
         }
 
         List<Cell> line = _model.GetLine (CurrentRow);
@@ -3917,7 +3919,7 @@ public class TextView : View
             _leftColumn = Math.Max (!_wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0);
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Select all text.</summary>
@@ -3933,7 +3935,7 @@ public class TextView : View
         _selectionStartRow = 0;
         CurrentColumn = _model.GetLine (_model.Count - 1).Count;
         CurrentRow = _model.Count - 1;
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     ///// <summary>Raised when the <see cref="Text"/> property of the <see cref="TextView"/> changes.</summary>
@@ -3960,7 +3962,7 @@ public class TextView : View
     /// <summary>
     ///     Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
     ///     of the current <paramref name="line"/>. Override to provide custom coloring by calling
-    ///     <see cref="ConsoleDriver.SetAttribute(Attribute)"/> Defaults to <see cref="ColorScheme.Normal"/>.
+    ///     <see cref="View.SetAttribute"/> Defaults to <see cref="ColorScheme.Normal"/>.
     /// </summary>
     /// <param name="line">The line.</param>
     /// <param name="idxCol">The col index.</param>
@@ -3974,18 +3976,18 @@ public class TextView : View
         if (line [idxCol].Attribute is { })
         {
             Attribute? attribute = line [idxCol].Attribute;
-            Driver.SetAttribute ((Attribute)attribute!);
+            SetAttribute ((Attribute)attribute!);
         }
         else
         {
-            Driver.SetAttribute (GetNormalColor ());
+            SetAttribute (GetNormalColor ());
         }
     }
 
     /// <summary>
     ///     Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
     ///     of the current <paramref name="line"/>. Override to provide custom coloring by calling
-    ///     <see cref="ConsoleDriver.SetAttribute(Attribute)"/> Defaults to <see cref="ColorScheme.Focus"/>.
+    ///     <see cref="View.SetAttribute(Attribute)"/> Defaults to <see cref="ColorScheme.Focus"/>.
     /// </summary>
     /// <param name="line">The line.</param>
     /// <param name="idxCol">The col index.</param>
@@ -4009,13 +4011,13 @@ public class TextView : View
             attribute = new (cellAttribute.Value.Foreground, ColorScheme!.Focus.Background);
         }
 
-        Driver.SetAttribute (attribute);
+        SetAttribute (attribute);
     }
 
     /// <summary>
     ///     Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
     ///     of the current <paramref name="line"/>. Override to provide custom coloring by calling
-    ///     <see cref="ConsoleDriver.SetAttribute(Attribute)"/> Defaults to <see cref="ColorScheme.Focus"/>.
+    ///     <see cref="View.SetAttribute(Attribute)"/> Defaults to <see cref="ColorScheme.Focus"/>.
     /// </summary>
     /// <param name="line">The line.</param>
     /// <param name="idxCol">The col index.</param>
@@ -4031,13 +4033,13 @@ public class TextView : View
         {
             Attribute? attribute = line [idxCol].Attribute;
 
-            Driver.SetAttribute (
+            SetAttribute (
                                  new (attribute!.Value.Background, attribute.Value.Foreground)
                                 );
         }
         else
         {
-            Driver.SetAttribute (
+            SetAttribute (
                                  new (
                                       ColorScheme!.Focus.Background,
                                       ColorScheme!.Focus.Foreground
@@ -4049,7 +4051,7 @@ public class TextView : View
     /// <summary>
     ///     Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
     ///     of the current <paramref name="line"/>. Override to provide custom coloring by calling
-    ///     <see cref="ConsoleDriver.SetAttribute(Attribute)"/> Defaults to <see cref="ColorScheme.HotFocus"/>.
+    ///     <see cref="View.SetAttribute(Attribute)"/> Defaults to <see cref="ColorScheme.HotFocus"/>.
     /// </summary>
     /// <param name="line">The line.</param>
     /// <param name="idxCol">The col index.</param>
@@ -4076,13 +4078,13 @@ public class TextView : View
     ///     Sets the driver to the default color for the control where no text is being rendered. Defaults to
     ///     <see cref="ColorScheme.Normal"/>.
     /// </summary>
-    protected virtual void SetNormalColor () { Driver.SetAttribute (GetNormalColor ()); }
+    protected virtual void SetNormalColor () { SetAttribute (GetNormalColor ()); }
 
     private void Adjust ()
     {
         (int width, int height) offB = OffSetBackground ();
         List<Cell> line = GetCurrentLine ();
-        bool need = NeedsDisplay || _wrapNeeded || !Used;
+        bool need = NeedsDraw || _wrapNeeded || !Used;
         (int size, int length) tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
         (int size, int length) dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth);
 
@@ -4136,7 +4138,7 @@ public class TextView : View
                 _wrapNeeded = false;
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else
         {
@@ -4266,14 +4268,14 @@ public class TextView : View
 
             if (_wordWrap)
             {
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
             else
             {
                 //QUESTION: Is the below comment still relevant?
                 // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
-                //SetNeedsDisplay (new (0, startRow - topRow, Viewport.Width, startRow - topRow + 1));
-                SetNeedsDisplay ();
+                //SetNeedsDraw (new (0, startRow - topRow, Viewport.Width, startRow - topRow + 1));
+                SetNeedsDraw ();
             }
 
             _historyText.Add (
@@ -4315,7 +4317,7 @@ public class TextView : View
 
         UpdateWrapModel ();
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     private void ClearSelectedRegion ()
@@ -4363,13 +4365,13 @@ public class TextView : View
             if (CurrentColumn < _leftColumn)
             {
                 _leftColumn--;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
             else
             {
                 // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
-                //SetNeedsDisplay (new (0, currentRow - topRow, 1, Viewport.Width));
-                SetNeedsDisplay ();
+                //SetNeedsDraw (new (0, currentRow - topRow, 1, Viewport.Width));
+                SetNeedsDraw ();
             }
         }
         else
@@ -4413,7 +4415,7 @@ public class TextView : View
                              );
 
             CurrentColumn = prevCount;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         UpdateWrapModel ();
@@ -4460,7 +4462,7 @@ public class TextView : View
                 _wrapNeeded = true;
             }
 
-            DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Viewport.Width, CurrentRow - _topRow + 1));
+            DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, CurrentRow - _topRow + 1));
         }
         else
         {
@@ -4479,7 +4481,7 @@ public class TextView : View
                 _wrapNeeded = true;
             }
 
-            DoSetNeedsDisplay (
+            DoSetNeedsDraw (
                                new (
                                     CurrentColumn - _leftColumn,
                                     CurrentRow - _topRow,
@@ -4496,7 +4498,7 @@ public class TextView : View
 
     private void DoNeededAction ()
     {
-        if (NeedsDisplay)
+        if (NeedsDraw)
         {
             Adjust ();
         }
@@ -4506,17 +4508,17 @@ public class TextView : View
         }
     }
 
-    private void DoSetNeedsDisplay (Rectangle rect)
+    private void DoSetNeedsDraw (Rectangle rect)
     {
         if (_wrapNeeded)
         {
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else
         {
             // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
-            //SetNeedsDisplay (rect);
-            SetNeedsDisplay ();
+            //SetNeedsDraw (rect);
+            SetNeedsDraw ();
         }
     }
 
@@ -4789,8 +4791,8 @@ public class TextView : View
         if (!_wrapNeeded)
         {
             // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
-            //SetNeedsDisplay (new (0, prow, Math.Max (Viewport.Width, 0), Math.Max (prow + 1, 0)));
-            SetNeedsDisplay ();
+            //SetNeedsDraw (new (0, prow, Math.Max (Viewport.Width, 0), Math.Max (prow + 1, 0)));
+            SetNeedsDraw ();
         }
     }
 
@@ -4844,13 +4846,13 @@ public class TextView : View
 
             if (_wordWrap)
             {
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
             else
             {
                 // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
-                //SetNeedsDisplay (new (0, currentRow - topRow, Viewport.Width, Math.Max (currentRow - topRow + 1, 0)));
-                SetNeedsDisplay ();
+                //SetNeedsDraw (new (0, currentRow - topRow, Viewport.Width, Math.Max (currentRow - topRow + 1, 0)));
+                SetNeedsDraw ();
             }
 
             UpdateWrapModel ();
@@ -4948,7 +4950,7 @@ public class TextView : View
                 if (CurrentColumn >= _leftColumn + Viewport.Width)
                 {
                     _leftColumn++;
-                    SetNeedsDisplay ();
+                    SetNeedsDraw ();
                 }
             }
             else
@@ -5063,7 +5065,7 @@ public class TextView : View
 
         UpdateWrapModel ();
 
-        DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
+        DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
 
         _lastWasKill = setLastWasKill;
         DoNeededAction ();
@@ -5170,7 +5172,7 @@ public class TextView : View
 
         UpdateWrapModel ();
 
-        DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
+        DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
 
         _lastWasKill = setLastWasKill;
         DoNeededAction ();
@@ -5240,7 +5242,7 @@ public class TextView : View
 
         UpdateWrapModel ();
 
-        DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
+        DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
         DoNeededAction ();
     }
 
@@ -5299,7 +5301,7 @@ public class TextView : View
 
         UpdateWrapModel ();
 
-        DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
+        DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
         DoNeededAction ();
     }
 
@@ -5352,7 +5354,7 @@ public class TextView : View
             if (CurrentRow >= _topRow + Viewport.Height)
             {
                 _topRow++;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             TrackColumn ();
@@ -5395,7 +5397,7 @@ public class TextView : View
                 if (CurrentRow < _topRow)
                 {
                     _topRow--;
-                    SetNeedsDisplay ();
+                    SetNeedsDraw ();
                 }
 
                 List<Cell> currentLine = GetCurrentLine ();
@@ -5433,7 +5435,7 @@ public class TextView : View
                 _topRow = CurrentRow >= _model.Count
                               ? CurrentRow - nPageDnShift
                               : _topRow + nPageDnShift;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             TrackColumn ();
@@ -5459,7 +5461,7 @@ public class TextView : View
             if (CurrentRow < _topRow)
             {
                 _topRow = _topRow - nPageUpShift < 0 ? 0 : _topRow - nPageUpShift;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             TrackColumn ();
@@ -5487,7 +5489,7 @@ public class TextView : View
                 if (CurrentRow >= _topRow + Viewport.Height)
                 {
                     _topRow++;
-                    SetNeedsDisplay ();
+                    SetNeedsDraw ();
                 }
                 else
                 {
@@ -5510,7 +5512,7 @@ public class TextView : View
     {
         if (_leftColumn > 0)
         {
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         CurrentColumn = 0;
@@ -5552,7 +5554,7 @@ public class TextView : View
             if (CurrentRow < _topRow)
             {
                 _topRow--;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             TrackColumn ();
@@ -5684,7 +5686,7 @@ public class TextView : View
                                  );
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             UpdateWrapModel ();
         }
@@ -6184,12 +6186,12 @@ public class TextView : View
 
         CurrentRow++;
 
-        var fullNeedsDisplay = false;
+        var fullNeedsDraw = false;
 
         if (CurrentRow >= _topRow + Viewport.Height)
         {
             _topRow++;
-            fullNeedsDisplay = true;
+            fullNeedsDraw = true;
         }
 
         CurrentColumn = 0;
@@ -6202,19 +6204,19 @@ public class TextView : View
 
         if (!_wordWrap && CurrentColumn < _leftColumn)
         {
-            fullNeedsDisplay = true;
+            fullNeedsDraw = true;
             _leftColumn = 0;
         }
 
-        if (fullNeedsDisplay)
+        if (fullNeedsDraw)
         {
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
         else
         {
             // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
-            //SetNeedsDisplay (new (0, currentRow - topRow, 2, Viewport.Height));
-            SetNeedsDisplay ();
+            //SetNeedsDraw (new (0, currentRow - topRow, 2, Viewport.Height));
+            SetNeedsDraw ();
         }
 
         UpdateWrapModel ();
@@ -6337,7 +6339,7 @@ public class TextView : View
             else
             {
                 UpdateWrapModel ();
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
                 Adjust ();
             }
 
@@ -6355,15 +6357,15 @@ public class TextView : View
     private void SetOverwrite (bool overwrite)
     {
         Used = overwrite;
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         DoNeededAction ();
     }
 
-    private static void SetValidUsedColor (Attribute? attribute)
+    private void SetValidUsedColor (Attribute? attribute)
     {
         // BUGBUG: (v2 truecolor) This code depends on 8-bit color names; disabling for now
         //if ((colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background) == colorScheme.Focus.Foreground) {
-        Driver.SetAttribute (new (attribute!.Value.Background, attribute!.Value.Foreground));
+        SetAttribute (new (attribute!.Value.Background, attribute!.Value.Foreground));
     }
 
     /// <summary>Restore from original model.</summary>
@@ -6527,7 +6529,7 @@ public class TextView : View
             _selectionStartColumn = nStartCol;
             _wrapNeeded = true;
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         if (_currentCaller is { })
@@ -6558,7 +6560,7 @@ public class TextView : View
             CurrentColumn = nCol;
             _selectionStartRow = nStartRow;
             _selectionStartColumn = nStartCol;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 }

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

@@ -31,7 +31,7 @@ public class Tile
     ///     The <see cref="ContentView"/> that is contained in this <see cref="TileView"/>. Add new child views to this
     ///     member for multiple <see cref="ContentView"/>s within the <see cref="Tile"/>.
     /// </summary>
-    public View ContentView { get; internal set; }
+    public View? ContentView { get; internal set; }
 
     /// <summary>
     ///     Gets or Sets the minimum size you to allow when splitter resizing along parent

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است