Browse Source

Merge branch 'v2_release' of tig:gui-cs/Terminal.Gui into v2_release

Tig 9 tháng trước cách đây
mục cha
commit
0085e25497
100 tập tin đã thay đổi với 5032 bổ sung3243 xóa
  1. 4 1
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 7 1
      .github/workflows/dotnet-core.yml
  3. 3 3
      .github/workflows/publish.yml
  4. 1 0
      .gitignore
  5. 2 2
      CommunityToolkitExample/LoginView.cs
  6. 1 1
      Example/Example.cs
  7. 1 1
      NativeAot/NativeAot.csproj
  8. 9 0
      NativeAot/PackTerminalGui.ps1
  9. 11 0
      NativeAot/PackTerminalGui.sh
  10. 1 1
      NativeAot/Program.cs
  11. 1 1
      NativeAot/Properties/launchSettings.json
  12. 3 3
      README.md
  13. 2 2
      ReactiveExample/LoginView.cs
  14. 9 0
      SelfContained/PackTerminalGui.ps1
  15. 11 0
      SelfContained/PackTerminalGui.sh
  16. 1 1
      SelfContained/Program.cs
  17. 1 1
      SelfContained/SelfContained.csproj
  18. 1 1
      Terminal.Gui/Application/Application.Driver.cs
  19. 25 3
      Terminal.Gui/Application/Application.Initialization.cs
  20. 61 56
      Terminal.Gui/Application/Application.Keyboard.cs
  21. 160 109
      Terminal.Gui/Application/Application.Mouse.cs
  22. 51 300
      Terminal.Gui/Application/Application.Run.cs
  23. 0 6
      Terminal.Gui/Application/Application.Screen.cs
  24. 1 43
      Terminal.Gui/Application/Application.Toplevel.cs
  25. 2 4
      Terminal.Gui/Application/Application.cs
  26. 3 1
      Terminal.Gui/Application/ApplicationNavigation.cs
  27. 0 444
      Terminal.Gui/Application/ApplicationOverlapped.cs
  28. 1 1
      Terminal.Gui/Configuration/AppScope.cs
  29. 18 5
      Terminal.Gui/Configuration/ColorJsonConverter.cs
  30. 14 5
      Terminal.Gui/Configuration/ConfigurationManager.cs
  31. 24 17
      Terminal.Gui/Configuration/RuneJsonConverter.cs
  32. 25 8
      Terminal.Gui/Configuration/SettingsScope.cs
  33. 3 1
      Terminal.Gui/Configuration/SourceGenerationContext.cs
  34. 1 1
      Terminal.Gui/Configuration/ThemeManager.cs
  35. 8 1
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  36. 249 82
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  37. 3 3
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  38. 2 2
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  39. 9 7
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  40. 55 53
      Terminal.Gui/Drawing/Alignment.cs
  41. 8 14
      Terminal.Gui/Drawing/AlignmentModes.cs
  42. 6 6
      Terminal.Gui/Drawing/Attribute.cs
  43. 152 6
      Terminal.Gui/Drawing/Cell.cs
  44. 8 8
      Terminal.Gui/Drawing/CellEventArgs.cs
  45. 47 44
      Terminal.Gui/Drawing/Color.ColorExtensions.cs
  46. 2 2
      Terminal.Gui/Drawing/Color.ColorName.cs
  47. 180 177
      Terminal.Gui/Drawing/Color.Formatting.cs
  48. 3 3
      Terminal.Gui/Drawing/Color.Operators.cs
  49. 40 40
      Terminal.Gui/Drawing/Color.cs
  50. 43 55
      Terminal.Gui/Drawing/ColorScheme.cs
  51. 30 27
      Terminal.Gui/Drawing/ColorStrings.cs
  52. 28 5
      Terminal.Gui/Drawing/Glyphs.cs
  53. 1 1
      Terminal.Gui/Drawing/LineCanvas.cs
  54. 1 1
      Terminal.Gui/Drawing/StraightLine.cs
  55. 2 2
      Terminal.Gui/FileServices/DefaultFileOperations.cs
  56. 191 147
      Terminal.Gui/Input/Command.cs
  57. 11 1
      Terminal.Gui/Input/CommandContext.cs
  58. 15 0
      Terminal.Gui/Input/CommandEventArgs.cs
  59. 1 1
      Terminal.Gui/Input/GrabMouseEventArgs.cs
  60. 93 47
      Terminal.Gui/Input/Key.cs
  61. 3 0
      Terminal.Gui/Input/KeyBinding.cs
  62. 30 21
      Terminal.Gui/Input/KeyBindingScope.cs
  63. 17 5
      Terminal.Gui/Input/KeyBindings.cs
  64. 72 0
      Terminal.Gui/Resources/Strings.Designer.cs
  65. 15 12
      Terminal.Gui/Resources/Strings.fr-FR.resx
  66. 58 55
      Terminal.Gui/Resources/Strings.ja-JP.resx
  67. 16 13
      Terminal.Gui/Resources/Strings.pt-PT.resx
  68. 273 249
      Terminal.Gui/Resources/Strings.resx
  69. 9 6
      Terminal.Gui/Resources/Strings.zh-Hans.resx
  70. 324 38
      Terminal.Gui/Resources/config.json
  71. 2 1
      Terminal.Gui/Terminal.Gui.csproj
  72. 2 2
      Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs
  73. 35 35
      Terminal.Gui/View/Adornment/Adornment.cs
  74. 915 173
      Terminal.Gui/View/Adornment/Border.cs
  75. 15 6
      Terminal.Gui/View/Adornment/Margin.cs
  76. 1 1
      Terminal.Gui/View/Adornment/Padding.cs
  77. 1 1
      Terminal.Gui/View/Adornment/ShadowView.cs
  78. 9 8
      Terminal.Gui/View/HighlightStyle.cs
  79. 42 0
      Terminal.Gui/View/Layout/DimAuto.cs
  80. 1 1
      Terminal.Gui/View/Layout/DimAutoStyle.cs
  81. 5 7
      Terminal.Gui/View/Navigation/TabBehavior.cs
  82. 29 18
      Terminal.Gui/View/View.Adornments.cs
  83. 2 3
      Terminal.Gui/View/View.Arrangement.cs
  84. 353 0
      Terminal.Gui/View/View.Command.cs
  85. 12 0
      Terminal.Gui/View/View.Content.cs
  86. 2 3
      Terminal.Gui/View/View.Diagnostics.cs
  87. 112 64
      Terminal.Gui/View/View.Drawing.cs
  88. 11 6
      Terminal.Gui/View/View.Hierarchy.cs
  89. 35 144
      Terminal.Gui/View/View.Keyboard.cs
  90. 65 101
      Terminal.Gui/View/View.Layout.cs
  91. 367 231
      Terminal.Gui/View/View.Mouse.cs
  92. 152 57
      Terminal.Gui/View/View.Navigation.cs
  93. 87 74
      Terminal.Gui/View/View.cs
  94. 15 12
      Terminal.Gui/View/ViewArrangement.cs
  95. 14 23
      Terminal.Gui/View/ViewportSettings.cs
  96. 2 2
      Terminal.Gui/Views/AutocompleteFilepathContext.cs
  97. 56 9
      Terminal.Gui/Views/Bar.cs
  98. 102 39
      Terminal.Gui/Views/Button.cs
  99. 123 63
      Terminal.Gui/Views/CheckBox.cs
  100. 2 2
      Terminal.Gui/Views/ColorBar.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.

+ 7 - 1
.github/workflows/dotnet-core.yml

@@ -90,7 +90,13 @@ jobs:
         dotnet-version: 8.x
         dotnet-quality: 'ga'
 
-    - name: Build Release
+    - name: Build Release Terminal.Gui
+      run: dotnet build Terminal.Gui/Terminal.Gui.csproj --configuration Release
+
+    - name: Pack Release Terminal.Gui
+      run: dotnet pack Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ./local_packages
+
+    - name: Build Release Solution
       run: dotnet build --configuration Release
 
 

+ 3 - 3
.github/workflows/publish.yml

@@ -43,11 +43,11 @@ jobs:
     - name: Build Release
       run: |
         dotnet-gitversion /updateprojectfiles
-        dotnet build --no-incremental --nologo --force --configuration Release
-        dotnet test --configuration Release
+        dotnet build Terminal.Gui/Terminal.Gui.csproj --no-incremental --nologo --force --configuration Release
+        dotnet test Terminal.Gui/Terminal.Gui.csproj --configuration Release
 
     - name: Pack
-      run: dotnet pack -c Release --include-symbols -p:Version='${{ steps.gitversion.outputs.SemVer }}' 
+      run: dotnet pack Terminal.Gui/Terminal.Gui.csproj -c Release --include-symbols -p:Version='${{ steps.gitversion.outputs.SemVer }}' 
 
     # - name: Test to generate Code Coverage Report
     #   run: |

+ 1 - 0
.gitignore

@@ -61,3 +61,4 @@ demo.*
 *.tui/
 
 *.dotCover
+/local_packages/

+ 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/NativeAot.csproj

@@ -15,7 +15,7 @@
   </ItemGroup>
 
   <ItemGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
-    <PackageReference Include="Terminal.Gui" Version="[2.0.0-v2-develop.2189,3)" />
+    <PackageReference Include="Terminal.Gui" Version="2.0.0" />
     <TrimmerRootAssembly Include="Terminal.Gui" />
   </ItemGroup>
 

+ 9 - 0
NativeAot/PackTerminalGui.ps1

@@ -0,0 +1,9 @@
+# Step 1: Build and pack Terminal.Gui
+dotnet build ../Terminal.Gui/Terminal.Gui.csproj --configuration Release
+dotnet pack ../Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ../local_packages
+
+# Step 2: Restore NativeAot with the new package
+dotnet restore ./NativeAot.csproj --source ./local_packages
+
+# Step 3: Build NativeAot
+dotnet build ./NativeAot.csproj --configuration Release

