2
0
Tig 9 сар өмнө
parent
commit
cbb7ddc1f5
100 өөрчлөгдсөн 3657 нэмэгдсэн , 2560 устгасан
  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. 91 41
      Terminal.Gui/Application/Application.Run.cs
  6. 28 5
      Terminal.Gui/Application/Application.Screen.cs
  7. 3 1
      Terminal.Gui/Application/Application.cs
  8. 1 1
      Terminal.Gui/Clipboard/Clipboard.cs
  9. 43 14
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  10. 11 11
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  11. 7 7
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  12. 11 13
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  13. 23 14
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  14. 269 0
      Terminal.Gui/Drawing/Region.cs
  15. 6 5
      Terminal.Gui/Drawing/Thickness.cs
  16. 5 7
      Terminal.Gui/Terminal.Gui.csproj
  17. 8 8
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs
  18. 4 2
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.PopUp.cs
  19. 9 9
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  20. 94 107
      Terminal.Gui/View/Adornment/Adornment.cs
  21. 61 46
      Terminal.Gui/View/Adornment/Border.cs
  22. 91 96
      Terminal.Gui/View/Adornment/Margin.cs
  23. 2 2
      Terminal.Gui/View/Adornment/Padding.cs
  24. 16 8
      Terminal.Gui/View/Adornment/ShadowView.cs
  25. 4 2
      Terminal.Gui/View/DrawEventArgs.cs
  26. 10 2
      Terminal.Gui/View/Layout/Dim.cs
  27. 69 67
      Terminal.Gui/View/Layout/DimAuto.cs
  28. 1 1
      Terminal.Gui/View/Layout/DimFunc.cs
  29. 1 1
      Terminal.Gui/View/Layout/LayoutEventArgs.cs
  30. 30 0
      Terminal.Gui/View/Layout/LayoutException.cs
  31. 8 8
      Terminal.Gui/View/Layout/Pos.cs
  32. 30 43
      Terminal.Gui/View/Layout/PosAlign.cs
  33. 1 1
      Terminal.Gui/View/Layout/PosAnchorEnd.cs
  34. 1 1
      Terminal.Gui/View/Layout/PosFunc.cs
  35. 23 5
      Terminal.Gui/View/Layout/PosView.cs
  36. 33 74
      Terminal.Gui/View/View.Adornments.cs
  37. 147 0
      Terminal.Gui/View/View.Attribute.cs
  38. 2 5
      Terminal.Gui/View/View.Content.cs
  39. 19 6
      Terminal.Gui/View/View.Diagnostics.cs
  40. 46 0
      Terminal.Gui/View/View.Drawing.Clipping.cs
  41. 128 0
      Terminal.Gui/View/View.Drawing.Primitives.cs
  42. 447 482
      Terminal.Gui/View/View.Drawing.cs
  43. 16 9
      Terminal.Gui/View/View.Hierarchy.cs
  44. 340 359
      Terminal.Gui/View/View.Layout.cs
  45. 13 10
      Terminal.Gui/View/View.Mouse.cs
  46. 9 9
      Terminal.Gui/View/View.Navigation.cs
  47. 13 8
      Terminal.Gui/View/View.Text.cs
  48. 22 21
      Terminal.Gui/View/View.cs
  49. 1 1
      Terminal.Gui/View/ViewportSettings.cs
  50. 65 49
      Terminal.Gui/Views/Bar.cs
  51. 1 1
      Terminal.Gui/Views/Button.cs
  52. 1 1
      Terminal.Gui/Views/CheckBox.cs
  53. 9 9
      Terminal.Gui/Views/ColorBar.cs
  54. 45 26
      Terminal.Gui/Views/ColorPicker.16.cs
  55. 7 7
      Terminal.Gui/Views/ColorPicker.cs
  56. 30 22
      Terminal.Gui/Views/ComboBox.cs
  57. 6 2
      Terminal.Gui/Views/DatePicker.cs
  58. 9 7
      Terminal.Gui/Views/FileDialog.cs
  59. 15 4
      Terminal.Gui/Views/GraphView/Annotations.cs
  60. 53 8
      Terminal.Gui/Views/GraphView/GraphView.cs
  61. 2 2
      Terminal.Gui/Views/GraphView/Series.cs
  62. 39 28
      Terminal.Gui/Views/HexView.cs
  63. 7 6
      Terminal.Gui/Views/Line.cs
  64. 4 5
      Terminal.Gui/Views/LineView.cs
  65. 57 37
      Terminal.Gui/Views/ListView.cs
  66. 37 30
      Terminal.Gui/Views/Menu/Menu.cs
  67. 18 20
      Terminal.Gui/Views/Menu/MenuBar.cs
  68. 1 1
      Terminal.Gui/Views/MenuBarv2.cs
  69. 2 2
      Terminal.Gui/Views/Menuv2.cs
  70. 17 3
      Terminal.Gui/Views/NumericUpDown.cs
  71. 12 10
      Terminal.Gui/Views/ProgressBar.cs
  72. 17 18
      Terminal.Gui/Views/RadioGroup.cs
  73. 17 13
      Terminal.Gui/Views/ScrollBarView.cs
  74. 11 9
      Terminal.Gui/Views/ScrollView.cs
  75. 95 119
      Terminal.Gui/Views/Shortcut.cs
  76. 19 17
      Terminal.Gui/Views/Slider.cs
  77. 1 1
      Terminal.Gui/Views/SpinnerView/SpinnerStyle.cs
  78. 34 9
      Terminal.Gui/Views/SpinnerView/SpinnerView.cs
  79. 1 1
      Terminal.Gui/Views/StatusBar.cs
  80. 5 4
      Terminal.Gui/Views/Tab.cs
  81. 127 92
      Terminal.Gui/Views/TabView.cs
  82. 3 3
      Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs
  83. 1 1
      Terminal.Gui/Views/TableView/ListTableSource.cs
  84. 120 39
      Terminal.Gui/Views/TableView/TableView.cs
  85. 2 2
      Terminal.Gui/Views/TableView/TreeTableSource.cs
  86. 22 20
      Terminal.Gui/Views/TextField.cs
  87. 19 17
      Terminal.Gui/Views/TextValidateField.cs
  88. 88 86
      Terminal.Gui/Views/TextView.cs
  89. 1 1
      Terminal.Gui/Views/Tile.cs
  90. 148 139
      Terminal.Gui/Views/TileView.cs
  91. 1 1
      Terminal.Gui/Views/TimeField.cs
  92. 10 20
      Terminal.Gui/Views/Toplevel.cs
  93. 64 42
      Terminal.Gui/Views/TreeView/TreeView.cs
  94. 1 1
      Terminal.Gui/Views/TreeViewTextFilter.cs
  95. 0 3
      Terminal.Gui/Views/Wizard/Wizard.cs
  96. 5 5
      Terminal.Gui/Views/Wizard/WizardStep.cs
  97. 26 0
      UICatalog/BenchmarkResults.cs
  98. 1 1
      UICatalog/KeyBindingsDialog.cs
  99. 14 40
      UICatalog/Properties/launchSettings.json
  100. 151 54
      UICatalog/Scenario.cs

+ 1 - 1
CommunityToolkitExample/LoginView.cs

@@ -59,7 +59,7 @@ internal partial class LoginView : IRecipient<Message<LoginActions>>
                 }
         }
         SetText();
-        Application.Refresh ();
+        Application.LayoutAndDrawToplevels ();
     }
 
     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 ();
+                        LayoutAndDrawToplevels ();
 
                         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
             {

+ 91 - 41
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,22 +495,60 @@ 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 ()
+    // TODO: Rename this to LayoutAndDrawRunnables in https://github.com/gui-cs/Terminal.Gui/issues/2491
+    /// <summary>
+    /// Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need dispplay. 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 LayoutAndDrawToplevels (bool forceDraw = false)
     {
+        bool neededLayout = false;
+        neededLayout = LayoutToplevels ();
+
+        if (forceDraw)
+        {
+            Driver?.ClearContents ();
+        }
+
+        DrawToplevels (neededLayout || forceDraw);
+
+        Driver?.Refresh ();
+    }
+
+    // TODO: Rename this to LayoutRunnables in https://github.com/gui-cs/Terminal.Gui/issues/2491
+
+    private static bool LayoutToplevels ()
+    {
+        bool neededLayout = false;
+
         foreach (Toplevel tl in TopLevels.Reverse ())
         {
-            if (tl.LayoutNeeded)
+            if (tl.NeedsLayout)
             {
-                tl.LayoutSubviews ();
+                neededLayout = true;
+                tl.Layout (Screen.Size);
+            }
+        }
+
+        return neededLayout;
+    }
+
+    // TODO: Rename this to DrawRunnables in https://github.com/gui-cs/Terminal.Gui/issues/2491
+    private static void DrawToplevels (bool forceDraw)
+    {
+        foreach (Toplevel tl in TopLevels.Reverse ())
+        {
+            if (forceDraw)
+            {
+                tl.SetNeedsDraw ();
             }
 
             tl.Draw ();
         }
-
-        Driver!.Refresh ();
     }
 
+
     /// <summary>This event is raised on each iteration of the main loop.</summary>
     /// <remarks>See also <see cref="Timeout"/></remarks>
     public static event EventHandler<IterationEventArgs>? Iteration;
@@ -534,24 +581,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 +609,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
             }
 
             MainLoop.RunIteration ();
+
             Iteration?.Invoke (null, new ());
         }
 
@@ -568,16 +617,17 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
         if (Top is null)
         {
-            return;
+            return firstIteration;
         }
 
-        Refresh ();
+        LayoutAndDrawToplevels ();
 
         if (PositionCursor ())
         {
             Driver!.UpdateCursor ();
         }
 
+        return firstIteration;
     }
 
     /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
@@ -652,7 +702,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 +720,6 @@ public static partial class Application // Run (Begin, Run, End, Stop)
         runState.Toplevel = null;
         runState.Dispose ();
 
-        Refresh ();
+        LayoutAndDrawToplevels ();
     }
 }

+ 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 ();
+        LayoutAndDrawToplevels ();
 
         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;

+ 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
                {

+ 43 - 14
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
+    public 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);
+            }
         }
     }
 
@@ -130,6 +135,8 @@ public abstract class ConsoleDriver
             return;
         }
 
+        Rectangle clipRect = Clip!.GetBounds ();
+
         if (validLocation)
         {
             rune = rune.MakePrintable ();
@@ -217,14 +224,14 @@ 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 (Col == clipRect.Right - 1)
                         {
                             // 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 ' '
@@ -234,7 +241,7 @@ public abstract class ConsoleDriver
                         {
                             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 +271,7 @@ public abstract class ConsoleDriver
         {
             Debug.Assert (runeWidth <= 2);
 
-            if (validLocation && Col < Clip.Right)
+            if (validLocation && Col < clipRect.Right)
             {
                 lock (Contents!)
                 {
@@ -314,9 +321,11 @@ public abstract class ConsoleDriver
     public 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,8 +344,15 @@ 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.
@@ -368,7 +384,8 @@ public abstract class ConsoleDriver
     /// <param name="rune">The Rune used to fill the rectangle</param>
     public 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++)
@@ -420,7 +437,7 @@ public abstract class ConsoleDriver
     /// </returns>
     public bool IsValidLocation (int col, int row)
     {
-        return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row);
+        return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip!.Contains (col, row);
     }
 
     /// <summary>
@@ -448,7 +465,18 @@ public abstract class ConsoleDriver
     public 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 ();
+    public 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 +494,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 +559,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 +569,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).

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

@@ -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 (new (0, 0, Cols, Rows));
     }
 
     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 (new (0, 0, Cols, Rows));
             }
             catch (ArgumentOutOfRangeException)
             {
                 // CONCURRENCY: Unsynchronized access to Clip is not safe.
-                Clip = new (0, 0, Cols, Rows);
+                Clip = new (new (0, 0, Cols, Rows));
             }
         }
         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 (new (0, 0, Cols, Rows));
     }
 
     #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 (new (0, 0, Cols, Rows));
 
         _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 (new (0, 0, Cols, Rows));
 
         _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 ();
+            }
         }
     }
 

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

@@ -0,0 +1,269 @@
+/// <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 portion of 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>
+    ///     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 (); }
+}

+ 6 - 5
Terminal.Gui/Drawing/Thickness.cs

@@ -82,15 +82,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 +104,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';
@@ -155,7 +156,7 @@ public record struct Thickness
                                         );
         }
 
-        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,7 +188,7 @@ 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}";

+ 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 (Rectangle viewport)
         {
             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;
         }

+ 94 - 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,53 @@ public class Adornment : View
     /// <inheritdoc/>
     public override Point ScreenToFrame (in Point location)
     {
-        return Parent!.ScreenToFrame (new (location.X - Frame.X, location.Y - Frame.Y));
-    }
+        View? parentOrSuperView = Parent;
 
-    /// <summary>Does nothing for Adornment</summary>
-    /// <returns></returns>
-    public override bool OnDrawAdornments () { return false; }
-
-    /// <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>
+    /// <param name="viewport"></param>
+    /// <returns><see langword="true"/> to stop further clearing.</returns>
+    protected override bool OnClearingViewport (Rectangle viewport)
+    {
+        if (Thickness == Thickness.Empty)
         {
-            base.OnDrawContent (viewport);
+            return true;
         }
 
-        if (Driver is { })
-        {
-           Driver.Clip = prevClip;
-        }
+        Attribute normalAttr = GetNormalColor ();
+        SetAttribute (normalAttr);
+
+        // This just draws/clears the thickness, not the insides.
+        Thickness.Draw (ViewportToScreen (viewport), Diagnostics, ToString ());
 
-        ClearLayoutNeeded ();
-        ClearNeedsDisplay ();
+        return true;
     }
 
+    /// <inheritdoc/>
+    protected override bool OnDrawingText (Rectangle viewport) { return Thickness == Thickness.Empty; }
+
+    /// <inheritdoc/>
+    protected override bool OnDrawingSubviews (Rectangle viewport) { 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 +194,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;
+    }
 }

+ 61 - 46
Terminal.Gui/View/Adornment/Border.cs

@@ -105,6 +105,16 @@ public class Border : Adornment
             LayoutStarted += OnLayoutStarted;
     }
 #endif
+        if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.DrawIndicator))
+        {
+            _drawIndicator = new SpinnerView ()
+            {
+                X = 1,
+                Style = new SpinnerStyle.Dots2 (),
+                SpinDelay = 0,
+            };
+            Add (_drawIndicator);
+        }
     }
 
 #if SUBVIEW_BASED_BORDER
@@ -142,7 +152,7 @@ public class Border : Adornment
         set
         {
             base.ColorScheme = value;
-            Parent?.SetNeedsDisplay ();
+            Parent?.SetNeedsDraw ();
         }
     }
 
@@ -191,7 +201,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 +223,7 @@ public class Border : Adornment
 
             _settings = value;
 
-            Parent?.SetNeedsDisplay ();
+            Parent?.SetNeedsDraw ();
         }
     }
 
@@ -254,7 +264,7 @@ public class Border : Adornment
             ColorScheme = cs;
         }
 
-        Parent?.SetNeedsDisplay ();
+        Parent?.SetNeedsDraw ();
         e.Cancel = true;
     }
 
@@ -266,9 +276,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 +447,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;
@@ -565,7 +575,6 @@ public class Border : Adornment
 
                         break;
                 }
-                Application.Refresh ();
 
                 return true;
             }
@@ -604,20 +613,15 @@ public class Border : Adornment
     #endregion Mouse Support
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent (Rectangle viewport)
     {
-        base.OnDrawContent (viewport);
-
         if (Thickness == Thickness.Empty)
         {
-            return;
+            return true;
         }
 
-        //Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal);
         Rectangle screenBounds = ViewportToScreen (viewport);
 
-        //OnDrawSubviews (bounds); 
-
         // TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title)
 
         // The border adornment (and title) are drawn at the outermost edge of border;
@@ -633,12 +637,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 +684,20 @@ 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 ());
+                                            Parent.HasFocus ? focus : GetNormalColor (),
+                                            Parent.HasFocus ? focus : GetHotNormalColor ());
         }
 
         if (canDrawBorder && LineStyle != LineStyle.None)
@@ -702,15 +709,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 +732,7 @@ public class Border : Adornment
                                  borderBounds.Width,
                                  Orientation.Horizontal,
                                  lineStyle,
-                                 Driver.GetAttribute ()
+                                 Driver?.GetAttribute ()
                                 );
                 }
                 else
@@ -740,7 +747,7 @@ public class Border : Adornment
                                      Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
                                      Orientation.Horizontal,
                                      lineStyle,
-                                     Driver.GetAttribute ()
+                                     Driver?.GetAttribute ()
                                     );
                     }
 
@@ -754,7 +761,7 @@ public class Border : Adornment
                                      Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
                                      Orientation.Horizontal,
                                      lineStyle,
-                                     Driver.GetAttribute ()
+                                     Driver?.GetAttribute ()
                                     );
 
                         lc?.AddLine (
@@ -762,7 +769,7 @@ public class Border : Adornment
                                      Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
                                      Orientation.Horizontal,
                                      lineStyle,
-                                     Driver.GetAttribute ()
+                                     Driver?.GetAttribute ()
                                     );
                     }
 
@@ -773,7 +780,7 @@ public class Border : Adornment
                                  2,
                                  Orientation.Horizontal,
                                  lineStyle,
-                                 Driver.GetAttribute ()
+                                 Driver?.GetAttribute ()
                                 );
 
                     // Add a vert line for ╔╡
@@ -782,7 +789,7 @@ public class Border : Adornment
                                  titleBarsLength,
                                  Orientation.Vertical,
                                  LineStyle.Single,
-                                 Driver.GetAttribute ()
+                                 Driver?.GetAttribute ()
                                 );
 
                     // Add a vert line for ╞
@@ -797,7 +804,7 @@ public class Border : Adornment
                                  titleBarsLength,
                                  Orientation.Vertical,
                                  LineStyle.Single,
-                                 Driver.GetAttribute ()
+                                 Driver?.GetAttribute ()
                                 );
 
                     // Add the right hand line for ╞═════╗
@@ -812,7 +819,7 @@ public class Border : Adornment
                                  borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
                                  Orientation.Horizontal,
                                  lineStyle,
-                                 Driver.GetAttribute ()
+                                 Driver?.GetAttribute ()
                                 );
                 }
             }
@@ -826,7 +833,7 @@ public class Border : Adornment
                              sideLineLength,
                              Orientation.Vertical,
                              lineStyle,
-                             Driver.GetAttribute ()
+                             Driver?.GetAttribute ()
                             );
             }
 #endif
@@ -838,7 +845,7 @@ public class Border : Adornment
                              borderBounds.Width,
                              Orientation.Horizontal,
                              lineStyle,
-                             Driver.GetAttribute ()
+                             Driver?.GetAttribute ()
                             );
             }
 
@@ -849,11 +856,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,6 +913,22 @@ public class Border : Adornment
                 lc!.Fill = null;
             }
         }
