Преглед на файлове

Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop

Tig преди 9 месеца
родител
ревизия
5c69dfd2f7
променени са 100 файла, в които са добавени 4671 реда и са изтрити 3081 реда
  1. 4 1
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 2 2
      CommunityToolkitExample/LoginView.cs
  3. 1 1
      Example/Example.cs
  4. 1 1
      NativeAot/Program.cs
  5. 1 1
      NativeAot/Properties/launchSettings.json
  6. 3 3
      README.md
  7. 2 2
      ReactiveExample/LoginView.cs
  8. 1 1
      SelfContained/Program.cs
  9. 1 1
      Terminal.Gui/Application/Application.Driver.cs
  10. 25 3
      Terminal.Gui/Application/Application.Initialization.cs
  11. 61 56
      Terminal.Gui/Application/Application.Keyboard.cs
  12. 160 109
      Terminal.Gui/Application/Application.Mouse.cs
  13. 51 300
      Terminal.Gui/Application/Application.Run.cs
  14. 0 6
      Terminal.Gui/Application/Application.Screen.cs
  15. 1 43
      Terminal.Gui/Application/Application.Toplevel.cs
  16. 2 4
      Terminal.Gui/Application/Application.cs
  17. 3 1
      Terminal.Gui/Application/ApplicationNavigation.cs
  18. 0 444
      Terminal.Gui/Application/ApplicationOverlapped.cs
  19. 1 1
      Terminal.Gui/Configuration/AppScope.cs
  20. 18 5
      Terminal.Gui/Configuration/ColorJsonConverter.cs
  21. 14 5
      Terminal.Gui/Configuration/ConfigurationManager.cs
  22. 24 17
      Terminal.Gui/Configuration/RuneJsonConverter.cs
  23. 25 8
      Terminal.Gui/Configuration/SettingsScope.cs
  24. 3 1
      Terminal.Gui/Configuration/SourceGenerationContext.cs
  25. 1 1
      Terminal.Gui/Configuration/ThemeManager.cs
  26. 8 1
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  27. 39 39
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  28. 3 3
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  29. 2 2
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  30. 3 1
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  31. 55 53
      Terminal.Gui/Drawing/Alignment.cs
  32. 8 14
      Terminal.Gui/Drawing/AlignmentModes.cs
  33. 6 6
      Terminal.Gui/Drawing/Attribute.cs
  34. 47 44
      Terminal.Gui/Drawing/Color.ColorExtensions.cs
  35. 2 2
      Terminal.Gui/Drawing/Color.ColorName.cs
  36. 180 177
      Terminal.Gui/Drawing/Color.Formatting.cs
  37. 3 3
      Terminal.Gui/Drawing/Color.Operators.cs
  38. 40 40
      Terminal.Gui/Drawing/Color.cs
  39. 43 55
      Terminal.Gui/Drawing/ColorScheme.cs
  40. 30 27
      Terminal.Gui/Drawing/ColorStrings.cs
  41. 28 5
      Terminal.Gui/Drawing/Glyphs.cs
  42. 2 2
      Terminal.Gui/FileServices/DefaultFileOperations.cs
  43. 191 147
      Terminal.Gui/Input/Command.cs
  44. 11 1
      Terminal.Gui/Input/CommandContext.cs
  45. 15 0
      Terminal.Gui/Input/CommandEventArgs.cs
  46. 1 1
      Terminal.Gui/Input/GrabMouseEventArgs.cs
  47. 93 47
      Terminal.Gui/Input/Key.cs
  48. 3 0
      Terminal.Gui/Input/KeyBinding.cs
  49. 30 21
      Terminal.Gui/Input/KeyBindingScope.cs
  50. 17 5
      Terminal.Gui/Input/KeyBindings.cs
  51. 63 0
      Terminal.Gui/Resources/Strings.Designer.cs
  52. 21 0
      Terminal.Gui/Resources/Strings.resx
  53. 324 38
      Terminal.Gui/Resources/config.json
  54. 1 0
      Terminal.Gui/Terminal.Gui.csproj
  55. 35 35
      Terminal.Gui/View/Adornment/Adornment.cs
  56. 915 173
      Terminal.Gui/View/Adornment/Border.cs
  57. 15 6
      Terminal.Gui/View/Adornment/Margin.cs
  58. 1 1
      Terminal.Gui/View/Adornment/Padding.cs
  59. 1 1
      Terminal.Gui/View/Adornment/ShadowView.cs
  60. 9 8
      Terminal.Gui/View/HighlightStyle.cs
  61. 42 0
      Terminal.Gui/View/Layout/DimAuto.cs
  62. 1 1
      Terminal.Gui/View/Layout/DimAutoStyle.cs
  63. 5 7
      Terminal.Gui/View/Navigation/TabBehavior.cs
  64. 29 18
      Terminal.Gui/View/View.Adornments.cs
  65. 2 3
      Terminal.Gui/View/View.Arrangement.cs
  66. 353 0
      Terminal.Gui/View/View.Command.cs
  67. 12 0
      Terminal.Gui/View/View.Content.cs
  68. 2 3
      Terminal.Gui/View/View.Diagnostics.cs
  69. 112 64
      Terminal.Gui/View/View.Drawing.cs
  70. 11 6
      Terminal.Gui/View/View.Hierarchy.cs
  71. 35 144
      Terminal.Gui/View/View.Keyboard.cs
  72. 65 101
      Terminal.Gui/View/View.Layout.cs
  73. 367 231
      Terminal.Gui/View/View.Mouse.cs
  74. 152 57
      Terminal.Gui/View/View.Navigation.cs
  75. 87 74
      Terminal.Gui/View/View.cs
  76. 15 12
      Terminal.Gui/View/ViewArrangement.cs
  77. 14 23
      Terminal.Gui/View/ViewportSettings.cs
  78. 56 9
      Terminal.Gui/Views/Bar.cs
  79. 102 39
      Terminal.Gui/Views/Button.cs
  80. 123 63
      Terminal.Gui/Views/CheckBox.cs
  81. 2 2
      Terminal.Gui/Views/ColorBar.cs
  82. 3 3
      Terminal.Gui/Views/ColorPicker.cs
  83. 9 9
      Terminal.Gui/Views/ColorPicker16.cs
  84. 46 36
      Terminal.Gui/Views/ComboBox.cs
  85. 3 3
      Terminal.Gui/Views/DateField.cs
  86. 2 3
      Terminal.Gui/Views/DatePicker.cs
  87. 1 12
      Terminal.Gui/Views/Dialog.cs
  88. 13 13
      Terminal.Gui/Views/FileDialog.cs
  89. 3 1
      Terminal.Gui/Views/FrameView.cs
  90. 62 24
      Terminal.Gui/Views/HexView.cs
  91. 44 17
      Terminal.Gui/Views/Label.cs
  92. 81 47
      Terminal.Gui/Views/ListView.cs
  93. 1 1
      Terminal.Gui/Views/Menu/ContextMenu.cs
  94. 19 19
      Terminal.Gui/Views/Menu/Menu.cs
  95. 70 32
      Terminal.Gui/Views/Menu/MenuBar.cs
  96. 1 1
      Terminal.Gui/Views/Menu/MenuItem.cs
  97. 38 2
      Terminal.Gui/Views/Menuv2.cs
  98. 18 16
      Terminal.Gui/Views/MessageBox.cs
  99. 14 6
      Terminal.Gui/Views/NumericUpDown.cs
  100. 6 4
      Terminal.Gui/Views/ProgressBar.cs

+ 4 - 1
.github/ISSUE_TEMPLATE/bug_report.md

@@ -2,7 +2,7 @@
 name: Bug report
 about: Create a report to help us improve
 title: ''
-labels: ''
+labels: bug
 assignees: ''
 
 ---
@@ -36,3 +36,6 @@ If applicable, add screenshots to help explain your problem.
 
 **Additional context**
 Add any other context about the problem here.
+
+**Set Project & Milestone**
+If you have access, please don't forget to set the right Project and Milestone.

+ 2 - 2
CommunityToolkitExample/LoginView.cs

@@ -19,13 +19,13 @@ internal partial class LoginView : IRecipient<Message<LoginActions>>
                                      {
                                          ViewModel.Password = passwordInput.Text;
                                      };
-        loginButton.Accept += (_, _) =>
+        loginButton.Accepting += (_, _) =>
                               {
                                   if (!ViewModel.CanLogin) { return; }
                                   ViewModel.LoginCommand.Execute (null);
                               };
 
-        clearButton.Accept += (_, _) =>
+        clearButton.Accepting += (_, _) =>
                               {
                                   ViewModel.ClearCommand.Execute (null);
                               };

+ 1 - 1
Example/Example.cs

@@ -63,7 +63,7 @@ public class ExampleWindow : Window
         };
 
         // When login button is clicked display a message popup
-        btnLogin.Accept += (s, e) =>
+        btnLogin.Accepting += (s, e) =>
                            {
                                if (userNameText.Text == "admin" && passwordText.Text == "password")
                                {

+ 1 - 1
NativeAot/Program.cs

@@ -93,7 +93,7 @@ public class ExampleWindow : Window
         };
 
         // When login button is clicked display a message popup
-        btnLogin.Accept += (s, e) =>
+        btnLogin.Accepting += (s, e) =>
         {
             if (userNameText.Text == "admin" && passwordText.Text == "password")
             {

+ 1 - 1
NativeAot/Properties/launchSettings.json

@@ -3,7 +3,7 @@
     "NativeAot": {
       "commandName": "Project"
     },
-    "WSL : UICatalog": {
+    "WSL : NativeAot": {
       "commandName": "Executable",
       "executablePath": "wsl",
       "commandLineArgs": "dotnet NativeAot.dll",

+ 3 - 3
README.md

@@ -8,7 +8,7 @@
 
 * The current, stable, release of Terminal.Gui v1 is [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui).
 * The current `prealpha` release of Terminal.Gui v2 can be found on [Nuget](https://www.nuget.org/packages/Terminal.Gui).
-* Developers starting new TUI projects are encouraged to target `v2`. The API is signifcantly changed, and significantly improved. There will be breaking changes in the API before Beta, but the core API is stable.
+* Developers starting new TUI projects are encouraged to target `v2`. The API is significantly changed, and significantly improved. There will be breaking changes in the API before Beta, but the core API is stable.
 * `v1` is in maintenance mode and we will only accept PRs for issues impacting existing functionality.
  
 **Terminal.Gui**: A toolkit for building rich console apps for .NET, .NET Core, and Mono that works on Windows, the Mac, and Linux/Unix.
@@ -35,9 +35,9 @@ dotnet run
 * [API Documentation](https://gui-cs.github.io/Terminal.GuiV2Docs/api/Terminal.Gui.html)
 * [Documentation Home](https://gui-cs.github.io/Terminal.GuiV2Docs)
 
-The above documentation matches the most recent Nuget release from the `v2_develop` branch. Get the [v1 documentation here](This is the v2 API documentation. For v1 go here: https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.html)
+The above documentation matches the most recent Nuget release from the `v2_develop` branch. Get the [v1 documentation here](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.html).
 
-See the [`Terminal.Gui/` README](https://github.com/gui-cs/Terminal.Gui/tree/master/Terminal.Gui) for an overview of how the library is structured. 
+See the [`Terminal.Gui/`README](https://github.com/gui-cs/Terminal.Gui/tree/master/Terminal.Gui) for an overview of how the library is structured. 
 
 ## Showcase & Examples
 

+ 2 - 2
ReactiveExample/LoginView.cs

@@ -108,7 +108,7 @@ public class LoginView : Window, IViewFor<LoginViewModel>
 
                 login
                     .Events ()
-                    .Accept
+                    .Accepting
                     .InvokeCommand (ViewModel, x => x.Login)
                     .DisposeWith (_disposable);
             })
@@ -120,7 +120,7 @@ public class LoginView : Window, IViewFor<LoginViewModel>
 
                 clear
                     .Events ()
-                    .Accept
+                    .Accepting
                     .InvokeCommand (ViewModel, x => x.ClearCommand)
                     .DisposeWith (_disposable);
             })

+ 1 - 1
SelfContained/Program.cs

@@ -92,7 +92,7 @@ public class ExampleWindow : Window
         };
 
         // When login button is clicked display a message popup
-        btnLogin.Accept += (s, e) =>
+        btnLogin.Accepting += (s, e) =>
                            {
                                if (userNameText.Text == "admin" && passwordText.Text == "password")
                                {

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

@@ -10,7 +10,7 @@ public static partial class Application // Driver abstractions
 
     /// <summary>
     ///     Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in
-    ///     <see cref="ColorName"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be output
+    ///     <see cref="ColorName16"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be output
     ///     as long as the selected <see cref="ConsoleDriver"/> supports TrueColor.
     /// </summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]

+ 25 - 3
Terminal.Gui/Application/Application.Initialization.cs

@@ -1,4 +1,5 @@
 #nullable enable
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 
@@ -71,7 +72,7 @@ public static partial class Application // Initialization (Init/Shutdown)
         if (!calledViaRunT)
         {
             // Reset all class variables (Application is a singleton).
-            ResetState ();
+            ResetState (ignoreDisposed: true);
         }
 
         Navigation = new ();
@@ -80,6 +81,16 @@ public static partial class Application // Initialization (Init/Shutdown)
         if (driver is { })
         {
             Driver = driver;
+
+            if (driver is FakeDriver)
+            {
+                // We're running unit tests. Disable loading config files other than default
+                if (Locations == ConfigLocations.All)
+                {
+                    Locations = ConfigLocations.DefaultOnly;
+                    Reset ();
+                }
+            }
         }
 
         // Start the process of configuration management.
@@ -88,7 +99,12 @@ public static partial class Application // Initialization (Init/Shutdown)
         // valid after a Driver is loaded. In this case we need just
         // `Settings` so we can determine which driver to use.
         // Don't reset, so we can inherit the theme from the previous run.
+        string previousTheme = Themes?.Theme ?? string.Empty;
         Load ();
+        if (Themes is { } && !string.IsNullOrEmpty (previousTheme) && previousTheme != "Default")
+        {
+            ThemeManager.SelectedTheme = previousTheme;
+        }
         Apply ();
 
         AddApplicationKeyBindings ();
@@ -198,10 +214,16 @@ public static partial class Application // Initialization (Init/Shutdown)
     public static void Shutdown ()
     {
         // TODO: Throw an exception if Init hasn't been called.
+
+        bool wasInitialized = IsInitialized;
         ResetState ();
         PrintJsonErrors ();
-        bool init = IsInitialized;
-        InitializedChanged?.Invoke (null, new (in init));
+
+        if (wasInitialized)
+        {
+            bool init = IsInitialized;
+            InitializedChanged?.Invoke (null, new (in init));
+        }
     }
 
     /// <summary>

+ 61 - 56
Terminal.Gui/Application/Application.Keyboard.cs

@@ -5,12 +5,10 @@ public static partial class Application // Keyboard handling
 {
     private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrrides
     private static Key _nextTabKey = Key.Tab; // Resources/config.json overrrides
-
     private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrrides
-
     private static Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrrides
-
     private static Key _quitKey = Key.Esc; // Resources/config.json overrrides
+    private static Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrrides
 
     static Application () { AddApplicationKeyBindings (); }
 
@@ -97,7 +95,7 @@ public static partial class Application // Keyboard handling
             return true;
         }
 
-        if (Current is null)
+        if (Top is null)
         {
             foreach (Toplevel topLevel in TopLevels.ToList ())
             {
@@ -114,7 +112,7 @@ public static partial class Application // Keyboard handling
         }
         else
         {
-            if (Current.NewKeyDownEvent (keyEvent))
+            if (Top.NewKeyDownEvent (keyEvent))
             {
                 return true;
             }
@@ -155,7 +153,7 @@ public static partial class Application // Keyboard handling
     }
 
     /// <summary>
-    /// INTENRAL method to invoke one of the commands in <see cref="CommandImplementations"/>
+    ///     INTENRAL method to invoke one of the commands in <see cref="CommandImplementations"/>
     /// </summary>
     /// <param name="command"></param>
     /// <param name="keyEvent"></param>
@@ -171,9 +169,10 @@ public static partial class Application // Keyboard handling
                                             );
         }
 
-        if (CommandImplementations.TryGetValue (command, out Func<CommandContext, bool?>? implementation))
+        if (CommandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
         {
             var context = new CommandContext (command, keyEvent, appBinding); // Create the context here
+
             return implementation (context);
         }
 
@@ -262,24 +261,31 @@ public static partial class Application // Keyboard handling
         }
     }
 
+    /// <summary>Gets or sets the key to activate arranging views using the keyboard.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static Key ArrangeKey
+    {
+        get => _arrangeKey;
+        set
+        {
+            if (_arrangeKey != value)
+            {
+                ReplaceKey (_arrangeKey, value);
+                _arrangeKey = value;
+            }
+        }
+    }
+
     internal static void AddApplicationKeyBindings ()
     {
         CommandImplementations = new ();
 
         // Things this view knows how to do
         AddCommand (
-                    Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic.
+                    Command.Quit,
                     static () =>
                     {
-                        if (ApplicationOverlapped.OverlappedTop is { })
-                        {
-                            RequestStop (Current!);
-                        }
-                        else
-                        {
-                            RequestStop ();
-                        }
-
+                        RequestStop ();
                         return true;
                     }
                    );
@@ -295,54 +301,50 @@ public static partial class Application // Keyboard handling
                    );
 
         AddCommand (
-                    Command.NextView,
+                    Command.NextTabStop,
                     static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));
 
         AddCommand (
-                    Command.PreviousView,
+                    Command.PreviousTabStop,
                     static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop));
 
         AddCommand (
-                    Command.NextViewOrTop,
-                    static () =>
-                    {
-                        // TODO: This OverlapppedTop tomfoolery goes away in addressing #2491
-                        if (ApplicationOverlapped.OverlappedTop is { })
-                        {
-                            ApplicationOverlapped.OverlappedMoveNext ();
-
-                            return true;
-                        }
+                    Command.NextTabGroup,
+                    static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup));
 
-                        return Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
-                    }
-                   );
+        AddCommand (
+                    Command.PreviousTabGroup,
+                    static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup));
 
         AddCommand (
-                    Command.PreviousViewOrTop,
+                    Command.Refresh,
                     static () =>
                     {
-                        // TODO: This OverlapppedTop tomfoolery goes away in addressing #2491
-                        if (ApplicationOverlapped.OverlappedTop is { })
-                        {
-                            ApplicationOverlapped.OverlappedMovePrevious ();
-
-                            return true;
-                        }
+                        Refresh ();
 
-                        return Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup);
+                        return true;
                     }
                    );
 
         AddCommand (
-                    Command.Refresh,
+                    Command.Edit,
                     static () =>
                     {
-                        Refresh ();
+                        View? viewToArrange = Navigation?.GetFocused ();
 
-                        return true;
-                    }
-                   );
+                        // Go up the superview hierarchy and find the first that is not ViewArrangement.Fixed
+                        while (viewToArrange is { SuperView: { }, Arrangement: ViewArrangement.Fixed })
+                        {
+                            viewToArrange = viewToArrange.SuperView;
+                        }
+
+                        if (viewToArrange is { })
+                        {
+                            return viewToArrange.Border?.EnterArrangeMode (ViewArrangement.Fixed);
+                        }
+
+                        return false;
+                    });
 
         KeyBindings.Clear ();
 
@@ -352,18 +354,21 @@ public static partial class Application // Keyboard handling
         NextTabGroupKey = Key.F6;
         PrevTabGroupKey = Key.F6.WithShift;
         QuitKey = Key.Esc;
+        ArrangeKey = Key.F5.WithCtrl;
+
+        KeyBindings.Add (QuitKey, KeyBindingScope.Application, Command.Quit);
 
-        KeyBindings.Add (QuitKey, KeyBindingScope.Application, Command.QuitToplevel);
+        KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextTabStop);
+        KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextTabStop);
+        KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousTabStop);
+        KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousTabStop);
+        KeyBindings.Add (NextTabKey, KeyBindingScope.Application, Command.NextTabStop);
+        KeyBindings.Add (PrevTabKey, KeyBindingScope.Application, Command.PreviousTabStop);
 
-        KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextView);
-        KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextView);
-        KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousView);
-        KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousView);
-        KeyBindings.Add (NextTabKey, KeyBindingScope.Application, Command.NextView);
-        KeyBindings.Add (PrevTabKey, KeyBindingScope.Application, Command.PreviousView);
+        KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextTabGroup);
+        KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousTabGroup);
 
-        KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextViewOrTop);
-        KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousViewOrTop);
+        KeyBindings.Add (ArrangeKey, KeyBindingScope.Application, Command.Edit);
 
         // TODO: Refresh Key should be configurable
         KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
@@ -413,7 +418,7 @@ public static partial class Application // Keyboard handling
     /// <summary>
     ///     Commands for Application.
     /// </summary>
-    private static Dictionary<Command, Func<CommandContext, bool?>>? CommandImplementations { get; set; }
+    private static Dictionary<Command, View.CommandImplementation>? CommandImplementations { get; set; }
 
     private static void ReplaceKey (Key oldKey, Key newKey)
     {

+ 160 - 109
Terminal.Gui/Application/Application.Mouse.cs

@@ -1,9 +1,17 @@
 #nullable enable
+using System.ComponentModel;
+using System.Diagnostics;
+
 namespace Terminal.Gui;
 
 public static partial class Application // Mouse handling
 {
-    #region Mouse handling
+    internal static Point? _lastMousePosition;
+
+    /// <summary>
+    ///     Gets the most recent position of the mouse.
+    /// </summary>
+    public static Point? GetLastMousePosition () { return _lastMousePosition; }
 
     /// <summary>Disable or enable the mouse. The mouse is enabled by default.</summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
@@ -116,9 +124,6 @@ public static partial class Application // Mouse handling
         UnGrabbedMouse?.Invoke (view, new (view));
     }
 
-    // Used by OnMouseEvent to track the last view that was clicked on.
-    internal static View? MouseEnteredView { get; set; }
-
     /// <summary>Event fired when a mouse move or click occurs. Coordinates are screen relative.</summary>
     /// <remarks>
     ///     <para>
@@ -129,27 +134,35 @@ public static partial class Application // Mouse handling
     /// </remarks>
     public static event EventHandler<MouseEvent>? MouseEvent;
 
-    /// <summary>Called when a mouse event occurs. Raises the <see cref="MouseEvent"/> event.</summary>
+    /// <summary>Called when a mouse event is raised by the driver.</summary>
     /// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
     /// <param name="mouseEvent">The mouse event with coordinates relative to the screen.</param>
     internal static void OnMouseEvent (MouseEvent mouseEvent)
     {
+        _lastMousePosition = mouseEvent.ScreenPosition;
+
         if (IsMouseDisabled)
         {
             return;
         }
 
-        var view = View.FindDeepestView (Current, mouseEvent.Position);
+        // The position of the mouse is the same as the screen position at the application level.
+        //Debug.Assert (mouseEvent.Position == mouseEvent.ScreenPosition);
+        mouseEvent.Position = mouseEvent.ScreenPosition;
+
+        List<View?> currentViewsUnderMouse = View.GetViewsUnderMouse (mouseEvent.ScreenPosition);
+
+        View? deepestViewUnderMouse = currentViewsUnderMouse.LastOrDefault ();
 
-        if (view is { })
+        if (deepestViewUnderMouse is { })
         {
 #if DEBUG_IDISPOSABLE
-            if (view.WasDisposed)
+            if (deepestViewUnderMouse.WasDisposed)
             {
-                throw new ObjectDisposedException (view.GetType ().FullName);
+                throw new ObjectDisposedException (deepestViewUnderMouse.GetType ().FullName);
             }
 #endif
-            mouseEvent.View = view;
+            mouseEvent.View = deepestViewUnderMouse;
         }
 
         MouseEvent?.Invoke (null, mouseEvent);
@@ -159,160 +172,198 @@ public static partial class Application // Mouse handling
             return;
         }
 
-        if (MouseGrabView is { })
+        if (HandleMouseGrab (deepestViewUnderMouse, mouseEvent))
         {
-
-#if DEBUG_IDISPOSABLE
-            if (MouseGrabView.WasDisposed)
-            {
-                throw new ObjectDisposedException (MouseGrabView.GetType ().FullName);
-            }
-#endif
-            // If the mouse is grabbed, send the event to the view that grabbed it.
-            // The coordinates are relative to the Bounds of the view that grabbed the mouse.
-            Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.Position);
-
-            var viewRelativeMouseEvent = new MouseEvent
-            {
-                Position = frameLoc,
-                Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.Position,
-                View = view ?? MouseGrabView
-            };
-
-            if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false)
-            {
-                // The mouse has moved outside the bounds of the view that grabbed the mouse
-                MouseGrabView.NewMouseLeaveEvent (mouseEvent);
-            }
-
-            //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
-            if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
-            {
-                return;
-            }
-
-            // ReSharper disable once ConditionIsAlwaysTrueOrFalse
-            if (MouseGrabView is null && view is Adornment)
-            {
-                // The view that grabbed the mouse has been disposed
-                return;
-            }
+            return;
         }
 
         // We can combine this into the switch expression to reduce cognitive complexity even more and likely
         // avoid one or two of these checks in the process, as well.
-        WantContinuousButtonPressedView = view switch
-                                          {
-                                              { WantContinuousButtonPressed: true } => view,
-                                              _                                     => null
-                                          };
-
-        if (view is not Adornment
-         && (view is null || view == ApplicationOverlapped.OverlappedTop)
-         && Current is { Modal: false }
-         && ApplicationOverlapped.OverlappedTop != null
-         && mouseEvent.Flags is not MouseFlags.ReportMousePosition and not 0)
-        {
-            // This occurs when there are multiple overlapped "tops"
-            // E.g. "Mdi" - in the Background Worker Scenario
-            View? top = ApplicationOverlapped.FindDeepestTop (Top!, mouseEvent.Position);
-            view = View.FindDeepestView (top, mouseEvent.Position);
 
-            if (view is { } && view != ApplicationOverlapped.OverlappedTop && top != Current && top is { })
-            {
-                ApplicationOverlapped.MoveCurrent ((Toplevel)top);
-            }
-        }
+        WantContinuousButtonPressedView = deepestViewUnderMouse switch
+        {
+            { WantContinuousButtonPressed: true } => deepestViewUnderMouse,
+            _ => null
+        };
 
         // May be null before the prior condition or the condition may set it as null.
         // So, the checking must be outside the prior condition.
-        if (view is null)
+        if (deepestViewUnderMouse is null)
         {
             return;
         }
 
-        MouseEvent? me;
+        // Create a view-relative mouse event to send to the view that is under the mouse.
+        MouseEvent? viewMouseEvent;
 
-        if (view is Adornment adornment)
+        if (deepestViewUnderMouse is Adornment adornment)
         {
-            Point frameLoc = adornment.ScreenToFrame (mouseEvent.Position);
+            Point frameLoc = adornment.ScreenToFrame (mouseEvent.ScreenPosition);
 
-            me = new ()
+            viewMouseEvent = new ()
             {
                 Position = frameLoc,
                 Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.Position,
-                View = view
+                ScreenPosition = mouseEvent.ScreenPosition,
+                View = deepestViewUnderMouse
             };
         }
-        else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.Position))
+        else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.Position))
         {
-            Point viewportLocation = view.ScreenToViewport (mouseEvent.Position);
+            Point viewportLocation = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
 
-            me = new ()
+            viewMouseEvent = new ()
             {
                 Position = viewportLocation,
                 Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.Position,
-                View = view
+                ScreenPosition = mouseEvent.ScreenPosition,
+                View = deepestViewUnderMouse
             };
         }
         else
         {
-            return;
-        }
+            // The mouse was outside any View's Viewport.
 
-        if (MouseEnteredView is null)
-        {
-            MouseEnteredView = view;
-            view.NewMouseEnterEvent (me);
-        }
-        else if (MouseEnteredView != view)
-        {
-            MouseEnteredView.NewMouseLeaveEvent (me);
-            view.NewMouseEnterEvent (me);
-            MouseEnteredView = view;
-        }
+           // Debug.Fail ("This should never happen. If it does please file an Issue!!");
 
-        if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
-        {
             return;
         }
 
-        WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null;
+        RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse);
 
-        //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
+        WantContinuousButtonPressedView = deepestViewUnderMouse.WantContinuousButtonPressed ? deepestViewUnderMouse : null;
 
-        while (view.NewMouseEvent (me) is not true && MouseGrabView is not { })
+        while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabView is not { })
         {
-            if (view is Adornment adornmentView)
+            if (deepestViewUnderMouse is Adornment adornmentView)
             {
-                view = adornmentView.Parent!.SuperView;
+                deepestViewUnderMouse = adornmentView.Parent!.SuperView;
             }
             else
             {
-                view = view.SuperView;
+                deepestViewUnderMouse = deepestViewUnderMouse.SuperView;
             }
 
-            if (view is null)
+            if (deepestViewUnderMouse is null)
             {
                 break;
             }
 
-            Point boundsPoint = view.ScreenToViewport (mouseEvent.Position);
+            Point boundsPoint = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
 
-            me = new ()
+            viewMouseEvent = new ()
             {
                 Position = boundsPoint,
                 Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.Position,
-                View = view
+                ScreenPosition = mouseEvent.ScreenPosition,
+                View = deepestViewUnderMouse
             };
         }
+    }
 
-        ApplicationOverlapped.BringOverlappedTopToFront ();
+    internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEvent mouseEvent)
+    {
+        if (MouseGrabView is { })
+        {
+#if DEBUG_IDISPOSABLE
+            if (MouseGrabView.WasDisposed)
+            {
+                throw new ObjectDisposedException (MouseGrabView.GetType ().FullName);
+            }
+#endif
+
+            // If the mouse is grabbed, send the event to the view that grabbed it.
+            // The coordinates are relative to the Bounds of the view that grabbed the mouse.
+            Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
+
+            var viewRelativeMouseEvent = new MouseEvent
+            {
+                Position = frameLoc,
+                Flags = mouseEvent.Flags,
+                ScreenPosition = mouseEvent.ScreenPosition,
+                View = deepestViewUnderMouse ?? MouseGrabView
+            };
+
+            //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
+            if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
+            {
+                return true;
+            }
+
+            // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+            if (MouseGrabView is null && deepestViewUnderMouse is Adornment)
+            {
+                // The view that grabbed the mouse has been disposed
+                return true;
+            }
+        }
+
+        return false;
     }
 
-    #endregion Mouse handling
+    internal static readonly List<View?> _cachedViewsUnderMouse = new ();
+
+    // TODO: Refactor MouseEnter/LeaveEvents to not take MouseEvent param.
+    /// <summary>
+    ///     INTERNAL: Raises the MouseEnter and MouseLeave events for the views that are under the mouse.
+    /// </summary>
+    /// <param name="screenPosition">The position of the mouse.</param>
+    /// <param name="currentViewsUnderMouse">The most recent result from GetViewsUnderMouse().</param>
+    internal static void RaiseMouseEnterLeaveEvents (Point screenPosition, List<View?> currentViewsUnderMouse)
+    {
+        // Tell any views that are no longer under the mouse that the mouse has left
+        List<View?> viewsToLeave = _cachedViewsUnderMouse.Where (v => v is { } && !currentViewsUnderMouse.Contains (v)).ToList ();
+
+        foreach (View? view in viewsToLeave)
+        {
+            if (view is null)
+            {
+                continue;
+            }
+
+            view.NewMouseLeaveEvent ();
+            _cachedViewsUnderMouse.Remove (view);
+        }
+
+        // Tell any views that are now under the mouse that the mouse has entered and add them to the list
+        foreach (View? view in currentViewsUnderMouse)
+        {
+            if (view is null)
+            {
+                continue;
+            }
+
+            if (_cachedViewsUnderMouse.Contains (view))
+            {
+                continue;
+            }
+
+            _cachedViewsUnderMouse.Add (view);
+            var raise = false;
+
+            if (view is Adornment { Parent: { } } adornmentView)
+            {
+                Point superViewLoc = adornmentView.Parent.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
+                raise = adornmentView.Contains (superViewLoc);
+            }
+            else
+            {
+                Point superViewLoc = view.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
+                raise = view.Contains (superViewLoc);
+            }
+
+            if (!raise)
+            {
+                continue;
+            }
+
+            CancelEventArgs eventArgs = new ();
+            bool? cancelled = view.NewMouseEnterEvent (eventArgs);
+
+            if (cancelled is true || eventArgs.Cancel)
+            {
+                break;
+            }
+        }
+    }
 }

+ 51 - 300
Terminal.Gui/Application/Application.Run.cs

@@ -54,11 +54,6 @@ public static partial class Application // Run (Begin, Run, End, Stop)
         }
 #endif
 
-        if (toplevel.IsOverlappedContainer && ApplicationOverlapped.OverlappedTop != toplevel && ApplicationOverlapped.OverlappedTop is { })
-        {
-            throw new InvalidOperationException ("Only one Overlapped Container is allowed.");
-        }
-
         // Ensure the mouse is ungrabbed.
         MouseGrabView = null;
 
@@ -96,11 +91,6 @@ public static partial class Application // Run (Begin, Run, End, Stop)
                     throw new ObjectDisposedException (Top.GetType ().FullName);
                 }
             }
-            else if (ApplicationOverlapped.OverlappedTop is { } && toplevel != Top && TopLevels.Contains (Top!))
-            {
-                // BUGBUG: Don't call OnLeave/OnEnter directly! Set HasFocus to false and let the system handle it.
-                //Top!.OnLeave (toplevel);
-            }
 
             // BUGBUG: We should not depend on `Id` internally.
             // BUGBUG: It is super unclear what this code does anyway.
@@ -135,79 +125,46 @@ public static partial class Application // Run (Begin, Run, End, Stop)
             }
         }
 
-        if (Top is null || toplevel.IsOverlappedContainer)
+        if (Top is null)
         {
             Top = toplevel;
         }
 
-        var refreshDriver = true;
-
-        if (ApplicationOverlapped.OverlappedTop is null
-            || toplevel.IsOverlappedContainer
-            || (Current?.Modal == false && toplevel.Modal)
-            || (Current?.Modal == false && !toplevel.Modal)
-            || (Current?.Modal == true && toplevel.Modal))
+        if ((Top?.Modal == false && toplevel.Modal)
+            || (Top?.Modal == false && !toplevel.Modal)
+            || (Top?.Modal == true && toplevel.Modal))
         {
             if (toplevel.Visible)
             {
-                if (Current is { HasFocus: true })
+                if (Top is { HasFocus: true })
                 {
-                    Current.HasFocus = false;
+                    Top.HasFocus = false;
                 }
 
-                Current?.OnDeactivate (toplevel);
-                Toplevel previousCurrent = Current!;
+                Top?.OnDeactivate (toplevel);
+                Toplevel previousCurrent = Top!;
 
-                Current = toplevel;
-                Current.OnActivate (previousCurrent);
-
-                ApplicationOverlapped.SetCurrentOverlappedAsTop ();
+                Top = toplevel;
+                Top.OnActivate (previousCurrent);
             }
-            else
-            {
-                refreshDriver = false;
-            }
-        }
-        else if ((toplevel != ApplicationOverlapped.OverlappedTop
-                  && Current?.Modal == true
-                  && !TopLevels.Peek ().Modal)
-                 || (toplevel != ApplicationOverlapped.OverlappedTop && Current?.Running == false))
-        {
-            refreshDriver = false;
-            ApplicationOverlapped.MoveCurrent (toplevel);
-        }
-        else
-        {
-            refreshDriver = false;
-            ApplicationOverlapped.MoveCurrent (Current!);
         }
 
         toplevel.SetRelativeLayout (Driver!.Screen.Size);
-
         toplevel.LayoutSubviews ();
-        toplevel.PositionToplevels ();
 
-        // TODO: Should this use FindDeepestFocusableView instead?
         // Try to set initial focus to any TabStop
         if (!toplevel.HasFocus)
         {
             toplevel.SetFocus ();
         }
 
-        ApplicationOverlapped.BringOverlappedTopToFront ();
+        toplevel.OnLoaded ();
 
-        if (refreshDriver)
-        {
-            ApplicationOverlapped.OverlappedTop?.OnChildLoaded (toplevel);
-            toplevel.OnLoaded ();
-            toplevel.SetNeedsDisplay ();
-            toplevel.Draw ();
-            Driver.UpdateScreen ();
+        Refresh ();
 
-            if (PositionCursor (toplevel))
-            {
-                Driver.UpdateCursor ();
-            }
+        if (PositionCursor ())
+        {
+            Driver.UpdateCursor ();
         }
 
         NotifyNewRunState?.Invoke (toplevel, new (rs));
@@ -216,35 +173,22 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     }
 
     /// <summary>
-    ///     Calls <see cref="View.PositionCursor"/> on the most focused view in the view starting with <paramref name="view"/>.
+    ///     Calls <see cref="View.PositionCursor"/> on the most focused view.
     /// </summary>
     /// <remarks>
-    ///     Does nothing if <paramref name="view"/> is <see langword="null"/> or if the most focused view is not visible or
-    ///     enabled.
+    ///     Does nothing if there is no most focused view.
     ///     <para>
     ///         If the most focused view is not visible within it's superview, the cursor will be hidden.
     ///     </para>
     /// </remarks>
     /// <returns><see langword="true"/> if a view positioned the cursor and the position is visible.</returns>
-    internal static bool PositionCursor (View view)
+    internal static bool PositionCursor ()
     {
         // Find the most focused view and position the cursor there.
-        View? mostFocused = view?.MostFocused;
-
-        if (mostFocused is null)
-        {
-            if (view is { HasFocus: true })
-            {
-                mostFocused = view;
-            }
-            else
-            {
-                return false;
-            }
-        }
+        View? mostFocused = Navigation?.GetFocused ();
 
         // If the view is not visible or enabled, don't position the cursor
-        if (!mostFocused.Visible || !mostFocused.Enabled)
+        if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
         {
             Driver!.GetCursorVisibility (out CursorVisibility current);
 
@@ -510,20 +454,17 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     /// <summary>Triggers a refresh of the entire display.</summary>
     public static void Refresh ()
     {
-        // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear
-        Driver!.ClearContents ();
-
-        foreach (Toplevel v in TopLevels.Reverse ())
+        foreach (Toplevel tl in TopLevels.Reverse ())
         {
-            if (v.Visible)
+            if (tl.LayoutNeeded)
             {
-                v.SetNeedsDisplay ();
-                v.SetSubViewNeedsDisplay ();
-                v.Draw ();
+                tl.LayoutSubviews ();
             }
+
+            tl.Draw ();
         }
 
-        Driver.Refresh ();
+        Driver!.Refresh ();
     }
 
     /// <summary>This event is raised on each iteration of the main loop.</summary>
@@ -586,80 +527,22 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
             MainLoop.RunIteration ();
             Iteration?.Invoke (null, new ());
-            EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
-
-            // TODO: Overlapped - Move elsewhere
-            if (state.Toplevel != Current)
-            {
-                ApplicationOverlapped.OverlappedTop?.OnDeactivate (state.Toplevel);
-                state.Toplevel = Current;
-                ApplicationOverlapped.OverlappedTop?.OnActivate (state.Toplevel!);
-                Top!.SetSubViewNeedsDisplay ();
-                Refresh ();
-            }
         }
 
         firstIteration = false;
 
-        if (Current == null)
+        if (Top is null)
         {
             return;
         }
 
-        if (state.Toplevel != Top && (Top!.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
-        {
-            state.Toplevel!.SetNeedsDisplay (state.Toplevel.Frame);
-            Top.Draw ();
+        Refresh ();
 
-            foreach (Toplevel top in TopLevels.Reverse ())
-            {
-                if (top != Top && top != state.Toplevel)
-                {
-                    top.SetNeedsDisplay ();
-                    top.SetSubViewNeedsDisplay ();
-                    top.Draw ();
-                }
-            }
-        }
-
-        if (TopLevels.Count == 1
-            && state.Toplevel == Top
-            && (Driver!.Cols != state.Toplevel!.Frame.Width
-                || Driver!.Rows != state.Toplevel.Frame.Height)
-            && (state.Toplevel.NeedsDisplay
-                || state.Toplevel.SubViewNeedsDisplay
-                || state.Toplevel.LayoutNeeded))
-        {
-            Driver.ClearContents ();
-        }
-
-        if (state.Toplevel!.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || ApplicationOverlapped.OverlappedChildNeedsDisplay ())
-        {
-            state.Toplevel.SetNeedsDisplay ();
-            state.Toplevel.Draw ();
-            Driver!.UpdateScreen ();
-
-            //Driver.UpdateCursor ();
-        }
-
-        if (PositionCursor (state.Toplevel))
+        if (PositionCursor ())
         {
             Driver!.UpdateCursor ();
         }
 
-        //        else
-        {
-            //if (PositionCursor (state.Toplevel))
-            //{
-            //    Driver.Refresh ();
-            //}
-            //Driver.UpdateCursor ();
-        }
-
-        if (state.Toplevel != Top && !state.Toplevel.Modal && (Top!.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
-        {
-            Top.Draw ();
-        }
     }
 
     /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
@@ -673,112 +556,26 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     /// </remarks>
     public static void RequestStop (Toplevel? top = null)
     {
-        if (ApplicationOverlapped.OverlappedTop is null || top is null)
+        if (top is null)
         {
-            top = Current;
+            top = Top;
         }
 
-        if (ApplicationOverlapped.OverlappedTop != null
-            && top!.IsOverlappedContainer
-            && top?.Running == true
-            && (Current?.Modal == false || Current is { Modal: true, Running: false }))
+        if (!top!.Running)
         {
-            ApplicationOverlapped.OverlappedTop.RequestStop ();
+            return;
         }
-        else if (ApplicationOverlapped.OverlappedTop != null
-                 && top != Current
-                 && Current is { Running: true, Modal: true }
-                 && top!.Modal
-                 && top.Running)
-        {
-            var ev = new ToplevelClosingEventArgs (Current);
-            Current.OnClosing (ev);
-
-            if (ev.Cancel)
-            {
-                return;
-            }
 
-            ev = new (top);
-            top.OnClosing (ev);
+        var ev = new ToplevelClosingEventArgs (top);
+        top.OnClosing (ev);
 
-            if (ev.Cancel)
-            {
-                return;
-            }
-
-            Current.Running = false;
-            OnNotifyStopRunState (Current);
-            top.Running = false;
-            OnNotifyStopRunState (top);
-        }
-        else if ((ApplicationOverlapped.OverlappedTop != null
-                  && top != ApplicationOverlapped.OverlappedTop
-                  && top != Current
-                  && Current is { Modal: false, Running: true }
-                  && !top!.Running)
-                 || (ApplicationOverlapped.OverlappedTop != null
-                     && top != ApplicationOverlapped.OverlappedTop
-                     && top != Current
-                     && Current is { Modal: false, Running: false }
-                     && !top!.Running
-                     && TopLevels.ToArray () [1].Running))
-        {
-            ApplicationOverlapped.MoveCurrent (top);
-        }
-        else if (ApplicationOverlapped.OverlappedTop != null
-                 && Current != top
-                 && Current?.Running == true
-                 && !top!.Running
-                 && Current?.Modal == true
-                 && top.Modal)
+        if (ev.Cancel)
         {
-            // The Current and the top are both modal so needed to set the Current.Running to false too.
-            Current.Running = false;
-            OnNotifyStopRunState (Current);
-        }
-        else if (ApplicationOverlapped.OverlappedTop != null
-                 && Current == top
-                 && ApplicationOverlapped.OverlappedTop?.Running == true
-                 && Current?.Running == true
-                 && top!.Running
-                 && Current?.Modal == true
-                 && top!.Modal)
-        {
-            // The OverlappedTop was requested to stop inside a modal Toplevel which is the Current and top,
-            // both are the same, so needed to set the Current.Running to false too.
-            Current.Running = false;
-            OnNotifyStopRunState (Current);
+            return;
         }
-        else
-        {
-            Toplevel currentTop;
-
-            if (top == Current || (Current?.Modal == true && !top!.Modal))
-            {
-                currentTop = Current!;
-            }
-            else
-            {
-                currentTop = top!;
-            }
-
-            if (!currentTop.Running)
-            {
-                return;
-            }
-
-            var ev = new ToplevelClosingEventArgs (currentTop);
-            currentTop.OnClosing (ev);
 
-            if (ev.Cancel)
-            {
-                return;
-            }
-
-            currentTop.Running = false;
-            OnNotifyStopRunState (currentTop);
-        }
+        top.Running = false;
+        OnNotifyStopRunState (top);
     }
 
     private static void OnNotifyStopRunState (Toplevel top)
@@ -798,14 +595,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     {
         ArgumentNullException.ThrowIfNull (runState);
 
-        if (ApplicationOverlapped.OverlappedTop is { })
-        {
-            ApplicationOverlapped.OverlappedTop.OnChildUnloaded (runState.Toplevel);
-        }
-        else
-        {
-            runState.Toplevel.OnUnloaded ();
-        }
+        runState.Toplevel.OnUnloaded ();
 
         // End the RunState.Toplevel
         // First, take it off the Toplevel Stack
@@ -824,66 +614,27 @@ public static partial class Application // Run (Begin, Run, End, Stop)
         // Notify that it is closing
         runState.Toplevel?.OnClosed (runState.Toplevel);
 
-        // If there is a OverlappedTop that is not the RunState.Toplevel then RunState.Toplevel
-        // is a child of MidTop, and we should notify the OverlappedTop that it is closing
-        if (ApplicationOverlapped.OverlappedTop is { } && !runState.Toplevel!.Modal && runState.Toplevel != ApplicationOverlapped.OverlappedTop)
+        if (TopLevels.Count > 0)
         {
-            ApplicationOverlapped.OverlappedTop.OnChildClosed (runState.Toplevel);
+            Top = TopLevels.Peek ();
+            Top.SetNeedsDisplay ();
         }
 
-        // Set Current and Top to the next TopLevel on the stack
-        if (TopLevels.Count == 0)
+        if (runState.Toplevel is { HasFocus: true })
         {
-            if (Current is { HasFocus: true })
-            {
-                Current.HasFocus = false;
-            }
-            Current = null;
+            runState.Toplevel.HasFocus = false;
         }
-        else
-        {
-            if (TopLevels.Count > 1 && TopLevels.Peek () == ApplicationOverlapped.OverlappedTop && ApplicationOverlapped.OverlappedChildren?.Any (t => t.Visible) != null)
-            {
-                ApplicationOverlapped.OverlappedMoveNext ();
-            }
 
-            Current = TopLevels.Peek ();
-
-            if (TopLevels.Count == 1 && Current == ApplicationOverlapped.OverlappedTop)
-            {
-                ApplicationOverlapped.OverlappedTop.OnAllChildClosed ();
-            }
-            else
-            {
-                ApplicationOverlapped.SetCurrentOverlappedAsTop ();
-                // BUGBUG: We should not call OnEnter/OnLeave directly; they should only be called by SetHasFocus
-                if (runState.Toplevel is { HasFocus: true })
-                {
-                    runState.Toplevel.HasFocus = false;
-                }
-
-                if (Current is { HasFocus: false })
-                {
-                    Current.SetFocus ();
-                }
-            }
-
-            Refresh ();
-        }
-
-        // Don't dispose runState.Toplevel. It's up to caller dispose it
-        // If it's not the same as the current in the RunIteration,
-        // it will be fixed later in the next RunIteration.
-        if (ApplicationOverlapped.OverlappedTop is { } && !TopLevels.Contains (ApplicationOverlapped.OverlappedTop))
+        if (Top is { HasFocus: false })
         {
-            _cachedRunStateToplevel = ApplicationOverlapped.OverlappedTop;
-        }
-        else
-        {
-            _cachedRunStateToplevel = runState.Toplevel;
+            Top.SetFocus ();
         }
 
+        _cachedRunStateToplevel = runState.Toplevel;
+
         runState.Toplevel = null;
         runState.Dispose ();
+
+        Refresh ();
     }
 }

+ 0 - 6
Terminal.Gui/Application/Application.Screen.cs

@@ -37,13 +37,7 @@ public static partial class Application // Screen related stuff
         {
             t.SetRelativeLayout (args.Size.Value);
             t.LayoutSubviews ();
-            t.PositionToplevels ();
             t.OnSizeChanging (new (args.Size));
-
-            if (PositionCursor (t))
-            {
-                Driver?.UpdateCursor ();
-            }
         }
 
         Refresh ();

+ 1 - 43
Terminal.Gui/Application/Application.Toplevel.cs

@@ -8,49 +8,7 @@ public static partial class Application // Toplevel handling
     /// <summary>Holds the stack of TopLevel views.</summary>
     internal static Stack<Toplevel> TopLevels { get; } = new ();
 
-    /// <summary>The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Top"/>)</summary>
+    /// <summary>The <see cref="Toplevel"/> that is currently active.</summary>
     /// <value>The top.</value>
     public static Toplevel? Top { get; internal set; }
-
-    // TODO: Determine why this can't just return _topLevels.Peek()?
-    /// <summary>
-    ///     The current <see cref="Toplevel"/> object. This is updated in <see cref="Application.Begin"/> enters and leaves to
-    ///     point to the current
-    ///     <see cref="Toplevel"/> .
-    /// </summary>
-    /// <remarks>
-    ///     This will only be distinct from <see cref="Application.Top"/> in scenarios where <see cref="Toplevel.IsOverlappedContainer"/> is <see langword="true"/>.
-    /// </remarks>
-    /// <value>The current.</value>
-    public static Toplevel? Current { get; internal set; }
-
-    /// <summary>
-    ///     If <paramref name="topLevel"/> is not already Current and visible, finds the last Modal Toplevel in the stack and makes it Current.
-    /// </summary>
-    private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel)
-    {
-        if (!topLevel.Running
-            || (topLevel == Current && topLevel.Visible)
-            || ApplicationOverlapped.OverlappedTop == null
-            || TopLevels.Peek ().Modal)
-        {
-            return;
-        }
-
-        foreach (Toplevel top in TopLevels.Reverse ())
-        {
-            if (top.Modal && top != Current)
-            {
-                ApplicationOverlapped.MoveCurrent (top);
-
-                return;
-            }
-        }
-
-        if (!topLevel.Visible && topLevel == Current)
-        {
-            ApplicationOverlapped.OverlappedMoveNext ();
-        }
-    }
-
 }

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

@@ -149,7 +149,6 @@ public static partial class Application
         }
 
         TopLevels.Clear ();
-        Current = null;
 #if DEBUG_IDISPOSABLE
 
         // Don't dispose the Top. It's up to caller dispose it
@@ -198,7 +197,8 @@ public static partial class Application
         IsInitialized = false;
 
         // Mouse
-        MouseEnteredView = null;
+        _lastMousePosition = null;
+        _cachedViewsUnderMouse.Clear ();
         WantContinuousButtonPressedView = null;
         MouseEvent = null;
         GrabbedMouse = null;
@@ -215,8 +215,6 @@ public static partial class Application
 
         AddApplicationKeyBindings ();
 
-        Colors.Reset ();
-
         // Reset synchronization context to allow the user to run async/await,
         // as the main loop has been ended, the synchronization context from
         // gui.cs does no longer process any callbacks. See #1084 for more details:

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

@@ -1,5 +1,7 @@
 #nullable enable
 
+using System.Diagnostics;
+
 namespace Terminal.Gui;
 
 /// <summary>
@@ -98,6 +100,6 @@ public class ApplicationNavigation
     /// </returns>
     public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
     {
-        return Application.Current is { } && Application.Current.AdvanceFocus (direction, behavior);
+        return Application.Top is { } && Application.Top.AdvanceFocus (direction, behavior);
     }
 }

+ 0 - 444
Terminal.Gui/Application/ApplicationOverlapped.cs

@@ -1,444 +0,0 @@
-#nullable enable
-using System.Diagnostics;
-using System.Reflection;
-
-namespace Terminal.Gui;
-
-/// <summary>
-/// Helper class for managing overlapped views in the application.
-/// </summary>
-public static class ApplicationOverlapped
-{
-
-    /// <summary>
-    ///     Gets or sets if <paramref name="top"/> is in overlapped mode within a Toplevel container.
-    /// </summary>
-    /// <param name="top"></param>
-    /// <returns></returns>
-    public static bool IsOverlapped (Toplevel? top)
-    {
-        return ApplicationOverlapped.OverlappedTop is { } && ApplicationOverlapped.OverlappedTop != top && !top!.Modal;
-    }
-
-    /// <summary>
-    ///     Gets the list of the Overlapped children which are not modal <see cref="Toplevel"/> from the
-    ///     <see cref="OverlappedTop"/>.
-    /// </summary>
-    public static List<Toplevel>? OverlappedChildren
-    {
-        get
-        {
-            if (OverlappedTop is { })
-            {
-                List<Toplevel> overlappedChildren = new ();
-
-                lock (Application.TopLevels)
-                {
-                    foreach (Toplevel top in Application.TopLevels)
-                    {
-                        if (top != OverlappedTop && !top.Modal)
-                        {
-                            overlappedChildren.Add (top);
-                        }
-                    }
-                }
-
-                return overlappedChildren;
-            }
-
-            return null;
-        }
-    }
-
-    /// <summary>
-    ///     The <see cref="Toplevel"/> object used for the application on startup which
-    ///     <see cref="Toplevel.IsOverlappedContainer"/> is true.
-    /// </summary>
-    public static Toplevel? OverlappedTop
-    {
-        get
-        {
-            if (Application.Top is { IsOverlappedContainer: true })
-            {
-                return Application.Top;
-            }
-
-            return null;
-        }
-    }
-
-    /// <summary>Brings the superview of the most focused overlapped view is on front.</summary>
-    public static void BringOverlappedTopToFront ()
-    {
-        if (OverlappedTop is { })
-        {
-            return;
-        }
-
-        View? top = FindTopFromView (Application.Top?.MostFocused);
-
-        if (top is Toplevel && Application.Top?.Subviews.Count > 1 && Application.Top.Subviews [^1] != top)
-        {
-            Application.Top.MoveSubviewToStart (top);
-        }
-    }
-
-    /// <summary>Gets the current visible Toplevel overlapped child that matches the arguments pattern.</summary>
-    /// <param name="type">The type.</param>
-    /// <param name="exclude">The strings to exclude.</param>
-    /// <returns>The matched view.</returns>
-    public static Toplevel? GetTopOverlappedChild (Type? type = null, string []? exclude = null)
-    {
-        if (OverlappedChildren is null || OverlappedTop is null)
-        {
-            return null;
-        }
-
-        foreach (Toplevel top in OverlappedChildren)
-        {
-            if (type is { } && top.GetType () == type && exclude?.Contains (top.Data?.ToString ()) == false)
-            {
-                return top;
-            }
-
-            if ((type is { } && top.GetType () != type) || exclude?.Contains (top.Data?.ToString ()) == true)
-            {
-                continue;
-            }
-
-            return top;
-        }
-
-        return null;
-    }
-
-
-    /// <summary>
-    /// Sets the focus to the next view in the specified direction within the provided list of views.
-    /// If the end of the list is reached, the focus wraps around to the first view in the list.
-    /// The method considers the current focused view (`Application.Current`) and attempts to move the focus
-    /// to the next view in the specified direction. If the focus cannot be set to the next view, it wraps around
-    /// to the first view in the list.
-    /// </summary>
-    /// <param name="viewsInTabIndexes"></param>
-    /// <param name="direction"></param>
-    internal static void SetFocusToNextViewWithWrap (IEnumerable<View>? viewsInTabIndexes, NavigationDirection direction)
-    {
-        if (viewsInTabIndexes is null)
-        {
-            return;
-        }
-
-        // This code-path only executes in obtuse IsOverlappedContainer scenarios.
-        Debug.Assert (Application.Current!.IsOverlappedContainer);
-
-        bool foundCurrentView = false;
-        bool focusSet = false;
-        IEnumerable<View> indexes = viewsInTabIndexes as View [] ?? viewsInTabIndexes.ToArray ();
-        int viewCount = indexes.Count ();
-        int currentIndex = 0;
-
-        foreach (View view in indexes)
-        {
-            if (view == Application.Current)
-            {
-                foundCurrentView = true;
-            }
-            else if (foundCurrentView && !focusSet)
-            {
-                // One of the views is Current, but view is not. Attempt to Advance...
-                Application.Current!.SuperView?.AdvanceFocus (direction, null);
-                // QUESTION: AdvanceFocus returns false AND sets Focused to null if no view was found to advance to. Should't we only set focusProcessed if it returned true?
-                focusSet = true;
-
-                if (Application.Current.SuperView?.Focused != Application.Current)
-                {
-                    return;
-                }
-
-                // Either AdvanceFocus didn't set focus or the view it set focus to is not current...
-                // continue...
-            }
-
-            currentIndex++;
-
-            if (foundCurrentView && !focusSet && currentIndex == viewCount)
-            {
-                // One of the views is Current AND AdvanceFocus didn't set focus AND we are at the last view in the list...
-                // This means we should wrap around to the first view in the list.
-                indexes.First ().SetFocus ();
-            }
-        }
-    }
-
-    /// <summary>
-    ///     Move to the next Overlapped child from the <see cref="OverlappedTop"/> and set it as the <see cref="Application.Top"/> if
-    ///     it is not already.
-    /// </summary>
-    /// <param name="top"></param>
-    /// <returns></returns>
-    public static bool MoveToOverlappedChild (Toplevel? top)
-    {
-        ArgumentNullException.ThrowIfNull (top);
-
-        if (top.Visible && OverlappedTop is { } && Application.Current?.Modal == false)
-        {
-            lock (Application.TopLevels)
-            {
-                Application.TopLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
-                Application.Current = top;
-            }
-
-            return true;
-        }
-
-        return false;
-    }
-
-    /// <summary>Move to the next Overlapped child from the <see cref="OverlappedTop"/>.</summary>
-    public static void OverlappedMoveNext ()
-    {
-        if (OverlappedTop is { } && !Application.Current!.Modal)
-        {
-            lock (Application.TopLevels)
-            {
-                Application.TopLevels.MoveNext ();
-                var isOverlapped = false;
-
-                while (Application.TopLevels.Peek () == OverlappedTop || !Application.TopLevels.Peek ().Visible)
-                {
-                    if (!isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
-                    {
-                        isOverlapped = true;
-                    }
-                    else if (isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
-                    {
-                        MoveCurrent (Application.Top!);
-
-                        break;
-                    }
-
-                    Application.TopLevels.MoveNext ();
-                }
-
-                Application.Current = Application.TopLevels.Peek ();
-            }
-        }
-    }
-
-    /// <summary>Move to the previous Overlapped child from the <see cref="OverlappedTop"/>.</summary>
-    public static void OverlappedMovePrevious ()
-    {
-        if (OverlappedTop is { } && !Application.Current!.Modal)
-        {
-            lock (Application.TopLevels)
-            {
-                Application.TopLevels.MovePrevious ();
-                var isOverlapped = false;
-
-                while (Application.TopLevels.Peek () == OverlappedTop || !Application.TopLevels.Peek ().Visible)
-                {
-                    if (!isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
-                    {
-                        isOverlapped = true;
-                    }
-                    else if (isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
-                    {
-                        MoveCurrent (Application.Top!);
-
-                        break;
-                    }
-
-                    Application.TopLevels.MovePrevious ();
-                }
-
-                 Application.Current = Application.TopLevels.Peek ();
-            }
-        }
-    }
-
-    internal static bool OverlappedChildNeedsDisplay ()
-    {
-        if (OverlappedTop is null)
-        {
-            return false;
-        }
-
-        lock (Application.TopLevels)
-        {
-            foreach (Toplevel top in Application.TopLevels)
-            {
-                if (top != Application.Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded))
-                {
-                    OverlappedTop.SetSubViewNeedsDisplay ();
-
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    internal static bool SetCurrentOverlappedAsTop ()
-    {
-        if (OverlappedTop is null && Application.Current != Application.Top && Application.Current?.SuperView is null && Application.Current?.Modal == false)
-        {
-            Application.Top = Application.Current;
-
-            return true;
-        }
-
-        return false;
-    }
-
-    /// <summary>
-    ///     Finds the first Toplevel in the stack that is Visible and who's Frame contains the <paramref name="location"/>.
-    /// </summary>
-    /// <param name="start"></param>
-    /// <param name="location"></param>
-    /// <returns></returns>
-    internal static Toplevel? FindDeepestTop (Toplevel start, in Point location)
-    {
-        if (!start.Frame.Contains (location))
-        {
-            return null;
-        }
-
-        lock (Application.TopLevels)
-        {
-            if (Application.TopLevels is not { Count: > 0 })
-            {
-                return start;
-            }
-
-            int rx = location.X - start.Frame.X;
-            int ry = location.Y - start.Frame.Y;
-
-            foreach (Toplevel t in Application.TopLevels)
-            {
-                if (t == Application.Current)
-                {
-                    continue;
-                }
-
-                if (t != start && t.Visible && t.Frame.Contains (rx, ry))
-                {
-                    start = t;
-
-                    break;
-                }
-            }
-        }
-
-        return start;
-    }
-
-    /// <summary>
-    ///     Given <paramref name="view"/>, returns the first Superview up the chain that is <see cref="Application.Top"/>.
-    /// </summary>
-    internal static View? FindTopFromView (View? view)
-    {
-        if (view is null)
-        {
-            return null;
-        }
-
-        View top = view.SuperView is { } && view.SuperView != Application.Top
-                       ? view.SuperView
-                       : view;
-
-        while (top?.SuperView is { } && top?.SuperView != Application.Top)
-        {
-            top = top!.SuperView;
-        }
-
-        return top;
-    }
-
-    /// <summary>
-    ///     If the <see cref="Application.Current"/> is not the <paramref name="top"/> then <paramref name="top"/> is moved to the top of
-    ///     the Toplevel stack and made Current.
-    /// </summary>
-    /// <param name="top"></param>
-    /// <returns></returns>
-    internal static bool MoveCurrent (Toplevel top)
-    {
-        // The Current is modal and the top is not modal Toplevel then
-        // the Current must be moved above the first not modal Toplevel.
-        if (OverlappedTop is { }
-            && top != OverlappedTop
-            && top != Application.Current
-            && Application.Current?.Modal == true
-            && !Application.TopLevels.Peek ().Modal)
-        {
-            lock (Application.TopLevels)
-            {
-                Application.TopLevels.MoveTo (Application.Current, 0, new ToplevelEqualityComparer ());
-            }
-
-            var index = 0;
-            Toplevel [] savedToplevels = Application.TopLevels.ToArray ();
-
-            foreach (Toplevel t in savedToplevels)
-            {
-                if (!t!.Modal && t != Application.Current && t != top && t != savedToplevels [index])
-                {
-                    lock (Application.TopLevels)
-                    {
-                        Application.TopLevels.MoveTo (top, index, new ToplevelEqualityComparer ());
-                    }
-                }
-
-                index++;
-            }
-
-            return false;
-        }
-
-        // The Current and the top are both not running Toplevel then
-        // the top must be moved above the first not running Toplevel.
-        if (OverlappedTop is { }
-            && top != OverlappedTop
-            && top != Application.Current
-            && Application.Current?.Running == false
-            && top?.Running == false)
-        {
-            lock (Application.TopLevels)
-            {
-                Application.TopLevels.MoveTo (Application.Current, 0, new ToplevelEqualityComparer ());
-            }
-
-            var index = 0;
-
-            foreach (Toplevel t in Application.TopLevels.ToArray ())
-            {
-                if (!t.Running && t != Application.Current && index > 0)
-                {
-                    lock (Application.TopLevels)
-                    {
-                        Application.TopLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
-                    }
-                }
-
-                index++;
-            }
-
-            return false;
-        }
-
-        if ((OverlappedTop is { } && top?.Modal == true && Application.TopLevels.Peek () != top)
-            || (OverlappedTop is { } && Application.Current != OverlappedTop && Application.Current?.Modal == false && top == OverlappedTop)
-            || (OverlappedTop is { } && Application.Current?.Modal == false && top != Application.Current)
-            || (OverlappedTop is { } && Application.Current?.Modal == true && top == OverlappedTop))
-        {
-            lock (Application.TopLevels)
-            {
-                Application.TopLevels.MoveTo (top!, 0, new ToplevelEqualityComparer ());
-                Application.Current = top;
-            }
-        }
-
-        return true;
-    }
-}

+ 1 - 1
Terminal.Gui/Configuration/AppScope.cs

@@ -16,7 +16,7 @@ namespace Terminal.Gui;
 /// 	public static bool? MyProperty { get; set; } = true;
 ///  }
 ///  </code>
-///     <para>THe resultant Json will look like this:</para>
+///     <para>The resultant Json will look like this:</para>
 ///     <code>
 ///    "AppSettings": {
 ///      "MyAppSettings.MyProperty": true,

+ 18 - 5
Terminal.Gui/Configuration/ColorJsonConverter.cs

@@ -1,9 +1,19 @@
 using System.Text.Json;
 using System.Text.Json.Serialization;
+using ColorHelper;
 
 namespace Terminal.Gui;
 
-/// <summary>Json converter for the <see cref="Color"/> class.</summary>
+/// <summary>
+/// Json converter for the <see cref="Color"/> class.
+/// <para>
+///     Serialization outputs a string with the color name if the color matches a name in <see cref="ColorStrings"/>
+///     or the "#RRGGBB" hexadecimal representation (e.g. "#FF0000" for red).
+/// </para>
+/// <para>
+///     Deserialization formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
+///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", or any W3C color name.</para>
+/// </summary>
 internal class ColorJsonConverter : JsonConverter<Color>
 {
     private static ColorJsonConverter _instance;
@@ -15,7 +25,7 @@ internal class ColorJsonConverter : JsonConverter<Color>
         {
             if (_instance is null)
             {
-                _instance = new ColorJsonConverter ();
+                _instance = new ();
             }
 
             return _instance;
@@ -31,10 +41,10 @@ internal class ColorJsonConverter : JsonConverter<Color>
             ReadOnlySpan<char> colorString = reader.GetString ();
 
             // Check if the color string is a color name
-            if (Enum.TryParse (colorString, true, out ColorName color))
+            if (ColorStrings.TryParseW3CColorName (colorString.ToString (), out Color color1))
             {
                 // Return the parsed color
-                return new Color (in color);
+                return new (color1);
             }
 
             if (Color.TryParse (colorString, null, out Color parsedColor))
@@ -48,5 +58,8 @@ internal class ColorJsonConverter : JsonConverter<Color>
         throw new JsonException ($"Unexpected token when parsing Color: {reader.TokenType}");
     }
 
-    public override void Write (Utf8JsonWriter writer, Color value, JsonSerializerOptions options) { writer.WriteStringValue (value.ToString ()); }
+    public override void Write (Utf8JsonWriter writer, Color value, JsonSerializerOptions options)
+    {
+        writer.WriteStringValue (value.ToString ());
+    }
 }

+ 14 - 5
Terminal.Gui/Configuration/ConfigurationManager.cs

@@ -24,7 +24,7 @@ namespace Terminal.Gui;
 ///     </para>
 ///     <para>
 ///         Settings are defined in JSON format, according to this schema:
-///         https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
+///        https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json
 ///     </para>
 ///     <para>
 ///         Settings that will apply to all applications (global settings) reside in files named <c>config.json</c>.
@@ -197,10 +197,19 @@ public static class ConfigurationManager
 
         try
         {
-            settings = Settings?.Apply () ?? false;
-
-            themes = !string.IsNullOrEmpty (ThemeManager.SelectedTheme)
-                     && (ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false);
+            if (string.IsNullOrEmpty (ThemeManager.SelectedTheme))
+            {
+                // First start. Apply settings first. This ensures if a config sets Theme to something other than "Default", it gets used
+                settings = Settings?.Apply () ?? false;
+                themes = !string.IsNullOrEmpty (ThemeManager.SelectedTheme)
+                         && (ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false);
+            }
+            else
+            {
+                // Subsequently. Apply Themes first using whatever the SelectedTheme is
+                themes = ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false;
+                settings = Settings?.Apply () ?? false;
+            }
             appSettings = AppSettings?.Apply () ?? false;
         }
         catch (JsonException e)

+ 24 - 17
Terminal.Gui/Configuration/RuneJsonConverter.cs

@@ -6,9 +6,17 @@ using System.Text.RegularExpressions;
 namespace Terminal.Gui;
 
 /// <summary>
-///     Json converter for <see cref="Rune"/>. Supports Json converter for <see cref="Rune"/>. Supports A string as
-///     one of: - unicode char (e.g. "☑") - U+hex format (e.g. "U+2611") - \u format (e.g. "\\u2611") A number - The
-///     unicode code in decimal
+///     Json converter for <see cref="Rune"/>.
+///     <para>
+///         If the Rune is printable, it will be serialized as the glyph; otherwise the \u format (e.g. "\\u2611") is used.
+///     </para>
+///     <para>
+///         Supports deserializing as one of:
+///         - unicode glyph in a string (e.g. "☑")
+///         - U+hex format in a string  (e.g. "U+2611")
+///         - \u format in a string (e.g. "\\u2611")
+///         - A decimal number (e.g. 97 for "a")
+///     </para>
 /// </summary>
 internal class RuneJsonConverter : JsonConverter<Rune>
 {
@@ -108,7 +116,7 @@ internal class RuneJsonConverter : JsonConverter<Rune>
                     throw new JsonException ($"Invalid combined Rune ({value})");
                 }
 
-                return new Rune (combined [0]);
+                return new (combined [0]);
             }
             case JsonTokenType.Number:
             {
@@ -116,7 +124,7 @@ internal class RuneJsonConverter : JsonConverter<Rune>
 
                 if (Rune.IsValid (num))
                 {
-                    return new Rune (num);
+                    return new (num);
                 }
 
                 throw new JsonException ($"Invalid Rune (not a scalar Unicode value): {num}.");
@@ -128,18 +136,17 @@ internal class RuneJsonConverter : JsonConverter<Rune>
 
     public override void Write (Utf8JsonWriter writer, Rune value, JsonSerializerOptions options)
     {
-        // HACK: Writes a JSON comment in addition to the glyph to ease debugging.
-        // Technically, JSON comments are not valid, but we use relaxed decoding
-        // (ReadCommentHandling = JsonCommentHandling.Skip)
-        //writer.WriteCommentValue ($"(U+{value.Value:X8})");
-        //var printable = value.MakePrintable ();
-        //if (printable == Rune.ReplacementChar) {
-        //	writer.WriteStringValue (value.ToString ());
-        //} else {
-        //	//writer.WriteRawValue ($"\"{value}\"");
-        //}
-
-        writer.WriteNumberValue (value.Value);
+        Rune printable = value.MakePrintable ();
+        if (printable == Rune.ReplacementChar)
+        {
+            // Write as /u string
+            writer.WriteRawValue ($"\"{value}\"");
+        }
+        else
+        {
+            // Write as the actual glyph
+            writer.WriteStringValue (value.ToString ());
+        }
     }
 }
 #pragma warning restore 1591

+ 25 - 8
Terminal.Gui/Configuration/SettingsScope.cs

@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.Json.Serialization;
-using static Terminal.Gui.SpinnerStyle;
 
 namespace Terminal.Gui;
 
@@ -15,7 +14,7 @@ namespace Terminal.Gui;
 /// <example>
 ///     <code>
 ///  {
-///    "$schema" : "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json",
+///    "$schema" : "https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json",
 ///    "Application.UseSystemConsole" : true,
 ///    "Theme" : "Default",
 ///    "Themes": {
@@ -33,7 +32,7 @@ public class SettingsScope : Scope<SettingsScope>
     /// <summary>Points to our JSON schema.</summary>
     [JsonInclude]
     [JsonPropertyName ("$schema")]
-    public string Schema { get; set; } = "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json";
+    public string Schema { get; set; } = "https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json";
 
     /// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON string.</summary>
     /// <param name="stream">Json document to update the settings with.</param>
@@ -87,12 +86,30 @@ public class SettingsScope : Scope<SettingsScope>
             return this;
         }
 
-        FileStream stream = File.OpenRead (realPath);
-        SettingsScope? s = Update (stream, filePath);
-        stream.Close ();
-        stream.Dispose ();
+        int retryCount = 0;
 
-        return s;
+        // Sometimes when the config file is written by an external agent, the change notification comes
+        // before the file is closed. This works around that.
+        while (retryCount < 2)
+        {
+            try
+            {
+                FileStream? stream = File.OpenRead (realPath);
+                SettingsScope? s = Update (stream, filePath);
+                stream.Close ();
+                stream.Dispose ();
+
+                return s;
+            }
+            catch (IOException ioe)
+            {
+                Debug.WriteLine($"Couldn't open {filePath}. Retrying...: {ioe}");
+                Task.Delay (100);
+                retryCount++;
+            }
+        }
+
+        return null;
     }
 
     /// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON string.</summary>

+ 3 - 1
Terminal.Gui/Configuration/SourceGenerationContext.cs

@@ -15,9 +15,11 @@ namespace Terminal.Gui;
 [JsonSerializable (typeof (AlignmentModes))]
 [JsonSerializable (typeof (LineStyle))]
 [JsonSerializable (typeof (ShadowStyle))]
+[JsonSerializable (typeof (HighlightStyle))]
 [JsonSerializable (typeof (bool?))]
-[JsonSerializable (typeof (Dictionary<ColorName, string>))]
+[JsonSerializable (typeof (Dictionary<ColorName16, string>))]
 [JsonSerializable (typeof (Dictionary<string, ThemeScope>))]
 [JsonSerializable (typeof (Dictionary<string, ColorScheme>))]
+[JsonSerializable (typeof (Dictionary<string, Color>))]
 internal partial class SourceGenerationContext : JsonSerializerContext
 { }

+ 1 - 1
Terminal.Gui/Configuration/ThemeManager.cs

@@ -110,7 +110,7 @@ public class ThemeManager : IDictionary<string, ThemeScope>
             string oldTheme = _theme;
             _theme = value;
 
-            if (oldTheme != _theme && Settings! ["Themes"]?.PropertyValue is Dictionary<string, ThemeScope> themes && themes.ContainsKey (_theme))
+            if ((oldTheme != _theme || oldTheme != Settings! ["Theme"].PropertyValue as string) && Settings! ["Themes"]?.PropertyValue is Dictionary<string, ThemeScope> themes && themes.ContainsKey (_theme))
             {
                 Settings! ["Theme"].PropertyValue = _theme;
                 Instance.OnThemeChanged (oldTheme);

+ 8 - 1
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -485,6 +485,7 @@ public abstract class ConsoleDriver
     public virtual bool SupportsTrueColor => true;
 
     // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
+    // BUGBUG: Application.Force16Colors should be bool? so if SupportsTrueColor and Application.Force16Colors == false, this doesn't override
     /// <summary>
     ///     Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors.
     ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
@@ -591,7 +592,13 @@ public abstract class ConsoleDriver
 
     /// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
     /// <param name="a"></param>
-    public void OnMouseEvent (MouseEvent a) { MouseEvent?.Invoke (this, a); }
+    public void OnMouseEvent (MouseEvent a)
+    {
+        // Ensure ScreenPosition is set
+        a.ScreenPosition = a.Position;
+
+        MouseEvent?.Invoke (this, a);
+    }
 
     /// <summary>Simulates a key press.</summary>
     /// <param name="keyChar">The key character.</param>

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

@@ -374,7 +374,7 @@ internal class CursesDriver : ConsoleDriver
                                                            );
         }
 
-        CurrentAttribute = new Attribute (ColorName.White, ColorName.Black);
+        CurrentAttribute = new Attribute (ColorName16.White, ColorName16.Black);
 
         if (Environment.OSVersion.Platform == PlatformID.Win32NT)
         {
@@ -859,8 +859,8 @@ internal class CursesDriver : ConsoleDriver
 
         return new Attribute (
                               Curses.ColorPair (v),
-                              CursesColorNumberToColorName (foreground),
-                              CursesColorNumberToColorName (background)
+                              CursesColorNumberToColorName16 (foreground),
+                              CursesColorNumberToColorName16 (background)
                              );
     }
 
@@ -875,8 +875,8 @@ internal class CursesDriver : ConsoleDriver
         if (!RunningUnitTests)
         {
             return MakeColor (
-                              ColorNameToCursesColorNumber (foreground.GetClosestNamedColor ()),
-                              ColorNameToCursesColorNumber (background.GetClosestNamedColor ())
+                              ColorNameToCursesColorNumber (foreground.GetClosestNamedColor16 ()),
+                              ColorNameToCursesColorNumber (background.GetClosestNamedColor16 ())
                              );
         }
 
@@ -887,83 +887,83 @@ internal class CursesDriver : ConsoleDriver
                              );
     }
 
-    private static short ColorNameToCursesColorNumber (ColorName color)
+    private static short ColorNameToCursesColorNumber (ColorName16 color)
     {
         switch (color)
         {
-            case ColorName.Black:
+            case ColorName16.Black:
                 return Curses.COLOR_BLACK;
-            case ColorName.Blue:
+            case ColorName16.Blue:
                 return Curses.COLOR_BLUE;
-            case ColorName.Green:
+            case ColorName16.Green:
                 return Curses.COLOR_GREEN;
-            case ColorName.Cyan:
+            case ColorName16.Cyan:
                 return Curses.COLOR_CYAN;
-            case ColorName.Red:
+            case ColorName16.Red:
                 return Curses.COLOR_RED;
-            case ColorName.Magenta:
+            case ColorName16.Magenta:
                 return Curses.COLOR_MAGENTA;
-            case ColorName.Yellow:
+            case ColorName16.Yellow:
                 return Curses.COLOR_YELLOW;
-            case ColorName.Gray:
+            case ColorName16.Gray:
                 return Curses.COLOR_WHITE;
-            case ColorName.DarkGray:
+            case ColorName16.DarkGray:
                 return Curses.COLOR_GRAY;
-            case ColorName.BrightBlue:
+            case ColorName16.BrightBlue:
                 return Curses.COLOR_BLUE | Curses.COLOR_GRAY;
-            case ColorName.BrightGreen:
+            case ColorName16.BrightGreen:
                 return Curses.COLOR_GREEN | Curses.COLOR_GRAY;
-            case ColorName.BrightCyan:
+            case ColorName16.BrightCyan:
                 return Curses.COLOR_CYAN | Curses.COLOR_GRAY;
-            case ColorName.BrightRed:
+            case ColorName16.BrightRed:
                 return Curses.COLOR_RED | Curses.COLOR_GRAY;
-            case ColorName.BrightMagenta:
+            case ColorName16.BrightMagenta:
                 return Curses.COLOR_MAGENTA | Curses.COLOR_GRAY;
-            case ColorName.BrightYellow:
+            case ColorName16.BrightYellow:
                 return Curses.COLOR_YELLOW | Curses.COLOR_GRAY;
-            case ColorName.White:
+            case ColorName16.White:
                 return Curses.COLOR_WHITE | Curses.COLOR_GRAY;
         }
 
         throw new ArgumentException ("Invalid color code");
     }
 
-    private static ColorName CursesColorNumberToColorName (short color)
+    private static ColorName16 CursesColorNumberToColorName16 (short color)
     {
         switch (color)
         {
             case Curses.COLOR_BLACK:
-                return ColorName.Black;
+                return ColorName16.Black;
             case Curses.COLOR_BLUE:
-                return ColorName.Blue;
+                return ColorName16.Blue;
             case Curses.COLOR_GREEN:
-                return ColorName.Green;
+                return ColorName16.Green;
             case Curses.COLOR_CYAN:
-                return ColorName.Cyan;
+                return ColorName16.Cyan;
             case Curses.COLOR_RED:
-                return ColorName.Red;
+                return ColorName16.Red;
             case Curses.COLOR_MAGENTA:
-                return ColorName.Magenta;
+                return ColorName16.Magenta;
             case Curses.COLOR_YELLOW:
-                return ColorName.Yellow;
+                return ColorName16.Yellow;
             case Curses.COLOR_WHITE:
-                return ColorName.Gray;
+                return ColorName16.Gray;
             case Curses.COLOR_GRAY:
-                return ColorName.DarkGray;
+                return ColorName16.DarkGray;
             case Curses.COLOR_BLUE | Curses.COLOR_GRAY:
-                return ColorName.BrightBlue;
+                return ColorName16.BrightBlue;
             case Curses.COLOR_GREEN | Curses.COLOR_GRAY:
-                return ColorName.BrightGreen;
+                return ColorName16.BrightGreen;
             case Curses.COLOR_CYAN | Curses.COLOR_GRAY:
-                return ColorName.BrightCyan;
+                return ColorName16.BrightCyan;
             case Curses.COLOR_RED | Curses.COLOR_GRAY:
-                return ColorName.BrightRed;
+                return ColorName16.BrightRed;
             case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY:
-                return ColorName.BrightMagenta;
+                return ColorName16.BrightMagenta;
             case Curses.COLOR_YELLOW | Curses.COLOR_GRAY:
-                return ColorName.BrightYellow;
+                return ColorName16.BrightYellow;
             case Curses.COLOR_WHITE | Curses.COLOR_GRAY:
-                return ColorName.White;
+                return ColorName16.White;
         }
 
         throw new ArgumentException ("Invalid curses color code");

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

@@ -93,7 +93,7 @@ public class FakeDriver : ConsoleDriver
         FakeConsole.Clear ();
         ResizeScreen ();
         CurrentAttribute = new Attribute (Color.White, Color.Black);
-        ClearContents ();
+        //ClearContents ();
 
         _mainLoopDriver = new FakeMainLoop (this);
         _mainLoopDriver.MockKeyPressed = MockKeyPressedHandler;
@@ -165,8 +165,8 @@ public class FakeDriver : ConsoleDriver
                     if (attr != redrawAttr)
                     {
                         redrawAttr = attr;
-                        FakeConsole.ForegroundColor = (ConsoleColor)attr.Foreground.GetClosestNamedColor ();
-                        FakeConsole.BackgroundColor = (ConsoleColor)attr.Background.GetClosestNamedColor ();
+                        FakeConsole.ForegroundColor = (ConsoleColor)attr.Foreground.GetClosestNamedColor16 ();
+                        FakeConsole.BackgroundColor = (ConsoleColor)attr.Background.GetClosestNamedColor16 ();
                     }
 
                     outputWidth++;

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

@@ -961,10 +961,10 @@ internal class NetDriver : ConsoleDriver
                             output.Append (
                                            EscSeqUtils.CSI_SetGraphicsRendition (
                                                                                  MapColors (
-                                                                                            (ConsoleColor)attr.Background.GetClosestNamedColor (),
+                                                                                            (ConsoleColor)attr.Background.GetClosestNamedColor16 (),
                                                                                             false
                                                                                            ),
-                                                                                 MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor ())
+                                                                                 MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor16 ())
                                                                                 )
                                           );
                         }

+ 3 - 1
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -54,6 +54,8 @@ internal class WindowsConsole
 
     public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
     {
+        //Debug.WriteLine ("WriteToConsole");
+
         if (_screenBuffer == nint.Zero)
         {
             ReadFromConsoleOutput (size, bufferSize, ref window);
@@ -72,7 +74,7 @@ internal class WindowsConsole
                 {
                     Char = new CharUnion { UnicodeChar = info.Char },
                     Attributes =
-                        (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor () | ((int)info.Attribute.Background.GetClosestNamedColor () << 4))
+                        (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
                 };
             }
 

+ 55 - 53
Terminal.Gui/Drawing/Alignment.cs

@@ -10,72 +10,74 @@ public enum Alignment
 {
     /// <summary>
     ///     The items will be aligned to the start (left or top) of the container.
+    ///     <remarks>
+    ///         <para>
+    ///             If the container is smaller than the total size of the items, the end items will be clipped (their
+    ///             locations
+    ///             will be greater than the container size).
+    ///         </para>
+    ///         <para>
+    ///             The <see cref="AlignmentModes"/> enumeration provides additional options for aligning items in a container.
+    ///         </para>
+    ///     </remarks>
+    ///     <example>
+    ///         <c>
+    ///             |111 2222 33333    |
+    ///         </c>
+    ///     </example>
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         If the container is smaller than the total size of the items, the end items will be clipped (their locations
-    ///         will be greater than the container size).
-    ///     </para>
-    ///     <para>
-    ///         The <see cref="AlignmentModes"/> enumeration provides additional options for aligning items in a container.
-    ///     </para>
-    /// </remarks>
-    /// <example>
-    ///     <c>
-    ///         |111 2222 33333    |
-    ///     </c>
-    /// </example>
     Start = 0,
 
     /// <summary>
     ///     The items will be aligned to the end (right or bottom) of the container.
+    ///     <remarks>
+    ///         <para>
+    ///             If the container is smaller than the total size of the items, the start items will be clipped (their
+    ///             locations
+    ///             will be negative).
+    ///         </para>
+    ///         <para>
+    ///             The <see cref="AlignmentModes"/> enumeration provides additional options for aligning items in a container.
+    ///         </para>
+    ///     </remarks>
+    ///     <example>
+    ///         <c>
+    ///             |    111 2222 33333|
+    ///         </c>
+    ///     </example>
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         If the container is smaller than the total size of the items, the start items will be clipped (their locations
-    ///         will be negative).
-    ///     </para>
-    ///     <para>
-    ///         The <see cref="AlignmentModes"/> enumeration provides additional options for aligning items in a container.
-    ///     </para>
-    /// </remarks>
-    /// <example>
-    ///     <c>
-    ///         |    111 2222 33333|
-    ///     </c>
-    /// </example>
     End,
 
     /// <summary>
     ///     Center in the available space.
+    ///     <remarks>
+    ///         <para>
+    ///             If centering is not possible, the group will be left-aligned.
+    ///         </para>
+    ///         <para>
+    ///             Extra space will be distributed between the items, biased towards the left.
+    ///         </para>
+    ///     </remarks>
+    ///     <example>
+    ///         <c>
+    ///             |  111 2222 33333  |
+    ///         </c>
+    ///     </example>
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///     If centering is not possible, the group will be left-aligned.
-    ///     </para>
-    ///     <para>
-    ///         Extra space will be distributed between the items, biased towards the left.
-    ///     </para>
-    /// </remarks>
-    /// <example>
-    ///     <c>
-    ///         |  111 2222 33333  |
-    ///     </c>
-    /// </example>
     Center,
 
     /// <summary>
     ///     The items will fill the available space.
+    ///     <remarks>
+    ///         <para>
+    ///             Extra space will be distributed between the items, biased towards the end.
+    ///         </para>
+    ///     </remarks>
+    ///     <example>
+    ///         <c>
+    ///             |111  2222    33333|
+    ///         </c>
+    ///     </example>
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         Extra space will be distributed between the items, biased towards the end.
-    ///     </para>
-    /// </remarks>
-    /// <example>
-    ///     <c>
-    ///        |111  2222    33333|
-    ///     </c>
-    /// </example>
-    Fill,
-}
+    Fill
+}

+ 8 - 14
Terminal.Gui/Drawing/AlignmentModes.cs

@@ -17,30 +17,24 @@ public enum AlignmentModes
     /// <summary>
     ///     The items will be arranged from end (right/bottom) to start (left/top).
     /// </summary>
-    /// <remarks>
-    ///     Not implemented.
-    /// </remarks>
     EndToStart = 1,
 
     /// <summary>
     ///     At least one space will be added between items. Useful for justifying text where at least one space is needed.
     /// </summary>
     /// <remarks>
-    ///     <para>
-    ///         If the total size of the items is greater than the container size, the space between items will be ignored
-    ///         starting from the end.
-    ///     </para>
+    ///     If the total size of the items is greater than the container size, the space between items will be ignored
+    ///     starting from the end.
     /// </remarks>
     AddSpaceBetweenItems = 2,
 
     /// <summary>
-    ///    When aligning via <see cref="Alignment.Start"/> or <see cref="Alignment.End"/>, the item opposite to the alignment (the first or last item) will be ignored.
+    ///     When aligning via <see cref="Alignment.Start"/> or <see cref="Alignment.End"/>, the item opposite to the alignment
+    ///     (the first or last item) will be ignored.
     /// </summary>
     /// <remarks>
-    ///     <para>
-    ///         If the container is smaller than the total size of the items, the end items will be clipped (their locations
-    ///         will be greater than the container size).
-    ///     </para>
+    ///     If the container is smaller than the total size of the items, the end items will be clipped (their locations
+    ///     will be greater than the container size).
     /// </remarks>
     /// <example>
     ///     <c>
@@ -48,5 +42,5 @@ public enum AlignmentModes
     ///         End:   |111     2222 33333|
     ///     </c>
     /// </example>
-    IgnoreFirstOrLast = 4,
-}
+    IgnoreFirstOrLast = 4
+}

+ 6 - 6
Terminal.Gui/Drawing/Attribute.cs

@@ -15,7 +15,7 @@ public readonly record struct Attribute : IEqualityOperators<Attribute, Attribut
 {
     /// <summary>Default empty attribute.</summary>
     [JsonIgnore]
-    public static Attribute Default => new (Color.White, ColorName.Black);
+    public static Attribute Default => new (Color.White, Color.Black);
 
     /// <summary>The <see cref="ConsoleDriver"/>-specific color value.</summary>
     [JsonIgnore (Condition = JsonIgnoreCondition.Always)]
@@ -65,28 +65,28 @@ public readonly record struct Attribute : IEqualityOperators<Attribute, Attribut
     }
 
     /// <summary>
-    ///     Initializes a new instance with a <see cref="ColorName"/> value. Both <see cref="Foreground"/> and
+    ///     Initializes a new instance with a <see cref="ColorName16"/> value. Both <see cref="Foreground"/> and
     ///     <see cref="Background"/> will be set to the specified color.
     /// </summary>
     /// <param name="colorName">Value.</param>
-    internal Attribute (in ColorName colorName) : this (in colorName, in colorName) { }
+    internal Attribute (in ColorName16 colorName) : this (in colorName, in colorName) { }
 
     /// <summary>Initializes a new instance of the <see cref="Attribute"/> struct.</summary>
     /// <param name="foregroundName">Foreground</param>
     /// <param name="backgroundName">Background</param>
-    public Attribute (in ColorName foregroundName, in ColorName backgroundName)
+    public Attribute (in ColorName16 foregroundName, in ColorName16 backgroundName)
         : this (new Color (in foregroundName), new Color (in backgroundName))
     { }
 
     /// <summary>Initializes a new instance of the <see cref="Attribute"/> struct.</summary>
     /// <param name="foregroundName">Foreground</param>
     /// <param name="background">Background</param>
-    public Attribute (in ColorName foregroundName, in Color background) : this (new Color (in foregroundName), in background) { }
+    public Attribute (in ColorName16 foregroundName, in Color background) : this (new Color (in foregroundName), in background) { }
 
     /// <summary>Initializes a new instance of the <see cref="Attribute"/> struct.</summary>
     /// <param name="foreground">Foreground</param>
     /// <param name="backgroundName">Background</param>
-    public Attribute (in Color foreground, in ColorName backgroundName) : this (in foreground, new Color (in backgroundName)) { }
+    public Attribute (in Color foreground, in ColorName16 backgroundName) : this (in foreground, new Color (in backgroundName)) { }
 
     /// <summary>
     ///     Initializes a new instance of the <see cref="Attribute"/> struct with the same colors for the foreground and

+ 47 - 44
Terminal.Gui/Drawing/Color.ColorExtensions.cs

@@ -1,72 +1,75 @@
 using System.Collections.Frozen;
+using ColorHelper;
 
 namespace Terminal.Gui;
 
 internal static class ColorExtensions
 {
-    private static FrozenDictionary<Color, ColorName> colorToNameMap;
+    // TODO: This should be refactored to support all W3CColors (`ColorStrings` and this should be merged).
+    // TODO: ColorName and AnsiColorCode are only needed when a driver is in Force16Color mode and we
+    // TODO: should be able to remove these from any non-Driver-specific usages.
+    private static FrozenDictionary<Color, ColorName16> colorToNameMap;
 
     static ColorExtensions ()
     {
-        Dictionary<ColorName, AnsiColorCode> nameToCodeMap = new ()
+        Dictionary<ColorName16, AnsiColorCode> nameToCodeMap = new ()
         {
-            { ColorName.Black, AnsiColorCode.BLACK },
-            { ColorName.Blue, AnsiColorCode.BLUE },
-            { ColorName.Green, AnsiColorCode.GREEN },
-            { ColorName.Cyan, AnsiColorCode.CYAN },
-            { ColorName.Red, AnsiColorCode.RED },
-            { ColorName.Magenta, AnsiColorCode.MAGENTA },
-            { ColorName.Yellow, AnsiColorCode.YELLOW },
-            { ColorName.Gray, AnsiColorCode.WHITE },
-            { ColorName.DarkGray, AnsiColorCode.BRIGHT_BLACK },
-            { ColorName.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
-            { ColorName.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
-            { ColorName.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
-            { ColorName.BrightRed, AnsiColorCode.BRIGHT_RED },
-            { ColorName.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
-            { ColorName.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
-            { ColorName.White, AnsiColorCode.BRIGHT_WHITE }
+            { ColorName16.Black, AnsiColorCode.BLACK },
+            { ColorName16.Blue, AnsiColorCode.BLUE },
+            { ColorName16.Green, AnsiColorCode.GREEN },
+            { ColorName16.Cyan, AnsiColorCode.CYAN },
+            { ColorName16.Red, AnsiColorCode.RED },
+            { ColorName16.Magenta, AnsiColorCode.MAGENTA },
+            { ColorName16.Yellow, AnsiColorCode.YELLOW },
+            { ColorName16.Gray, AnsiColorCode.WHITE },
+            { ColorName16.DarkGray, AnsiColorCode.BRIGHT_BLACK },
+            { ColorName16.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
+            { ColorName16.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
+            { ColorName16.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
+            { ColorName16.BrightRed, AnsiColorCode.BRIGHT_RED },
+            { ColorName16.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
+            { ColorName16.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
+            { ColorName16.White, AnsiColorCode.BRIGHT_WHITE }
         };
-        ColorNameToAnsiColorMap = nameToCodeMap.ToFrozenDictionary ();
+        ColorName16ToAnsiColorMap = nameToCodeMap.ToFrozenDictionary ();
 
-        ColorToNameMap = new Dictionary<Color, ColorName>
+        ColorToName16Map = new Dictionary<Color, ColorName16>
         {
-            // using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
-            // See also: https://en.wikipedia.org/wiki/ANSI_escape_code
-            { new Color (12, 12, 12), ColorName.Black },
-            { new Color (0, 55, 218), ColorName.Blue },
-            { new Color (19, 161, 14), ColorName.Green },
-            { new Color (58, 150, 221), ColorName.Cyan },
-            { new Color (197, 15, 31), ColorName.Red },
-            { new Color (136, 23, 152), ColorName.Magenta },
-            { new Color (128, 64, 32), ColorName.Yellow },
-            { new Color (204, 204, 204), ColorName.Gray },
-            { new Color (118, 118, 118), ColorName.DarkGray },
-            { new Color (59, 120, 255), ColorName.BrightBlue },
-            { new Color (22, 198, 12), ColorName.BrightGreen },
-            { new Color (97, 214, 214), ColorName.BrightCyan },
-            { new Color (231, 72, 86), ColorName.BrightRed },
-            { new Color (180, 0, 158), ColorName.BrightMagenta },
-            { new Color (249, 241, 165), ColorName.BrightYellow },
-            { new Color (242, 242, 242), ColorName.White }
+            // These match the values in Strings.resx, which are the W3C colors.
+            { new Color(0, 0, 0), ColorName16.Black },
+            { new Color(0, 0, 255), ColorName16.Blue },
+            { new Color(0, 128, 0), ColorName16.Green },
+            { new Color(0, 255, 255), ColorName16.Cyan },
+            { new Color(255, 0, 0), ColorName16.Red },
+            { new Color(255, 0, 255), ColorName16.Magenta },
+            { new Color(255, 255, 0), ColorName16.Yellow },
+            { new Color(128, 128, 128), ColorName16.Gray },
+            { new Color(118, 118, 118), ColorName16.DarkGray },
+            { new Color(59, 120, 255), ColorName16.BrightBlue },
+            { new Color(22, 198, 12), ColorName16.BrightGreen },
+            { new Color(97, 214, 214), ColorName16.BrightCyan },
+            { new Color(231, 72, 86), ColorName16.BrightRed },
+            { new Color(180, 0, 158), ColorName16.BrightMagenta },
+            { new Color(249, 241, 165), ColorName16.BrightYellow },
+            { new Color(255, 255, 255), ColorName16.White }
         }.ToFrozenDictionary ();
     }
 
     /// <summary>Defines the 16 legacy color names and their corresponding ANSI color codes.</summary>
-    internal static FrozenDictionary<ColorName, AnsiColorCode> ColorNameToAnsiColorMap { get; }
+    internal static FrozenDictionary<ColorName16, AnsiColorCode> ColorName16ToAnsiColorMap { get; }
 
-    /// <summary>Reverse mapping for <see cref="ColorToNameMap"/>.</summary>
-    internal static FrozenDictionary<ColorName, Color> ColorNameToColorMap { get; private set; }
+    /// <summary>Reverse mapping for <see cref="ColorToName16Map"/>.</summary>
+    internal static FrozenDictionary<ColorName16, Color> ColorName16ToColorMap { get; private set; }
 
     /// <summary>
     ///     Gets or sets a <see cref="FrozenDictionary{TKey,TValue}"/> that maps legacy 16-color values to the
-    ///     corresponding <see cref="ColorName"/>.
+    ///     corresponding <see cref="ColorName16"/>.
     /// </summary>
     /// <remarks>
     ///     Setter should be called as infrequently as possible, as <see cref="FrozenDictionary{TKey,TValue}"/> is
     ///     expensive to create.
     /// </remarks>
-    internal static FrozenDictionary<Color, ColorName> ColorToNameMap
+    internal static FrozenDictionary<Color, ColorName16> ColorToName16Map
     {
         get => colorToNameMap;
         set
@@ -74,7 +77,7 @@ internal static class ColorExtensions
             colorToNameMap = value;
 
             //Also be sure to set the reverse mapping
-            ColorNameToColorMap = value.ToFrozenDictionary (static kvp => kvp.Value, static kvp => kvp.Key);
+            ColorName16ToColorMap = value.ToFrozenDictionary (static kvp => kvp.Value, static kvp => kvp.Key);
         }
     }
 }

+ 2 - 2
Terminal.Gui/Drawing/Color.ColorName.cs

@@ -8,10 +8,10 @@ namespace Terminal.Gui;
 ///     <para>These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.</para>
 ///     <para>
 ///         For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be
-///         configured using the <see cref="Color.Colors"/> property.
+///         configured using the <see cref="Color.Colors16"/> property.
 ///     </para>
 /// </remarks>
-public enum ColorName
+public enum ColorName16
 {
     /// <summary>The black color. ANSI escape sequence: <c>\u001b[30m</c>.</summary>
     Black,

+ 180 - 177
Terminal.Gui/Drawing/Color.Formatting.cs

@@ -97,49 +97,49 @@ public readonly partial record struct Color
     )
     {
         return (formatString, formatProvider) switch
-               {
-                   // Null or empty string and null formatProvider - Revert to 'g' case behavior
-                   (null or { Length: 0 }, null) => ToString (),
-
-                   // Null or empty string and formatProvider is an ICustomColorFormatter - Output according to the given ICustomColorFormatted, with R, G, B, and A as typed arguments
-                   (null or { Length: 0 }, ICustomColorFormatter ccf) => ccf.Format (null, R, G, B, A),
-
-                   // Null or empty string and formatProvider is otherwise non-null but not the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
-                   (null or { Length: 0 }, { }) when !Equals (formatProvider, CultureInfo.InvariantCulture) =>
-                       string.Format (formatProvider, formatString ?? string.Empty, R, G, B, A),
-
-                   // Null or empty string and formatProvider is the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
-                   (null or { Length: 0 }, { }) when Equals (formatProvider, CultureInfo.InvariantCulture) =>
-                       $"#{R:X2}{G:X2}{B:X2}",
-
-                   // Non-null string and non-null formatProvider - let formatProvider handle it and give it R, G, B, and A
-                   ({ }, { }) => string.Format (formatProvider, CompositeFormat.Parse (formatString), R, G, B, A),
-
-                   // g format string and null formatProvider - Output as 24-bit hex according to invariant culture rules from R, G, and B
-                   (['g'], null) => ToString (),
-
-                   // G format string and null formatProvider - Output as 32-bit hex according to invariant culture rules from Argb
-                   (['G'], null) => $"#{A:X2}{R:X2}{G:X2}{B:X2}",
-
-                   // d format string and null formatProvider - Output as 24-bit decimal rgb(r,g,b) according to invariant culture rules from R, G, and B
-                   (['d'], null) => $"rgb({R:D},{G:D},{B:D})",
-
-                   // D format string and null formatProvider - Output as 32-bit decimal rgba(r,g,b,a) according to invariant culture rules from R, G, B, and A. Non-standard: a is a decimal byte value.
-                   (['D'], null) => $"rgba({R:D},{G:D},{B:D},{A:D})",
-
-                   // All other cases (formatString is not null here) - Delegate to formatProvider, first, and otherwise to invariant culture, and try to format the provided string from the channels
-                   ({ }, _) => string.Format (
-                                              formatProvider ?? CultureInfo.InvariantCulture,
-                                              CompositeFormat.Parse (formatString),
-                                              R,
-                                              G,
-                                              B,
-                                              A
-                                             ),
-                   _ => throw new InvalidOperationException (
-                                                             $"Unable to create string from Color with value {Argb}, using format string {formatString}"
-                                                            )
-               }
+        {
+            // Null or empty string and null formatProvider - Revert to 'g' case behavior
+            (null or { Length: 0 }, null) => ToString (),
+
+            // Null or empty string and formatProvider is an ICustomColorFormatter - Output according to the given ICustomColorFormatted, with R, G, B, and A as typed arguments
+            (null or { Length: 0 }, ICustomColorFormatter ccf) => ccf.Format (null, R, G, B, A),
+
+            // Null or empty string and formatProvider is otherwise non-null but not the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
+            (null or { Length: 0 }, { }) when !Equals (formatProvider, CultureInfo.InvariantCulture) =>
+                string.Format (formatProvider, formatString ?? string.Empty, R, G, B, A),
+
+            // Null or empty string and formatProvider is the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
+            (null or { Length: 0 }, { }) when Equals (formatProvider, CultureInfo.InvariantCulture) =>
+                $"#{R:X2}{G:X2}{B:X2}",
+
+            // Non-null string and non-null formatProvider - let formatProvider handle it and give it R, G, B, and A
+            ({ }, { }) => string.Format (formatProvider, CompositeFormat.Parse (formatString), R, G, B, A),
+
+            // g format string and null formatProvider - Output as 24-bit hex according to invariant culture rules from R, G, and B
+            ( ['g'], null) => ToString (),
+
+            // G format string and null formatProvider - Output as 32-bit hex according to invariant culture rules from Argb
+            ( ['G'], null) => $"#{A:X2}{R:X2}{G:X2}{B:X2}",
+
+            // d format string and null formatProvider - Output as 24-bit decimal rgb(r,g,b) according to invariant culture rules from R, G, and B
+            ( ['d'], null) => $"rgb({R:D},{G:D},{B:D})",
+
+            // D format string and null formatProvider - Output as 32-bit decimal rgba(r,g,b,a) according to invariant culture rules from R, G, B, and A. Non-standard: a is a decimal byte value.
+            ( ['D'], null) => $"rgba({R:D},{G:D},{B:D},{A:D})",
+
+            // All other cases (formatString is not null here) - Delegate to formatProvider, first, and otherwise to invariant culture, and try to format the provided string from the channels
+            ({ }, _) => string.Format (
+                                       formatProvider ?? CultureInfo.InvariantCulture,
+                                       CompositeFormat.Parse (formatString),
+                                       R,
+                                       G,
+                                       B,
+                                       A
+                                      ),
+            _ => throw new InvalidOperationException (
+                                                      $"Unable to create string from Color with value {Argb}, using format string {formatString}"
+                                                     )
+        }
                ?? throw new InvalidOperationException (
                                                        $"Unable to create string from Color with value {Argb}, using format string {formatString}"
                                                       );
@@ -205,7 +205,7 @@ public readonly partial record struct Color
     /// <summary>Converts the provided <see langword="string"/> to a new <see cref="Color"/> value.</summary>
     /// <param name="text">
     ///     The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
-    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName"/> string values.
+    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="ColorName16"/> string values.
     /// </param>
     /// <param name="formatProvider">
     ///     If specified and not <see langword="null"/>, will be passed to
@@ -246,7 +246,7 @@ public readonly partial record struct Color
     /// </summary>
     /// <param name="text">
     ///     The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#RGBA", "#AARRGGBB", "rgb(r,g,b)",
-    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName"/> string values.
+    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="ColorName16"/> string values.
     /// </param>
     /// <param name="formatProvider">
     ///     Optional <see cref="IFormatProvider"/> to provide parsing services for the input text.
@@ -265,95 +265,95 @@ public readonly partial record struct Color
     public static Color Parse (ReadOnlySpan<char> text, IFormatProvider? formatProvider = null)
     {
         return text switch
-               {
-                   // Null string or empty span provided
-                   { IsEmpty: true } when formatProvider is null => throw new ColorParseException (
-                                                                                                   in text,
-                                                                                                   "The text provided was null or empty.",
-                                                                                                   in text
-                                                                                                  ),
-
-                   // A valid ICustomColorFormatter was specified and the text wasn't null or empty
-                   { IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text),
-
-                   // Input string is only whitespace
-                   { Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException (
-                                                                                               in text,
-                                                                                               "The text provided consisted of only whitespace characters.",
-                                                                                               in text
-                                                                                              ),
-
-                   // Any string too short to possibly be any supported format.
-                   { Length: > 0 and < 3 } => throw new ColorParseException (
-                                                                             in text,
-                                                                             "Text was too short to be any possible supported format.",
-                                                                             in text
-                                                                            ),
-
-                   // The various hexadecimal cases
-                   ['#', ..] hexString => hexString switch
-                                          {
-                                              // #RGB
-                                              ['#', var rChar, var gChar, var bChar] chars when chars [1..]
-                                                      .IsAllAsciiHexDigits () =>
-                                                  new Color (
-                                                             byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
-                                                             byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
-                                                             byte.Parse ([bChar, bChar], NumberStyles.HexNumber)
-                                                            ),
-
-                                              // #ARGB
-                                              ['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..]
-                                                      .IsAllAsciiHexDigits () =>
-                                                  new Color (
-                                                             byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
-                                                             byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
-                                                             byte.Parse ([bChar, bChar], NumberStyles.HexNumber),
-                                                             byte.Parse ([aChar, aChar], NumberStyles.HexNumber)
-                                                            ),
-
-                                              // #RRGGBB
-                                              [
-                                                      '#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char,
-                                                      var b2Char
-                                                  ] chars when chars [1..].IsAllAsciiHexDigits () =>
-                                                  new Color (
-                                                             byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
-                                                             byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
-                                                             byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)
-                                                            ),
-
-                                              // #AARRGGBB
-                                              [
-                                                      '#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char,
-                                                      var g2Char, var b1Char, var b2Char
-                                                  ] chars when chars [1..].IsAllAsciiHexDigits () =>
-                                                  new Color (
-                                                             byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
-                                                             byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
-                                                             byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber),
-                                                             byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber)
-                                                            ),
-                                              _ => throw new ColorParseException (
-                                                                                  in hexString,
-                                                                                  $"Color hex string {hexString} was not in a supported format",
-                                                                                  in hexString
-                                                                                 )
-                                          },
-
-                   // rgb(r,g,b) or rgb(r,g,b,a)
-                   ['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
-
-                   // rgba(r,g,b,a) or rgba(r,g,b)
-                   ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
-
-                   // Attempt to parse as a named color from the ColorName enum
-                   { } when char.IsLetter (text [0]) && Enum.TryParse (text, true, out ColorName colorName) =>
-                       new Color (colorName),
-
-                   // Any other input
-                   _ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, [])
-               };
+        {
+            // Null string or empty span provided
+            { IsEmpty: true } when formatProvider is null => throw new ColorParseException (
+                                                                                            in text,
+                                                                                            "The text provided was null or empty.",
+                                                                                            in text
+                                                                                           ),
+
+            // A valid ICustomColorFormatter was specified and the text wasn't null or empty
+            { IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text),
+
+            // Input string is only whitespace
+            { Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException (
+                                                                                        in text,
+                                                                                        "The text provided consisted of only whitespace characters.",
+                                                                                        in text
+                                                                                       ),
+
+            // Any string too short to possibly be any supported format.
+            { Length: > 0 and < 3 } => throw new ColorParseException (
+                                                                      in text,
+                                                                      "Text was too short to be any possible supported format.",
+                                                                      in text
+                                                                     ),
+
+                                                                     // The various hexadecimal cases
+                                                                     ['#', ..] hexString => hexString switch
+                                                                     {
+                                                                     // #RGB
+                                                                     ['#', var rChar, var gChar, var bChar] chars when chars [1..]
+                                                                                    .IsAllAsciiHexDigits () =>
+                                                                                new Color (
+                                                                                           byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
+                                                                                           byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
+                                                                                           byte.Parse ([bChar, bChar], NumberStyles.HexNumber)
+                                                                                          ),
+
+                                                                                          // #ARGB
+                                                                                          ['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..]
+                                                                                                         .IsAllAsciiHexDigits () =>
+                                                                                                     new Color (
+                                                                                                                byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
+                                                                                                                byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
+                                                                                                                byte.Parse ([bChar, bChar], NumberStyles.HexNumber),
+                                                                                                                byte.Parse ([aChar, aChar], NumberStyles.HexNumber)
+                                                                                                               ),
+
+                                                                                                               // #RRGGBB
+                                                                                                               [
+                                                                                         '#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char,
+                                                                                         var b2Char
+                                                                                     ] chars when chars [1..].IsAllAsciiHexDigits () =>
+                                                                                     new Color (
+                                                                                                byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
+                                                                                                byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
+                                                                                                byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)
+                                                                                               ),
+
+                                                                                               // #AARRGGBB
+                                                                                               [
+                                                                                         '#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char,
+                                                                                         var g2Char, var b1Char, var b2Char
+                                                                                     ] chars when chars [1..].IsAllAsciiHexDigits () =>
+                                                                                     new Color (
+                                                                                                byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
+                                                                                                byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
+                                                                                                byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber),
+                                                                                                byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber)
+                                                                                               ),
+                                                                         _ => throw new ColorParseException (
+                                                                                                                    in hexString,
+                                                                                                                    $"Color hex string {hexString} was not in a supported format",
+                                                                                                                    in hexString
+                                                                                                                   )
+                                                                     },
+
+                                                                     // rgb(r,g,b) or rgb(r,g,b,a)
+                                                                     ['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
+
+                                                                     // rgba(r,g,b,a) or rgba(r,g,b)
+                                                                     ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
+
+            // Attempt to parse as a named color from the ColorStrings resources
+            { } when char.IsLetter (text [0]) && ColorStrings.TryParseW3CColorName (text.ToString (), out Color color) =>
+                new Color (color),
+
+            // Any other input
+            _ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, [])
+        };
 
         [Pure]
         [SkipLocalsInit]
@@ -372,44 +372,44 @@ public readonly partial record struct Color
             switch (rangeCount)
             {
                 case 3:
-                {
-                    // rgba(r,g,b)
-                    ParseRgbValues (
-                                    in valuesSubstring,
-                                    in valueRanges,
-                                    in originalString,
-                                    out ReadOnlySpan<char> rSpan,
-                                    out ReadOnlySpan<char> gSpan,
-                                    out ReadOnlySpan<char> bSpan
-                                   );
-
-                    return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan));
-                }
+                    {
+                        // rgba(r,g,b)
+                        ParseRgbValues (
+                                        in valuesSubstring,
+                                        in valueRanges,
+                                        in originalString,
+                                        out ReadOnlySpan<char> rSpan,
+                                        out ReadOnlySpan<char> gSpan,
+                                        out ReadOnlySpan<char> bSpan
+                                       );
+
+                        return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan));
+                    }
                 case 4:
-                {
-                    // rgba(r,g,b,a)
-                    ParseRgbValues (
-                                    in valuesSubstring,
-                                    in valueRanges,
-                                    in originalString,
-                                    out ReadOnlySpan<char> rSpan,
-                                    out ReadOnlySpan<char> gSpan,
-                                    out ReadOnlySpan<char> bSpan
-                                   );
-                    ReadOnlySpan<char> aSpan = valuesSubstring [valueRanges [3]];
-
-                    if (!aSpan.IsAllAsciiDigits ())
                     {
-                        throw new ColorParseException (
-                                                       in originalString,
-                                                       "Value was not composed entirely of decimal digits.",
-                                                       in aSpan,
-                                                       nameof (A)
-                                                      );
+                        // rgba(r,g,b,a)
+                        ParseRgbValues (
+                                        in valuesSubstring,
+                                        in valueRanges,
+                                        in originalString,
+                                        out ReadOnlySpan<char> rSpan,
+                                        out ReadOnlySpan<char> gSpan,
+                                        out ReadOnlySpan<char> bSpan
+                                       );
+                        ReadOnlySpan<char> aSpan = valuesSubstring [valueRanges [3]];
+
+                        if (!aSpan.IsAllAsciiDigits ())
+                        {
+                            throw new ColorParseException (
+                                                           in originalString,
+                                                           "Value was not composed entirely of decimal digits.",
+                                                           in aSpan,
+                                                           nameof (A)
+                                                          );
+                        }
+
+                        return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan), int.Parse (aSpan));
                     }
-
-                    return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan), int.Parse (aSpan));
-                }
                 default:
                     throw new ColorParseException (
                                                    in originalString,
@@ -471,7 +471,7 @@ public readonly partial record struct Color
     /// <summary>Converts the provided <see langword="string"/> to a new <see cref="Color"/> value.</summary>
     /// <param name="text">
     ///     The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
-    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="GetClosestNamedColor (Color)"/> string
+    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="GetClosestNamedColor16(Terminal.Gui.Color)"/> string
     ///     values.
     /// </param>
     /// <param name="formatProvider">
@@ -501,7 +501,7 @@ public readonly partial record struct Color
     /// </summary>
     /// <param name="text">
     ///     The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
-    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="GetClosestNamedColor (Color)"/> string
+    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any W3C color name."/> string
     ///     values.
     /// </param>
     /// <param name="formatProvider">
@@ -585,17 +585,20 @@ public readonly partial record struct Color
     [SkipLocalsInit]
     public override string ToString ()
     {
-        // If Values has an exact match with a named color (in _colorNames), use that.
-        return ColorExtensions.ColorToNameMap.TryGetValue (this, out ColorName colorName)
-                   ? Enum.GetName (typeof (ColorName), colorName) ?? $"#{R:X2}{G:X2}{B:X2}"
-                   : // Otherwise return as an RGB hex value.
-                   $"#{R:X2}{G:X2}{B:X2}";
+        string? name = ColorStrings.GetW3CColorName (this);
+
+        if (name is { })
+        {
+            return name;
+        }
+
+        return $"#{R:X2}{G:X2}{B:X2}";
     }
 
     /// <summary>Converts the provided string to a new <see cref="Color"/> instance.</summary>
     /// <param name="text">
     ///     The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
-    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName"/> string values.
+    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="ColorName16"/> string values.
     /// </param>
     /// <param name="color">The parsed value.</param>
     /// <returns>A boolean value indicating whether parsing was successful.</returns>

+ 3 - 3
Terminal.Gui/Drawing/Color.Operators.cs

@@ -53,11 +53,11 @@ public readonly partial record struct Color
     public static implicit operator Color (uint u) { return new Color (u); }
 
     /// <summary>
-    ///     Implicit conversion from <see cref="GetClosestNamedColor (Color)"/> to <see cref="Color"/> via lookup from
-    ///     <see cref="ColorExtensions.ColorNameToColorMap"/>.
+    ///     Implicit conversion from <see cref="GetClosestNamedColor16(Terminal.Gui.Color)"/> to <see cref="Color"/> via lookup from
+    ///     <see cref="ColorExtensions.ColorName16ToColorMap"/>.
     /// </summary>
     [Pure]
-    public static implicit operator Color (ColorName colorName) { return ColorExtensions.ColorNameToColorMap [colorName]; }
+    public static implicit operator Color (ColorName16 colorName) { return ColorExtensions.ColorName16ToColorMap [colorName]; }
 
     /// <summary>
     ///     Implicit conversion from <see cref="Vector4"/> to <see cref="Color"/>, where (<see cref="Vector4.X"/>,

+ 40 - 40
Terminal.Gui/Drawing/Color.cs

@@ -17,7 +17,7 @@ namespace Terminal.Gui;
 /// </summary>
 /// <seealso cref="Attribute"/>
 /// <seealso cref="ColorExtensions"/>
-/// <seealso cref="ColorName"/>
+/// <seealso cref="ColorName16"/>
 [JsonConverter (typeof (ColorJsonConverter))]
 [StructLayout (LayoutKind.Explicit)]
 public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanParsable<Color>, ISpanFormattable,
@@ -109,7 +109,7 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
 
     /// <summary>Initializes a new instance of the <see cref="Color"/> color from a legacy 16-color named value.</summary>
     /// <param name="colorName">The 16-color value.</param>
-    public Color (in ColorName colorName) { this = ColorExtensions.ColorNameToColorMap [colorName]; }
+    public Color (in ColorName16 colorName) { this = ColorExtensions.ColorName16ToColorMap [colorName]; }
 
     /// <summary>
     ///     Initializes a new instance of the <see cref="Color"/> color from string. See
@@ -131,26 +131,28 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
     /// <summary>Initializes a new instance of the <see cref="Color"/> with all channels set to 0.</summary>
     public Color () { Argb = 0u; }
 
+    // TODO: ColorName and AnsiColorCode are only needed when a driver is in Force16Color mode and we
+    // TODO: should be able to remove these from any non-Driver-specific usages.
     /// <summary>Gets or sets the 3-byte/6-character hexadecimal value for each of the legacy 16-color values.</summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
-    public static Dictionary<ColorName, string> Colors
+    public static Dictionary<ColorName16, string> Colors16
     {
         get =>
 
             // Transform _colorToNameMap into a Dictionary<ColorNames,string>
-            ColorExtensions.ColorToNameMap.ToDictionary (static kvp => kvp.Value, static kvp => kvp.Key.ToString ("g"));
+            ColorExtensions.ColorToName16Map.ToDictionary (static kvp => kvp.Value, static kvp => kvp.Key.ToString ("g"));
         set
         {
             // Transform Dictionary<ColorNames,string> into _colorToNameMap
-            ColorExtensions.ColorToNameMap = value.ToFrozenDictionary (GetColorToNameMapKey, GetColorToNameMapValue);
+            ColorExtensions.ColorToName16Map = value.ToFrozenDictionary (GetColorToNameMapKey, GetColorToNameMapValue);
 
             return;
 
-            static Color GetColorToNameMapKey (KeyValuePair<ColorName, string> kvp) { return new Color (kvp.Value); }
+            static Color GetColorToNameMapKey (KeyValuePair<ColorName16, string> kvp) { return new Color (kvp.Value); }
 
-            static ColorName GetColorToNameMapValue (KeyValuePair<ColorName, string> kvp)
+            static ColorName16 GetColorToNameMapValue (KeyValuePair<ColorName16, string> kvp)
             {
-                return Enum.TryParse (kvp.Key.ToString (), true, out ColorName colorName)
+                return Enum.TryParse (kvp.Key.ToString (), true, out ColorName16 colorName)
                            ? colorName
                            : throw new ArgumentException ($"Invalid color name: {kvp.Key}");
             }
@@ -158,31 +160,31 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
     }
 
     /// <summary>
-    ///     Gets the <see cref="Color"/> using a legacy 16-color <see cref="ColorName"/> value. <see langword="get"/> will
+    ///     Gets the <see cref="Color"/> using a legacy 16-color <see cref="ColorName16"/> value. <see langword="get"/> will
     ///     return the closest 16 color match to the true color when no exact value is found.
     /// </summary>
     /// <remarks>
-    ///     Get returns the <see cref="GetClosestNamedColor (Color)"/> of the closest 24-bit color value. Set sets the RGB
+    ///     Get returns the <see cref="GetClosestNamedColor16(Color)"/> of the closest 24-bit color value. Set sets the RGB
     ///     value using a hard-coded map.
     /// </remarks>
-    public AnsiColorCode GetAnsiColorCode () { return ColorExtensions.ColorNameToAnsiColorMap [GetClosestNamedColor ()]; }
+    public AnsiColorCode GetAnsiColorCode () { return ColorExtensions.ColorName16ToAnsiColorMap [GetClosestNamedColor16 ()]; }
 
     /// <summary>
-    ///     Gets the <see cref="Color"/> using a legacy 16-color <see cref="Gui.ColorName"/> value. <see langword="get"/>
+    ///     Gets the <see cref="Color"/> using a legacy 16-color <see cref="ColorName16"/> value. <see langword="get"/>
     ///     will return the closest 16 color match to the true color when no exact value is found.
     /// </summary>
     /// <remarks>
-    ///     Get returns the <see cref="GetClosestNamedColor (Color)"/> of the closest 24-bit color value. Set sets the RGB
+    ///     Get returns the <see cref="GetClosestNamedColor16(Terminal.Gui.Color)"/> of the closest 24-bit color value. Set sets the RGB
     ///     value using a hard-coded map.
     /// </remarks>
-    public ColorName GetClosestNamedColor () { return GetClosestNamedColor (this); }
+    public ColorName16 GetClosestNamedColor16 () { return GetClosestNamedColor16 (this); }
 
     /// <summary>
     ///     Determines if the closest named <see cref="Color"/> to <see langword="this"/> is the provided
     ///     <paramref name="namedColor"/>.
     /// </summary>
     /// <param name="namedColor">
-    ///     The <see cref="GetClosestNamedColor (Color)"/> to check if this <see cref="Color"/> is closer
+    ///     The <see cref="GetClosestNamedColor16(Terminal.Gui.Color)"/> to check if this <see cref="Color"/> is closer
     ///     to than any other configured named color.
     /// </param>
     /// <returns>
@@ -195,18 +197,18 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
     /// </remarks>
     [Pure]
     [MethodImpl (MethodImplOptions.AggressiveInlining)]
-    public bool IsClosestToNamedColor (in ColorName namedColor) { return GetClosestNamedColor () == namedColor; }
+    public bool IsClosestToNamedColor16 (in ColorName16 namedColor) { return GetClosestNamedColor16 () == namedColor; }
 
     /// <summary>
     ///     Determines if the closest named <see cref="Color"/> to <paramref name="color"/>/> is the provided
     ///     <paramref name="namedColor"/>.
     /// </summary>
     /// <param name="color">
-    ///     The color to test against the <see cref="GetClosestNamedColor (Color)"/> value in
+    ///     The color to test against the <see cref="GetClosestNamedColor16(Terminal.Gui.Color)"/> value in
     ///     <paramref name="namedColor"/>.
     /// </param>
     /// <param name="namedColor">
-    ///     The <see cref="GetClosestNamedColor (Color)"/> to check if this <see cref="Color"/> is closer
+    ///     The <see cref="GetClosestNamedColor16(Terminal.Gui.Color)"/> to check if this <see cref="Color"/> is closer
     ///     to than any other configured named color.
     /// </param>
     /// <returns>
@@ -220,20 +222,18 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
     /// </remarks>
     [Pure]
     [MethodImpl (MethodImplOptions.AggressiveInlining)]
-    public static bool IsColorClosestToNamedColor (in Color color, in ColorName namedColor) { return color.IsClosestToNamedColor (in namedColor); }
+    public static bool IsColorClosestToNamedColor16 (in Color color, in ColorName16 namedColor) { return color.IsClosestToNamedColor16 (in namedColor); }
 
     /// <summary>Gets the "closest" named color to this <see cref="Color"/> value.</summary>
     /// <param name="inputColor"></param>
     /// <remarks>
     ///     Distance is defined here as the Euclidean distance between each color interpreted as a <see cref="Vector3"/>.
-    ///     <para/>
-    ///     The order of the values in the passed Vector3 must be
     /// </remarks>
     /// <returns></returns>
     [SkipLocalsInit]
-    internal static ColorName GetClosestNamedColor (Color inputColor)
+    internal static ColorName16 GetClosestNamedColor16 (Color inputColor)
     {
-        return ColorExtensions.ColorToNameMap.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).Value;
+        return ColorExtensions.ColorToName16Map.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).Value;
     }
 
     [SkipLocalsInit]
@@ -246,7 +246,7 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
     public Color GetHighlightColor ()
     {
         // TODO: This is a temporary implementation; just enough to show how it could work. 
-        var hsl = ColorHelper.ColorConverter.RgbToHsl(new RGB (R, G, B));
+        var hsl = ColorHelper.ColorConverter.RgbToHsl (new RGB (R, G, B));
 
         var amount = .7;
         if (hsl.L <= 5)
@@ -284,52 +284,52 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
     #region Legacy Color Names
 
     /// <summary>The black color.</summary>
-    public const ColorName Black = ColorName.Black;
+    public const ColorName16 Black = ColorName16.Black;
 
     /// <summary>The blue color.</summary>
-    public const ColorName Blue = ColorName.Blue;
+    public const ColorName16 Blue = ColorName16.Blue;
 
     /// <summary>The green color.</summary>
-    public const ColorName Green = ColorName.Green;
+    public const ColorName16 Green = ColorName16.Green;
 
     /// <summary>The cyan color.</summary>
-    public const ColorName Cyan = ColorName.Cyan;
+    public const ColorName16 Cyan = ColorName16.Cyan;
 
     /// <summary>The red color.</summary>
-    public const ColorName Red = ColorName.Red;
+    public const ColorName16 Red = ColorName16.Red;
 
     /// <summary>The magenta color.</summary>
-    public const ColorName Magenta = ColorName.Magenta;
+    public const ColorName16 Magenta = ColorName16.Magenta;
 
     /// <summary>The yellow color.</summary>
-    public const ColorName Yellow = ColorName.Yellow;
+    public const ColorName16 Yellow = ColorName16.Yellow;
 
     /// <summary>The gray color.</summary>
-    public const ColorName Gray = ColorName.Gray;
+    public const ColorName16 Gray = ColorName16.Gray;
 
     /// <summary>The dark gray color.</summary>
-    public const ColorName DarkGray = ColorName.DarkGray;
+    public const ColorName16 DarkGray = ColorName16.DarkGray;
 
     /// <summary>The bright bBlue color.</summary>
-    public const ColorName BrightBlue = ColorName.BrightBlue;
+    public const ColorName16 BrightBlue = ColorName16.BrightBlue;
 
     /// <summary>The bright green color.</summary>
-    public const ColorName BrightGreen = ColorName.BrightGreen;
+    public const ColorName16 BrightGreen = ColorName16.BrightGreen;
 
     /// <summary>The bright cyan color.</summary>
-    public const ColorName BrightCyan = ColorName.BrightCyan;
+    public const ColorName16 BrightCyan = ColorName16.BrightCyan;
 
     /// <summary>The bright red color.</summary>
-    public const ColorName BrightRed = ColorName.BrightRed;
+    public const ColorName16 BrightRed = ColorName16.BrightRed;
 
     /// <summary>The bright magenta color.</summary>
-    public const ColorName BrightMagenta = ColorName.BrightMagenta;
+    public const ColorName16 BrightMagenta = ColorName16.BrightMagenta;
 
     /// <summary>The bright yellow color.</summary>
-    public const ColorName BrightYellow = ColorName.BrightYellow;
+    public const ColorName16 BrightYellow = ColorName16.BrightYellow;
 
     /// <summary>The White color.</summary>
-    public const ColorName White = ColorName.White;
+    public const ColorName16 White = ColorName16.White;
 
     #endregion
 }

+ 43 - 55
Terminal.Gui/Drawing/ColorScheme.cs

@@ -15,12 +15,6 @@ namespace Terminal.Gui;
 [JsonConverter (typeof (ColorSchemeJsonConverter))]
 public record ColorScheme : IEqualityOperators<ColorScheme, ColorScheme, bool>
 {
-    private readonly Attribute _disabled;
-    private readonly Attribute _focus;
-    private readonly Attribute _hotFocus;
-    private readonly Attribute _hotNormal;
-    private readonly Attribute _normal;
-
     /// <summary>Creates a new instance set to the default colors (see <see cref="Attribute.Default"/>).</summary>
     public ColorScheme () : this (Attribute.Default) { }
 
@@ -30,22 +24,22 @@ public record ColorScheme : IEqualityOperators<ColorScheme, ColorScheme, bool>
     {
         ArgumentNullException.ThrowIfNull (scheme);
 
-        _normal = scheme.Normal;
-        _focus = scheme.Focus;
-        _hotNormal = scheme.HotNormal;
-        _disabled = scheme.Disabled;
-        _hotFocus = scheme.HotFocus;
+        Normal = scheme.Normal;
+        Focus = scheme.Focus;
+        HotNormal = scheme.HotNormal;
+        Disabled = scheme.Disabled;
+        HotFocus = scheme.HotFocus;
     }
 
     /// <summary>Creates a new instance, initialized with the values from <paramref name="attribute"/>.</summary>
     /// <param name="attribute">The attribute to initialize the new instance with.</param>
     public ColorScheme (Attribute attribute)
     {
-        _normal = attribute;
-        _focus = attribute;
-        _hotNormal = attribute;
-        _disabled = attribute;
-        _hotFocus = attribute;
+        Normal = attribute;
+        Focus = attribute;
+        HotNormal = attribute;
+        Disabled = attribute;
+        HotFocus = attribute;
     }
 
     /// <summary>Creates a new instance, initialized with the values provided.</summary>
@@ -54,48 +48,45 @@ public record ColorScheme : IEqualityOperators<ColorScheme, ColorScheme, bool>
         Attribute focus,
         Attribute hotNormal,
         Attribute disabled,
-        Attribute hotFocus)
+        Attribute hotFocus
+    )
     {
-        _normal = normal;
-        _focus = focus;
-        _hotNormal = hotNormal;
-        _disabled = disabled;
-        _hotFocus = hotFocus;
+        Normal = normal;
+        Focus = focus;
+        HotNormal = hotNormal;
+        Disabled = disabled;
+        HotFocus = hotFocus;
     }
 
     /// <summary>The default foreground and background color for text when the view is disabled.</summary>
-    public Attribute Disabled
-    {
-        get => _disabled;
-        init => _disabled = value;
-    }
+    public Attribute Disabled { get; init; }
 
     /// <summary>The foreground and background color for text when the view has the focus.</summary>
-    public Attribute Focus
-    {
-        get => _focus;
-        init => _focus = value;
-    }
+    public Attribute Focus { get; init; }
 
     /// <summary>The foreground and background color for text in a focused view that indicates a <see cref="View.HotKey"/>.</summary>
-    public Attribute HotFocus
-    {
-        get => _hotFocus;
-        init => _hotFocus = value;
-    }
+    public Attribute HotFocus { get; init; }
 
     /// <summary>The foreground and background color for text in a non-focused view that indicates a <see cref="View.HotKey"/>.</summary>
-    public Attribute HotNormal
-    {
-        get => _hotNormal;
-        init => _hotNormal = value;
-    }
+    public Attribute HotNormal { get; init; }
 
     /// <summary>The foreground and background color for text when the view is not focused, hot, or disabled.</summary>
-    public Attribute Normal
+    public Attribute Normal { get; init; }
+
+    /// <summary>
+    ///     Gets a new <see cref="ColorScheme"/> with the same values as this instance, but with the foreground and background
+    ///     colors adjusted to be more visible.
+    /// </summary>
+    /// <returns></returns>
+    public ColorScheme GetHighlightColorScheme ()
     {
-        get => _normal;
-        init => _normal = value;
+        return this with
+        {
+            Normal = new (Normal.Foreground.GetHighlightColor (), Normal.Background),
+            HotNormal = new (HotNormal.Foreground.GetHighlightColor (), HotNormal.Background),
+            Focus = new (Focus.Foreground.GetHighlightColor (), Focus.Background),
+            HotFocus = new (HotFocus.Foreground.GetHighlightColor (), HotFocus.Background)
+        };
     }
 
     /// <summary>Compares two <see cref="ColorScheme"/> objects for equality.</summary>
@@ -104,20 +95,17 @@ public record ColorScheme : IEqualityOperators<ColorScheme, ColorScheme, bool>
     public virtual bool Equals (ColorScheme? other)
     {
         return other is { }
-               && EqualityComparer<Attribute>.Default.Equals (_normal, other._normal)
-               && EqualityComparer<Attribute>.Default.Equals (_focus, other._focus)
-               && EqualityComparer<Attribute>.Default.Equals (_hotNormal, other._hotNormal)
-               && EqualityComparer<Attribute>.Default.Equals (_hotFocus, other._hotFocus)
-               && EqualityComparer<Attribute>.Default.Equals (_disabled, other._disabled);
+               && EqualityComparer<Attribute>.Default.Equals (Normal, other.Normal)
+               && EqualityComparer<Attribute>.Default.Equals (Focus, other.Focus)
+               && EqualityComparer<Attribute>.Default.Equals (HotNormal, other.HotNormal)
+               && EqualityComparer<Attribute>.Default.Equals (HotFocus, other.HotFocus)
+               && EqualityComparer<Attribute>.Default.Equals (Disabled, other.Disabled);
     }
 
     /// <summary>Returns a hashcode for this instance.</summary>
     /// <returns>hashcode for this instance</returns>
-    public override int GetHashCode ()
-    {
-        return HashCode.Combine (_normal, _focus, _hotNormal, _hotFocus, _disabled);
-    }
+    public override int GetHashCode () { return HashCode.Combine (Normal, Focus, HotNormal, HotFocus, Disabled); }
 
     /// <inheritdoc/>
     public override string ToString () { return $"Normal: {Normal}; Focus: {Focus}; HotNormal: {HotNormal}; HotFocus: {HotFocus}; Disabled: {Disabled}"; }
-}
+}

+ 30 - 27
Terminal.Gui/Drawing/ColorStrings.cs

@@ -1,6 +1,7 @@
 #nullable enable
 using System.Collections;
 using System.Globalization;
+using System.Resources;
 using Terminal.Gui.Resources;
 
 namespace Terminal.Gui;
@@ -10,6 +11,8 @@ namespace Terminal.Gui;
 /// </summary>
 public static class ColorStrings
 {
+    // PERFORMANCE: See https://stackoverflow.com/a/15521524/297526 for why GlobalResources.GetString is fast.
+
     /// <summary>
     ///     Gets the W3C standard string for <paramref name="color"/>.
     /// </summary>
@@ -17,7 +20,6 @@ public static class ColorStrings
     /// <returns><see langword="null"/> if there is no standard color name for the specified color.</returns>
     public static string? GetW3CColorName (Color color)
     {
-        // Fetch the color name from the resource file
         return GlobalResources.GetString ($"#{color.R:X2}{color.G:X2}{color.B:X2}", CultureInfo.CurrentUICulture);
     }
 
@@ -27,18 +29,18 @@ public static class ColorStrings
     /// <returns></returns>
     public static IEnumerable<string> GetW3CColorNames ()
     {
-        foreach (DictionaryEntry entry in GlobalResources.GetResourceSet (
-                                                                          CultureInfo.CurrentUICulture,
-                                                                          true,
-                                                                          true,
-                                                                          e =>
-                                                                          {
-                                                                              string keyName = e.Key.ToString () ?? string.Empty;
+        ResourceSet? resourceSet = GlobalResources.GetResourceSet (CultureInfo.CurrentUICulture, true, true);
+        if (resourceSet == null)
+        {
+            yield break;
+        }
 
-                                                                              return e.Value is string && keyName.StartsWith ('#');
-                                                                          })!)
+        foreach (DictionaryEntry entry in resourceSet)
         {
-            yield return (entry.Value as string)!;
+            if (entry is { Value: string colorName, Key: string key } && key.StartsWith ('#'))
+            {
+                yield return colorName;
+            }
         }
     }
 
@@ -50,30 +52,31 @@ public static class ColorStrings
     /// <returns><see langword="true"/> if <paramref name="name"/> was parsed successfully.</returns>
     public static bool TryParseW3CColorName (string name, out Color color)
     {
-        // Iterate through all resource entries to find the matching color name
         foreach (DictionaryEntry entry in GlobalResources.GetResourceSet (CultureInfo.CurrentUICulture, true, true)!)
         {
             if (entry.Value is string colorName && colorName.Equals (name, StringComparison.OrdinalIgnoreCase))
             {
-                // Parse the key to extract the color components
-                string key = entry.Key.ToString () ?? string.Empty;
+                return TryParseColorKey (entry.Key.ToString (), out color);
+            }
+        }
 
-                if (key.StartsWith ("#") && key.Length == 7)
-                {
-                    if (int.TryParse (key.Substring (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r)
-                        && int.TryParse (key.Substring (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g)
-                        && int.TryParse (key.Substring (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b))
-                    {
-                        color = new (r, g, b);
+        return TryParseColorKey (name, out color);
 
-                        return true;
-                    }
+        bool TryParseColorKey (string? key, out Color color)
+        {
+            if (key != null && key.StartsWith ('#') && key.Length == 7)
+            {
+                if (int.TryParse (key.AsSpan (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r) &&
+                    int.TryParse (key.AsSpan (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g) &&
+                    int.TryParse (key.AsSpan (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b))
+                {
+                    color = new Color (r, g, b);
+                    return true;
                 }
             }
-        }
-
-        color = default (Color);
 
-        return false;
+            color = default (Color);
+            return false;
+        }
     }
 }

+ 28 - 5
Terminal.Gui/Drawing/Glyphs.cs

@@ -8,11 +8,14 @@
 ///     </para>
 ///     <para>
 ///         The default glyphs can be changed via the <see cref="ConfigurationManager"/>. Within a <c>config.json</c>
-///         file The Json property name is the property name prefixed with "Glyphs.".
+///         file the Json property name is the property name prefixed with "Glyphs.".
 ///     </para>
 ///     <para>
-///         The JSon property can be either a decimal number or a string. The string may be one of: - A unicode char
-///         (e.g. "☑") - A hex value in U+ format (e.g. "U+2611") - A hex value in UTF-16 format (e.g. "\\u2611")
+///         The Json property can be one of:
+///         - unicode glyph in a string (e.g. "☑")
+///         - U+hex format in a string  (e.g. "U+2611")
+///         - \u format in a string (e.g. "\\u2611")
+///         - A decimal number (e.g. 97 for "a")
 ///     </para>
 /// </remarks>
 public class GlyphDefinitions
@@ -106,6 +109,27 @@ public class GlyphDefinitions
     /// <summary>Identical To (U+226)</summary>
     public Rune IdenticalTo { get; set; } = (Rune)'≡';
 
+    /// <summary>Move indicator. Default is Lozenge (U+25CA) - ◊.</summary>
+    public Rune Move { get; set; } = (Rune)'◊';
+
+    /// <summary>Size Horizontally indicator. Default is ┥Left Right Arrow - ↔ U+02194</summary>
+    public Rune SizeHorizontal { get; set; } = (Rune)'↔';
+
+    /// <summary>Size Vertical indicator. Default Up Down Arrow - ↕ U+02195</summary>
+    public Rune SizeVertical { get; set; } = (Rune)'↕';
+
+    /// <summary>Size Top Left indicator. North West Arrow - ↖ U+02196</summary>
+    public Rune SizeTopLeft { get; set; } = (Rune)'↖';
+
+    /// <summary>Size Top Right indicator. North East Arrow - ↗ U+02197</summary>
+    public Rune SizeTopRight { get; set; } = (Rune)'↗';
+
+    /// <summary>Size Bottom Right indicator. South East Arrow - ↘ U+02198</summary>
+    public Rune SizeBottomRight { get; set; } = (Rune)'↘';
+
+    /// <summary>Size Bottom Left indicator. South West Arrow - ↙ U+02199</summary>
+    public Rune SizeBottomLeft { get; set; } = (Rune)'↙';
+
     /// <summary>Apple (non-BMP). Because snek. And because it's an example of a non-BMP surrogate pair. See Issue #2610.</summary>
     public Rune Apple { get; set; } = "🍎".ToRunes () [0]; // nonBMP
 
@@ -440,9 +464,8 @@ public class GlyphDefinitions
 
     #region ----------------- ShadowStyle -----------------
 
-
     /// <summary>Shadow - Vertical Start - Left Half Block - ▌ U+0258c</summary>
-    public Rune ShadowVerticalStart { get; set; } =  (Rune)'▖'; // Half: '\u2596'  ▖;
+    public Rune ShadowVerticalStart { get; set; } = (Rune)'▖'; // Half: '\u2596'  ▖;
 
     /// <summary>Shadow - Vertical - Left Half Block - ▌ U+0258c</summary>
     public Rune ShadowVertical { get; set; } = (Rune)'▌';

+ 2 - 2
Terminal.Gui/FileServices/DefaultFileOperations.cs

@@ -135,14 +135,14 @@ public class DefaultFileOperations : IFileOperations
         var confirm = false;
         var btnOk = new Button { IsDefault = true, Text = Strings.btnOk };
 
-        btnOk.Accept += (s, e) =>
+        btnOk.Accepting += (s, e) =>
                          {
                              confirm = true;
                              Application.RequestStop ();
                          };
         var btnCancel = new Button { Text = Strings.btnCancel };
 
-        btnCancel.Accept += (s, e) =>
+        btnCancel.Accepting += (s, e) =>
                              {
                                  confirm = false;
                                  Application.RequestStop ();

+ 191 - 147
Terminal.Gui/Input/Command.cs

@@ -3,160 +3,171 @@
 
 namespace Terminal.Gui;
 
-/// <summary>Actions which can be performed by the application or bound to keys in a <see cref="View"/> control.</summary>
+/// <summary>
+///     Actions which can be performed by a <see cref="View"/>. Commands are typically invoked via
+///     <see cref="View.KeyBindings"/> and mouse events.
+/// </summary>
 public enum Command
 {
-    /// <summary>Invoked when the HotKey for the View has been pressed.</summary>
-    HotKey,
+    #region Base View Commands
 
-    /// <summary>Accepts the current state (e.g. list selection, button press, toggle, etc).</summary>
+    /// <summary>
+    ///     Accepts the current state of the View (e.g. list selection, button press, checkbox state, etc.).
+    ///     <para>
+    ///         The default implementation in <see cref="View"/> calls <see cref="View.RaiseAccepting"/>. If the event is not handled,
+    ///         the command is invoked on:
+    ///             - Any peer-view that is a <see cref="Button"/> with <see cref="Button.IsDefault"/> set to <see langword="true"/>.
+    ///             - The <see cref="View.SuperView"/>. This enables default Accept behavior.
+    ///     </para>
+    /// </summary>
     Accept,
 
-    /// <summary>Selects an item (e.g. a list item or menu item) without necessarily accepting it.</summary>
-    Select,
-
-    /// <summary>Moves down one item (cell, line, etc...).</summary>
-    LineDown,
-
-    /// <summary>Extends the selection down one (cell, line, etc...).</summary>
-    LineDownExtend,
+    /// <summary>
+    ///     Performs a hot key action (e.g. setting focus, accepting, and/or moving focus to the next View).
+    ///     <para>
+    ///         The default implementation in <see cref="View"/> calls <see cref="View.SetFocus"/> and then
+    ///         <see cref="View.RaiseHandlingHotKey"/>.
+    ///     </para>
+    /// </summary>
+    HotKey,
 
-    /// <summary>Moves down to the last child node of the branch that holds the current selection.</summary>
-    LineDownToLastBranch,
+    /// <summary>
+    ///     Selects the View or an item in the View (e.g. a list item or menu item) without necessarily accepting it.
+    ///     <para>
+    ///         The default implementation in <see cref="View"/> calls <see cref="View.RaiseSelecting"/>.
+    ///     </para>
+    /// </summary>
+    Select,
 
-    /// <summary>Scrolls down one (cell, line, etc...) (without changing the selection).</summary>
-    ScrollDown,
+    #endregion
 
-    // --------------------------------------------------------------------
+    #region Movement Commands
 
     /// <summary>Moves up one (cell, line, etc...).</summary>
-    LineUp,
-
-    /// <summary>Extends the selection up one item (cell, line, etc...).</summary>
-    LineUpExtend,
-
-    /// <summary>Moves up to the first child node of the branch that holds the current selection.</summary>
-    LineUpToFirstBranch,
+    Up,
 
-    /// <summary>Scrolls up one item (cell, line, etc...) (without changing the selection).</summary>
-    ScrollUp,
+    /// <summary>Moves down one item (cell, line, etc...).</summary>
+    Down,
 
     /// <summary>
-    ///     Moves the selection left one by the minimum increment supported by the <see cref="View"/> e.g. single
-    ///     character, cell, item etc.
+    ///     Moves left one (cell, line, etc...).
     /// </summary>
     Left,
 
-    /// <summary>Scrolls one item (cell, character, etc...) to the left</summary>
-    ScrollLeft,
-
-    /// <summary>
-    ///     Extends the selection left one by the minimum increment supported by the view e.g. single character, cell,
-    ///     item etc.
-    /// </summary>
-    LeftExtend,
-
     /// <summary>
-    ///     Moves the selection right one by the minimum increment supported by the view e.g. single character, cell, item
-    ///     etc.
+    ///     Moves right one (cell, line, etc...).
     /// </summary>
     Right,
 
-    /// <summary>Scrolls one item (cell, character, etc...) to the right.</summary>
-    ScrollRight,
+    /// <summary>Move one page up.</summary>
+    PageUp,
 
-    /// <summary>
-    ///     Extends the selection right one by the minimum increment supported by the view e.g. single character, cell,
-    ///     item etc.
-    /// </summary>
-    RightExtend,
+    /// <summary>Move one page down.</summary>
+    PageDown,
 
-    /// <summary>Moves the caret to the start of the previous word.</summary>
-    WordLeft,
+    /// <summary>Moves to the left page.</summary>
+    PageLeft,
 
-    /// <summary>Extends the selection to the start of the previous word.</summary>
-    WordLeftExtend,
+    /// <summary>Moves to the right page.</summary>
+    PageRight,
 
-    /// <summary>Moves the caret to the start of the next word.</summary>
-    WordRight,
+    /// <summary>Moves to the top of page.</summary>
+    StartOfPage,
 
-    /// <summary>Extends the selection to the start of the next word.</summary>
-    WordRightExtend,
+    /// <summary>Moves to the bottom of page.</summary>
+    EndOfPage,
 
-    /// <summary>Cuts to the clipboard the characters from the current position to the end of the line.</summary>
-    CutToEndLine,
+    /// <summary>Moves to the start (e.g. the top or home).</summary>
+    Start,
 
-    /// <summary>Cuts to the clipboard the characters from the current position to the start of the line.</summary>
-    CutToStartLine,
+    /// <summary>Moves to the end (e.g. the bottom).</summary>
+    End,
 
-    /// <summary>Deletes the characters forwards.</summary>
-    KillWordForwards,
+    /// <summary>Moves left to the start on the current row/line.</summary>
+    LeftStart,
 
-    /// <summary>Deletes the characters backwards.</summary>
-    KillWordBackwards,
+    /// <summary>Moves right to the end on the current row/line.</summary>
+    RightEnd,
+
+    /// <summary>Moves to the start of the previous word.</summary>
+    WordLeft,
+
+    /// <summary>Moves the start of the next word.</summary>
+    WordRight,
+
+    #endregion
+
+    #region Movement With Extension Commands
+
+    /// <summary>Extends the selection up one item (cell, line, etc...).</summary>
+    UpExtend,
+
+    /// <summary>Extends the selection down one (cell, line, etc...).</summary>
+    DownExtend,
 
     /// <summary>
-    ///     Toggles overwrite mode such that newly typed text overwrites the text that is already there (typically
-    ///     associated with the Insert key).
+    ///     Extends the selection left one item (cell, line, etc...)
     /// </summary>
-    ToggleOverwrite,
+    LeftExtend,
 
     /// <summary>
-    ///     Enables overwrite mode such that newly typed text overwrites the text that is already there (typically
-    ///     associated with the Insert key).
+    ///     Extends the selection right one item (cell, line, etc...)
     /// </summary>
-    EnableOverwrite,
+    RightExtend,
 
-    /// <summary>Disables overwrite mode (<see cref="EnableOverwrite"/>)</summary>
-    DisableOverwrite,
+    /// <summary>Extends the selection to the start of the previous word.</summary>
+    WordLeftExtend,
 
-    /// <summary>Move one page down.</summary>
-    PageDown,
+    /// <summary>Extends the selection to the start of the next word.</summary>
+    WordRightExtend,
 
     /// <summary>Move one page down extending the selection to cover revealed objects/characters.</summary>
     PageDownExtend,
 
-    /// <summary>Move one page up.</summary>
-    PageUp,
-
     /// <summary>Move one page up extending the selection to cover revealed objects/characters.</summary>
     PageUpExtend,
 
-    /// <summary>Moves to the top/home.</summary>
-    TopHome,
+    /// <summary>Extends the selection to start (e.g. home or top).</summary>
+    StartExtend,
 
-    /// <summary>Extends the selection to the top/home.</summary>
-    TopHomeExtend,
+    /// <summary>Extends the selection to end (e.g. bottom).</summary>
+    EndExtend,
 
-    /// <summary>Moves to the bottom/end.</summary>
-    BottomEnd,
+    /// <summary>Extends the selection to the start on the current row/line.</summary>
+    LeftStartExtend,
 
-    /// <summary>Extends the selection to the bottom/end.</summary>
-    BottomEndExtend,
+    /// <summary>Extends the selection to the right on the current row/line.</summary>
+    RightEndExtend,
 
-    /// <summary>Open the selected item.</summary>
-    OpenSelectedItem,
+    /// <summary>Toggles the selection.</summary>
+    ToggleExtend,
 
-    /// <summary>Toggles the Expanded or collapsed state of a list or item (with subitems).</summary>
-    ToggleExpandCollapse,
+    #endregion
 
-    /// <summary>Expands a list or item (with subitems).</summary>
-    Expand,
+    #region Editing Commands
 
-    /// <summary>Recursively Expands all child items and their child items (if any).</summary>
-    ExpandAll,
+    /// <summary>Deletes the characters forwards.</summary>
+    KillWordForwards,
 
-    /// <summary>Collapses a list or item (with subitems).</summary>
-    Collapse,
+    /// <summary>Deletes the characters backwards.</summary>
+    KillWordBackwards,
 
-    /// <summary>Recursively collapses a list items of their children (if any).</summary>
-    CollapseAll,
+    /// <summary>
+    ///     Toggles overwrite mode such that newly typed text overwrites the text that is already there (typically
+    ///     associated with the Insert key).
+    /// </summary>
+    ToggleOverwrite,
 
-    /// <summary>Cancels an action or any temporary states on the control e.g. expanding a combo list.</summary>
-    Cancel,
+    // QUESTION: What is the difference between EnableOverwrite and ToggleOverwrite?
 
-    /// <summary>Unix emulation.</summary>
-    UnixEmulation,
+    /// <summary>
+    ///     Enables overwrite mode such that newly typed text overwrites the text that is already there (typically
+    ///     associated with the Insert key).
+    /// </summary>
+    EnableOverwrite,
+
+    /// <summary>Disables overwrite mode (<see cref="EnableOverwrite"/>)</summary>
+    DisableOverwrite,
 
     /// <summary>Deletes the character on the right.</summary>
     DeleteCharRight,
@@ -170,41 +181,41 @@ public enum Command
     /// <summary>Deletes all objects.</summary>
     DeleteAll,
 
-    /// <summary>Moves the cursor to the start of line.</summary>
-    StartOfLine,
+    /// <summary>Inserts a new item.</summary>
+    NewLine,
 
-    /// <summary>Extends the selection to the start of line.</summary>
-    StartOfLineExtend,
+    /// <summary>Unix emulation.</summary>
+    UnixEmulation,
 
-    /// <summary>Moves the cursor to the end of line.</summary>
-    EndOfLine,
+    #endregion
 
-    /// <summary>Extends the selection to the end of line.</summary>
-    EndOfLineExtend,
+    #region Tree Commands
 
-    /// <summary>Moves the cursor to the top of page.</summary>
-    StartOfPage,
+    /// <summary>Moves down to the last child node of the branch that holds the current selection.</summary>
+    LineDownToLastBranch,
 
-    /// <summary>Moves the cursor to the bottom of page.</summary>
-    EndOfPage,
+    /// <summary>Moves up to the first child node of the branch that holds the current selection.</summary>
+    LineUpToFirstBranch,
 
-    /// <summary>Moves to the left page.</summary>
-    PageLeft,
+    #endregion
 
-    /// <summary>Moves to the right page.</summary>
-    PageRight,
+    #region Scroll Commands
 
-    /// <summary>Moves to the left begin.</summary>
-    LeftHome,
+    /// <summary>Scrolls down one (cell, line, etc...).</summary>
+    ScrollDown,
 
-    /// <summary>Extends the selection to the left begin.</summary>
-    LeftHomeExtend,
+    /// <summary>Scrolls up one item (cell, line, etc...).</summary>
+    ScrollUp,
 
-    /// <summary>Moves to the right end.</summary>
-    RightEnd,
+    /// <summary>Scrolls one item (cell, character, etc...) to the left.</summary>
+    ScrollLeft,
 
-    /// <summary>Extends the selection to the right end.</summary>
-    RightEndExtend,
+    /// <summary>Scrolls one item (cell, character, etc...) to the right.</summary>
+    ScrollRight,
+
+    #endregion
+
+    #region Clipboard Commands
 
     /// <summary>Undo changes.</summary>
     Undo,
@@ -221,35 +232,27 @@ public enum Command
     /// <summary>Pastes the current selection.</summary>
     Paste,
 
-    /// TODO: IRunnable: Rename to Command.Quit to make more generic.
-    /// <summary>Quit a <see cref="Toplevel"/>.</summary>
-    QuitToplevel,
-
-    /// TODO: Overlapped: Add Command.ShowHide
-
-    /// <summary>Suspend an application (Only implemented in <see cref="CursesDriver"/>).</summary>
-    Suspend,
+    /// <summary>Cuts to the clipboard the characters from the current position to the end of the line.</summary>
+    CutToEndLine,
 
-    /// <summary>Moves focus to the next view.</summary>
-    NextView,
+    /// <summary>Cuts to the clipboard the characters from the current position to the start of the line.</summary>
+    CutToStartLine,
 
-    /// <summary>Moves focus to the previous view.</summary>
-    PreviousView,
+    #endregion
 
-    /// <summary>Moves focus to the next view or Toplevel (case of Overlapped).</summary>
-    NextViewOrTop,
+    #region Navigation Commands
 
-    /// <summary>Moves focus to the next previous or Toplevel (case of Overlapped).</summary>
-    PreviousViewOrTop,
+    /// <summary>Moves focus to the next <see cref="TabBehavior.TabStop"/>.</summary>
+    NextTabStop,
 
-    /// <summary>Refresh.</summary>
-    Refresh,
+    /// <summary>Moves focus to the previous <see cref="TabBehavior.TabStop"/>.</summary>
+    PreviousTabStop,
 
-    /// <summary>Toggles the selection.</summary>
-    ToggleExtend,
+    /// <summary>Moves focus to the next <see cref="TabBehavior.TabGroup"/>.</summary>
+    NextTabGroup,
 
-    /// <summary>Inserts a new item.</summary>
-    NewLine,
+    /// <summary>Moves focus to the next<see cref="TabBehavior.TabGroup"/>.</summary>
+    PreviousTabGroup,
 
     /// <summary>Tabs to the next item.</summary>
     Tab,
@@ -257,6 +260,40 @@ public enum Command
     /// <summary>Tabs back to the previous item.</summary>
     BackTab,
 
+    #endregion
+
+    #region Action Commands
+
+    /// <summary>Toggles something (e.g. the expanded or collapsed state of a list).</summary>
+    Toggle,
+
+    /// <summary>Expands a list or item (with subitems).</summary>
+    Expand,
+
+    /// <summary>Recursively Expands all child items and their child items (if any).</summary>
+    ExpandAll,
+
+    /// <summary>Collapses a list or item (with subitems).</summary>
+    Collapse,
+
+    /// <summary>Recursively collapses a list items of their children (if any).</summary>
+    CollapseAll,
+
+    /// <summary>Cancels an action or any temporary states on the control e.g. expanding a combo list.</summary>
+    Cancel,
+
+    /// <summary>Quit.</summary>
+    Quit,
+
+    /// <summary>Refresh.</summary>
+    Refresh,
+
+    /// <summary>Suspend an application (Only implemented in <see cref="CursesDriver"/>).</summary>
+    Suspend,
+
+    /// <summary>Open the selected item or invoke a UI for opening something.</summary>
+    Open,
+
     /// <summary>Saves the current document.</summary>
     Save,
 
@@ -267,5 +304,12 @@ public enum Command
     New,
 
     /// <summary>Shows context about the item (e.g. a context menu).</summary>
-    ShowContextMenu
-}
+    Context,
+
+    /// <summary>
+    ///     Invokes a user interface for editing or configuring something.
+    /// </summary>
+    Edit,
+
+    #endregion
+}

+ 11 - 1
Terminal.Gui/Input/CommandContext.cs

@@ -11,6 +11,9 @@ namespace Terminal.Gui;
 ///         use <see cref="View.AddCommand(Command,Func{CommandContext,System.Nullable{bool}})"/>.
 ///     </para>
 /// </remarks>
+/// <seealso cref="Application.KeyBindings"/>
+/// <seealso cref="View.KeyBindings"/>
+/// <seealso cref="Command"/>
 #pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
 public record struct CommandContext
 {
@@ -20,11 +23,13 @@ public record struct CommandContext
     /// <param name="command"></param>
     /// <param name="key"></param>
     /// <param name="keyBinding"></param>
-    public CommandContext (Command command, Key? key, KeyBinding? keyBinding = null)
+    /// <param name="data"></param>
+    public CommandContext (Command command, Key? key, KeyBinding? keyBinding = null, object? data = null)
     {
         Command = command;
         Key = key;
         KeyBinding = keyBinding;
+        Data = data;
     }
 
     /// <summary>
@@ -41,4 +46,9 @@ public record struct CommandContext
     /// The KeyBinding that was used to invoke the <see cref="Command"/>, if any.
     /// </summary>
     public KeyBinding? KeyBinding { get; set; }
+
+    /// <summary>
+    ///     Arbitrary data.
+    /// </summary>
+    public object? Data { get; set; }
 }

+ 15 - 0
Terminal.Gui/Input/CommandEventArgs.cs

@@ -0,0 +1,15 @@
+#nullable enable
+using System.ComponentModel;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Event arguments for <see cref="Command"/> events.
+/// </summary>
+public class CommandEventArgs : CancelEventArgs
+{
+    /// <summary>
+    ///     The context for the command.
+    /// </summary>
+    public CommandContext Context { get; init; }
+}

+ 1 - 1
Terminal.Gui/Input/GrabMouseEventArgs.cs

@@ -1,6 +1,6 @@
 namespace Terminal.Gui;
 
-/// <summary>Args <see cref="Application.GrabMouse"/> related events.</summary>
+/// <summary>Args GrabMouse related events.</summary>
 public class GrabMouseEventArgs : EventArgs
 {
     /// <summary>Creates a new instance of the <see cref="GrabMouseEventArgs"/> class.</summary>

+ 93 - 47
Terminal.Gui/Input/Key.cs

@@ -1,6 +1,5 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
-using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
@@ -80,7 +79,7 @@ public class Key : EventArgs, IEquatable<Key>
     public Key (KeyCode k) { KeyCode = k; }
 
     /// <summary>
-    /// Copy constructor.
+    ///     Copy constructor.
     /// </summary>
     /// <param name="key">The Key to copy</param>
     public Key (Key key)
@@ -155,18 +154,13 @@ public class Key : EventArgs, IEquatable<Key>
     /// </remarks>
     public Rune AsRune => ToRune (KeyCode);
 
-    private bool _handled = false;
-
     /// <summary>
     ///     Indicates if the current Key event has already been processed and the driver should stop notifying any other
-    ///     event subscriber. It's important to set this value to true specially when updating any View's layout from inside the
+    ///     event subscriber. It's important to set this value to true specially when updating any View's layout from inside
+    ///     the
     ///     subscriber method.
     /// </summary>
-    public bool Handled
-    {
-        get => _handled;
-        set => _handled = value;
-    }
+    public bool Handled { get; set; }
 
     /// <summary>Gets a value indicating whether the Alt key was pressed (real or synthesized)</summary>
     /// <value><see langword="true"/> if is alternate; otherwise, <see langword="false"/>.</value>
@@ -339,11 +333,11 @@ public class Key : EventArgs, IEquatable<Key>
         switch (baseKey)
         {
             case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask):
-                return new Rune ((uint)(baseKey + 32));
+                return new ((uint)(baseKey + 32));
             case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask):
-                return new Rune ((uint)baseKey);
+                return new ((uint)baseKey);
             case > KeyCode.Null and < KeyCode.A:
-                return new Rune ((uint)baseKey);
+                return new ((uint)baseKey);
         }
 
         if (Enum.IsDefined (typeof (KeyCode), baseKey))
@@ -351,7 +345,7 @@ public class Key : EventArgs, IEquatable<Key>
             return default (Rune);
         }
 
-        return new Rune ((uint)baseKey);
+        return new ((uint)baseKey);
     }
 
     #region Operators
@@ -381,17 +375,17 @@ public class Key : EventArgs, IEquatable<Key>
 
     /// <summary>Cast <see cref="KeyCode"/> to a <see cref="Key"/>.</summary>
     /// <param name="keyCode"></param>
-    public static implicit operator Key (KeyCode keyCode) { return new Key (keyCode); }
+    public static implicit operator Key (KeyCode keyCode) { return new (keyCode); }
 
     /// <summary>Cast <see langword="char"/> to a <see cref="Key"/>.</summary>
     /// <remarks>See <see cref="Key(char)"/> for more information.</remarks>
     /// <param name="ch"></param>
-    public static implicit operator Key (char ch) { return new Key (ch); }
+    public static implicit operator Key (char ch) { return new (ch); }
 
     /// <summary>Cast <see langword="string"/> to a <see cref="Key"/>.</summary>
     /// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
     /// <param name="str"></param>
-    public static implicit operator Key (string str) { return new Key (str); }
+    public static implicit operator Key (string str) { return new (str); }
 
     /// <summary>Cast a <see cref="Key"/> to a <see langword="string"/>.</summary>
     /// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
@@ -399,10 +393,7 @@ public class Key : EventArgs, IEquatable<Key>
     public static implicit operator string (Key key) { return key.ToString (); }
 
     /// <inheritdoc/>
-    public override bool Equals (object obj)
-    {
-        return obj is Key k && k.KeyCode == KeyCode && k.Handled == Handled;
-    }
+    public override bool Equals (object obj) { return obj is Key k && k.KeyCode == KeyCode && k.Handled == Handled; }
 
     bool IEquatable<Key>.Equals (Key other) { return Equals (other); }
 
@@ -568,7 +559,10 @@ public class Key : EventArgs, IEquatable<Key>
     /// <summary>Converts the provided string to a new <see cref="Key"/> instance.</summary>
     /// <param name="text">
     ///     The text to analyze. Formats supported are "Ctrl+X", "Alt+X", "Shift+X", "Ctrl+Alt+X",
-    ///     "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", and "X".
+    ///     "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", "X", and "120" (Unicode codepoint).
+    ///     <para>
+    ///         The separator can be any character, not just <see cref="Key.Separator"/> (e.g. "Ctrl@Alt@X").
+    ///     </para>
     /// </param>
     /// <param name="key">The parsed value.</param>
     /// <returns>A boolean value indicating whether parsing was successful.</returns>
@@ -577,38 +571,88 @@ public class Key : EventArgs, IEquatable<Key>
     {
         if (string.IsNullOrEmpty (text))
         {
-            key = Key.Empty;
+            key = Empty;
 
             return true;
         }
 
+        switch (text)
+        {
+            case "Ctrl":
+                key = KeyCode.CtrlMask;
+
+                return true;
+            case "Alt":
+                key = KeyCode.AltMask;
+
+                return true;
+            case "Shift":
+                key = KeyCode.ShiftMask;
+
+                return true;
+        }
+
         key = null;
 
-        // Split the string into parts
-        string [] parts = text.Split ('+', '-', (char)Separator.Value);
+        Rune separator = Separator;
 
-        if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
+        // Perhaps the separator was written using a different Key.Separator? Does the string
+        // start with "Ctrl", "Alt" or "Shift"? If so, get the char after the modifier string and use that as the separator.
+        if (text.StartsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase))
+        {
+            separator = (Rune)text [4];
+        }
+        else if (text.StartsWith ("Alt", StringComparison.InvariantCultureIgnoreCase))
+        {
+            separator = (Rune)text [3];
+        }
+        else if (text.StartsWith ("Shift", StringComparison.InvariantCultureIgnoreCase))
+        {
+            separator = (Rune)text [5];
+        }
+        else if (text.EndsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase))
         {
+            separator = (Rune)text [^5];
+        }
+        else if (text.EndsWith ("Alt", StringComparison.InvariantCultureIgnoreCase))
+        {
+            separator = (Rune)text [^4];
+        }
+        else if (text.EndsWith ("Shift", StringComparison.InvariantCultureIgnoreCase))
+        {
+            separator = (Rune)text [^6];
+        }
+
+        // Split the string into parts using the set Separator
+        string [] parts = text.Split ((char)separator.Value);
+
+        if (parts.Length is > 4)
+        {
+            // Invalid
             return false;
         }
 
-        // if it's just a shift key
-        if (parts.Length == 1)
+        // e.g. "Ctrl++"
+        if ((Rune)text [^1] != separator && parts.Any (string.IsNullOrEmpty))
         {
-            switch (parts [0])
-            {
-                case "Ctrl":
-                    key = KeyCode.CtrlMask;
+            // Invalid
+            return false;
+        }
 
-                    return true;
-                case "Alt":
-                    key = KeyCode.AltMask;
+        if ((Rune)text [^1] == separator)
+        {
+            parts [^1] = separator.Value.ToString ();
+            key = (char)separator.Value;
+        }
 
-                    return true;
-                case "Shift":
-                    key = KeyCode.ShiftMask;
+        if (separator != Separator && (parts.Length is 1 || (key is { } && parts.Length is 2)))
+        {
+            parts = text.Split ((char)separator.Value);
 
-                    return true;
+            if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
+            {
+                // Invalid
+                return false;
             }
         }
 
@@ -649,12 +693,12 @@ public class Key : EventArgs, IEquatable<Key>
                 {
                     if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
                     {
-                        key = new Key (parsedKeyCode | KeyCode.ShiftMask);
+                        key = new (parsedKeyCode | KeyCode.ShiftMask);
 
                         return true;
                     }
 
-                    key = new Key (parsedKeyCode | modifiers);
+                    key = new (parsedKeyCode | modifiers);
 
                     return true;
                 }
@@ -664,7 +708,8 @@ public class Key : EventArgs, IEquatable<Key>
             {
                 keyCode = keyCode & ~KeyCode.Space;
             }
-            key = new Key (keyCode | modifiers);
+
+            key = new (keyCode | modifiers);
 
             return true;
         }
@@ -675,7 +720,7 @@ public class Key : EventArgs, IEquatable<Key>
             {
                 if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
                 {
-                    key = new Key (parsedKeyCode | KeyCode.ShiftMask);
+                    key = new (parsedKeyCode | KeyCode.ShiftMask);
 
                     return true;
                 }
@@ -684,7 +729,8 @@ public class Key : EventArgs, IEquatable<Key>
                 {
                     parsedKeyCode = parsedKeyCode & ~KeyCode.Space;
                 }
-                key = new Key (parsedKeyCode | modifiers);
+
+                key = new (parsedKeyCode | modifiers);
 
                 return true;
             }
@@ -705,12 +751,12 @@ public class Key : EventArgs, IEquatable<Key>
 
             if ((KeyCode)parsedInt is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
             {
-                key = new Key ((KeyCode)parsedInt | KeyCode.ShiftMask);
+                key = new ((KeyCode)parsedInt | KeyCode.ShiftMask);
 
                 return true;
             }
 
-            key = new Key ((KeyCode)parsedInt);
+            key = new ((KeyCode)parsedInt);
 
             return true;
         }
@@ -722,7 +768,7 @@ public class Key : EventArgs, IEquatable<Key>
 
         if (GetIsKeyCodeAtoZ (parsedKeyCode))
         {
-            key = new Key (parsedKeyCode | (modifiers & ~KeyCode.Space));
+            key = new (parsedKeyCode | (modifiers & ~KeyCode.Space));
 
             return true;
         }

+ 3 - 0
Terminal.Gui/Input/KeyBinding.cs

@@ -8,6 +8,9 @@ namespace Terminal.Gui;
 /// <summary>
 /// Provides a collection of <see cref="Command"/> objects that are scoped to <see cref="KeyBindingScope"/>.
 /// </summary>
+/// <seealso cref="Application.KeyBindings"/>
+/// <seealso cref="View.KeyBindings"/>
+/// <seealso cref="Command"/>
 public record struct KeyBinding
 {
     /// <summary>Initializes a new instance.</summary>

+ 30 - 21
Terminal.Gui/Input/KeyBindingScope.cs

@@ -1,6 +1,4 @@
-
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
 ///     Defines the scope of a <see cref="Command"/> that has been bound to a key with
@@ -9,41 +7,52 @@ namespace Terminal.Gui;
 /// <remarks>
 ///     <para>Key bindings are scoped to the most-focused view (<see cref="Focused"/>) by default.</para>
 /// </remarks>
+/// <seealso cref="Application.KeyBindings"/>
+/// <seealso cref="View.KeyBindings"/>
+/// <seealso cref="Command"/>
 [Flags]
-
 public enum KeyBindingScope
 {
     /// <summary>The key binding is disabled.</summary>
     Disabled = 0,
 
-    /// <summary>The key binding is scoped to just the view that has focus.</summary>
+    /// <summary>
+    ///     The key binding is scoped to just the view that has focus.
+    ///     <para>
+    ///     </para>
+    /// </summary>
+    /// <seealso cref="View.KeyBindings"/>
     Focused = 1,
 
     /// <summary>
-    ///     The key binding is scoped to the View's Superview hierarchy and will be triggered even when the View does not have
+    ///     The key binding is scoped to the View's Superview hierarchy and the bound <see cref="Command"/>s will be invoked
+    ///     even when the View does not have
     ///     focus, as
-    ///     long as the SuperView does have focus. This is typically used for <see cref="View.HotKey"/>s.
-    ///     <remarks>
-    ///         <para>
-    ///             The View must be visible.
-    ///         </para>
-    ///         <para>
-    ///             HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or
-    ///             any of its subviews.
-    ///         </para>
-    ///     </remarks>
+    ///     long as some View up the SuperView hierachy does have focus. This is typically used for <see cref="View.HotKey"/>s.
+    ///     <para>
+    ///         The View must be visible.
+    ///     </para>
+    ///     <para>
+    ///         HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or
+    ///         any of its subviews.
+    ///     </para>
     /// </summary>
+    /// <seealso cref="View.KeyBindings"/>
+    /// <seeals cref="View.HotKey"/>
     HotKey = 2,
 
     /// <summary>
-    ///     The key binding will be triggered regardless of which view has focus. This is typically used for global
+    ///     The and the bound <see cref="Command"/>s will be invoked regardless of which View has focus. This is typically used
+    ///     for global
     ///     commands, which are called Shortcuts.
-    /// </summary>
-    /// <remarks>
+    ///     <para>
+    ///         The View does not need to be visible.
+    ///     </para>
     ///     <para>
     ///         Application-scoped key bindings are only invoked if the key down event was not handled by the focused view or
     ///         any of its subviews, and if the key was not bound to a <see cref="View.HotKey"/>.
     ///     </para>
-    /// </remarks>
-    Application = 4,
+    /// </summary>
+    /// <seealso cref="Application.KeyBindings"/>
+    Application = 4
 }

+ 17 - 5
Terminal.Gui/Input/KeyBindings.cs

@@ -5,6 +5,9 @@ namespace Terminal.Gui;
 /// <summary>
 ///     Provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.
 /// </summary>
+/// <seealso cref="Application.KeyBindings"/>
+/// <seealso cref="View.KeyBindings"/>
+/// <seealso cref="Command"/>
 public class KeyBindings
 {
     /// <summary>
@@ -284,12 +287,21 @@ public class KeyBindings
         return Array.Empty<Command> ();
     }
 
-    /// <summary>Gets the Key used by a set of commands.</summary>
-    /// <remarks></remarks>
+    /// <summary>Gets the first Key bound to the set of commands specified by <paramref name="commands"/>.</summary>
     /// <param name="commands">The set of commands to search.</param>
-    /// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
-    /// <exception cref="InvalidOperationException">If no matching set of commands was found.</exception>
-    public Key GetKeyFromCommands (params Command [] commands) { return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key; }
+    /// <returns>The first <see cref="Key"/> bound to the set of commands specified by <paramref name="commands"/>. <see langword="null"/> if the set of caommands was not found.</returns>
+    public Key? GetKeyFromCommands (params Command [] commands)
+    {
+        return Bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key;
+    }
+
+    /// <summary>Gets Keys bound to the set of commands specified by <paramref name="commands"/>.</summary>
+    /// <param name="commands">The set of commands to search.</param>
+    /// <returns>The <see cref="Key"/>s bound to the set of commands specified by <paramref name="commands"/>. An empty list if the set of caommands was not found.</returns>
+    public IEnumerable<Key> GetKeysFromCommands (params Command [] commands)
+    {
+        return Bindings.Where (a => a.Value.Commands.SequenceEqual (commands)).Select (a => a.Key);
+    }
 
     /// <summary>Removes a <see cref="KeyBinding"/> from the collection.</summary>
     /// <param name="key"></param>

+ 63 - 0
Terminal.Gui/Resources/Strings.Designer.cs

@@ -195,6 +195,15 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to BrightGreen.
+        /// </summary>
+        internal static string _16C60C {
+            get {
+                return ResourceManager.GetString("#16C60C", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to MidnightBlue.
         /// </summary>
@@ -258,6 +267,15 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to BrightBlue.
+        /// </summary>
+        internal static string _3B78FF {
+            get {
+                return ResourceManager.GetString("#3B78FF", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to MediumSeaGreen.
         /// </summary>
@@ -339,6 +357,15 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to BrightCyan.
+        /// </summary>
+        internal static string _61D6D6 {
+            get {
+                return ResourceManager.GetString("#61D6D6", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to CornflowerBlue.
         /// </summary>
@@ -402,6 +429,15 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to DarkGray.
+        /// </summary>
+        internal static string _767676 {
+            get {
+                return ResourceManager.GetString("#767676", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to LightSlateGrey.
         /// </summary>
@@ -681,6 +717,15 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to BrightMagenta.
+        /// </summary>
+        internal static string _B4009E {
+            get {
+                return ResourceManager.GetString("#B4009E", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to DarkGoldenRod.
         /// </summary>
@@ -870,6 +915,15 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to BrightRed.
+        /// </summary>
+        internal static string _E74856 {
+            get {
+                return ResourceManager.GetString("#E74856", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to DarkSalmon.
         /// </summary>
@@ -996,6 +1050,15 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to BrightYellow.
+        /// </summary>
+        internal static string _F9F1A5 {
+            get {
+                return ResourceManager.GetString("#F9F1A5", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Salmon.
         /// </summary>

+ 21 - 0
Terminal.Gui/Resources/Strings.resx

@@ -697,4 +697,25 @@
     <data name="#9ACD32" xml:space="preserve">
     <value>YellowGreen</value>
   </data>
+    <data name="#3B78FF" xml:space="preserve">
+    <value>BrightBlue</value>
+  </data>
+    <data name="#61D6D6" xml:space="preserve">
+    <value>BrightCyan</value>
+  </data>
+    <data name="#E74856" xml:space="preserve">
+    <value>BrightRed</value>
+</data>
+    <data name="#16C60C" xml:space="preserve">
+    <value>BrightGreen</value>
+</data>
+    <data name="#B4009E" xml:space="preserve">
+    <value>BrightMagenta</value>
+</data>
+    <data name="#F9F1A5" xml:space="preserve">
+    <value>BrightYellow</value>
+</data>
+    <data name="#767676" xml:space="preserve">
+    <value>DarkGray</value>
+</data>
 </root>

+ 324 - 38
Terminal.Gui/Resources/config.json

@@ -10,8 +10,7 @@
   // note that not all values here will be recreated (e.g. the Light and Dark themes and any property initialized
   // null).
   //
-  // TODO: V2 - Reference via http
-  "$schema": "../../docfx/schemas/tui-config-schema.json",
+  "$schema": "https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json",
 
   // Set this to true in a .config file to be loaded to cause JSON parsing errors
   // to throw exceptions. 
@@ -22,6 +21,7 @@
   "Application.NextTabGroupKey": "F6",
   "Application.PrevTabGroupKey": "Shift+F6",
   "Application.QuitKey": "Esc",
+  "Application.ArrangeKey": "Ctrl+F5",
   "Key.Separator": "+",
 
   "Theme": "Default",
@@ -30,34 +30,35 @@
       "Default": {
         "Dialog.DefaultButtonAlignment": "End",
         "Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
+        "Dialog.DefaultBorderStyle": "Heavy",
+        "Dialog.DefaultShadow": "Transparent",
         "FrameView.DefaultBorderStyle": "Single",
         "Window.DefaultBorderStyle": "Single",
-        "Dialog.DefaultBorderStyle": "Heavy",
         "MessageBox.DefaultButtonAlignment": "Center",
         "MessageBox.DefaultBorderStyle": "Heavy",
-        "Button.DefaultShadow": "None",
+        "Button.DefaultShadow": "Opaque",
         "ColorSchemes": [
           {
             "TopLevel": {
               "Normal": {
                 "Foreground": "BrightGreen",
-                "Background": "Black"
+                "Background": "#505050" // DarkerGray
               },
               "Focus": {
                 "Foreground": "White",
-                "Background": "Cyan"
+                "Background": "#696969" // DimGray
               },
               "HotNormal": {
                 "Foreground": "Yellow",
-                "Background": "Black"
+                "Background": "#505050" // DarkerGray
               },
               "HotFocus": {
-                "Foreground": "Blue",
-                "Background": "Cyan"
+                "Foreground": "Yellow",
+                "Background": "#696969" // DimGray
               },
               "Disabled": {
                 "Foreground": "DarkGray",
-                "Background": "Black"
+                "Background": "#505050" // DarkerGray
               }
             }
           },
@@ -68,8 +69,8 @@
                 "Background": "Blue"
               },
               "Focus": {
-                "Foreground": "Black",
-                "Background": "Gray"
+                "Foreground": "DarkBlue",
+                "Background": "LightGray"
               },
               "HotNormal": {
                 "Foreground": "BrightCyan",
@@ -77,7 +78,7 @@
               },
               "HotFocus": {
                 "Foreground": "BrightBlue",
-                "Background": "Gray"
+                "Background": "LightGray"
               },
               "Disabled": {
                 "Foreground": "DarkGray",
@@ -89,19 +90,19 @@
             "Dialog": {
               "Normal": {
                 "Foreground": "Black",
-                "Background": "Gray"
+                "Background": "LightGray"
               },
               "Focus": {
-                "Foreground": "White",
-                "Background": "DarkGray"
+                "Foreground": "DarkGray",
+                "Background": "LightGray"
               },
               "HotNormal": {
                 "Foreground": "Blue",
-                "Background": "Gray"
+                "Background": "LightGray"
               },
               "HotFocus": {
-                "Foreground": "BrightYellow",
-                "Background": "DarkGray"
+                "Foreground": "BrightBlue",
+                "Background": "LightGray"
               },
               "Disabled": {
                 "Foreground": "Gray",
@@ -113,19 +114,19 @@
             "Menu": {
               "Normal": {
                 "Foreground": "White",
-                "Background": "DarkGray"
+                "Background": "DarkBlue"
               },
               "Focus": {
                 "Foreground": "White",
-                "Background": "Black"
+                "Background": "Blue"
               },
               "HotNormal": {
-                "Foreground": "BrightYellow",
-                "Background": "DarkGray"
+                "Foreground": "Yellow",
+                "Background": "DarkBlue"
               },
               "HotFocus": {
-                "Foreground": "BrightYellow",
-                "Background": "Black"
+                "Foreground": "Yellow",
+                "Background": "Blue"
               },
               "Disabled": {
                 "Foreground": "Gray",
@@ -137,18 +138,18 @@
             "Error": {
               "Normal": {
                 "Foreground": "Red",
-                "Background": "White"
+                "Background": "Pink"
               },
               "Focus": {
-                "Foreground": "Black",
+                "Foreground": "White",
                 "Background": "BrightRed"
               },
               "HotNormal": {
                 "Foreground": "Black",
-                "Background": "White"
+                "Background": "Pink"
               },
               "HotFocus": {
-                "Foreground": "White",
+                "Foreground": "Pink",
                 "Background": "BrightRed"
               },
               "Disabled": {
@@ -162,6 +163,15 @@
     },
     {
       "Dark": {
+        "Dialog.DefaultButtonAlignment": "End",
+        "Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
+        "Dialog.DefaultBorderStyle": "Heavy",
+        "Dialog.DefaultShadow": "Transparent",
+        "FrameView.DefaultBorderStyle": "Single",
+        "Window.DefaultBorderStyle": "Single",
+        "MessageBox.DefaultButtonAlignment": "Center",
+        "MessageBox.DefaultBorderStyle": "Heavy",
+        "Button.DefaultShadow": "Opaque",
         "ColorSchemes": [
           {
             "TopLevel": {
@@ -238,16 +248,16 @@
           {
             "Menu": {
               "Normal": {
-                "Foreground": "White",
-                "Background": "DarkGray"
+                "Foreground": "LightGray",
+                "Background": "#505050" // DarkerGray
               },
               "Focus": {
                 "Foreground": "White",
                 "Background": "Black"
               },
               "HotNormal": {
-                "Foreground": "Gray",
-                "Background": "DarkGray"
+                "Foreground": "White",
+                "Background": "#505050" // DarkerGray
               },
               "HotFocus": {
                 "Foreground": "White",
@@ -288,6 +298,15 @@
     },
     {
       "Light": {
+        "Dialog.DefaultButtonAlignment": "End",
+        "Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
+        "Dialog.DefaultBorderStyle": "Heavy",
+        "Dialog.DefaultShadow": "Transparent",
+        "FrameView.DefaultBorderStyle": "Single",
+        "Window.DefaultBorderStyle": "Single",
+        "MessageBox.DefaultButtonAlignment": "Center",
+        "MessageBox.DefaultBorderStyle": "Heavy",
+        "Button.DefaultShadow": "Opaque",
         "ColorSchemes": [
           {
             "TopLevel": {
@@ -316,7 +335,7 @@
           {
             "Base": {
               "Normal": {
-                "Foreground": "DarkGray",
+                "Foreground": "#505050", // DarkerGray
                 "Background": "White"
               },
               "Focus": {
@@ -365,19 +384,19 @@
             "Menu": {
               "Normal": {
                 "Foreground": "DarkGray",
-                "Background": "White"
+                "Background": "LightGray"
               },
               "Focus": {
                 "Foreground": "DarkGray",
-                "Background": "Gray"
+                "Background": "White"
               },
               "HotNormal": {
                 "Foreground": "BrightRed",
-                "Background": "White"
+                "Background": "LightGray"
               },
               "HotFocus": {
                 "Foreground": "BrightRed",
-                "Background": "Gray"
+                "Background": "White"
               },
               "Disabled": {
                 "Foreground": "Gray",
@@ -411,6 +430,273 @@
           }
         ]
       }
+    },
+    {
+      "Black & White": {
+        "Dialog.DefaultShadow": "None",
+        "FrameView.DefaultBorderStyle": "Single",
+        "Window.DefaultBorderStyle": "Single",
+        "MessageBox.DefaultButtonAlignment": "Center",
+        "MessageBox.DefaultBorderStyle": "Heavy",
+        "Button.DefaultShadow": "None",
+        "ColorSchemes": [
+          {
+            "TopLevel": {
+              "Normal": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "Focus": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "HotNormal": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "HotFocus": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "Disabled": {
+                "Foreground": "Black",
+                "Background": "Black"
+              }
+            }
+          },
+          {
+            "Base": {
+              "Normal": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "Focus": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "HotNormal": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "HotFocus": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "Disabled": {
+                "Foreground": "Black",
+                "Background": "Black"
+              }
+            }
+          },
+          {
+            "Dialog": {
+              "Normal": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "Focus": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "HotNormal": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "HotFocus": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "Disabled": {
+                "Foreground": "White",
+                "Background": "White"
+              }
+            }
+          },
+          {
+            "Menu": {
+              "Normal": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "Focus": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "HotNormal": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "HotFocus": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "Disabled": {
+                "Foreground": "White",
+                "Background": "White"
+              }
+            }
+          },
+          {
+            "Error": {
+              "Normal": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "Focus": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "HotNormal": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "HotFocus": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "Disabled": {
+                "Foreground": "Black",
+                "Background": "Black"
+              }
+            }
+          }
+        ]
+      }
+    },
+    {
+      "Gray Scale": {
+        "Dialog.DefaultButtonAlignment": "End",
+        "Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
+        "Dialog.DefaultBorderStyle": "Heavy",
+        "Dialog.DefaultShadow": "Transparent",
+        "FrameView.DefaultBorderStyle": "Single",
+        "Window.DefaultBorderStyle": "Single",
+        "MessageBox.DefaultButtonAlignment": "Center",
+        "MessageBox.DefaultBorderStyle": "Heavy",
+        "Button.DefaultShadow": "Opaque",
+        "ColorSchemes": [
+          {
+            "TopLevel": {
+              "Normal": {
+                "Foreground": "#A9A9A9", // DarkGray
+                "Background": "#505050" // DarkerGray
+              },
+              "Focus": {
+                "Foreground": "White",
+                "Background": "#696969" // DimGray
+              },
+              "HotNormal": {
+                "Foreground": "#808080", // Gray
+                "Background": "#505050" // DarkerGray
+              },
+              "HotFocus": {
+                "Foreground": "White",
+                "Background": "#808080" // Gray
+              },
+              "Disabled": {
+                "Foreground": "#505050", // DarkerGray
+                "Background": "Black"
+              }
+            }
+          },
+          {
+            "Base": {
+              "Normal": {
+                "Foreground": "#A9A9A9", // DarkGray
+                "Background": "Black"
+              },
+              "Focus": {
+                "Foreground": "White",
+                "Background": "#505050" // DarkerGray
+              },
+              "HotNormal": {
+                "Foreground": "#808080", // Gray
+                "Background": "Black"
+              },
+              "HotFocus": {
+                "Foreground": "White",
+                "Background": "#505050" // DarkerGray
+              },
+              "Disabled": {
+                "Foreground": "#696969", // DimGray
+                "Background": "Black"
+              }
+            }
+          },
+          {
+            "Dialog": {
+              "Normal": {
+                "Foreground": "#505050", // DarkerGray
+                "Background": "White"
+              },
+              "Focus": {
+                "Foreground": "Black",
+                "Background": "#D3D3D3" // LightGray
+              },
+              "HotNormal": {
+                "Foreground": "#808080", // Gray
+                "Background": "White"
+              },
+              "HotFocus": {
+                "Foreground": "Black",
+                "Background": "#D3D3D3" // LightGray
+              },
+              "Disabled": {
+                "Foreground": "#696969", // DimGray
+                "Background": "White"
+              }
+            }
+          },
+          {
+            "Menu": {
+              "Normal": {
+                "Foreground": "#D3D3D3", // LightGray
+                "Background": "#505050" // DarkerGray
+              },
+              "Focus": {
+                "Foreground": "White",
+                "Background": "#808080" // Gray
+              },
+              "HotNormal": {
+                "Foreground": "#808080", // Gray
+                "Background": "#505050" // DarkerGray
+              },
+              "HotFocus": {
+                "Foreground": "White",
+                "Background": "#808080" // Gray
+              },
+              "Disabled": {
+                "Foreground": "#505050", // DarkerGray
+                "Background": "#505050" // DarkerGray
+              }
+            }
+          },
+          {
+            "Error": {
+              "Normal": {
+                "Foreground": "Black",
+                "Background": "White"
+              },
+              "Focus": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "HotNormal": {
+                "Foreground": "Black",
+                "Background": "#D3D3D3" // LightGray
+              },
+              "HotFocus": {
+                "Foreground": "White",
+                "Background": "Black"
+              },
+              "Disabled": {
+                "Foreground": "#696969", // DimGray
+                "Background": "White"
+              }
+            }
+          }
+        ]
+      }
     }
   ]
 }

+ 1 - 0
Terminal.Gui/Terminal.Gui.csproj

@@ -141,4 +141,5 @@
     <EnableSourceLink>true</EnableSourceLink>
     <Authors>Miguel de Icaza, Tig Kindel (@tig), @BDisp</Authors>
   </PropertyGroup>
+  <ProjectExtensions><VisualStudio><UserProperties resources_4config_1json__JsonSchema="../../docfx/schemas/tui-config-schema.json" /></VisualStudio></ProjectExtensions>
 </Project>

+ 35 - 35
Terminal.Gui/View/Adornment/Adornment.cs

@@ -1,4 +1,5 @@
 #nullable enable
+using System.ComponentModel;
 using Terminal.Gui;
 using Attribute = Terminal.Gui.Attribute;
 
@@ -26,7 +27,9 @@ public class Adornment : View
     /// <param name="parent"></param>
     public Adornment (View parent)
     {
-        CanFocus = true;
+        // By default Adornments can't get focus; has to be enabled specifically.
+        CanFocus = false;
+        TabStop = TabBehavior.NoStop;
         Parent = parent;
     }
 
@@ -220,43 +223,40 @@ public class Adornment : View
             return false;
         }
 
-        Rectangle frame = Frame;
-        frame.Offset (Parent.Frame.Location);
+        Rectangle outside = Frame;
+        outside.Offset (Parent.Frame.Location);
 
-        return Thickness.Contains (frame, location);
+        return Thickness.Contains (outside, location);
     }
 
-    /// <inheritdoc/>
-    protected internal override bool? OnMouseEnter (MouseEvent 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 base.OnMouseEnter (mouseEvent);
-    }
-
-    /// <inheritdoc/>   
-    protected internal override bool OnMouseLeave (MouseEvent mouseEvent)
-    {
-        // Invert Normal
-        if (Diagnostics.FastHasFlags (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
-        {
-            var cs = new ColorScheme (ColorScheme)
-            {
-                Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
-            };
-            ColorScheme = cs;
-        }
-
-        return base.OnMouseLeave (mouseEvent);
-    }
+    ///// <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
 }

+ 915 - 173
Terminal.Gui/View/Adornment/Border.cs

@@ -1,6 +1,9 @@
+#nullable enable
+using System.Diagnostics;
+
 namespace Terminal.Gui;
 
-/// <summary>The Border for a <see cref="View"/>.</summary>
+/// <summary>The Border for a <see cref="View"/>. Accessed via <see cref="View.Border"/></summary>
 /// <remarks>
 ///     <para>
 ///         Renders a border around the view with the <see cref="View.Title"/>. A border using <see cref="LineStyle"/>
@@ -52,8 +55,10 @@ public class Border : Adornment
     /// <inheritdoc/>
     public Border (View parent) : base (parent)
     {
-        /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */
         Parent = parent;
+        CanFocus = false;
+        TabStop = TabBehavior.TabGroup;
+
         Application.GrabbingMouse += Application_GrabbingMouse;
         Application.UnGrabbingMouse += Application_UnGrabbingMouse;
 
@@ -73,14 +78,6 @@ public class Border : Adornment
     /// <inheritdoc/>
     public override void BeginInit ()
     {
-#if HOVER
-        // TOOD: Hack - make Arrangement overridable
-        if ((Parent?.Arrangement & ViewArrangement.Movable) != 0)
-        {
-            HighlightStyle |= HighlightStyle.Hover;
-        }
-#endif
-
         base.BeginInit ();
 
 #if SUBVIEW_BASED_BORDER
@@ -131,7 +128,7 @@ public class Border : Adornment
     ///     The color scheme for the Border. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/>
     ///     scheme. color scheme.
     /// </summary>
-    public override ColorScheme ColorScheme
+    public override ColorScheme? ColorScheme
     {
         get
         {
@@ -152,6 +149,7 @@ public class Border : Adornment
     internal Rectangle GetBorderRectangle ()
     {
         Rectangle screenRect = ViewportToScreen (Viewport);
+
         return new (
                     screenRect.X + Math.Max (0, Thickness.Left - 1),
                     screenRect.Y + Math.Max (0, Thickness.Top - 1),
@@ -193,7 +191,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;
     }
@@ -223,9 +221,9 @@ public class Border : Adornment
 
     private Color? _savedForeColor;
 
-    private void Border_Highlight (object sender, CancelEventArgs<HighlightStyle> e)
+    private void Border_Highlight (object? sender, CancelEventArgs<HighlightStyle> e)
     {
-        if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable))
+        if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
         {
             e.Cancel = true;
 
@@ -236,31 +234,22 @@ public class Border : Adornment
         {
             if (!_savedForeColor.HasValue)
             {
-                _savedForeColor = ColorScheme.Normal.Foreground;
+                _savedForeColor = ColorScheme!.Normal.Foreground;
             }
 
             var cs = new ColorScheme (ColorScheme)
             {
-                Normal = new (ColorScheme.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background)
+                Normal = new (ColorScheme!.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background)
             };
             ColorScheme = cs;
         }
-#if HOVER
-        else if (e.HighlightStyle.HasFlag (HighlightStyle.Hover))
-        {
-            if (!_savedHighlightLineStyle.HasValue)
-            {
-                _savedHighlightLineStyle = Parent?.BorderStyle ?? LineStyle;
-            }
-            LineStyle = LineStyle.Double;
-        }
-#endif
+
 
         if (e.NewValue == HighlightStyle.None && _savedForeColor.HasValue)
         {
             var cs = new ColorScheme (ColorScheme)
             {
-                Normal = new (_savedForeColor.Value, ColorScheme.Normal.Background)
+                Normal = new (_savedForeColor.Value, ColorScheme!.Normal.Background)
             };
             ColorScheme = cs;
         }
@@ -280,46 +269,180 @@ public class Border : Adornment
             return true;
         }
 
-        // BUGBUG: Shouldn't non-focusable views be draggable??
-        //if (!Parent.CanFocus)
-        //{
-        //    return false;
-        //}
-
-        if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable))
-        {
-            return false;
-        }
-
         // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/3312
-        if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
+        if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
+                                    // HACK: Prevents Window from being draggable if it's Top
+                                    //&& Parent is Toplevel { Modal: true }
+                                    )
         {
-            Parent.SetFocus ();
-            ApplicationOverlapped.BringOverlappedTopToFront ();
+            Parent!.SetFocus ();
+
+            if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable)
+                && !Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable)
+                && !Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable)
+                && !Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable)
+                && !Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable)
+               )
+            {
+                return false;
+            }
 
             // Only start grabbing if the user clicks in the Thickness area
             // Adornment.Contains takes Parent SuperView=relative coords.
             if (Contains (new (mouseEvent.Position.X + Parent.Frame.X + Frame.X, mouseEvent.Position.Y + Parent.Frame.Y + Frame.Y)))
             {
+                if (_arranging != ViewArrangement.Fixed)
+                {
+                    EndArrangeMode ();
+                }
+
                 // Set the start grab point to the Frame coords
                 _startGrabPoint = new (mouseEvent.Position.X + Frame.X, mouseEvent.Position.Y + Frame.Y);
                 _dragPosition = mouseEvent.Position;
                 Application.GrabMouse (this);
 
-                SetHighlight (HighlightStyle);
+                SetPressedHighlight (HighlightStyle);
+
+                // Arrange Mode -
+                // TODO: This code can be refactored to be more readable and maintainable.
+
+                // If not resizable, but movable: Drag anywhere is move
+                // If resizable and movable: Drag on top is move, other 3 sides are size
+                // If not movable, but resizable: Drag on any side sizes.
+
+                // Get rectangle representing Thickness.Top
+                // If mouse is in that rectangle, set _arranging to ViewArrangement.Movable
+                Rectangle sideRect;
+
+                // If mouse is in any other rectangle, set _arranging to ViewArrangement.<side>
+                if (Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
+                {
+                    sideRect = new (Frame.X, Frame.Y + Thickness.Top, Thickness.Left, Frame.Height - Thickness.Top - Thickness.Bottom);
+
+                    if (sideRect.Contains (_startGrabPoint))
+                    {
+                        EnterArrangeMode (ViewArrangement.LeftResizable);
+
+                        return true;
+                    }
+                }
+
+                if (Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
+                {
+                    sideRect = new (
+                                    Frame.X + Frame.Width - Thickness.Right,
+                                    Frame.Y + Thickness.Top,
+                                    Thickness.Right,
+                                    Frame.Height - Thickness.Top - Thickness.Bottom);
+
+                    if (sideRect.Contains (_startGrabPoint))
+                    {
+                        EnterArrangeMode (ViewArrangement.RightResizable);
+
+                        return true;
+                    }
+                }
+
+                if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && !Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+                {
+                    sideRect = new (Frame.X + Thickness.Left, Frame.Y, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Top);
+
+                    if (sideRect.Contains (_startGrabPoint))
+                    {
+                        EnterArrangeMode (ViewArrangement.TopResizable);
+
+                        return true;
+                    }
+                }
+
+                if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable))
+                {
+                    sideRect = new (
+                                    Frame.X + Thickness.Left,
+                                    Frame.Y + Frame.Height - Thickness.Bottom,
+                                    Frame.Width - Thickness.Left - Thickness.Right,
+                                    Thickness.Bottom);
+
+                    if (sideRect.Contains (_startGrabPoint))
+                    {
+                        EnterArrangeMode (ViewArrangement.BottomResizable);
+
+                        return true;
+                    }
+                }
+
+                if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
+                {
+                    sideRect = new (Frame.X, Frame.Height - Thickness.Top, Thickness.Left, Thickness.Bottom);
+
+                    if (sideRect.Contains (_startGrabPoint))
+                    {
+                        EnterArrangeMode (ViewArrangement.BottomResizable | ViewArrangement.LeftResizable);
+
+                        return true;
+                    }
+                }
+
+                if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
+                {
+                    sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Height - Thickness.Top, Thickness.Right, Thickness.Bottom);
+
+                    if (sideRect.Contains (_startGrabPoint))
+                    {
+                        EnterArrangeMode (ViewArrangement.BottomResizable | ViewArrangement.RightResizable);
+
+                        return true;
+                    }
+                }
+
+                if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
+                {
+                    sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Y, Thickness.Right, Thickness.Top);
+
+                    if (sideRect.Contains (_startGrabPoint))
+                    {
+                        EnterArrangeMode (ViewArrangement.TopResizable | ViewArrangement.RightResizable);
+
+                        return true;
+                    }
+                }
+
+                if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
+                {
+                    sideRect = new (Frame.X, Frame.Y, Thickness.Left, Thickness.Top);
+
+                    if (sideRect.Contains (_startGrabPoint))
+                    {
+                        EnterArrangeMode (ViewArrangement.TopResizable | ViewArrangement.LeftResizable);
+
+                        return true;
+                    }
+                }
+
+                if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+                {
+                    //sideRect = new (Frame.X + Thickness.Left, Frame.Y, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Top);
+
+                    //if (sideRect.Contains (_startGrabPoint))
+                    {
+                        EnterArrangeMode (ViewArrangement.Movable);
+
+                        return true;
+                    }
+                }
             }
 
             return true;
         }
 
-        if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
+        if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && Application.MouseGrabView == this)
         {
-            if (Application.MouseGrabView == this && _dragPosition.HasValue)
+            if (_dragPosition.HasValue)
             {
-                if (Parent.SuperView is null)
+                if (Parent!.SuperView is null)
                 {
                     // Redraw the entire app window.
-                    Application.Top.SetNeedsDisplay ();
+                    Application.Top!.SetNeedsDisplay ();
                 }
                 else
                 {
@@ -331,17 +454,123 @@ public class Border : Adornment
                 Point parentLoc = Parent.SuperView?.ScreenToViewport (new (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y))
                                   ?? mouseEvent.ScreenPosition;
 
-                GetLocationEnsuringFullVisibility (
-                                                   Parent,
-                                                   parentLoc.X - _startGrabPoint.X,
-                                                   parentLoc.Y - _startGrabPoint.Y,
-                                                   out int nx,
-                                                   out int ny,
-                                                   out _
-                                                  );
+                int minHeight = Thickness.Vertical + Parent!.Margin.Thickness.Bottom;
+                int minWidth = Thickness.Horizontal + Parent!.Margin.Thickness.Right;
+
+                // TODO: This code can be refactored to be more readable and maintainable.
+                switch (_arranging)
+                {
+                    case ViewArrangement.Movable:
+
+                        GetLocationEnsuringFullVisibility (
+                                                           Parent,
+                                                           parentLoc.X - _startGrabPoint.X,
+                                                           parentLoc.Y - _startGrabPoint.Y,
+                                                           out int nx,
+                                                           out int ny
+                                                          //,
+                                                          // out _
+                                                          );
+
+                        Parent.X = parentLoc.X - _startGrabPoint.X;
+                        Parent.Y = parentLoc.Y - _startGrabPoint.Y;
+
+                        break;
+
+                    case ViewArrangement.TopResizable:
+                        // Get how much the mouse has moved since the start of the drag
+                        // and adjust the height of the parent by that amount
+                        int deltaY = parentLoc.Y - Parent.Frame.Y;
+                        int newHeight = Math.Max (minHeight, Parent.Frame.Height - deltaY);
+
+                        if (newHeight != Parent.Frame.Height)
+                        {
+                            Parent.Height = newHeight;
+                            Parent.Y = parentLoc.Y - _startGrabPoint.Y;
+                        }
+
+                        break;
+
+                    case ViewArrangement.BottomResizable:
+                        Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
+
+                        break;
+
+                    case ViewArrangement.LeftResizable:
+                        // Get how much the mouse has moved since the start of the drag
+                        // and adjust the height of the parent by that amount
+                        int deltaX = parentLoc.X - Parent.Frame.X;
+                        int newWidth = Math.Max (minWidth, Parent.Frame.Width - deltaX);
+
+                        if (newWidth != Parent.Frame.Width)
+                        {
+                            Parent.Width = newWidth;
+                            Parent.X = parentLoc.X - _startGrabPoint.X;
+                        }
 
-                Parent.X = nx;
-                Parent.Y = ny;
+                        break;
+
+                    case ViewArrangement.RightResizable:
+                        Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin.Thickness.Right + 1);
+
+                        break;
+
+                    case ViewArrangement.BottomResizable | ViewArrangement.RightResizable:
+                        Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin.Thickness.Right + 1);
+                        Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
+
+                        break;
+
+                    case ViewArrangement.BottomResizable | ViewArrangement.LeftResizable:
+                        int dX = parentLoc.X - Parent.Frame.X;
+                        int newW = Math.Max (minWidth, Parent.Frame.Width - dX);
+
+                        if (newW != Parent.Frame.Width)
+                        {
+                            Parent.Width = newW;
+                            Parent.X = parentLoc.X - _startGrabPoint.X;
+                        }
+
+                        Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
+
+                        break;
+
+                    case ViewArrangement.TopResizable | ViewArrangement.RightResizable:
+                        int dY = parentLoc.Y - Parent.Frame.Y;
+                        int newH = Math.Max (minHeight, Parent.Frame.Height - dY);
+
+                        if (newH != Parent.Frame.Height)
+                        {
+                            Parent.Height = newH;
+                            Parent.Y = parentLoc.Y - _startGrabPoint.Y;
+                        }
+
+                        Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin.Thickness.Right + 1);
+
+                        break;
+
+                    case ViewArrangement.TopResizable | ViewArrangement.LeftResizable:
+                        int dY2 = parentLoc.Y - Parent.Frame.Y;
+                        int newH2 = Math.Max (minHeight, Parent.Frame.Height - dY2);
+
+                        if (newH2 != Parent.Frame.Height)
+                        {
+                            Parent.Height = newH2;
+                            Parent.Y = parentLoc.Y - _startGrabPoint.Y;
+                        }
+
+                        int dX2 = parentLoc.X - Parent.Frame.X;
+                        int newW2 = Math.Max (minWidth, Parent.Frame.Width - dX2);
+
+                        if (newW2 != Parent.Frame.Width)
+                        {
+                            Parent.Width = newW2;
+                            Parent.X = parentLoc.X - _startGrabPoint.X;
+                        }
+
+                        break;
+                }
+                Application.Refresh ();
 
                 return true;
             }
@@ -351,7 +580,9 @@ public class Border : Adornment
         {
             _dragPosition = null;
             Application.UngrabMouse ();
-            SetHighlight (HighlightStyle.None);
+            SetPressedHighlight (HighlightStyle.None);
+
+            EndArrangeMode ();
 
             return true;
         }
@@ -359,17 +590,7 @@ public class Border : Adornment
         return false;
     }
 
-    /// <inheritdoc/>
-    protected override void Dispose (bool disposing)
-    {
-        Application.GrabbingMouse -= Application_GrabbingMouse;
-        Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
-
-        _dragPosition = null;
-        base.Dispose (disposing);
-    }
-
-    private void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
+    private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e)
     {
         if (Application.MouseGrabView == this && _dragPosition.HasValue)
         {
@@ -377,7 +598,7 @@ public class Border : Adornment
         }
     }
 
-    private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
+    private void Application_UnGrabbingMouse (object? sender, GrabMouseEventArgs e)
     {
         if (Application.MouseGrabView == this && _dragPosition.HasValue)
         {
@@ -417,7 +638,7 @@ public class Border : Adornment
         int maxTitleWidth = Math.Max (
                                       0,
                                       Math.Min (
-                                                Parent.TitleTextFormatter.FormatAndGetSize ().Width,
+                                                Parent!.TitleTextFormatter.FormatAndGetSize ().Width,
                                                 Math.Min (screenBounds.Width - 4, borderBounds.Width - 4)
                                                )
                                      );
@@ -427,6 +648,8 @@ public class Border : Adornment
         int sideLineLength = borderBounds.Height;
         bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 };
 
+        LineStyle lineStyle = LineStyle;
+
         if (Settings.FastHasFlags (BorderSettings.Title))
         {
             if (Thickness.Top == 2)
@@ -477,7 +700,7 @@ public class Border : Adornment
 
         if (canDrawBorder && LineStyle != LineStyle.None)
         {
-            LineCanvas lc = Parent?.LineCanvas;
+            LineCanvas? lc = Parent?.LineCanvas;
 
             bool drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height >= 1;
             bool drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
@@ -492,7 +715,7 @@ public class Border : Adornment
             }
             else
             {
-                Driver.SetAttribute (Parent.GetNormalColor ());
+                Driver.SetAttribute (Parent!.GetNormalColor ());
             }
 
             if (drawTop)
@@ -502,13 +725,13 @@ public class Border : Adornment
                 if (borderBounds.Width < 4 || !Settings.FastHasFlags (BorderSettings.Title) || string.IsNullOrEmpty (Parent?.Title))
                 {
                     // ╔╡╞╗ should be ╔══╗
-                    lc.AddLine (
-                                new (borderBounds.Location.X, titleY),
-                                borderBounds.Width,
-                                Orientation.Horizontal,
-                                LineStyle,
-                                Driver.GetAttribute ()
-                               );
+                    lc?.AddLine (
+                                 new (borderBounds.Location.X, titleY),
+                                 borderBounds.Width,
+                                 Orientation.Horizontal,
+                                 lineStyle,
+                                 Driver.GetAttribute ()
+                                );
                 }
                 else
                 {
@@ -517,13 +740,13 @@ public class Border : Adornment
                     //│
                     if (Thickness.Top == 2)
                     {
-                        lc.AddLine (
-                                    new (borderBounds.X + 1, topTitleLineY),
-                                    Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
-                                    Orientation.Horizontal,
-                                    LineStyle,
-                                    Driver.GetAttribute ()
-                                   );
+                        lc?.AddLine (
+                                     new (borderBounds.X + 1, topTitleLineY),
+                                     Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+                                     Orientation.Horizontal,
+                                     lineStyle,
+                                     Driver.GetAttribute ()
+                                    );
                     }
 
                     // ┌────┐
@@ -531,71 +754,71 @@ public class Border : Adornment
                     //│
                     if (borderBounds.Width >= 4 && Thickness.Top > 2)
                     {
-                        lc.AddLine (
-                                    new (borderBounds.X + 1, topTitleLineY),
-                                    Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
-                                    Orientation.Horizontal,
-                                    LineStyle,
-                                    Driver.GetAttribute ()
-                                   );
-
-                        lc.AddLine (
-                                    new (borderBounds.X + 1, topTitleLineY + 2),
-                                    Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
-                                    Orientation.Horizontal,
-                                    LineStyle,
-                                    Driver.GetAttribute ()
-                                   );
+                        lc?.AddLine (
+                                     new (borderBounds.X + 1, topTitleLineY),
+                                     Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+                                     Orientation.Horizontal,
+                                     lineStyle,
+                                     Driver.GetAttribute ()
+                                    );
+
+                        lc?.AddLine (
+                                     new (borderBounds.X + 1, topTitleLineY + 2),
+                                     Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+                                     Orientation.Horizontal,
+                                     lineStyle,
+                                     Driver.GetAttribute ()
+                                    );
                     }
 
                     // ╔╡Title╞═════╗
                     // Add a short horiz line for ╔╡
-                    lc.AddLine (
-                                new (borderBounds.Location.X, titleY),
-                                2,
-                                Orientation.Horizontal,
-                                LineStyle,
-                                Driver.GetAttribute ()
-                               );
+                    lc?.AddLine (
+                                 new (borderBounds.Location.X, titleY),
+                                 2,
+                                 Orientation.Horizontal,
+                                 lineStyle,
+                                 Driver.GetAttribute ()
+                                );
 
                     // Add a vert line for ╔╡
-                    lc.AddLine (
-                                new (borderBounds.X + 1, topTitleLineY),
-                                titleBarsLength,
-                                Orientation.Vertical,
-                                LineStyle.Single,
-                                Driver.GetAttribute ()
-                               );
+                    lc?.AddLine (
+                                 new (borderBounds.X + 1, topTitleLineY),
+                                 titleBarsLength,
+                                 Orientation.Vertical,
+                                 LineStyle.Single,
+                                 Driver.GetAttribute ()
+                                );
 
                     // Add a vert line for ╞
-                    lc.AddLine (
-                                new (
-                                     borderBounds.X
-                                     + 1
-                                     + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
-                                     - 1,
-                                     topTitleLineY
-                                    ),
-                                titleBarsLength,
-                                Orientation.Vertical,
-                                LineStyle.Single,
-                                Driver.GetAttribute ()
-                               );
+                    lc?.AddLine (
+                                 new (
+                                      borderBounds.X
+                                      + 1
+                                      + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
+                                      - 1,
+                                      topTitleLineY
+                                     ),
+                                 titleBarsLength,
+                                 Orientation.Vertical,
+                                 LineStyle.Single,
+                                 Driver.GetAttribute ()
+                                );
 
                     // Add the right hand line for ╞═════╗
-                    lc.AddLine (
-                                new (
-                                     borderBounds.X
-                                     + 1
-                                     + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
-                                     - 1,
-                                     titleY
-                                    ),
-                                borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
-                                Orientation.Horizontal,
-                                LineStyle,
-                                Driver.GetAttribute ()
-                               );
+                    lc?.AddLine (
+                                 new (
+                                      borderBounds.X
+                                      + 1
+                                      + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
+                                      - 1,
+                                      titleY
+                                     ),
+                                 borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+                                 Orientation.Horizontal,
+                                 lineStyle,
+                                 Driver.GetAttribute ()
+                                );
                 }
             }
 
@@ -603,36 +826,36 @@ public class Border : Adornment
 
             if (drawLeft)
             {
-                lc.AddLine (
-                            new (borderBounds.Location.X, titleY),
-                            sideLineLength,
-                            Orientation.Vertical,
-                            LineStyle,
-                            Driver.GetAttribute ()
-                           );
+                lc?.AddLine (
+                             new (borderBounds.Location.X, titleY),
+                             sideLineLength,
+                             Orientation.Vertical,
+                             lineStyle,
+                             Driver.GetAttribute ()
+                            );
             }
 #endif
 
             if (drawBottom)
             {
-                lc.AddLine (
-                            new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1),
-                            borderBounds.Width,
-                            Orientation.Horizontal,
-                            LineStyle,
-                            Driver.GetAttribute ()
-                           );
+                lc?.AddLine (
+                             new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1),
+                             borderBounds.Width,
+                             Orientation.Horizontal,
+                             lineStyle,
+                             Driver.GetAttribute ()
+                            );
             }
 
             if (drawRight)
             {
-                lc.AddLine (
-                            new (borderBounds.X + borderBounds.Width - 1, titleY),
-                            sideLineLength,
-                            Orientation.Vertical,
-                            LineStyle,
-                            Driver.GetAttribute ()
-                           );
+                lc?.AddLine (
+                             new (borderBounds.X + borderBounds.Width - 1, titleY),
+                             sideLineLength,
+                             Orientation.Vertical,
+                             lineStyle,
+                             Driver.GetAttribute ()
+                            );
             }
 
             Driver.SetAttribute (prevAttr);
@@ -651,10 +874,10 @@ public class Border : Adornment
                 // Redraw title 
                 if (drawTop && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title))
                 {
-                    Parent.TitleTextFormatter.Draw (
-                                                    new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
-                                                    Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
-                                                    Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
+                    Parent!.TitleTextFormatter.Draw (
+                                                     new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
+                                                     Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
+                                                     Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
                 }
 
                 //Left
@@ -681,11 +904,11 @@ public class Border : Adornment
             // TODO: This should not be done on each draw?
             if (Settings.FastHasFlags (BorderSettings.Gradient))
             {
-                SetupGradientLineCanvas (lc, screenBounds);
+                SetupGradientLineCanvas (lc!, screenBounds);
             }
             else
             {
-                lc.Fill = null;
+                lc!.Fill = null;
             }
         }
     }
@@ -705,17 +928,536 @@ public class Border : Adornment
     private static void GetAppealingGradientColors (out List<Color> stops, out List<int> steps)
     {
         // Define the colors of the gradient stops with more appealing colors
-        stops = new()
-        {
+        stops =
+        [
             new (0, 128, 255), // Bright Blue
             new (0, 255, 128), // Bright Green
             new (255, 255), // Bright Yellow
             new (255, 128), // Bright Orange
-            new (255, 0, 128) // Bright Pink
-        };
+            new (255, 0, 128)
+        ];
 
         // Define the number of steps between each color for smoother transitions
         // If we pass only a single value then it will assume equal steps between all pairs
-        steps = new() { 15 };
+        steps = [15];
+    }
+
+    private ViewArrangement _arranging;
+
+    private Button? _moveButton; // always top-left
+    private Button? _allSizeButton;
+    private Button? _leftSizeButton;
+    private Button? _rightSizeButton;
+    private Button? _topSizeButton;
+    private Button? _bottomSizeButton;
+
+    /// <summary>
+    ///     Starts "Arrange Mode" where <see cref="Adornment.Parent"/> can be moved and/or resized using the mouse
+    ///     or keyboard. If <paramref name="arrangement"/> is <see cref="ViewArrangement.Fixed"/> keyboard mode is enabled.
+    /// </summary>
+    /// <remarks>
+    ///     Arrange Mode is exited by the user pressing <see cref="Application.ArrangeKey"/>, <see cref="Key.Esc"/>, or by
+    ///     clicking
+    ///     the mouse out of the <see cref="Adornment.Parent"/>'s Frame.
+    /// </remarks>
+    /// <returns></returns>
+    public bool? EnterArrangeMode (ViewArrangement arrangement)
+    {
+        Debug.Assert (_arranging == ViewArrangement.Fixed);
+
+        if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable)
+            && !Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable)
+            && !Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable)
+            && !Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable)
+            && !Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable)
+           )
+        {
+            return false;
+        }
+
+        // Add Commands and Keybindigs - Note it's ok these get added each time. KeyBindings are cleared in EndArrange()
+        AddArrangeModeKeyBindings ();
+
+        Application.MouseEvent += ApplicationOnMouseEvent;
+
+        // TODO: This code can be refactored to be more readable and maintainable.
+
+        // Create buttons for resizing and moving
+        if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+        {
+            Debug.Assert (_moveButton is null);
+
+            _moveButton = new ()
+            {
+                Id = "moveButton",
+                CanFocus = true,
+                Width = 1,
+                Height = 1,
+                NoDecorations = true,
+                NoPadding = true,
+                ShadowStyle = ShadowStyle.None,
+                Text = $"{Glyphs.Move}",
+                Visible = false,
+                Data = ViewArrangement.Movable
+            };
+            Add (_moveButton);
+        }
+
+        if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
+        {
+            Debug.Assert (_allSizeButton is null);
+
+            _allSizeButton = new ()
+            {
+                Id = "allSizeButton",
+                CanFocus = true,
+                Width = 1,
+                Height = 1,
+                NoDecorations = true,
+                NoPadding = true,
+                ShadowStyle = ShadowStyle.None,
+                Text = $"{Glyphs.SizeBottomRight}",
+                X = Pos.AnchorEnd (),
+                Y = Pos.AnchorEnd (),
+                Visible = false,
+                Data = ViewArrangement.Resizable
+            };
+            Add (_allSizeButton);
+        }
+
+        if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable))
+        {
+            Debug.Assert (_topSizeButton is null);
+
+            _topSizeButton = new ()
+            {
+                Id = "topSizeButton",
+                CanFocus = true,
+                Width = 1,
+                Height = 1,
+                NoDecorations = true,
+                NoPadding = true,
+                ShadowStyle = ShadowStyle.None,
+                Text = $"{Glyphs.SizeVertical}",
+                X = Pos.Center () + Parent!.Margin.Thickness.Horizontal,
+                Y = 0,
+                Visible = false,
+                Data = ViewArrangement.TopResizable
+            };
+            Add (_topSizeButton);
+        }
+
+        if (Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
+        {
+            Debug.Assert (_rightSizeButton is null);
+
+            _rightSizeButton = new ()
+            {
+                Id = "rightSizeButton",
+                CanFocus = true,
+                Width = 1,
+                Height = 1,
+                NoDecorations = true,
+                NoPadding = true,
+                ShadowStyle = ShadowStyle.None,
+                Text = $"{Glyphs.SizeHorizontal}",
+                X = Pos.AnchorEnd (),
+                Y = Pos.Center () + Parent!.Margin.Thickness.Vertical / 2,
+                Visible = false,
+                Data = ViewArrangement.RightResizable
+            };
+            Add (_rightSizeButton);
+        }
+
+        if (Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
+        {
+            Debug.Assert (_leftSizeButton is null);
+
+            _leftSizeButton = new ()
+            {
+                Id = "leftSizeButton",
+                CanFocus = true,
+                Width = 1,
+                Height = 1,
+                NoDecorations = true,
+                NoPadding = true,
+                ShadowStyle = ShadowStyle.None,
+                Text = $"{Glyphs.SizeHorizontal}",
+                X = 0,
+                Y = Pos.Center () + Parent!.Margin.Thickness.Vertical / 2,
+                Visible = false,
+                Data = ViewArrangement.LeftResizable
+            };
+            Add (_leftSizeButton);
+        }
+
+        if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable))
+        {
+            Debug.Assert (_bottomSizeButton is null);
+
+            _bottomSizeButton = new ()
+            {
+                Id = "bottomSizeButton",
+                CanFocus = true,
+                Width = 1,
+                Height = 1,
+                NoDecorations = true,
+                NoPadding = true,
+                ShadowStyle = ShadowStyle.None,
+                Text = $"{Glyphs.SizeVertical}",
+                X = Pos.Center () + Parent!.Margin.Thickness.Horizontal / 2,
+                Y = Pos.AnchorEnd (),
+                Visible = false,
+                Data = ViewArrangement.BottomResizable
+            };
+            Add (_bottomSizeButton);
+        }
+
+        if (arrangement == ViewArrangement.Fixed)
+        {
+            // Keyboard mode
+            if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+            {
+                _moveButton!.Visible = true;
+            }
+
+            if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
+            {
+                _allSizeButton!.Visible = true;
+            }
+
+            _arranging = ViewArrangement.Movable;
+            CanFocus = true;
+            SetFocus ();
+        }
+        else
+        {
+            // Mouse mode
+            _arranging = arrangement;
+
+            switch (_arranging)
+            {
+                case ViewArrangement.Movable:
+                    _moveButton!.Visible = true;
+
+                    break;
+
+                case ViewArrangement.RightResizable | ViewArrangement.BottomResizable:
+                case ViewArrangement.Resizable:
+                    _rightSizeButton!.Visible = true;
+                    _bottomSizeButton!.Visible = true;
+
+                    if (_allSizeButton is { })
+                    {
+                        _allSizeButton!.X = Pos.AnchorEnd ();
+                        _allSizeButton!.Y = Pos.AnchorEnd ();
+                        _allSizeButton!.Visible = true;
+                    }
+
+                    break;
+
+                case ViewArrangement.LeftResizable:
+                    _leftSizeButton!.Visible = true;
+
+                    break;
+
+                case ViewArrangement.RightResizable:
+                    _rightSizeButton!.Visible = true;
+
+                    break;
+
+                case ViewArrangement.TopResizable:
+                    _topSizeButton!.Visible = true;
+
+                    break;
+
+                case ViewArrangement.BottomResizable:
+                    _bottomSizeButton!.Visible = true;
+
+                    break;
+
+                case ViewArrangement.LeftResizable | ViewArrangement.BottomResizable:
+                    _rightSizeButton!.Visible = true;
+                    _bottomSizeButton!.Visible = true;
+
+                    if (_allSizeButton is { })
+                    {
+                        _allSizeButton.X = 0;
+                        _allSizeButton.Y = Pos.AnchorEnd ();
+                        _allSizeButton.Visible = true;
+                    }
+
+                    break;
+
+                case ViewArrangement.LeftResizable | ViewArrangement.TopResizable:
+                    _leftSizeButton!.Visible = true;
+                    _topSizeButton!.Visible = true;
+
+                    break;
+
+                case ViewArrangement.RightResizable | ViewArrangement.TopResizable:
+                    _rightSizeButton!.Visible = true;
+                    _topSizeButton!.Visible = true;
+
+                    if (_allSizeButton is { })
+                    {
+                        _allSizeButton.X = Pos.AnchorEnd ();
+                        _allSizeButton.Y = 0;
+                        _allSizeButton.Visible = true;
+                    }
+
+                    break;
+            }
+        }
+
+        if (_arranging != ViewArrangement.Fixed)
+        {
+            if (arrangement == ViewArrangement.Fixed)
+            {
+                // Keyboard mode - enable nav
+                // TODO: Keyboard mode only supports sizing from bottom/right.
+                _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
+            }
+
+            return true;
+        }
+
+        // Hack for now
+        EndArrangeMode ();
+
+        return false;
+    }
+
+    private void AddArrangeModeKeyBindings ()
+    {
+        AddCommand (Command.Quit, EndArrangeMode);
+
+        AddCommand (
+                    Command.Up,
+                    () =>
+                    {
+                        if (Parent is null)
+                        {
+                            return false;
+                        }
+
+                        if (_arranging == ViewArrangement.Movable)
+                        {
+                            Parent!.Y = Parent.Y - 1;
+                        }
+
+                        if (_arranging == ViewArrangement.Resizable)
+                        {
+                            if (Parent!.Viewport.Height > 0)
+                            {
+                                Parent!.Height = Parent.Height! - 1;
+                            }
+                        }
+
+                        Application.Refresh ();
+
+                        return true;
+                    });
+
+        AddCommand (
+                    Command.Down,
+                    () =>
+                    {
+                        if (Parent is null)
+                        {
+                            return false;
+                        }
+
+                        if (_arranging == ViewArrangement.Movable)
+                        {
+                            Parent!.Y = Parent.Y + 1;
+                        }
+
+                        if (_arranging == ViewArrangement.Resizable)
+                        {
+                            Parent!.Height = Parent.Height! + 1;
+                        }
+
+                        Application.Refresh ();
+
+                        return true;
+                    });
+
+        AddCommand (
+                    Command.Left,
+                    () =>
+                    {
+                        if (Parent is null)
+                        {
+                            return false;
+                        }
+
+                        if (_arranging == ViewArrangement.Movable)
+                        {
+                            Parent!.X = Parent.X - 1;
+                        }
+
+                        if (_arranging == ViewArrangement.Resizable)
+                        {
+                            if (Parent!.Viewport.Width > 0)
+                            {
+                                Parent!.Width = Parent.Width! - 1;
+                            }
+                        }
+
+                        Application.Refresh ();
+
+                        return true;
+                    });
+
+        AddCommand (
+                    Command.Right,
+                    () =>
+                    {
+                        if (Parent is null)
+                        {
+                            return false;
+                        }
+
+                        if (_arranging == ViewArrangement.Movable)
+                        {
+                            Parent!.X = Parent.X + 1;
+                        }
+
+                        if (_arranging == ViewArrangement.Resizable)
+                        {
+                            Parent!.Width = Parent.Width! + 1;
+                        }
+
+                        Application.Refresh ();
+
+                        return true;
+                    });
+
+        AddCommand (
+                    Command.Tab,
+                    () =>
+                    {
+                        // BUGBUG: If an arrangable view has only arrangable subviews, it's not possible to activate
+                        // BUGBUG: ArrangeMode with keyboard for the superview.
+                        // BUGBUG: AdvanceFocus should be wise to this and when in ArrangeMode, should move across
+                        // BUGBUG: the view hierachy.
+
+                        AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
+                        _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
+
+                        return true; // Always eat
+                    });
+
+        AddCommand (
+                    Command.BackTab,
+                    () =>
+                    {
+                        AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop);
+                        _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
+
+                        return true; // Always eat
+                    });
+
+        KeyBindings.Add (Key.Esc, KeyBindingScope.HotKey, Command.Quit);
+        KeyBindings.Add (Application.ArrangeKey, KeyBindingScope.HotKey, Command.Quit);
+        KeyBindings.Add (Key.CursorUp, KeyBindingScope.HotKey, Command.Up);
+        KeyBindings.Add (Key.CursorDown, KeyBindingScope.HotKey, Command.Down);
+        KeyBindings.Add (Key.CursorLeft, KeyBindingScope.HotKey, Command.Left);
+        KeyBindings.Add (Key.CursorRight, KeyBindingScope.HotKey, Command.Right);
+
+        KeyBindings.Add (Key.Tab, KeyBindingScope.HotKey, Command.Tab);
+        KeyBindings.Add (Key.Tab.WithShift, KeyBindingScope.HotKey, Command.BackTab);
+    }
+
+    private void ApplicationOnMouseEvent (object? sender, MouseEvent e)
+    {
+        if (e.Flags != MouseFlags.Button1Clicked)
+        {
+            return;
+        }
+
+        // If mouse click is outside of Border.Thickness then exit Arrange Mode
+        // e.Position is screen relative
+        Point framePos = ScreenToFrame (e.ScreenPosition);
+
+        if (!Thickness.Contains (Frame, framePos))
+        {
+            EndArrangeMode ();
+        }
+    }
+
+    private bool? EndArrangeMode ()
+    {
+        // Debug.Assert (_arranging != ViewArrangement.Fixed);
+        _arranging = ViewArrangement.Fixed;
+
+        Application.MouseEvent -= ApplicationOnMouseEvent;
+
+        if (Application.MouseGrabView == this && _dragPosition.HasValue)
+        {
+            Application.UngrabMouse ();
+        }
+
+        if (_moveButton is { })
+        {
+            Remove (_moveButton);
+            _moveButton.Dispose ();
+            _moveButton = null;
+        }
+
+        if (_allSizeButton is { })
+        {
+            Remove (_allSizeButton);
+            _allSizeButton.Dispose ();
+            _allSizeButton = null;
+        }
+
+        if (_leftSizeButton is { })
+        {
+            Remove (_leftSizeButton);
+            _leftSizeButton.Dispose ();
+            _leftSizeButton = null;
+        }
+
+        if (_rightSizeButton is { })
+        {
+            Remove (_rightSizeButton);
+            _rightSizeButton.Dispose ();
+            _rightSizeButton = null;
+        }
+
+        if (_topSizeButton is { })
+        {
+            Remove (_topSizeButton);
+            _topSizeButton.Dispose ();
+            _topSizeButton = null;
+        }
+
+        if (_bottomSizeButton is { })
+        {
+            Remove (_bottomSizeButton);
+            _bottomSizeButton.Dispose ();
+            _bottomSizeButton = null;
+        }
+
+        KeyBindings.Clear ();
+
+        if (CanFocus)
+        {
+            CanFocus = false;
+        }
+
+        return true;
+    }
+
+    /// <inheritdoc/>
+    protected override void Dispose (bool disposing)
+    {
+        Application.GrabbingMouse -= Application_GrabbingMouse;
+        Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
+
+        _dragPosition = null;
+        base.Dispose (disposing);
     }
 }

+ 15 - 6
Terminal.Gui/View/Adornment/Margin.cs

@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-/// <summary>The Margin for a <see cref="View"/>.</summary>
+/// <summary>The Margin for a <see cref="View"/>. Accessed via <see cref="View.Margin"/></summary>
 /// <remarks>
 ///     <para>See the <see cref="Adornment"/> class.</para>
 /// </remarks>
@@ -18,7 +18,8 @@ public class Margin : Adornment
     {
         /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */
 
-        HighlightStyle |= HighlightStyle.Pressed;
+        // BUGBUG: We should not set HighlightStyle.Pressed here, but wherever it is actually needed
+       // HighlightStyle |= HighlightStyle.Pressed;
         Highlight += Margin_Highlight;
         LayoutStarted += Margin_LayoutStarted;
 
@@ -69,7 +70,7 @@ public class Margin : Adornment
     ///     The color scheme for the Margin. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/>'s
     ///     <see cref="View.SuperView"/> scheme. color scheme.
     /// </summary>
-    public override ColorScheme ColorScheme
+    public override ColorScheme? ColorScheme
     {
         get
         {
@@ -90,17 +91,22 @@ public class Margin : Adornment
     /// <inheritdoc/>
     public override void OnDrawContent (Rectangle viewport)
     {
+        if (!NeedsDisplay)
+        {
+            return;
+        }
+
         Rectangle screen = ViewportToScreen (viewport);
         Attribute normalAttr = GetNormalColor ();
 
         Driver?.SetAttribute (normalAttr);
 
-        // This just draws/clears the thickness, not the insides.
         if (ShadowStyle != ShadowStyle.None)
         {
             screen = Rectangle.Inflate (screen, -1, -1);
         }
 
+        // This just draws/clears the thickness, not the insides.
         Thickness.Draw (screen, ToString ());
 
         if (Subviews.Count > 0)
@@ -170,6 +176,9 @@ public class Margin : Adornment
         set => base.ShadowStyle = SetShadow (value);
     }
 
+    private const int PRESS_MOVE_HORIZONTAL = 1;
+    private const int PRESS_MOVE_VERTICAL = 0;
+
     private void Margin_Highlight (object? sender, CancelEventArgs<HighlightStyle> e)
     {
         if (ShadowStyle != ShadowStyle.None)
@@ -179,7 +188,7 @@ public class Margin : Adornment
                 // 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 - 1, Thickness.Top, Thickness.Right + 1, Thickness.Bottom);
+                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 { })
                 {
@@ -201,7 +210,7 @@ public class Margin : Adornment
                 // 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 + 1, Thickness.Top, Thickness.Right - 1, Thickness.Bottom);
+                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 { })

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

@@ -1,6 +1,6 @@
 namespace Terminal.Gui;
 
-/// <summary>The Padding for a <see cref="View"/>.</summary>
+/// <summary>The Padding for a <see cref="View"/>. Accessed via <see cref="View.Padding"/></summary>
 /// <remarks>
 ///     <para>See the <see cref="Adornment"/> class.</para>
 /// </remarks>

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

@@ -137,7 +137,7 @@ internal class ShadowView : View
         Rectangle screen = ViewportToScreen (viewport);
 
         // Fill the rest of the rectangle
-        for (int i = screen.Y; i < screen.Y + viewport.Height; i++)
+        for (int i = Math.Max (0, screen.Y); i < screen.Y + viewport.Height; i++)
         {
             Driver.Move (screen.X, i);
 

+ 9 - 8
Terminal.Gui/View/HighlightStyle.cs

@@ -1,30 +1,31 @@
-namespace Terminal.Gui;
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
 
 /// <summary>
-/// Describes the highlight style of a view.
+///     Describes the highlight style of a view when the mouse is over it.
 /// </summary>
+[JsonConverter (typeof (JsonStringEnumConverter<HighlightStyle>))]
 [Flags]
 public enum HighlightStyle
 {
     /// <summary>
-    /// No highlight.
+    ///     No highlight.
     /// </summary>
     None = 0,
 
-#if HOVER
     /// <summary>
-    /// The mouse is hovering over the view.
+    ///     The mouse is hovering over the view (but not pressed). See <see cref="View.MouseEnter"/>.
     /// </summary>
     Hover = 1,
-#endif
 
     /// <summary>
-    /// The mouse is pressed within the <see cref="View.Viewport"/>.
+    ///     The mouse is pressed within the <see cref="View.Viewport"/>.
     /// </summary>
     Pressed = 2,
 
     /// <summary>
-    /// The mouse is pressed but moved outside the <see cref="View.Viewport"/>.
+    ///     The mouse is pressed but moved outside the <see cref="View.Viewport"/>.
     /// </summary>
     PressedOutside = 4
 }

+ 42 - 0
Terminal.Gui/View/Layout/DimAuto.cs

@@ -408,6 +408,48 @@ 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 ();
+                }
+                else
+                {
+                    dimAutoSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has<DimAuto> (out _)).ToList ();
+                }
+
+                for (var i = 0; i < dimAutoSubViews.Count; i++)
+                {
+                    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;
+
+                    if (maxDimAuto > maxCalculatedSize)
+                    {
+                        maxCalculatedSize = maxDimAuto;
+                    }
+                }
+
+                #endregion
             }
         }
 

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

@@ -35,7 +35,7 @@ public enum DimAutoStyle
     ///     </para>
     ///     <para>
     ///         If <see cref="DimAuto.MaximumContentDim"/> is set, the dimension will be the maximum of the formatted text and the
-    ///         demension provided by <see cref="DimAuto.MaximumContentDim"/>. Otherwise, the dimension will be that of the formatted text.
+    ///         dimension provided by <see cref="DimAuto.MaximumContentDim"/>. Otherwise, the dimension will be that of the formatted text.
     ///     </para>
     /// </summary>
     Text = 2,

+ 5 - 7
Terminal.Gui/View/Navigation/TabBehavior.cs

@@ -6,25 +6,23 @@
 public enum TabBehavior
 {
     /// <summary>
-    ///     The View will not be a stop-poknt for keyboard-based navigation.
-    /// </summary>
-    /// <remarks>
+    ///     The View will not be a stop-point for keyboard-based navigation.
     ///     <para>
     ///         This flag has no impact on whether the view can be focused via means other than the keyboard. Use
     ///         <see cref="View.CanFocus"/>
     ///         to control whether a View can focus or not.
     ///     </para>
-    /// </remarks>
+    /// </summary>
     NoStop = 0,
 
     /// <summary>
-    ///     The View will be a stop-point for keybaord-based navigation across Views (e.g. if the user presses `Tab`).
+    ///     The View will be a stop-point for keyboard-based navigation across Views (e.g. if the user presses `Tab`).
     /// </summary>
     TabStop = 1,
 
     /// <summary>
-    ///     The View will be a stop-point for keyboard-based navigation across groups (e.g. if the user presses
-    ///     <see cref="Application.NextTabGroupKey"/> (`Ctrl-PageDown`).
+    ///     The View will be a stop-point for keyboard-based navigation across groups. (e.g. if the user presses
+    ///     <see cref="Application.NextTabGroupKey"/> (`Ctrl+PageDown`)).
     /// </summary>
     TabGroup = 2
 }

+ 29 - 18
Terminal.Gui/View/View.Adornments.cs

@@ -1,12 +1,9 @@
-using System.ComponentModel;
-using System.Text.Json.Serialization;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 public partial class View // Adornments
 {
     /// <summary>
-    ///    Initializes the Adornments of the View. Called by the constructor.
+    ///     Initializes the Adornments of the View. Called by the constructor.
     /// </summary>
     private void SetupAdornments ()
     {
@@ -49,6 +46,9 @@ public partial class View // Adornments
     /// </summary>
     /// <remarks>
     ///     <para>
+    ///         Enabling <see cref="ShadowStyle"/> will change the Thickness of the Margin to include the shadow.
+    ///     </para>
+    ///     <para>
     ///         The adornments (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the
     ///         View's content and are not clipped by the View's Clip Area.
     ///     </para>
@@ -61,8 +61,10 @@ public partial class View // Adornments
     public Margin Margin { get; private set; }
 
     private ShadowStyle _shadowStyle;
+
     /// <summary>
-    ///     Gets or sets whether the View is shown with a shadow effect. The shadow is drawn on the right and bottom sides of the
+    ///     Gets or sets whether the View is shown with a shadow effect. The shadow is drawn on the right and bottom sides of
+    ///     the
     ///     Margin.
     /// </summary>
     /// <remarks>
@@ -78,7 +80,9 @@ public partial class View // Adornments
             {
                 return;
             }
+
             _shadowStyle = value;
+
             if (Margin is { })
             {
                 Margin.ShadowStyle = value;
@@ -88,9 +92,15 @@ public partial class View // Adornments
 
     /// <summary>
     ///     The <see cref="Adornment"/> that offsets the <see cref="Viewport"/> from the <see cref="Margin"/>.
-    ///     The Border provides the space for a visual border (drawn using
-    ///     line-drawing glyphs) and the Title. The Border expands inward; in other words if `Border.Thickness.Top == 2` the
-    ///     border and title will take up the first row and the second row will be filled with spaces.
+    ///     <para>
+    ///         The Border provides the space for a visual border (drawn using
+    ///         line-drawing glyphs) and the Title. The Border expands inward; in other words if `Border.Thickness.Top == 2`
+    ///         the
+    ///         border and title will take up the first row and the second row will be filled with spaces.
+    ///     </para>
+    ///     <para>
+    ///         The Border provides the UI for mouse and keyboard arrangement of the View. See <see cref="Arrangement"/>.
+    ///     </para>
     /// </summary>
     /// <remarks>
     ///     <para><see cref="BorderStyle"/> provides a simple helper for turning a simple border frame on or off.</para>
@@ -124,15 +134,15 @@ public partial class View // Adornments
         get => Border?.LineStyle ?? LineStyle.Single;
         set
         {
-            var old = Border?.LineStyle ?? LineStyle.None;
+            LineStyle old = Border?.LineStyle ?? LineStyle.None;
             CancelEventArgs<LineStyle> e = new (ref old, ref value);
             OnBorderStyleChanging (e);
-
         }
     }
 
     /// <summary>
-    /// Called when the <see cref="BorderStyle"/> is changing. Invokes <see cref="BorderStyleChanging"/>, which allows the event to be cancelled.
+    ///     Called when the <see cref="BorderStyle"/> is changing. Invokes <see cref="BorderStyleChanging"/>, which allows the
+    ///     event to be cancelled.
     /// </summary>
     /// <remarks>
     ///     Override <see cref="SetBorderStyle"/> to prevent the <see cref="BorderStyle"/> from changing.
@@ -146,6 +156,7 @@ public partial class View // Adornments
         }
 
         BorderStyleChanging?.Invoke (this, e);
+
         if (e.Cancel)
         {
             return;
@@ -154,8 +165,6 @@ public partial class View // Adornments
         SetBorderStyle (e.NewValue);
         LayoutAdornments ();
         SetNeedsLayout ();
-
-        return;
     }
 
     /// <summary>
@@ -163,7 +172,8 @@ public partial class View // Adornments
     /// </summary>
     /// <remarks>
     ///     <para>
-    ///          <see cref="BorderStyle"/> is a helper for manipulating the view's <see cref="Border"/>. Setting this property to any value other
+    ///         <see cref="BorderStyle"/> is a helper for manipulating the view's <see cref="Border"/>. Setting this property
+    ///         to any value other
     ///         than <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s
     ///         <see cref="Adornment.Thickness"/> to `1` and <see cref="BorderStyle"/> to the value.
     ///     </para>
@@ -218,9 +228,9 @@ public partial class View // Adornments
     ///     <para>Gets the thickness describing the sum of the Adornments' thicknesses.</para>
     /// </summary>
     /// <remarks>
-    /// <para>
-    ///     The <see cref="Viewport"/> is offset from the <see cref="Frame"/> by the thickness returned by this method.
-    /// </para>
+    ///     <para>
+    ///         The <see cref="Viewport"/> is offset from the <see cref="Frame"/> by the thickness returned by this method.
+    ///     </para>
     /// </remarks>
     /// <returns>A thickness that describes the sum of the Adornments' thicknesses.</returns>
     public Thickness GetAdornmentsThickness ()
@@ -229,6 +239,7 @@ public partial class View // Adornments
         {
             return Thickness.Empty;
         }
+
         return Margin.Thickness + Border.Thickness + Padding.Thickness;
     }
 

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

@@ -3,12 +3,11 @@
 public partial class View
 {
     /// <summary>
-    ///    Gets or sets the user actions that are enabled for the view within it's <see cref="SuperView"/>.
+    ///    Gets or sets the user actions that are enabled for the arranging this view within it's <see cref="SuperView"/>.
     /// </summary>
     /// <remarks>
     /// <para>
-    ///     Sizing or moving a view is only possible if the <see cref="View"/> is part of a <see cref="SuperView"/> and
-    ///     the relevant position and dimensions of the <see cref="View"/> are independent of other SubViews
+    ///     See the View Arrangement Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/arrangement.html"/>
     /// </para>
     /// </remarks>
     public ViewArrangement Arrangement { get; set; }

+ 353 - 0
Terminal.Gui/View/View.Command.cs

@@ -0,0 +1,353 @@
+#nullable enable
+using System.ComponentModel;
+
+namespace Terminal.Gui;
+
+public partial class View // Command APIs
+{
+    #region Default Implementation
+
+    /// <summary>
+    ///     Helper to configure all things Command related for a View. Called from the View constructor.
+    /// </summary>
+    private void SetupCommands ()
+    {
+        // Enter - Raise Accepted
+        AddCommand (Command.Accept, RaiseAccepting);
+
+        // HotKey - SetFocus and raise HandlingHotKey
+        AddCommand (Command.HotKey,
+                    () =>
+                    {
+                        if (RaiseHandlingHotKey () is true)
+                        {
+                            return true;
+                        }
+
+                        SetFocus ();
+
+                        return true;
+                    });
+
+        // Space or single-click - Raise Selecting
+        AddCommand (Command.Select, (ctx) =>
+                                    {
+                                        if (RaiseSelecting (ctx) is true)
+                                        {
+                                            return true;
+                                        }
+
+                                        if (CanFocus)
+                                        {
+                                            SetFocus ();
+
+                                            return true;
+                                        }
+
+                                        return false;
+                                    });
+    }
+
+    /// <summary>
+    ///     Called when the user is accepting the state of the View and the <see cref="Command.Accept"/> has been invoked. Calls <see cref="OnAccepting"/> which can be cancelled; if not cancelled raises <see cref="Accepting"/>.
+    ///     event. The default <see cref="Command.Accept"/> handler calls this method.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    ///     The <see cref="Accepting"/> event should raised after the state of the View has changed (after <see cref="Selecting"/> is raised).
+    /// </para>
+    /// <para>
+    ///    If the Accepting event is not handled, <see cref="Command.Accept"/> will be invoked on the SuperView, enabling default Accept behavior.
+    /// </para>
+    /// <para>
+    ///    If a peer-View raises the Accepting event and the event is not cancelled, the <see cref="Command.Accept"/> will be invoked on the
+    ///    first Button in the SuperView that has <see cref="Button.IsDefault"/> set to <see langword="true"/>.
+    /// </para>
+    /// </remarks>
+    /// <returns>
+    ///     <see langword="null"/> if no event was raised; input proessing should continue.
+    ///     <see langword="false"/> if the event was raised and was not handled (or cancelled); input proessing should continue.
+    ///     <see langword="true"/> if the event was raised and handled (or cancelled); input proessing should stop.
+    /// </returns>
+    protected bool? RaiseAccepting (CommandContext ctx)
+    {
+        CommandEventArgs args = new () { Context = ctx };
+
+        // Best practice is to invoke the virtual method first.
+        // This allows derived classes to handle the event and potentially cancel it.
+        args.Cancel = OnAccepting (args) || args.Cancel;
+
+        if (!args.Cancel)
+        {
+            // If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
+            Accepting?.Invoke (this, args);
+        }
+
+        // Accept is a special case where if the event is not canceled, the event is
+        //  - Invoked on any peer-View with IsDefault == true
+        //  - bubbled up the SuperView hierarchy.
+        if (!args.Cancel)
+        {
+            // If there's an IsDefault peer view in Subviews, try it
+            var isDefaultView = SuperView?.Subviews.FirstOrDefault (v => v is Button { IsDefault: true });
+
+            if (isDefaultView != this && isDefaultView is Button { IsDefault: true } button)
+            {
+                bool? handled = isDefaultView.InvokeCommand (Command.Accept, ctx: new (Command.Accept, null, null, this));
+                if (handled == true)
+                {
+                    return true;
+                }
+            }
+
+            return SuperView?.InvokeCommand (Command.Accept, ctx: new (Command.Accept, null, null, this)) == true;
+        }
+
+        return Accepting is null ? null : args.Cancel;
+    }
+
+    /// <summary>
+    ///     Called when the user is accepting the state of the View and the <see cref="Command.Accept"/> has been invoked. Set CommandEventArgs.Cancel to
+    ///     <see langword="true"/> and return <see langword="true"/> to stop processing.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    ///    See <see cref="View.RaiseAccepting"/> for more information.
+    /// </para>
+    /// </remarks>
+    /// <param name="args"></param>
+    /// <returns><see langword="true"/> to stop processing.</returns>
+    protected virtual bool OnAccepting (CommandEventArgs args) { return false; }
+
+    /// <summary>
+    ///     Cancelable event raised when the user is accepting the state of the View and the <see cref="Command.Accept"/> has been invoked. Set
+    ///     CommandEventArgs.Cancel to cancel the event.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    ///    See <see cref="View.RaiseAccepting"/> for more information.
+    /// </para>
+    /// </remarks>
+    public event EventHandler<CommandEventArgs>? Accepting;
+
+    /// <summary>
+    ///     Called when the user has performed an action (e.g. <see cref="Command.Select"/>) causing the View to change state. Calls <see cref="OnSelecting"/> which can be cancelled; if not cancelled raises <see cref="Accepting"/>.
+    ///     event. The default <see cref="Command.Select"/> handler calls this method.
+    /// </summary>
+    /// <remarks>
+    ///     The <see cref="Selecting"/> event should raised after the state of the View has been changed and before see <see cref="Accepting"/>.
+    /// </remarks>
+    /// <returns>
+    ///     <see langword="null"/> if no event was raised; input proessing should continue.
+    ///     <see langword="false"/> if the event was raised and was not handled (or cancelled); input proessing should continue.
+    ///     <see langword="true"/> if the event was raised and handled (or cancelled); input proessing should stop.
+    /// </returns>
+    protected bool? RaiseSelecting (CommandContext ctx)
+    {
+        CommandEventArgs args = new () { Context = ctx };
+
+        // Best practice is to invoke the virtual method first.
+        // This allows derived classes to handle the event and potentially cancel it.
+        if (OnSelecting (args) || args.Cancel)
+        {
+            return true;
+        }
+
+        // If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
+        Selecting?.Invoke (this, args);
+
+        return Selecting is null ? null : args.Cancel;
+    }
+
+    /// <summary>
+    ///     Called when the user has performed an action (e.g. <see cref="Command.Select"/>) causing the View to change state.
+    ///     Set CommandEventArgs.Cancel to
+    ///     <see langword="true"/> and return <see langword="true"/> to cancel the state change. The default implementation does nothing.
+    /// </summary>
+    /// <param name="args">The event arguments.</param>
+    /// <returns><see langword="true"/> to stop processing.</returns>
+    protected virtual bool OnSelecting (CommandEventArgs args) { return false; }
+
+    /// <summary>
+    ///     Cancelable event raised when the user has performed an action (e.g. <see cref="Command.Select"/>) causing the View to change state.
+    ///     CommandEventArgs.Cancel to <see langword="true"/> to cancel the state change.
+    /// </summary>
+    public event EventHandler<CommandEventArgs>? Selecting;
+
+    /// <summary>
+    ///     Called when the View is handling the user pressing the View's <see cref="HotKey"/>s. Calls <see cref="OnHandlingHotKey"/> which can be cancelled; if not cancelled raises <see cref="Accepting"/>.
+    ///     event. The default <see cref="Command.HotKey"/> handler calls this method.
+    /// </summary>
+    /// <returns>
+    ///     <see langword="null"/> if no event was raised; input proessing should continue.
+    ///     <see langword="false"/> if the event was raised and was not handled (or cancelled); input proessing should continue.
+    ///     <see langword="true"/> if the event was raised and handled (or cancelled); input proessing should stop.
+    /// </returns>
+    protected bool? RaiseHandlingHotKey ()
+    {
+        CommandEventArgs args = new ();
+
+        // Best practice is to invoke the virtual method first.
+        // This allows derived classes to handle the event and potentially cancel it.
+        if (OnHandlingHotKey (args) || args.Cancel)
+        {
+            return true;
+        }
+
+        // If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
+        HandlingHotKey?.Invoke (this, args);
+
+        return HandlingHotKey is null ? null : args.Cancel;
+    }
+
+    /// <summary>
+    ///     Called when the View is handling the user pressing the View's <see cref="HotKey"/>. Set CommandEventArgs.Cancel to
+    ///     <see langword="true"/> to stop processing.
+    /// </summary>
+    /// <param name="args"></param>
+    /// <returns><see langword="true"/> to stop processing.</returns>
+    protected virtual bool OnHandlingHotKey (CommandEventArgs args) { return false; }
+
+    /// <summary>
+    ///     Cancelable event raised when the View is handling the user pressing the View's <see cref="HotKey"/>. Set
+    ///     CommandEventArgs.Cancel to cancel the event.
+    /// </summary>
+    public event EventHandler<CommandEventArgs>? HandlingHotKey;
+
+    #endregion Default Implementation
+
+    /// <summary>
+    /// Function signature commands.
+    /// </summary>
+    /// <param name="ctx">Provides information about the circumstances of invoking the command (e.g. <see cref="CommandContext.Key"/>)</param>
+    /// <returns>
+    ///     <see langword="null"/> if no command was found; input proessing should continue.
+    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input proessing should continue.
+    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input proessing should stop.
+    /// </returns>
+    public delegate bool? CommandImplementation (CommandContext ctx);
+
+    /// <summary>
+    ///     <para>
+    ///         Sets the function that will be invoked for a <see cref="Command"/>. Views should call
+    ///         AddCommand for each command they support.
+    ///     </para>
+    ///     <para>
+    ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="impl"/> will
+    ///         replace the old one.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This version of AddCommand is for commands that require <see cref="CommandContext"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="command">The command.</param>
+    /// <param name="impl">The delegate.</param>
+    protected void AddCommand (Command command, CommandImplementation impl) { CommandImplementations [command] = impl; }
+
+    /// <summary>
+    ///     <para>
+    ///         Sets the function that will be invoked for a <see cref="Command"/>. Views should call
+    ///         AddCommand for each command they support.
+    ///     </para>
+    ///     <para>
+    ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="impl"/> will
+    ///         replace the old one.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This version of AddCommand is for commands that do not require a <see cref="CommandContext"/>.
+    ///         If the command requires context, use
+    ///         <see cref="AddCommand(Command,CommandImplementation)"/>
+    ///     </para>
+    /// </remarks>
+    /// <param name="command">The command.</param>
+    /// <param name="impl">The delegate.</param>
+    protected void AddCommand (Command command, Func<bool?> impl) { CommandImplementations [command] = ctx => impl (); }
+
+    /// <summary>Returns all commands that are supported by this <see cref="View"/>.</summary>
+    /// <returns></returns>
+    public IEnumerable<Command> GetSupportedCommands () { return CommandImplementations.Keys; }
+
+    /// <summary>
+    ///     Invokes the specified commands.
+    /// </summary>
+    /// <param name="commands">The set of commands to invoke.</param>
+    /// <param name="key">The key that caused the command to be invoked, if any. This will be passed as context with the command.</param>
+    /// <param name="keyBinding">The key binding that was bound to the key and caused the invocation, if any. This will be passed as context with the command.</param>
+    /// <returns>
+    ///     <see langword="null"/> if no command was found; input proessing should continue.
+    ///     <see langword="false"/> if at least one command was invoked and was not handled (or cancelled); input proessing should continue.
+    ///     <see langword="true"/> if at least one command was invoked the command was handled (or cancelled); input proessing should stop.
+    /// </returns>
+    public bool? InvokeCommands (Command [] commands, Key? key = null, KeyBinding? keyBinding = null)
+    {
+        bool? toReturn = null;
+
+        foreach (Command command in commands)
+        {
+            if (!CommandImplementations.ContainsKey (command))
+            {
+                throw new NotSupportedException (
+                                                 @$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by this View ({GetType ().Name})"
+                                                );
+            }
+
+            // each command has its own return value
+            bool? thisReturn = InvokeCommand (command, key, keyBinding);
+
+            // if we haven't got anything yet, the current command result should be used
+            toReturn ??= thisReturn;
+
+            // if ever see a true then that's what we will return
+            if (thisReturn ?? false)
+            {
+                toReturn = true;
+            }
+        }
+
+        return toReturn;
+    }
+
+    /// <summary>Invokes the specified command.</summary>
+    /// <param name="command">The command to invoke.</param>
+    /// <param name="key">The key that caused the command to be invoked, if any. This will be passed as context with the command.</param>
+    /// <param name="keyBinding">The key binding that was bound to the key and caused the invocation, if any. This will be passed as context with the command.</param>
+    /// <returns>
+    ///     <see langword="null"/> if no command was found; input proessing should continue.
+    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input proessing should continue.
+    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input proessing should stop.
+    /// </returns>
+    public bool? InvokeCommand (Command command, Key? key = null, KeyBinding? keyBinding = null)
+    {
+        if (CommandImplementations.TryGetValue (command, out CommandImplementation? implementation))
+        {
+            var context = new CommandContext (command, key, keyBinding); // Create the context here
+            return implementation (context);
+        }
+
+        return null;
+    }
+
+    /// <summary>
+    /// Invokes the specified command.
+    /// </summary>
+    /// <param name="command">The command to invoke.</param>
+    /// <param name="ctx">Context to pass with the invocation.</param>
+    /// <returns>
+    ///     <see langword="null"/> if no command was found; input proessing should continue.
+    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input proessing should continue.
+    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input proessing should stop.
+    /// </returns>
+    public bool? InvokeCommand (Command command, CommandContext ctx)
+    {
+        if (CommandImplementations.TryGetValue (command, out CommandImplementation? implementation))
+        {
+            return implementation (ctx);
+        }
+
+        return null;
+    }
+}

+ 12 - 0
Terminal.Gui/View/View.Content.cs

@@ -12,6 +12,9 @@ public partial class View
     /// </summary>
     /// <remarks>
     ///     <para>
+    ///         See the View Layout Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+    ///     </para>
+    ///     <para>
     ///         Negative sizes are not supported.
     ///     </para>
     ///     <para>
@@ -55,6 +58,9 @@ public partial class View
     /// </summary>
     /// <remarks>a>
     ///     <para>
+    ///         See the View Layout Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+    ///     </para>
+    ///     <para>
     ///         If the content size was not explicitly set by <see cref="SetContentSize"/>, and the View has no visible subviews, <see cref="GetContentSize ()"/> will return the
     ///         size of
     ///         <see cref="Viewport"/>.
@@ -85,6 +91,9 @@ public partial class View
     ///     size or not.
     /// </summary>
     /// <remarks>
+    ///     <para>
+    ///         See the View Layout Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+    ///     </para>
     ///     <list type="bullet">
     ///         <listheader>
     ///             <term>Value</term> <description>Result</description>
@@ -236,6 +245,9 @@ public partial class View
     /// </value>
     /// <remarks>
     ///     <para>
+    ///         See the View Layout Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+    ///     </para>
+    ///     <para>
     ///         Positive values for the location indicate the visible area is offset into (down-and-right) the View's virtual
     ///         <see cref="GetContentSize ()"/>. This enables scrolling down and to the right (e.g. in a <see cref="ListView"/>
     ///         .

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

@@ -20,10 +20,9 @@ public enum ViewDiagnosticFlags : uint
     Padding = 0b_0000_0010,
 
     /// <summary>
-    ///     When enabled, <see cref="Adornment.OnMouseEnter(Gui.MouseEvent)"/> and <see cref="Adornment.OnMouseLeave(Gui.MouseEvent)"/>
-    ///     will invert the foreground and background colors.
+    ///     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>
-    MouseEnter = 0b_0000_00100
+    Hover = 0b_0000_00100
 }
 
 public partial class View

+ 112 - 64
Terminal.Gui/View/View.Drawing.cs

@@ -1,13 +1,12 @@
-using System.Drawing;
-
+#nullable enable
 namespace Terminal.Gui;
 
 public partial class View // Drawing APIs
 {
-    private ColorScheme _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
+    public virtual ColorScheme? ColorScheme
     {
         get
         {
@@ -23,6 +22,11 @@ public partial class View // Drawing APIs
             if (_colorScheme != value)
             {
                 _colorScheme = value;
+
+                if (Border is { } && Border.LineStyle != LineStyle.None && Border.ColorScheme is { })
+                {
+                    Border.ColorScheme = _colorScheme;
+                }
                 SetNeedsDisplay ();
             }
         }
@@ -56,7 +60,7 @@ public partial class View // Drawing APIs
     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 
+    ///     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.
@@ -86,7 +90,8 @@ public partial class View // Drawing APIs
     ///     <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
+    ///         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.
@@ -143,15 +148,15 @@ public partial class View // Drawing APIs
 
     /// <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>
+    ///     <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
@@ -182,11 +187,15 @@ public partial class View // Drawing APIs
     }
 
     /// <summary>
-    ///     Draws the view. Causes the following virtual methods to be called (along with their related events):
+    ///     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"/>.
     /// </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>
@@ -202,6 +211,23 @@ public partial class View // Drawing APIs
     /// </remarks>
     public void Draw ()
     {
+        if (!CanBeVisible (this))
+        {
+            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))
+        {
+            SetNeedsDisplay ();
+        }
+
+        if (!NeedsDisplay && !SubViewNeedsDisplay && !LayoutNeeded)
+        {
+            return;
+        }
+
         OnDrawAdornments ();
 
         if (ColorScheme is { })
@@ -257,8 +283,7 @@ public partial class View // Drawing APIs
     ///         <see cref="View"/> .
     ///     </para>
     /// </remarks>
-    [CanBeNull]
-    public event EventHandler<DrawEventArgs> DrawContent;
+    public event EventHandler<DrawEventArgs>? DrawContent;
 
     /// <summary>Event invoked when the content area of the View is completed drawing.</summary>
     /// <remarks>
@@ -268,8 +293,7 @@ public partial class View // Drawing APIs
     ///         <see cref="View"/> .
     ///     </para>
     /// </remarks>
-    [CanBeNull]
-    public event EventHandler<DrawEventArgs> DrawContentComplete;
+    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>
@@ -310,19 +334,18 @@ public partial class View // Drawing APIs
     ///     If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise
     ///     the regular ones.
     /// </param>
-    /// <param name="scheme">The color scheme to use.</param>
-    public void DrawHotString (string text, bool focused, ColorScheme scheme)
+    public void DrawHotString (string text, bool focused)
     {
         if (focused)
         {
-            DrawHotString (text, scheme.HotFocus, scheme.Focus);
+            DrawHotString (text, GetHotFocusColor (), GetFocusColor ());
         }
         else
         {
             DrawHotString (
                            text,
-                           Enabled ? scheme.HotNormal : scheme.Disabled,
-                           Enabled ? scheme.Normal : scheme.Disabled
+                           Enabled ? GetHotNormalColor () : ColorScheme!.Disabled,
+                           Enabled ? GetNormalColor () : ColorScheme!.Disabled
                           );
         }
     }
@@ -335,13 +358,27 @@ public partial class View // Drawing APIs
     /// </returns>
     public virtual Attribute GetFocusColor ()
     {
-        ColorScheme cs = ColorScheme;
+        ColorScheme? cs = ColorScheme;
+
         if (cs is null)
         {
             cs = new ();
         }
 
-        return Enabled ? cs.Focus : cs.Disabled;
+        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>
@@ -352,14 +389,14 @@ public partial class View // Drawing APIs
     /// </returns>
     public virtual Attribute GetHotNormalColor ()
     {
-        ColorScheme cs = ColorScheme;
+        ColorScheme? cs = ColorScheme;
 
         if (cs is null)
         {
             cs = new ();
         }
 
-        return Enabled ? cs.HotNormal : cs.Disabled;
+        return Enabled ? GetColor (cs.HotNormal) : cs.Disabled;
     }
 
     /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
@@ -370,14 +407,30 @@ public partial class View // Drawing APIs
     /// </returns>
     public virtual Attribute GetNormalColor ()
     {
-        ColorScheme cs = ColorScheme;
+        ColorScheme? cs = ColorScheme;
 
         if (cs is null)
         {
             cs = new ();
         }
 
-        return Enabled ? cs.Normal : cs.Disabled;
+        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;
     }
 
     /// <summary>Moves the drawing cursor to the specified <see cref="Viewport"/>-relative location in the view.</summary>
@@ -403,7 +456,7 @@ public partial class View // Drawing APIs
             return false;
         }
 
-        var screen = ViewportToScreen (new Point (col, row));
+        Point screen = ViewportToScreen (new Point (col, row));
         Driver?.Move (screen.X, screen.Y);
 
         return true;
@@ -442,12 +495,14 @@ public partial class View // Drawing APIs
     ///     </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
+    ///         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"/>
+    ///         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>
@@ -467,14 +522,15 @@ public partial class View // Drawing APIs
     {
         if (NeedsDisplay)
         {
-            if (SuperView is { })
+            if (!CanBeVisible (this))
             {
-                Clear ();
+                return;
             }
 
-            if (!CanBeVisible (this))
+            // BUGBUG: this clears way too frequently. Need to optimize this.
+            if (SuperView is { } || Arrangement.HasFlag (ViewArrangement.Overlapped))
             {
-                return;
+                Clear ();
             }
 
             if (!string.IsNullOrEmpty (TextFormatter.Text))
@@ -492,7 +548,7 @@ public partial class View // Drawing APIs
             TextFormatter?.Draw (
                                  drawRect,
                                  HasFocus ? GetFocusColor () : GetNormalColor (),
-                                 HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
+                                 HasFocus ? GetHotFocusColor () : GetHotNormalColor (),
                                  Rectangle.Empty
                                 );
             SetSubViewNeedsDisplay ();
@@ -503,24 +559,14 @@ public partial class View // Drawing APIs
         // TODO: Implement OnDrawSubviews (cancelable);
         if (_subviews is { } && SubViewNeedsDisplay)
         {
-            IEnumerable<View> subviewsNeedingDraw;
-            if (TabStop == TabBehavior.TabGroup && _subviews.Count(v => v.Arrangement.HasFlag (ViewArrangement.Overlapped)) > 0)
-            {
-                // TODO: This is a temporary hack to make overlapped non-Toplevels have a zorder. See also View.SetFocus
-                subviewsNeedingDraw = _subviews.Where (
-                                                       view => view.Visible
-                                                               && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
-                                                      ).Reverse ();
-
-            }
-            else
-            {
-                subviewsNeedingDraw = _subviews.Where (
-                                                                         view => view.Visible
-                                                                                 && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
-                                                                        );
+            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)
@@ -528,6 +574,13 @@ public partial class View // Drawing APIs
                     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 ();
+                }
+
                 view.Draw ();
             }
         }
@@ -564,7 +617,7 @@ public partial class View // Drawing APIs
                 // Get the entire map
                 if (p.Value is { })
                 {
-                    Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme.Normal);
+                    Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme!.Normal);
                     Driver.Move (p.Key.X, p.Key.Y);
 
                     // TODO: #2616 - Support combining sequences that don't normalize
@@ -589,7 +642,7 @@ public partial class View // Drawing APIs
                 // Get the entire map
                 if (p.Value is { })
                 {
-                    Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme.Normal);
+                    Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme!.Normal);
                     Driver.Move (p.Key.X, p.Key.Y);
 
                     // TODO: #2616 - Support combining sequences that don't normalize
@@ -608,10 +661,7 @@ public partial class View // Drawing APIs
     ///     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 SetNeedsDisplay () { SetNeedsDisplay (Viewport); }
 
     /// <summary>Expands the area of this view needing to be redrawn to include <paramref name="region"/>.</summary>
     /// <remarks>
@@ -671,8 +721,6 @@ public partial class View // Drawing APIs
         if (SuperView is { SubViewNeedsDisplay: false })
         {
             SuperView.SetSubViewNeedsDisplay ();
-
-            return;
         }
     }
 

+ 11 - 6
Terminal.Gui/View/View.Hierarchy.cs

@@ -159,14 +159,17 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
 
         Rectangle touched = view.Frame;
 
-        // If a view being removed is focused, it should lose focus.
-        if (view.HasFocus)
+        bool hadFocus = view.HasFocus;
+        bool couldFocus = view.CanFocus;
+
+        if (hadFocus)
         {
-            view.HasFocus = false;
+            view.CanFocus = false; // If view had focus, this will ensure it doesn't and it stays that way
         }
+        Debug.Assert (!view.HasFocus);
 
         _subviews.Remove (view);
-        view._superView = null; // Null this AFTER removing focus
+        view._superView = null;
 
         SetNeedsLayout ();
         SetNeedsDisplay ();
@@ -179,9 +182,11 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
             }
         }
 
-        if (HasFocus)
+        view.CanFocus = couldFocus; // Restore to previous value
+
+        if (_previouslyFocused == view)
         {
-            FocusDeepest (NavigationDirection.Forward, TabStop);
+            _previouslyFocused = null;
         }
 
         OnRemoved (new (this, view));

+ 35 - 144
Terminal.Gui/View/View.Keyboard.cs

@@ -11,14 +11,12 @@ public partial class View // Keyboard APIs
     private void SetupKeyboard ()
     {
         KeyBindings = new (this);
+        KeyBindings.Add (Key.Space, Command.Select);
+        KeyBindings.Add (Key.Enter, Command.Accept);
+
+        // Note, setting HotKey will bind HotKey to Command.HotKey
         HotKeySpecifier = (Rune)'_';
         TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged;
-
-        // By default, the HotKey command sets the focus
-        AddCommand (Command.HotKey, OnHotKey);
-
-        // By default, the Accept command raises the Accept event
-        AddCommand (Command.Accept, OnAccept);
     }
 
     /// <summary>
@@ -28,22 +26,6 @@ public partial class View // Keyboard APIs
 
     #region HotKey Support
 
-    /// <summary>
-    ///     Called when the HotKey command (<see cref="Command.HotKey"/>) is invoked. Causes this view to be focused.
-    /// </summary>
-    /// <returns>If <see langword="true"/> the command was canceled.</returns>
-    private bool? OnHotKey ()
-    {
-        if (CanFocus)
-        {
-            SetFocus ();
-
-            return true;
-        }
-
-        return false;
-    }
-
     /// <summary>Invoked when the <see cref="HotKey"/> is changed.</summary>
     public event EventHandler<KeyChangedEventArgs>? HotKeyChanged;
 
@@ -52,16 +34,22 @@ public partial class View // Keyboard APIs
 
     /// <summary>
     ///     Gets or sets the hot key defined for this view. Pressing the hot key on the keyboard while this view has focus will
-    ///     invoke the <see cref="Command.HotKey"/> and <see cref="Command.Accept"/> commands. <see cref="Command.HotKey"/>
-    ///     causes the view to be focused and <see cref="Command.Accept"/> does nothing. By default, the HotKey is
-    ///     automatically set to the first character of <see cref="Text"/> that is prefixed with <see cref="HotKeySpecifier"/>.
+    ///     invoke <see cref="Command.HotKey"/>. By default, the HotKey is set to the first character of <see cref="Text"/>
+    ///     that is prefixed with <see cref="HotKeySpecifier"/>.
+    ///     <para>
+    ///         A HotKey is a keypress that causes a visible UI item to perform an action. For example, in a Dialog,
+    ///         with a Button with the text of "_Text" <c>Alt+T</c> will cause the button to gain focus and to raise its
+    ///         <see cref="Accepting"/> event.
+    ///         Or, in a
+    ///         <see cref="Menu"/> with "_File _Edit", <c>Alt+F</c> will select (show) the "_File" menu. If the "_File" menu
+    ///         has a
+    ///         sub-menu of "_New" <c>Alt+N</c> or <c>N</c> will ONLY select the "_New" sub-menu if the "_File" menu is already
+    ///         opened.
+    ///     </para>
     ///     <para>
-    ///         A HotKey is a keypress that selects a visible UI item. For selecting items across <see cref="View"/>`s (e.g.a
-    ///         <see cref="Button"/> in a <see cref="Dialog"/>) the keypress must include the <see cref="Key.WithAlt"/>
-    ///         modifier. For selecting items within a View that are not Views themselves, the keypress can be key without the
-    ///         Alt modifier. For example, in a Dialog, a Button with the text of "_Text" can be selected with Alt-T. Or, in a
-    ///         <see cref="Menu"/> with "_File _Edit", Alt-F will select (show) the "_File" menu. If the "_File" menu has a
-    ///         sub-menu of "_New" `Alt-N` or `N` will ONLY select the "_New" sub-menu if the "_File" menu is already opened.
+    ///         View subclasses can use <see cref="View.AddCommand(Command,CommandImplementation)"/> to
+    ///         define the
+    ///         behavior of the hot key.
     ///     </para>
     /// </summary>
     /// <remarks>
@@ -510,7 +498,7 @@ public partial class View // Keyboard APIs
     /// <summary>Gets the key bindings for this view.</summary>
     public KeyBindings KeyBindings { get; internal set; } = null!;
 
-    private Dictionary<Command, Func<CommandContext, bool?>> CommandImplementations { get; } = new ();
+    private Dictionary<Command, CommandImplementation> CommandImplementations { get; } = new ();
 
     /// <summary>
     ///     Low-level API called when a user presses a key; invokes any key bindings set on the view. This is called
@@ -523,8 +511,9 @@ public partial class View // Keyboard APIs
     /// <param name="keyEvent">Contains the details about the key that produced the event.</param>
     /// <param name="scope">The scope.</param>
     /// <returns>
-    ///     <see langword="false"/> if the key press was not handled. <see langword="true"/> if the keypress was handled
-    ///     and no other view should see it.
+    ///     <see langword="null"/> if no event was raised; input proessing should continue.
+    ///     <see langword="false"/> if the event was raised and was not handled (or cancelled); input proessing should continue.
+    ///     <see langword="true"/> if the event was raised and handled (or cancelled); input proessing should stop.
     /// </returns>
     public virtual bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope)
     {
@@ -579,6 +568,13 @@ public partial class View // Keyboard APIs
 
     private bool ProcessAdornmentKeyBindings (Adornment adornment, Key keyEvent, KeyBindingScope scope, ref bool? handled)
     {
+        bool? adornmentHandled = adornment.OnInvokingKeyBindings (keyEvent, scope);
+
+        if (adornmentHandled is true)
+        {
+            return true;
+        }
+
         if (adornment?.Subviews is null)
         {
             return false;
@@ -681,7 +677,7 @@ public partial class View // Keyboard APIs
     }
 
     /// <summary>
-    ///     Invoked when a key is pressed that may be mapped to a key binding. Set <see cref="Key.Handled"/> to true to
+    ///     Raised when a key is pressed that may be mapped to a key binding. Set <see cref="Key.Handled"/> to true to
     ///     stop the key from being processed by other views.
     /// </summary>
     public event EventHandler<Key>? InvokingKeyBindings;
@@ -693,9 +689,9 @@ public partial class View // Keyboard APIs
     /// <param name="key">The key event passed.</param>
     /// <param name="scope">The scope.</param>
     /// <returns>
-    ///     <see langword="null"/> if no command was bound the <paramref name="key"/>. <see langword="true"/> if
-    ///     commands were invoked and at least one handled the command. <see langword="false"/> if commands were invoked and at
-    ///     none handled the command.
+    ///     <see langword="null"/> if no command was invoked; input proessing should continue.
+    ///     <see langword="false"/> if at least one command was invoked and was not handled (or cancelled); input proessing should continue.
+    ///     <see langword="true"/> if at least one command was invoked and handled (or cancelled); input proessing should stop.
     /// </returns>
     protected bool? InvokeKeyBindings (Key key, KeyBindingScope scope)
     {
@@ -726,6 +722,7 @@ public partial class View // Keyboard APIs
         }
 
 #endif
+        return InvokeCommands (binding.Commands, key, binding);
 
         foreach (Command command in binding.Commands)
         {
@@ -752,111 +749,5 @@ public partial class View // Keyboard APIs
         return toReturn;
     }
 
-    /// <summary>
-    ///     Invokes the specified commands.
-    /// </summary>
-    /// <param name="commands"></param>
-    /// <param name="key">The key that caused the commands to be invoked, if any.</param>
-    /// <param name="keyBinding"></param>
-    /// <returns>
-    ///     <see langword="null"/> if no command was found.
-    ///     <see langword="true"/> if the command was invoked the command was handled.
-    ///     <see langword="false"/> if the command was invoked and the command was not handled.
-    /// </returns>
-    public bool? InvokeCommands (Command [] commands, Key? key = null, KeyBinding? keyBinding = null)
-    {
-        bool? toReturn = null;
-
-        foreach (Command command in commands)
-        {
-            if (!CommandImplementations.ContainsKey (command))
-            {
-                throw new NotSupportedException (@$"{command} is not supported by ({GetType ().Name}).");
-            }
-
-            // each command has its own return value
-            bool? thisReturn = InvokeCommand (command, key, keyBinding);
-
-            // if we haven't got anything yet, the current command result should be used
-            toReturn ??= thisReturn;
-
-            // if ever see a true then that's what we will return
-            if (thisReturn ?? false)
-            {
-                toReturn = true;
-            }
-        }
-
-        return toReturn;
-    }
-
-    /// <summary>Invokes the specified command.</summary>
-    /// <param name="command">The command to invoke.</param>
-    /// <param name="key">The key that caused the command to be invoked, if any.</param>
-    /// <param name="keyBinding"></param>
-    /// <returns>
-    ///     <see langword="null"/> if no command was found. <see langword="true"/> if the command was invoked, and it
-    ///     handled the command. <see langword="false"/> if the command was invoked, and it did not handle the command.
-    /// </returns>
-    public bool? InvokeCommand (Command command, Key? key = null, KeyBinding? keyBinding = null)
-    {
-        if (CommandImplementations.TryGetValue (command, out Func<CommandContext, bool?>? implementation))
-        {
-            var context = new CommandContext (command, key, keyBinding); // Create the context here
-
-            return implementation (context);
-        }
-
-        return null;
-    }
-
-    /// <summary>
-    ///     <para>
-    ///         Sets the function that will be invoked for a <see cref="Command"/>. Views should call
-    ///         AddCommand for each command they support.
-    ///     </para>
-    ///     <para>
-    ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
-    ///         replace the old one.
-    ///     </para>
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         This version of AddCommand is for commands that require <see cref="CommandContext"/>. Use
-    ///         <see cref="AddCommand(Command,Func{System.Nullable{bool}})"/>
-    ///         in cases where the command does not require a <see cref="CommandContext"/>.
-    ///     </para>
-    /// </remarks>
-    /// <param name="command">The command.</param>
-    /// <param name="f">The function.</param>
-    protected void AddCommand (Command command, Func<CommandContext, bool?> f) { CommandImplementations [command] = f; }
-
-    /// <summary>
-    ///     <para>
-    ///         Sets the function that will be invoked for a <see cref="Command"/>. Views should call
-    ///         AddCommand for each command they support.
-    ///     </para>
-    ///     <para>
-    ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
-    ///         replace the old one.
-    ///     </para>
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         This version of AddCommand is for commands that do not require a <see cref="CommandContext"/>.
-    ///         If the command requires context, use
-    ///         <see cref="AddCommand(Command,Func{CommandContext,System.Nullable{bool}})"/>
-    ///     </para>
-    /// </remarks>
-    /// <param name="command">The command.</param>
-    /// <param name="f">The function.</param>
-    protected void AddCommand (Command command, Func<bool?> f) { CommandImplementations [command] = ctx => f (); }
-
-    /// <summary>Returns all commands that are supported by this <see cref="View"/>.</summary>
-    /// <returns></returns>
-    public IEnumerable<Command> GetSupportedCommands () { return CommandImplementations.Keys; }
-
-    // TODO: Add GetKeysBoundToCommand() - given a Command, return all Keys that would invoke it
-
     #endregion Key Bindings
 }

+ 65 - 101
Terminal.Gui/View/View.Layout.cs

@@ -1,5 +1,6 @@
 #nullable enable
 using System.Diagnostics;
+using Microsoft.CodeAnalysis;
 
 namespace Terminal.Gui;
 
@@ -12,83 +13,7 @@ public partial class View // Layout APIs
     /// <returns><see langword="true"/> if the specified SuperView-relative coordinates are within the View.</returns>
     public virtual bool Contains (in Point location) { return Frame.Contains (location); }
 
-    /// <summary>Finds the first Subview of <paramref name="start"/> that is visible at the provided location.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         Used to determine what view the mouse is over.
-    ///     </para>
-    /// </remarks>
-    /// <param name="start">The view to scope the search by.</param>
-    /// <param name="location"><paramref name="start"/>.SuperView-relative coordinate.</param>
-    /// <returns>
-    ///     The view that was found at the <paramref name="location"/> coordinate.
-    ///     <see langword="null"/> if no view was found.
-    /// </returns>
-
-    // CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
-    internal static View? FindDeepestView (View? start, in Point location)
-    {
-        Point currentLocation = location;
-
-        while (start is { Visible: true } && start.Contains (currentLocation))
-        {
-            Adornment? found = null;
-
-            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 ();
-
-            if (found is { })
-            {
-                start = found;
-                viewportOffset = found.Parent?.Frame.Location ?? Point.Empty;
-            }
-
-            int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X);
-            int startOffsetY = currentLocation.Y - (start.Frame.Y + viewportOffset.Y);
-
-            View? subview = null;
-
-            for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
-            {
-                if (start.InternalSubviews [i].Visible
-                    && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y)))
-                {
-                    subview = start.InternalSubviews [i];
-                    currentLocation.X = startOffsetX + start.Viewport.X;
-                    currentLocation.Y = startOffsetY + start.Viewport.Y;
-
-                    // start is the deepest subview under the mouse; stop searching the subviews
-                    break;
-                }
-            }
-
-            if (subview is null)
-            {
-                // No subview was found that's under the mouse, so we're done
-                return start;
-            }
-
-            // We found a subview of start that's under the mouse, continue...
-            start = subview;
-        }
-
-        return null;
-    }
-
     // BUGBUG: This method interferes with Dialog/MessageBox default min/max size.
-
     /// <summary>
     ///     Gets a new location of the <see cref="View"/> that is within the Viewport of the <paramref name="viewToMove"/>'s
     ///     <see cref="View.SuperView"/> (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates.
@@ -113,13 +38,14 @@ public partial class View // Layout APIs
         int targetX,
         int targetY,
         out int nx,
-        out int ny,
-        out StatusBar? statusBar
+        out int ny
+       //,
+       // out StatusBar? statusBar
     )
     {
         int maxDimension;
         View? superView;
-        statusBar = null!;
+        //statusBar = null!;
 
         if (viewToMove is not Toplevel || viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         {
@@ -187,26 +113,26 @@ public partial class View // Layout APIs
 
         ny = Math.Max (targetY, maxDimension);
 
-        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
-        {
-            statusVisible = Application.Top?.StatusBar?.Visible == true;
-            statusBar = Application.Top?.StatusBar!;
-        }
-        else
-        {
-            View? t = viewToMove!.SuperView;
-
-            while (t is { } and not Toplevel)
-            {
-                t = t.SuperView;
-            }
-
-            if (t is Toplevel topLevel)
-            {
-                statusVisible = topLevel.StatusBar?.Visible == true;
-                statusBar = topLevel.StatusBar!;
-            }
-        }
+        //if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
+        //{
+        //    statusVisible = Application.Top?.StatusBar?.Visible == true;
+        //    statusBar = Application.Top?.StatusBar!;
+        //}
+        //else
+        //{
+        //    View? t = viewToMove!.SuperView;
+
+        //    while (t is { } and not Toplevel)
+        //    {
+        //        t = t.SuperView;
+        //    }
+
+        //    if (t is Toplevel topLevel)
+        //    {
+        //        statusVisible = topLevel.StatusBar?.Visible == true;
+        //        statusBar = topLevel.StatusBar!;
+        //    }
+        //}
 
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         {
@@ -252,6 +178,10 @@ public partial class View // Layout APIs
     /// </value>
     /// <remarks>
     ///     <para>
+    ///         See the View Layout Deep Dive for more information:
+    ///         <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+    ///     </para>
+    ///     <para>
     ///         Frame is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="GetContentSize ()"/>
     ///         .
     ///     </para>
@@ -288,6 +218,8 @@ public partial class View // Layout APIs
             {
                 OnResizeNeeded ();
             }
+
+            SetNeedsDisplay ();
         }
     }
 
@@ -369,6 +301,10 @@ public partial class View // Layout APIs
     /// <value>The <see cref="Pos"/> object representing the X position.</value>
     /// <remarks>
     ///     <para>
+    ///         See the View Layout Deep Dive for more information:
+    ///         <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+    ///     </para>
+    ///     <para>
     ///         The position is relative to the <see cref="SuperView"/>'s Content, which is bound by
     ///         <see cref="GetContentSize ()"/>.
     ///     </para>
@@ -408,6 +344,10 @@ public partial class View // Layout APIs
     /// <value>The <see cref="Pos"/> object representing the Y position.</value>
     /// <remarks>
     ///     <para>
+    ///         See the View Layout Deep Dive for more information:
+    ///         <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+    ///     </para>
+    ///     <para>
     ///         The position is relative to the <see cref="SuperView"/>'s Content, which is bound by
     ///         <see cref="GetContentSize ()"/>.
     ///     </para>
@@ -446,6 +386,10 @@ public partial class View // Layout APIs
     /// <value>The <see cref="Dim"/> object representing the height of the view (the number of rows).</value>
     /// <remarks>
     ///     <para>
+    ///         See the View Layout Deep Dive for more information:
+    ///         <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+    ///     </para>
+    ///     <para>
     ///         The dimension is relative to the <see cref="SuperView"/>'s Content, which is bound by
     ///         <see cref="GetContentSize ()"/>
     ///         .
@@ -495,6 +439,10 @@ public partial class View // Layout APIs
     /// <value>The <see cref="Dim"/> object representing the width of the view (the number of columns).</value>
     /// <remarks>
     ///     <para>
+    ///         See the View Layout Deep Dive for more information:
+    ///         <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+    ///     </para>
+    ///     <para>
     ///         The dimension is relative to the <see cref="SuperView"/>'s Content, which is bound by
     ///         <see cref="GetContentSize ()"/>
     ///         .
@@ -666,6 +614,10 @@ public partial class View // Layout APIs
     /// </summary>
     /// <remarks>
     ///     <para>
+    ///         See the View Layout Deep Dive for more information:
+    ///         <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+    ///     </para>
+    ///     <para>
     ///         The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, the
     ///         behavior of this method is indeterminate if <see cref="IsInitialized"/> is <see langword="false"/>.
     ///     </para>
@@ -768,14 +720,26 @@ public partial class View // Layout APIs
             LayoutAdornments ();
         }
 
-        SetNeedsDisplay ();
         SetNeedsLayout ();
+
+        // 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))
+        {
+            foreach (Toplevel v in Application.TopLevels)
+            {
+                if (v.Visible && v != this)
+                {
+                    v.SetNeedsDisplay ();
+                }
+            }
+        }
     }
 
     internal bool LayoutNeeded { get; private set; } = true;
 
     /// <summary>
-    ///     Sets the internal <see cref="LayoutNeeded"/> flag for this View and all of it's subviews and it's SuperView.
+    ///     Sets <see cref="LayoutNeeded"/> for this View and all of it's subviews and it's SuperView.
     ///     The main loop will call SetRelativeLayout and LayoutSubviews for any view with <see cref="LayoutNeeded"/> set.
     /// </summary>
     internal void SetNeedsLayout ()

+ 367 - 231
Terminal.Gui/View/View.Mouse.cs

@@ -5,34 +5,196 @@ namespace Terminal.Gui;
 
 public partial class View // Mouse APIs
 {
-    private ColorScheme? _savedHighlightColorScheme;
+    #region MouseEnterLeave
+
+    private bool _hovering;
+    private ColorScheme? _savedNonHoverColorScheme;
 
     /// <summary>
-    ///     Fired when the view is highlighted. Set <see cref="CancelEventArgs.Cancel"/> to <see langword="true"/>
-    ///     to implement a custom highlight scheme or prevent the view from being highlighted.
+    ///     INTERNAL Called by <see cref="Application.OnMouseEvent"/> when the mouse moves over the View's <see cref="Frame"/>.
+    ///     <see cref="MouseLeave"/> will
+    ///     be raised when the mouse is no longer over the <see cref="Frame"/>. If another View occludes this View, the
+    ///     that View will also receive MouseEnter/Leave events.
     /// </summary>
-    public event EventHandler<CancelEventArgs<HighlightStyle>>? Highlight;
+    /// <param name="eventArgs"></param>
+    /// <returns>
+    ///     <see langword="true"/> if the event was canceled, <see langword="false"/> if not, <see langword="null"/> if the
+    ///     view is not visible. Cancelling the event
+    ///     prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
+    /// </returns>
+    internal bool? NewMouseEnterEvent (CancelEventArgs eventArgs)
+    {
+        // Pre-conditions
+        if (!CanBeVisible (this))
+        {
+            return null;
+        }
+
+        // Cancellable event
+        if (OnMouseEnter (eventArgs))
+        {
+            return true;
+        }
+
+        MouseEnter?.Invoke (this, eventArgs);
+
+        _hovering = !eventArgs.Cancel;
+
+        if (eventArgs.Cancel)
+        {
+            return true;
+        }
+
+        // Post-conditions
+        if (HighlightStyle.HasFlag (HighlightStyle.Hover) || Diagnostics.HasFlag (ViewDiagnosticFlags.Hover))
+        {
+            HighlightStyle copy = HighlightStyle;
+            var hover = HighlightStyle.Hover;
+            CancelEventArgs<HighlightStyle> args = new (ref copy, ref hover);
+
+            if (RaiseHighlight (args) || args.Cancel)
+            {
+                return args.Cancel;
+            }
+
+            ColorScheme? cs = ColorScheme;
+
+            if (cs is null)
+            {
+                cs = new ();
+            }
+
+            _savedNonHoverColorScheme = cs;
+
+            ColorScheme = ColorScheme?.GetHighlightColorScheme ();
+        }
+
+        return false;
+    }
 
     /// <summary>
-    ///     Gets or sets whether the <see cref="View"/> will be highlighted visually while the mouse button is
-    ///     pressed.
+    ///     Called when the mouse moves over the View's <see cref="Frame"/> and no other non-Subview occludes it.
+    ///     <see cref="MouseLeave"/> will
+    ///     be raised when the mouse is no longer over the <see cref="Frame"/>.
     /// </summary>
-    public HighlightStyle HighlightStyle { get; set; }
+    /// <remarks>
+    ///     <para>
+    ///         A view must be visible to receive Enter events (Leave events are always received).
+    ///     </para>
+    ///     <para>
+    ///         If the event is cancelled, the mouse event will not be propagated to other views and <see cref="MouseEnter"/>
+    ///         will not be raised.
+    ///     </para>
+    ///     <para>
+    ///         Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
+    ///     </para>
+    ///     <para>
+    ///         See <see cref="SetPressedHighlight"/> for more information.
+    ///     </para>
+    /// </remarks>
+    /// <param name="eventArgs"></param>
+    /// <returns>
+    ///     <see langword="true"/> if the event was canceled, <see langword="false"/> if not. Cancelling the event
+    ///     prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
+    /// </returns>
+    protected virtual bool OnMouseEnter (CancelEventArgs eventArgs) { return false; }
 
-    /// <summary>Event fired when a mouse click occurs.</summary>
+    /// <summary>
+    ///     Raised when the mouse moves over the View's <see cref="Frame"/>. <see cref="MouseLeave"/> will
+    ///     be raised when the mouse is no longer over the <see cref="Frame"/>. If another View occludes this View, the
+    ///     that View will also receive MouseEnter/Leave events.
+    /// </summary>
     /// <remarks>
     ///     <para>
-    ///         Fired when the mouse is either clicked or double-clicked. Check
-    ///         <see cref="MouseEvent.Flags"/> to see which button was clicked.
+    ///         A view must be visible to receive Enter events (Leave events are always received).
     ///     </para>
     ///     <para>
-    ///         The coordinates are relative to <see cref="View.Viewport"/>.
+    ///         If the event is cancelled, the mouse event will not be propagated to other views.
+    ///     </para>
+    ///     <para>
+    ///         Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
+    ///     </para>
+    ///     <para>
+    ///         Set <see cref="CancelEventArgs.Cancel"/> to <see langword="true"/> if the event was canceled,
+    ///         <see langword="false"/> if not. Cancelling the event
+    ///         prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
+    ///     </para>
+    ///     <para>
+    ///         See <see cref="SetPressedHighlight"/> for more information.
     ///     </para>
     /// </remarks>
-    public event EventHandler<MouseEventEventArgs>? MouseClick;
+    public event EventHandler<CancelEventArgs>? MouseEnter;
+
+    /// <summary>
+    ///     INTERNAL Called by <see cref="Application.OnMouseEvent"/> when the mouse leaves <see cref="Frame"/>, or is occluded
+    ///     by another non-SubView.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This method calls <see cref="OnMouseLeave"/> and raises the <see cref="MouseLeave"/> event.
+    ///     </para>
+    ///     <para>
+    ///         Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
+    ///     </para>
+    ///     <para>
+    ///         See <see cref="SetPressedHighlight"/> for more information.
+    ///     </para>
+    /// </remarks>
+    internal void NewMouseLeaveEvent ()
+    {
+        // Pre-conditions
+
+        // Non-cancellable event
+        OnMouseLeave ();
+
+        MouseLeave?.Invoke (this, EventArgs.Empty);
+
+        // Post-conditions
+        _hovering = false;
 
-    /// <summary>Event fired when the mouse moves into the View's <see cref="Viewport"/>.</summary>
-    public event EventHandler<MouseEventEventArgs>? MouseEnter;
+        if (HighlightStyle.HasFlag (HighlightStyle.Hover) || Diagnostics.HasFlag (ViewDiagnosticFlags.Hover))
+        {
+            HighlightStyle copy = HighlightStyle;
+            var hover = HighlightStyle.None;
+            RaiseHighlight (new (ref copy, ref hover));
+
+            if (_savedNonHoverColorScheme is { })
+            {
+                ColorScheme = _savedNonHoverColorScheme;
+                _savedNonHoverColorScheme = null;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Called when the mouse moves outside View's <see cref="Frame"/>, or is occluded by another non-SubView.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
+    ///     </para>
+    ///     <para>
+    ///         See <see cref="SetPressedHighlight"/> for more information.
+    ///     </para>
+    /// </remarks>
+    protected virtual void OnMouseLeave () { }
+
+    /// <summary>
+    ///     Raised when the mouse moves outside View's <see cref="Frame"/>, or is occluded by another non-SubView.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
+    ///     </para>
+    ///     <para>
+    ///         See <see cref="SetPressedHighlight"/> for more information.
+    ///     </para>
+    /// </remarks>
+    public event EventHandler? MouseLeave;
+
+    #endregion MouseEnterLeave
+
+    #region Low Level Mouse Events
 
     /// <summary>Event fired when a mouse event occurs.</summary>
     /// <remarks>
@@ -42,9 +204,6 @@ public partial class View // Mouse APIs
     /// </remarks>
     public event EventHandler<MouseEventEventArgs>? MouseEvent;
 
-    /// <summary>Event fired when the mouse leaves the View's <see cref="Viewport"/>.</summary>
-    public event EventHandler<MouseEventEventArgs>? MouseLeave;
-
     /// <summary>
     ///     Processes a <see cref="MouseEvent"/>. This method is called by <see cref="Application.OnMouseEvent"/> when a mouse
     ///     event occurs.
@@ -58,7 +217,7 @@ public partial class View // Mouse APIs
     ///         mouse buttons was clicked, it calls <see cref="OnMouseClick"/> to process the click.
     ///     </para>
     ///     <para>
-    ///         See <see cref="SetHighlight"/> for more information.
+    ///         See <see cref="SetPressedHighlight"/> for more information.
     ///     </para>
     ///     <para>
     ///         If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, the <see cref="OnMouseClick"/> event
@@ -69,6 +228,7 @@ public partial class View // Mouse APIs
     /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise.</returns>
     public bool? NewMouseEvent (MouseEvent mouseEvent)
     {
+        // Pre-conditions
         if (!Enabled)
         {
             // A disabled view should not eat mouse events
@@ -80,6 +240,12 @@ public partial class View // Mouse APIs
             return false;
         }
 
+        if (!WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
+        {
+            return false;
+        }
+
+        // Cancellable event
         if (OnMouseEvent (mouseEvent))
         {
             // Technically mouseEvent.Handled should already be true if implementers of OnMouseEvent
@@ -87,19 +253,22 @@ public partial class View // Mouse APIs
             return mouseEvent.Handled = true;
         }
 
+        // BUGBUG: MouseEvent should be fired from here. Fix this in https://github.com/gui-cs/Terminal.Gui/issues/3029
+
+        // Post-Conditions
         if (HighlightStyle != HighlightStyle.None || (WantContinuousButtonPressed && WantMousePositionReports))
         {
-            if (HandlePressed (mouseEvent))
+            if (WhenGrabbedHandlePressed (mouseEvent))
             {
                 return mouseEvent.Handled;
             }
 
-            if (HandleReleased (mouseEvent))
+            if (WhenGrabbedHandleReleased (mouseEvent))
             {
                 return mouseEvent.Handled;
             }
 
-            if (HandleClicked (mouseEvent))
+            if (WhenGrabbedHandleClicked (mouseEvent))
             {
                 return mouseEvent.Handled;
             }
@@ -119,7 +288,7 @@ public partial class View // Mouse APIs
             || mouseEvent.Flags.HasFlag (MouseFlags.Button4TripleClicked)
            )
         {
-            // If it's a click, and we didn't handle it, then we'll call OnMouseClick
+            // If it's a click, and we didn't handle it, then we need to generate a click event
             // We get here if the view did not handle the mouse event via OnMouseEvent/MouseEvent and
             // it did not handle the press/release/clicked events via HandlePress/HandleRelease/HandleClicked
             return OnMouseClick (new (mouseEvent));
@@ -135,29 +304,6 @@ public partial class View // Mouse APIs
     /// <value><see langword="true"/> if mouse position reports are wanted; otherwise, <see langword="false"/>.</value>
     public virtual bool WantMousePositionReports { get; set; }
 
-    /// <summary>
-    ///     Called by <see cref="NewMouseEvent"/> when the mouse enters <see cref="Viewport"/>. The view will
-    ///     then receive mouse events until <see cref="OnMouseLeave"/> is called indicating the mouse has left
-    ///     the view.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         Override this method or subscribe to <see cref="MouseEnter"/> to change the default enter behavior.
-    ///     </para>
-    ///     <para>
-    ///         The coordinates are relative to <see cref="View.Viewport"/>.
-    ///     </para>
-    /// </remarks>
-    /// <param name="mouseEvent"></param>
-    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
-    protected internal virtual bool? OnMouseEnter (MouseEvent mouseEvent)
-    {
-        var args = new MouseEventEventArgs (mouseEvent);
-        MouseEnter?.Invoke (this, args);
-
-        return args.Handled;
-    }
-
     /// <summary>Called when a mouse event occurs within the view's <see cref="Viewport"/>.</summary>
     /// <remarks>
     ///     <para>
@@ -175,61 +321,22 @@ public partial class View // Mouse APIs
         return args.Handled;
     }
 
-    /// <summary>
-    ///     Called by <see cref="NewMouseEvent"/> when a mouse leaves <see cref="Viewport"/>. The view will
-    ///     no longer receive mouse events.
-    /// </summary>
+    #endregion Low Level Mouse Events
+
+    #region Mouse Click Events
+
+    /// <summary>Event fired when a mouse click occurs.</summary>
+    /// 
     /// <remarks>
     ///     <para>
-    ///         Override this method or subscribe to <see cref="MouseEnter"/> to change the default leave behavior.
+    ///         Fired when the mouse is either clicked or double-clicked. Check
+    ///         <see cref="MouseEvent.Flags"/> to see which button was clicked.
     ///     </para>
     ///     <para>
     ///         The coordinates are relative to <see cref="View.Viewport"/>.
     ///     </para>
     /// </remarks>
-    /// <param name="mouseEvent"></param>
-    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
-    protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent)
-    {
-        if (!Enabled)
-        {
-            return true;
-        }
-
-        if (!CanBeVisible (this))
-        {
-            return false;
-        }
-
-        var args = new MouseEventEventArgs (mouseEvent);
-        MouseLeave?.Invoke (this, args);
-
-        return args.Handled;
-    }
-
-    /// <summary>
-    ///     Called when the view is to be highlighted.
-    /// </summary>
-    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
-    protected virtual bool? OnHighlight (CancelEventArgs<HighlightStyle> args)
-    {
-        Highlight?.Invoke (this, args);
-
-        if (args.Cancel)
-        {
-            return true;
-        }
-
-        Margin?.Highlight?.Invoke (this, args);
-
-        //args = new (highlight);
-        //Border?.Highlight?.Invoke (this, args);
-
-        //args = new (highlight);
-        //Padding?.Highlight?.Invoke (this, args);
-
-        return args.Cancel;
-    }
+    public event EventHandler<MouseEventEventArgs>? MouseClick;
 
     /// <summary>Invokes the MouseClick event.</summary>
     /// <remarks>
@@ -241,12 +348,19 @@ public partial class View // Mouse APIs
     /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
     protected bool OnMouseClick (MouseEventEventArgs args)
     {
+        // BUGBUG: This should be named NewMouseClickEvent. Fix this in https://github.com/gui-cs/Terminal.Gui/issues/3029
+
+        // Pre-conditions
         if (!Enabled)
         {
             // QUESTION: Is this right? Should a disabled view eat mouse clicks?
             return args.Handled = false;
         }
 
+        // Cancellable event
+
+        // BUGBUG: There should be a call to a protected virtual OnMouseClick here. Fix this in https://github.com/gui-cs/Terminal.Gui/issues/3029
+
         MouseClick?.Invoke (this, args);
 
         if (args.Handled)
@@ -254,17 +368,17 @@ public partial class View // Mouse APIs
             return true;
         }
 
-        if (!HasFocus && CanFocus)
-        {
-            args.Handled = true;
-            SetFocus ();
-        }
+        // Post-conditions
+
+        // Always invoke Select command on MouseClick
+        // By default, this will raise Selecting/OnSelecting - Subclasses can override this via AddCommand (Command.Select ...).
+        args.Handled = InvokeCommand (Command.Select, ctx: new (Command.Select, key: null, data: args.MouseEvent)) == true;
 
         return args.Handled;
     }
 
     /// <summary>
-    ///     For cases where the view is grabbed and the mouse is clicked, this method handles the click event (typically
+    ///     INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the click event (typically
     ///     when <see cref="WantContinuousButtonPressed"/> or <see cref="HighlightStyle"/> are set).
     /// </summary>
     /// <remarks>
@@ -272,7 +386,7 @@ public partial class View // Mouse APIs
     /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
-    internal bool HandleClicked (MouseEvent mouseEvent)
+    internal bool WhenGrabbedHandleClicked (MouseEvent mouseEvent)
     {
         if (Application.MouseGrabView == this
             && (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)
@@ -283,13 +397,13 @@ public partial class View // Mouse APIs
             // We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab
             Application.UngrabMouse ();
 
-            if (SetHighlight (HighlightStyle.None))
+            if (SetPressedHighlight (HighlightStyle.None))
             {
                 return true;
             }
 
-            // If mouse is still in bounds, click
-            if (!WantContinuousButtonPressed && Viewport.Contains (mouseEvent.Position))
+            // If mouse is still in bounds, generate a click
+            if (!WantMousePositionReports && Viewport.Contains (mouseEvent.Position))
             {
                 return OnMouseClick (new (mouseEvent));
             }
@@ -301,7 +415,7 @@ public partial class View // Mouse APIs
     }
 
     /// <summary>
-    ///     For cases where the view is grabbed and the mouse is clicked, this method handles the released event (typically
+    ///     INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the released event (typically
     ///     when <see cref="WantContinuousButtonPressed"/> or <see cref="HighlightStyle"/> are set).
     /// </summary>
     /// <remarks>
@@ -309,7 +423,7 @@ public partial class View // Mouse APIs
     /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
-    internal bool HandleReleased (MouseEvent mouseEvent)
+    internal bool WhenGrabbedHandleReleased (MouseEvent mouseEvent)
     {
         if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released)
             || mouseEvent.Flags.HasFlag (MouseFlags.Button2Released)
@@ -318,7 +432,7 @@ public partial class View // Mouse APIs
         {
             if (Application.MouseGrabView == this)
             {
-                SetHighlight (HighlightStyle.None);
+                SetPressedHighlight (HighlightStyle.None);
             }
 
             return mouseEvent.Handled = true;
@@ -328,111 +442,134 @@ public partial class View // Mouse APIs
     }
 
     /// <summary>
-    ///     Called by <see cref="Application.OnMouseEvent"/> when the mouse enters <see cref="Viewport"/>. The view will
-    ///     then receive mouse events until <see cref="NewMouseLeaveEvent"/> is called indicating the mouse has left
-    ///     the view.
+    ///     INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the released event (typically
+    ///     when <see cref="WantContinuousButtonPressed"/> or <see cref="HighlightStyle"/> are set).
     /// </summary>
     /// <remarks>
     ///     <para>
-    ///         A view must be both enabled and visible to receive mouse events.
-    ///     </para>
-    ///     <para>
-    ///         This method calls <see cref="OnMouseEnter"/> to fire the event.
-    ///     </para>
-    ///     <para>
-    ///         See <see cref="SetHighlight"/> for more information.
+    ///         Marked internal just to support unit tests
     ///     </para>
     /// </remarks>
     /// <param name="mouseEvent"></param>
-    /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise.</returns>
-    internal bool? NewMouseEnterEvent (MouseEvent mouseEvent)
+    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
+    private bool WhenGrabbedHandlePressed (MouseEvent mouseEvent)
     {
-        if (!Enabled)
+        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
+            || mouseEvent.Flags.HasFlag (MouseFlags.Button2Pressed)
+            || mouseEvent.Flags.HasFlag (MouseFlags.Button3Pressed)
+            || mouseEvent.Flags.HasFlag (MouseFlags.Button4Pressed))
         {
-            return true;
-        }
+            // The first time we get pressed event, grab the mouse and set focus
+            if (Application.MouseGrabView != this)
+            {
+                Application.GrabMouse (this);
 
-        if (!CanBeVisible (this))
-        {
-            return false;
-        }
+                if (!HasFocus && CanFocus)
+                {
+                    // Set the focus, but don't invoke Accept
+                    SetFocus ();
+                }
 
-        if (OnMouseEnter (mouseEvent) == true)
-        {
-            return true;
-        }
+                mouseEvent.Handled = true;
+            }
 
-#if HOVER
-        if (HighlightStyle.HasFlag(HighlightStyle.Hover))
-        {
-            if (SetHighlight (HighlightStyle.Hover))
+            if (Viewport.Contains (mouseEvent.Position))
             {
-                return true;
+                if (this is not Adornment
+                    && SetPressedHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None))
+                {
+                    return true;
+                }
+            }
+            else
+            {
+                if (this is not Adornment
+                    && SetPressedHighlight (HighlightStyle.HasFlag (HighlightStyle.PressedOutside) ? HighlightStyle.PressedOutside : HighlightStyle.None))
+
+                {
+                    return true;
+                }
+            }
+
+            if (WantContinuousButtonPressed && Application.MouseGrabView == this)
+            {
+                // If this is not the first pressed event, generate a click
+                return OnMouseClick (new (mouseEvent));
             }
+
+            return mouseEvent.Handled = true;
         }
-#endif
+
         return false;
     }
 
+    #endregion Mouse Click Events
+
+    #region Highlight Handling
+
+    // Used for Pressed highlighting
+    private ColorScheme? _savedHighlightColorScheme;
+
     /// <summary>
-    ///     Called by <see cref="Application.OnMouseEvent"/> when the mouse leaves <see cref="Viewport"/>. The view will
-    ///     then no longer receive mouse events.
+    ///     Gets or sets whether the <see cref="View"/> will be highlighted visually by mouse interaction.
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         A view must be both enabled and visible to receive mouse events.
-    ///     </para>
-    ///     <para>
-    ///         This method calls <see cref="OnMouseLeave"/> to fire the event.
-    ///     </para>
-    ///     <para>
-    ///         See <see cref="SetHighlight"/> for more information.
-    ///     </para>
-    /// </remarks>
-    /// <param name="mouseEvent"></param>
-    /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise.</returns>
-    internal bool? NewMouseLeaveEvent (MouseEvent mouseEvent)
+    public HighlightStyle HighlightStyle { get; set; }
+
+    /// <summary>
+    ///     INTERNAL Raises the <see cref="Highlight"/> event. Returns <see langword="true"/> if the event was handled,
+    ///     <see langword="false"/> otherwise.
+    /// </summary>
+    /// <param name="args"></param>
+    /// <returns></returns>
+    private bool RaiseHighlight (CancelEventArgs<HighlightStyle> args)
     {
-        if (!Enabled)
+        if (OnHighlight (args))
         {
             return true;
         }
 
-        if (!CanBeVisible (this))
-        {
-            return false;
-        }
-
-        if (OnMouseLeave (mouseEvent))
-        {
-            return true;
-        }
-#if HOVER
-        if (HighlightStyle.HasFlag (HighlightStyle.Hover))
-        {
-            SetHighlight (HighlightStyle.None);
-        }
-#endif
+        Highlight?.Invoke (this, args);
 
-        return false;
+        return args.Cancel;
     }
 
     /// <summary>
-    ///     Enables the highlight for the view when the mouse is pressed. Called from OnMouseEvent.
+    ///     Called when the view is to be highlighted. The <see cref="HighlightStyle"/> passed in the event indicates the
+    ///     highlight style that will be applied. The view can modify the highlight style by setting the
+    ///     <see cref="CancelEventArgs{T}.NewValue"/> property.
+    /// </summary>
+    /// <param name="args">
+    ///     Set the <see cref="CancelEventArgs{T}.NewValue"/> property to <see langword="true"/>, to cancel, indicating custom
+    ///     highlighting.
+    /// </param>
+    /// <returns><see langword="true"/>, to cancel, indicating custom highlighting.</returns>
+    protected virtual bool OnHighlight (CancelEventArgs<HighlightStyle> args) { return false; }
+
+    /// <summary>
+    ///     Raised when the view is to be highlighted. The <see cref="HighlightStyle"/> passed in the event indicates the
+    ///     highlight style that will be applied. The view can modify the highlight style by setting the
+    ///     <see cref="CancelEventArgs{T}.NewValue"/> property.
+    ///     Set to <see langword="true"/>, to cancel, indicating custom highlighting.
+    /// </summary>
+    public event EventHandler<CancelEventArgs<HighlightStyle>>? Highlight;
+
+    /// <summary>
+    ///     INTERNAL Enables the highlight for the view when the mouse is pressed. Called from OnMouseEvent.
     /// </summary>
     /// <remarks>
     ///     <para>
-    ///         Set <see cref="HighlightStyle"/> to have the view highlighted based on the mouse.
+    ///         Set <see cref="HighlightStyle"/> to <see cref="HighlightStyle.Pressed"/> and/or
+    ///         <see cref="HighlightStyle.PressedOutside"/> to enable.
     ///     </para>
     ///     <para>
-    ///         Calls <see cref="OnHighlight"/> which fires the <see cref="Highlight"/> event.
+    ///         Calls <see cref="OnHighlight"/> and raises the <see cref="Highlight"/> event.
     ///     </para>
     ///     <para>
     ///         Marked internal just to support unit tests
     ///     </para>
     /// </remarks>
     /// <returns><see langword="true"/>, if the Highlight event was handled, <see langword="false"/> otherwise.</returns>
-    internal bool SetHighlight (HighlightStyle newHighlightStyle)
+    internal bool SetPressedHighlight (HighlightStyle newHighlightStyle)
     {
         // TODO: Make the highlight colors configurable
         if (!CanFocus)
@@ -440,32 +577,18 @@ public partial class View // Mouse APIs
             return false;
         }
 
-        // Enable override via virtual method and/or event
         HighlightStyle copy = HighlightStyle;
-        var args = new CancelEventArgs<HighlightStyle> (ref copy, ref newHighlightStyle);
+        CancelEventArgs<HighlightStyle> args = new (ref copy, ref newHighlightStyle);
 
-        if (OnHighlight (args) == true)
+        if (RaiseHighlight (args) || args.Cancel)
         {
             return true;
         }
-#if HOVER
-        if (style.HasFlag (HighlightStyle.Hover))
-        {
-            if (_savedHighlightColorScheme is null && ColorScheme is { })
-            {
-                _savedHighlightColorScheme ??= ColorScheme;
 
-                var cs = new ColorScheme (ColorScheme)
-                {
-                    Normal = GetFocusColor (),
-                    HotNormal = ColorScheme.HotFocus
-                };
-                ColorScheme = cs;
-            }
+        // For 3D Pressed Style - Note we don't care about canceling the event here
+        Margin?.RaiseHighlight (args);
+        args.Cancel = false; // Just in case
 
-            return true;
-        }
-#endif
         if (args.NewValue.HasFlag (HighlightStyle.Pressed) || args.NewValue.HasFlag (HighlightStyle.PressedOutside))
         {
             if (_savedHighlightColorScheme is null && ColorScheme is { })
@@ -509,65 +632,78 @@ public partial class View // Mouse APIs
         return false;
     }
 
+    #endregion Highlight Handling
+
     /// <summary>
-    ///     For cases where the view is grabbed and the mouse is clicked, this method handles the released event (typically
-    ///     when <see cref="WantContinuousButtonPressed"/> or <see cref="HighlightStyle"/> are set).
+    ///     INTERNAL: Gets the Views that are under the mouse at <paramref name="location"/>, including Adornments.
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         Marked internal just to support unit tests
-    ///     </para>
-    /// </remarks>
-    /// <param name="mouseEvent"></param>
-    /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
-    private bool HandlePressed (MouseEvent mouseEvent)
+    /// <param name="location"></param>
+    /// <returns></returns>
+    internal static List<View?> GetViewsUnderMouse (in Point location)
     {
-        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
-            || mouseEvent.Flags.HasFlag (MouseFlags.Button2Pressed)
-            || mouseEvent.Flags.HasFlag (MouseFlags.Button3Pressed)
-            || mouseEvent.Flags.HasFlag (MouseFlags.Button4Pressed))
+        List<View?> viewsUnderMouse = new ();
+
+        View? start = Application.Top;
+
+        Point currentLocation = location;
+
+        while (start is { Visible: true } && start.Contains (currentLocation))
         {
-            // The first time we get pressed event, grab the mouse and set focus
-            if (Application.MouseGrabView != this)
-            {
-                Application.GrabMouse (this);
+            viewsUnderMouse.Add (start);
 
-                if (!HasFocus && CanFocus)
-                {
-                    // Set the focus, but don't invoke Accept
-                    SetFocus ();
-                }
+            Adornment? found = null;
 
-                mouseEvent.Handled = true;
+            if (start.Margin.Contains (currentLocation))
+            {
+                found = start.Margin;
             }
-
-            if (Viewport.Contains (mouseEvent.Position))
+            else if (start.Border.Contains (currentLocation))
             {
-                if (this is not Adornment
-                    && SetHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None))
-                {
-                    return true;
-                }
+                found = start.Border;
             }
-            else
+            else if (start.Padding.Contains (currentLocation))
             {
-                if (this is not Adornment
-                    && SetHighlight (HighlightStyle.HasFlag (HighlightStyle.PressedOutside) ? HighlightStyle.PressedOutside : HighlightStyle.None))
+                found = start.Padding;
+            }
+
+            Point viewportOffset = start.GetViewportOffsetFromFrame ();
+
+            if (found is { })
+            {
+                start = found;
+                viewsUnderMouse.Add (start);
+                viewportOffset = found.Parent?.Frame.Location ?? Point.Empty;
+            }
+
+            int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X);
+            int startOffsetY = currentLocation.Y - (start.Frame.Y + viewportOffset.Y);
+
+            View? subview = null;
 
+            for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
+            {
+                if (start.InternalSubviews [i].Visible
+                    && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y)))
                 {
-                    return true;
+                    subview = start.InternalSubviews [i];
+                    currentLocation.X = startOffsetX + start.Viewport.X;
+                    currentLocation.Y = startOffsetY + start.Viewport.Y;
+
+                    // start is the deepest subview under the mouse; stop searching the subviews
+                    break;
                 }
             }
 
-            if (WantContinuousButtonPressed && Application.MouseGrabView == this)
+            if (subview is null)
             {
-                // If this is not the first pressed event, click
-                return OnMouseClick (new (mouseEvent));
+                // No subview was found that's under the mouse, so we're done
+                return viewsUnderMouse;
             }
 
-            return mouseEvent.Handled = true;
+            // We found a subview of start that's under the mouse, continue...
+            start = subview;
         }
 
-        return false;
+        return viewsUnderMouse;
     }
 }

+ 152 - 57
Terminal.Gui/View/View.Navigation.cs

@@ -1,5 +1,6 @@
 #nullable enable
 using System.Diagnostics;
+using System.Reflection.PortableExecutable;
 
 namespace Terminal.Gui;
 
@@ -16,6 +17,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
     ///     <para>
     ///         If there is no next/previous view to advance to, the focus is set to the view itself.
     ///     </para>
+    ///     <para>
+    ///         See the View Navigation Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/navigation.html"/>
+    ///     </para>
     /// </remarks>
     /// <param name="direction"></param>
     /// <param name="behavior"></param>
@@ -39,7 +43,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
         // AdvanceFocus did not advance - do we wrap, or move up to the superview?
 
-        View [] focusChain = GetSubviewFocusChain (direction, behavior);
+        View [] focusChain = GetFocusChain (direction, behavior);
 
         if (focusChain.Length == 0)
         {
@@ -52,11 +56,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
             if (direction == NavigationDirection.Forward && focused == focusChain [^1] && SuperView is null)
             {
                 // We're at the top of the focus chain. Go back down the focus chain and focus the first TabGroup
-                View [] views = GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
+                View [] views = GetFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
 
                 if (views.Length > 0)
                 {
-                    View [] subViews = views [0].GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
+                    View [] subViews = views [0].GetFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
 
                     if (subViews.Length > 0)
                     {
@@ -71,11 +75,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
             if (direction == NavigationDirection.Backward && focused == focusChain [0])
             {
                 // We're at the bottom of the focus chain
-                View [] views = GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
+                View [] views = GetFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
 
                 if (views.Length > 0)
                 {
-                    View [] subViews = views [^1].GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
+                    View [] subViews = views [^1].GetFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
 
                     if (subViews.Length > 0)
                     {
@@ -102,7 +106,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
             if (SuperView is { })
             {
                 // If we are TabStop, and we have at least one other focusable peer, move to the SuperView's chain
-                if (TabStop == TabBehavior.TabStop && SuperView is { } && SuperView.GetSubviewFocusChain (direction, behavior).Length > 1)
+                if (TabStop == TabBehavior.TabStop && SuperView is { } && SuperView.GetFocusChain (direction, behavior).Length > 1)
                 {
                     return false;
                 }
@@ -110,7 +114,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
                 // TabGroup is special-cased. 
                 if (focused?.TabStop == TabBehavior.TabGroup)
                 {
-                    if (SuperView?.GetSubviewFocusChain (direction, TabBehavior.TabGroup)?.Length > 0)
+                    if (SuperView?.GetFocusChain (direction, TabBehavior.TabGroup)?.Length > 0)
                     {
                         // Our superview has a TabGroup subview; signal we couldn't move so we nav out to it
                         return false;
@@ -136,6 +140,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
     /// <summary>Gets or sets a value indicating whether this <see cref="View"/> can be focused.</summary>
     /// <remarks>
     ///     <para>
+    ///         See the View Navigation Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/navigation.html"/>
+    ///     </para>
+    ///     <para>
     ///         <see cref="SuperView"/> must also have <see cref="CanFocus"/> set to <see langword="true"/>.
     ///     </para>
     ///     <para>
@@ -176,7 +183,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
                 HasFocus = false;
             }
 
-            if (_canFocus && !HasFocus && Visible && SuperView is { } && SuperView.Focused is null)
+            if (_canFocus && !HasFocus && Visible && SuperView is { Focused: null })
             {
                 // If CanFocus is set to true and this view does not have focus, make it enter focus
                 SetFocus ();
@@ -211,14 +218,40 @@ public partial class View // Focus and cross-view navigation management (TabStop
         return SetFocus ();
     }
 
-    /// <summary>Gets the currently focused Subview of this view, or <see langword="null"/> if nothing is focused.</summary>
+    /// <summary>Gets the currently focused Subview or Adornment of this view, or <see langword="null"/> if nothing is focused.</summary>
     public View? Focused
     {
-        get { return Subviews.FirstOrDefault (v => v.HasFocus); }
+        get
+        {
+            View? focused = Subviews.FirstOrDefault (v => v.HasFocus);
+
+            if (focused is { })
+            {
+                return focused;
+            }
+
+            // How about in Adornments?
+            if (Margin is { HasFocus: true })
+            {
+                return Margin;
+            }
+
+            if (Border is { HasFocus: true })
+            {
+                return Border;
+            }
+
+            if (Padding is { HasFocus: true })
+            {
+                return Padding;
+            }
+
+            return null;
+        }
     }
 
     /// <summary>Returns a value indicating if this View is currently on Top (Active)</summary>
-    public bool IsCurrentTop => Application.Current == this;
+    public bool IsCurrentTop => Application.Top == this;
 
     /// <summary>
     ///     Returns the most focused Subview down the subview-hierarchy.
@@ -259,14 +292,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
     /// </returns>
     internal bool RestoreFocus ()
     {
-        if (Focused is null && _subviews?.Count > 0)
-        {
-            if (_previouslyMostFocused is { })
-            {
-                return _previouslyMostFocused.SetFocus ();
-            }
+        View [] indicies = GetFocusChain (NavigationDirection.Forward, null);
 
-            return false;
+        if (Focused is null && _previouslyFocused is { } && indicies.Contains (_previouslyFocused))
+        {
+            return _previouslyFocused.SetFocus ();
         }
 
         return false;
@@ -274,7 +304,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
     private View? FindDeepestFocusableView (NavigationDirection direction, TabBehavior? behavior)
     {
-        View [] indicies = GetSubviewFocusChain (direction, behavior);
+        View [] indicies = GetFocusChain (direction, behavior);
 
         foreach (View v in indicies)
         {
@@ -294,6 +324,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
     /// </summary>
     /// <remarks>
     ///     <para>
+    ///         See the View Navigation Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/navigation.html"/>
+    ///     </para>
+    ///     <para>
     ///         Only Views that are visible, enabled, and have <see cref="CanFocus"/> set to <see langword="true"/> are
     ///         focusable. If
     ///         these conditions are not met when this property is set to <see langword="true"/> <see cref="HasFocus"/> will
@@ -341,6 +374,12 @@ public partial class View // Focus and cross-view navigation management (TabStop
             else
             {
                 SetHasFocusFalse (null);
+
+                if (_hasFocus)
+                {
+                    // force it.
+                    _hasFocus = false;
+                }
             }
         }
         get => _hasFocus;
@@ -350,6 +389,12 @@ public partial class View // Focus and cross-view navigation management (TabStop
     ///     Causes this view to be focused. Calling this method has the same effect as setting <see cref="HasFocus"/> to
     ///     <see langword="true"/> but with the added benefit of returning a value indicating whether the focus was set.
     /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         See the View Navigation Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/navigation.html"/>
+    ///     </para>
+    /// </remarks>
+    /// <returns><see langword="true"/> if the focus changed; <see langword="true"/> false otherwise.</returns>
     public bool SetFocus ()
     {
         (bool focusSet, bool _) = SetHasFocusTrue (Application.Navigation?.GetFocused ());
@@ -358,9 +403,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
     }
 
     /// <summary>
-    ///     Caches the most focused subview when this view is losing focus. This is used by <see cref="RestoreFocus"/>.
+    ///     A cache of the subview that was focused when this view last lost focus. This is used by <see cref="RestoreFocus"/>.
     /// </summary>
-    private View? _previouslyMostFocused;
+    private View? _previouslyFocused;
 
     /// <summary>
     ///     INTERNAL: Called when focus is going to change to this view. This method is called by <see cref="SetFocus"/> and
@@ -376,7 +421,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
     /// <exception cref="InvalidOperationException"></exception>
     private (bool focusSet, bool cancelled) SetHasFocusTrue (View? previousFocusedView, bool traversingUp = false)
     {
-        Debug.Assert (ApplicationNavigation.IsInHierarchy (SuperView, this));
+        Debug.Assert (SuperView is null || ApplicationNavigation.IsInHierarchy (SuperView, this));
 
         // Pre-conditions
         if (_hasFocus)
@@ -384,7 +429,10 @@ public partial class View // Focus and cross-view navigation management (TabStop
             return (false, false);
         }
 
-        if (CanFocus && SuperView is { CanFocus: false })
+        var thisAsAdornment = this as Adornment;
+        View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
+
+        if (CanFocus && superViewOrParent is { CanFocus: false })
         {
             Debug.WriteLine ($@"WARNING: Attempt to FocusChanging where SuperView.CanFocus == false. {this}");
 
@@ -412,7 +460,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
         // Make sure superviews up the superview hierarchy have focus.
         // Any of them may cancel gaining focus. In which case we need to back out.
-        if (SuperView is { HasFocus: false } sv)
+        if (superViewOrParent is { HasFocus: false } sv)
         {
             (bool focusSet, bool svCancelled) = sv.SetHasFocusTrue (previousFocusedView, true);
 
@@ -431,7 +479,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
         // By setting _hasFocus to true we definitively change HasFocus for this view.
 
         // Get whatever peer has focus, if any
-        View? focusedPeer = SuperView?.Focused;
+        View? focusedPeer = superViewOrParent?.Focused;
 
         _hasFocus = true;
 
@@ -440,31 +488,34 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
         if (!traversingUp)
         {
-            // Restore focus to the previously most focused subview in the subview-hierarchy
+            // Restore focus to the previously focused subview 
             if (!RestoreFocus ())
             {
                 // Couldn't restore focus, so use Advance to navigate to the next focusable subview
                 if (!AdvanceFocus (NavigationDirection.Forward, null))
                 {
                     // Couldn't advance, so we're the most focused view in the application
-                    _previouslyMostFocused = null;
                     Application.Navigation?.SetFocused (this);
                 }
             }
         }
 
-        if (previousFocusedView is { HasFocus: true } && Subviews.Contains (previousFocusedView))
+        if (previousFocusedView is { HasFocus: true } && GetFocusChain (NavigationDirection.Forward, TabStop).Contains (previousFocusedView))
         {
             previousFocusedView.SetHasFocusFalse (this);
         }
 
+        _previouslyFocused = null;
+
         if (Arrangement.HasFlag (ViewArrangement.Overlapped))
         {
-            SuperView?.MoveSubviewToStart (this);
+            SuperView?.MoveSubviewToEnd (this);
         }
 
         NotifyFocusChanged (HasFocus, previousFocusedView, this);
 
+        SetNeedsDisplay ();
+
         // Post-conditions - prove correctness
         if (HasFocus == previousValue)
         {
@@ -547,29 +598,40 @@ public partial class View // Focus and cross-view navigation management (TabStop
             throw new InvalidOperationException ("SetHasFocusFalse should not be called if the view does not have focus.");
         }
 
+        var thisAsAdornment = this as Adornment;
+        View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
+
         // If newFocusedVew is null, we need to find the view that should get focus, and SetFocus on it.
         if (!traversingDown && newFocusedView is null)
         {
-            if (SuperView?._previouslyMostFocused is { } && SuperView?._previouslyMostFocused != this)
+            if (superViewOrParent?._previouslyFocused is { })
             {
-                SuperView?._previouslyMostFocused?.SetFocus ();
+                if (superViewOrParent._previouslyFocused != this)
+                {
+                    superViewOrParent?._previouslyFocused?.SetFocus ();
 
-                // The above will cause SetHasFocusFalse, so we can return
-                return;
+                    // The above will cause SetHasFocusFalse, so we can return
+                    return;
+                }
             }
 
-            if (SuperView is { } && SuperView.AdvanceFocus (NavigationDirection.Forward, TabStop))
+            if (superViewOrParent is { })
             {
-                // The above will cause SetHasFocusFalse, so we can return
-                return;
+                if (superViewOrParent.AdvanceFocus (NavigationDirection.Forward, TabStop))
+                {
+                    // The above will cause SetHasFocusFalse, so we can return
+                    return;
+                }
+
+                newFocusedView = superViewOrParent;
             }
 
-            if (Application.Navigation is { } && Application.Current is { })
+            if (Application.Navigation is { } && Application.Top is { })
             {
                 // Temporarily ensure this view can't get focus
                 bool prevCanFocus = _canFocus;
                 _canFocus = false;
-                bool restoredFocus = Application.Current!.RestoreFocus ();
+                bool restoredFocus = Application.Top!.RestoreFocus ();
                 _canFocus = prevCanFocus;
 
                 if (restoredFocus)
@@ -582,6 +644,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
             // No other focusable view to be found. Just "leave" us...
         }
 
+
         // Before we can leave focus, we need to make sure that all views down the subview-hierarchy have left focus.
         View? mostFocused = MostFocused;
 
@@ -595,12 +658,27 @@ public partial class View // Focus and cross-view navigation management (TabStop
                 if (bottom.HasFocus)
                 {
                     bottom.SetHasFocusFalse (newFocusedView, true);
+
+                    Debug.Assert (_hasFocus);
                 }
 
                 bottom = bottom.SuperView;
             }
 
-            _previouslyMostFocused = mostFocused;
+            if (bottom == this && bottom.SuperView is Adornment a)
+            {
+                //a.SetHasFocusFalse (newFocusedView, true);
+
+                Debug.Assert (_hasFocus);
+            }
+
+            Debug.Assert (_hasFocus);
+
+        }
+
+        if (superViewOrParent is { })
+        {
+            superViewOrParent._previouslyFocused = this;
         }
 
         bool previousValue = HasFocus;
@@ -608,8 +686,10 @@ public partial class View // Focus and cross-view navigation management (TabStop
         // Note, can't be cancelled.
         NotifyFocusChanging (HasFocus, !HasFocus, newFocusedView, this);
 
-        // Get whatever peer has focus, if any
-        View? focusedPeer = SuperView?.Focused;
+        // Get whatever peer has focus, if any so we can update our superview's _previouslyMostFocused
+        View? focusedPeer = superViewOrParent?.Focused;
+
+        // Set HasFocus false
         _hasFocus = false;
 
         if (Application.Navigation is { })
@@ -618,7 +698,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
             if (appFocused is { } || appFocused == this)
             {
-                Application.Navigation.SetFocused (newFocusedView ?? SuperView);
+                Application.Navigation.SetFocused (newFocusedView ?? superViewOrParent);
             }
         }
 
@@ -630,11 +710,6 @@ public partial class View // Focus and cross-view navigation management (TabStop
             return;
         }
 
-        if (SuperView is { })
-        {
-            //SuperView._previouslyMostFocused = focusedPeer;
-        }
-
         // Post-conditions - prove correctness
         if (HasFocus == previousValue)
         {
@@ -681,32 +756,48 @@ public partial class View // Focus and cross-view navigation management (TabStop
     #region Tab/Focus Handling
 
     /// <summary>
-    ///     Gets TabIndexes that are scoped to the specified behavior and direction. If behavior is null, all TabIndexes are
-    ///     returned.
+    ///     Gets the subviews and Adornments of this view that are scoped to the specified behavior and direction. If behavior is null, all focusable subviews and
+    ///     Adornments are returned.
     /// </summary>
     /// <param name="direction"></param>
     /// <param name="behavior"></param>
     /// <returns></returns>
-    /// GetScopedTabIndexes
-    internal View [] GetSubviewFocusChain (NavigationDirection direction, TabBehavior? behavior)
+    internal View [] GetFocusChain (NavigationDirection direction, TabBehavior? behavior)
     {
-        IEnumerable<View>? fitleredSubviews;
+        IEnumerable<View>? filteredSubviews;
 
         if (behavior.HasValue)
         {
-            fitleredSubviews = _subviews?.Where (v => v.TabStop == behavior && v is { CanFocus: true, Visible: true, Enabled: true });
+            filteredSubviews = _subviews?.Where (v => v.TabStop == behavior && v is { CanFocus: true, Visible: true, Enabled: true });
         }
         else
         {
-            fitleredSubviews = _subviews?.Where (v => v is { CanFocus: true, Visible: true, Enabled: true });
+            filteredSubviews = _subviews?.Where (v => v is { CanFocus: true, Visible: true, Enabled: true });
+        }
+
+
+        // How about in Adornments? 
+        if (Padding is { CanFocus: true, Visible: true, Enabled: true } && Padding.TabStop == behavior)
+        {
+            filteredSubviews = filteredSubviews?.Append (Padding);
+        }
+
+        if (Border is { CanFocus: true, Visible: true, Enabled: true } && Border.TabStop == behavior)
+        {
+            filteredSubviews = filteredSubviews?.Append (Border);
+        }
+
+        if (Margin is { CanFocus: true, Visible: true, Enabled: true } && Margin.TabStop == behavior)
+        {
+            filteredSubviews = filteredSubviews?.Append (Margin);
         }
 
         if (direction == NavigationDirection.Backward)
         {
-            fitleredSubviews = fitleredSubviews?.Reverse ();
+            filteredSubviews = filteredSubviews?.Reverse ();
         }
 
-        return fitleredSubviews?.ToArray () ?? Array.Empty<View> ();
+        return filteredSubviews?.ToArray () ?? Array.Empty<View> ();
     }
 
     private TabBehavior? _tabStop;
@@ -715,7 +806,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
     ///     Gets or sets the behavior of <see cref="AdvanceFocus"/> for keyboard navigation.
     /// </summary>
     /// <remarks>
+    /// <remarks>
     ///     <para>
+    ///         See the View Navigation Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/navigation.html"/>
+    ///     </para>
+    /// </remarks>    ///     <para>
     ///         If <see langword="null"/> the tab stop has not been set and setting <see cref="CanFocus"/> to true will set it
     ///         to
     ///         <see cref="TabBehavior.TabStop"/>.
@@ -723,7 +818,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
     ///     <para>
     ///         TabStop is independent of <see cref="CanFocus"/>. If <see cref="CanFocus"/> is <see langword="false"/>, the
     ///         view will not gain
-    ///         focus even if this property is set and vice-versa.
+    ///         focus even if this property is set and vice versa.
     ///     </para>
     ///     <para>
     ///         The default <see cref="TabBehavior.TabStop"/> keys are <see cref="Application.NextTabKey"/> (<c>Key.Tab</c>)

+ 87 - 74
Terminal.Gui/View/View.cs

@@ -7,7 +7,7 @@ namespace Terminal.Gui;
 #region API Docs
 
 /// <summary>
-///     View is the base class for all views on the screen and represents a visible element that can render itself and
+///     View is the base class all visible elements. View can render itself and
 ///     contains zero or more nested views, called SubViews. View provides basic functionality for layout, positioning, and
 ///     drawing. In addition, View provides keyboard and mouse event handling.
 /// </summary>
@@ -100,20 +100,14 @@ namespace Terminal.Gui;
 ///         <see cref="View"/> inheritance hierarchies to override base class layout code optimally by doing so only on
 ///         first run, instead of on every run.
 ///     </para>
-///     <para>See <see href="../docs/keyboard.md">for an overview of View keyboard handling.</see></para>
-///     ///
+///     <para>See <see href="../docs/keyboard.md"> for an overview of View keyboard handling.</see></para>
 /// </remarks>
 
 #endregion API Docs
 
 public partial class View : Responder, ISupportInitializeNotification
 {
-    /// <summary>
-    ///     Cancelable event fired when the <see cref="Command.Accept"/> command is invoked. Set
-    ///     <see cref="HandledEventArgs.Handled"/>
-    ///     to cancel the event.
-    /// </summary>
-    public event EventHandler<HandledEventArgs>? Accept;
+    #region Constructors and Initialization
 
     /// <summary>Gets or sets arbitrary data for the view.</summary>
     /// <remarks>This property is not used internally.</remarks>
@@ -124,47 +118,6 @@ public partial class View : Responder, ISupportInitializeNotification
     /// <remarks>The id should be unique across all Views that share a SuperView.</remarks>
     public string Id { get; set; } = "";
 
-    /// <summary>Pretty prints the View</summary>
-    /// <returns></returns>
-    public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; }
-
-    /// <inheritdoc/>
-    protected override void Dispose (bool disposing)
-    {
-        LineCanvas.Dispose ();
-
-        DisposeKeyboard ();
-        DisposeAdornments ();
-
-        for (int i = InternalSubviews.Count - 1; i >= 0; i--)
-        {
-            View subview = InternalSubviews [i];
-            Remove (subview);
-            subview.Dispose ();
-        }
-
-        base.Dispose (disposing);
-        Debug.Assert (InternalSubviews.Count == 0);
-    }
-
-    /// <summary>
-    ///     Called when the <see cref="Command.Accept"/> command is invoked. Raises <see cref="Accept"/>
-    ///     event.
-    /// </summary>
-    /// <returns>
-    ///     If <see langword="true"/> the event was canceled. If <see langword="false"/> the event was raised but not canceled.
-    ///     If <see langword="null"/> no event was raised.
-    /// </returns>
-    protected bool? OnAccept ()
-    {
-        var args = new HandledEventArgs ();
-        Accept?.Invoke (this, args);
-
-        return Accept is null ? null : args.Handled;
-    }
-
-    #region Constructors and Initialization
-
     /// <summary>
     ///     Points to the current driver in use by the view, it is a convenience property for simplifying the development
     ///     of new views.
@@ -181,14 +134,19 @@ public partial class View : Responder, ISupportInitializeNotification
     public View ()
     {
         SetupAdornments ();
+
+        SetupCommands ();
+
         SetupKeyboard ();
 
         //SetupMouse ();
+
         SetupText ();
+
     }
 
     /// <summary>
-    ///     Event called only once when the <see cref="View"/> is being initialized for the first time. Allows
+    ///     Raised once when the <see cref="View"/> is being initialized for the first time. Allows
     ///     configurations and assignments to be performed before the <see cref="View"/> being shown.
     ///     View implements <see cref="ISupportInitializeNotification"/> to allow for more sophisticated initialization.
     /// </summary>
@@ -287,7 +245,7 @@ public partial class View : Responder, ISupportInitializeNotification
         Initialized?.Invoke (this, EventArgs.Empty);
     }
 
-#endregion Constructors and Initialization
+    #endregion Constructors and Initialization
 
     #region Visibility
 
@@ -314,7 +272,10 @@ public partial class View : Responder, ISupportInitializeNotification
                 HasFocus = false;
             }
 
-            if (_enabled && CanFocus && Visible && !HasFocus
+            if (_enabled
+                && CanFocus
+                && Visible
+                && !HasFocus
                 && SuperView is null or { HasFocus: true, Visible: true, Enabled: true, Focused: null })
             {
                 SetFocus ();
@@ -346,15 +307,17 @@ public partial class View : Responder, ISupportInitializeNotification
         }
     }
 
-    /// <summary>Event fired when the <see cref="Enabled"/> value is being changed.</summary>
+    /// <summary>Raised when the <see cref="Enabled"/> value is being changed.</summary>
     public event EventHandler? EnabledChanged;
 
-    /// <summary>Method invoked when the <see cref="Enabled"/> property from a view is changed.</summary>
+    // TODO: Change this event to match the standard TG event model.
+    /// <summary>Invoked when the <see cref="Enabled"/> property from a view is changed.</summary>
     public virtual void OnEnabledChanged () { EnabledChanged?.Invoke (this, EventArgs.Empty); }
 
     private bool _visible = true;
 
-    /// <summary>Gets or sets a value indicating whether this <see cref="Responder"/> and all its child controls are displayed.</summary>
+    // TODO: Remove virtual once Menu/MenuBar are removed. MenuBar is the only override.
+    /// <summary>Gets or sets a value indicating whether this <see cref="View"/> is visible.</summary>
     public virtual bool Visible
     {
         get => _visible;
@@ -365,6 +328,19 @@ public partial class View : Responder, ISupportInitializeNotification
                 return;
             }
 
+            if (OnVisibleChanging ())
+            {
+                return;
+            }
+
+            CancelEventArgs<bool> args = new (in _visible, ref value);
+            VisibleChanging?.Invoke (this, args);
+
+            if (args.Cancel)
+            {
+                return;
+            }
+
             _visible = value;
 
             if (!_visible)
@@ -375,29 +351,45 @@ public partial class View : Responder, ISupportInitializeNotification
                 }
             }
 
-            if (_visible && CanFocus && Enabled && !HasFocus
+            if (_visible
+                && CanFocus
+                && Enabled
+                && !HasFocus
                 && SuperView is null or { HasFocus: true, Visible: true, Enabled: true, Focused: null })
             {
                 SetFocus ();
             }
 
             OnVisibleChanged ();
+            VisibleChanged?.Invoke (this, EventArgs.Empty);
+
             SetNeedsDisplay ();
         }
     }
 
-    /// <summary>Method invoked when the <see cref="Visible"/> property from a view is changed.</summary>
-    public virtual void OnVisibleChanged () { VisibleChanged?.Invoke (this, EventArgs.Empty); }
+    /// <summary>Called when <see cref="Visible"/> is changing. Can be cancelled by returning <see langword="true"/>.</summary>
+    protected virtual bool OnVisibleChanging () { return false; }
+
+    /// <summary>
+    ///     Raised when the <see cref="Visible"/> value is being changed. Can be cancelled by setting Cancel to
+    ///     <see langword="true"/>.
+    /// </summary>
+    public event EventHandler<CancelEventArgs<bool>>? VisibleChanging;
+
+    /// <summary>Called when <see cref="Visible"/> has changed.</summary>
+    protected virtual void OnVisibleChanged () { }
 
-    /// <summary>Event fired when the <see cref="Visible"/> value is being changed.</summary>
+    /// <summary>Raised when <see cref="Visible"/> has changed.</summary>
     public event EventHandler? VisibleChanged;
 
-    // TODO: This API is a hack. We should make Visible propogate automatically, no? See https://github.com/gui-cs/Terminal.Gui/issues/3703
     /// <summary>
     ///     INTERNAL Indicates whether all views up the Superview hierarchy are visible.
     /// </summary>
     /// <param name="view">The view to test.</param>
-    /// <returns> <see langword="false"/> if `view.Visible` is  <see langword="false"/> or any Superview is not visible, <see langword="true"/> otherwise.</returns>
+    /// <returns>
+    ///     <see langword="false"/> if `view.Visible` is  <see langword="false"/> or any Superview is not visible,
+    ///     <see langword="true"/> otherwise.
+    /// </returns>
     internal static bool CanBeVisible (View view)
     {
         if (!view.Visible)
@@ -416,7 +408,7 @@ public partial class View : Responder, ISupportInitializeNotification
         return true;
     }
 
-#endregion Visibility
+    #endregion Visibility
 
     #region Title
 
@@ -492,18 +484,16 @@ public partial class View : Responder, ISupportInitializeNotification
     private void SetTitleTextFormatterSize ()
     {
         TitleTextFormatter.ConstrainToSize = new (
-                                       TextFormatter.GetWidestLineLength (TitleTextFormatter.Text)
-                                       - (TitleTextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
-                                              ? Math.Max (HotKeySpecifier.GetColumns (), 0)
-                                              : 0),
-                                       1);
+                                                  TextFormatter.GetWidestLineLength (TitleTextFormatter.Text)
+                                                  - (TitleTextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+                                                         ? Math.Max (HotKeySpecifier.GetColumns (), 0)
+                                                         : 0),
+                                                  1);
     }
 
+    // TODO: Change this event to match the standard TG event model.
     /// <summary>Called when the <see cref="View.Title"/> has been changed. Invokes the <see cref="TitleChanged"/> event.</summary>
-    protected void OnTitleChanged ()
-    {
-        TitleChanged?.Invoke (this, new (in _title));
-    }
+    protected void OnTitleChanged () { TitleChanged?.Invoke (this, new (in _title)); }
 
     /// <summary>
     ///     Called before the <see cref="View.Title"/> changes. Invokes the <see cref="TitleChanging"/> event, which can
@@ -519,14 +509,37 @@ public partial class View : Responder, ISupportInitializeNotification
         return args.Cancel;
     }
 
-    /// <summary>Event fired after the <see cref="View.Title"/> has been changed.</summary>
+    /// <summary>Raised after the <see cref="View.Title"/> has been changed.</summary>
     public event EventHandler<EventArgs<string>>? TitleChanged;
 
     /// <summary>
-    ///     Event fired when the <see cref="View.Title"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to `true`
+    ///     Raised when the <see cref="View.Title"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to `true`
     ///     to cancel the Title change.
     /// </summary>
     public event EventHandler<CancelEventArgs<string>>? TitleChanging;
 
     #endregion
+
+    /// <summary>Pretty prints the View</summary>
+    /// <returns></returns>
+    public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; }
+
+    /// <inheritdoc/>
+    protected override void Dispose (bool disposing)
+    {
+        LineCanvas.Dispose ();
+
+        DisposeKeyboard ();
+        DisposeAdornments ();
+
+        for (int i = InternalSubviews.Count - 1; i >= 0; i--)
+        {
+            View subview = InternalSubviews [i];
+            Remove (subview);
+            subview.Dispose ();
+        }
+
+        base.Dispose (disposing);
+        Debug.Assert (InternalSubviews.Count == 0);
+    }
 }

+ 15 - 12
Terminal.Gui/View/ViewArrangement.cs

@@ -7,6 +7,10 @@
 /// </summary>
 /// <remarks>
 ///     <para>
+///         See the View Arrangement Deep Dive for more information:
+///         <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/arrangement.html"/>
+///     </para>
+///     <para>
 ///         Sizing or moving a view is only possible if the <see cref="View"/> is part of a <see cref="View.SuperView"/>
 ///         and
 ///         the relevant position and dimensions of the <see cref="View"/> are independent of other SubViews
@@ -37,11 +41,11 @@ public enum ViewArrangement
 
     /// <summary>
     ///     The top edge of the view can be resized.
+    ///     <para>
+    ///         This flag is mutually exclusive with <see cref="Movable"/>. If both are set, <see cref="Movable"/> takes
+    ///         precedence.
+    ///     </para>
     /// </summary>
-    /// <remarks>
-    ///     This flag is mutually exclusive with <see cref="Movable"/>. If both are set, <see cref="Movable"/> takes
-    ///     precedence.
-    /// </remarks>
     TopResizable = 8,
 
     /// <summary>
@@ -51,21 +55,20 @@ public enum ViewArrangement
 
     /// <summary>
     ///     The view can be resized in any direction.
+    ///     <para>
+    ///         If <see cref="Movable"/> is also set, the top will not be resizable.
+    ///     </para>
     /// </summary>
-    /// <remarks>
-    ///     If <see cref="Movable"/> is also set, the top will not be resizable.
-    /// </remarks>
     Resizable = LeftResizable | RightResizable | TopResizable | BottomResizable,
 
     /// <summary>
-    ///     The view overlap other views.
-    /// </summary>
-    /// <remarks>
+    ///     The view overlaps other views (the order of <see cref="View.Subviews"/> dicates the Z-order). If this flag is not
+    ///     set the view will operate in tiled mode.
     ///     <para>
     ///         When set, Tab and Shift-Tab will be constrained to the subviews of the view (normally, they will navigate to
     ///         the next/prev view in the next/prev Tabindex).
     ///         Use Ctrl-Tab (Ctrl-PageDown) / Ctrl-Shift-Tab (Ctrl-PageUp) to move between overlapped views.
     ///     </para>
-    /// </remarks>
-    Overlapped = 32,
+    /// </summary>
+    Overlapped = 32
 }

+ 14 - 23
Terminal.Gui/View/ViewportSettings.cs

@@ -3,6 +3,9 @@
 /// <summary>
 ///     Settings for how the <see cref="View.Viewport"/> behaves relative to the View's Content area.
 /// </summary>
+/// <remarks>
+///     See the Layout Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+/// </remarks>
 [Flags]
 public enum ViewportSettings
 {
@@ -15,43 +18,35 @@ public enum ViewportSettings
     ///     If set, <see cref="View.Viewport"/><c>.X</c> can be set to negative values enabling scrolling beyond the left of
     ///     the
     ///     content area.
-    /// </summary>
-    /// <remarks>
     ///     <para>
     ///         When not set, <see cref="View.Viewport"/><c>.X</c> is constrained to positive values.
     ///     </para>
-    /// </remarks>
+    /// </summary>
     AllowNegativeX = 1,
 
     /// <summary>
     ///     If set, <see cref="View.Viewport"/><c>.Y</c> can be set to negative values enabling scrolling beyond the top of the
     ///     content area.
-    /// </summary>
-    /// <remarks>
     ///     <para>
     ///         When not set, <see cref="View.Viewport"/><c>.Y</c> is constrained to positive values.
     ///     </para>
-    /// </remarks>
+    /// </summary>
     AllowNegativeY = 2,
 
     /// <summary>
     ///     If set, <see cref="View.Viewport"/><c>.Size</c> can be set to negative coordinates enabling scrolling beyond the
     ///     top-left of the
     ///     content area.
-    /// </summary>
-    /// <remarks>
     ///     <para>
     ///         When not set, <see cref="View.Viewport"/><c>.Size</c> is constrained to positive coordinates.
     ///     </para>
-    /// </remarks>
+    /// </summary>
     AllowNegativeLocation = AllowNegativeX | AllowNegativeY,
 
     /// <summary>
     ///     If set, <see cref="View.Viewport"/><c>.X</c> can be set values greater than <see cref="View.GetContentSize ()"/>
     ///     <c>.Width</c> enabling scrolling beyond the right
     ///     of the content area.
-    /// </summary>
-    /// <remarks>
     ///     <para>
     ///         When not set, <see cref="View.Viewport"/><c>.X</c> is constrained to <see cref="View.GetContentSize ()"/>
     ///         <c>.Width - 1</c>.
@@ -61,15 +56,13 @@ public enum ViewportSettings
     ///     <para>
     ///         The practical effect of this is that the last column of the content will always be visible.
     ///     </para>
-    /// </remarks>
+    /// </summary>
     AllowXGreaterThanContentWidth = 4,
 
     /// <summary>
     ///     If set, <see cref="View.Viewport"/><c>.Y</c> can be set values greater than <see cref="View.GetContentSize ()"/>
     ///     <c>.Height</c> enabling scrolling beyond the right
     ///     of the content area.
-    /// </summary>
-    /// <remarks>
     ///     <para>
     ///         When not set, <see cref="View.Viewport"/><c>.Y</c> is constrained to <see cref="View.GetContentSize ()"/>
     ///         <c>.Height - 1</c>.
@@ -79,21 +72,19 @@ public enum ViewportSettings
     ///     <para>
     ///         The practical effect of this is that the last row of the content will always be visible.
     ///     </para>
-    /// </remarks>
+    /// </summary>
     AllowYGreaterThanContentHeight = 8,
 
     /// <summary>
     ///     If set, <see cref="View.Viewport"/><c>.Size</c> can be set values greater than <see cref="View.GetContentSize ()"/>
     ///     enabling scrolling beyond the bottom-right
     ///     of the content area.
-    /// </summary>
-    /// <remarks>
     ///     <para>
     ///         When not set, <see cref="View.Viewport"/> is constrained to <see cref="View.GetContentSize ()"/><c> -1</c>.
     ///         This means the last column and row of the content will remain visible even if there is an attempt to
     ///         scroll the Viewport past the last column or row.
     ///     </para>
-    /// </remarks>
+    /// </summary>
     AllowLocationGreaterThanContentSize = AllowXGreaterThanContentWidth | AllowYGreaterThanContentHeight,
 
     /// <summary>
@@ -106,10 +97,10 @@ public enum ViewportSettings
     ///     If set <see cref="View.Clear()"/> will clear only the portion of the content
     ///     area that is visible within the <see cref="View.Viewport"/>. This is useful for views that have a
     ///     content area larger than the Viewport and want the area outside the content to be visually distinct.
+    ///     <para>
+    ///         <see cref="ClipContentOnly"/> must be set for this setting to work (clipping beyond the visible area must be
+    ///         disabled).
+    ///     </para>
     /// </summary>
-    /// <remarks>
-    ///     <see cref="ClipContentOnly"/> must be set for this setting to work (clipping beyond the visible area must be
-    ///     disabled).
-    /// </remarks>
     ClearContentOnly = 32
-}
+}

+ 56 - 9
Terminal.Gui/Views/Bar.cs

@@ -32,6 +32,7 @@ public class Bar : View, IOrientation, IDesignable
         _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
 
         Initialized += Bar_Initialized;
+        MouseEvent += OnMouseEvent;
 
         if (shortcuts is null)
         {
@@ -44,7 +45,43 @@ public class Bar : View, IOrientation, IDesignable
         }
     }
 
-    private void Bar_Initialized (object? sender, EventArgs e) { ColorScheme = Colors.ColorSchemes ["Menu"]; }
+    private void OnMouseEvent (object? sender, MouseEventEventArgs e)
+    {
+        NavigationDirection direction = NavigationDirection.Backward;
+
+        if (e.MouseEvent.Flags == MouseFlags.WheeledDown)
+        {
+            e.Handled = true;
+        }
+
+        if (e.MouseEvent.Flags == MouseFlags.WheeledUp)
+        {
+            direction = NavigationDirection.Forward;
+            e.Handled = true;
+        }
+
+        if (e.MouseEvent.Flags == MouseFlags.WheeledRight)
+        {
+            e.Handled = true;
+        }
+
+        if (e.MouseEvent.Flags == MouseFlags.WheeledLeft)
+        {
+            direction = NavigationDirection.Forward;
+            e.Handled = true;
+        }
+
+        if (e.Handled)
+        {
+            e.Handled = AdvanceFocus (direction, TabBehavior.TabStop);
+        }
+    }
+
+    private void Bar_Initialized (object? sender, EventArgs e)
+    {
+        ColorScheme = Colors.ColorSchemes ["Menu"];
+        LayoutBarItems (GetContentSize ());
+    }
 
     /// <inheritdoc/>
     public override void SetBorderStyle (LineStyle value)
@@ -159,6 +196,11 @@ public class Bar : View, IOrientation, IDesignable
     {
         base.OnLayoutStarted (args);
 
+        LayoutBarItems (args.OldContentSize);
+    }
+
+    private void LayoutBarItems (Size contentSize)
+    {
         View? prevBarItem = null;
 
         switch (Orientation)
@@ -171,8 +213,6 @@ public class Bar : View, IOrientation, IDesignable
                     barItem.ColorScheme = ColorScheme;
                     barItem.X = Pos.Align (Alignment.Start, AlignmentModes);
                     barItem.Y = 0; //Pos.Center ();
-                    // HACK: This should not be needed
-                    barItem.SetRelativeLayout (GetContentSize ());
                 }
                 break;
 
@@ -206,8 +246,6 @@ public class Bar : View, IOrientation, IDesignable
                     if (barItem is Shortcut scBarItem)
                     {
                         scBarItem.MinimumKeyTextSize = minKeyWidth;
-                        // HACK: This should not be needed
-                        scBarItem.SetRelativeLayout (GetContentSize ());
                         maxBarItemWidth = Math.Max (maxBarItemWidth, scBarItem.Frame.Width);
                     }
 
@@ -231,10 +269,6 @@ public class Bar : View, IOrientation, IDesignable
                 foreach (View barItem in Subviews)
                 {
                     barItem.Width = maxBarItemWidth;
-
-                    if (barItem is Line line)
-                    {
-                    }
                 }
 
                 Height = Dim.Auto (DimAutoStyle.Content, totalHeight);
@@ -264,6 +298,19 @@ public class Bar : View, IOrientation, IDesignable
 
         Add (shortcut);
 
+        shortcut = new Shortcut
+        {
+            Text = "Czech",
+            CommandView = new CheckBox ()
+            {
+                Title = "_Check"
+            },
+            Key = Key.F9,
+            CanFocus = false
+        };
+
+        Add (shortcut);
+
         return true;
     }
 }

+ 102 - 39
Terminal.Gui/Views/Button.cs

@@ -1,27 +1,22 @@
-//
-// Button.cs: Button control
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-
 namespace Terminal.Gui;
 
-/// <summary>Button is a <see cref="View"/> that provides an item that invokes raises the <see cref="View.Accept"/> event.</summary>
+/// <summary>
+///     A button View that can be pressed with the mouse or keybaord.
+/// </summary>
 /// <remarks>
 ///     <para>
-///         Provides a button showing text that raises the <see cref="View.Accept"/> event when clicked on with a mouse or
-///         when the user presses SPACE, ENTER, or the <see cref="View.HotKey"/>. The hot key is the first letter or digit
-///         following the first underscore ('_') in the button text.
+///         The Button will raise the <see cref="View.Accepting"/> event when the user presses <see cref="View.HotKey"/>,
+///         <c>Enter</c>, or <c>Space</c>
+///         or clicks on the button with the mouse.
 ///     </para>
 ///     <para>Use <see cref="View.HotKeySpecifier"/> to change the hot key specifier from the default of ('_').</para>
 ///     <para>
-///         When the button is configured as the default (<see cref="IsDefault"/>) and the user presses the ENTER key, if
-///         no other <see cref="View"/> processes the key, the <see cref="Button"/>'s <see cref="View.Accept"/> event will
-///         be fired.
+///         Button can act as the default <see cref="Command.Accept"/> handler for all peer-Views. See
+///         <see cref="IsDefault"/>.
 ///     </para>
 ///     <para>
-///         Set <see cref="View.WantContinuousButtonPressed"/> to <see langword="true"/> to have the <see cref="View.Accept"/> event
+///         Set <see cref="View.WantContinuousButtonPressed"/> to <see langword="true"/> to have the
+///         <see cref="View.Accepting"/> event
 ///         invoked repeatedly while the button is pressed.
 ///     </para>
 /// </remarks>
@@ -34,11 +29,17 @@ public class Button : View, IDesignable
     private bool _isDefault;
 
     /// <summary>
-    /// Gets or sets whether <see cref="Button"/>s are shown with a shadow effect by default.
+    ///     Gets or sets whether <see cref="Button"/>s are shown with a shadow effect by default.
     /// </summary>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
     public static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.None;
 
+    /// <summary>
+    ///     Gets or sets the default Highlight Style.
+    /// </summary>
+    [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+    public static HighlightStyle DefaultHighlightStyle { get; set; } = HighlightStyle.Pressed | HighlightStyle.Hover;
+
     /// <summary>Initializes a new instance of <see cref="Button"/>.</summary>
     public Button ()
     {
@@ -54,30 +55,53 @@ public class Button : View, IDesignable
         Width = Dim.Auto (DimAutoStyle.Text);
 
         CanFocus = true;
-        HighlightStyle |= HighlightStyle.Pressed;
-#if HOVER
-        HighlightStyle |= HighlightStyle.Hover;
-#endif
 
-        // Override default behavior of View
-        AddCommand (Command.HotKey, () =>
-        {
-            SetFocus ();
-            return !OnAccept ();
-        });
+        AddCommand (Command.HotKey, HandleHotKeyCommand);
 
+        KeyBindings.Remove (Key.Space);
         KeyBindings.Add (Key.Space, Command.HotKey);
+        KeyBindings.Remove (Key.Enter);
         KeyBindings.Add (Key.Enter, Command.HotKey);
 
         TitleChanged += Button_TitleChanged;
         MouseClick += Button_MouseClick;
 
         ShadowStyle = DefaultShadow;
+        HighlightStyle = DefaultHighlightStyle;
+    }
+
+    private bool? HandleHotKeyCommand (CommandContext ctx)
+    {
+        bool cachedIsDefault = IsDefault; // Supports "Swap Default" in Buttons scenario where IsDefault changes
+
+        if (RaiseSelecting (ctx) is true)
+        {
+            return true;
+        }
+
+        bool? handled = RaiseAccepting (ctx);
+
+        if (handled == true)
+        {
+            return true;
+        }
+
+        SetFocus ();
+
+        // TODO: If `IsDefault` were a property on `View` *any* View could work this way. That's theoretical as
+        // TODO: no use-case has been identified for any View other than Button to act like this.
+        // If Accept was not handled...
+        if (cachedIsDefault && SuperView is { })
+        {
+            return SuperView.InvokeCommand (Command.Accept);
+        }
+
+        return false;
     }
 
     private bool _wantContinuousButtonPressed;
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override bool WantContinuousButtonPressed
     {
         get => _wantContinuousButtonPressed;
@@ -103,7 +127,13 @@ public class Button : View, IDesignable
 
     private void Button_MouseClick (object sender, MouseEventEventArgs e)
     {
-        e.Handled = InvokeCommand (Command.HotKey) == true;
+        if (e.Handled)
+        {
+            return;
+        }
+
+        // TODO: With https://github.com/gui-cs/Terminal.Gui/issues/3778 we won't have to pass data:
+        e.Handled = InvokeCommand (Command.HotKey, new (Command.HotKey, null, data: this)) == true;
     }
 
     private void Button_TitleChanged (object sender, EventArgs<string> e)
@@ -112,37 +142,68 @@ public class Button : View, IDesignable
         TextFormatter.HotKeySpecifier = HotKeySpecifier;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override string Text
     {
-        get => base.Title;
-        set => base.Text = base.Title = value;
+        get => Title;
+        set => base.Text = Title = value;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override Rune HotKeySpecifier
     {
         get => base.HotKeySpecifier;
         set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
     }
 
-    /// <summary>Gets or sets whether the <see cref="Button"/> is the default action to activate in a dialog.</summary>
-    /// <value><c>true</c> if is default; otherwise, <c>false</c>.</value>
+    /// <summary>
+    ///     Gets or sets whether the <see cref="Button"/> will act as the default handler for <see cref="Command.Accept"/>
+    ///     commands on the <see cref="View.SuperView"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If <see langword="true"/>:
+    ///     </para>
+    ///     <para>
+    ///         - the Button will display an indicator that it is the default Button.
+    ///     </para>
+    ///     <para>
+    ///         - when clicked, if the Accepting event is not handled, <see cref="Command.Accept"/> will be
+    ///         invoked on the SuperView.
+    ///     </para>
+    ///     <para>
+    ///         - If a peer-View receives <see cref="Command.Accept"/> and does not handle it, the command will be passed to
+    ///         the
+    ///         first Button in the SuperView that has <see cref="IsDefault"/> set to <see langword="true"/>. See
+    ///         <see cref="View.RaiseAccepting"/> for more information.
+    ///     </para>
+    /// </remarks>
     public bool IsDefault
     {
         get => _isDefault;
         set
         {
+            if (_isDefault == value)
+            {
+                return;
+            }
+
             _isDefault = value;
+
             UpdateTextFormatterText ();
             OnResizeNeeded ();
         }
     }
 
-    /// <summary></summary>
+    /// <summary>
+    ///     Gets or sets whether the Button will show decorations or not. If <see langword="true"/> the glyphs that normally
+    ///     brakcet the Button Title and the <see cref="IsDefault"/> indicator will not be shown.
+    /// </summary>
     public bool NoDecorations { get; set; }
 
-    /// <summary></summary>
+    /// <summary>
+    ///     Gets or sets whether the Button will include padding on each side of the Title.
+    /// </summary>
     public bool NoPadding { get; set; }
 
     /// <inheritdoc/>
@@ -155,6 +216,7 @@ public class Button : View, IDesignable
                 if (TextFormatter.Text [i] == Text [0])
                 {
                     Move (i, 0);
+
                     return null; // Don't show the cursor
                 }
             }
@@ -166,7 +228,8 @@ public class Button : View, IDesignable
     /// <inheritdoc/>
     protected override void UpdateTextFormatterText ()
     {
-        base.UpdateTextFormatterText();
+        base.UpdateTextFormatterText ();
+
         if (NoDecorations)
         {
             TextFormatter.Text = Text;
@@ -188,11 +251,11 @@ public class Button : View, IDesignable
         }
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public bool EnableForDesign ()
     {
         Title = "_Button";
 
         return true;
     }
-}
+}

+ 123 - 63
Terminal.Gui/Views/CheckBox.cs

@@ -1,35 +1,54 @@
 #nullable enable
 namespace Terminal.Gui;
 
-/// <summary>Shows a check box that can be cycled between three states.</summary>
+/// <summary>Shows a check box that can be cycled between two or three states.</summary>
 public class CheckBox : View
 {
+    /// <summary>
+    ///     Gets or sets the default Highlight Style.
+    /// </summary>
+    [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+    public static HighlightStyle DefaultHighlightStyle { get; set; } = HighlightStyle.PressedOutside | HighlightStyle.Pressed | HighlightStyle.Hover;
+
     /// <summary>
     ///     Initializes a new instance of <see cref="CheckBox"/>.
     /// </summary>
     public CheckBox ()
     {
         Width = Dim.Auto (DimAutoStyle.Text);
-        Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1);
+        Height = Dim.Auto (DimAutoStyle.Text, 1);
 
         CanFocus = true;
 
-        // Things this view knows how to do
-        AddCommand (Command.Accept, AdvanceCheckState);
-        AddCommand (Command.HotKey, AdvanceCheckState);
+        // Select (Space key and single-click) - Advance state and raise Select event - DO NOT raise Accept
+        AddCommand (Command.Select, AdvanceAndSelect);
 
-        // Default keybindings for this view
-        KeyBindings.Add (Key.Space, Command.Accept);
+        // Hotkey - Advance state and raise Select event - DO NOT raise Accept
+        AddCommand (Command.HotKey, AdvanceAndSelect);
+
+        // Accept (Enter key) - Raise Accept event - DO NOT advance state
+        AddCommand (Command.Accept, RaiseAccepting);
 
         TitleChanged += Checkbox_TitleChanged;
 
-        HighlightStyle = Gui.HighlightStyle.PressedOutside | Gui.HighlightStyle.Pressed;
-        MouseClick += CheckBox_MouseClick;
+        HighlightStyle = DefaultHighlightStyle;
     }
 
-    private void CheckBox_MouseClick (object? sender, MouseEventEventArgs e)
+    private bool? AdvanceAndSelect (CommandContext ctx)
     {
-        e.Handled = AdvanceCheckState () == true;
+        bool? cancelled = AdvanceCheckState ();
+
+        if (cancelled is true)
+        {
+            return true;
+        }
+
+        if (RaiseSelecting (ctx) is true)
+        {
+            return true;
+        }
+
+        return ctx.Command == Command.HotKey ? cancelled : cancelled is false;
     }
 
     private void Checkbox_TitleChanged (object? sender, EventArgs<string> e)
@@ -38,24 +57,25 @@ public class CheckBox : View
         TextFormatter.HotKeySpecifier = HotKeySpecifier;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override string Text
     {
-        get => base.Title;
-        set => base.Text = base.Title = value;
+        get => Title;
+        set => base.Text = Title = value;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override Rune HotKeySpecifier
     {
         get => base.HotKeySpecifier;
         set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
     }
 
-    private bool _allowNone = false;
+    private bool _allowNone;
 
     /// <summary>
-    ///     If <see langword="true"/> allows <see cref="CheckedState"/> to be <see cref="CheckState.None"/>. The default is <see langword="false"/>.
+    ///     If <see langword="true"/> allows <see cref="CheckedState"/> to be <see cref="CheckState.None"/>. The default is
+    ///     <see langword="false"/>.
     /// </summary>
     public bool AllowCheckStateNone
     {
@@ -66,6 +86,7 @@ public class CheckBox : View
             {
                 return;
             }
+
             _allowNone = value;
 
             if (CheckedState == CheckState.None)
@@ -82,48 +103,106 @@ public class CheckBox : View
     /// </summary>
     /// <remarks>
     ///     <para>
-    ///        If <see cref="AllowCheckStateNone"/> is <see langword="true"/> and <see cref="CheckState.None"/>, the <see cref="CheckBox"/>
-    ///        will display the <c>ConfigurationManager.Glyphs.CheckStateNone</c> character (☒).
+    ///         If <see cref="AllowCheckStateNone"/> is <see langword="true"/> and <see cref="CheckState.None"/>, the
+    ///         <see cref="CheckBox"/>
+    ///         will display the <c>ConfigurationManager.Glyphs.CheckStateNone</c> character (☒).
     ///     </para>
     ///     <para>
-    ///        If <see cref="CheckState.UnChecked"/>, the <see cref="CheckBox"/>
-    ///        will display the <c>ConfigurationManager.Glyphs.CheckStateUnChecked</c> character (☐).
+    ///         If <see cref="CheckState.UnChecked"/>, the <see cref="CheckBox"/>
+    ///         will display the <c>ConfigurationManager.Glyphs.CheckStateUnChecked</c> character (☐).
     ///     </para>
     ///     <para>
-    ///        If <see cref="CheckState.Checked"/>, the <see cref="CheckBox"/>
-    ///        will display the <c>ConfigurationManager.Glyphs.CheckStateChecked</c> character (☑).
+    ///         If <see cref="CheckState.Checked"/>, the <see cref="CheckBox"/>
+    ///         will display the <c>ConfigurationManager.Glyphs.CheckStateChecked</c> character (☑).
     ///     </para>
     /// </remarks>
     public CheckState CheckedState
     {
         get => _checkedState;
-        set
+        set => ChangeCheckedState (value);
+    }
+
+    /// <summary>
+    ///     INTERNAL Sets CheckedState.
+    /// </summary>
+    /// <param name="value"></param>
+    /// <returns>
+    ///     <see langword="true"/> if state change was canceled, <see langword="false"/> if the state changed, and
+    ///     <see langword="null"/> if the state was not changed for some other reason.
+    /// </returns>
+    private bool? ChangeCheckedState (CheckState value)
+    {
+        if (_checkedState == value || (value is CheckState.None && !AllowCheckStateNone))
         {
-            if (_checkedState == value || (value is CheckState.None && !AllowCheckStateNone))
-            {
-                return;
-            }
+            return null;
+        }
+
+        CancelEventArgs<CheckState> e = new (in _checkedState, ref value);
 
-            _checkedState = value;
-            UpdateTextFormatterText ();
-            OnResizeNeeded ();
+        if (OnCheckedStateChanging (e))
+        {
+            return true;
         }
+
+        CheckedStateChanging?.Invoke (this, e);
+
+        if (e.Cancel)
+        {
+            return e.Cancel;
+        }
+
+        _checkedState = value;
+        UpdateTextFormatterText ();
+        OnResizeNeeded ();
+
+        EventArgs<CheckState> args = new (in _checkedState);
+        OnCheckedStateChanged (args);
+
+        CheckedStateChanged?.Invoke (this, args);
+
+        return false;
     }
 
-    /// <summary>
-    ///     Advances <see cref="CheckedState"/> to the next value. Invokes the cancelable <see cref="CheckedStateChanging"/> event.
-    /// </summary>
+    /// <summary>Called when the <see cref="CheckBox"/> state is changing.</summary>
     /// <remarks>
+    ///     <para>
+    ///         The state cahnge can be cancelled by setting the args.Cancel to <see langword="true"/>.
+    ///     </para>
     /// </remarks>
-    /// <returns>If <see langword="true"/> the <see cref="CheckedStateChanging"/> event was canceled.</returns>
+    protected virtual bool OnCheckedStateChanging (CancelEventArgs<CheckState> args) { return false; }
+
+    /// <summary>Raised when the <see cref="CheckBox"/> state is changing.</summary>
     /// <remarks>
-    /// <para>
-    ///     Cycles through the states <see cref="CheckState.None"/>, <see cref="CheckState.Checked"/>, and <see cref="CheckState.UnChecked"/>.
-    /// </para>
-    /// <para>
-    ///     If the <see cref="CheckedStateChanging"/> event is not canceled, the <see cref="CheckedState"/> will be updated and the <see cref="Command.Accept"/> event will be raised.
-    /// </para>
+    ///     <para>
+    ///         This event can be cancelled. If cancelled, the <see cref="CheckBox"/> will not change its state.
+    ///     </para>
+    /// </remarks>
+    public event EventHandler<CancelEventArgs<CheckState>>? CheckedStateChanging;
+
+    /// <summary>Called when the <see cref="CheckBox"/> state has changed.</summary>
+    protected virtual void OnCheckedStateChanged (EventArgs<CheckState> args) { }
+
+    /// <summary>Raised when the <see cref="CheckBox"/> state has changed.</summary>
+    public event EventHandler<EventArgs<CheckState>>? CheckedStateChanged;
+
+    /// <summary>
+    ///     Advances <see cref="CheckedState"/> to the next value. Invokes the cancelable <see cref="CheckedStateChanging"/>
+    ///     event.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Cycles through the states <see cref="CheckState.None"/>, <see cref="CheckState.Checked"/>, and
+    ///         <see cref="CheckState.UnChecked"/>.
+    ///     </para>
+    ///     <para>
+    ///         If the <see cref="CheckedStateChanging"/> event is not canceled, the <see cref="CheckedState"/> will be updated
+    ///         and the <see cref="Command.Accept"/> event will be raised.
+    ///     </para>
     /// </remarks>
+    /// <returns>
+    ///     <see langword="true"/> if state change was canceled, <see langword="false"/> if the state changed, and
+    ///     <see langword="null"/> if the state was not changed for some other reason.
+    /// </returns>
     public bool? AdvanceCheckState ()
     {
         CheckState oldValue = CheckedState;
@@ -152,35 +231,16 @@ public class CheckBox : View
                 break;
         }
 
-        CheckedStateChanging?.Invoke (this, e);
-        if (e.Cancel)
-        {
-            return e.Cancel;
-        }
+        bool? cancelled = ChangeCheckedState (e.NewValue);
 
-        // By default, Command.Accept calls OnAccept, so we need to call it here to ensure that the Accept event is fired.
-        if (OnAccept () == true)
-        {
-            return true;
-        }
-
-        CheckedState = e.NewValue;
-
-        return true;
+        return cancelled;
     }
 
-    /// <summary>Raised when the <see cref="CheckBox"/> state is changing.</summary>
-    /// <remarks>
-    /// <para>
-    ///    This event can be cancelled. If cancelled, the <see cref="CheckBox"/> will not change its state.
-    /// </para>
-    /// </remarks>
-    public event EventHandler<CancelEventArgs<CheckState>>? CheckedStateChanging;
-
     /// <inheritdoc/>
     protected override void UpdateTextFormatterText ()
     {
         base.UpdateTextFormatterText ();
+
         switch (TextAlignment)
         {
             case Alignment.Start:

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

@@ -23,14 +23,14 @@ internal abstract class ColorBar : View, IColorBar
         AddCommand (Command.LeftExtend, _ => Adjust (-MaxValue / 20));
         AddCommand (Command.RightExtend, _ => Adjust (MaxValue / 20));
 
-        AddCommand (Command.LeftHome, _ => SetZero ());
+        AddCommand (Command.LeftStart, _ => SetZero ());
         AddCommand (Command.RightEnd, _ => SetMax ());
 
         KeyBindings.Add (Key.CursorLeft, Command.Left);
         KeyBindings.Add (Key.CursorRight, Command.Right);
         KeyBindings.Add (Key.CursorLeft.WithShift, Command.LeftExtend);
         KeyBindings.Add (Key.CursorRight.WithShift, Command.RightExtend);
-        KeyBindings.Add (Key.Home, Command.LeftHome);
+        KeyBindings.Add (Key.Home, Command.LeftStart);
         KeyBindings.Add (Key.End, Command.RightEnd);
     }
 

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

@@ -64,7 +64,7 @@ public class ColorPicker : View
                     Width = textFieldWidth
                 };
                 tfValue.HasFocusChanged += UpdateSingleBarValueFromTextField;
-                tfValue.Accept += (s, _)=>UpdateSingleBarValueFromTextField(s);
+                tfValue.Accepting += (s, _)=>UpdateSingleBarValueFromTextField(s);
                 _textFields.Add (bar, tfValue);
             }
 
@@ -154,7 +154,7 @@ public class ColorPicker : View
         _tfName.Autocomplete = auto;
 
         _tfName.HasFocusChanged += UpdateValueFromName;
-        _tfName.Accept += (s, _) => UpdateValueFromName ();
+        _tfName.Accepting += (s, _) => UpdateValueFromName ();
     }
 
     private void CreateTextField ()
@@ -184,7 +184,7 @@ public class ColorPicker : View
         Add (_tfHex);
 
         _tfHex.HasFocusChanged += UpdateValueFromTextField;
-        _tfHex.Accept += (_,_)=> UpdateValueFromTextField();
+        _tfHex.Accepting += (_,_)=> UpdateValueFromTextField();
     }
 
     private void DisposeOldViews ()

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

@@ -61,7 +61,7 @@ public class ColorPicker16 : View
         set
         {
             int colorIndex = value.Y * _cols + value.X;
-            SelectedColor = (ColorName)colorIndex;
+            SelectedColor = (ColorName16)colorIndex;
         }
     }
 
@@ -132,7 +132,7 @@ public class ColorPicker16 : View
                     continue;
                 }
 
-                Driver.SetAttribute (new ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
+                Driver.SetAttribute (new ((ColorName16)foregroundColorIndex, (ColorName16)colorIndex));
                 bool selected = x == Cursor.X && y == Cursor.Y;
                 DrawColorBox (x, y, selected);
                 colorIndex++;
@@ -141,12 +141,12 @@ public class ColorPicker16 : View
     }
 
     /// <summary>Selected color.</summary>
-    public ColorName SelectedColor
+    public ColorName16 SelectedColor
     {
-        get => (ColorName)_selectColorIndex;
+        get => (ColorName16)_selectColorIndex;
         set
         {
-            if (value == (ColorName)_selectColorIndex)
+            if (value == (ColorName16)_selectColorIndex)
             {
                 return;
             }
@@ -166,8 +166,8 @@ public class ColorPicker16 : View
     {
         AddCommand (Command.Left, () => MoveLeft ());
         AddCommand (Command.Right, () => MoveRight ());
-        AddCommand (Command.LineUp, () => MoveUp ());
-        AddCommand (Command.LineDown, () => MoveDown ());
+        AddCommand (Command.Up, () => MoveUp ());
+        AddCommand (Command.Down, () => MoveDown ());
     }
 
     /// <summary>Add the KeyBindinds.</summary>
@@ -175,8 +175,8 @@ public class ColorPicker16 : View
     {
         KeyBindings.Add (Key.CursorLeft, Command.Left);
         KeyBindings.Add (Key.CursorRight, Command.Right);
-        KeyBindings.Add (Key.CursorUp, Command.LineUp);
-        KeyBindings.Add (Key.CursorDown, Command.LineDown);
+        KeyBindings.Add (Key.CursorUp, Command.Up);
+        KeyBindings.Add (Key.CursorDown, Command.Down);
     }
 
     // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.

+ 46 - 36
Terminal.Gui/Views/ComboBox.cs

@@ -7,8 +7,6 @@
 
 using System.Collections.ObjectModel;
 using System.ComponentModel;
-using System.Diagnostics;
-using System.Threading.Channels;
 
 namespace Terminal.Gui;
 
@@ -35,20 +33,17 @@ public class ComboBox : View, IDesignable
         _listview = new ComboListView (this, HideDropdownListOnClick) { CanFocus = true, TabStop = TabBehavior.NoStop };
 
         _search.TextChanged += Search_Changed;
-        _search.Accept += Search_Accept;
 
         _listview.Y = Pos.Bottom (_search);
-        _listview.OpenSelectedItem += (sender, a) => Selected ();
-
-        Add (_search, _listview);
-
-        // BUGBUG: This should not be needed; LayoutComplete will handle
-        Initialized += (s, e) => ProcessLayout ();
-
-        // On resize
-        LayoutComplete += (sender, a) => ProcessLayout ();
-        ;
-
+        _listview.OpenSelectedItem += (sender, a) => SelectText ();
+        _listview.Accepting += (sender, args) =>
+                              {
+                                  // This prevents Accepted from bubbling up to the combobox
+                                  args.Cancel = true;
+
+                                  // But OpenSelectedItem won't be fired because of that. So do it here.
+                                  SelectText ();
+                              };
         _listview.SelectedItemChanged += (sender, e) =>
                                          {
                                              if (!HideDropdownListOnClick && _searchSet.Count > 0)
@@ -56,6 +51,13 @@ public class ComboBox : View, IDesignable
                                                  SetValue (_searchSet [_listview.SelectedItem]);
                                              }
                                          };
+        Add (_search, _listview);
+
+        // BUGBUG: This should not be needed; LayoutComplete will handle
+        Initialized += (s, e) => ProcessLayout ();
+
+        // On resize
+        LayoutComplete += (sender, a) => ProcessLayout ();
 
         Added += (s, e) =>
                  {
@@ -76,28 +78,34 @@ public class ComboBox : View, IDesignable
                  };
 
         // Things this view knows how to do
-        AddCommand (Command.Accept, () => ActivateSelected ());
-        AddCommand (Command.ToggleExpandCollapse, () => ExpandCollapse ());
+        AddCommand (Command.Accept, (ctx) =>
+                                    {
+                                        if (ctx.Data == _search)
+                                        {
+                                            return null;
+                                        }
+                                        return ActivateSelected (ctx);
+                                    });
+        AddCommand (Command.Toggle, () => ExpandCollapse ());
         AddCommand (Command.Expand, () => Expand ());
         AddCommand (Command.Collapse, () => Collapse ());
-        AddCommand (Command.LineDown, () => MoveDown ());
-        AddCommand (Command.LineUp, () => MoveUp ());
+        AddCommand (Command.Down, () => MoveDown ());
+        AddCommand (Command.Up, () => MoveUp ());
         AddCommand (Command.PageDown, () => PageDown ());
         AddCommand (Command.PageUp, () => PageUp ());
-        AddCommand (Command.TopHome, () => MoveHome ());
-        AddCommand (Command.BottomEnd, () => MoveEnd ());
+        AddCommand (Command.Start, () => MoveHome ());
+        AddCommand (Command.End, () => MoveEnd ());
         AddCommand (Command.Cancel, () => CancelSelected ());
         AddCommand (Command.UnixEmulation, () => UnixEmulation ());
 
         // Default keybindings for this view
-        KeyBindings.Add (Key.Enter, Command.Accept);
-        KeyBindings.Add (Key.F4, Command.ToggleExpandCollapse);
-        KeyBindings.Add (Key.CursorDown, Command.LineDown);
-        KeyBindings.Add (Key.CursorUp, Command.LineUp);
+        KeyBindings.Add (Key.F4, Command.Toggle);
+        KeyBindings.Add (Key.CursorDown, Command.Down);
+        KeyBindings.Add (Key.CursorUp, Command.Up);
         KeyBindings.Add (Key.PageDown, Command.PageDown);
         KeyBindings.Add (Key.PageUp, Command.PageUp);
-        KeyBindings.Add (Key.Home, Command.TopHome);
-        KeyBindings.Add (Key.End, Command.BottomEnd);
+        KeyBindings.Add (Key.Home, Command.Start);
+        KeyBindings.Add (Key.End, Command.End);
         KeyBindings.Add (Key.Esc, Command.Cancel);
         KeyBindings.Add (Key.U.WithCtrl, Command.UnixEmulation);
     }
@@ -316,7 +324,7 @@ public class ComboBox : View, IDesignable
             _search.CursorPosition = _search.Text.GetRuneCount ();
         }
         else
-        { 
+        {
             if (_source?.Count > 0
               && _selectedItem > -1
               && _selectedItem < _source.Count - 1
@@ -384,13 +392,16 @@ public class ComboBox : View, IDesignable
         }
     }
 
-    private bool ActivateSelected ()
+    private bool ActivateSelected (CommandContext ctx)
     {
         if (HasItems ())
         {
-            Selected ();
+            if (SelectText ())
+            {
+                return false;
+            }
 
-            return true;
+            return RaiseAccepting (ctx) == true;
         }
 
         return false;
@@ -658,9 +669,6 @@ public class ComboBox : View, IDesignable
         SetSearchSet ();
     }
 
-    // Tell TextField to handle Accept Command (Enter)
-    void Search_Accept (object sender, HandledEventArgs e) { e.Handled = true; }
-
     private void Search_Changed (object sender, EventArgs e)
     {
         if (_source is null)
@@ -720,7 +728,7 @@ public class ComboBox : View, IDesignable
         }
     }
 
-    private void Selected ()
+    private bool SelectText ()
     {
         IsShow = false;
         _listview.TabStop = TabBehavior.NoStop;
@@ -731,7 +739,7 @@ public class ComboBox : View, IDesignable
             HideList ();
             IsShow = false;
 
-            return;
+            return false;
         }
 
         SetValue (_listview.SelectedItem > -1 ? _searchSet [_listview.SelectedItem] : _text);
@@ -741,6 +749,8 @@ public class ComboBox : View, IDesignable
         Reset (true);
         HideList ();
         IsShow = false;
+
+        return true;
     }
 
     private void SetSearchSet ()
@@ -992,7 +1002,7 @@ public class ComboBox : View, IDesignable
                                                              "ComboBox container cannot be null."
                                                             );
             HideDropdownListOnClick = hideDropdownListOnClick;
-            AddCommand (Command.LineUp, () => _container.MoveUpList ());
+            AddCommand (Command.Up, () => _container.MoveUpList ());
         }
     }
 

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

@@ -395,7 +395,7 @@ public class DateField : TextField
                         return true;
                     }
                    );
-        AddCommand (Command.LeftHome, () => MoveHome ());
+        AddCommand (Command.LeftStart, () => MoveHome ());
         AddCommand (Command.Left, () => MoveLeft ());
         AddCommand (Command.RightEnd, () => MoveEnd ());
         AddCommand (Command.Right, () => MoveRight ());
@@ -406,8 +406,8 @@ public class DateField : TextField
 
         KeyBindings.ReplaceCommands (Key.Backspace, Command.DeleteCharLeft);
 
-        KeyBindings.ReplaceCommands (Key.Home, Command.LeftHome);
-        KeyBindings.ReplaceCommands (Key.A.WithCtrl, Command.LeftHome);
+        KeyBindings.ReplaceCommands (Key.Home, Command.LeftStart);
+        KeyBindings.ReplaceCommands (Key.A.WithCtrl, Command.LeftStart);
 
         KeyBindings.ReplaceCommands (Key.CursorLeft, Command.Left);
         KeyBindings.ReplaceCommands (Key.B.WithCtrl, Command.Left);

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

@@ -184,7 +184,6 @@ public class DatePicker : View
     private void SetInitialProperties (DateTime date)
     {
         _date = date;
-        Title = "Date Picker";
         BorderStyle = LineStyle.Single;
         Date = date;
         _dateLabel = new Label { X = 0, Y = 0, Text = "Date: " };
@@ -228,7 +227,7 @@ public class DatePicker : View
             ShadowStyle = ShadowStyle.None
         };
 
-        _previousMonthButton.Accept += (sender, e) =>
+        _previousMonthButton.Accepting += (sender, e) =>
                                         {
                                             Date = _date.AddMonths (-1);
                                             CreateCalendar ();
@@ -248,7 +247,7 @@ public class DatePicker : View
             ShadowStyle = ShadowStyle.None
         };
 
-        _nextMonthButton.Accept += (sender, e) =>
+        _nextMonthButton.Accepting += (sender, e) =>
                                     {
                                         Date = _date.AddMonths (1);
                                         CreateCalendar ();

+ 1 - 12
Terminal.Gui/Views/Dialog.cs

@@ -66,7 +66,7 @@ public class Dialog : Window
     /// </remarks>
     public Dialog ()
     {
-        Arrangement = ViewArrangement.Movable;
+        Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped;
         ShadowStyle = DefaultShadow;
         BorderStyle = DefaultBorderStyle;
 
@@ -80,17 +80,6 @@ public class Dialog : Window
         Modal = true;
         ButtonAlignment = DefaultButtonAlignment;
         ButtonAlignmentModes = DefaultButtonAlignmentModes;
-
-        AddCommand (
-                    Command.QuitToplevel,
-                    () =>
-                    {
-                        Canceled = true;
-                        RequestStop ();
-
-                        return true;
-                    });
-        KeyBindings.Add (Key.Esc, Command.QuitToplevel);
     }
 
     // BUGBUG: We override GetNormal/FocusColor because "Dialog" ColorScheme is goofy.

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

@@ -78,7 +78,7 @@ public class FileDialog : Dialog
             Y = Pos.AnchorEnd (),
             IsDefault = true, Text = Style.OkButtonText
         };
-        _btnOk.Accept += (s, e) => Accept (true);
+        _btnOk.Accepting += (s, e) => Accept (true);
 
 
         _btnCancel = new Button
@@ -88,7 +88,7 @@ public class FileDialog : Dialog
             Text = Strings.btnCancel
         };
 
-        _btnCancel.Accept += (s, e) =>
+        _btnCancel.Accepting += (s, e) =>
         {
             Canceled = true;
             Application.RequestStop ();
@@ -96,15 +96,15 @@ public class FileDialog : Dialog
 
         _btnUp = new Button { X = 0, Y = 1, NoPadding = true };
         _btnUp.Text = GetUpButtonText ();
-        _btnUp.Accept += (s, e) => _history.Up ();
+        _btnUp.Accepting += (s, e) => _history.Up ();
 
         _btnBack = new Button { X = Pos.Right (_btnUp) + 1, Y = 1, NoPadding = true };
         _btnBack.Text = GetBackButtonText ();
-        _btnBack.Accept += (s, e) => _history.Back ();
+        _btnBack.Accepting += (s, e) => _history.Back ();
 
         _btnForward = new Button { X = Pos.Right (_btnBack) + 1, Y = 1, NoPadding = true };
         _btnForward.Text = GetForwardButtonText ();
-        _btnForward.Accept += (s, e) => _history.Forward ();
+        _btnForward.Accepting += (s, e) => _history.Forward ();
 
         _tbPath = new TextField { Width = Dim.Fill (), CaptionColor = new Color (Color.Black) };
 
@@ -182,7 +182,7 @@ public class FileDialog : Dialog
             Y = Pos.AnchorEnd (), Text = GetToggleSplitterText (false)
         };
 
-        _btnToggleSplitterCollapse.Accept += (s, e) =>
+        _btnToggleSplitterCollapse.Accepting += (s, e) =>
                                               {
                                                   Tile tile = _splitContainer.Tiles.ElementAt (0);
 
@@ -236,10 +236,10 @@ public class FileDialog : Dialog
         _tableView.KeyUp += (s, k) => k.Handled = TableView_KeyUp (k);
         _tableView.SelectedCellChanged += TableView_SelectedCellChanged;
 
-        _tableView.KeyBindings.ReplaceCommands (Key.Home, Command.TopHome);
-        _tableView.KeyBindings.ReplaceCommands (Key.End, Command.BottomEnd);
-        _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.TopHomeExtend);
-        _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.BottomEndExtend);
+        _tableView.KeyBindings.ReplaceCommands (Key.Home, Command.Start);
+        _tableView.KeyBindings.ReplaceCommands (Key.End, Command.End);
+        _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.StartExtend);
+        _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.EndExtend);
         
         AllowsMultipleSelection = false;
 
@@ -610,7 +610,7 @@ public class FileDialog : Dialog
         ApplySort ();
     }
 
-    private new void Accept (IEnumerable<FileSystemInfoStats> toMultiAccept)
+    private void Accept (IEnumerable<FileSystemInfoStats> toMultiAccept)
     {
         if (!AllowsMultipleSelection)
         {
@@ -629,7 +629,7 @@ public class FileDialog : Dialog
         FinishAccept ();
     }
 
-    private new void Accept (IFileInfo f)
+    private void Accept (IFileInfo f)
     {
         if (!IsCompatibleWithOpenMode (f.FullName, out string reason))
         {
@@ -649,7 +649,7 @@ public class FileDialog : Dialog
         FinishAccept ();
     }
 
-    private new void Accept (bool allowMulti)
+    private void Accept (bool allowMulti)
     {
         if (allowMulti && TryAcceptMulti ())
         {

+ 3 - 1
Terminal.Gui/Views/FrameView.cs

@@ -1,5 +1,6 @@
 namespace Terminal.Gui;
 
+// TODO: FrameView is mis-named, really. It's far more about it being a TabGroup than a frame. 
 /// <summary>
 ///     The FrameView is a container View with a border around it. 
 /// </summary>
@@ -23,7 +24,8 @@ public class FrameView : View
 
     private void FrameView_MouseClick (object sender, MouseEventEventArgs e)
     {
-        e.Handled = InvokeCommand (Command.HotKey) == true;
+        // base sets focus on HotKey
+        e.Handled = InvokeCommand (Command.HotKey, ctx: new (Command.HotKey, key: null, data: this)) == true;
     }
 
 

+ 62 - 24
Terminal.Gui/Views/HexView.cs

@@ -25,7 +25,7 @@ namespace Terminal.Gui;
 ///     </para>
 ///     <para>Control the first byte shown by setting the <see cref="DisplayStart"/> property to an offset in the stream.</para>
 /// </remarks>
-public class HexView : View
+public class HexView : View, IDesignable
 {
     private const int bsize = 4;
     private const int displayWidth = 9;
@@ -33,7 +33,8 @@ public class HexView : View
     private int bpl;
     private long displayStart, pos;
     private SortedDictionary<long, byte> edits = [];
-    private bool firstNibble, leftSide;
+    private bool firstNibble;
+    private bool leftSide;
     private Stream source;
     private static readonly Rune SpaceCharRune = new (' ');
     private static readonly Rune PeriodCharRune = new ('.');
@@ -46,8 +47,7 @@ public class HexView : View
     public HexView (Stream source)
     {
         Source = source;
-        // BUG: This will always call the most-derived definition of CanFocus.
-        // Either seal it or don't set it here.
+
         CanFocus = true;
         CursorVisibility = CursorVisibility.Default;
         leftSide = true;
@@ -59,15 +59,16 @@ public class HexView : View
         // Things this view knows how to do
         AddCommand (Command.Left, () => MoveLeft ());
         AddCommand (Command.Right, () => MoveRight ());
-        AddCommand (Command.LineDown, () => MoveDown (bytesPerLine));
-        AddCommand (Command.LineUp, () => MoveUp (bytesPerLine));
-        AddCommand (Command.Accept, () => ToggleSide ());
+        AddCommand (Command.Down, () => MoveDown (bytesPerLine));
+        AddCommand (Command.Up, () => MoveUp (bytesPerLine));
+        AddCommand (Command.Tab, () => Navigate (NavigationDirection.Forward));
+        AddCommand (Command.BackTab, () => Navigate (NavigationDirection.Backward));
         AddCommand (Command.PageUp, () => MoveUp (bytesPerLine * Frame.Height));
         AddCommand (Command.PageDown, () => MoveDown (bytesPerLine * Frame.Height));
-        AddCommand (Command.TopHome, () => MoveHome ());
-        AddCommand (Command.BottomEnd, () => MoveEnd ());
-        AddCommand (Command.StartOfLine, () => MoveStartOfLine ());
-        AddCommand (Command.EndOfLine, () => MoveEndOfLine ());
+        AddCommand (Command.Start, () => MoveHome ());
+        AddCommand (Command.End, () => MoveEnd ());
+        AddCommand (Command.LeftStart, () => MoveLeftStart ());
+        AddCommand (Command.RightEnd, () => MoveEndOfLine ());
         AddCommand (Command.StartOfPage, () => MoveUp (bytesPerLine * ((int)(position - displayStart) / bytesPerLine)));
 
         AddCommand (
@@ -78,9 +79,8 @@ public class HexView : View
         // Default keybindings for this view
         KeyBindings.Add (Key.CursorLeft, Command.Left);
         KeyBindings.Add (Key.CursorRight, Command.Right);
-        KeyBindings.Add (Key.CursorDown, Command.LineDown);
-        KeyBindings.Add (Key.CursorUp, Command.LineUp);
-        KeyBindings.Add (Key.Enter, Command.Accept);
+        KeyBindings.Add (Key.CursorDown, Command.Down);
+        KeyBindings.Add (Key.CursorUp, Command.Up);
 
         KeyBindings.Add (Key.V.WithAlt, Command.PageUp);
         KeyBindings.Add (Key.PageUp, Command.PageUp);
@@ -88,13 +88,16 @@ public class HexView : View
         KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
         KeyBindings.Add (Key.PageDown, Command.PageDown);
 
-        KeyBindings.Add (Key.Home, Command.TopHome);
-        KeyBindings.Add (Key.End, Command.BottomEnd);
-        KeyBindings.Add (Key.CursorLeft.WithCtrl, Command.StartOfLine);
-        KeyBindings.Add (Key.CursorRight.WithCtrl, Command.EndOfLine);
+        KeyBindings.Add (Key.Home, Command.Start);
+        KeyBindings.Add (Key.End, Command.End);
+        KeyBindings.Add (Key.CursorLeft.WithCtrl, Command.LeftStart);
+        KeyBindings.Add (Key.CursorRight.WithCtrl, Command.RightEnd);
         KeyBindings.Add (Key.CursorUp.WithCtrl, Command.StartOfPage);
         KeyBindings.Add (Key.CursorDown.WithCtrl, Command.EndOfPage);
 
+        KeyBindings.Add (Key.Tab, Command.Tab);
+        KeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
+
         LayoutComplete += HexView_LayoutComplete;
     }
 
@@ -247,7 +250,7 @@ public class HexView : View
     public event EventHandler<HexViewEditEventArgs> Edited;
 
     /// <inheritdoc/>
-    protected internal override bool OnMouseEvent  (MouseEvent me)
+    protected internal override bool OnMouseEvent (MouseEvent me)
     {
         if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)
             && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
@@ -529,12 +532,14 @@ public class HexView : View
 
         int x = displayWidth + block * 14 + column + (firstNibble ? 0 : 1);
         int y = line;
+
         if (!leftSide)
         {
             x = displayWidth + bytesPerLine / bsize * 14 + item - 1;
         }
 
         Move (x, y);
+
         return new (x, y);
     }
 
@@ -728,7 +733,7 @@ public class HexView : View
         return true;
     }
 
-    private bool MoveStartOfLine ()
+    private bool MoveLeftStart ()
     {
         position = position / bytesPerLine * bytesPerLine;
         SetNeedsDisplay ();
@@ -764,17 +769,50 @@ public class HexView : View
         {
             return;
         }
+
         var delta = (int)(pos - DisplayStart);
         int line = delta / bytesPerLine;
 
         SetNeedsDisplay (new (0, line, Viewport.Width, 1));
     }
 
-    private bool ToggleSide ()
+    private bool Navigate (NavigationDirection direction)
     {
-        leftSide = !leftSide;
-        RedisplayLine (position);
-        firstNibble = true;
+        switch (direction)
+        {
+            case NavigationDirection.Forward:
+                if (leftSide)
+                {
+                    leftSide = false;
+                    RedisplayLine (position);
+                    firstNibble = true;
+
+                    return true;
+                }
+
+                break;
+
+            case NavigationDirection.Backward:
+                if (!leftSide)
+                {
+                    leftSide = true;
+                    RedisplayLine (position);
+                    firstNibble = true;
+                    return true;
+                }
+
+
+                break;
+        }
+
+        return false;
+    }
+
+
+    /// <inheritdoc />
+    bool IDesignable.EnableForDesign ()
+    {
+        Source = new MemoryStream (Encoding.UTF8.GetBytes ("HexEditor Unicode that shouldn't 𝔹Aℝ𝔽!"));
 
         return true;
     }

+ 44 - 17
Terminal.Gui/Views/Label.cs

@@ -1,14 +1,22 @@
 namespace Terminal.Gui;
 
 /// <summary>
-///     The Label <see cref="View"/> displays a string at a given position and supports multiple lines separated by
-///     newline characters. Multi-line Labels support word wrap.
+///     The Label <see cref="View"/> displays text that describes the View next in the <see cref="View.Subviews"/>. When
+///     Label
+///     recieves a <see cref="Command.HotKey"/> command it will pass it to the next <see cref="View"/> in
+///     <see cref="View.Subviews"/>.
 /// </summary>
 /// <remarks>
-///     The <see cref="Label"/> view is functionality identical to <see cref="View"/> and is included for API
-///     backwards compatibility.
+///     <para>
+///         Title and Text are the same property. When Title is set Text s also set. When Text is set Title is also set.
+///     </para>
+///     <para>
+///         If <see cref="View.CanFocus"/> is <see langword="false"/> and the use clicks on the Label,
+///         the <see cref="Command.HotKey"/> will be invoked on the next <see cref="View"/> in
+///         <see cref="View.Subviews"/>."
+///     </para>
 /// </remarks>
-public class Label : View
+public class Label : View, IDesignable
 {
     /// <inheritdoc/>
     public Label ()
@@ -16,21 +24,19 @@ public class Label : View
         Height = Dim.Auto (DimAutoStyle.Text);
         Width = Dim.Auto (DimAutoStyle.Text);
 
-        // Things this view knows how to do
-        AddCommand (Command.HotKey, FocusNext);
-
-        // Default key bindings for this view
-        KeyBindings.Add (Key.Space, Command.Accept);
+        // On HoKey, pass it to the next view
+        AddCommand (Command.HotKey, InvokeHotKeyOnNext);
 
         TitleChanged += Label_TitleChanged;
         MouseClick += Label_MouseClick;
     }
 
+    // TODO: base raises Select, but we want to raise HotKey. This can be simplified?
     private void Label_MouseClick (object sender, MouseEventEventArgs e)
     {
         if (!CanFocus)
         {
-            e.Handled = InvokeCommand (Command.HotKey) == true;
+            e.Handled = InvokeCommand (Command.HotKey, ctx: new (Command.HotKey, key: null, data: this)) == true;
         }
     }
 
@@ -40,28 +46,49 @@ public class Label : View
         TextFormatter.HotKeySpecifier = HotKeySpecifier;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override string Text
     {
-        get => base.Title;
-        set => base.Text = base.Title = value;
+        get => Title;
+        set => base.Text = Title = value;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override Rune HotKeySpecifier
     {
         get => base.HotKeySpecifier;
         set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
     }
 
-    private bool? FocusNext ()
+    private bool? InvokeHotKeyOnNext (CommandContext context)
     {
+        if (RaiseHandlingHotKey () == true)
+        {
+            return true;
+        }
+
+        if (CanFocus)
+        {
+            SetFocus ();
+
+            return true;
+        }
+
         int me = SuperView?.Subviews.IndexOf (this) ?? -1;
+
         if (me != -1 && me < SuperView?.Subviews.Count - 1)
         {
-            SuperView?.Subviews [me + 1].SetFocus ();
+            return SuperView?.Subviews [me + 1].InvokeCommand (Command.HotKey, context.Key, context.KeyBinding) == true;
         }
 
+        return false;
+    }
+
+    /// <inheritdoc/>
+    bool IDesignable.EnableForDesign ()
+    {
+        Text = "_Label";
+
         return true;
     }
 }

+ 81 - 47
Terminal.Gui/Views/ListView.cs

@@ -6,7 +6,7 @@ using static Terminal.Gui.SpinnerStyle;
 namespace Terminal.Gui;
 
 /// <summary>Implement <see cref="IListDataSource"/> to provide custom rendering for a <see cref="ListView"/>.</summary>
-public interface IListDataSource: IDisposable
+public interface IListDataSource : IDisposable
 {
     /// <summary>
     /// Event to raise when an item is added, removed, or moved, or the entire list is refreshed.
@@ -123,47 +123,95 @@ public class ListView : View, IDesignable
 
         // Things this view knows how to do
         // 
-        // BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
-        AddCommand (Command.LineUp, () => MoveUp ());
-        // BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
-        AddCommand (Command.LineDown, () => MoveDown ());
+        // 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.ScrollUp, () => ScrollVertical (-1));
         AddCommand (Command.ScrollDown, () => ScrollVertical (1));
         AddCommand (Command.PageUp, () => MovePageUp ());
         AddCommand (Command.PageDown, () => MovePageDown ());
-        AddCommand (Command.TopHome, () => MoveHome ());
-        AddCommand (Command.BottomEnd, () => MoveEnd ());
-        AddCommand (Command.Accept, () => OnOpenSelectedItem ());
-        AddCommand (Command.OpenSelectedItem, () => OnOpenSelectedItem ());
-        AddCommand (Command.Select, () => MarkUnmarkRow ());
-
+        AddCommand (Command.Start, () => MoveHome ());
+        AddCommand (Command.End, () => MoveEnd ());
         AddCommand (Command.ScrollLeft, () => ScrollHorizontal (-1));
         AddCommand (Command.ScrollRight, () => ScrollHorizontal (1));
 
+        // Accept (Enter key) - Raise Accept event - DO NOT advance state
+        AddCommand (Command.Accept, (ctx) =>
+                                    {
+                                        if (RaiseAccepting (ctx) == true)
+                                        {
+                                            return true;
+                                        }
+
+                                        if (OnOpenSelectedItem ())
+                                        {
+                                                return true;
+                                        }
+
+                                        return false;
+                                    });
+
+        // Select (Space key and single-click) - If markable, change mark and raise Select event
+        AddCommand (Command.Select, (ctx) =>
+                                    {
+                                        if (_allowsMarking)
+                                        {
+                                            if (RaiseSelecting (ctx) == true)
+                                            {
+                                                return true;
+                                            }
+
+                                            if (MarkUnmarkSelectedItem ())
+                                            {
+                                                return true;
+                                            }
+                                        }
+
+                                        return false;
+                                    });
+
+
+        // Hotkey - If none set, select and raise Select event. SetFocus. - DO NOT raise Accept
+        AddCommand (Command.HotKey, (ctx) =>
+                                    {
+                                        if (SelectedItem == -1)
+                                        {
+                                            SelectedItem = 0;
+                                            if (RaiseSelecting (ctx) == true)
+                                            {
+                                                return true;
+
+                                            }
+                                        }
+
+                                        return !SetFocus ();
+                                    });
+
+
         // Default keybindings for all ListViews
-        KeyBindings.Add (Key.CursorUp, Command.LineUp);
-        KeyBindings.Add (Key.P.WithCtrl, Command.LineUp);
+        KeyBindings.Add (Key.CursorUp, Command.Up);
+        KeyBindings.Add (Key.P.WithCtrl, Command.Up);
 
-        KeyBindings.Add (Key.CursorDown, Command.LineDown);
-        KeyBindings.Add (Key.N.WithCtrl, Command.LineDown);
+        KeyBindings.Add (Key.CursorDown, Command.Down);
+        KeyBindings.Add (Key.N.WithCtrl, Command.Down);
 
         KeyBindings.Add (Key.PageUp, Command.PageUp);
 
         KeyBindings.Add (Key.PageDown, Command.PageDown);
         KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
 
-        KeyBindings.Add (Key.Home, Command.TopHome);
-
-        KeyBindings.Add (Key.End, Command.BottomEnd);
+        KeyBindings.Add (Key.Home, Command.Start);
 
-        KeyBindings.Add (Key.Enter, Command.OpenSelectedItem);
+        KeyBindings.Add (Key.End, Command.End);
     }
 
     /// <summary>Gets or sets whether this <see cref="ListView"/> allows items to be marked.</summary>
     /// <value>Set to <see langword="true"/> to allow marking elements of the list.</value>
     /// <remarks>
     ///     If set to <see langword="true"/>, <see cref="ListView"/> will render items marked items with "[x]", and
-    ///     unmarked items with "[ ]" spaces. SPACE key will toggle marking. The default is <see langword="false"/>.
+    ///     unmarked items with "[ ]". SPACE key will toggle marking. The default is <see langword="false"/>.
     /// </remarks>
     public bool AllowsMarking
     {
@@ -171,16 +219,6 @@ public class ListView : View, IDesignable
         set
         {
             _allowsMarking = value;
-
-            if (_allowsMarking)
-            {
-                KeyBindings.Add (Key.Space, Command.Select);
-            }
-            else
-            {
-                KeyBindings.Remove (Key.Space);
-            }
-
             SetNeedsDisplay ();
         }
     }
@@ -334,10 +372,10 @@ public class ListView : View, IDesignable
 
     /// <summary>
     ///     If <see cref="AllowsMarking"/> and <see cref="AllowsMultipleSelection"/> are both <see langword="true"/>,
-    ///     unmarks all marked items other than the currently selected.
+    ///     unmarks all marked items other than <see cref="SelectedItem"/>.
     /// </summary>
     /// <returns><see langword="true"/> if unmarking was successful.</returns>
-    public virtual bool AllowsAll ()
+    public bool UnmarkAllButSelected ()
     {
         if (!_allowsMarking)
         {
@@ -385,16 +423,18 @@ public class ListView : View, IDesignable
 
     /// <summary>Marks the <see cref="SelectedItem"/> if it is not already marked.</summary>
     /// <returns><see langword="true"/> if the <see cref="SelectedItem"/> was marked.</returns>
-    public virtual bool MarkUnmarkRow ()
+    public bool MarkUnmarkSelectedItem ()
     {
-        if (AllowsAll ())
+        if (UnmarkAllButSelected ())
         {
             Source.SetMark (SelectedItem, !Source.IsMarked (SelectedItem));
             SetNeedsDisplay ();
 
-            return true;
+            return Source.IsMarked (SelectedItem);
         }
 
+        // BUGBUG: Shouldn't this retrn Source.IsMarked (SelectedItem)
+
         return false;
     }
 
@@ -458,12 +498,9 @@ public class ListView : View, IDesignable
 
         _selected = Viewport.Y + me.Position.Y;
 
-        if (AllowsAll ())
+        if (MarkUnmarkSelectedItem ())
         {
-            Source.SetMark (SelectedItem, !Source.IsMarked (SelectedItem));
-            SetNeedsDisplay ();
-
-            return true;
+            // return true;
         }
 
         OnSelectedChanged ();
@@ -471,7 +508,7 @@ public class ListView : View, IDesignable
 
         if (me.Flags == MouseFlags.Button1DoubleClicked)
         {
-            OnOpenSelectedItem ();
+            return InvokeCommand (Command.Accept) is true;
         }
 
         return true;
@@ -759,13 +796,9 @@ public class ListView : View, IDesignable
 
         object value = _source.ToList () [_selected];
 
-        // By default, Command.Accept calls OnAccept, so we need to call it here to ensure that the event is fired.
-        if (OnAccept () == true)
-        {
-            return true;
-        }
-
         OpenSelectedItem?.Invoke (this, new ListViewItemEventArgs (_selected, value));
+
+        // BUGBUG: this should not blindly return true.
         return true;
     }
 
@@ -794,6 +827,7 @@ public class ListView : View, IDesignable
     /// <param name="rowEventArgs"></param>
     public virtual void OnRowRender (ListViewRowEventArgs rowEventArgs) { RowRender?.Invoke (this, rowEventArgs); }
 
+    // TODO: Use standard event model
     /// <summary>Invokes the <see cref="SelectedItemChanged"/> event if it is defined.</summary>
     /// <returns></returns>
     public virtual bool OnSelectedChanged ()

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

@@ -177,7 +177,7 @@ public sealed class ContextMenu : IDisposable
         }
 
         MenuItems = menuItems;
-        _container = Application.Current;
+        _container = Application.Top;
         _container!.Closing += Container_Closing;
         _container.Deactivate += Container_Deactivate;
         _container.Disposing += Container_Disposing;

+ 19 - 19
Terminal.Gui/Views/Menu/Menu.cs

@@ -101,7 +101,7 @@ internal sealed class Menu : View
 
             for (var i = 0; i < _barItems.Children?.Length; i++)
             {
-                if (_barItems.Children [i]!.IsEnabled ())
+                if (_barItems.Children [i]?.IsEnabled () == true)
                 {
                     _currentChild = i;
 
@@ -144,17 +144,17 @@ internal sealed class Menu : View
 
     public Menu ()
     {
-        if (Application.Current is { })
+        if (Application.Top is { })
         {
-            Application.Current.DrawContentComplete += Current_DrawContentComplete;
-            Application.Current.SizeChanging += Current_TerminalResized;
+            Application.Top.DrawContentComplete += Current_DrawContentComplete;
+            Application.Top.SizeChanging += Current_TerminalResized;
         }
 
         Application.MouseEvent += Application_RootMouseEvent;
 
         // Things this view knows how to do
-        AddCommand (Command.LineUp, () => MoveUp ());
-        AddCommand (Command.LineDown, () => MoveDown ());
+        AddCommand (Command.Up, () => MoveUp ());
+        AddCommand (Command.Down, () => MoveDown ());
 
         AddCommand (
                     Command.Left,
@@ -186,16 +186,15 @@ internal sealed class Menu : View
                     }
                    );
         AddCommand (Command.Select, ctx => _host?.SelectItem ((ctx.KeyBinding?.Context as MenuItem)!));
-        AddCommand (Command.ToggleExpandCollapse, ctx => ExpandCollapse ((ctx.KeyBinding?.Context as MenuItem)!));
+        AddCommand (Command.Toggle, ctx => ExpandCollapse ((ctx.KeyBinding?.Context as MenuItem)!));
         AddCommand (Command.HotKey, ctx => _host?.SelectItem ((ctx.KeyBinding?.Context as MenuItem)!));
 
         // Default key bindings for this view
-        KeyBindings.Add (Key.CursorUp, Command.LineUp);
-        KeyBindings.Add (Key.CursorDown, Command.LineDown);
+        KeyBindings.Add (Key.CursorUp, Command.Up);
+        KeyBindings.Add (Key.CursorDown, Command.Down);
         KeyBindings.Add (Key.CursorLeft, Command.Left);
         KeyBindings.Add (Key.CursorRight, Command.Right);
         KeyBindings.Add (Key.Esc, Command.Cancel);
-        KeyBindings.Add (Key.Enter, Command.Accept);
     }
 
     private void AddKeyBindingsHotKey (MenuBarItem? menuBarItem)
@@ -209,7 +208,7 @@ internal sealed class Menu : View
 
         foreach (MenuItem menuItem in menuItems)
         {
-            KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, menuItem);
+            KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, menuItem);
 
             if (menuItem.HotKey != Key.Empty)
             {
@@ -330,7 +329,7 @@ internal sealed class Menu : View
     }
 
     /// <inheritdoc/>
-    public override void OnVisibleChanged ()
+    protected override void OnVisibleChanged ()
     {
         base.OnVisibleChanged ();
 
@@ -386,7 +385,7 @@ internal sealed class Menu : View
             return GetFocusColor ();
         }
 
-        return !item.IsEnabled () ? ColorScheme.Disabled : GetNormalColor ();
+        return !item.IsEnabled () ? ColorScheme!.Disabled : GetNormalColor ();
     }
 
     public override void OnDrawContent (Rectangle viewport)
@@ -518,7 +517,7 @@ internal sealed class Menu : View
 
                 if (!item.IsEnabled ())
                 {
-                    DrawHotString (textToDraw, ColorScheme.Disabled, ColorScheme.Disabled);
+                    DrawHotString (textToDraw, ColorScheme!.Disabled, ColorScheme.Disabled);
                 }
                 else if (i == 0 && _host.UseSubMenusSingleFrame && item.Parent!.Parent is { })
                 {
@@ -533,7 +532,7 @@ internal sealed class Menu : View
                     tf.Draw (
                              ViewportToScreen (new Rectangle (1, i, Frame.Width - 3, 1)),
                              i == _currentChild ? GetFocusColor () : GetNormalColor (),
-                             i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal,
+                             i == _currentChild ? ColorScheme!.HotFocus : ColorScheme!.HotNormal,
                              SuperView?.ViewportToScreen (SuperView.Viewport) ?? Rectangle.Empty
                             );
                 }
@@ -541,7 +540,7 @@ internal sealed class Menu : View
                 {
                     DrawHotString (
                                    textToDraw,
-                                   i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal,
+                                   i == _currentChild ? ColorScheme!.HotFocus : ColorScheme!.HotNormal,
                                    i == _currentChild ? GetFocusColor () : GetNormalColor ()
                                   );
                 }
@@ -607,6 +606,7 @@ internal sealed class Menu : View
 
         Application.UngrabMouse ();
         _host.CloseAllMenus ();
+        Application.Driver!.ClearContents ();
         Application.Refresh ();
 
         _host.Run (action);
@@ -952,10 +952,10 @@ internal sealed class Menu : View
     {
         RemoveKeyBindingsHotKey (_barItems);
 
-        if (Application.Current is { })
+        if (Application.Top is { })
         {
-            Application.Current.DrawContentComplete -= Current_DrawContentComplete;
-            Application.Current.SizeChanging -= Current_TerminalResized;
+            Application.Top.DrawContentComplete -= Current_DrawContentComplete;
+            Application.Top.SizeChanging -= Current_TerminalResized;
         }
 
         Application.MouseEvent -= Application_RootMouseEvent;

+ 70 - 32
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -119,14 +119,17 @@ public class MenuBar : View, IDesignable
 
         AddCommand (
                     Command.Accept,
-                    () =>
+                    (ctx) =>
                     {
-                        ProcessMenu (_selected, Menus [_selected]);
+                        if (Menus.Length > 0)
+                        {
+                            ProcessMenu (_selected, Menus [_selected]);
+                        }
 
-                        return true;
+                        return RaiseAccepting (ctx);
                     }
                    );
-        AddCommand (Command.ToggleExpandCollapse, ctx =>
+        AddCommand (Command.Toggle, ctx =>
                                                   {
                                                       CloseOtherOpenedMenuBar ();
 
@@ -134,7 +137,12 @@ public class MenuBar : View, IDesignable
                                                   });
         AddCommand (Command.Select, ctx =>
                                     {
-                                        var res =  Run ((ctx.KeyBinding?.Context as MenuItem)?.Action!);
+                                        if (ctx.Data is MouseEvent)
+                                        {
+                                            // HACK: Work around the fact that View.MouseClick always invokes Select
+                                            return false;
+                                        }
+                                        var res = Run ((ctx.KeyBinding?.Context as MenuItem)?.Action!);
                                         CloseAllMenus ();
 
                                         return res;
@@ -145,9 +153,8 @@ public class MenuBar : View, IDesignable
         KeyBindings.Add (Key.CursorRight, Command.Right);
         KeyBindings.Add (Key.Esc, Command.Cancel);
         KeyBindings.Add (Key.CursorDown, Command.Accept);
-        KeyBindings.Add (Key.Enter, Command.Accept);
 
-        KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
+        KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
         KeyBindings.Add (Key, keyBinding);
 
         // TODO: Why do we have two keybindings for opening the menu? Ctrl-Space and Key?
@@ -190,10 +197,10 @@ public class MenuBar : View, IDesignable
                 if (menuBarItem.HotKey != Key.Empty)
                 {
                     KeyBindings.Remove (menuBarItem.HotKey!);
-                    KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.Focused, menuBarItem);
+                    KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.Focused, menuBarItem);
                     KeyBindings.Add (menuBarItem.HotKey!, keyBinding);
                     KeyBindings.Remove (menuBarItem.HotKey!.WithAlt);
-                    keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, menuBarItem);
+                    keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, menuBarItem);
                     KeyBindings.Add (menuBarItem.HotKey.WithAlt, keyBinding);
                 }
 
@@ -306,7 +313,7 @@ public class MenuBar : View, IDesignable
 
             if (i == _selected && IsMenuOpen)
             {
-                hotColor = i == _selected ? ColorScheme.HotFocus : GetHotNormalColor ();
+                hotColor = i == _selected ? ColorScheme!.HotFocus : GetHotNormalColor ();
                 normalColor = i == _selected ? GetFocusColor () : GetNormalColor ();
             }
             else
@@ -351,7 +358,7 @@ public class MenuBar : View, IDesignable
     /// <summary>Virtual method that will invoke the <see cref="MenuOpened"/> event if it's defined.</summary>
     public virtual void OnMenuOpened ()
     {
-        MenuItem? mi;
+        MenuItem? mi = null;
         MenuBarItem? parent;
 
         if (OpenCurrentMenu?.BarItems?.Children is { Length: > 0 }
@@ -368,7 +375,11 @@ public class MenuBar : View, IDesignable
         else
         {
             parent = _openMenu?.BarItems;
-            mi = parent?.Children?.Length > 0 ? parent.Children [_openMenu!._currentChild] : null;
+
+            if (OpenCurrentMenu?._currentChild > -1)
+            {
+                mi = parent?.Children?.Length > 0 ? parent.Children [_openMenu!._currentChild] : null;
+            }
         }
 
         MenuOpened?.Invoke (this, new (parent, mi));
@@ -402,7 +413,7 @@ public class MenuBar : View, IDesignable
         _selected = 0;
         SetNeedsDisplay ();
 
-        _previousFocused = (SuperView is null ? Application.Current?.Focused : SuperView.Focused)!;
+        _previousFocused = (SuperView is null ? Application.Top?.Focused : SuperView.Focused)!;
         OpenMenu (_selected);
 
         if (!SelectEnabledItem (
@@ -463,7 +474,7 @@ public class MenuBar : View, IDesignable
 
         if (_openMenu is null)
         {
-            _previousFocused = (SuperView is null ? Application.Current?.Focused ?? null : SuperView.Focused)!;
+            _previousFocused = (SuperView is null ? Application.Top?.Focused ?? null : SuperView.Focused)!;
         }
 
         OpenMenu (idx, sIdx, subMenu);
@@ -540,10 +551,10 @@ public class MenuBar : View, IDesignable
 
     private void CloseOtherOpenedMenuBar ()
     {
-        if (Application.Current is { })
+        if (Application.Top is { })
         {
             // Close others menu bar opened
-            Menu? menu = Application.Current.Subviews.FirstOrDefault (v => v is Menu m && m.Host != this && m.Host.IsMenuOpen) as Menu;
+            Menu? menu = Application.Top.Subviews.FirstOrDefault (v => v is Menu m && m.Host != this && m.Host.IsMenuOpen) as Menu;
             menu?.Host.CleanUp ();
         }
     }
@@ -579,7 +590,7 @@ public class MenuBar : View, IDesignable
             case false:
                 if (_openMenu is { })
                 {
-                    Application.Current?.Remove (_openMenu);
+                    Application.Top?.Remove (_openMenu);
                 }
 
                 SetNeedsDisplay ();
@@ -589,6 +600,10 @@ public class MenuBar : View, IDesignable
                     _previousFocused.SetFocus ();
                 }
 
+                if (Application.MouseGrabView == _openMenu)
+                {
+                    Application.UngrabMouse();
+                }
                 _openMenu?.Dispose ();
                 _openMenu = null;
 
@@ -614,7 +629,11 @@ public class MenuBar : View, IDesignable
 
                     if (OpenCurrentMenu is { })
                     {
-                        Application.Current?.Remove (OpenCurrentMenu);
+                        Application.Top?.Remove (OpenCurrentMenu);
+                        if (Application.MouseGrabView == OpenCurrentMenu)
+                        {
+                            Application.UngrabMouse ();
+                        }
                         OpenCurrentMenu.Dispose ();
                         OpenCurrentMenu = null;
                     }
@@ -662,7 +681,7 @@ public class MenuBar : View, IDesignable
         }
 
         Rectangle superViewFrame = SuperView?.Frame ?? Application.Screen;
-        View? sv = SuperView ?? Application.Current;
+        View? sv = SuperView ?? Application.Top;
 
         if (sv is null)
         {
@@ -789,7 +808,7 @@ public class MenuBar : View, IDesignable
         {
             case null:
                 // Open a submenu below a MenuBar
-                _lastFocused ??= SuperView is null ? Application.Current?.MostFocused : SuperView.MostFocused;
+                _lastFocused ??= SuperView is null ? Application.Top?.MostFocused : SuperView.MostFocused;
 
                 if (_openSubMenu is { } && !CloseMenu (false, true))
                 {
@@ -798,7 +817,11 @@ public class MenuBar : View, IDesignable
 
                 if (_openMenu is { })
                 {
-                    Application.Current?.Remove (_openMenu);
+                    Application.Top?.Remove (_openMenu);
+                    if (Application.MouseGrabView == _openMenu)
+                    {
+                        Application.UngrabMouse ();
+                    }
                     _openMenu.Dispose ();
                     _openMenu = null;
                 }
@@ -818,7 +841,7 @@ public class MenuBar : View, IDesignable
                     locationOffset = GetScreenOffset ();
                 }
 
-                if (SuperView is { } && SuperView != Application.Current)
+                if (SuperView is { } && SuperView != Application.Top)
                 {
                     locationOffset.X += SuperView.Border.Thickness.Left;
                     locationOffset.Y += SuperView.Border.Thickness.Top;
@@ -835,9 +858,9 @@ public class MenuBar : View, IDesignable
                 OpenCurrentMenu = _openMenu;
                 OpenCurrentMenu._previousSubFocused = _openMenu;
 
-                if (Application.Current is { })
+                if (Application.Top is { })
                 {
-                    Application.Current.Add (_openMenu);
+                    Application.Top.Add (_openMenu);
                 }
                 else
                 {
@@ -902,7 +925,7 @@ public class MenuBar : View, IDesignable
 
                     OpenCurrentMenu._previousSubFocused = last._previousSubFocused;
                     _openSubMenu.Add (OpenCurrentMenu);
-                    Application.Current?.Add (OpenCurrentMenu);
+                    Application.Top?.Add (OpenCurrentMenu);
 
                     if (!OpenCurrentMenu.IsInitialized)
                     {
@@ -985,7 +1008,11 @@ public class MenuBar : View, IDesignable
         {
             foreach (Menu item in _openSubMenu)
             {
-                Application.Current!.Remove (item);
+                Application.Top!.Remove (item);
+                if (Application.MouseGrabView == item)
+                {
+                    Application.UngrabMouse ();
+                }
                 item.Dispose ();
             }
         }
@@ -1150,11 +1177,11 @@ public class MenuBar : View, IDesignable
         SetNeedsDisplay ();
     }
 
-    private void ProcessMenu (int i, MenuBarItem mi)
+    private bool ProcessMenu (int i, MenuBarItem mi)
     {
         if (_selected < 0 && IsMenuOpen)
         {
-            return;
+            return false;
         }
 
         if (mi.IsTopLevel)
@@ -1162,6 +1189,10 @@ public class MenuBar : View, IDesignable
             Point screen = ViewportToScreen (new Point (0, i));
             var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = mi };
             menu.Run (mi.Action);
+            if (Application.MouseGrabView == menu)
+            {
+                Application.UngrabMouse ();
+            }
             menu.Dispose ();
         }
         else
@@ -1177,16 +1208,18 @@ public class MenuBar : View, IDesignable
                                    )
                 && !CloseMenu ())
             {
-                return;
+                return true;
             }
 
             if (!OpenCurrentMenu.CheckSubMenu ())
             {
-                return;
+                return true;
             }
         }
 
         SetNeedsDisplay ();
+
+        return true;
     }
 
     private void RemoveSubMenu (int index, bool ignoreUseSubMenusSingleFrame = false)
@@ -1224,7 +1257,7 @@ public class MenuBar : View, IDesignable
             if (_openSubMenu is { })
             {
                 menu = _openSubMenu [i];
-                Application.Current!.Remove (menu);
+                Application.Top!.Remove (menu);
                 _openSubMenu.Remove (menu);
 
                 if (Application.MouseGrabView == menu)
@@ -1272,7 +1305,7 @@ public class MenuBar : View, IDesignable
             }
 
             KeyBindings.Remove (_key);
-            KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
+            KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
             KeyBindings.Add (value, keyBinding);
             _key = value;
         }
@@ -1405,6 +1438,11 @@ public class MenuBar : View, IDesignable
                             Point screen = ViewportToScreen (new Point (0, i));
                             var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = Menus [i] };
                             menu.Run (Menus [i].Action);
+                            if (Application.MouseGrabView == menu)
+                            {
+                                Application.UngrabMouse ();
+                            }
+
                             menu.Dispose ();
                         }
                         else if (!IsMenuOpen)

+ 1 - 1
Terminal.Gui/Views/Menu/MenuItem.cs

@@ -325,7 +325,7 @@ public class MenuItem
             if (index > -1)
             {
                 _menuBar.KeyBindings.Remove (HotKey!.WithAlt);
-                KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, this);
+                KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, this);
                 _menuBar.KeyBindings.Add (HotKey.WithAlt, keyBinding);
             }
         }

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

@@ -1,4 +1,5 @@
 using System;
+using System.ComponentModel;
 using System.Reflection;
 
 namespace Terminal.Gui;
@@ -16,13 +17,30 @@ public class Menuv2 : Bar
         Orientation = Orientation.Vertical;
         Width = Dim.Auto ();
         Height = Dim.Auto (DimAutoStyle.Content, 1);
-        ColorScheme = Colors.ColorSchemes ["Menu"];
         Initialized += Menuv2_Initialized;
+        VisibleChanged += OnVisibleChanged;
+    }
+
+    private void OnVisibleChanged (object sender, EventArgs e)
+    {
+        if (Visible)
+        {
+            //Application.GrabMouse(this);
+        }
+        else
+        {
+            if (Application.MouseGrabView == this)
+            {
+                //Application.UngrabMouse ();
+            }
+        }
     }
 
     private void Menuv2_Initialized (object sender, EventArgs e)
     {
         Border.Thickness = new Thickness (1, 1, 1, 1);
+        Border.LineStyle = LineStyle.Single;
+        ColorScheme = Colors.ColorSchemes ["Menu"];
     }
 
     // Menuv2 arranges the items horizontally.
@@ -51,12 +69,30 @@ public class Menuv2 : Bar
         if (view is Shortcut shortcut)
         {
             shortcut.CanFocus = true;
-            shortcut.KeyBindingScope = KeyBindingScope.Application;
             shortcut.Orientation = Orientation.Vertical;
+            shortcut.HighlightStyle |= HighlightStyle.Hover;
 
             // TODO: not happy about using AlignmentModes for this. Too implied.
             // TODO: instead, add a property (a style enum?) to Shortcut to control this
             //shortcut.AlignmentModes = AlignmentModes.EndToStart;
+
+            shortcut.Accepting += ShortcutOnAccept;
+
+            void ShortcutOnAccept (object sender, CommandEventArgs e)
+            {
+                if (Arrangement.HasFlag (ViewArrangement.Overlapped) && Visible)
+                {
+                    Visible = false;
+                    e.Cancel = true;
+
+                    return;
+                }
+
+                //if (!e.Handled)
+                //{
+                //    RaiseAcceptEvent ();
+                //}
+            }
         }
 
         return view;

+ 18 - 16
Terminal.Gui/Views/MessageBox.cs

@@ -338,6 +338,7 @@ public static class MessageBox
         // Create button array for Dialog
         var count = 0;
         List<Button> buttonList = new ();
+        Clicked = -1;
 
         if (buttons is { })
         {
@@ -351,11 +352,27 @@ public static class MessageBox
                 var b = new Button
                 {
                     Text = s,
+                    Data = count,
                 };
 
                 if (count == defaultButton)
                 {
                     b.IsDefault = true;
+                    b.Accepting += (s, e) =>
+                                   {
+                                       // TODO: With https://github.com/gui-cs/Terminal.Gui/issues/3778 we can simplify this
+                                       if (e.Context.Data is Button button)
+                                       {
+                                           Clicked = (int)button.Data!;
+                                       } 
+                                       else if (e.Context.KeyBinding?.BoundView is Button btn)
+                                       {
+                                           Clicked = (int)btn.Data!;
+                                       }
+
+                                       e.Cancel = true;
+                                       Application.RequestStop ();
+                                   };
                 }
 
                 buttonList.Add (b);
@@ -373,7 +390,7 @@ public static class MessageBox
         };
 
         d.Width = Dim.Auto (DimAutoStyle.Auto,
-                            minimumContentDim: Dim.Func (() => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * (DefaultMinimumWidth / 100f) )),
+                            minimumContentDim: Dim.Func (() => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * (DefaultMinimumWidth / 100f))),
                             maximumContentDim: Dim.Func (() => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * 0.9f)));
 
         d.Height = Dim.Auto (DimAutoStyle.Auto,
@@ -400,21 +417,6 @@ public static class MessageBox
         d.TextFormatter.WordWrap = wrapMessage;
         d.TextFormatter.MultiLine = !wrapMessage;
 
-        // Setup actions
-        Clicked = -1;
-
-        for (var n = 0; n < buttonList.Count; n++)
-        {
-            int buttonId = n;
-            Button b = buttonList [n];
-
-            b.Accept += (s, e) =>
-                         {
-                             Clicked = buttonId;
-                             Application.RequestStop ();
-                         };
-        }
-
         // Run the modal; do not shut down the mainloop driver when done
         Application.Run (d);
         d.Dispose ();

+ 14 - 6
Terminal.Gui/Views/NumericUpDown.cs

@@ -56,7 +56,7 @@ public class NumericUpDown<T> : View where T : notnull
             Title = $"{Glyphs.DownArrow}",
             WantContinuousButtonPressed = true,
             CanFocus = false,
-            ShadowStyle = ShadowStyle.None
+            ShadowStyle = ShadowStyle.None,
         };
 
         _number = new ()
@@ -81,13 +81,13 @@ public class NumericUpDown<T> : View where T : notnull
             Title = $"{Glyphs.UpArrow}",
             WantContinuousButtonPressed = true,
             CanFocus = false,
-            ShadowStyle = ShadowStyle.None
+            ShadowStyle = ShadowStyle.None,
         };
 
         CanFocus = true;
 
-        _down.Accept += OnDownButtonOnAccept;
-        _up.Accept += OnUpButtonOnAccept;
+        _down.Accepting += OnDownButtonOnAccept;
+        _up.Accepting += OnUpButtonOnAccept;
 
         Add (_down, _number, _up);
 
@@ -133,9 +133,17 @@ public class NumericUpDown<T> : View where T : notnull
 
         return;
 
-        void OnDownButtonOnAccept (object? s, HandledEventArgs e) { InvokeCommand (Command.ScrollDown); }
+        void OnDownButtonOnAccept (object? s, CommandEventArgs e)
+        {
+            InvokeCommand (Command.ScrollDown);
+            e.Cancel = true;
+        }
 
-        void OnUpButtonOnAccept (object? s, HandledEventArgs e) { InvokeCommand (Command.ScrollUp); }
+        void OnUpButtonOnAccept (object? s, CommandEventArgs e)
+        {
+            InvokeCommand (Command.ScrollUp);
+            e.Cancel = true;
+        }
     }
 
     private T _value = default!;

+ 6 - 4
Terminal.Gui/Views/ProgressBar.cs

@@ -71,6 +71,7 @@ public class ProgressBar : View, IDesignable
         {
             _fraction = Math.Min (value, 1);
             _isActivity = false;
+            SetNeedsDisplay ();
         }
     }
 
@@ -108,6 +109,7 @@ public class ProgressBar : View, IDesignable
 
                     break;
             }
+            SetNeedsDisplay ();
         }
     }
 
@@ -263,10 +265,10 @@ public class ProgressBar : View, IDesignable
 
     private void ProgressBar_Initialized (object sender, EventArgs e)
     {
-        ColorScheme = new ColorScheme (ColorScheme ?? SuperView?.ColorScheme ?? Colors.ColorSchemes ["Base"])
-        {
-            HotNormal = new Attribute (Color.BrightGreen, Color.Gray)
-        };
+        //ColorScheme = new ColorScheme (ColorScheme ?? SuperView?.ColorScheme ?? Colors.ColorSchemes ["Base"])
+        //{
+        //    HotNormal = new Attribute (Color.BrightGreen, Color.Gray)
+        //};
     }
 
     private void SetInitialProperties ()

Някои файлове не бяха показани, защото твърде много файлове са промени