+ 11 - 0
NativeAot/PackTerminalGui.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# Step 1: Build and pack Terminal.Gui
+dotnet build ../Terminal.Gui/Terminal.Gui.csproj --configuration Release
+dotnet pack ../Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ../local_packages
+
+# Step 2: Restore NativeAot with the new package
+dotnet restore ./NativeAot.csproj --source ./local_packages
+
+# Step 3: Build NativeAot
+dotnet build ./NativeAot.csproj --configuration Release

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

+ 9 - 0
SelfContained/PackTerminalGui.ps1

@@ -0,0 +1,9 @@
+# Step 1: Build and pack Terminal.Gui
+dotnet build ../Terminal.Gui/Terminal.Gui.csproj --configuration Release
+dotnet pack ../Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ../local_packages
+
+# Step 2: Restore SelfContained with the new package
+dotnet restore ./SelfContained.csproj --source ./local_packages
+
+# Step 3: Build SelfContained
+dotnet build ./SelfContained.csproj --configuration Release

+ 11 - 0
SelfContained/PackTerminalGui.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# Step 1: Build and pack Terminal.Gui
+dotnet build ../Terminal.Gui/Terminal.Gui.csproj --configuration Release
+dotnet pack ../Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ../local_packages
+
+# Step 2: Restore SelfContained with the new package
+dotnet restore ./SelfContained.csproj --source ./local_packages
+
+# Step 3: Build SelfContained
+dotnet build ./SelfContained.csproj --configuration Release

+ 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
SelfContained/SelfContained.csproj

@@ -18,7 +18,7 @@
   </ItemGroup>
 
   <ItemGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
-    <PackageReference Include="Terminal.Gui" Version="[2.0.0-pre.1788,3)" />
+    <PackageReference Include="Terminal.Gui" Version="2.0.0" />
     <TrimmerRootAssembly Include="Terminal.Gui" />
   </ItemGroup>
 

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

+ 249 - 82
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -39,7 +39,7 @@ internal class CursesDriver : ConsoleDriver
         }
     }
 
-    public override bool SupportsTrueColor => false;
+    public override bool SupportsTrueColor => true;
 
     /// <inheritdoc/>
     public override bool EnsureCursorVisibility () { return false; }
@@ -200,8 +200,12 @@ internal class CursesDriver : ConsoleDriver
         if (!RunningUnitTests)
         {
             Platform.Suspend ();
-            Curses.Window.Standard.redrawwin ();
-            Curses.refresh ();
+
+            if (Force16Colors)
+            {
+                Curses.Window.Standard.redrawwin ();
+                Curses.refresh ();
+            }
         }
 
         StartReportingMouseMoves ();
@@ -214,74 +218,232 @@ internal class CursesDriver : ConsoleDriver
         if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows)
         {
             Curses.move (Row, Col);
-            Curses.raw ();
-            Curses.noecho ();
-            Curses.refresh ();
+
+            if (Force16Colors)
+            {
+                Curses.raw ();
+                Curses.noecho ();
+                Curses.refresh ();
+            }
         }
     }
 
-
     public override void UpdateScreen ()
     {
-        for (var row = 0; row < Rows; row++)
+        if (Force16Colors)
         {
-            if (!_dirtyLines [row])
+            for (var row = 0; row < Rows; row++)
             {
-                continue;
-            }
-
-            _dirtyLines [row] = false;
-
-            for (var col = 0; col < Cols; col++)
-            {
-                if (Contents [row, col].IsDirty == false)
+                if (!_dirtyLines [row])
                 {
                     continue;
                 }
 
-                if (RunningUnitTests)
+                _dirtyLines [row] = false;
+
+                for (var col = 0; col < Cols; col++)
                 {
-                    // In unit tests, we don't want to actually write to the screen.
-                    continue;
-                }
+                    if (Contents [row, col].IsDirty == false)
+                    {
+                        continue;
+                    }
 
-                Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor);
+                    if (RunningUnitTests)
+                    {
+                        // In unit tests, we don't want to actually write to the screen.
+                        continue;
+                    }
 
-                Rune rune = Contents [row, col].Rune;
+                    Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor);
 
-                if (rune.IsBmp)
-                {
-                    // BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell.
-                    if (rune.GetColumns () < 2)
+                    Rune rune = Contents [row, col].Rune;
+
+                    if (rune.IsBmp)
                     {
-                        Curses.mvaddch (row, col, rune.Value);
+                        // BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell.
+                        if (rune.GetColumns () < 2)
+                        {
+                            Curses.mvaddch (row, col, rune.Value);
+                        }
+                        else /*if (col + 1 < Cols)*/
+                        {
+                            Curses.mvaddwstr (row, col, rune.ToString ());
+                        }
                     }
-                    else /*if (col + 1 < Cols)*/
+                    else
                     {
                         Curses.mvaddwstr (row, col, rune.ToString ());
+
+                        if (rune.GetColumns () > 1 && col + 1 < Cols)
+                        {
+                            // TODO: This is a hack to deal with non-BMP and wide characters.
+                            //col++;
+                            Curses.mvaddch (row, ++col, '*');
+                        }
                     }
                 }
-                else
+            }
+
+            if (!RunningUnitTests)
+            {
+                Curses.move (Row, Col);
+                _window.wrefresh ();
+            }
+        }
+        else
+        {
+            if (RunningUnitTests
+                || Console.WindowHeight < 1
+                || Contents.Length != Rows * Cols
+                || Rows != Console.WindowHeight)
+            {
+                return;
+            }
+
+            var top = 0;
+            var left = 0;
+            int rows = Rows;
+            int cols = Cols;
+            var output = new StringBuilder ();
+            Attribute? redrawAttr = null;
+            int lastCol = -1;
+
+            CursorVisibility? savedVisibility = _currentCursorVisibility;
+            SetCursorVisibility (CursorVisibility.Invisible);
+
+            for (int row = top; row < rows; row++)
+            {
+                if (Console.WindowHeight < 1)
+                {
+                    return;
+                }
+
+                if (!_dirtyLines [row])
+                {
+                    continue;
+                }
+
+                if (!SetCursorPosition (0, row))
                 {
-                    Curses.mvaddwstr (row, col, rune.ToString ());
+                    return;
+                }
+
+                _dirtyLines [row] = false;
+                output.Clear ();
 
-                    if (rune.GetColumns () > 1 && col + 1 < Cols)
+                for (int col = left; col < cols; col++)
+                {
+                    lastCol = -1;
+                    var outputWidth = 0;
+
+                    for (; col < cols; col++)
                     {
-                        // TODO: This is a hack to deal with non-BMP and wide characters.
-                        //col++;
-                        Curses.mvaddch (row, ++col, '*');
+                        if (!Contents [row, col].IsDirty)
+                        {
+                            if (output.Length > 0)
+                            {
+                                WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                            }
+                            else if (lastCol == -1)
+                            {
+                                lastCol = col;
+                            }
+
+                            if (lastCol + 1 < cols)
+                            {
+                                lastCol++;
+                            }
+
+                            continue;
+                        }
+
+                        if (lastCol == -1)
+                        {
+                            lastCol = col;
+                        }
+
+                        Attribute attr = Contents [row, col].Attribute.Value;
+
+                        // Performance: Only send the escape sequence if the attribute has changed.
+                        if (attr != redrawAttr)
+                        {
+                            redrawAttr = attr;
+
+                            output.Append (
+                                           EscSeqUtils.CSI_SetForegroundColorRGB (
+                                                                                  attr.Foreground.R,
+                                                                                  attr.Foreground.G,
+                                                                                  attr.Foreground.B
+                                                                                 )
+                                          );
+
+                            output.Append (
+                                           EscSeqUtils.CSI_SetBackgroundColorRGB (
+                                                                                  attr.Background.R,
+                                                                                  attr.Background.G,
+                                                                                  attr.Background.B
+                                                                                 )
+                                          );
+                        }
+
+                        outputWidth++;
+                        Rune rune = Contents [row, col].Rune;
+                        output.Append (rune);
+
+                        if (Contents [row, col].CombiningMarks.Count > 0)
+                        {
+                            // AtlasEngine does not support NON-NORMALIZED combining marks in a way
+                            // compatible with the driver architecture. Any CMs (except in the first col)
+                            // are correctly combined with the base char, but are ALSO treated as 1 column
+                            // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
+                            // 
+                            // For now, we just ignore the list of CMs.
+                            //foreach (var combMark in Contents [row, col].CombiningMarks) {
+                            //	output.Append (combMark);
+                            //}
+                            // WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                        }
+                        else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
+                        {
+                            WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                            SetCursorPosition (col - 1, row);
+                        }
+
+                        Contents [row, col].IsDirty = false;
                     }
                 }
+
+                if (output.Length > 0)
+                {
+                    SetCursorPosition (lastCol, row);
+                    Console.Write (output);
+                }
             }
-        }
 
-        if (!RunningUnitTests)
-        {
-            Curses.move (Row, Col);
-            _window.wrefresh ();
+            SetCursorPosition (0, 0);
+
+            _currentCursorVisibility = savedVisibility;
+
+            void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+            {
+                SetCursorPosition (lastCol, row);
+                Console.Write (output);
+                output.Clear ();
+                lastCol += outputWidth;
+                outputWidth = 0;
+            }
         }
     }
 