+
+        return true;
+    }
+
+    private SpinnerView? _drawIndicator = null;
+    /// <inheritdoc />
+    protected override bool OnRenderingLineCanvas ()
+    {
+        if (_drawIndicator is { })
+        {
+            _drawIndicator.AdvanceAnimation (false);
+            _drawIndicator.DrawText();
+        }
+
+        RenderLineCanvas ();
+        return true;
     }
 
     private void SetupGradientLineCanvas (LineCanvas lc, Rectangle rect)
@@ -1249,8 +1272,6 @@ public class Border : Adornment
                             }
                         }
 
-                        Application.Refresh ();
-
                         return true;
                     });
 
@@ -1273,8 +1294,6 @@ public class Border : Adornment
                             Parent!.Height = Parent.Height! + 1;
                         }
 
-                        Application.Refresh ();
-
                         return true;
                     });
 
@@ -1300,8 +1319,6 @@ public class Border : Adornment
                             }
                         }
 
-                        Application.Refresh ();
-
                         return true;
                     });
 
@@ -1324,8 +1341,6 @@ public class Border : Adornment
                             Parent!.Width = Parent.Width! + 1;
                         }
 
-                        Application.Refresh ();
-
                         return true;
                     });
 

+ 91 - 96
Terminal.Gui/View/Adornment/Margin.cs

@@ -19,9 +19,9 @@ 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;
@@ -43,27 +43,6 @@ 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>
@@ -84,64 +63,60 @@ public class Margin : Adornment
         set
         {
             base.ColorScheme = value;
-            Parent?.SetNeedsDisplay ();
+            Parent?.SetNeedsDraw ();
         }
     }
 
-    /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    /// <inheritdoc />
+    protected override bool OnClearingViewport (Rectangle viewport)
     {
-        if (!NeedsDisplay)
+        if (Thickness == Thickness.Empty)
         {
-            return;
+            return true;
         }
 
         Rectangle screen = ViewportToScreen (viewport);
-        Attribute normalAttr = GetNormalColor ();
-
-        Driver?.SetAttribute (normalAttr);
 
         if (ShadowStyle != ShadowStyle.None)
         {
+            // Don't clear where the shadow goes
             screen = Rectangle.Inflate (screen, -1, -1);
         }
 
         // This just draws/clears the thickness, not the insides.
-        Thickness.Draw (screen, ToString ());
+        Thickness.Draw (screen, Diagnostics, ToString ());
 
-        if (Subviews.Count > 0)
-        {
-            // 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 ();
-                }
-            }
-        }
+        return true;
     }
 
+    ///// <inheritdoc />
+    ////protected override bool OnDrawSubviews (Rectangle viewport) { return true; }
+
+    //protected override bool OnDrawComplete (Rectangle viewport)
+    //{
+    //    DoDrawSubviews (viewport);
+
+    //    return true;
+    //}
+
     /// <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)
@@ -156,14 +131,28 @@ public class Margin : Adornment
             Thickness = new (Thickness.Left, Thickness.Top, Thickness.Right + 1, Thickness.Bottom + 1);
         }
 
-        if (_rightShadow is { })
-        {
-            _rightShadow.ShadowStyle = style;
-        }
-
-        if (_bottomShadow is { })
+        if (style != ShadowStyle.None)
         {
-            _bottomShadow.ShadowStyle = style;
+            _rightShadow = new ()
+            {
+                X = Pos.AnchorEnd (1),
+                Y = 0,
+                Width = 1,
+                Height = Dim.Fill (),
+                ShadowStyle = style,
+                Orientation = Orientation.Vertical
+            };
+
+            _bottomShadow = new ()
+            {
+                X = 0,
+                Y = Pos.AnchorEnd (1),
+                Width = Dim.Fill (),
+                Height = 1,
+                ShadowStyle = style,
+                Orientation = Orientation.Horizontal
+            };
+            Add (_rightShadow, _bottomShadow);
         }
 
         return style;
@@ -173,7 +162,11 @@ public class Margin : Adornment
     public override ShadowStyle ShadowStyle
     {
         get => base.ShadowStyle;
-        set => base.ShadowStyle = SetShadow (value);
+        set
+        {
+            base.ShadowStyle = SetShadow (value);
+
+        }
     }
 
     private const int PRESS_MOVE_HORIZONTAL = 1;
@@ -181,47 +174,49 @@ public class Margin : Adornment
 
     private void Margin_Highlight (object? sender, CancelEventArgs<HighlightStyle> e)
     {
-        if (ShadowStyle != ShadowStyle.None)
+        if (Thickness == Thickness.Empty || ShadowStyle == ShadowStyle.None)
         {
-            if (_pressed && e.NewValue == HighlightStyle.None)
+            return;
+        }
+
+        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 { })
+            {
+                _rightShadow.Visible = true;
+            }
+
+            if (_bottomShadow 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);
+                _bottomShadow.Visible = true;
+            }
 
-                if (_rightShadow is { })
-                {
-                    _rightShadow.Visible = true;
-                }
+            _pressed = false;
 
-                if (_bottomShadow is { })
-                {
-                    _bottomShadow.Visible = true;
-                }
+            return;
+        }
 
-                _pressed = false;
+        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;
 
-                return;
+            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;
             }
         }
     }

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

@@ -35,7 +35,7 @@ public class Padding : Adornment
         set
         {
             base.ColorScheme = value;
-            Parent?.SetNeedsDisplay ();
+            Parent?.SetNeedsDraw ();
         }
     }
 
@@ -62,7 +62,7 @@ public class Padding : Adornment
             if (Parent.CanFocus && !Parent.HasFocus)
             {
                 Parent.SetFocus ();
-                Parent.SetNeedsDisplay ();
+                Parent.SetNeedsDraw ();
                 return mouseEvent.Handled = true;
             }
         }

+ 16 - 8
Terminal.Gui/View/Adornment/ShadowView.cs

@@ -36,10 +36,16 @@ internal class ShadowView : View
         return base.GetNormalColor ();
     }
 
+    /// <inheritdoc />
+    protected override bool OnClearingViewport (Rectangle viewport)
+    {
+        // Prevent clearing (so we can have transparency)
+        return true;
+    }
+
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent (Rectangle viewport)
     {
-        //base.OnDrawContent (viewport);
         switch (ShadowStyle)
         {
             case ShadowStyle.Opaque:
@@ -57,7 +63,7 @@ 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)
                 {
@@ -68,10 +74,12 @@ internal class ShadowView : View
                     DrawHorizontalShadowTransparent (viewport);
                 }
 
-                //Driver.SetAttribute (prevAttr);
+                //SetAttribute (prevAttr);
 
                 break;
         }
+
+        return true;
     }
 
     /// <summary>
@@ -111,9 +119,9 @@ internal class ShadowView : View
         // 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++)
         {
-            Driver.Move (i, screen.Y);
+            Driver?.Move (i, screen.Y);
 
-            if (i < Driver.Contents!.GetLength (1) && screen.Y < Driver.Contents.GetLength (0))
+            if (i < Driver?.Contents!.GetLength (1) && screen.Y < Driver?.Contents?.GetLength (0))
             {
                 Driver.AddRune (Driver.Contents [screen.Y, i].Rune);
             }
@@ -139,9 +147,9 @@ internal class ShadowView : View
         // Fill the rest of the rectangle
         for (int i = Math.Max (0, screen.Y); i < screen.Y + viewport.Height; i++)
         {
-            Driver.Move (screen.X, i);
+            Driver?.Move (screen.X, i);
 
-            if (Driver.Contents is { } && screen.X < Driver.Contents.GetLength (1) && i < Driver.Contents.GetLength (0))
+            if (Driver?.Contents is { } && screen.X < Driver.Contents.GetLength (1) && i < Driver.Contents.GetLength (0))
             {
                 Driver.AddRune (Driver.Contents [i, screen.X].Rune);
             }

+ 4 - 2
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">

+ 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 ()

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

@@ -54,7 +54,7 @@ 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>
@@ -109,8 +109,8 @@ 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>
@@ -134,9 +134,23 @@ public partial class View // Adornments
         get => Border?.LineStyle ?? LineStyle.Single;
         set
         {
+            if (Border is null)
+            {
+                return;
+            }
+
             LineStyle old = Border?.LineStyle ?? LineStyle.None;
             CancelEventArgs<LineStyle> e = new (ref old, ref value);
-            OnBorderStyleChanging (e);
+
+            if (OnBorderStyleChanging (e) || e.Cancel)
+            {
+                return;
+            }
+
+            SetBorderStyle (e.NewValue);
+            SetAdornmentFrames ();
+            SetNeedsLayout ();
+
         }
     }
 
@@ -148,23 +162,16 @@ public partial class View // Adornments
     ///     Override <see cref="SetBorderStyle"/> to prevent the <see cref="BorderStyle"/> from changing.
     /// </remarks>
     /// <param name="e"></param>
-    protected void OnBorderStyleChanging (CancelEventArgs<LineStyle> e)
+    protected virtual bool OnBorderStyleChanging (CancelEventArgs<LineStyle> e)
     {
         if (Border is null)
         {
-            return;
+            return false;
         }
 
         BorderStyleChanging?.Invoke (this, e);
 
-        if (e.Cancel)
-        {
-            return;
-        }
-
-        SetBorderStyle (e.NewValue);
-        LayoutAdornments ();
-        SetNeedsLayout ();
+        return e.Cancel;
     }
 
     /// <summary>
@@ -217,8 +224,8 @@ 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>
@@ -243,70 +250,22 @@ public partial class View // Adornments
         return Margin.Thickness + Border.Thickness + Padding.Thickness;
     }
 
-    /// <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 ()
+    /// <summary>Sets the Frame's of the Margin, Border, and Padding.</summary>
+    internal void SetAdornmentFrames ()
     {
-        if (Margin is null)
+        if (this is Adornment)
         {
-            return; // CreateAdornments () has not been called yet
-        }
-
-        if (Margin.Frame.Size != Frame.Size)
-        {
-            Margin.SetFrame (Rectangle.Empty with { Size = Frame.Size });
-            Margin.X = 0;
-            Margin.Y = 0;
-            Margin.Width = Frame.Size.Width;
-            Margin.Height = Frame.Size.Height;
-        }
-
-        Margin.SetNeedsLayout ();
-        Margin.SetNeedsDisplay ();
-
-        if (IsInitialized)
-        {
-            Margin.LayoutSubviews ();
-        }
-
-        Rectangle border = Margin.Thickness.GetInside (Margin.Frame);
-
-        if (border != Border.Frame)
-        {
-            Border.SetFrame (border);
-            Border.X = border.Location.X;
-            Border.Y = border.Location.Y;
-            Border.Width = border.Size.Width;
-            Border.Height = border.Size.Height;
-        }
-
-        Border.SetNeedsLayout ();
-        Border.SetNeedsDisplay ();
-
-        if (IsInitialized)
-        {
-            Border.LayoutSubviews ();
+            // Adornments do not have Adornments
+            return;
         }
 
-        Rectangle padding = Border.Thickness.GetInside (Border.Frame);
-
-        if (padding != Padding.Frame)
+        if (Margin is null)
         {
-            Padding.SetFrame (padding);
-            Padding.X = padding.Location.X;
-            Padding.Y = padding.Location.Y;
-            Padding.Width = padding.Size.Width;
-            Padding.Height = padding.Size.Height;
+            return; // CreateAdornments () has not been called yet
         }
 
-        Padding.SetNeedsLayout ();
-        Padding.SetNeedsDisplay ();
-
-        if (IsInitialized)
-        {
-            Padding.LayoutSubviews ();
-        }
+        Margin.Frame = Rectangle.Empty with { Size = Frame.Size };
+        Border.Frame = Margin.Thickness.GetInside (Margin.Frame);
+        Padding.Frame = Border.Thickness.GetInside (Border.Frame);
     }
 }

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

@@ -0,0 +1,147 @@
+using System;
+
+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
+        {
+            if (_colorScheme is null)
+            {
+                return SuperView?.ColorScheme;
+            }
+
+            return _colorScheme;
+        }
+        set
+        {
+            if (_colorScheme != value)
+            {
+                _colorScheme = value;
+
+                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;
+
+        if (cs is null)
+        {
+            cs = 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="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/> or
+    ///     <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>. If it's
+    ///     overridden can return other values.
+    /// </returns>
+    public virtual Attribute GetHotNormalColor ()
+    {
+        ColorScheme cs = ColorScheme;
+
+        if (cs is null)
+        {
+            cs = 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="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/> or
+    ///     <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>. If it's
+    ///     overridden can return other values.
+    /// </returns>
+    public virtual Attribute GetNormalColor ()
+    {
+        ColorScheme cs = ColorScheme;
+
+        if (cs is null)
+        {
+            cs = 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
+}

+ 2 - 5
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;
@@ -268,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

+ 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"/> is enabled for all Views independently of Adornments.
+    /// </para>
+    /// </remarks>
     public static ViewDiagnosticFlags Diagnostics { get; set; }
 }

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

@@ -0,0 +1,46 @@
+#nullable enable
+namespace Terminal.Gui;
+
+public partial class View
+{
+    /// <summary>Sets the <see cref="ConsoleDriver"/>'s clip region to <see cref="Viewport"/>.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         By default, the clip rectangle is set to the intersection of the current clip region and the
+    ///         <see cref="Viewport"/>. This ensures that drawing is constrained to the viewport, but allows
+    ///         content to be drawn beyond the viewport.
+    ///     </para>
+    ///     <para>
+    ///         If <see cref="ViewportSettings"/> has <see cref="Gui.ViewportSettings.ClipContentOnly"/> set, clipping will be
+    ///         applied to just the visible content area.
+    ///     </para>
+    /// </remarks>
+    /// <returns>
+    ///     The current screen-relative clip region, which can be then re-applied by setting
+    ///     <see cref="ConsoleDriver.Clip"/>.
+    /// </returns>
+    public Region? SetClip ()
+    {
+        if (Driver is null)
+        {
+            return null;
+        }
+
+        Region previous = Driver.Clip ?? new (Application.Screen);
+
+        // Clamp the Clip to the entire visible area
+        Rectangle clip = Rectangle.Intersect (ViewportToScreen (Viewport with { Location = Point.Empty }), previous.GetBounds());
+
+        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 ()));
+            clip = Rectangle.Intersect (clip, visibleContent);
+        }
+
+        Driver.Clip = new (clip);// !.Complement(clip);
+
+        return previous;
+    }
+
+}

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

@@ -0,0 +1,128 @@
+namespace Terminal.Gui;
+
+public partial class View
+{
+    #region Drawing Primitives
+
+    /// <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 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>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;
+            }
+
+            Application.Driver?.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 = SetClip ();
+        Rectangle toClear = ViewportToScreen (rect);
+        Attribute prev = SetAttribute (new (color ?? GetNormalColor ().Background));
+        Driver.FillRect (toClear);
+        SetAttribute (prev);
+        Driver.Clip = prevClip;
+    }
+
+    #endregion Drawing Primitives
+}

+ 447 - 482
Terminal.Gui/View/View.Drawing.cs

@@ -1,280 +1,224 @@
 #nullable enable
+#define HACK_DRAW_OVERLAPPED
+using System.ComponentModel;
+using System.Diagnostics;
+
 namespace Terminal.Gui;
 
 public partial class View // Drawing APIs
 {
-    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
-        {
-            if (_colorScheme is null)
-            {
-                return SuperView?.ColorScheme;
-            }
-
-            return _colorScheme;
-        }
-        set
-        {
-            if (_colorScheme != value)
-            {
-                _colorScheme = value;
-
-                if (Border is { } && Border.LineStyle != LineStyle.None && Border.ColorScheme is { })
-                {
-                    Border.ColorScheme = _colorScheme;
-                }
-                SetNeedsDisplay ();
-            }
-        }
-    }
-
-    /// <summary>The canvas that any line drawing that is to be shared by subviews of this view should add lines to.</summary>
-    /// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
-    public LineCanvas LineCanvas { get; } = new ();
-
-    // The view-relative region that needs to be redrawn. Marked internal for unit tests.
-    internal Rectangle _needsDisplayRect = Rectangle.Empty;
-
-    /// <summary>Gets or sets whether the view needs to be redrawn.</summary>
-    public bool NeedsDisplay
-    {
-        get => _needsDisplayRect != Rectangle.Empty;
-        set
-        {
-            if (value)
-            {
-                SetNeedsDisplay ();
-            }
-            else
-            {
-                ClearNeedsDisplay ();
-            }
-        }
-    }
-
-    /// <summary>Gets whether any Subviews need to be redrawn.</summary>
-    public bool SubViewNeedsDisplay { get; private set; }
-
     /// <summary>
-    ///     Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for rendering any
-    ///     lines. If <see langword="true"/> the rendering of any borders drawn by this Frame will be done by its parent's
-    ///     SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawAdornments"/> method will be
-    ///     called to render the borders.
+    ///     Draws the view if it needs to be drawn.
     /// </summary>
-    public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
-
-    /// <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>Clears <see cref="Viewport"/> with the normal background.</summary>
     /// <remarks>
     ///     <para>
-    ///         If <see cref="ViewportSettings"/> has <see cref="Gui.ViewportSettings.ClearContentOnly"/> only
-    ///         the portion of the content
-    ///         area that is visible within the <see cref="View.Viewport"/> will be cleared. This is useful for views that have
-    ///         a
-    ///         content area larger than the Viewport (e.g. when <see cref="ViewportSettings.AllowNegativeLocation"/> is
-    ///         enabled) and want
-    ///         the area outside the content to be visually distinct.
+    ///         The view will only be drawn if it is visible, and has any of <see cref="NeedsDraw"/>,
+    ///         <see cref="SubViewNeedsDraw"/>,
+    ///         or <see cref="NeedsLayout"/> set.
+    ///     </para>
+    ///     <para>
+    ///         // TODO: Add docs for the drawing process.
     ///     </para>
     /// </remarks>
-    public void Clear ()
+    public void Draw ()
     {
-        if (Driver is null)
+        if (!CanBeVisible (this) || (!NeedsDraw && !SubViewNeedsDraw))
         {
             return;
         }
 
-        // Get screen-relative coords
-        Rectangle toClear = ViewportToScreen (Viewport with { Location = new (0, 0) });
+        DoDrawAdornments ();
+        DoSetAttribute ();
 
-        Rectangle prevClip = Driver.Clip;
+        // By default, we clip to the viewport preventing drawing outside the viewport
+        // We also clip to the content, but if a developer wants to draw outside the viewport, they can do
+        // so via settings. SetClip honors the ViewportSettings.DisableVisibleContentClipping flag.
+        Region prevClip = SetClip ();
 
-        if (ViewportSettings.HasFlag (ViewportSettings.ClearContentOnly))
+        DoClearViewport (Viewport);
+        DoDrawText (Viewport);
+        DoDrawContent (Viewport);
+
+        DoDrawSubviews (Viewport);
+
+        // Restore the clip before rendering the line canvas and adornment subviews
+        // because they may draw outside the viewport.
+        if (Driver is { })
         {
-            Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), GetContentSize ()));
-            toClear = Rectangle.Intersect (toClear, visibleContent);
+            Driver.Clip = prevClip;
         }
 