+    private bool SetCursorPosition (int col, int row)
+    {
+        // + 1 is needed because non-Windows is based on 1 instead of 0 and
+        // Console.CursorTop/CursorLeft isn't reliable.
+        Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
+
+        return true;
+    }
+
     internal override void End ()
     {
         StopReportingMouseMoves ();
@@ -374,7 +536,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)
         {
@@ -405,7 +567,11 @@ internal class CursesDriver : ConsoleDriver
         if (!RunningUnitTests)
         {
             Curses.CheckWinChange ();
-            Curses.refresh ();
+
+            if (Force16Colors)
+            {
+                Curses.refresh ();
+            }
         }
 
         return new MainLoop (_mainLoopDriver);
@@ -852,15 +1018,16 @@ internal class CursesDriver : ConsoleDriver
     /// <returns></returns>
     private static Attribute MakeColor (short foreground, short background)
     {
-        var v = (short)((ushort)foreground | (background << 4));
+        //var v = (short)((ushort)foreground | (background << 4));
+        var v = (short)(((ushort)(foreground & 0xffff) << 16) | (background & 0xffff));
 
         // TODO: for TrueColor - Use InitExtendedPair
         Curses.InitColorPair (v, foreground, background);
 
         return new Attribute (
                               Curses.ColorPair (v),
-                              CursesColorNumberToColorName (foreground),
-                              CursesColorNumberToColorName (background)
+                              CursesColorNumberToColorName16 (foreground),
+                              CursesColorNumberToColorName16 (background)
                              );
     }
 
@@ -872,11 +1039,11 @@ internal class CursesDriver : ConsoleDriver
     /// </remarks>
     public override Attribute MakeColor (in Color foreground, in Color background)
     {
-        if (!RunningUnitTests)
+        if (!RunningUnitTests && Force16Colors)
         {
             return MakeColor (
-                              ColorNameToCursesColorNumber (foreground.GetClosestNamedColor ()),
-                              ColorNameToCursesColorNumber (background.GetClosestNamedColor ())
+                              ColorNameToCursesColorNumber (foreground.GetClosestNamedColor16 ()),
+                              ColorNameToCursesColorNumber (background.GetClosestNamedColor16 ())
                              );
         }
 
@@ -887,83 +1054,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 ())
                                                                                 )
                                           );
                         }

+ 9 - 7
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))
                 };
             }
 
@@ -1811,12 +1813,6 @@ internal class WindowsDriver : ConsoleDriver
         int delay = startDelay;
         while (_isButtonPressed)
         {
-            var me = new MouseEvent
-            {
-                Position = _pointMove,
-                Flags = mouseFlag
-            };
-
             // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
             View view = Application.WantContinuousButtonPressedView;
 
@@ -1831,6 +1827,12 @@ internal class WindowsDriver : ConsoleDriver
             }
             await Task.Delay (delay);
 
+            var me = new MouseEvent
+            {
+                Position = _pointMove,
+                Flags = mouseFlag
+            };
+
             //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}");
             if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
             {

+ 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

+ 152 - 6
Terminal.Gui/Drawing/Cell.cs

@@ -4,19 +4,18 @@
 ///     Represents a single row/column in a Terminal.Gui rendering surface (e.g. <see cref="LineCanvas"/> and
 ///     <see cref="ConsoleDriver"/>).
 /// </summary>
-public record struct Cell ()
+public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default)
 {
-
     /// <summary>The attributes to use when drawing the Glyph.</summary>
-    public Attribute? Attribute { get; set; } = null;
+    public Attribute? Attribute { get; set; } = Attribute;
 
     /// <summary>
     ///     Gets or sets a value indicating whether this <see cref="T:Terminal.Gui.Cell"/> has been modified since the
     ///     last time it was drawn.
     /// </summary>
-    public bool IsDirty { get; set; } = false;
+    public bool IsDirty { get; set; } = IsDirty;
 
-    private Rune _rune = default;
+    private Rune _rune = Rune;
 
     /// <summary>The character to display. If <see cref="Rune"/> is <see langword="null"/>, then <see cref="Rune"/> is ignored.</summary>
     public Rune Rune
@@ -29,6 +28,8 @@ public record struct Cell ()
         }
     }
 
+    private List<Rune> _combiningMarks;
+
     /// <summary>
     ///     The combining marks for <see cref="Rune"/> that when combined makes this Cell a combining sequence. If
     ///     <see cref="CombiningMarks"/> empty, then <see cref="CombiningMarks"/> is ignored.
@@ -37,8 +38,153 @@ public record struct Cell ()
     ///     Only valid in the rare case where <see cref="Rune"/> is a combining sequence that could not be normalized to a
     ///     single Rune.
     /// </remarks>
-    internal List<Rune> CombiningMarks { get; } = new ();
+    internal List<Rune> CombiningMarks
+    {
+        get => _combiningMarks ?? [];
+        private set => _combiningMarks = value ?? [];
+    }
 
     /// <inheritdoc/>
     public override string ToString () { return $"[{Rune}, {Attribute}]"; }
+
+    /// <summary>Converts the string into a <see cref="List{Cell}"/>.</summary>
+    /// <param name="str">The string to convert.</param>
+    /// <param name="attribute">The <see cref="Gui.ColorScheme"/> to use.</param>
+    /// <returns></returns>
+    public static List<Cell> ToCellList (string str, Attribute? attribute = null)
+    {
+        List<Cell> cells = new ();
+
+        foreach (Rune rune in str.EnumerateRunes ())
+        {
+            cells.Add (new () { Rune = rune, Attribute = attribute });
+        }
+
+        return cells;
+    }
+
+    /// <summary>
+    ///     Splits a string into a List that will contain a <see cref="List{Cell}"/> for each line.
+    /// </summary>
+    /// <param name="content">The string content.</param>
+    /// <param name="attribute">The color scheme.</param>
+    /// <returns>A <see cref="List{Cell}"/> for each line.</returns>
+    public static List<List<Cell>> StringToLinesOfCells (string content, Attribute? attribute = null)
+    {
+        List<Cell> cells = content.EnumerateRunes ()
+                                  .Select (x => new Cell { Rune = x, Attribute = attribute })
+                                  .ToList ();
+
+        return SplitNewLines (cells);
+    }
+
+    /// <summary>Converts a <see cref="Cell"/> generic collection into a string.</summary>
+    /// <param name="cells">The enumerable cell to convert.</param>
+    /// <returns></returns>
+    public static string ToString (IEnumerable<Cell> cells)
+    {
+        var str = string.Empty;
+
+        foreach (Cell cell in cells)
+        {
+            str += cell.Rune.ToString ();
+        }
+
+        return str;
+    }
+
+    /// <summary>Converts a <see cref="List{Cell}"/> generic collection into a string.</summary>
+    /// <param name="cellsList">The enumerable cell to convert.</param>
+    /// <returns></returns>
+    public static string ToString (List<List<Cell>> cellsList)
+    {
+        var str = string.Empty;
+
+        for (var i = 0; i < cellsList.Count; i++)
+        {
+            IEnumerable<Cell> cellList = cellsList [i];
+            str += ToString (cellList);
+
+            if (i + 1 < cellsList.Count)
+            {
+                str += Environment.NewLine;
+            }
+        }
+
+        return str;
+    }
+
+    // Turns the string into cells, this does not split the contents on a newline if it is present.
+
+    internal static List<Cell> StringToCells (string str, Attribute? attribute = null)
+    {
+        List<Cell> cells = [];
+
+        foreach (Rune rune in str.ToRunes ())
+        {
+            cells.Add (new () { Rune = rune, Attribute = attribute });
+        }
+
+        return cells;
+    }
+
+    internal static List<Cell> ToCells (IEnumerable<Rune> runes, Attribute? attribute = null)
+    {
+        List<Cell> cells = new ();
+
+        foreach (Rune rune in runes)
+        {
+            cells.Add (new () { Rune = rune, Attribute = attribute });
+        }
+
+        return cells;
+    }
+
+    private static List<List<Cell>> SplitNewLines (List<Cell> cells)
+    {
+        List<List<Cell>> lines = [];
+        int start = 0, i = 0;
+        var hasCR = false;
+
+        // ASCII code 13 = Carriage Return.
+        // ASCII code 10 = Line Feed.
+        for (; i < cells.Count; i++)
+        {
+            if (cells [i].Rune.Value == 13)
+            {
+                hasCR = true;
+
+                continue;
+            }
+
+            if (cells [i].Rune.Value == 10)
+            {
+                if (i - start > 0)
+                {
+                    lines.Add (cells.GetRange (start, hasCR ? i - 1 - start : i - start));
+                }
+                else
+                {
+                    lines.Add (StringToCells (string.Empty));
+                }
+
+                start = i + 1;
+                hasCR = false;
+            }
+        }
+
+        if (i - start >= 0)
+        {
+            lines.Add (cells.GetRange (start, i - start));
+        }
+
+        return lines;
+    }
+
+    /// <summary>
+    ///     Splits a rune cell list into a List that will contain a <see cref="List{Cell}"/> for each line.
+    /// </summary>
+    /// <param name="cells">The cells list.</param>
+    /// <returns></returns>
+    public static List<List<Cell>> ToCells (List<Cell> cells) { return SplitNewLines (cells); }
 }

+ 8 - 8
Terminal.Gui/Views/RuneCellEventArgs.cs → Terminal.Gui/Drawing/CellEventArgs.cs

@@ -1,27 +1,27 @@
 namespace Terminal.Gui;
 
-/// <summary>Args for events that relate to a specific <see cref="RuneCell"/>.</summary>
-public class RuneCellEventArgs
+/// <summary>Args for events that relate to a specific <see cref="Cell"/>.</summary>
+public record struct CellEventArgs
 {
-    /// <summary>Creates a new instance of the <see cref="RuneCellEventArgs"/> class.</summary>
+    /// <summary>Creates a new instance of the <see cref="CellEventArgs"/> class.</summary>
     /// <param name="line">The line.</param>
     /// <param name="col">The col index.</param>
     /// <param name="unwrappedPosition">The unwrapped row and col index.</param>
-    public RuneCellEventArgs (List<RuneCell> line, int col, (int Row, int Col) unwrappedPosition)
+    public CellEventArgs (List<Cell> line, int col, (int Row, int Col) unwrappedPosition)
     {
         Line = line;
         Col = col;
         UnwrappedPosition = unwrappedPosition;
     }
 
-    /// <summary>The index of the RuneCell in the line.</summary>
+    /// <summary>The index of the Cell in the line.</summary>
     public int Col { get; }
 
-    /// <summary>The list of runes the RuneCell is part of.</summary>
-    public List<RuneCell> Line { get; }
+    /// <summary>The list of runes the Cell is part of.</summary>
+    public List<Cell> Line { get; }
 
     /// <summary>
-    ///     The unwrapped row and column index into the text containing the RuneCell. Unwrapped means the text without
+    ///     The unwrapped row and column index into the text containing the Cell. Unwrapped means the text without
     ///     word wrapping or other visual formatting having been applied.
     /// </summary>
     public (int Row, int Col) UnwrappedPosition { get; }

+ 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)'▌';

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