-        Attribute prev = Driver.SetAttribute (GetNormalColor ());
-        Driver.FillRect (toClear);
-        Driver.SetAttribute (prev);
+        DoRenderLineCanvas ();
+        DoDrawAdornmentSubViews ();
+        ClearNeedsDraw ();
 
-        Driver.Clip = prevClip;
+        // We're done
+        DoDrawComplete ();
     }
 
-    /// <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)
+    #region DrawAdornments
+
+    private void DoDrawAdornmentSubViews ()
     {
-        if (Driver is null)
+        // This causes the Adornment's subviews to be REDRAWN
+        // TODO: Figure out how to make this more efficient
+        if (Margin?.Subviews is { })
         {
-            return;
-        }
+            foreach (View subview in Margin.Subviews)
+            {
+                subview.SetNeedsDraw ();
+            }
 
-        // Get screen-relative coords
-        Rectangle toClear = ViewportToScreen (rect);
+            Margin?.DoDrawSubviews (Margin.Viewport);
+        }
 
-        Rectangle prevClip = Driver.Clip;
+        if (Border?.Subviews is { })
+        {
+            foreach (View subview in Border.Subviews)
+            {
+                subview.SetNeedsDraw ();
+            }
 
-        Driver.Clip = Rectangle.Intersect (prevClip, ViewportToScreen (Viewport with { Location = new (0, 0) }));
+            Border?.DoDrawSubviews (Border.Viewport);
+        }
 
-        Attribute prev = Driver.SetAttribute (new (color ?? GetNormalColor ().Background));
-        Driver.FillRect (toClear);
-        Driver.SetAttribute (prev);
+        if (Padding?.Subviews is { })
+        {
+            foreach (View subview in Padding.Subviews)
+            {
+                subview.SetNeedsDraw ();
+            }
 
-        Driver.Clip = prevClip;
+            Padding?.DoDrawSubviews (Padding.Viewport);
+        }
     }
 
-    /// <summary>Sets the <see cref="ConsoleDriver"/>'s clip region to <see cref="Viewport"/>.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         By default, the clip rectangle is set to the intersection of the current clip region and the
-    ///         <see cref="Viewport"/>. This ensures that drawing is constrained to the viewport, but allows
-    ///         content to be drawn beyond the viewport.
-    ///     </para>
-    ///     <para>
-    ///         If <see cref="ViewportSettings"/> has <see cref="Gui.ViewportSettings.ClipContentOnly"/> set, clipping will be
-    ///         applied to just the visible content area.
-    ///     </para>
-    /// </remarks>
-    /// <returns>
-    ///     The current screen-relative clip region, which can be then re-applied by setting
-    ///     <see cref="ConsoleDriver.Clip"/>.
-    /// </returns>
-    public Rectangle SetClip ()
+    private void DoDrawAdornments ()
     {
-        if (Driver is null)
+        if (OnDrawingAdornments ())
         {
-            return Rectangle.Empty;
+            return;
         }
 
-        Rectangle previous = Driver.Clip;
-
-        // Clamp the Clip to the entire visible area
-        Rectangle clip = Rectangle.Intersect (ViewportToScreen (Viewport with { Location = Point.Empty }), previous);
+        // TODO: add event.
 
-        if (ViewportSettings.HasFlag (ViewportSettings.ClipContentOnly))
+        // Subviews of Adornments count as subviews
+        if (!NeedsDraw && !SubViewNeedsDraw)
         {
-            // Clamp the Clip to the just content area that is within the viewport
-            Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), GetContentSize ()));
-            clip = Rectangle.Intersect (clip, visibleContent);
+            return;
         }
 
-        Driver.Clip = clip;
+        DrawAdornments ();
+    }
 
-        return previous;
+    /// <summary>
+    ///     Causes each of the View's Adornments to be drawn. This includes the <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>.
+    /// </summary>
+    public void DrawAdornments ()
+    {
+        // Each of these renders lines to either this View's LineCanvas 
+        // Those lines will be finally rendered in OnRenderLineCanvas
+        Margin?.Draw ();
+        Border?.Draw ();
+        Padding?.Draw ();
     }
 
     /// <summary>
-    ///     Draws the view if it needs to be drawn. Causes the following virtual methods to be called (along with their related events):
-    ///     <see cref="OnDrawContent"/>, <see cref="OnDrawContentComplete"/>.
+    ///     Called when the View's Adornments are to be drawn. Prepares <see cref="View.LineCanvas"/>. If
+    ///     <see cref="SuperViewRendersLineCanvas"/> is true, only the
+    ///     <see cref="LineCanvas"/> of this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
+    ///     false (the default), this method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         The view will only be drawn if it is visible, and has any of <see cref="NeedsDisplay"/>, <see cref="SubViewNeedsDisplay"/>,
-    ///         or <see cref="LayoutNeeded"/> set.
-    ///     </para>
-    ///     <para>
-    ///         Always use <see cref="Viewport"/> (view-relative) when calling <see cref="OnDrawContent(Rectangle)"/>, NOT
-    ///         <see cref="Frame"/> (superview-relative).
-    ///     </para>
-    ///     <para>
-    ///         Views should set the color that they want to use on entry, as otherwise this will inherit the last color that
-    ///         was set globally on the driver.
-    ///     </para>
-    ///     <para>
-    ///         Overrides of <see cref="OnDrawContent(Rectangle)"/> must ensure they do not set <c>Driver.Clip</c> to a clip
-    ///         region larger than the <ref name="Viewport"/> property, as this will cause the driver to clip the entire
-    ///         region.
-    ///     </para>
-    /// </remarks>
-    public void Draw ()
+    /// <returns><see langword="true"/> to stop further drawing of the Adornments.</returns>
+    protected virtual bool OnDrawingAdornments () { return false; }
+
+    #endregion DrawAdornments
+
+    #region SetAttribute
+
+    private void DoSetAttribute ()
     {
-        if (!CanBeVisible (this))
+        if (OnSettingAttribute ())
         {
             return;
         }
 
-        // TODO: This ensures overlapped views are drawn correctly. However, this is inefficient.
-        // TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413
-        if (Arrangement.HasFlag (ViewArrangement.Overlapped))
+        var args = new CancelEventArgs ();
+        SettingAttribute?.Invoke (this, args);
+
+        if (args.Cancel)
         {
-            SetNeedsDisplay ();
+            return;
         }
 
-        if (!NeedsDisplay && !SubViewNeedsDisplay && !LayoutNeeded)
+        if (!NeedsDraw && !SubViewNeedsDraw)
         {
             return;
         }
 
-        OnDrawAdornments ();
+        SetNormalAttribute ();
+    }
+
+
+    /// <summary>
+    ///     Called when the normal attribute for the View is to be set. This is called before the View is drawn.
+    /// </summary>
+    /// <returns><see langword="true"/> to stop default behavior.</returns>
+    protected virtual bool OnSettingAttribute () { return false; }
+
+    /// <summary>Raised  when the normal attribute for the View is to be set. This is raised before the View is drawn.</summary>
+    /// <returns>
+    ///     Set <see cref="CancelEventArgs.Cancel"/> to <see langword="true"/> to stop default behavior.
+    /// </returns>
+    public event EventHandler<CancelEventArgs>? SettingAttribute;
 
+    /// <summary>
+    ///     Sets the attribute for the View. This is called before the View is drawn.
+    /// </summary>
+    public void SetNormalAttribute ()
+    {
         if (ColorScheme is { })
         {
-            //Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
-            Driver?.SetAttribute (GetNormalColor ());
+            SetAttribute (GetNormalColor ());
         }
+    }
 
-        // By default, we clip to the viewport preventing drawing outside the viewport
-        // We also clip to the content, but if a developer wants to draw outside the viewport, they can do
-        // so via settings. SetClip honors the ViewportSettings.DisableVisibleContentClipping flag.
-        Rectangle prevClip = SetClip ();
 
-        // Invoke DrawContentEvent
-        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
-        DrawContent?.Invoke (this, dev);
+    #endregion
+    #region ClearViewport
 
-        if (!dev.Cancel)
-        {
-            OnDrawContent (Viewport);
-        }
+    private void DoClearViewport (Rectangle viewport)
+    {
+        Debug.Assert (viewport == Viewport);
 
-        if (Driver is { })
+        if (OnClearingViewport (Viewport))
         {
-            Driver.Clip = prevClip;
+            return;
         }
 
-        OnRenderLineCanvas ();
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
+        ClearingViewport?.Invoke (this, dev);
 
-        // TODO: This is a hack to force the border subviews to draw.
-        if (Border?.Subviews is { })
+        if (dev.Cancel)
         {
-            foreach (View view in Border.Subviews)
-            {
-                view.SetNeedsDisplay ();
-                view.Draw ();
-            }
+            return;
         }
 
-        // Invoke DrawContentCompleteEvent
-        OnDrawContentComplete (Viewport);
+        if (!NeedsDraw)
+        {
+            return;
+        }
 
-        // BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details.
-        ClearLayoutNeeded ();
-        ClearNeedsDisplay ();
+        ClearViewport ();
     }
 
+    /// <summary>
+    ///     Called when the <see cref="Viewport"/> is to be cleared.
+    /// </summary>
+    /// <param name="viewport"></param>
+    /// <returns><see langword="true"/> to stop further clearing.</returns>
+    protected virtual bool OnClearingViewport (Rectangle viewport) { return false; }
+
     /// <summary>Event invoked when the content area of the View is to be drawn.</summary>
     /// <remarks>
     ///     <para>Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.</para>
@@ -283,330 +227,273 @@ public partial class View // Drawing APIs
     ///         <see cref="View"/> .
     ///     </para>
     /// </remarks>
-    public event EventHandler<DrawEventArgs>? DrawContent;
+    public event EventHandler<DrawEventArgs>? ClearingViewport;
 
-    /// <summary>Event invoked when the content area of the View is completed drawing.</summary>
-    /// <remarks>
-    ///     <para>Will be invoked after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.</para>
-    ///     <para>
-    ///         Rect provides the view-relative rectangle describing the currently visible viewport into the
-    ///         <see cref="View"/> .
-    ///     </para>
-    /// </remarks>
-    public event EventHandler<DrawEventArgs>? DrawContentComplete;
-
-    /// <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>
+    /// <summary>Clears <see cref="Viewport"/> with the normal background.</summary>
     /// <remarks>
     ///     <para>
-    ///         The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by
-    ///         default.
+    ///         If <see cref="ViewportSettings"/> has <see cref="Gui.ViewportSettings.ClearContentOnly"/> only
+    ///         the portion of the content
+    ///         area that is visible within the <see cref="View.Viewport"/> will be cleared. This is useful for views that have
+    ///         a
+    ///         content area larger than the Viewport (e.g. when <see cref="ViewportSettings.AllowNegativeLocation"/> is
+    ///         enabled) and want
+    ///         the area outside the content to be visually distinct.
     ///     </para>
-    ///     <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
     /// </remarks>
-    public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
+    public void ClearViewport ()
     {
-        Rune hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
-        Application.Driver?.SetAttribute (normalColor);
-
-        foreach (Rune rune in text.EnumerateRunes ())
+        if (Driver is null)
         {
-            if (rune == new Rune (hotkeySpec.Value))
-            {
-                Application.Driver?.SetAttribute (hotColor);
+            return;
+        }
 
-                continue;
-            }
+        // Get screen-relative coords
+        Rectangle toClear = ViewportToScreen (Viewport with { Location = new (0, 0) });
 
-            Application.Driver?.AddRune (rune);
-            Application.Driver?.SetAttribute (normalColor);
+        if (ViewportSettings.HasFlag (ViewportSettings.ClearContentOnly))
+        {
+            Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), GetContentSize ()));
+            toClear = Rectangle.Intersect (toClear, visibleContent);
         }
+
+        Attribute prev = SetAttribute (GetNormalColor ());
+        Driver.FillRect (toClear);
+        SetAttribute (prev);
+        SetNeedsDraw ();
     }
 
-    /// <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)
+    #endregion ClearViewport
+
+    #region DrawText
+
+    private void DoDrawText (Rectangle viewport)
     {
-        if (focused)
+        Debug.Assert (viewport == Viewport);
+
+        if (OnDrawingText (Viewport))
         {
-            DrawHotString (text, GetHotFocusColor (), GetFocusColor ());
+            return;
         }
-        else
+
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
+        DrawingText?.Invoke (this, dev);
+
+        if (dev.Cancel)
         {
-            DrawHotString (
-                           text,
-                           Enabled ? GetHotNormalColor () : ColorScheme!.Disabled,
-                           Enabled ? GetNormalColor () : ColorScheme!.Disabled
-                          );
+            return;
         }
-    }
-
-    /// <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;
 
-        if (cs is null)
+        if (!NeedsDraw)
         {
-            cs = new ();
+            return;
         }
 
-        return Enabled ? GetColor (cs.Focus) : cs.Disabled;
+        DrawText ();
     }
 
-    /// <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>
+    ///     Called when the <see cref="Text"/> of the View is to be drawn.
+    /// </summary>
+    /// <param name="viewport"></param>
+    /// <returns><see langword="true"/> to stop further drawing of  <see cref="Text"/>.</returns>
+    protected virtual bool OnDrawingText (Rectangle viewport) { return false; }
 
-    /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
+    /// <summary>Raised when the <see cref="Text"/> of the View is to be drawn.</summary>
     /// <returns>
-    ///     <see cref="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/> or
-    ///     <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>. If it's
-    ///     overridden can return other values.
+    ///     Set <see cref="DrawEventArgs.Cancel"/> to <see langword="true"/> to stop further drawing of
+    ///     <see cref="Text"/>.
     /// </returns>