@@ -138,7 +138,7 @@ public class LineCanvas : IDisposable
         int length,
         Orientation orientation,
         LineStyle style,
-        Attribute? attribute = default
+        Attribute? attribute = null
     )
     {
         _cachedViewport = Rectangle.Empty;

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

@@ -16,7 +16,7 @@ public class StraightLine
         int length,
         Orientation orientation,
         LineStyle style,
-        Attribute? attribute = default
+        Attribute? attribute = null
     )
     {
         Start = start;

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

+ 72 - 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>
@@ -1374,6 +1437,15 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Co_lors.
+        /// </summary>
+        internal static string ctxColors {
+            get {
+                return ResourceManager.GetString("ctxColors", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to _Copy.
         /// </summary>

+ 15 - 12
Terminal.Gui/Resources/Strings.fr-FR.resx

@@ -117,27 +117,27 @@
   <resheader name="writer">
     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
+  <data name="ctxSelectAll" xml:space="preserve">
+    <value>Tout _sélectionner</value>
+  </data>
+  <data name="ctxDeleteAll" xml:space="preserve">
+    <value>_Tout supprimer</value>
+  </data>
   <data name="ctxCopy" xml:space="preserve">
     <value>_Copier</value>
   </data>
   <data name="ctxCut" xml:space="preserve">
     <value>Co_uper</value>
   </data>
-  <data name="ctxDeleteAll" xml:space="preserve">
-    <value>_Tout supprimer</value>
-  </data>
   <data name="ctxPaste" xml:space="preserve">
     <value>C_oller</value>
   </data>
-  <data name="ctxRedo" xml:space="preserve">
-    <value>_Rétablir</value>
-  </data>
-  <data name="ctxSelectAll" xml:space="preserve">
-    <value>Tout _sélectionner</value>
-  </data>
   <data name="ctxUndo" xml:space="preserve">
     <value>_Annuler</value>
   </data>
+  <data name="ctxRedo" xml:space="preserve">
+    <value>_Rétablir</value>
+  </data>
   <data name="fdDirectory" xml:space="preserve">
     <value>_Dossier</value>
   </data>
@@ -168,16 +168,19 @@
   <data name="wzNext" xml:space="preserve">
     <value>Prochai_n...</value>
   </data>
+  <data name="btnOpen" xml:space="preserve">
+    <value>Ouvrir</value>
+  </data>
   <data name="btnSave" xml:space="preserve">
     <value>Enregistrer</value>
   </data>
   <data name="btnSaveAs" xml:space="preserve">
     <value>E_nregistrer sous</value>
   </data>
-  <data name="btnOpen" xml:space="preserve">
-    <value>Ouvrir</value>
-  </data>
   <data name="dpTitle" xml:space="preserve">
     <value>Sélecteur de Date</value>
   </data>
+  <data name="ctxColors" xml:space="preserve">
+    <value>Cou_leurs</value>
+  </data>
 </root>

+ 58 - 55
Terminal.Gui/Resources/Strings.ja-JP.resx

@@ -117,27 +117,27 @@
   <resheader name="writer">
     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
+  <data name="ctxSelectAll" xml:space="preserve">
+    <value>全て選択 (_S)</value>
+  </data>
+  <data name="ctxDeleteAll" xml:space="preserve">
+    <value>全て削除 (_D)</value>
+  </data>
   <data name="ctxCopy" xml:space="preserve">
     <value>コピー (_C)</value>
   </data>
   <data name="ctxCut" xml:space="preserve">
     <value>切り取り (_T)</value>
   </data>
-  <data name="ctxDeleteAll" xml:space="preserve">
-    <value>全て削除 (_D)</value>
-  </data>
   <data name="ctxPaste" xml:space="preserve">
     <value>貼り付け (_P)</value>
   </data>
-  <data name="ctxRedo" xml:space="preserve">
-    <value>やり直し (_R)</value>
-  </data>
-  <data name="ctxSelectAll" xml:space="preserve">
-    <value>全て選択 (_S)</value>
-  </data>
   <data name="ctxUndo" xml:space="preserve">
     <value>元に戻す (_U)</value>
   </data>
+  <data name="ctxRedo" xml:space="preserve">
+    <value>やり直し (_R)</value>
+  </data>
   <data name="fdDirectory" xml:space="preserve">
     <value>ディレクトリ</value>
   </data>
@@ -171,44 +171,47 @@
   <data name="fdDirectoryAlreadyExistsFeedback" xml:space="preserve">
     <value>同じ名前のディレクトリはすでに存在しました</value>
   </data>
-  <data name="fdDeleteBody" xml:space="preserve">
-    <value>“{0}”を削除もよろしいですか?この操作は元に戻りません</value>
-  </data>
-  <data name="fdType" xml:space="preserve">
-    <value>タイプ</value>
+  <data name="fdDirectoryMustExistFeedback" xml:space="preserve">
+    <value>すでに存在したディレクトリを選択してください</value>
   </data>
-  <data name="fdSize" xml:space="preserve">
-    <value>サイズ</value>
+  <data name="fdFileAlreadyExistsFeedback" xml:space="preserve">
+    <value>同じ名前のファイルはすでに存在しました</value>
   </data>
-  <data name="fdPathCaption" xml:space="preserve">
-    <value>パスを入力</value>
+  <data name="fdFileMustExistFeedback" xml:space="preserve">
+    <value>すでに存在したファイルを選択してください</value>
   </data>
   <data name="fdFilename" xml:space="preserve">
     <value>ファイル名</value>
   </data>
-  <data name="fdNewTitle" xml:space="preserve">
-    <value>新規ディレクトリ</value>
-  </data>
-  <data name="btnNo" xml:space="preserve">
-    <value>いいえ (_N)</value>
-  </data>
-  <data name="btnYes" xml:space="preserve">
-    <value>はい (_Y)</value>
+  <data name="fdFileOrDirectoryMustExistFeedback" xml:space="preserve">
+    <value>すでに存在したファイルまたはディレクトリを選択してください</value>
   </data>
   <data name="fdModified" xml:space="preserve">
     <value>変更日時</value>
   </data>
-  <data name="fdFileOrDirectoryMustExistFeedback" xml:space="preserve">
-    <value>すでに存在したファイルまたはディレクトリを選択してください</value>
+  <data name="fdPathCaption" xml:space="preserve">
+    <value>パスを入力</value>
   </data>
-  <data name="fdDirectoryMustExistFeedback" xml:space="preserve">
-    <value>すでに存在したディレクトリを選択してください</value>
+  <data name="fdSearchCaption" xml:space="preserve">
+    <value>検索を入力</value>
   </data>
-  <data name="fdFileMustExistFeedback" xml:space="preserve">
-    <value>すでに存在したファイルを選択してください</value>
+  <data name="fdSize" xml:space="preserve">
+    <value>サイズ</value>
   </data>
-  <data name="fdRenamePrompt" xml:space="preserve">
-    <value>名前:</value>
+  <data name="fdType" xml:space="preserve">
+    <value>タイプ</value>
+  </data>
+  <data name="fdWrongFileTypeFeedback" xml:space="preserve">
+    <value>ファイルタイプが間違っでいます</value>
+  </data>
+  <data name="fdAnyFiles" xml:space="preserve">
+    <value>任意ファイル</value>
+  </data>
+  <data name="fdDeleteBody" xml:space="preserve">
+    <value>“{0}”を削除もよろしいですか?この操作は元に戻りません</value>
+  </data>
+  <data name="fdDeleteFailedTitle" xml:space="preserve">
+    <value>削除失敗</value>
   </data>
   <data name="fdDeleteTitle" xml:space="preserve">
     <value>{0} を削除</value>
@@ -216,35 +219,26 @@
   <data name="fdNewFailed" xml:space="preserve">
     <value>新規失敗</value>
   </data>
-  <data name="fdExisting" xml:space="preserve">
-    <value>既存</value>
+  <data name="fdNewTitle" xml:space="preserve">
+    <value>新規ディレクトリ</value>
   </data>
-  <data name="fdRenameTitle" xml:space="preserve">
-    <value>名前を変更</value>
+  <data name="btnNo" xml:space="preserve">
+    <value>いいえ (_N)</value>
   </data>
   <data name="fdRenameFailedTitle" xml:space="preserve">
     <value>変更失敗</value>
   </data>
-  <data name="fdDeleteFailedTitle" xml:space="preserve">
-    <value>削除失敗</value>
-  </data>
-  <data name="fdFileAlreadyExistsFeedback" xml:space="preserve">
-    <value>同じ名前のファイルはすでに存在しました</value>
-  </data>
-  <data name="fdSearchCaption" xml:space="preserve">
-    <value>検索を入力</value>
-  </data>
-  <data name="fdWrongFileTypeFeedback" xml:space="preserve">
-    <value>ファイルタイプが間違っでいます</value>
+  <data name="fdRenamePrompt" xml:space="preserve">
+    <value>名前:</value>
   </data>
-  <data name="fdAnyFiles" xml:space="preserve">
-    <value>任意ファイル</value>
+  <data name="fdRenameTitle" xml:space="preserve">
+    <value>名前を変更</value>
   </data>
-  <data name="btnCancel" xml:space="preserve">
-    <value>キャンセル (_C)</value>
+  <data name="btnYes" xml:space="preserve">
+    <value>はい (_Y)</value>
   </data>
-  <data name="btnOk" xml:space="preserve">
-    <value>OK (_O)</value>
+  <data name="fdExisting" xml:space="preserve">
+    <value>既存</value>
   </data>
   <data name="btnOpen" xml:space="preserve">
     <value>開く (_O)</value>
@@ -255,6 +249,12 @@
   <data name="btnSaveAs" xml:space="preserve">
     <value>名前を付けて保存 (_S)</value>
   </data>
+  <data name="btnOk" xml:space="preserve">
+    <value>OK (_O)</value>
+  </data>
+  <data name="btnCancel" xml:space="preserve">
+    <value>キャンセル (_C)</value>
+  </data>
   <data name="fdCtxDelete" xml:space="preserve">
     <value>削除 (_D)</value>
   </data>
@@ -276,4 +276,7 @@
   <data name="dpTitle" xml:space="preserve">
     <value>日付ピッカー</value>
   </data>
+  <data name="ctxColors" xml:space="preserve">
+    <value>絵の具 (_L)</value>
+  </data>
 </root>

+ 16 - 13
Terminal.Gui/Resources/Strings.pt-PT.resx

@@ -117,27 +117,27 @@
   <resheader name="writer">
     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
+  <data name="ctxSelectAll" xml:space="preserve">
+    <value>_Selecionar Tudo</value>
+  </data>
+  <data name="ctxDeleteAll" xml:space="preserve">
+    <value>_Apagar Tudo</value>
+  </data>
   <data name="ctxCopy" xml:space="preserve">
     <value>_Copiar</value>
   </data>
   <data name="ctxCut" xml:space="preserve">
     <value>Cor_tar</value>
   </data>
-  <data name="ctxDeleteAll" xml:space="preserve">
-    <value>_Apagar Tudo</value>
-  </data>
   <data name="ctxPaste" xml:space="preserve">
     <value>Co_lar</value>
   </data>
-  <data name="ctxRedo" xml:space="preserve">
-    <value>_Refazer</value>
-  </data>
-  <data name="ctxSelectAll" xml:space="preserve">
-    <value>_Selecionar Tudo</value>
-  </data>
   <data name="ctxUndo" xml:space="preserve">
     <value>_Desfazer</value>
   </data>
+  <data name="ctxRedo" xml:space="preserve">
+    <value>_Refazer</value>
+  </data>
   <data name="fdDirectory" xml:space="preserve">
     <value>Diretório</value>
   </data>
@@ -168,16 +168,19 @@
   <data name="wzNext" xml:space="preserve">
     <value>S_eguir...</value>
   </data>
-  <data name="btnSaveAs" xml:space="preserve">
-    <value>Guardar como</value>
+  <data name="btnOpen" xml:space="preserve">
+    <value>Abrir</value>
   </data>
   <data name="btnSave" xml:space="preserve">
     <value>Guardar</value>
   </data>
-  <data name="btnOpen" xml:space="preserve">
-    <value>Abrir</value>
+  <data name="btnSaveAs" xml:space="preserve">
+    <value>Guardar como</value>
   </data>
   <data name="dpTitle" xml:space="preserve">
     <value>Seletor de Data</value>
   </data>
+  <data name="ctxColors" xml:space="preserve">
+    <value>Co_res</value>
+  </data>
 </root>

+ 273 - 249
Terminal.Gui/Resources/Strings.resx

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <root>
-    <!-- 
+  <!-- 
     Microsoft ResX Schema 
     
     Version 2.0
@@ -59,642 +59,666 @@
             : using a System.ComponentModel.TypeConverter
             : and then encoded with base64 encoding.
     -->
-    <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
-        <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
-        <xsd:element name="root" msdata:IsDataSet="true">
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
             <xsd:complexType>
-                <xsd:choice maxOccurs="unbounded">
-                    <xsd:element name="metadata">
-                        <xsd:complexType>
-                            <xsd:sequence>
-                                <xsd:element name="value" type="xsd:string" minOccurs="0" />
-                            </xsd:sequence>
-                            <xsd:attribute name="name" use="required" type="xsd:string" />
-                            <xsd:attribute name="type" type="xsd:string" />
-                            <xsd:attribute name="mimetype" type="xsd:string" />
-                            <xsd:attribute ref="xml:space" />
-                        </xsd:complexType>
-                    </xsd:element>
-                    <xsd:element name="assembly">
-                        <xsd:complexType>
-                            <xsd:attribute name="alias" type="xsd:string" />
-                            <xsd:attribute name="name" type="xsd:string" />
-                        </xsd:complexType>
-                    </xsd:element>
-                    <xsd:element name="data">
-                        <xsd:complexType>
-                            <xsd:sequence>
-                                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
-                                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
-                            </xsd:sequence>
-                            <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
-                            <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
-                            <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
-                            <xsd:attribute ref="xml:space" />
-                        </xsd:complexType>
-                    </xsd:element>
-                    <xsd:element name="resheader">
-                        <xsd:complexType>
-                            <xsd:sequence>
-                                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
-                            </xsd:sequence>
-                            <xsd:attribute name="name" type="xsd:string" use="required" />
-                        </xsd:complexType>
-                    </xsd:element>
-                </xsd:choice>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
             </xsd:complexType>
-        </xsd:element>
-    </xsd:schema>
-    <resheader name="resmimetype">
-        <value>text/microsoft-resx</value>
-    </resheader>
-    <resheader name="version">
-        <value>2.0</value>
-    </resheader>
-    <resheader name="reader">
-        <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-    </resheader>
-    <resheader name="writer">
-        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-    </resheader>
-    <data name="ctxSelectAll" xml:space="preserve">
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="ctxSelectAll" xml:space="preserve">
     <value>_Select All</value>
   </data>
-    <data name="ctxDeleteAll" xml:space="preserve">
+  <data name="ctxDeleteAll" xml:space="preserve">
     <value>_Delete All</value>
   </data>
-    <data name="ctxCopy" xml:space="preserve">
+  <data name="ctxCopy" xml:space="preserve">
     <value>_Copy</value>
   </data>
-    <data name="ctxCut" xml:space="preserve">
+  <data name="ctxCut" xml:space="preserve">
     <value>Cu_t</value>
   </data>
-    <data name="ctxPaste" xml:space="preserve">
+  <data name="ctxPaste" xml:space="preserve">
     <value>_Paste</value>
   </data>
-    <data name="ctxUndo" xml:space="preserve">
+  <data name="ctxUndo" xml:space="preserve">
     <value>_Undo</value>
   </data>
-    <data name="ctxRedo" xml:space="preserve">
+  <data name="ctxRedo" xml:space="preserve">
     <value>_Redo</value>
   </data>
-    <data name="fdDirectory" xml:space="preserve">
+  <data name="fdDirectory" xml:space="preserve">
     <value>Directory</value>
   </data>
-    <data name="fdFile" xml:space="preserve">
+  <data name="fdFile" xml:space="preserve">
     <value>File</value>
   </data>
-    <data name="fdSave" xml:space="preserve">
+  <data name="fdSave" xml:space="preserve">
     <value>Save</value>
   </data>
-    <data name="fdSaveAs" xml:space="preserve">
+  <data name="fdSaveAs" xml:space="preserve">
     <value>Save as</value>
   </data>
-    <data name="fdOpen" xml:space="preserve">
+  <data name="fdOpen" xml:space="preserve">
     <value>Open</value>
   </data>
-    <data name="fdSelectFolder" xml:space="preserve">
+  <data name="fdSelectFolder" xml:space="preserve">
     <value>Select folder</value>
   </data>
-    <data name="fdSelectMixed" xml:space="preserve">
+  <data name="fdSelectMixed" xml:space="preserve">
     <value>Select Mixed</value>
   </data>
-    <data name="wzBack" xml:space="preserve">
+  <data name="wzBack" xml:space="preserve">
     <value>_Back</value>
   </data>
-    <data name="wzFinish" xml:space="preserve">
+  <data name="wzFinish" xml:space="preserve">
     <value>Fi_nish</value>
   </data>
-    <data name="wzNext" xml:space="preserve">
+  <data name="wzNext" xml:space="preserve">
     <value>_Next...</value>
   </data>
-    <data name="fdDirectoryAlreadyExistsFeedback" xml:space="preserve">
+  <data name="fdDirectoryAlreadyExistsFeedback" xml:space="preserve">
     <value>Directory already exists with that name</value>
     <comment>When trying to save a file with a name already taken by a directory</comment>
   </data>
-    <data name="fdDirectoryMustExistFeedback" xml:space="preserve">
+  <data name="fdDirectoryMustExistFeedback" xml:space="preserve">
     <value>Must select an existing directory</value>
   </data>
-    <data name="fdFileAlreadyExistsFeedback" xml:space="preserve">
+  <data name="fdFileAlreadyExistsFeedback" xml:space="preserve">
     <value>File already exists with that name</value>
   </data>
-    <data name="fdFileMustExistFeedback" xml:space="preserve">
+  <data name="fdFileMustExistFeedback" xml:space="preserve">
     <value>Must select an existing file</value>
     <comment>When trying to save a directory with a name already used by a file</comment>
   </data>
-    <data name="fdFilename" xml:space="preserve">
+  <data name="fdFilename" xml:space="preserve">
     <value>Filename</value>
   </data>
-    <data name="fdFileOrDirectoryMustExistFeedback" xml:space="preserve">
+  <data name="fdFileOrDirectoryMustExistFeedback" xml:space="preserve">
     <value>Must select an existing file or directory</value>
   </data>
-    <data name="fdModified" xml:space="preserve">
+  <data name="fdModified" xml:space="preserve">
     <value>Modified</value>
   </data>
-    <data name="fdPathCaption" xml:space="preserve">
+  <data name="fdPathCaption" xml:space="preserve">
     <value>Enter Path</value>
   </data>
-    <data name="fdSearchCaption" xml:space="preserve">
+  <data name="fdSearchCaption" xml:space="preserve">
     <value>Enter Search</value>
   </data>
-    <data name="fdSize" xml:space="preserve">
+  <data name="fdSize" xml:space="preserve">
     <value>Size</value>
   </data>
-    <data name="fdType" xml:space="preserve">
+  <data name="fdType" xml:space="preserve">
     <value>Type</value>
   </data>
-    <data name="fdWrongFileTypeFeedback" xml:space="preserve">
+  <data name="fdWrongFileTypeFeedback" xml:space="preserve">
     <value>Wrong file type</value>
     <comment>When trying to open/save a file that does not match the provided filter (e.g. csv)</comment>
   </data>
-    <data name="fdAnyFiles" xml:space="preserve">
+  <data name="fdAnyFiles" xml:space="preserve">
     <value>Any Files</value>
     <comment>Describes an AllowedType that matches anything</comment>
   </data>
-    <data name="fdDeleteBody" xml:space="preserve">
+  <data name="fdDeleteBody" xml:space="preserve">
     <value>Are you sure you want to delete '{0}'? This operation is permanent</value>
   </data>
-    <data name="fdDeleteFailedTitle" xml:space="preserve">
+  <data name="fdDeleteFailedTitle" xml:space="preserve">
     <value>Delete Failed</value>
   </data>
-    <data name="fdDeleteTitle" xml:space="preserve">
+  <data name="fdDeleteTitle" xml:space="preserve">
     <value>Delete {0}</value>
   </data>
-    <data name="fdNewFailed" xml:space="preserve">
+  <data name="fdNewFailed" xml:space="preserve">
     <value>New Failed</value>
   </data>
-    <data name="fdNewTitle" xml:space="preserve">
+  <data name="fdNewTitle" xml:space="preserve">
     <value>New Folder</value>
   </data>
-    <data name="btnNo" xml:space="preserve">
+  <data name="btnNo" xml:space="preserve">
     <value>_No</value>
   </data>
-    <data name="fdRenameFailedTitle" xml:space="preserve">
+  <data name="fdRenameFailedTitle" xml:space="preserve">
     <value>Rename Failed</value>
   </data>
-    <data name="fdRenamePrompt" xml:space="preserve">
+  <data name="fdRenamePrompt" xml:space="preserve">
     <value>Name:</value>
   </data>
-    <data name="fdRenameTitle" xml:space="preserve">
+  <data name="fdRenameTitle" xml:space="preserve">
     <value>Rename</value>
   </data>
-    <data name="btnYes" xml:space="preserve">
+  <data name="btnYes" xml:space="preserve">
     <value>_Yes</value>
   </data>
-    <data name="fdExisting" xml:space="preserve">
+  <data name="fdExisting" xml:space="preserve">
     <value>Existing</value>
   </data>
-    <data name="btnOpen" xml:space="preserve">
+  <data name="btnOpen" xml:space="preserve">
     <value>O_pen</value>
   </data>
-    <data name="btnSave" xml:space="preserve">
+  <data name="btnSave" xml:space="preserve">
     <value>_Save</value>
   </data>
-    <data name="btnSaveAs" xml:space="preserve">
+  <data name="btnSaveAs" xml:space="preserve">
     <value>Save _as</value>
   </data>
-    <data name="btnOk" xml:space="preserve">
+  <data name="btnOk" xml:space="preserve">
     <value>_OK</value>
   </data>
-    <data name="btnCancel" xml:space="preserve">
+  <data name="btnCancel" xml:space="preserve">
     <value>_Cancel</value>
   </data>
-    <data name="fdCtxDelete" xml:space="preserve">
+  <data name="fdCtxDelete" xml:space="preserve">
     <value>_Delete</value>
   </data>
-    <data name="fdCtxHide" xml:space="preserve">
+  <data name="fdCtxHide" xml:space="preserve">
     <value>_Hide {0}</value>
   </data>
-    <data name="fdCtxNew" xml:space="preserve">
+  <data name="fdCtxNew" xml:space="preserve">
     <value>_New</value>
   </data>
-    <data name="fdCtxRename" xml:space="preserve">
+  <data name="fdCtxRename" xml:space="preserve">
     <value>_Rename</value>
   </data>
-    <data name="fdCtxSortAsc" xml:space="preserve">
+  <data name="fdCtxSortAsc" xml:space="preserve">
     <value>_Sort {0} ASC</value>
   </data>
-    <data name="fdCtxSortDesc" xml:space="preserve">
+  <data name="fdCtxSortDesc" xml:space="preserve">
     <value>_Sort {0} DESC</value>
   </data>
-    <data name="dpTitle" xml:space="preserve">
+  <data name="dpTitle" xml:space="preserve">
     <value>Date Picker</value>
   </data>
-    <data name="#F0F8FF" xml:space="preserve">
+  <data name="#F0F8FF" xml:space="preserve">
     <value>AliceBlue</value>
   </data>
-    <data name="#FAEBD7" xml:space="preserve">
+  <data name="#FAEBD7" xml:space="preserve">
     <value>AntiqueWhite</value>
   </data>
-    <data name="#7FFFD4" xml:space="preserve">
+  <data name="#7FFFD4" xml:space="preserve">
     <value>Aquamarine</value>
   </data>
-    <data name="#F0FFFF" xml:space="preserve">
+  <data name="#F0FFFF" xml:space="preserve">
     <value>Azure</value>
   </data>
-    <data name="#F5F5DC" xml:space="preserve">
+  <data name="#F5F5DC" xml:space="preserve">
     <value>Beige</value>
   </data>
-    <data name="#FFE4C4" xml:space="preserve">
+  <data name="#FFE4C4" xml:space="preserve">
     <value>Bisque</value>
   </data>
-    <data name="#000000" xml:space="preserve">
+  <data name="#000000" xml:space="preserve">
     <value>Black</value>
   </data>
-    <data name="#FFEBCD" xml:space="preserve">
+  <data name="#FFEBCD" xml:space="preserve">
     <value>BlanchedAlmond</value>
   </data>
-    <data name="#0000FF" xml:space="preserve">
+  <data name="#0000FF" xml:space="preserve">
     <value>Blue</value>
   </data>
-    <data name="#8A2BE2" xml:space="preserve">
+  <data name="#8A2BE2" xml:space="preserve">
     <value>BlueViolet</value>
   </data>
-    <data name="#A52A2A" xml:space="preserve">
+  <data name="#A52A2A" xml:space="preserve">
     <value>Brown</value>
   </data>
-    <data name="#DEB887" xml:space="preserve">
+  <data name="#DEB887" xml:space="preserve">
     <value>BurlyWood</value>
   </data>
-    <data name="#5F9EA0" xml:space="preserve">
+  <data name="#5F9EA0" xml:space="preserve">
     <value>CadetBlue</value>
   </data>
-    <data name="#7FFF00" xml:space="preserve">
+  <data name="#7FFF00" xml:space="preserve">
     <value>Chartreuse</value>
   </data>
-    <data name="#D2691E" xml:space="preserve">
+  <data name="#D2691E" xml:space="preserve">
     <value>Chocolate</value>
   </data>
-    <data name="#FF7F50" xml:space="preserve">
+  <data name="#FF7F50" xml:space="preserve">
     <value>Coral</value>
   </data>
-    <data name="#6495ED" xml:space="preserve">
+  <data name="#6495ED" xml:space="preserve">
     <value>CornflowerBlue</value>
   </data>
-    <data name="#FFF8DC" xml:space="preserve">
+  <data name="#FFF8DC" xml:space="preserve">
     <value>Cornsilk</value>
   </data>
-    <data name="#DC143C" xml:space="preserve">
+  <data name="#DC143C" xml:space="preserve">
     <value>Crimson</value>
   </data>
-    <data name="#00FFFF" xml:space="preserve">
+  <data name="#00FFFF" xml:space="preserve">
     <value>Cyan</value>
   </data>
-    <data name="#00008B" xml:space="preserve">
+  <data name="#00008B" xml:space="preserve">
     <value>DarkBlue</value>
   </data>
-    <data name="#008B8B" xml:space="preserve">
+  <data name="#008B8B" xml:space="preserve">
     <value>DarkCyan</value>
   </data>
-    <data name="#B8860B" xml:space="preserve">
+  <data name="#B8860B" xml:space="preserve">
     <value>DarkGoldenRod</value>
   </data>
-    <data name="#A9A9A9" xml:space="preserve">
+  <data name="#A9A9A9" xml:space="preserve">
     <value>DarkGrey</value>
   </data>
-    <data name="#006400" xml:space="preserve">
+  <data name="#006400" xml:space="preserve">
     <value>DarkGreen</value>
   </data>
-    <data name="#BDB76B" xml:space="preserve">
+  <data name="#BDB76B" xml:space="preserve">
     <value>DarkKhaki</value>
   </data>
-    <data name="#8B008B" xml:space="preserve">
+  <data name="#8B008B" xml:space="preserve">
     <value>DarkMagenta</value>
   </data>
-    <data name="#556B2F" xml:space="preserve">
+  <data name="#556B2F" xml:space="preserve">
     <value>DarkOliveGreen</value>
   </data>
-    <data name="#FF8C00" xml:space="preserve">
+  <data name="#FF8C00" xml:space="preserve">
     <value>DarkOrange</value>
   </data>
-    <data name="#9932CC" xml:space="preserve">
+  <data name="#9932CC" xml:space="preserve">
     <value>DarkOrchid</value>
   </data>
-    <data name="#8B0000" xml:space="preserve">
+  <data name="#8B0000" xml:space="preserve">
     <value>DarkRed</value>
   </data>
-    <data name="#E9967A" xml:space="preserve">
+  <data name="#E9967A" xml:space="preserve">
     <value>DarkSalmon</value>
   </data>
-    <data name="#8FBC8F" xml:space="preserve">
+  <data name="#8FBC8F" xml:space="preserve">
     <value>DarkSeaGreen</value>
   </data>
-    <data name="#483D8B" xml:space="preserve">
+  <data name="#483D8B" xml:space="preserve">
     <value>DarkSlateBlue</value>
   </data>
-    <data name="#2F4F4F" xml:space="preserve">
+  <data name="#2F4F4F" xml:space="preserve">
     <value>DarkSlateGrey</value>
   </data>
-    <data name="#00CED1" xml:space="preserve">
+  <data name="#00CED1" xml:space="preserve">
     <value>DarkTurquoise</value>
   </data>
-    <data name="#9400D3" xml:space="preserve">
+  <data name="#9400D3" xml:space="preserve">
     <value>DarkViolet</value>
   </data>
-    <data name="#FF1493" xml:space="preserve">
+  <data name="#FF1493" xml:space="preserve">
     <value>DeepPink</value>
   </data>
-    <data name="#00BFFF" xml:space="preserve">
+  <data name="#00BFFF" xml:space="preserve">
     <value>DeepSkyBlue</value>
   </data>
-    <data name="#696969" xml:space="preserve">
+  <data name="#696969" xml:space="preserve">
     <value>DimGray</value>
   </data>
-    <data name="#1E90FF" xml:space="preserve">
+  <data name="#1E90FF" xml:space="preserve">
     <value>DodgerBlue</value>
   </data>
-    <data name="#B22222" xml:space="preserve">
+  <data name="#B22222" xml:space="preserve">
     <value>FireBrick</value>
   </data>
-    <data name="#FFFAF0" xml:space="preserve">
+  <data name="#FFFAF0" xml:space="preserve">
     <value>FloralWhite</value>
   </data>
-    <data name="#228B22" xml:space="preserve">
+  <data name="#228B22" xml:space="preserve">
     <value>ForestGreen</value>
   </data>
-    <data name="#DCDCDC" xml:space="preserve">
+  <data name="#DCDCDC" xml:space="preserve">
     <value>Gainsboro</value>
   </data>
-    <data name="#F8F8FF" xml:space="preserve">
+  <data name="#F8F8FF" xml:space="preserve">
     <value>GhostWhite</value>
   </data>
-    <data name="#FFD700" xml:space="preserve">
+  <data name="#FFD700" xml:space="preserve">
     <value>Gold</value>
   </data>
-    <data name="#DAA520" xml:space="preserve">
+  <data name="#DAA520" xml:space="preserve">
     <value>GoldenRod</value>
   </data>
-    <data name="#808080" xml:space="preserve">
+  <data name="#808080" xml:space="preserve">
     <value>Gray</value>
   </data>
-    <data name="#008000" xml:space="preserve">
+  <data name="#008000" xml:space="preserve">
     <value>Green</value>
   </data>
-    <data name="#ADFF2F" xml:space="preserve">
+  <data name="#ADFF2F" xml:space="preserve">
     <value>GreenYellow</value>
   </data>
-    <data name="#F0FFF0" xml:space="preserve">
+  <data name="#F0FFF0" xml:space="preserve">
     <value>HoneyDew</value>
   </data>
-    <data name="#FF69B4" xml:space="preserve">
+  <data name="#FF69B4" xml:space="preserve">
     <value>HotPink</value>
   </data>
-    <data name="#CD5C5C" xml:space="preserve">
+  <data name="#CD5C5C" xml:space="preserve">
     <value>IndianRed</value>
   </data>
-    <data name="#4B0082" xml:space="preserve">
+  <data name="#4B0082" xml:space="preserve">
     <value>Indigo</value>
   </data>
-    <data name="#FFFFF0" xml:space="preserve">
+  <data name="#FFFFF0" xml:space="preserve">
     <value>Ivory</value>
   </data>
-    <data name="#F0E68C" xml:space="preserve">
+  <data name="#F0E68C" xml:space="preserve">
     <value>Khaki</value>
   </data>
-    <data name="#E6E6FA" xml:space="preserve">
+  <data name="#E6E6FA" xml:space="preserve">
     <value>Lavender</value>
   </data>
-    <data name="#FFF0F5" xml:space="preserve">
+  <data name="#FFF0F5" xml:space="preserve">
     <value>LavenderBlush</value>
   </data>
-    <data name="#7CFC00" xml:space="preserve">
+  <data name="#7CFC00" xml:space="preserve">
     <value>LawnGreen</value>
   </data>
-    <data name="#FFFACD" xml:space="preserve">
+  <data name="#FFFACD" xml:space="preserve">
     <value>LemonChiffon</value>
   </data>
-    <data name="#ADD8E6" xml:space="preserve">
+  <data name="#ADD8E6" xml:space="preserve">
     <value>LightBlue</value>
   </data>
-    <data name="#F08080" xml:space="preserve">
+  <data name="#F08080" xml:space="preserve">
     <value>LightCoral</value>
   </data>
-    <data name="#E0FFFF" xml:space="preserve">
+  <data name="#E0FFFF" xml:space="preserve">
     <value>LightCyan</value>
   </data>
-    <data name="#FAFAD2" xml:space="preserve">
+  <data name="#FAFAD2" xml:space="preserve">
     <value>LightGoldenRodYellow</value>
   </data>
-    <data name="#D3D3D3" xml:space="preserve">
+  <data name="#D3D3D3" xml:space="preserve">
     <value>LightGray</value>
   </data>
-    <data name="#90EE90" xml:space="preserve">
+  <data name="#90EE90" xml:space="preserve">
     <value>LightGreen</value>
   </data>
-    <data name="#FFB6C1" xml:space="preserve">
+  <data name="#FFB6C1" xml:space="preserve">
     <value>LightPink</value>
   </data>
-    <data name="#FFA07A" xml:space="preserve">
+  <data name="#FFA07A" xml:space="preserve">
     <value>LightSalmon</value>
   </data>
-    <data name="#20B2AA" xml:space="preserve">
+  <data name="#20B2AA" xml:space="preserve">
     <value>LightSeaGreen</value>
   </data>
-    <data name="#87CEFA" xml:space="preserve">
+  <data name="#87CEFA" xml:space="preserve">
     <value>LightSkyBlue</value>
   </data>
-    <data name="#778899" xml:space="preserve">
+  <data name="#778899" xml:space="preserve">
     <value>LightSlateGrey</value>
   </data>
-    <data name="#B0C4DE" xml:space="preserve">
+  <data name="#B0C4DE" xml:space="preserve">
     <value>LightSteelBlue</value>
   </data>
-    <data name="#FFFFE0" xml:space="preserve">
+  <data name="#FFFFE0" xml:space="preserve">
     <value>LightYellow</value>
   </data>
-    <data name="#00FF00" xml:space="preserve">
+  <data name="#00FF00" xml:space="preserve">
     <value>Lime</value>
   </data>
-    <data name="#32CD32" xml:space="preserve">
+  <data name="#32CD32" xml:space="preserve">
     <value>LimeGreen</value>
   </data>
-    <data name="#FAF0E6" xml:space="preserve">
+  <data name="#FAF0E6" xml:space="preserve">
     <value>Linen</value>
   </data>
-    <data name="#FF00FF" xml:space="preserve">
+  <data name="#FF00FF" xml:space="preserve">
     <value>Magenta</value>
   </data>
-    <data name="#800000" xml:space="preserve">
+  <data name="#800000" xml:space="preserve">
     <value>Maroon</value>
   </data>
-    <data name="#66CDAA" xml:space="preserve">
+  <data name="#66CDAA" xml:space="preserve">
     <value>MediumAquaMarine</value>
   </data>
-    <data name="#0000CD" xml:space="preserve">
+  <data name="#0000CD" xml:space="preserve">
     <value>MediumBlue</value>
   </data>
-    <data name="#BA55D3" xml:space="preserve">
+  <data name="#BA55D3" xml:space="preserve">
     <value>MediumOrchid</value>
   </data>
-    <data name="#9370DB" xml:space="preserve">
+  <data name="#9370DB" xml:space="preserve">
     <value>MediumPurple</value>
   </data>
-    <data name="#3CB371" xml:space="preserve">
+  <data name="#3CB371" xml:space="preserve">
     <value>MediumSeaGreen</value>
   </data>
-    <data name="#7B68EE" xml:space="preserve">
+  <data name="#7B68EE" xml:space="preserve">
     <value>MediumSlateBlue</value>
   </data>
-    <data name="#00FA9A" xml:space="preserve">
+  <data name="#00FA9A" xml:space="preserve">
     <value>MediumSpringGreen</value>
   </data>
-    <data name="#48D1CC" xml:space="preserve">
+  <data name="#48D1CC" xml:space="preserve">
     <value>MediumTurquoise</value>
   </data>
-    <data name="#C71585" xml:space="preserve">
+  <data name="#C71585" xml:space="preserve">
     <value>MediumVioletRed</value>
   </data>
-    <data name="#191970" xml:space="preserve">
+  <data name="#191970" xml:space="preserve">
     <value>MidnightBlue</value>
   </data>
-    <data name="#F5FFFA" xml:space="preserve">
+  <data name="#F5FFFA" xml:space="preserve">
     <value>MintCream</value>
   </data>
-    <data name="#FFE4E1" xml:space="preserve">
+  <data name="#FFE4E1" xml:space="preserve">
     <value>MistyRose</value>
   </data>
-    <data name="#FFE4B5" xml:space="preserve">
+  <data name="#FFE4B5" xml:space="preserve">
     <value>Moccasin</value>
   </data>
-    <data name="#FFDEAD" xml:space="preserve">
+  <data name="#FFDEAD" xml:space="preserve">
     <value>NavajoWhite</value>
   </data>
-    <data name="#000080" xml:space="preserve">
+  <data name="#000080" xml:space="preserve">
     <value>Navy</value>
   </data>
-    <data name="#FDF5E6" xml:space="preserve">
+  <data name="#FDF5E6" xml:space="preserve">
     <value>OldLace</value>
   </data>
-    <data name="#808000" xml:space="preserve">
+  <data name="#808000" xml:space="preserve">
     <value>Olive</value>
   </data>
-    <data name="#6B8E23" xml:space="preserve">
+  <data name="#6B8E23" xml:space="preserve">
     <value>OliveDrab</value>
   </data>
-    <data name="#FFA500" xml:space="preserve">
+  <data name="#FFA500" xml:space="preserve">
     <value>Orange</value>
   </data>
-    <data name="#FF4500" xml:space="preserve">
+  <data name="#FF4500" xml:space="preserve">
     <value>OrangeRed</value>
   </data>
-    <data name="#DA70D6" xml:space="preserve">
+  <data name="#DA70D6" xml:space="preserve">
     <value>Orchid</value>
   </data>
-    <data name="#EEE8AA" xml:space="preserve">
+  <data name="#EEE8AA" xml:space="preserve">
     <value>PaleGoldenRod</value>
   </data>
-    <data name="#98FB98" xml:space="preserve">
+  <data name="#98FB98" xml:space="preserve">
     <value>PaleGreen</value>
   </data>
-    <data name="#AFEEEE" xml:space="preserve">
+  <data name="#AFEEEE" xml:space="preserve">
     <value>PaleTurquoise</value>
   </data>
-    <data name="#DB7093" xml:space="preserve">
+  <data name="#DB7093" xml:space="preserve">
     <value>PaleVioletRed</value>
   </data>
-    <data name="#FFEFD5" xml:space="preserve">
+  <data name="#FFEFD5" xml:space="preserve">
     <value>PapayaWhip</value>
   </data>
-    <data name="#FFDAB9" xml:space="preserve">
+  <data name="#FFDAB9" xml:space="preserve">
     <value>PeachPuff</value>
   </data>
-    <data name="#CD853F" xml:space="preserve">
+  <data name="#CD853F" xml:space="preserve">
     <value>Peru</value>
   </data>
-    <data name="#FFC0CB" xml:space="preserve">
+  <data name="#FFC0CB" xml:space="preserve">
     <value>Pink</value>
   </data>
-    <data name="#DDA0DD" xml:space="preserve">
+  <data name="#DDA0DD" xml:space="preserve">
     <value>Plum</value>
   </data>
-    <data name="#B0E0E6" xml:space="preserve">
+  <data name="#B0E0E6" xml:space="preserve">
     <value>PowderBlue</value>
   </data>
-    <data name="#800080" xml:space="preserve">
+  <data name="#800080" xml:space="preserve">
     <value>Purple</value>
   </data>
-    <data name="#663399" xml:space="preserve">
+  <data name="#663399" xml:space="preserve">
     <value>RebeccaPurple</value>
   </data>
-    <data name="#FF0000" xml:space="preserve">
+  <data name="#FF0000" xml:space="preserve">
     <value>Red</value>
   </data>
-    <data name="#BC8F8F" xml:space="preserve">
+  <data name="#BC8F8F" xml:space="preserve">
     <value>RosyBrown</value>
   </data>
-    <data name="#4169E1" xml:space="preserve">
+  <data name="#4169E1" xml:space="preserve">
     <value>RoyalBlue</value>
   </data>
-    <data name="#8B4513" xml:space="preserve">
+  <data name="#8B4513" xml:space="preserve">
     <value>SaddleBrown</value>
   </data>
-    <data name="#FA8072" xml:space="preserve">
+  <data name="#FA8072" xml:space="preserve">
     <value>Salmon</value>
   </data>
-    <data name="#F4A460" xml:space="preserve">
+  <data name="#F4A460" xml:space="preserve">
     <value>SandyBrown</value>
   </data>
-    <data name="#2E8B57" xml:space="preserve">
+  <data name="#2E8B57" xml:space="preserve">
     <value>SeaGreen</value>
   </data>
-    <data name="#FFF5EE" xml:space="preserve">
+  <data name="#FFF5EE" xml:space="preserve">
     <value>SeaShell</value>
   </data>
-    <data name="#A0522D" xml:space="preserve">
+  <data name="#A0522D" xml:space="preserve">
     <value>Sienna</value>
   </data>
-    <data name="#C0C0C0" xml:space="preserve">
+  <data name="#C0C0C0" xml:space="preserve">
     <value>Silver</value>
   </data>
-    <data name="#87CEEB" xml:space="preserve">
+  <data name="#87CEEB" xml:space="preserve">
     <value>SkyBlue</value>
   </data>
-    <data name="#6A5ACD" xml:space="preserve">
+  <data name="#6A5ACD" xml:space="preserve">
     <value>SlateBlue</value>
   </data>
-    <data name="#708090" xml:space="preserve">
+  <data name="#708090" xml:space="preserve">
     <value>SlateGray</value>
   </data>
-    <data name="#FFFAFA" xml:space="preserve">
+  <data name="#FFFAFA" xml:space="preserve">
     <value>Snow</value>
   </data>
-    <data name="#00FF7F" xml:space="preserve">
+  <data name="#00FF7F" xml:space="preserve">
     <value>SpringGreen</value>
   </data>
-    <data name="#4682B4" xml:space="preserve">
+  <data name="#4682B4" xml:space="preserve">
     <value>SteelBlue</value>
   </data>
-    <data name="#D2B48C" xml:space="preserve">
+  <data name="#D2B48C" xml:space="preserve">
     <value>Tan</value>
   </data>
-    <data name="#008080" xml:space="preserve">
+  <data name="#008080" xml:space="preserve">
     <value>Teal</value>
   </data>
-    <data name="#D8BFD8" xml:space="preserve">
+  <data name="#D8BFD8" xml:space="preserve">
     <value>Thistle</value>
   </data>
-    <data name="#FF6347" xml:space="preserve">
+  <data name="#FF6347" xml:space="preserve">
     <value>Tomato</value>
   </data>
-    <data name="#40E0D0" xml:space="preserve">
+  <data name="#40E0D0" xml:space="preserve">
     <value>Turquoise</value>
   </data>
-    <data name="#EE82EE" xml:space="preserve">
+  <data name="#EE82EE" xml:space="preserve">
     <value>Violet</value>
   </data>
-    <data name="#F5DEB3" xml:space="preserve">
+  <data name="#F5DEB3" xml:space="preserve">
     <value>Wheat</value>
   </data>
-    <data name="#FFFFFF" xml:space="preserve">
+  <data name="#FFFFFF" xml:space="preserve">
     <value>White</value>
   </data>
-    <data name="#F5F5F5" xml:space="preserve">
+  <data name="#F5F5F5" xml:space="preserve">
     <value>WhiteSmoke</value>
   </data>
-    <data name="#FFFF00" xml:space="preserve">
+  <data name="#FFFF00" xml:space="preserve">
     <value>Yellow</value>
   </data>
-    <data name="#9ACD32" xml:space="preserve">
+  <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>
+  <data name="ctxColors" xml:space="preserve">
+    <value>Co_lors</value>
+  </data>
 </root>

+ 9 - 6
Terminal.Gui/Resources/Strings.zh-Hans.resx

@@ -153,9 +153,6 @@
   <data name="fdOpen" xml:space="preserve">
     <value>打开</value>
   </data>
-  <data name="wzNext" xml:space="preserve">
-    <value>下一步 (_N)...</value>
-  </data>
   <data name="fdSelectFolder" xml:space="preserve">
     <value>选择文件夹 (_S)</value>
   </data>
@@ -168,6 +165,9 @@
   <data name="wzFinish" xml:space="preserve">
     <value>结束 (_N)</value>
   </data>
+  <data name="wzNext" xml:space="preserve">
+    <value>下一步 (_N)...</value>
+  </data>
   <data name="fdDirectoryAlreadyExistsFeedback" xml:space="preserve">
     <value>已存在相同名称的目录</value>
   </data>
@@ -240,9 +240,6 @@
   <data name="fdExisting" xml:space="preserve">
     <value>已有</value>
   </data>
-  <data name="btnOk" xml:space="preserve">
-    <value>确定 (_O)</value>
-  </data>
   <data name="btnOpen" xml:space="preserve">
     <value>打开 (_O)</value>
   </data>
@@ -252,6 +249,9 @@
   <data name="btnSaveAs" xml:space="preserve">
     <value>另存为 (_S)</value>
   </data>
+  <data name="btnOk" xml:space="preserve">
+    <value>确定 (_O)</value>
+  </data>
   <data name="btnCancel" xml:space="preserve">
     <value>取消 (_C)</value>
   </data>
@@ -276,4 +276,7 @@
   <data name="dpTitle" xml:space="preserve">
     <value>日期选择器</value>
   </data>
+  <data name="ctxColors" xml:space="preserve">
+    <value>旗帜 (_L)</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"
+              }
+            }
+          }
+        ]
+      }
     }
   ]
 }

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