-    public virtual Attribute GetHotNormalColor ()
-    {
-        ColorScheme? cs = ColorScheme;
+    public event EventHandler<DrawEventArgs>? DrawingText;
 
-        if (cs is null)
+    /// <summary>
+    ///     Draws the <see cref="Text"/> of the View using the <see cref="TextFormatter"/>.
+    /// </summary>
+    public void DrawText ()
+    {
+        if (!string.IsNullOrEmpty (TextFormatter.Text))
         {
-            cs = new ();
+            TextFormatter.NeedsFormat = true;
         }
 
-        return Enabled ? GetColor (cs.HotNormal) : cs.Disabled;
+        // This should NOT clear 
+        // TODO: If the output is not in the Viewport, do nothing
+        var drawRect = new Rectangle (ContentToScreen (Point.Empty), GetContentSize ());
+
+        TextFormatter?.Draw (
+                             drawRect,
+                             HasFocus ? GetFocusColor () : GetNormalColor (),
+                             HasFocus ? GetHotFocusColor () : GetHotNormalColor (),
+                             Rectangle.Empty
+                            );
+
+        // We assume that the text has been drawn over the entire area; ensure that the subviews are redrawn.
+        SetSubViewNeedsDraw ();
     }
 
-    /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
-    /// <returns>
-    ///     <see cref="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/> or
-    ///     <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>. If it's
-    ///     overridden can return other values.
-    /// </returns>
-    public virtual Attribute GetNormalColor ()
+    #endregion DrawText
+
+    #region DrawContent
+
+    private void DoDrawContent (Rectangle viewport)
     {
-        ColorScheme? cs = ColorScheme;
+        Debug.Assert (viewport == Viewport);
 
-        if (cs is null)
+        if (OnDrawingContent (Viewport))
         {
-            cs = new ();
+            return;
         }
 
-        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;
-    }
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
+        DrawingContent?.Invoke (this, dev);
 
-    private Attribute GetColor (Attribute inputAttribute)
-    {
-        Attribute attr = inputAttribute;
-        if (Diagnostics.HasFlag (ViewDiagnosticFlags.Hover) && _hovering)
-        {
-            attr = new (attr.Foreground.GetDarkerColor (), attr.Background.GetDarkerColor ());
-        }
+        if (dev.Cancel)
+        { }
 
-        return attr;
+        // Do nothing.
     }
 
-    /// <summary>Moves the drawing cursor to the specified <see cref="Viewport"/>-relative location in the view.</summary>
+    /// <summary>
+    ///     Called when the View's content is to be drawn. The default implementation does nothing.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    /// <returns><see langword="true"/> to stop further drawing content.</returns>
+    protected virtual bool OnDrawingContent (Rectangle viewport) { return false; }
+
+    /// <summary>Raised when  the View's content is to be drawn.</summary>
     /// <remarks>
+    ///     <para>Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.</para>
     ///     <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>.
+    ///         Rect provides the view-relative rectangle describing the currently visible viewport into the
+    ///         <see cref="View"/> .
     ///     </para>
     /// </remarks>
-    /// <param name="col">Column (viewport-relative).</param>
-    /// <param name="row">Row (viewport-relative).</param>
-    public bool Move (int col, int row)
+    public event EventHandler<DrawEventArgs>? DrawingContent;
+
+    #endregion DrawContent
+
+    #region DrawSubviews
+
+    private void DoDrawSubviews (Rectangle viewport)
     {
-        if (Driver is null || Driver?.Rows == 0)
-        {
-            return false;
-        }
+        Debug.Assert (viewport == Viewport);
 
-        if (col < 0 || row < 0 || col >= Viewport.Width || row >= Viewport.Height)
+        if (OnDrawingSubviews (Viewport))
         {
-            return false;
+            return;
         }
 
-        Point screen = ViewportToScreen (new Point (col, row));
-        Driver?.Move (screen.X, screen.Y);
-
-        return true;
-    }
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
+        DrawingSubviews?.Invoke (this, dev);
 
-    // TODO: Make this cancelable
-    /// <summary>
-    ///     Prepares <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the
-    ///     <see cref="LineCanvas"/> of this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
-    ///     false (the default), this method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
-    /// </summary>
-    /// <returns></returns>
-    public virtual bool OnDrawAdornments ()
-    {
-        if (!IsInitialized)
+        if (dev.Cancel)
         {
-            return false;
+            return;
         }
 
-        // Each of these renders lines to either this View's LineCanvas 
-        // Those lines will be finally rendered in OnRenderLineCanvas
-        Margin?.OnDrawContent (Margin.Viewport);
-        Border?.OnDrawContent (Border.Viewport);
-        Padding?.OnDrawContent (Padding.Viewport);
+        if (!SubViewNeedsDraw)
+        {
+            return;
+        }
 
-        return true;
+        DrawSubviews ();
     }
 
     /// <summary>
-    ///     Draws the view's content, including Subviews.
+    ///     Called when the <see cref="Subviews"/> are to be drawn.
     /// </summary>
+    /// <param name="viewport"></param>
+    /// <returns><see langword="true"/> to stop further drawing of <see cref="Subviews"/>.</returns>
+    protected virtual bool OnDrawingSubviews (Rectangle viewport) { return false; }
+
+    /// <summary>Raised when the <see cref="Subviews"/> are to be drawn.</summary>
     /// <remarks>
-    ///     <para>
-    ///         The <paramref name="viewport"/> parameter is provided as a convenience; it has the same values as the
-    ///         <see cref="Viewport"/> property.
-    ///     </para>
-    ///     <para>
-    ///         The <see cref="Viewport"/> Location and Size indicate what part of the View's content, defined
-    ///         by <see cref="GetContentSize ()"/>, is visible and should be drawn. The coordinates taken by <see cref="Move"/>
-    ///         and
-    ///         <see cref="AddRune"/> are relative to <see cref="Viewport"/>, thus if <c>ViewPort.Location.Y</c> is <c>5</c>
-    ///         the 6th row of the content should be drawn using <c>MoveTo (x, 5)</c>.
-    ///     </para>
-    ///     <para>
-    ///         If <see cref="GetContentSize ()"/> is larger than <c>ViewPort.Size</c> drawing code should use
-    ///         <see cref="Viewport"/>
-    ///         to constrain drawing for better performance.
-    ///     </para>
-    ///     <para>
-    ///         The <see cref="ConsoleDriver.Clip"/> may define smaller area than <see cref="Viewport"/>; complex drawing code
-    ///         can be more
-    ///         efficient by using <see cref="ConsoleDriver.Clip"/> to constrain drawing for better performance.
-    ///     </para>
-    ///     <para>
-    ///         Overrides should loop through the subviews and call <see cref="Draw"/>.
-    ///     </para>
     /// </remarks>
-    /// <param name="viewport">
-    ///     The rectangle describing the currently visible viewport into the <see cref="View"/>; has the same value as
-    ///     <see cref="Viewport"/>.
-    /// </param>
-    public virtual void OnDrawContent (Rectangle viewport)
+    /// <returns>
+    ///     Set <see cref="DrawEventArgs.Cancel"/> to <see langword="true"/> to stop further drawing of
+    ///     <see cref="Subviews"/>.
+    /// </returns>
+    public event EventHandler<DrawEventArgs>? DrawingSubviews;
+
+    /// <summary>
+    ///     Draws the <see cref="Subviews"/>.
+    /// </summary>
+    public void DrawSubviews ()
     {
-        if (NeedsDisplay)
+        if (_subviews is null || !SubViewNeedsDraw)
         {
-            if (!CanBeVisible (this))
-            {
-                return;
-            }
+            return;
+        }
 
-            // BUGBUG: this clears way too frequently. Need to optimize this.
-            if (SuperView is { } || Arrangement.HasFlag (ViewArrangement.Overlapped))
-            {
-                Clear ();
-            }
+#if HACK_DRAW_OVERLAPPED
+        IEnumerable<View> subviewsNeedingDraw = _subviews.Where (view => (view.Visible && (view.NeedsDraw || view.SubViewNeedsDraw))
+                                                                      || view.Arrangement.HasFlag (ViewArrangement.Overlapped));
+#else
+        IEnumerable<View> subviewsNeedingDraw = _subviews.Where (view => (view.Visible && (view.NeedsDraw || view.SubViewNeedsDraw)));
+#endif
 
-            if (!string.IsNullOrEmpty (TextFormatter.Text))
+        foreach (View view in subviewsNeedingDraw)
+        {
+#if HACK_DRAW_OVERLAPPED
+            if (view.Arrangement.HasFlag (ViewArrangement.Overlapped))
             {
-                if (TextFormatter is { })
-                {
-                    TextFormatter.NeedsFormat = true;
-                }
+
+                view.SetNeedsDraw ();
             }
+#endif
+            view.Draw ();
 
-            // This should NOT clear 
-            // TODO: If the output is not in the Viewport, do nothing
-            var drawRect = new Rectangle (ContentToScreen (Point.Empty), GetContentSize ());
-
-            TextFormatter?.Draw (
-                                 drawRect,
-                                 HasFocus ? GetFocusColor () : GetNormalColor (),
-                                 HasFocus ? GetHotFocusColor () : GetHotNormalColor (),
-                                 Rectangle.Empty
-                                );
-            SetSubViewNeedsDisplay ();
-        }
-
-        // TODO: Move drawing of subviews to a separate OnDrawSubviews virtual method
-        // Draw subviews
-        // TODO: Implement OnDrawSubviews (cancelable);
-        if (_subviews is { } && SubViewNeedsDisplay)
-        {
-            IEnumerable<View> subviewsNeedingDraw = _subviews.Where (
-                                                                     view => view.Visible
-                                                                             && (view.NeedsDisplay
-                                                                                 || view.SubViewNeedsDisplay
-                                                                                 || view.LayoutNeeded
-                                                                                 || view.Arrangement.HasFlag (ViewArrangement.Overlapped)
-                                                                    ));
-
-            foreach (View view in subviewsNeedingDraw)
-            {
-                if (view.LayoutNeeded)
-                {
-                    view.LayoutSubviews ();
-                }
+        }
+    }
 
-                // TODO: This ensures overlapped views are drawn correctly. However, this is inefficient.
-                // TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413
-                if (view.Arrangement.HasFlag (ViewArrangement.Overlapped))
-                {
-                    view.SetNeedsDisplay ();
-                }
+    #endregion DrawSubviews
 
-                view.Draw ();
-            }
+    #region DrawLineCanvas
+
+    private void DoRenderLineCanvas ()
+    {
+        if (OnRenderingLineCanvas ())
+        {
+            return;
         }
+
+        // TODO: Add event
+
+        RenderLineCanvas ();
     }
 
     /// <summary>
-    ///     Called after <see cref="OnDrawContent"/> to enable overrides.
+    ///     Called when the <see cref="View.LineCanvas"/> is to be rendered. See <see cref="RenderLineCanvas"/>.
     /// </summary>
-    /// <param name="viewport">
-    ///     The viewport-relative rectangle describing the currently visible viewport into the
-    ///     <see cref="View"/>
-    /// </param>
-    public virtual void OnDrawContentComplete (Rectangle viewport) { DrawContentComplete?.Invoke (this, new (viewport, Rectangle.Empty)); }
+    /// <returns><see langword="true"/> to stop further drawing of <see cref="LineCanvas"/>.</returns>
+    protected virtual bool OnRenderingLineCanvas () { return false; }
+
+    /// <summary>The canvas that any line drawing that is to be shared by subviews of this view should add lines to.</summary>
+    /// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
+    public LineCanvas LineCanvas { get; } = new ();
 
-    // TODO: Make this cancelable
     /// <summary>
-    ///     Renders <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the
+    ///     Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for rendering any
+    ///     lines. If <see langword="true"/> the rendering of any borders drawn by this Frame will be done by its parent's
+    ///     SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawingAdornments"/> method will be
+    ///     called to render the borders.
+    /// </summary>
+    public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
+
+    /// <summary>
+    ///     Causes the contents of <see cref="LineCanvas"/> to be drawn. 
+    ///      If <see cref="SuperViewRendersLineCanvas"/> is true, only the
     ///     <see cref="LineCanvas"/> of this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
     ///     false (the default), this method will cause the <see cref="LineCanvas"/> to be rendered.
     /// </summary>
-    /// <returns></returns>
-    public virtual bool OnRenderLineCanvas ()
+    public void RenderLineCanvas ()
     {
-        if (!IsInitialized || Driver is null)
+        // TODO: This is super confusing and needs to be refactored.
+
+        if (Driver is null)
         {
-            return false;
+            return;
         }
 
         // If we have a SuperView, it'll render our frames.
@@ -617,7 +504,7 @@ public partial class View // Drawing APIs
                 // Get the entire map
                 if (p.Value is { })
                 {
-                    Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme!.Normal);
+                    SetAttribute (p.Value.Value.Attribute ?? ColorScheme!.Normal);
                     Driver.Move (p.Key.X, p.Key.Y);
 
                     // TODO: #2616 - Support combining sequences that don't normalize
@@ -642,7 +529,7 @@ public partial class View // Drawing APIs
                 // Get the entire map
                 if (p.Value is { })
                 {
-                    Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme!.Normal);
+                    SetAttribute (p.Value.Value.Attribute ?? ColorScheme!.Normal);
                     Driver.Move (p.Key.X, p.Key.Y);
 
                     // TODO: #2616 - Support combining sequences that don't normalize
@@ -652,91 +539,169 @@ public partial class View // Drawing APIs
 
             LineCanvas.Clear ();
         }
+    }
+    #endregion DrawLineCanvas
+
+    #region DrawComplete
 
-        return true;
+    private void DoDrawComplete ()
+    {
+        OnDrawComplete ();
+
+        DrawComplete?.Invoke (this, EventArgs.Empty);
+
+        // Default implementation does nothing.
     }
 
-    /// <summary>Sets the area of this view needing to be redrawn to <see cref="Viewport"/>.</summary>
+    /// <summary>
+    ///     Called when the View is completed drawing.
+    /// </summary>
+    protected virtual void OnDrawComplete () { }
+
+    /// <summary>Raised when the View is completed drawing.</summary>
+    /// <remarks>
+    /// </remarks>
+    public event EventHandler<EventArgs>? DrawComplete;
+
+    #endregion DrawComplete
+
+    #region NeedsDraw
+
+    // TODO: Make _needsDrawRect nullable instead of relying on Empty
+    // TODO: If null, it means ?
+    // TODO: If Empty, it means no need to redraw
+    // TODO: If not Empty, it means the region that needs to be redrawn
+    // The viewport-relative region that needs to be redrawn. Marked internal for unit tests.
+    internal Rectangle _needsDrawRect = Rectangle.Empty;
+
+    /// <summary>Gets or sets whether the view needs to be redrawn.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Will be <see langword="true"/> if the <see cref="NeedsLayout"/> property is <see langword="true"/> or if
+    ///         any part of the view's <see cref="Viewport"/> needs to be redrawn.
+    ///     </para>
+    ///     <para>
+    ///         Setting has no effect on <see cref="NeedsLayout"/>.
+    ///     </para>
+    /// </remarks>
+    public bool NeedsDraw
+    {
+        // TODO: Figure out if we can decouple NeedsDraw from NeedsLayout. This is a temporary fix.
+        get => _needsDrawRect != Rectangle.Empty || NeedsLayout;
+        set
+        {
+            if (value)
+            {
+                SetNeedsDraw ();
+            }
+            else
+            {
+                ClearNeedsDraw ();
+            }
+        }
+    }
+
+    /// <summary>Gets whether any Subviews need to be redrawn.</summary>
+    public bool SubViewNeedsDraw { get; private set; }
+
+    /// <summary>Sets that the <see cref="Viewport"/> of this View needs to be redrawn.</summary>
     /// <remarks>
     ///     If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), this method
     ///     does nothing.
     /// </remarks>
-    public void SetNeedsDisplay () { SetNeedsDisplay (Viewport); }
+    public void SetNeedsDraw ()
+    {
+        Rectangle viewport = Viewport;
 
-    /// <summary>Expands the area of this view needing to be redrawn to include <paramref name="region"/>.</summary>
+        if (_needsDrawRect != Rectangle.Empty && viewport.IsEmpty)
+        {
+            // This handles the case where the view has not been initialized yet
+            return;
+        }
+
+        SetNeedsDraw (viewport);
+    }
+
+    /// <summary>Expands the area of this view needing to be redrawn to include <paramref name="viewPortRelativeRegion"/>.</summary>
     /// <remarks>
     ///     <para>
-    ///         The location of <paramref name="region"/> is relative to the View's content, bound by <c>Size.Empty</c> and
-    ///         <see cref="GetContentSize ()"/>.
+    ///         The location of <paramref name="viewPortRelativeRegion"/> is relative to the View's <see cref="Viewport"/>.
     ///     </para>
     ///     <para>
     ///         If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), the area to be
-    ///         redrawn will be the <paramref name="region"/>.
+    ///         redrawn will be the <paramref name="viewPortRelativeRegion"/>.
     ///     </para>
     /// </remarks>
-    /// <param name="region">The content-relative region that needs to be redrawn.</param>
-    public void SetNeedsDisplay (Rectangle region)
+    /// <param name="viewPortRelativeRegion">The <see cref="Viewport"/>relative region that needs to be redrawn.</param>
+    public void SetNeedsDraw (Rectangle viewPortRelativeRegion)
     {
-        if (_needsDisplayRect.IsEmpty)
+        if (_needsDrawRect.IsEmpty)
         {
-            _needsDisplayRect = region;
+            _needsDrawRect = viewPortRelativeRegion;
         }
         else
         {
-            int x = Math.Min (_needsDisplayRect.X, region.X);
-            int y = Math.Min (_needsDisplayRect.Y, region.Y);
-            int w = Math.Max (_needsDisplayRect.Width, region.Width);
-            int h = Math.Max (_needsDisplayRect.Height, region.Height);
-            _needsDisplayRect = new (x, y, w, h);
+            int x = Math.Min (Viewport.X, viewPortRelativeRegion.X);
+            int y = Math.Min (Viewport.Y, viewPortRelativeRegion.Y);
+            int w = Math.Max (Viewport.Width, viewPortRelativeRegion.Width);
+            int h = Math.Max (Viewport.Height, viewPortRelativeRegion.Height);
+            _needsDrawRect = new (x, y, w, h);
         }
 
-        Margin?.SetNeedsDisplay ();
-        Border?.SetNeedsDisplay ();
-        Padding?.SetNeedsDisplay ();
+        Margin?.SetNeedsDraw ();
+        Border?.SetNeedsDraw ();
+        Padding?.SetNeedsDraw ();
 
-        SuperView?.SetSubViewNeedsDisplay ();
+        SuperView?.SetSubViewNeedsDraw ();
+
+        if (this is Adornment adornment)
+        {
+            adornment.Parent?.SetSubViewNeedsDraw ();
+        }
 
         foreach (View subview in Subviews)
         {
-            if (subview.Frame.IntersectsWith (region))
+            if (subview.Frame.IntersectsWith (viewPortRelativeRegion))
             {
-                Rectangle subviewRegion = Rectangle.Intersect (subview.Frame, region);
+                Rectangle subviewRegion = Rectangle.Intersect (subview.Frame, viewPortRelativeRegion);
                 subviewRegion.X -= subview.Frame.X;
                 subviewRegion.Y -= subview.Frame.Y;
-                subview.SetNeedsDisplay (subviewRegion);
+                subview.SetNeedsDraw (subviewRegion);
             }
         }
     }
 
-    /// <summary>Sets <see cref="SubViewNeedsDisplay"/> to <see langword="true"/> for this View and all Superviews.</summary>
-    public void SetSubViewNeedsDisplay ()
+    /// <summary>Sets <see cref="SubViewNeedsDraw"/> to <see langword="true"/> for this View and all Superviews.</summary>
+    public void SetSubViewNeedsDraw ()
     {
-        SubViewNeedsDisplay = true;
+        SubViewNeedsDraw = true;
 
         if (this is Adornment adornment)
         {
-            adornment.Parent?.SetSubViewNeedsDisplay ();
+            adornment.Parent?.SetSubViewNeedsDraw ();
         }
 
-        if (SuperView is { SubViewNeedsDisplay: false })
+        if (SuperView is { SubViewNeedsDraw: false })
         {
-            SuperView.SetSubViewNeedsDisplay ();
+            SuperView.SetSubViewNeedsDraw ();
         }
     }
 
-    /// <summary>Clears <see cref="NeedsDisplay"/> and <see cref="SubViewNeedsDisplay"/>.</summary>
-    protected void ClearNeedsDisplay ()
+    /// <summary>Clears <see cref="NeedsDraw"/> and <see cref="SubViewNeedsDraw"/>.</summary>
+    protected void ClearNeedsDraw ()
     {
-        _needsDisplayRect = Rectangle.Empty;
-        SubViewNeedsDisplay = false;
+        _needsDrawRect = Rectangle.Empty;
+        SubViewNeedsDraw = false;
 
-        Margin?.ClearNeedsDisplay ();
-        Border?.ClearNeedsDisplay ();
-        Padding?.ClearNeedsDisplay ();
+        Margin?.ClearNeedsDraw ();
+        Border?.ClearNeedsDraw ();
+        Padding?.ClearNeedsDraw ();
 
         foreach (View subview in Subviews)
         {
-            subview.ClearNeedsDisplay ();
+            subview.ClearNeedsDraw ();
         }
     }
+
+    #endregion NeedsDraw
 }

+ 16 - 9
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 = [];
@@ -85,9 +89,8 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
             view.EndInit ();
         }
 
-        CheckDimAuto ();
+        SetNeedsDraw ();
         SetNeedsLayout ();
-        SetNeedsDisplay ();
 
         return view;
     }
@@ -126,7 +129,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 +152,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 +186,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 +346,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

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 340 - 359
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.Contains (currentLocation))
+                {
+                    found = start.Margin;
+                }
+                else if (start.Border.Contains (currentLocation))
+                {
+                    found = start.Border;
+                }
+                else if (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>

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

+ 22 - 21
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>
@@ -142,7 +144,6 @@ public partial class View : Responder, ISupportInitializeNotification
         //SetupMouse ();
 
         SetupText ();
-
     }
 
     /// <summary>
@@ -229,7 +230,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 +242,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);
     }
 
@@ -282,7 +286,12 @@ public partial class View : Responder, ISupportInitializeNotification
             }
 
             OnEnabledChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
+
+            if (Border is { })
+            {
+                Border.Enabled = _enabled;
+            }
 
             if (_subviews is null)
             {
@@ -291,18 +300,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 +361,10 @@ public partial class View : Responder, ISupportInitializeNotification
             OnVisibleChanged ();
             VisibleChanged?.Invoke (this, EventArgs.Empty);
 
-            SetNeedsDisplay ();
+            SetNeedsLayout ();
+            SuperView?.SetNeedsLayout();
+            SetNeedsDraw ();
+            SuperView?.SetNeedsDraw();
         }
     }
 
@@ -469,7 +470,7 @@ public partial class View : Responder, ISupportInitializeNotification
 
                 SetTitleTextFormatterSize ();
                 SetHotKeyFromTitle ();
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
 #if DEBUG
                 if (string.IsNullOrEmpty (Id))
                 {

+ 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.
     ///     <para>

+ 65 - 49
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,10 +77,11 @@ 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/>
@@ -119,7 +120,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 +137,7 @@ public class Bar : View, IOrientation, IDesignable
         set
         {
             _alignmentModes = value;
+            SetNeedsDraw ();
             SetNeedsLayout ();
         }
     }
@@ -162,7 +165,8 @@ public class Bar : View, IOrientation, IDesignable
             }
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
+        SetNeedsLayout ();
     }
 
     // TODO: Move this to View
@@ -185,20 +189,22 @@ 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);
     }
 
+    // This is used to calculate the minimum width of the Bar when the width is NOT Dim.Auto
+    private int? _minimumDimAutoWidth;
+
     private void LayoutBarItems (Size contentSize)
     {
         View? prevBarItem = null;
@@ -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;
+                    var minKeyWidth = 0;
 
-                for (var index = 0; index < Subviews.Count; index++)
-                {
-                    View barItem = Subviews [index];
+                    List<Shortcut> shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast<Shortcut> ().ToList ();
 
-                    barItem.ColorScheme = ColorScheme;
-
-                    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);

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

@@ -78,21 +78,19 @@ 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 (Rectangle viewport)
     {
-        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 ());
@@ -102,6 +100,8 @@ internal abstract class ColorBar : View, IColorBar
         _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 ()

+ 45 - 26
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,11 +131,11 @@ public class ColorPicker16 : View
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent (Rectangle viewport)
     {
-        base.OnDrawContent (viewport);
+        base.OnDrawingContent (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++)
@@ -132,12 +149,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 +176,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 +211,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>
@@ -204,7 +225,7 @@ public class ColorPicker16 : View
             for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++)
             {
                 Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY);
-                Driver.AddRune ((Rune)' ');
+                Driver?.AddRune ((Rune)' ');
                 index++;
             }
         }
@@ -265,7 +286,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 (Rectangle viewport)
     {
-        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)

+ 30 - 22
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,22 @@ 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 (Rectangle viewport)
     {
-        base.OnDrawContent (viewport);
 
         if (!_autoHide)
         {
-            return;
+            return true;
         }
 
-        Driver.SetAttribute (ColorScheme.Focus);
+        if (ColorScheme != null)
+        {
+            SetAttribute (ColorScheme.Focus);
+        }
         Move (Viewport.Right - 1, 0);
-        Driver.AddRune (Glyphs.DownArrow);
+        Driver?.AddRune (Glyphs.DownArrow);
+
+        return true;
     }
 
 
@@ -504,11 +508,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 +809,7 @@ public class ComboBox : View, IDesignable
         _listview.SetSource (_searchSet);
         _listview.ResumeSuspendCollectionChangedEvent ();
 
-        _listview.Clear ();
+        _listview.ClearViewport ();
         _listview.Height = CalculateHeight ();
         SuperView?.MoveSubviewToStart (this);
     }
@@ -872,7 +878,7 @@ public class ComboBox : View, IDesignable
                 if (isMousePositionValid)
                 {
                     _highlighted = Math.Min (TopItem + me.Position.Y, Source.Count);
-                    SetNeedsDisplay ();
+                    SetNeedsDraw ();
                 }
 
                 _isFocusing = false;
@@ -883,10 +889,10 @@ public class ComboBox : View, IDesignable
             return res;
         }
 
-        public override void OnDrawContent (Rectangle viewport)
+        protected override bool OnDrawingContent (Rectangle viewport)
         {
-            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 +922,7 @@ public class ComboBox : View, IDesignable
 
                 if (newcolor != current)
                 {
-                    Driver.SetAttribute (newcolor);
+                    SetAttribute (newcolor);
                     current = newcolor;
                 }
 
@@ -926,7 +932,7 @@ public class ComboBox : View, IDesignable
                 {
                     for (var c = 0; c < f.Width; c++)
                     {
-                        Driver.AddRune ((Rune)' ');
+                        Driver?.AddRune ((Rune)' ');
                     }
                 }
                 else
@@ -937,24 +943,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 (
+                        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);
                 }
             }
+
+            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 (Rectangle viewport) { return true; }
+
     private static string StandardizeDateFormat (string format)
     {
         return format switch

+ 9 - 7
Terminal.Gui/Views/FileDialog.cs

@@ -189,7 +189,7 @@ public class FileDialog : Dialog
                                                   bool newState = !tile.ContentView.Visible;
                                                   tile.ContentView.Visible = newState;
                                                   _btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState);
-                                                  LayoutSubviews ();
+                                                  SetNeedsLayout();
                                               };
 
         _tbFind = new TextField
@@ -368,10 +368,8 @@ public class FileDialog : Dialog
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent (Rectangle viewport)
     {
-        base.OnDrawContent (viewport);
-
         if (!string.IsNullOrWhiteSpace (_feedback))
         {
             int feedbackWidth = _feedback.EnumerateRunes ().Sum (c => c.GetColumns ());
@@ -386,11 +384,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 +461,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 +493,9 @@ public class FileDialog : Dialog
             _btnOk.X = Pos.Right (_btnCancel) + 1;
             MoveSubviewTowardsStart (_btnCancel);
         }
-        LayoutSubviews ();
+
+        SetNeedsDisplay();
+        SetNeedsLayout();
     }
 
     /// <inheritdoc/>

+ 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 (Rectangle viewport) { 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 (Rectangle viewport) { 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 ();
+            OnDrawingAdornments ();
+            OnRenderingLineCanvas ();
         }
 
         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 (Rectangle viewport)
     {
         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);

+ 39 - 28
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,22 +415,22 @@ public class HexView : View, IDesignable
             }
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         return true;
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent (Rectangle viewport)
     {
         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;
@@ -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 />

+ 7 - 6
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 (Rectangle viewport)
     {
         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;
         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 (Rectangle viewport)
     {
-        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;
     }
 }

+ 57 - 37
Terminal.Gui/Views/ListView.cs

@@ -123,11 +123,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 +226,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 +247,7 @@ public class ListView : View, IDesignable
         set
         {
             _allowsMarking = value;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -254,7 +274,7 @@ public class ListView : View, IDesignable
                 }
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -282,7 +302,7 @@ public class ListView : View, IDesignable
             }
 
             Viewport = Viewport with { X = value };
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -313,7 +333,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 +355,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 +375,7 @@ public class ListView : View, IDesignable
             SelectedItem = Source.Count - 1;
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         OnCollectionChanged (e);
     }
@@ -446,11 +467,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 +482,7 @@ public class ListView : View, IDesignable
         if (UnmarkAllButSelected ())
         {
             Source.SetMark (SelectedItem, !Source.IsMarked (SelectedItem));
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return Source.IsMarked (SelectedItem);
         }
@@ -537,7 +558,7 @@ public class ListView : View, IDesignable
         }
 
         OnSelectedChanged ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         if (me.Flags == MouseFlags.Button1DoubleClicked)
         {
@@ -564,7 +585,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 +602,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 +637,7 @@ public class ListView : View, IDesignable
             }
 
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return true;
@@ -631,7 +652,7 @@ public class ListView : View, IDesignable
             _selected = 0;
             Viewport = Viewport with { Y = _selected };
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return true;
@@ -670,7 +691,7 @@ public class ListView : View, IDesignable
             }
 
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return true;
@@ -692,7 +713,7 @@ public class ListView : View, IDesignable
             _selected = n;
             Viewport = Viewport with { Y = _selected };
             OnSelectedChanged ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
 
         return true;
@@ -715,7 +736,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 +757,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 (Rectangle viewport)
     {
-        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 +789,7 @@ public class ListView : View, IDesignable
 
             if (newcolor != current)
             {
-                Driver.SetAttribute (newcolor);
+                SetAttribute (newcolor);
                 current = newcolor;
             }
 
@@ -780,7 +799,7 @@ public class ListView : View, IDesignable
             {
                 for (var c = 0; c < f.Width; c++)
                 {
-                    Driver.AddRune ((Rune)' ');
+                    Driver?.AddRune ((Rune)' ');
                 }
             }
             else
@@ -791,21 +810,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);
             }
         }
+        return true;
     }
 
     /// <inheritdoc/>
@@ -866,7 +886,7 @@ public class ListView : View, IDesignable
             {
                 SelectedItem = (int)newItem;
                 EnsureSelectedItemVisible ();
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
 
                 return true;
             }

+ 37 - 30
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,25 @@ 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, EventArgs 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 ());
+        DrawAdornments ();
+        RenderLineCanvas ();
 
-        OnDrawAdornments ();
-        OnRenderLineCanvas ();
+        Region savedClip = Driver.Clip;
+        Driver.Clip = new (new (0, 0, Driver.Cols, Driver.Rows));
+        SetAttribute (GetNormalColor ());
 
         for (int i = Viewport.Y; i < _barItems!.Children.Length; i++)
         {
@@ -410,7 +427,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 +444,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++)
             {
@@ -562,16 +579,6 @@ internal sealed class Menu : View
         }
 
         Driver.Clip = savedClip;
-
-        // PositionCursor ();
-    }
-
-    private void Current_DrawContentComplete (object? sender, DrawEventArgs e)
-    {
-        if (Visible)
-        {
-            OnDrawContent (Viewport);
-        }
     }
 
     public override Point? PositionCursor ()
@@ -601,7 +608,7 @@ internal sealed class Menu : View
         Application.UngrabMouse ();
         _host.CloseAllMenus ();
         Application.Driver!.ClearContents ();
-        Application.Refresh ();
+        Application.LayoutAndDrawToplevels ();
 
         _host.Run (action);
     }
@@ -702,7 +709,7 @@ internal sealed class Menu : View
         }
         while (_barItems?.Children? [_currentChild] is null || disabled);
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         SetParentSetNeedsDisplay ();
 
         if (!_host.UseSubMenusSingleFrame)
@@ -783,7 +790,7 @@ internal sealed class Menu : View
         }
         while (_barItems.Children [_currentChild] is null || disabled);
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         SetParentSetNeedsDisplay ();
 
         if (!_host.UseSubMenusSingleFrame)
@@ -800,12 +807,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 +895,7 @@ internal sealed class Menu : View
 
             if (_host.UseSubMenusSingleFrame || !CheckSubMenu ())
             {
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
                 SetParentSetNeedsDisplay ();
 
                 return me.Handled = true;
@@ -935,7 +942,7 @@ internal sealed class Menu : View
         }
         else
         {
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
             SetParentSetNeedsDisplay ();
         }
 
@@ -948,7 +955,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 (Rectangle viewport)
     {
-        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.LayoutAndDrawToplevels ();
         _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.

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

@@ -46,7 +46,7 @@ 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)
+    protected override void OnSubviewLayout (LayoutEventArgs args)
     {
         for (int index = 0; index < Subviews.Count; index++)
         {
@@ -58,7 +58,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 (Rectangle viewport) { return true; }
 }
 
 /// <summary>

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

@@ -71,7 +71,7 @@ public class ProgressBar : View, IDesignable
         {
             _fraction = Math.Min (value, 1);
             _isActivity = false;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -109,7 +109,7 @@ public class ProgressBar : View, IDesignable
 
                     break;
             }
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -139,9 +139,9 @@ public class ProgressBar : View, IDesignable
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent (Rectangle viewport)
     {
-        Driver.SetAttribute (GetHotNormalColor ());
+        SetAttribute (GetHotNormalColor ());
 
         Move (0, 0);
 
@@ -151,11 +151,11 @@ public class ProgressBar : View, IDesignable
             {
                 if (Array.IndexOf (_activityPos, i) != -1)
                 {
-                    Driver.AddRune (SegmentCharacter);
+                    Driver?.AddRune (SegmentCharacter);
                 }
                 else
                 {
-                    Driver.AddRune ((Rune)' ');
+                    Driver?.AddRune ((Rune)' ');
                 }
             }
         }
@@ -166,12 +166,12 @@ 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)' ');
             }
         }
 
@@ -185,13 +185,15 @@ public class ProgressBar : View, IDesignable
                 attr = new Attribute (ColorScheme.HotNormal.Background, ColorScheme.HotNormal.Foreground);
             }
 
-            tf?.Draw (
+            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>
@@ -250,7 +252,7 @@ public class ProgressBar : View, IDesignable
             }
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     private void PopulateActivityPos ()

+ 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 (Rectangle viewport)
     {
-        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;
         }

+ 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 (Rectangle viewport)
     {
         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

+ 11 - 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,12 +372,12 @@ public class ScrollView : View
     }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent (Rectangle viewport)
     {
-        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)
         {
@@ -385,6 +385,8 @@ public class ScrollView : View
         }
 
         DrawScrollBars ();
+
+        return true;
     }
 
     /// <inheritdoc/>
@@ -467,7 +469,7 @@ public class ScrollView : View
             return view;
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         View container = view?.SuperView;
 
         if (container == this)
@@ -626,14 +628,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 ();
         }
     }
 

+ 95 - 119
Terminal.Gui/Views/Shortcut.cs

@@ -96,6 +96,10 @@ public class Shortcut : View, IOrientation, IDesignable
 
         HighlightStyle = HighlightStyle.None;
         CanFocus = true;
+
+        SuperViewRendersLineCanvas = true;
+        Border.Settings &= ~BorderSettings.Title;
+
         Width = GetWidthDimAuto ();
         Height = Dim.Auto (DimAutoStyle.Content, 1);
 
@@ -109,64 +113,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,99 +193,82 @@ 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)
     {
+        ShowHide ();
+        ForceCalculateNaturalWidth ();
+
         if (Width is DimAuto widthAuto)
         {
-            _minimumDimAutoWidth = Frame.Width;
-        }
+
+        } 
         else
         {
-            if (string.IsNullOrEmpty (HelpView.Text))
+            // Frame.Width is smaller than the natural width. Reduce width of HelpView.
+            _maxHelpWidth = int.Max (0, GetContentSize ().Width - CommandView.Frame.Width - KeyView.Frame.Width);
+            if (_maxHelpWidth < 3)
             {
-                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)
+                Thickness t = GetMarginThickness ();
+                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);
+                        // Scrunch it by removing both margins
+                        HelpView.Margin.Thickness = new (t.Right - 1, t.Top, t.Left - 1, t.Bottom);
 
                         break;
 
                     case 2:
+
                         // Scrunch just the right margin
-                        Thickness t = GetMarginThickness ();
                         HelpView.Margin.Thickness = new (t.Right, t.Top, t.Left - 1, t.Bottom);
 
-                        break;
-
-                    default:
-                        // Default margin
-                        HelpView.Margin.Thickness = GetMarginThickness ();
-
                         break;
                 }
-
-                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 ();
+                HelpView.Margin.Thickness = GetMarginThickness ();
             }
         }
     }
@@ -387,14 +353,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 +463,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;
@@ -538,8 +493,11 @@ public class Shortcut : View, IOrientation, IDesignable
     {
         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 +512,26 @@ 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 ();
         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 +623,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 +642,22 @@ public class Shortcut : View, IOrientation, IDesignable
 
             _minimumKeyTextSize = value;
             SetKeyViewDefaultLayout ();
+
+            // TODO: Prob not needed
             CommandView.SetNeedsLayout ();
             HelpView.SetNeedsLayout ();
             KeyView.SetNeedsLayout ();
-            SetSubViewNeedsDisplay ();
+            SetSubViewNeedsDraw ();
         }
     }
 
-    private int GetMinimumKeyViewSize () { return MinimumKeyTextSize; }
 
     private void SetKeyViewDefaultLayout ()
     {
         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,11 +705,12 @@ 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)
@@ -754,11 +718,16 @@ public class Shortcut : View, IOrientation, IDesignable
         // Border should match superview.
         if (Border is { })
         {
-            Border.ColorScheme = SuperView?.ColorScheme;
+            // 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 +741,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"
@@ -802,7 +779,6 @@ public class Shortcut : View, IOrientation, IDesignable
         return true;
     }
 
-
     /// <inheritdoc/>
     protected override void Dispose (bool disposing)
     {

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

@@ -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 (Rectangle viewport)
     {
         // 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
 
@@ -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++)
             {
@@ -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; }
 

+ 34 - 9
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 ();
@@ -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,29 @@ public class SpinnerView : View
                         _currentIdx = Sequence.Length - 1;
                     }
                 }
-
-                Text = "" + Sequence [_currentIdx]; //.EnumerateRunes;
             }
 
             _lastRender = DateTime.Now;
         }
 
-        SetNeedsDisplay ();
+        if (setNeedsDraw)
+        {
+            SetNeedsDraw ();
+        }
+    }
+
+    /// <inheritdoc />
+    protected override bool OnClearingViewport (Rectangle viewport) { return true; }
+
+    /// <inheritdoc />
+    protected override bool OnDrawingText (Rectangle viewport)
+    {
+        if (Sequence is { Length: > 0 } && _currentIdx < Sequence.Length)
+        {
+            Move (Viewport.X, Viewport.Y);
+            View.Driver?.AddStr (Sequence [_currentIdx]);
+        }
+        return true;
     }
 
     /// <inheritdoc/>
@@ -198,7 +215,7 @@ public class SpinnerView : View
                                            TimeSpan.FromMilliseconds (SpinDelay),
                                            () =>
                                            {
-                                               Application.Invoke (AdvanceAnimation);
+                                               Application.Invoke (() => AdvanceAnimation());
 
                                                return true;
                                            }
@@ -289,4 +306,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; }
 }

+ 127 - 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 (Rectangle viewport)
+    {
         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,34 @@ public class TabView : View
             return false;
         }
 
-        public override void OnDrawContent (Rectangle viewport)
+        /// <inheritdoc />
+        protected override bool OnClearingViewport (Rectangle viewport)
+        {
+            // clear any old text
+            ClearViewport ();
+
+            return true;
+        }
+
+        protected override bool OnDrawingContent (Rectangle viewport)
         {
             _host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
 
-            // clear any old text
-            Clear ();
 
+            SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
+
+            return true;
+        }
+
+        /// <inheritdoc />
+        protected override bool OnDrawingSubviews (Rectangle viewport)
+        {
             RenderTabLine ();
 
-            RenderUnderline ();
-            Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
+            return true;
         }
 
-        public override void OnDrawContentComplete (Rectangle viewport)
+        protected override void OnDrawComplete ()
         {
             if (_host._tabLocations is null)
             {
@@ -1171,7 +1212,9 @@ public class TabView : View
                 }
 
                 tab.LineCanvas.Merge (lc);
-                tab.OnRenderLineCanvas ();
+                tab.RenderLineCanvas ();
+
+                RenderUnderline ();
             }
         }
 
@@ -1188,21 +1231,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 +1273,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 +1288,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.DrawAdornments ();
 
-                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 +1321,10 @@ public class TabView : View
                                         ColorScheme.HotNormal
                                        );
 
-                tab.OnRenderLineCanvas ();
+                tab.DrawAdornments ();
+
 
-                Driver.SetAttribute (GetNormalColor ());
+                SetAttribute (GetNormalColor ());
             }
         }
 
@@ -1294,7 +1333,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 +1379,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 +1397,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/>

+ 120 - 39
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"/>
@@ -320,7 +321,7 @@ public class TableView : View
         set
         {
             columnOffset = TableIsNullOrInvisible () ? 0 : Math.Max (0, Math.Min (Table.Columns - 1, value));
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -582,7 +583,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 +644,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 +663,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 +830,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 +867,7 @@ public class TableView : View
             {
                 ColumnOffset--;
                 EnsureValidScrollOffsets ();
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             if (scrollRightPoint != null
@@ -875,7 +876,7 @@ public class TableView : View
             {
                 ColumnOffset++;
                 EnsureValidScrollOffsets ();
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
 
             Point? hit = ScreenToCell (boundsX, boundsY);
@@ -910,10 +911,8 @@ public class TableView : View
     }
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent (Rectangle viewport)
     {
-        base.OnDrawContent (viewport);
-
         Move (0, 0);
 
         scrollRightPoint = null;
@@ -922,7 +921,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 +984,8 @@ public class TableView : View
 
             RenderRow (line, rowToRender, columnsToRender);
         }
+
+        return true;
     }
 
     /// <inheritdoc/>
@@ -1226,12 +1227,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 +1242,7 @@ public class TableView : View
 
         EnsureSelectedCellIsVisible ();
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Invokes the <see cref="CellActivated"/> event</summary>
@@ -1280,19 +1281,19 @@ 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));
+                SetAttribute (new Attribute (cellColor.Background, cellColor.Foreground));
                 Driver.AddRune ((Rune)render [0]);
 
                 if (render.Length > 1)
                 {
-                    Driver.SetAttribute (cellColor);
+                    SetAttribute (cellColor);
                     Driver.AddStr (render.Substring (1));
                 }
             }
         }
         else
         {
-            Driver.SetAttribute (cellColor);
+            SetAttribute (cellColor);
             Driver.AddStr (render);
         }
     }
@@ -1323,7 +1324,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 +1506,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 +1585,7 @@ public class TableView : View
             SelectedRow = match;
             EnsureValidSelection ();
             EnsureSelectedCellIsVisible ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -1728,7 +1733,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 +1781,10 @@ public class TableView : View
                 }
             }
 
-            AddRuneAt (Driver, c, row, rune);
+            if (Driver is { })
+            {
+                AddRuneAt (Driver, c, row, rune);
+            }
         }
     }
 
@@ -1885,19 +1893,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 +1959,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 +1975,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 +1992,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 +2022,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 +2320,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 ();
         }
     }
 }

+ 22 - 20
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 (Rectangle viewport)
     {
         _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;
@@ -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 (Rectangle viewport)
         {
             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;
         }

+ 88 - 86
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 (Rectangle viewport)
     {
         _isDrawing = true;
 
@@ -3645,6 +3645,8 @@ public class TextView : View
         //PositionCursor ();
 
         _isDrawing = false;
+
+        return true;
     }
 
     /// <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

+ 148 - 139
Terminal.Gui/Views/TileView.cs

@@ -1,4 +1,5 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 /// <summary>
 ///     A <see cref="View"/> consisting of a moveable bar that divides the display area into resizeable
@@ -7,15 +8,13 @@
 public class TileView : View
 {
     private Orientation _orientation = Orientation.Vertical;
-    private List<Pos> _splitterDistances;
-    private List<TileViewLineView> _splitterLines;
-    private List<Tile> _tiles;
-    private TileView _parentTileView;
+    private List<Pos>? _splitterDistances;
+    private List<TileViewLineView>? _splitterLines;
+    private List<Tile>? _tiles;
+    private TileView? _parentTileView;
 
     /// <summary>Creates a new instance of the <see cref="TileView"/> class with 2 tiles (i.e. left and right).</summary>
-    public TileView () : this (2)
-    {
-    }
+    public TileView () : this (2) { }
 
     /// <summary>Creates a new instance of the <see cref="TileView"/> class with <paramref name="tiles"/> number of tiles.</summary>
     /// <param name="tiles"></param>
@@ -23,6 +22,23 @@ public class TileView : View
     {
         CanFocus = true;
         RebuildForTileCount (tiles);
+
+        SubviewLayout += (_, _) =>
+                         {
+                             Rectangle viewport = Viewport;
+
+                             if (HasBorder ())
+                             {
+                                 viewport = new (
+                                                 viewport.X + 1,
+                                                 viewport.Y + 1,
+                                                 Math.Max (0, viewport.Width - 2),
+                                                 Math.Max (0, viewport.Height - 2)
+                                                );
+                             }
+
+                             Setup (viewport);
+                         };
     }
 
     /// <summary>The line style to use when drawing the splitter lines.</summary>
@@ -34,20 +50,24 @@ public class TileView : View
         get => _orientation;
         set
         {
-            _orientation = value;
-
-            if (IsInitialized)
+            if (_orientation == value)
             {
-                LayoutSubviews ();
+                return;
             }
+
+            _orientation = value;
+
+            SetNeedsDraw ();
+            SetNeedsLayout ();
+
         }
     }
 
     /// <summary>The splitter locations. Note that there will be N-1 splitters where N is the number of <see cref="Tiles"/>.</summary>
-    public IReadOnlyCollection<Pos> SplitterDistances => _splitterDistances.AsReadOnly ();
+    public IReadOnlyCollection<Pos> SplitterDistances => _splitterDistances!.AsReadOnly ();
 
     /// <summary>The sub sections hosted by the view</summary>
-    public IReadOnlyCollection<Tile> Tiles => _tiles.AsReadOnly ();
+    public IReadOnlyCollection<Tile> Tiles => _tiles!.AsReadOnly ();
 
     // TODO: Update to use Key instead of KeyCode
     /// <summary>
@@ -63,7 +83,7 @@ public class TileView : View
     /// </summary>
     /// <remarks>Use <see cref="IsRootTileView"/> to determine if the returned value is the root.</remarks>
     /// <returns></returns>
-    public TileView GetParentTileView () { return _parentTileView; }
+    public TileView? GetParentTileView () { return _parentTileView; }
 
     /// <summary>
     ///     Returns the index of the first <see cref="Tile"/> in <see cref="Tiles"/> which contains
@@ -71,9 +91,9 @@ public class TileView : View
     /// </summary>
     public int IndexOf (View toFind, bool recursive = false)
     {
-        for (var i = 0; i < _tiles.Count; i++)
+        for (var i = 0; i < _tiles!.Count; i++)
         {
-            View v = _tiles [i].ContentView;
+            View v = _tiles [i].ContentView!;
 
             if (v == toFind)
             {
@@ -102,14 +122,14 @@ public class TileView : View
     ///     line
     /// </summary>
     /// <param name="idx"></param>
-    public Tile InsertTile (int idx)
+    public Tile? InsertTile (int idx)
     {
         Tile [] oldTiles = Tiles.ToArray ();
         RebuildForTileCount (oldTiles.Length + 1);
 
-        Tile toReturn = null;
+        Tile? toReturn = null;
 
-        for (var i = 0; i < _tiles.Count; i++)
+        for (var i = 0; i < _tiles?.Count; i++)
         {
             if (i != idx)
             {
@@ -117,12 +137,12 @@ public class TileView : View
 
                 // remove the new empty View
                 Remove (_tiles [i].ContentView);
-                _tiles [i].ContentView.Dispose ();
+                _tiles [i].ContentView?.Dispose ();
                 _tiles [i].ContentView = null;
 
                 // restore old Tile and View
                 _tiles [i] = oldTile;
-                _tiles [i].ContentView.TabStop = TabStop;
+                _tiles [i].ContentView!.TabStop = TabStop;
                 Add (_tiles [i].ContentView);
             }
             else
@@ -131,12 +151,8 @@ public class TileView : View
             }
         }
 
-        SetNeedsDisplay ();
-
-        if (IsInitialized)
-        {
-            LayoutSubviews ();
-        }
+        SetNeedsDraw ();
+        SetNeedsLayout ();
 
         return toReturn;
     }
@@ -155,44 +171,22 @@ public class TileView : View
     /// <returns></returns>
     public bool IsRootTileView () { return _parentTileView == null; }
 
-    /// <inheritdoc/>
-    public override void LayoutSubviews ()
-    {
-        if (!IsInitialized)
-        {
-            return;
-        }
-
-        Rectangle viewport = Viewport;
-
-        if (HasBorder ())
-        {
-            viewport = new (
-                            viewport.X + 1,
-                            viewport.Y + 1,
-                            Math.Max (0, viewport.Width - 2),
-                            Math.Max (0, viewport.Height - 2)
-                           );
-        }
-
-        Setup (viewport);
-        base.LayoutSubviews ();
-    }
-
     // BUG: v2 fix this hack
     // QUESTION: Does this need to be fixed before events are refactored?
     /// <summary>Overridden so no Frames get drawn</summary>
     /// <returns></returns>
-    public override bool OnDrawAdornments () { return false; }
+    protected override bool OnDrawingAdornments () { return true; }
 
     /// <inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
-    {
-        Driver.SetAttribute (ColorScheme.Normal);
-
-        Clear ();
+    protected override bool OnRenderingLineCanvas () { return false; }
 
-        base.OnDrawContent (viewport);
+    /// <inheritdoc/>
+    protected override void OnDrawComplete ()
+    {
+        if (ColorScheme is { })
+        {
+            SetAttribute (ColorScheme.Normal);
+        }
 
         var lc = new LineCanvas ();
 
@@ -207,14 +201,14 @@ public class TileView : View
                 lc.AddLine (Point.Empty, Viewport.Height, Orientation.Vertical, LineStyle);
 
                 lc.AddLine (
-                            new Point (Viewport.Width - 1, Viewport.Height - 1),
+                            new (Viewport.Width - 1, Viewport.Height - 1),
                             -Viewport.Width,
                             Orientation.Horizontal,
                             LineStyle
                            );
 
                 lc.AddLine (
-                            new Point (Viewport.Width - 1, Viewport.Height - 1),
+                            new (Viewport.Width - 1, Viewport.Height - 1),
                             -Viewport.Height,
                             Orientation.Vertical,
                             LineStyle
@@ -223,7 +217,7 @@ public class TileView : View
 
             foreach (TileViewLineView line in allLines)
             {
-                bool isRoot = _splitterLines.Contains (line);
+                bool isRoot = _splitterLines!.Contains (line);
 
                 Rectangle screen = line.ViewportToScreen (Rectangle.Empty);
                 Point origin = ScreenToFrame (screen.Location);
@@ -247,7 +241,10 @@ public class TileView : View
             }
         }
 
-        Driver.SetAttribute (ColorScheme.Normal);
+        if (ColorScheme is { })
+        {
+            SetAttribute (ColorScheme.Normal);
+        }
 
         foreach (KeyValuePair<Point, Rune> p in lc.GetMap (Viewport))
         {
@@ -282,6 +279,8 @@ public class TileView : View
                 AddRune (renderAt.X + i, renderAt.Y, (Rune)title [i]);
             }
         }
+
+        return;
     }
 
     //// BUGBUG: Why is this not handled by a key binding???
@@ -292,7 +291,7 @@ public class TileView : View
 
         if (key.KeyCode == ToggleResizable)
         {
-            foreach (TileViewLineView l in _splitterLines)
+            foreach (TileViewLineView l in _splitterLines!)
             {
                 bool iniBefore = l.IsInitialized;
                 l.IsInitialized = false;
@@ -319,8 +318,8 @@ public class TileView : View
     /// <param name="count"></param>
     public void RebuildForTileCount (int count)
     {
-        _tiles = new List<Tile> ();
-        _splitterDistances = new List<Pos> ();
+        _tiles = new ();
+        _splitterDistances = new ();
 
         if (_splitterLines is { })
         {
@@ -330,13 +329,13 @@ public class TileView : View
             }
         }
 
-        _splitterLines = new List<TileViewLineView> ();
+        _splitterLines = new ();
 
         RemoveAll ();
 
         foreach (Tile tile in _tiles)
         {
-            tile.ContentView.Dispose ();
+            tile.ContentView?.Dispose ();
             tile.ContentView = null;
         }
 
@@ -361,15 +360,14 @@ public class TileView : View
 
             var tile = new Tile ();
             _tiles.Add (tile);
-            tile.ContentView.Id = $"Tile.ContentView {i}";
+            tile.ContentView!.Id = $"Tile.ContentView {i}";
             Add (tile.ContentView);
-            tile.TitleChanged += (s, e) => SetNeedsDisplay ();
-        }
 
-        if (IsInitialized)
-        {
-            LayoutSubviews ();
+            // BUGBUG: This should not be needed:
+            tile.TitleChanged += (s, e) => SetNeedsLayout ();
         }
+
+        SetNeedsLayout ();
     }
 
     /// <summary>
@@ -378,7 +376,7 @@ public class TileView : View
     /// </summary>
     /// <param name="idx"></param>
     /// <returns></returns>
-    public Tile RemoveTile (int idx)
+    public Tile? RemoveTile (int idx)
     {
         Tile [] oldTiles = Tiles.ToArray ();
 
@@ -391,14 +389,14 @@ public class TileView : View
 
         RebuildForTileCount (oldTiles.Length - 1);
 
-        for (var i = 0; i < _tiles.Count; i++)
+        for (var i = 0; i < _tiles?.Count; i++)
         {
             int oldIdx = i >= idx ? i + 1 : i;
             Tile oldTile = oldTiles [oldIdx];
 
             // remove the new empty View
             Remove (_tiles [i].ContentView);
-            _tiles [i].ContentView.Dispose ();
+            _tiles [i].ContentView?.Dispose ();
             _tiles [i].ContentView = null;
 
             // restore old Tile and View
@@ -406,9 +404,6 @@ public class TileView : View
             Add (_tiles [i].ContentView);
         }
 
-        SetNeedsDisplay ();
-        LayoutSubviews ();
-
         return removed;
     }
 
@@ -439,15 +434,20 @@ public class TileView : View
             return false;
         }
 
-        _splitterDistances [idx] = value;
-        GetRootTileView ().LayoutSubviews ();
+        if (_splitterDistances is { })
+        {
+            _splitterDistances [idx] = value;
+        }
+
         OnSplitterMoved (idx);
+        SetNeedsDraw ();
+        SetNeedsLayout ();
 
         return true;
     }
 
     /// <summary>Invoked when any of the <see cref="SplitterDistances"/> is changed.</summary>
-    public event SplitterEventHandler SplitterMoved;
+    public event SplitterEventHandler? SplitterMoved;
 
     /// <summary>
     ///     Converts of <see cref="Tiles"/> element <paramref name="idx"/> from a regular <see cref="View"/> to a new
@@ -469,10 +469,10 @@ public class TileView : View
     {
         // when splitting a view into 2 sub views we will need to migrate
         // the title too
-        Tile tile = _tiles [idx];
+        Tile tile = _tiles! [idx];
 
         string title = tile.Title;
-        View toMove = tile.ContentView;
+        View? toMove = tile.ContentView;
 
         if (toMove is TileView existing)
         {
@@ -487,7 +487,7 @@ public class TileView : View
         };
 
         // Take everything out of the View we are moving
-        View [] childViews = toMove.Subviews.ToArray ();
+        View [] childViews = toMove!.Subviews.ToArray ();
         toMove.RemoveAll ();
 
         // Remove the view itself and replace it with the new TileView
@@ -499,16 +499,16 @@ public class TileView : View
 
         tile.ContentView = newContainer;
 
-        View newTileView1 = newContainer._tiles [0].ContentView;
+        View newTileView1 = newContainer!._tiles? [0].ContentView!;
 
         // Add the original content into the first view of the new container
         foreach (View childView in childViews)
         {
-            newTileView1.Add (childView);
+            newTileView1!.Add (childView);
         }
 
         // Move the title across too
-        newContainer._tiles [0].Title = title;
+        newContainer._tiles! [0].Title = title;
         tile.Title = string.Empty;
 
         result = newContainer;
@@ -522,14 +522,14 @@ public class TileView : View
         foreach (Tile tile in Tiles)
         {
             Remove (tile.ContentView);
-            tile.ContentView.Dispose ();
+            tile.ContentView?.Dispose ();
         }
 
         base.Dispose (disposing);
     }
 
     /// <summary>Raises the <see cref="SplitterMoved"/> event</summary>
-    protected virtual void OnSplitterMoved (int idx) { SplitterMoved?.Invoke (this, new SplitterEventArgs (this, idx, _splitterDistances [idx])); }
+    protected virtual void OnSplitterMoved (int idx) { SplitterMoved?.Invoke (this, new (this, idx, _splitterDistances! [idx])); }
 
     private List<TileViewLineView> GetAllLineViewsRecursively (View v)
     {
@@ -556,14 +556,14 @@ public class TileView : View
         return lines;
     }
 
-    private List<TileTitleToRender> GetAllTitlesToRenderRecursively (TileView v, int depth = 0)
+    private List<TileTitleToRender> GetAllTitlesToRenderRecursively (TileView? v, int depth = 0)
     {
         List<TileTitleToRender> titles = new ();
 
-        foreach (Tile sub in v.Tiles)
+        foreach (Tile sub in v!.Tiles)
         {
             // Don't render titles for invisible stuff!
-            if (!sub.ContentView.Visible)
+            if (!sub.ContentView!.Visible)
             {
                 continue;
             }
@@ -579,7 +579,7 @@ public class TileView : View
             {
                 if (sub.Title.Length > 0)
                 {
-                    titles.Add (new TileTitleToRender (v, sub, depth));
+                    titles.Add (new (v, sub, depth));
                 }
             }
         }
@@ -599,20 +599,20 @@ public class TileView : View
         return root;
     }
 
-    private Dim GetTileWidthOrHeight (int i, int space, Tile [] visibleTiles, TileViewLineView [] visibleSplitterLines)
+    private Dim GetTileWidthOrHeight (int i, int space, Tile? [] visibleTiles, TileViewLineView? [] visibleSplitterLines)
     {
         // last tile
         if (i + 1 >= visibleTiles.Length)
         {
-            return Dim.Fill (HasBorder () ? 1 : 0);
+            return Dim.Fill (HasBorder () ? 1 : 0)!;
         }
 
-        TileViewLineView nextSplitter = visibleSplitterLines [i];
-        Pos nextSplitterPos = Orientation == Orientation.Vertical ? nextSplitter.X : nextSplitter.Y;
+        TileViewLineView? nextSplitter = visibleSplitterLines [i];
+        Pos? nextSplitterPos = Orientation == Orientation.Vertical ? nextSplitter!.X : nextSplitter!.Y;
         int nextSplitterDistance = nextSplitterPos.GetAnchor (space);
 
-        TileViewLineView lastSplitter = i >= 1 ? visibleSplitterLines [i - 1] : null;
-        Pos lastSplitterPos = Orientation == Orientation.Vertical ? lastSplitter?.X : lastSplitter?.Y;
+        TileViewLineView? lastSplitter = i >= 1 ? visibleSplitterLines [i - 1] : null;
+        Pos? lastSplitterPos = Orientation == Orientation.Vertical ? lastSplitter?.X : lastSplitter?.Y;
         int lastSplitterDistance = lastSplitterPos?.GetAnchor (space) ?? 0;
 
         int distance = nextSplitterDistance - lastSplitterDistance;
@@ -629,19 +629,19 @@ public class TileView : View
 
     private void HideSplittersBasedOnTileVisibility ()
     {
-        if (_splitterLines.Count == 0)
+        if (_splitterLines is { Count: 0 })
         {
             return;
         }
 
-        foreach (TileViewLineView line in _splitterLines)
+        foreach (TileViewLineView line in _splitterLines!)
         {
             line.Visible = true;
         }
 
-        for (var i = 0; i < _tiles.Count; i++)
+        for (var i = 0; i < _tiles!.Count; i++)
         {
-            if (!_tiles [i].ContentView.Visible)
+            if (!_tiles [i].ContentView!.Visible)
             {
                 // when a tile is not visible, prefer hiding
                 // the splitter on it's left
@@ -665,7 +665,7 @@ public class TileView : View
     private bool IsValidNewSplitterPos (int idx, Pos value, int fullSpace)
     {
         int newSize = value.GetAnchor (fullSpace);
-        bool isGettingBigger = newSize > _splitterDistances [idx].GetAnchor (fullSpace);
+        bool isGettingBigger = newSize > _splitterDistances! [idx].GetAnchor (fullSpace);
         int lastSplitterOrBorder = HasBorder () ? 1 : 0;
         int nextSplitterOrBorder = HasBorder () ? fullSpace - 1 : fullSpace;
 
@@ -724,7 +724,7 @@ public class TileView : View
             }
 
             // don't grow if it would take us below min size of right panel
-            if (spaceForNext < _tiles [idx + 1].MinSize)
+            if (spaceForNext < _tiles! [idx + 1].MinSize)
             {
                 return false;
             }
@@ -740,7 +740,7 @@ public class TileView : View
             }
 
             // don't shrink if it would take us below min size of left panel
-            if (spaceForLast < _tiles [idx].MinSize)
+            if (spaceForLast < _tiles! [idx].MinSize)
             {
                 return false;
             }
@@ -774,7 +774,7 @@ public class TileView : View
             return;
         }
 
-        for (var i = 0; i < _splitterLines.Count; i++)
+        for (var i = 0; i < _splitterLines!.Count; i++)
         {
             TileViewLineView line = _splitterLines [i];
 
@@ -791,19 +791,19 @@ public class TileView : View
 
             if (_orientation == Orientation.Vertical)
             {
-                line.X = _splitterDistances [i];
+                line.X = _splitterDistances! [i];
                 line.Y = 0;
             }
             else
             {
-                line.Y = _splitterDistances [i];
+                line.Y = _splitterDistances! [i];
                 line.X = 0;
             }
         }
 
         HideSplittersBasedOnTileVisibility ();
 
-        Tile [] visibleTiles = _tiles.Where (t => t.ContentView.Visible).ToArray ();
+        Tile [] visibleTiles = _tiles!.Where (t => t.ContentView!.Visible).ToArray ();
         TileViewLineView [] visibleSplitterLines = _splitterLines.Where (l => l.Visible).ToArray ();
 
         for (var i = 0; i < visibleTiles.Length; i++)
@@ -812,26 +812,27 @@ public class TileView : View
 
             if (Orientation == Orientation.Vertical)
             {
-                tile.ContentView.X = i == 0 ? viewport.X : Pos.Right (visibleSplitterLines [i - 1]);
+                tile.ContentView!.X = i == 0 ? viewport.X : Pos.Right (visibleSplitterLines [i - 1]);
                 tile.ContentView.Y = viewport.Y;
                 tile.ContentView.Height = viewport.Height;
                 tile.ContentView.Width = GetTileWidthOrHeight (i, Viewport.Width, visibleTiles, visibleSplitterLines);
             }
             else
             {
-                tile.ContentView.X = viewport.X;
+                tile.ContentView!.X = viewport.X;
                 tile.ContentView.Y = i == 0 ? viewport.Y : Pos.Bottom (visibleSplitterLines [i - 1]);
                 tile.ContentView.Width = viewport.Width;
                 tile.ContentView.Height = GetTileWidthOrHeight (i, Viewport.Height, visibleTiles, visibleSplitterLines);
             }
+
             //  BUGBUG: This should not be needed. If any of the pos/dim setters above actually changed values, NeedsDisplay should have already been set. 
-            tile.ContentView.SetNeedsDisplay ();
+            tile.ContentView.SetNeedsDraw ();
         }
     }
 
     private class TileTitleToRender
     {
-        public TileTitleToRender (TileView parent, Tile tile, int depth)
+        public TileTitleToRender (TileView? parent, Tile tile, int depth)
         {
             Parent = parent;
             Tile = tile;
@@ -839,8 +840,8 @@ public class TileView : View
         }
 
         public int Depth { get; }
-        public TileView Parent { get; }
-        public Tile Tile { get; }
+        public TileView? Parent { get; }
+        public Tile? Tile { get; }
 
         /// <summary>
         ///     Translates the <see cref="Tile"/> title location from its local coordinate space
@@ -848,21 +849,22 @@ public class TileView : View
         /// </summary>
         public Point GetLocalCoordinateForTitle (TileView intoCoordinateSpace)
         {
-            Rectangle screen = Tile.ContentView.ViewportToScreen (Rectangle.Empty);
+            Rectangle screen = Tile!.ContentView!.ViewportToScreen (Rectangle.Empty);
+
             return intoCoordinateSpace.ScreenToFrame (new (screen.X, screen.Y - 1));
         }
 
         internal string GetTrimmedTitle ()
         {
-            Dim spaceDim = Tile.ContentView.Width;
+            Dim? spaceDim = Tile?.ContentView?.Width;
 
-            int spaceAbs = spaceDim.GetAnchor (Parent.Viewport.Width);
+            int spaceAbs = spaceDim!.GetAnchor (Parent!.Viewport.Width);
 
-            var title = $" {Tile.Title} ";
+            var title = $" {Tile!.Title} ";
 
             if (title.Length > spaceAbs)
             {
-                return title.Substring (0, spaceAbs);
+                return title!.Substring (0, spaceAbs);
             }
 
             return title;
@@ -873,7 +875,7 @@ public class TileView : View
     {
         public Point? moveRuneRenderLocation;
 
-        private Pos dragOrignalPos;
+        private Pos? dragOrignalPos;
         private Point? dragPosition;
 
         public TileViewLineView (TileView parent, int idx)
@@ -883,13 +885,13 @@ public class TileView : View
 
             Parent = parent;
             Idx = idx;
-            AddCommand (Command.Right, () => { return MoveSplitter (1, 0); });
+            AddCommand (Command.Right, () => MoveSplitter (1, 0));
 
-            AddCommand (Command.Left, () => { return MoveSplitter (-1, 0); });
+            AddCommand (Command.Left, () => MoveSplitter (-1, 0));
 
-            AddCommand (Command.Up, () => { return MoveSplitter (0, -1); });
+            AddCommand (Command.Up, () => MoveSplitter (0, -1));
 
-            AddCommand (Command.Down, () => { return MoveSplitter (0, 1); });
+            AddCommand (Command.Down, () => MoveSplitter (0, 1));
 
             KeyBindings.Add (Key.CursorRight, Command.Right);
             KeyBindings.Add (Key.CursorLeft, Command.Left);
@@ -956,7 +958,7 @@ public class TileView : View
                     moveRuneRenderLocation = new Point (0, Math.Max (1, Math.Min (Viewport.Height - 2, mouseEvent.Position.Y)));
                 }
 
-                Parent.SetNeedsDisplay ();
+                Parent.SetNeedsLayout ();
 
                 return true;
             }
@@ -969,7 +971,7 @@ public class TileView : View
 
                 //Driver.UncookMouse ();
                 FinalisePosition (
-                                  dragOrignalPos,
+                                  dragOrignalPos!,
                                   Orientation == Orientation.Horizontal ? Y : X
                                  );
                 dragPosition = null;
@@ -979,11 +981,14 @@ public class TileView : View
             return false;
         }
 
-        public override void OnDrawContent (Rectangle viewport)
-        {
-            base.OnDrawContent (viewport);
+        /// <inheritdoc/>
+        protected override bool OnClearingViewport (Rectangle viewport) { return true; }
 
+        protected override bool OnDrawingContent (Rectangle viewport)
+        {
             DrawSplitterSymbol ();
+
+            return true;
         }
 
         public override Point? PositionCursor ()
@@ -1015,7 +1020,7 @@ public class TileView : View
             float position = p.GetAnchor (parentLength) + 0.5f;
 
             // Calculate the percentage
-            int percent = (int)Math.Round ((position / parentLength) * 100);
+            var percent = (int)Math.Round (position / parentLength * 100);
 
             // Return a new PosPercent object
             return Pos.Percent (percent);
@@ -1036,6 +1041,10 @@ public class TileView : View
         /// <param name="newValue"></param>
         private bool FinalisePosition (Pos oldValue, Pos newValue)
         {
+            SetNeedsDraw ();
+
+            SetNeedsLayout ();
+
             if (oldValue is PosPercent)
             {
                 if (Orientation == Orientation.Horizontal)
@@ -1078,10 +1087,10 @@ public class TileView : View
         private Pos Offset (Pos pos, int delta)
         {
             int posAbsolute = pos.GetAnchor (
-                                          Orientation == Orientation.Horizontal
-                                              ? Parent.Viewport.Height
-                                              : Parent.Viewport.Width
-                                         );
+                                             Orientation == Orientation.Horizontal
+                                                 ? Parent.Viewport.Height
+                                                 : Parent.Viewport.Width
+                                            );
 
             return posAbsolute + delta;
         }
@@ -1089,4 +1098,4 @@ public class TileView : View
 }
 
 /// <summary>Represents a method that will handle splitter events.</summary>
-public delegate void SplitterEventHandler (object sender, SplitterEventArgs e);
+public delegate void SplitterEventHandler (object? sender, SplitterEventArgs e);

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

@@ -106,7 +106,7 @@ public class TimeField : TextField
 
             SetText (Text);
             ReadOnly = ro;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 

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

@@ -235,7 +235,7 @@ public partial class Toplevel : View
             return;
         }
 
-        var layoutSubviews = false;
+        //var layoutSubviews = false;
         var maxWidth = 0;
 
         if (superView.Margin is { } && superView == top.SuperView)
@@ -251,36 +251,26 @@ public partial class Toplevel : View
             if (top?.X is null or PosAbsolute && top?.Frame.X != nx)
             {
                 top!.X = nx;
-                layoutSubviews = true;
+                //layoutSubviews = true;
             }
 
             if (top?.Y is null or PosAbsolute && top?.Frame.Y != ny)
             {
                 top!.Y = ny;
-                layoutSubviews = true;
+                //layoutSubviews = true;
             }
         }
 
-        //// TODO: v2 - This is a hack to get the StatusBar to be positioned correctly.
-        //if (sb != null
-        //    && !top!.Subviews.Contains (sb)
-        //    && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0)
-        //    && top.Height is DimFill
-        //    && -top.Height.GetAnchor (0) < 1)
+
+        //if (superView.IsLayoutNeeded () || layoutSubviews)
         //{
-        //    top.Height = Dim.Fill (sb.Visible ? 1 : 0);
-        //    layoutSubviews = true;
+        //    superView.LayoutSubviews ();
         //}
 
-        if (superView.LayoutNeeded || layoutSubviews)
-        {
-            superView.LayoutSubviews ();
-        }
-
-        if (LayoutNeeded)
-        {
-            LayoutSubviews ();
-        }
+        //if (IsLayoutNeeded ())
+        //{
+        //    LayoutSubviews ();
+        //}
     }
 
     /// <summary>Invoked when the terminal has been resized. The new <see cref="Size"/> of the terminal is provided.</summary>

+ 64 - 42
Terminal.Gui/Views/TreeView/TreeView.cs

@@ -3,6 +3,7 @@
 // and code to be used in this library under the MIT license.
 
 using System.Collections.ObjectModel;
+using static Terminal.Gui.SpinnerStyle;
 
 namespace Terminal.Gui;
 
@@ -18,15 +19,15 @@ public interface ITreeView
     /// <summary>Removes all objects from the tree and clears selection.</summary>
     void ClearObjects ();
 
-    /// <summary>Sets a flag indicating this view needs to be redisplayed because its state has changed.</summary>
-    void SetNeedsDisplay ();
+    /// <summary>Sets a flag indicating this view needs to be drawn because its state has changed.</summary>
+    void SetNeedsDraw ();
 }
 
 /// <summary>
 ///     Convenience implementation of generic <see cref="TreeView{T}"/> for any tree were all nodes implement
 ///     <see cref="ITreeNode"/>. <a href="../docs/treeview.md">See TreeView Deep Dive for more information</a>.
 /// </summary>
-public class TreeView : TreeView<ITreeNode>
+public class TreeView : TreeView<ITreeNode>, IDesignable
 {
     /// <summary>
     ///     Creates a new instance of the tree control with absolute positioning and initialises
@@ -39,6 +40,24 @@ public class TreeView : TreeView<ITreeNode>
         TreeBuilder = new TreeNodeBuilder ();
         AspectGetter = o => o is null ? "Null" : o.Text ?? o?.ToString () ?? "Unnamed Node";
     }
+
+
+    bool IDesignable.EnableForDesign ()
+    {
+        var root1 = new TreeNode ("Root1");
+        root1.Children.Add (new TreeNode ("Child1.1"));
+        root1.Children.Add (new TreeNode ("Child1.2"));
+
+        var root2 = new TreeNode ("Root2");
+        root2.Children.Add (new TreeNode ("Child2.1"));
+        root2.Children.Add (new TreeNode ("Child2.2"));
+
+        AddObject (root1);
+        AddObject (root2);
+
+        ExpandAll ();
+        return true;
+    }
 }
 
 /// <summary>
@@ -358,7 +377,7 @@ public class TreeView<T> : View, ITreeView where T : class
             {
                 KeyBindings.ReplaceKey (ObjectActivationKey, value);
                 objectActivationKey = value;
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
         }
     }
@@ -369,7 +388,7 @@ public class TreeView<T> : View, ITreeView where T : class
     /// <summary>The amount of tree view that has been scrolled to the right (horizontally).</summary>
     /// <remarks>
     ///     Setting a value of less than 0 will result in a offset of 0. To see changes in the UI call
-    ///     <see cref="View.SetNeedsDisplay()"/>.
+    ///     <see cref="View.SetNeedsDraw()"/>.
     /// </remarks>
     public int ScrollOffsetHorizontal
     {
@@ -377,14 +396,14 @@ public class TreeView<T> : View, ITreeView where T : class
         set
         {
             scrollOffsetHorizontal = Math.Max (0, value);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
     /// <summary>The amount of tree view that has been scrolled off the top of the screen (by the user scrolling down).</summary>
     /// <remarks>
     ///     Setting a value of less than 0 will result in an offset of 0. To see changes in the UI call
-    ///     <see cref="View.SetNeedsDisplay()"/>.
+    ///     <see cref="View.SetNeedsDraw()"/>.
     /// </remarks>
     public int ScrollOffsetVertical
     {
@@ -392,7 +411,7 @@ public class TreeView<T> : View, ITreeView where T : class
         set
         {
             scrollOffsetVertical = Math.Max (0, value);
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -435,7 +454,7 @@ public class TreeView<T> : View, ITreeView where T : class
         multiSelectedRegions.Clear ();
         roots = new Dictionary<T, Branch<T>> ();
         InvalidateLineMap ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
@@ -471,7 +490,7 @@ public class TreeView<T> : View, ITreeView where T : class
         {
             roots.Add (o, new Branch<T> (this, null, o));
             InvalidateLineMap ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -494,7 +513,7 @@ public class TreeView<T> : View, ITreeView where T : class
         if (objectsAdded)
         {
             InvalidateLineMap ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -562,7 +581,7 @@ public class TreeView<T> : View, ITreeView where T : class
             }
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Moves the selection to the last child in the currently selected level.</summary>
@@ -594,7 +613,7 @@ public class TreeView<T> : View, ITreeView where T : class
             {
                 SelectedObject = currentBranch.Model;
                 EnsureVisible (currentBranch.Model);
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
 
                 return;
             }
@@ -636,7 +655,7 @@ public class TreeView<T> : View, ITreeView where T : class
             {
                 SelectedObject = currentBranch.Model;
                 EnsureVisible (currentBranch.Model);
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
 
                 return;
             }
@@ -697,7 +716,7 @@ public class TreeView<T> : View, ITreeView where T : class
         }
 
         InvalidateLineMap ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
@@ -753,7 +772,7 @@ public class TreeView<T> : View, ITreeView where T : class
 
         ObjectToBranch (toExpand)?.Expand ();
         InvalidateLineMap ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Expands the supplied object and all child objects.</summary>
@@ -767,7 +786,7 @@ public class TreeView<T> : View, ITreeView where T : class
 
         ObjectToBranch (toExpand)?.ExpandAll ();
         InvalidateLineMap ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
@@ -782,7 +801,7 @@ public class TreeView<T> : View, ITreeView where T : class
         }
 
         InvalidateLineMap ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
@@ -911,7 +930,7 @@ public class TreeView<T> : View, ITreeView where T : class
 
     /// <summary>
     ///     Returns the index of the object <paramref name="o"/> if it is currently exposed (it's parent(s) have been
-    ///     expanded). This can be used with <see cref="ScrollOffsetVertical"/> and <see cref="View.SetNeedsDisplay()"/> to
+    ///     expanded). This can be used with <see cref="ScrollOffsetVertical"/> and <see cref="View.SetNeedsDraw()"/> to
     ///     scroll to a specific object.
     /// </summary>
     /// <remarks>Uses the Equals method and returns the first index at which the object is found or -1 if it is not found.</remarks>
@@ -947,7 +966,7 @@ public class TreeView<T> : View, ITreeView where T : class
 
         SelectedObject = toSelect;
         EnsureVisible (toSelect);
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Changes the <see cref="SelectedObject"/> to the last object in the tree and scrolls so that it is visible.</summary>
@@ -957,7 +976,7 @@ public class TreeView<T> : View, ITreeView where T : class
         ScrollOffsetVertical = Math.Max (0, map.Count - Viewport.Height + 1);
         SelectedObject = map.LastOrDefault ()?.Model;
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
@@ -969,7 +988,7 @@ public class TreeView<T> : View, ITreeView where T : class
         ScrollOffsetVertical = 0;
         SelectedObject = roots.Keys.FirstOrDefault ();
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Clears any cached results of the tree state.</summary>
@@ -1022,7 +1041,7 @@ public class TreeView<T> : View, ITreeView where T : class
         if (me.Flags == MouseFlags.WheeledRight)
         {
             ScrollOffsetHorizontal++;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -1030,7 +1049,7 @@ public class TreeView<T> : View, ITreeView where T : class
         if (me.Flags == MouseFlags.WheeledLeft)
         {
             ScrollOffsetHorizontal--;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -1079,7 +1098,7 @@ public class TreeView<T> : View, ITreeView where T : class
                 multiSelectedRegions.Clear ();
             }
 
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             return true;
         }
@@ -1098,7 +1117,7 @@ public class TreeView<T> : View, ITreeView where T : class
             // Double click changes the selection to the clicked node as well as triggering
             // activation otherwise it feels wierd
             SelectedObject = clickedBranch.Model;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             // trigger activation event
             OnObjectActivated (new ObjectActivatedEventArgs<T> (this, clickedBranch.Model));
@@ -1127,19 +1146,19 @@ public class TreeView<T> : View, ITreeView where T : class
     public event EventHandler<ObjectActivatedEventArgs<T>> ObjectActivated;
 
     ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    protected override bool OnDrawingContent (Rectangle viewport)
     {
         if (roots is null)
         {
-            return;
+            return true;
         }
 
         if (TreeBuilder is null)
         {
             Move (0, 0);
-            Driver.AddStr (NoBuilderError);
+            Driver?.AddStr (NoBuilderError);
 
-            return;
+            return true;
         }
 
         IReadOnlyCollection<Branch<T>> map = BuildLineMap ();
@@ -1158,10 +1177,12 @@ public class TreeView<T> : View, ITreeView where T : class
             {
                 // Else clear the line to prevent stale symbols due to scrolling etc
                 Move (0, line);
-                Driver.SetAttribute (GetNormalColor ());
-                Driver.AddStr (new string (' ', Viewport.Width));
+                SetAttribute (GetNormalColor ());
+                Driver?.AddStr (new string (' ', Viewport.Width));
             }
         }
+
+        return true;
     }
 
     ///<inheritdoc/>
@@ -1200,7 +1221,7 @@ public class TreeView<T> : View, ITreeView where T : class
             {
                 SelectedObject = map.ElementAt ((int)newIndex).Model;
                 EnsureVisible (selectedObject);
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
 
                 return true;
             }
@@ -1241,7 +1262,7 @@ public class TreeView<T> : View, ITreeView where T : class
         }
 
         InvalidateLineMap ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
@@ -1262,7 +1283,7 @@ public class TreeView<T> : View, ITreeView where T : class
         {
             branch.Refresh (startAtTop);
             InvalidateLineMap ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -1276,7 +1297,7 @@ public class TreeView<T> : View, ITreeView where T : class
         {
             roots.Remove (o);
             InvalidateLineMap ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
 
             if (Equals (SelectedObject, o))
             {
@@ -1291,7 +1312,7 @@ public class TreeView<T> : View, ITreeView where T : class
         if (ScrollOffsetVertical <= ContentHeight - 2)
         {
             ScrollOffsetVertical++;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -1301,7 +1322,7 @@ public class TreeView<T> : View, ITreeView where T : class
         if (scrollOffsetVertical > 0)
         {
             ScrollOffsetVertical--;
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -1323,7 +1344,7 @@ public class TreeView<T> : View, ITreeView where T : class
         }
 
         multiSelectedRegions.Push (new TreeSelection<T> (map.ElementAt (0), map.Count, map));
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
 
         OnSelectionChanged (new SelectionChangedEventArgs<T> (this, SelectedObject, SelectedObject));
     }
@@ -1368,7 +1389,7 @@ public class TreeView<T> : View, ITreeView where T : class
         }
 
         InvalidateLineMap ();
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>
@@ -1396,7 +1417,7 @@ public class TreeView<T> : View, ITreeView where T : class
             {
                 SelectedObject = parent;
                 AdjustSelection (0);
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
             }
         }
     }
@@ -1529,7 +1550,7 @@ public class TreeView<T> : View, ITreeView where T : class
             {
                 SelectedObject = map.ElementAt (idxCur).Model;
                 EnsureVisible (map.ElementAt (idxCur).Model);
-                SetNeedsDisplay ();
+                SetNeedsDraw ();
 
                 return;
             }
@@ -1593,4 +1614,5 @@ internal class TreeSelection<T> where T : class
 
     public Branch<T> Origin { get; }
     public bool Contains (T model) { return included.Contains (model); }
+
 }

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

@@ -51,6 +51,6 @@ public class TreeViewTextFilter<T> : ITreeViewFilter<T> where T : class
     private void RefreshTreeView ()
     {
         _forTree.InvalidateLineMap ();
-        _forTree.SetNeedsDisplay ();
+        _forTree.SetNeedsDraw ();
     }
 }

+ 0 - 3
Terminal.Gui/Views/Wizard/Wizard.cs

@@ -546,9 +546,6 @@ public class Wizard : Dialog
         SizeStep (CurrentStep);
 
         SetNeedsLayout ();
-        LayoutSubviews ();
-
-        //Draw ();
     }
 
     private void Wizard_Closing (object sender, ToplevelClosingEventArgs obj)

+ 5 - 5
Terminal.Gui/Views/Wizard/WizardStep.cs

@@ -29,7 +29,7 @@ public class WizardStep : View
     //			OnTitleChanged (old, title);
     //		}
     //		base.Title = value;
-    //		SetNeedsDisplay ();
+    //		SetNeedsDraw ();
     //	}
     //}
 
@@ -73,7 +73,7 @@ public class WizardStep : View
         //	if (helpTextView.TopRow != scrollBar.Position) {
         //		scrollBar.Position = helpTextView.TopRow;
         //	}
-        //	helpTextView.SetNeedsDisplay ();
+        //	helpTextView.SetNeedsDraw ();
         //};
 
         //scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => {
@@ -81,7 +81,7 @@ public class WizardStep : View
         //	if (helpTextView.LeftColumn != scrollBar.OtherScrollBarView.Position) {
         //		scrollBar.OtherScrollBarView.Position = helpTextView.LeftColumn;
         //	}
-        //	helpTextView.SetNeedsDisplay ();
+        //	helpTextView.SetNeedsDraw ();
         //};
 
         //scrollBar.VisibleChanged += (s,e) => {
@@ -130,7 +130,7 @@ public class WizardStep : View
         {
             _helpTextView.Text = value;
             ShowHide ();
-            SetNeedsDisplay ();
+            SetNeedsDraw ();
         }
     }
 
@@ -158,7 +158,7 @@ public class WizardStep : View
     /// <remarks></remarks>
     public override View Remove (View view)
     {
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
         View container = view?.SuperView;
 
         if (container == this)

+ 26 - 0
UICatalog/BenchmarkResults.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace UICatalog;
+
+public class BenchmarkResults
+{
+    [JsonInclude]
+    public string Scenario { get; set; }
+
+    [JsonInclude]
+    public TimeSpan Duration { get; set; }
+
+    [JsonInclude]
+    public int IterationCount { get; set; } = 0;
+    [JsonInclude]
+    public int ClearedContentCount { get; set; } = 0;
+    [JsonInclude]
+    public int RefreshedCount { get; set; } = 0;
+    [JsonInclude]
+    public int UpdatedCount { get; set; } = 0;
+    [JsonInclude]
+    public int DrawCompleteCount { get; set; } = 0;
+    [JsonInclude]
+    public int LaidOutCount { get; set; } = 0;
+}

+ 1 - 1
UICatalog/KeyBindingsDialog.cs

@@ -103,7 +103,7 @@ internal class KeyBindingsDialog : Dialog
             _keyLabel.Text = "Key: None";
         }
 
-        SetNeedsDisplay ();
+        SetNeedsDraw ();
     }
 
     /// <summary>Tracks views as they are created in UICatalog so that their keybindings can be managed.</summary>

+ 14 - 40
UICatalog/Properties/launchSettings.json

@@ -11,7 +11,7 @@
       "commandName": "Project",
       "commandLineArgs": "--driver WindowsDriver"
     },
-    "WSL : UICatalog": {
+    "WSL: UICatalog": {
       "commandName": "Executable",
       "executablePath": "wsl",
       "commandLineArgs": "dotnet UICatalog.dll",
@@ -23,56 +23,30 @@
       "commandLineArgs": "dotnet UICatalog.dll --driver NetDriver",
       "distributionName": ""
     },
-    "Sliders": {
+    "Benchmark All": {
       "commandName": "Project",
-      "commandLineArgs": "Sliders"
+      "commandLineArgs": "--benchmark"
     },
-    "Wizards": {
+    "Benchmark All --driver NetDriver": {
       "commandName": "Project",
-      "commandLineArgs": "Wizards"
+      "commandLineArgs": "--driver NetDriver --benchmark"
     },
-    "Dialogs": {
-      "commandName": "Project",
-      "commandLineArgs": "Dialogs"
-    },
-    "Buttons": {
-      "commandName": "Project",
-      "commandLineArgs": "Buttons"
-    },
-    "WizardAsView": {
-      "commandName": "Project",
-      "commandLineArgs": "WizardAsView"
-    },
-    "CollectionNavigatorTester": {
-      "commandName": "Project",
-      "commandLineArgs": "\"Search Collection Nav\""
-    },
-    "Charmap": {
-      "commandName": "Project",
-      "commandLineArgs": "\"Character Map\""
-    },
-    "All Views Tester": {
-      "commandName": "Project",
-      "commandLineArgs": "\"All Views Tester\""
-    },
-    "Windows & FrameViews": {
-      "commandName": "Project",
-      "commandLineArgs": "\"Windows & FrameViews\""
+    "WSL: Benchmark All": {
+      "commandName": "Executable",
+      "executablePath": "wsl",
+      "commandLineArgs": "dotnet UICatalog.dll --benchmark",
+      "distributionName": ""
     },
     "Docker": {
       "commandName": "Docker"
     },
-    "MenuBarScenario": {
-      "commandName": "Project",
-      "commandLineArgs": "MenuBar"
-    },
-    "ListView & ComboBox": {
+    "All Views Tester": {
       "commandName": "Project",
-      "commandLineArgs": "\"ListView & ComboBox\""
+      "commandLineArgs": "\"All Views Tester\" -b"
     },
-    "Frames Demo": {
+    "Charmap": {
       "commandName": "Project",
-      "commandLineArgs": "\"Frames Demo\""
+      "commandLineArgs": "\"Character Map\" -b"
     }
   }
 }

+ 151 - 54
UICatalog/Scenario.cs

@@ -1,6 +1,8 @@
-using System;
+#nullable enable
+using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Diagnostics;
 using System.Linq;
 using Terminal.Gui;
 
@@ -20,12 +22,12 @@ namespace UICatalog;
 ///             <item>
 ///                 <description>
 ///                     Annotate the <see cref="Scenario"/> derived class with a
-///                     <see cref="Scenario.ScenarioMetadata"/> attribute specifying the scenario's name and description.
+///                     <see cref="ScenarioMetadata"/> attribute specifying the scenario's name and description.
 ///                 </description>
 ///             </item>
 ///             <item>
 ///                 <description>
-///                     Add one or more <see cref="Scenario.ScenarioCategory"/> attributes to the class specifying
+///                     Add one or more <see cref="ScenarioCategory"/> attributes to the class specifying
 ///                     which categories the scenario belongs to. If you don't specify a category the scenario will show up
 ///                     in "_All".
 ///                 </description>
@@ -82,7 +84,13 @@ namespace UICatalog;
 public class Scenario : IDisposable
 {
     private static int _maxScenarioNameLen = 30;
-    public string TopLevelColorScheme = "Base";
+    public string TopLevelColorScheme { get; set; } = "Base";
+
+    public BenchmarkResults BenchmarkResults
+    {
+        get { return _benchmarkResults; }
+    }
+
     private bool _disposedValue;
 
     /// <summary>
@@ -114,16 +122,19 @@ public class Scenario : IDisposable
     /// </summary>
     public static ObservableCollection<Scenario> GetScenarios ()
     {
-        List<Scenario> objects = new ();
+        List<Scenario> objects = [];
 
         foreach (Type type in typeof (Scenario).Assembly.ExportedTypes
                                                .Where (
-                                                       myType => myType.IsClass
-                                                                 && !myType.IsAbstract
+                                                       myType => myType is { IsClass: true, IsAbstract: false }
                                                                  && myType.IsSubclassOf (typeof (Scenario))
                                                       ))
         {
-            var scenario = (Scenario)Activator.CreateInstance (type);
+            if (Activator.CreateInstance (type) is not Scenario { } scenario)
+            {
+                continue;
+            }
+
             objects.Add (scenario);
             _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
         }
@@ -137,6 +148,135 @@ public class Scenario : IDisposable
     /// </summary>
     public virtual void Main () { }
 
+    private const uint MAX_NATURAL_ITERATIONS = 500; // not including needed for demo keys
+    private const uint ABORT_TIMEOUT_MS = 2500;
+    private const int DEMO_KEY_PACING_MS = 1; // Must be non-zero
+
+    private readonly object _timeoutLock = new ();
+    private object? _timeout;
+    private Stopwatch? _stopwatch;
+    private readonly BenchmarkResults _benchmarkResults = new BenchmarkResults ();
+
+    public void StartBenchmark ()
+    {
+        BenchmarkResults.Scenario = GetName ();
+        Application.InitializedChanged += OnApplicationOnInitializedChanged;
+    }
+
+    public BenchmarkResults EndBenchmark ()
+    {
+        Application.InitializedChanged -= OnApplicationOnInitializedChanged;
+
+        lock (_timeoutLock)
+        {
+            if (_timeout is { })
+            {
+                _timeout = null;
+            }
+        }
+
+        return _benchmarkResults;
+    }
+
+    private List<Key> _demoKeys;
+    private int _currentDemoKey = 0;
+
+    private void OnApplicationOnInitializedChanged (object? s, EventArgs<bool> a)
+    {
+        if (a.CurrentValue)
+        {
+            lock (_timeoutLock!)
+            {
+                _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (ABORT_TIMEOUT_MS), ForceCloseCallback);
+            }
+
+            Application.Iteration += OnApplicationOnIteration;
+            Application.Driver!.ClearedContents += (sender, args) => BenchmarkResults.ClearedContentCount++;
+            Application.Driver!.Refreshed += (sender, args) =>
+            {
+                BenchmarkResults.RefreshedCount++;
+
+                if (args.CurrentValue)
+                {
+                    BenchmarkResults.UpdatedCount++;
+                }
+            };
+            Application.NotifyNewRunState += OnApplicationNotifyNewRunState;
+
+
+            _stopwatch = Stopwatch.StartNew ();
+        }
+        else
+        {
+            Application.NotifyNewRunState -= OnApplicationNotifyNewRunState;
+            Application.Iteration -= OnApplicationOnIteration;
+            BenchmarkResults.Duration = _stopwatch!.Elapsed;
+            _stopwatch?.Stop ();
+        }
+    }
+
+    private void OnApplicationOnIteration (object? s, IterationEventArgs a)
+    {
+        BenchmarkResults.IterationCount++;
+        if (BenchmarkResults.IterationCount > MAX_NATURAL_ITERATIONS + (_demoKeys.Count* DEMO_KEY_PACING_MS))
+        {
+            Application.RequestStop ();
+        }
+    }
+
+    private void OnApplicationNotifyNewRunState (object? sender, RunStateEventArgs e)
+    {
+        // Get a list of all subviews under Application.Top (and their subviews, etc.)
+        // and subscribe to their DrawComplete event
+        void SubscribeAllSubviews (View view)
+        {
+            view.DrawComplete += (s, a) => BenchmarkResults.DrawCompleteCount++;
+            view.SubviewsLaidOut += (s, a) => BenchmarkResults.LaidOutCount++;
+            foreach (View subview in view.Subviews)
+            {
+                SubscribeAllSubviews (subview);
+            }
+        }
+
+        SubscribeAllSubviews (Application.Top!);
+
+        _currentDemoKey = 0;
+        _demoKeys = GetDemoKeyStrokes ();
+
+        Application.AddTimeout (
+                                new TimeSpan (0, 0, 0, 0, DEMO_KEY_PACING_MS),
+                                () =>
+                                {
+                                    if (_currentDemoKey >= _demoKeys.Count)
+                                    {
+                                        return false;
+                                    }
+
+                                    Application.RaiseKeyDownEvent (_demoKeys [_currentDemoKey++]);
+
+                                    return true;
+                                });
+
+    }
+
+    // If the scenario doesn't close within the abort time, this will force it to quit
+    private bool ForceCloseCallback ()
+    {
+        lock (_timeoutLock)
+        {
+            if (_timeout is { })
+            {
+                _timeout = null;
+            }
+        }
+
+        Debug.WriteLine ($@"  Failed to Quit with {Application.QuitKey} after {ABORT_TIMEOUT_MS}ms and {BenchmarkResults.IterationCount} iterations. Force quit.");
+
+        Application.RequestStop ();
+
+        return false;
+    }
+
     /// <summary>Gets the Scenario Name + Description with the Description padded based on the longest known Scenario name.</summary>
     /// <returns></returns>
     public override string ToString () { return $"{GetName ().PadRight (_maxScenarioNameLen)}{GetDescription ()}"; }
@@ -170,8 +310,7 @@ public class Scenario : IDisposable
 
         aCategories = typeof (Scenario).Assembly.GetTypes ()
                                        .Where (
-                                               myType => myType.IsClass
-                                                         && !myType.IsAbstract
+                                               myType => myType is { IsClass: true, IsAbstract: false }
                                                          && myType.IsSubclassOf (typeof (Scenario)))
                                        .Select (type => System.Attribute.GetCustomAttributes (type).ToList ())
                                        .Aggregate (
@@ -189,51 +328,9 @@ public class Scenario : IDisposable
         categories.Insert (0, "All Scenarios");
 
         return categories;
-    }
 
-    /// <summary>Defines the category names used to categorize a <see cref="Scenario"/></summary>
-    [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)]
-    public class ScenarioCategory (string name) : System.Attribute
-    {
-        /// <summary>Static helper function to get the <see cref="Scenario"/> Categories given a Type</summary>
-        /// <param name="t"></param>
-        /// <returns>list of category names</returns>
-        public static List<string> GetCategories (Type t)
-        {
-            return GetCustomAttributes (t)
-                   .ToList ()
-                   .Where (a => a is ScenarioCategory)
-                   .Select (a => ((ScenarioCategory)a).Name)
-                   .ToList ();
-        }
-
-        /// <summary>Static helper function to get the <see cref="Scenario"/> Name given a Type</summary>
-        /// <param name="t"></param>
-        /// <returns>Name of the category</returns>
-        public static string GetName (Type t) { return ((ScenarioCategory)GetCustomAttributes (t) [0]).Name; }
-
-        /// <summary>Category Name</summary>
-        public string Name { get; set; } = name;
     }
 
-    /// <summary>Defines the metadata (Name and Description) for a <see cref="Scenario"/></summary>
-    [AttributeUsage (AttributeTargets.Class)]
-    public class ScenarioMetadata (string name, string description) : System.Attribute
-    {
-        /// <summary><see cref="Scenario"/> Description</summary>
-        public string Description { get; set; } = description;
+    public virtual List<Key> GetDemoKeyStrokes () => new List<Key> ();
 
-        /// <summary>Static helper function to get the <see cref="Scenario"/> Description given a Type</summary>
-        /// <param name="t"></param>
-        /// <returns></returns>
-        public static string GetDescription (Type t) { return ((ScenarioMetadata)GetCustomAttributes (t) [0]).Description; }
-
-        /// <summary>Static helper function to get the <see cref="Scenario"/> Name given a Type</summary>
-        /// <param name="t"></param>
-        /// <returns></returns>
-        public static string GetName (Type t) { return ((ScenarioMetadata)GetCustomAttributes (t) [0]).Name; }
-
-        /// <summary><see cref="Scenario"/> Name</summary>
-        public string Name { get; set; } = name;
-    }
-}
+}

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