@@ -60,7 +60,7 @@
     <!-- Enable Nuget Source Link for github -->
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="[8,9)" PrivateAssets="all" />
     <PackageReference Include="System.IO.Abstractions" Version="[21.0.22,22)" />
-    <PackageReference Include="System.Text.Json" Version="[8.0.4,9)" />
+    <PackageReference Include="System.Text.Json" Version="[8.0.5,9)" />
     <PackageReference Include="Wcwidth" Version="[2,3)" />
   </ItemGroup>
   <!-- =================================================================== -->
@@ -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>

+ 2 - 2
Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs

@@ -7,7 +7,7 @@ namespace Terminal.Gui;
 public class AutocompleteContext
 {
     /// <summary>Creates a new instance of the <see cref="AutocompleteContext"/> class</summary>
-    public AutocompleteContext (List<RuneCell> currentLine, int cursorPosition, bool canceled = false)
+    public AutocompleteContext (List<Cell> currentLine, int cursorPosition, bool canceled = false)
     {
         CurrentLine = currentLine;
         CursorPosition = cursorPosition;
@@ -18,7 +18,7 @@ public class AutocompleteContext
     public bool Canceled { get; set; }
 
     /// <summary>The text on the current line.</summary>
-    public List<RuneCell> CurrentLine { get; set; }
+    public List<Cell> CurrentLine { get; set; }
 
     /// <summary>The position of the input cursor within the <see cref="CurrentLine"/>.</summary>
     public int CursorPosition { get; set; }

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

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

@@ -6,7 +6,7 @@ namespace Terminal.Gui;
 internal class AutocompleteFilepathContext : AutocompleteContext
 {
     public AutocompleteFilepathContext (string currentLine, int cursorPosition, FileDialogState state)
-        : base (TextModel.ToRuneCellList (currentLine), cursorPosition)
+        : base (Cell.ToCellList (currentLine), cursorPosition)
     {
         State = state;
     }
@@ -30,7 +30,7 @@ internal class FilepathSuggestionGenerator : ISuggestionGenerator
             return Enumerable.Empty<Suggestion> ();
         }
 
-        var path = TextModel.ToString (context.CurrentLine);
+        var path = Cell.ToString (context.CurrentLine);
         int last = path.LastIndexOfAny (FileDialog.Separators);
 
         if (string.IsNullOrWhiteSpace (path) || !Path.IsPathRooted (path))

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

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác