Kaynağa Gözat

Merge pull request #3718 from gui-cs/v2_develop

Merge v2_develop in to v2_release
Tig 11 ay önce
ebeveyn
işleme
9b87f52e24
100 değiştirilmiş dosya ile 4943 ekleme ve 2210 silme
  1. 20 2
      .github/workflows/dotnet-core.yml
  2. 1 0
      .github/workflows/publish.yml
  3. 7 1
      CONTRIBUTING.md
  4. 22 0
      NativeAot/NativeAot.csproj
  5. 113 0
      NativeAot/Program.cs
  6. 18 0
      NativeAot/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Debug.pubxml
  7. 18 0
      NativeAot/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Release.pubxml
  8. 13 0
      NativeAot/Properties/launchSettings.json
  9. 5 0
      NativeAot/Publish_linux-x64_Debug.sh
  10. 5 0
      NativeAot/Publish_linux-x64_Release.sh
  11. 5 0
      NativeAot/Publish_osx-x64_Debug.sh
  12. 5 0
      NativeAot/Publish_osx-x64_Release.sh
  13. 10 0
      NativeAot/README.md
  14. 1 1
      README.md
  15. 165 198
      Terminal.Gui/Application/Application.Keyboard.cs
  16. 60 44
      Terminal.Gui/Application/Application.Mouse.cs
  17. 9 3
      Terminal.Gui/Application/Application.Run.cs
  18. 10 4
      Terminal.Gui/Application/Application.cs
  19. 33 159
      Terminal.Gui/Application/ApplicationNavigation.cs
  20. 3 3
      Terminal.Gui/Application/ApplicationOverlapped.cs
  21. 1 0
      Terminal.Gui/Application/MainLoop.cs
  22. 6 6
      Terminal.Gui/Configuration/SourceGenerationContext.cs
  23. 16 1
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  24. 2 1
      Terminal.Gui/Drawing/Alignment.cs
  25. 2 1
      Terminal.Gui/Drawing/AlignmentModes.cs
  26. 1 1
      Terminal.Gui/Drawing/Color.Formatting.cs
  27. 2 1
      Terminal.Gui/Drawing/Color.cs
  28. 11 0
      Terminal.Gui/Drawing/ColorEventArgs.cs
  29. 25 0
      Terminal.Gui/Drawing/ColorModel.cs
  30. 79 0
      Terminal.Gui/Drawing/ColorStrings.cs
  31. 34 0
      Terminal.Gui/Drawing/IColorNameResolver.cs
  32. 3 0
      Terminal.Gui/Drawing/LineStyle.cs
  33. 4 4
      Terminal.Gui/Drawing/Thickness.cs
  34. 24 0
      Terminal.Gui/Drawing/W3CColors.cs
  35. 21 4
      Terminal.Gui/Input/Key.cs
  36. 1 0
      Terminal.Gui/Input/Responder.cs
  37. 2 2
      Terminal.Gui/Input/ShortcutHelper.cs
  38. 70 0
      Terminal.Gui/Resources/GlobalResources.cs
  39. 112 0
      Terminal.Gui/Resources/ResourceManagerWrapper.cs
  40. 1251 0
      Terminal.Gui/Resources/Strings.Designer.cs
  41. 527 110
      Terminal.Gui/Resources/Strings.resx
  42. 1 0
      Terminal.Gui/Resources/config.json
  43. 3 6
      Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs
  44. 33 36
      Terminal.Gui/Text/Autocomplete/IAutocomplete.cs
  45. 5 4
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.PopUp.cs
  46. 98 70
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  47. 1 0
      Terminal.Gui/Text/CollectionNavigatorBase.cs
  48. 1 1
      Terminal.Gui/View/Adornment/Adornment.cs
  49. 4 1
      Terminal.Gui/View/Adornment/ShadowStyle.cs
  50. 1 1
      Terminal.Gui/View/Adornment/ShadowView.cs
  51. 26 28
      Terminal.Gui/View/Layout/Dim.cs
  52. 3 9
      Terminal.Gui/View/Layout/DimAbsolute.cs
  53. 32 74
      Terminal.Gui/View/Layout/DimAuto.cs
  54. 7 7
      Terminal.Gui/View/Layout/DimCombine.cs
  55. 3 14
      Terminal.Gui/View/Layout/DimFill.cs
  56. 7 13
      Terminal.Gui/View/Layout/DimFunc.cs
  57. 6 17
      Terminal.Gui/View/Layout/DimPercent.cs
  58. 1 7
      Terminal.Gui/View/Layout/DimView.cs
  59. 10 17
      Terminal.Gui/View/Layout/Pos.cs
  60. 3 9
      Terminal.Gui/View/Layout/PosAbsolute.cs
  61. 93 106
      Terminal.Gui/View/Layout/PosAlign.cs
  62. 1 7
      Terminal.Gui/View/Layout/PosAnchorEnd.cs
  63. 1 1
      Terminal.Gui/View/Layout/PosCenter.cs
  64. 7 7
      Terminal.Gui/View/Layout/PosCombine.cs
  65. 4 21
      Terminal.Gui/View/Layout/PosFunc.cs
  66. 3 9
      Terminal.Gui/View/Layout/PosPercent.cs
  67. 5 11
      Terminal.Gui/View/Layout/PosView.cs
  68. 15 19
      Terminal.Gui/View/Navigation/FocusEventArgs.cs
  69. 10 2
      Terminal.Gui/View/Navigation/TabBehavior.cs
  70. 4 3
      Terminal.Gui/View/Orientation/IOrientation.cs
  71. 2 1
      Terminal.Gui/View/Orientation/Orientation.cs
  72. 4 3
      Terminal.Gui/View/Orientation/OrientationHelper.cs
  73. 7 9
      Terminal.Gui/View/SuperViewChangedEventArgs.cs
  74. 1 0
      Terminal.Gui/View/View.Adornments.cs
  75. 4 4
      Terminal.Gui/View/View.Content.cs
  76. 1 0
      Terminal.Gui/View/View.Cursor.cs
  77. 1 3
      Terminal.Gui/View/View.Diagnostics.cs
  78. 8 6
      Terminal.Gui/View/View.Drawing.cs
  79. 95 85
      Terminal.Gui/View/View.Hierarchy.cs
  80. 47 51
      Terminal.Gui/View/View.Keyboard.cs
  81. 9 9
      Terminal.Gui/View/View.Layout.cs
  82. 10 10
      Terminal.Gui/View/View.Mouse.cs
  83. 423 540
      Terminal.Gui/View/View.Navigation.cs
  84. 1 1
      Terminal.Gui/View/View.Text.cs
  85. 41 25
      Terminal.Gui/View/View.cs
  86. 27 0
      Terminal.Gui/Views/BBar.cs
  87. 7 6
      Terminal.Gui/Views/Bar.cs
  88. 0 4
      Terminal.Gui/Views/Button.cs
  89. 235 0
      Terminal.Gui/Views/ColorBar.cs
  90. 167 0
      Terminal.Gui/Views/ColorModelStrategy.cs
  91. 277 186
      Terminal.Gui/Views/ColorPicker.cs
  92. 271 0
      Terminal.Gui/Views/ColorPicker16.cs
  93. 25 0
      Terminal.Gui/Views/ColorPickerStyle.cs
  94. 51 50
      Terminal.Gui/Views/ComboBox.cs
  95. 8 3
      Terminal.Gui/Views/DatePicker.cs
  96. 18 15
      Terminal.Gui/Views/Dialog.cs
  97. 66 149
      Terminal.Gui/Views/FileDialog.cs
  98. 1 4
      Terminal.Gui/Views/FrameView.cs
  99. 27 0
      Terminal.Gui/Views/GBar.cs
  100. 4 0
      Terminal.Gui/Views/HexView.cs

+ 20 - 2
.github/workflows/dotnet-core.yml

@@ -11,7 +11,7 @@ on:
       - '**.md'
       
 jobs:
-  build_and_test:
+  build_and_test_debug:
 
     runs-on: ${{ matrix.os }}
     strategy:
@@ -23,7 +23,7 @@ jobs:
     timeout-minutes: 10
     steps:
 
-# Build
+# Build (Debug)
 
     - name: Checkout code
       uses: actions/checkout@v4
@@ -76,6 +76,24 @@ jobs:
           logs/    
           UnitTests/TestResults/
   
+
+  build_release:
+    # Ensure that RELEASE builds are not broken
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout code
+      uses: actions/checkout@v4
+
+    - name: Setup .NET Core
+      uses: actions/setup-dotnet@v4
+      with:
+        dotnet-version: 8.x
+        dotnet-quality: 'ga'
+
+    - name: Build Release
+      run: dotnet build --configuration Release
+
+
     # Note: this step is currently not writing to the gist for some reason
     # - name: Create Test Coverage Badge
     #   uses: simon-k/[email protected]

+ 1 - 0
.github/workflows/publish.yml

@@ -44,6 +44,7 @@ jobs:
       run: |
         dotnet-gitversion /updateprojectfiles
         dotnet build --no-incremental --nologo --force --configuration Release
+        dotnet test --configuration Release
 
     - name: Pack
       run: dotnet pack -c Release --include-symbols -p:Version='${{ steps.gitversion.outputs.SemVer }}' 

+ 7 - 1
CONTRIBUTING.md

@@ -90,7 +90,13 @@ remote:
 
 Follow the template instructions found on Github.
 
-## Terminal.Gui Coding Style
+## Tenets for [gui-cs](www.github.com/gui-cs) Code Style (Unless you have better ones)
+
+* **Six-Year-Old Reading Level** - Our code style is biased towards code readability and away from terseness. This is *Systems Software* and needs to stand the test of time. Code should be structured and use variable names that make it readable by a 6-year-old, and comments in code are encouraged. 
+* **Consistency, Consistency, Consistency** - We adopt and document our standards for code style and then enforce them ruthlessly. For example, we require code reviews to pay attention to code style, not just functionality. 
+* **Don't be Weird** - Like all developers we have opinions, but our opinions on code style are tempered by existing standards. We are biased towards code style that used by Microsoft and other leading dotnet developers. For example, we choose 4 spaces for indentation instead of 8.
+* **Set and Forget** - We embrace and encourage the use of technology that makes it easy for contributors to apply best-practice code-style, such as ReSharper. As we do so we are mindful that tools can cause hidden issues and merge hell.
+* **Documentation is the Spec** - We care deeply about providing delightful developer documentation and are sticklers for grammar and clarity. If the code and the docs conflict, we are biased to believe what we wrote in the API documentation. This drives a virtuous cycle of clear thinking.
 
 **Terminal.Gui** uses a derivative of the [Microsoft C# Coding Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions), with any deviations from those (somewhat older) conventions codified in the .editorconfig for the solution, as well as even more specific definitions in team-shared dotsettings files, used by ReSharper and Rider.\
 Before you commit code, please run the formatting rules on **only the code file(s) you have modified**, in one of the following ways, in order of most preferred to least preferred:

+ 22 - 0
NativeAot/NativeAot.csproj

@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <PublishAot>true</PublishAot>
+    <InvariantGlobalization>false</InvariantGlobalization>
+  </PropertyGroup>
+
+  <ItemGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
+    <TrimmerRootAssembly Include="Terminal.Gui" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <PackageReference Include="Terminal.Gui" Version="[2.0.0-v2-develop.2189,3)" />
+    <TrimmerRootAssembly Include="Terminal.Gui" />
+  </ItemGroup>
+
+</Project>

+ 113 - 0
NativeAot/Program.cs

@@ -0,0 +1,113 @@
+// This is a test application for a native Aot file.
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Terminal.Gui;
+
+namespace NativeAot;
+
+public static class Program
+{
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(ConsoleDriver, String)")]
+    [RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(ConsoleDriver, String)")]
+    private static void Main (string [] args)
+    {
+        Application.Init ();
+
+        #region The code in this region is not intended for use in a native Aot self-contained. It's just here to make sure there is no functionality break with localization in Terminal.Gui using self-contained
+
+        if (Equals(Thread.CurrentThread.CurrentUICulture, CultureInfo.InvariantCulture) && Application.SupportedCultures!.Count == 0)
+        {
+            // Only happens if the project has <InvariantGlobalization>true</InvariantGlobalization>
+            Debug.Assert (Application.SupportedCultures.Count == 0);
+        }
+        else
+        {
+            Debug.Assert (Application.SupportedCultures!.Count > 0);
+            Debug.Assert (Equals (CultureInfo.CurrentCulture, Thread.CurrentThread.CurrentUICulture));
+        }
+
+        #endregion
+
+        ExampleWindow app = new ();
+        Application.Run (app);
+
+        // Dispose the app object before shutdown
+        app.Dispose ();
+
+        // Before the application exits, reset Terminal.Gui for clean shutdown
+        Application.Shutdown ();
+
+        // To see this output on the screen it must be done after shutdown,
+        // which restores the previous screen.
+        Console.WriteLine ($@"Username: {ExampleWindow.UserName}");
+    }
+}
+
+// Defines a top-level window with border and title
+public class ExampleWindow : Window
+{
+    public static string? UserName;
+
+    public ExampleWindow ()
+    {
+        Title = $"Example App ({Application.QuitKey} to quit)";
+
+        // Create input components and labels
+        var usernameLabel = new Label { Text = "Username:" };
+
+        var userNameText = new TextField
+        {
+            // Position text field adjacent to the label
+            X = Pos.Right (usernameLabel) + 1,
+
+            // Fill remaining horizontal space
+            Width = Dim.Fill ()
+        };
+
+        var passwordLabel = new Label
+        {
+            Text = "Password:", X = Pos.Left (usernameLabel), Y = Pos.Bottom (usernameLabel) + 1
+        };
+
+        var passwordText = new TextField
+        {
+            Secret = true,
+
+            // align with the text box above
+            X = Pos.Left (userNameText),
+            Y = Pos.Top (passwordLabel),
+            Width = Dim.Fill ()
+        };
+
+        // Create login button
+        var btnLogin = new Button
+        {
+            Text = "Login",
+            Y = Pos.Bottom (passwordLabel) + 1,
+
+            // center the login button horizontally
+            X = Pos.Center (),
+            IsDefault = true
+        };
+
+        // When login button is clicked display a message popup
+        btnLogin.Accept += (s, e) =>
+        {
+            if (userNameText.Text == "admin" && passwordText.Text == "password")
+            {
+                MessageBox.Query ("Logging In", "Login Successful", "Ok");
+                UserName = userNameText.Text;
+                Application.RequestStop ();
+            }
+            else
+            {
+                MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");
+            }
+        };
+
+        // Add the views to the Window
+        Add (usernameLabel, userNameText, passwordLabel, passwordText, btnLogin);
+    }
+}

+ 18 - 0
NativeAot/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Debug.pubxml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project>
+  <PropertyGroup>
+    <Configuration>Debug</Configuration>
+    <Platform>Any CPU</Platform>
+    <PublishDir>bin\Debug\net8.0\publish\win-x64\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+    <TargetFramework>net8.0</TargetFramework>
+    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
+    <SelfContained>true</SelfContained>
+    <PublishSingleFile>false</PublishSingleFile>
+    <PublishReadyToRun>false</PublishReadyToRun>
+  </PropertyGroup>
+</Project>

+ 18 - 0
NativeAot/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Release.pubxml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project>
+  <PropertyGroup>
+    <Configuration>Release</Configuration>
+    <Platform>Any CPU</Platform>
+    <PublishDir>bin\Release\net8.0\publish\win-x64\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+    <TargetFramework>net8.0</TargetFramework>
+    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
+    <SelfContained>true</SelfContained>
+    <PublishSingleFile>false</PublishSingleFile>
+    <PublishReadyToRun>false</PublishReadyToRun>
+  </PropertyGroup>
+</Project>

+ 13 - 0
NativeAot/Properties/launchSettings.json

@@ -0,0 +1,13 @@
+{
+  "profiles": {
+    "NativeAot": {
+      "commandName": "Project"
+    },
+    "WSL : UICatalog": {
+      "commandName": "Executable",
+      "executablePath": "wsl",
+      "commandLineArgs": "dotnet NativeAot.dll",
+      "distributionName": ""
+    }
+  }
+}

+ 5 - 0
NativeAot/Publish_linux-x64_Debug.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+dotnet clean
+dotnet build
+dotnet publish -c Debug -r linux-x64 --self-contained

+ 5 - 0
NativeAot/Publish_linux-x64_Release.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+dotnet clean
+dotnet build
+dotnet publish -c Release -r linux-x64 --self-contained

+ 5 - 0
NativeAot/Publish_osx-x64_Debug.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+dotnet clean
+dotnet build
+dotnet publish -c Debug -r osx-x64 --self-contained

+ 5 - 0
NativeAot/Publish_osx-x64_Release.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+dotnet clean
+dotnet build
+dotnet publish -c Release -r osx-x64 --self-contained

+ 10 - 0
NativeAot/README.md

@@ -0,0 +1,10 @@
+# Terminal.Gui C# SelfContained
+
+This project aims to test the `Terminal.Gui` library to create a simple `native AOT` `self-container` GUI application in C#, ensuring that all its features are available.
+
+With `Debug` the `.csproj` is used and with `Release` the latest `nuget package` is used, either in `Solution Configurations` or in `Profile Publish` or in the `Publish_linux-x64` or in the `Publish_osx-x64` files.
+Unlike self-contained single-file publishing, native AOT publishing must be generated on the same platform as the target execution version. Therefore, if the target execution is Linux, then the publishing must be generated on a Linux operating system. Attempting to generate on Windows for the Linux target will throw an exception.
+
+To publish the `native AOT` file in `Debug` or `Release` mode, it is not necessary to select it in the `Solution Configurations`, just choose the `Debug` or `Release` configuration in the `Publish Profile` or the `*.sh` files.
+
+When executing the file directly from the `native AOT` file and needing to debug it, it will be necessary to attach it to the debugger, just like any other standalone application and selecting `Native Code`.

+ 1 - 1
README.md

@@ -35,7 +35,7 @@ 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 Documentation matches the most recent Nuget release from the `v2_develop` branch. The documentation for v1 is here: ([![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui))_
+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)
 
 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. 
 

+ 165 - 198
Terminal.Gui/Application/Application.Keyboard.cs

@@ -1,51 +1,52 @@
 #nullable enable
-using System.Text.Json.Serialization;
-
 namespace Terminal.Gui;
 
 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
 
-    /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
-    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
-    public static Key NextTabKey
-    {
-        get => _nextTabKey;
-        set
-        {
-            if (_nextTabKey != value)
-            {
-                ReplaceKey (_nextTabKey, value);
-                _nextTabKey = value;
-            }
-        }
-    }
+    private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrrides
 
     private static Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrrides
 
-    /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
-    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
-    public static Key PrevTabKey
-    {
-        get => _prevTabKey;
-        set
-        {
-            if (_prevTabKey != value)
-            {
-                ReplaceKey (_prevTabKey, value);
-                _prevTabKey = value;
-            }
-        }
-    }
+    private static Key _quitKey = Key.Esc; // Resources/config.json overrrides
 
-    private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrrides
+    static Application () { AddApplicationKeyBindings (); }
+
+    /// <summary>Gets the key bindings for this view.</summary>
+    public static KeyBindings KeyBindings { get; internal set; } = new ();
+
+    /// <summary>
+    ///     Event fired when the user presses a key. Fired by <see cref="OnKeyDown"/>.
+    ///     <para>
+    ///         Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
+    ///         additional processing.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
+    ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+    ///     <para>Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.</para>
+    /// </remarks>
+    public static event EventHandler<Key>? KeyDown;
+
+    /// <summary>
+    ///     Event fired when the user releases a key. Fired by <see cref="OnKeyUp"/>.
+    ///     <para>
+    ///         Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
+    ///         additional processing.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
+    ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+    ///     <para>Fired after <see cref="KeyDown"/>.</para>
+    /// </remarks>
+    public static event EventHandler<Key>? KeyUp;
 
     /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
     public static Key NextTabGroupKey
     {
         get => _nextTabGroupKey;
@@ -59,73 +60,21 @@ public static partial class Application // Keyboard handling
         }
     }
 
-    private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrrides
-
-    /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
-    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
-    public static Key PrevTabGroupKey
-    {
-        get => _prevTabGroupKey;
-        set
-        {
-            if (_prevTabGroupKey != value)
-            {
-                ReplaceKey (_prevTabGroupKey, value);
-                _prevTabGroupKey = value;
-            }
-        }
-    }
-
-    private static Key _quitKey = Key.Esc; // Resources/config.json overrrides
-
-    /// <summary>Gets or sets the key to quit the application.</summary>
+    /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
-    public static Key QuitKey
+    public static Key NextTabKey
     {
-        get => _quitKey;
+        get => _nextTabKey;
         set
         {
-            if (_quitKey != value)
+            if (_nextTabKey != value)
             {
-                ReplaceKey (_quitKey, value);
-                _quitKey = value;
+                ReplaceKey (_nextTabKey, value);
+                _nextTabKey = value;
             }
         }
     }
 
-    private static void ReplaceKey (Key oldKey, Key newKey)
-    {
-        if (KeyBindings.Bindings.Count == 0)
-        {
-            return;
-        }
-
-        if (newKey == Key.Empty)
-        {
-            KeyBindings.Remove (oldKey);
-        }
-        else
-        {
-            KeyBindings.ReplaceKey (oldKey, newKey);
-        }
-    }
-
-    /// <summary>
-    ///     Event fired when the user presses a key. Fired by <see cref="OnKeyDown"/>.
-    ///     <para>
-    ///         Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
-    ///         additional processing.
-    ///     </para>
-    /// </summary>
-    /// <remarks>
-    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
-    ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
-    ///     <para>Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.</para>
-    /// </remarks>
-    public static event EventHandler<Key>? KeyDown;
-
     /// <summary>
     ///     Called by the <see cref="ConsoleDriver"/> when the user presses a key. Fires the <see cref="KeyDown"/> event
     ///     then calls <see cref="View.NewKeyDownEvent"/> on all top level views. Called after <see cref="OnKeyDown"/> and
@@ -195,24 +144,7 @@ public static partial class Application // Keyboard handling
 
                 foreach (Command command in appBinding.Commands)
                 {
-                    if (!CommandImplementations.ContainsKey (command))
-                    {
-                        throw new NotSupportedException (
-                                                         @$"A KeyBinding was set up for the command {command} ({keyEvent}) but that command is not supported by Application."
-                                                        );
-                    }
-
-                    if (CommandImplementations.TryGetValue (command, out Func<CommandContext, bool?>? implementation))
-                    {
-                        var context = new CommandContext (command, keyEvent, appBinding); // Create the context here
-                        toReturn = implementation (context);
-                    }
-
-                    // if ever see a true then that's what we will return
-                    if (toReturn ?? false)
-                    {
-                        toReturn = true;
-                    }
+                    toReturn = InvokeCommand (command, keyEvent, appBinding);
                 }
 
                 return toReturn ?? true;
@@ -223,18 +155,30 @@ public static partial class Application // Keyboard handling
     }
 
     /// <summary>
-    ///     Event fired when the user releases a key. Fired by <see cref="OnKeyUp"/>.
-    ///     <para>
-    ///         Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
-    ///         additional processing.
-    ///     </para>
+    /// INTENRAL method to invoke one of the commands in <see cref="CommandImplementations"/>
     /// </summary>
-    /// <remarks>
-    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
-    ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
-    ///     <para>Fired after <see cref="KeyDown"/>.</para>
-    /// </remarks>
-    public static event EventHandler<Key>? KeyUp;
+    /// <param name="command"></param>
+    /// <param name="keyEvent"></param>
+    /// <param name="appBinding"></param>
+    /// <returns></returns>
+    /// <exception cref="NotSupportedException"></exception>
+    private static bool? InvokeCommand (Command command, Key keyEvent, KeyBinding appBinding)
+    {
+        if (!CommandImplementations!.ContainsKey (command))
+        {
+            throw new NotSupportedException (
+                                             @$"A KeyBinding was set up for the command {command} ({keyEvent}) but that command is not supported by Application."
+                                            );
+        }
+
+        if (CommandImplementations.TryGetValue (command, out Func<CommandContext, bool?>? implementation))
+        {
+            var context = new CommandContext (command, keyEvent, appBinding); // Create the context here
+            return implementation (context);
+        }
+
+        return false;
+    }
 
     /// <summary>
     ///     Called by the <see cref="ConsoleDriver"/> when the user releases a key. Fires the <see cref="KeyUp"/> event
@@ -273,33 +217,50 @@ public static partial class Application // Keyboard handling
         return false;
     }
 
-    /// <summary>Gets the key bindings for this view.</summary>
-    public static KeyBindings KeyBindings { get; internal set; } = new ();
-
-    /// <summary>
-    ///     Commands for Application.
-    /// </summary>
-    private static Dictionary<Command, Func<CommandContext, bool?>> CommandImplementations { get; set; }
+    /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static Key PrevTabGroupKey
+    {
+        get => _prevTabGroupKey;
+        set
+        {
+            if (_prevTabGroupKey != value)
+            {
+                ReplaceKey (_prevTabGroupKey, value);
+                _prevTabGroupKey = value;
+            }
+        }
+    }
 
-    /// <summary>
-    ///     <para>
-    ///         Sets the function that will be invoked for a <see cref="Command"/>.
-    ///     </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"/>.
-    ///     </para>
-    /// </remarks>
-    /// <param name="command">The command.</param>
-    /// <param name="f">The function.</param>
-    private static void AddCommand (Command command, Func<bool?> f) { CommandImplementations [command] = ctx => f (); }
+    /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static Key PrevTabKey
+    {
+        get => _prevTabKey;
+        set
+        {
+            if (_prevTabKey != value)
+            {
+                ReplaceKey (_prevTabKey, value);
+                _prevTabKey = value;
+            }
+        }
+    }
 
-    static Application () { AddApplicationKeyBindings (); }
+    /// <summary>Gets or sets the key to quit the application.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static Key QuitKey
+    {
+        get => _quitKey;
+        set
+        {
+            if (_quitKey != value)
+            {
+                ReplaceKey (_quitKey, value);
+                _quitKey = value;
+            }
+        }
+    }
 
     internal static void AddApplicationKeyBindings ()
     {
@@ -308,7 +269,7 @@ public static partial class Application // Keyboard handling
         // Things this view knows how to do
         AddCommand (
                     Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic.
-                    () =>
+                    static () =>
                     {
                         if (ApplicationOverlapped.OverlappedTop is { })
                         {
@@ -325,7 +286,7 @@ public static partial class Application // Keyboard handling
 
         AddCommand (
                     Command.Suspend,
-                    () =>
+                    static () =>
                     {
                         Driver?.Suspend ();
 
@@ -335,47 +296,47 @@ public static partial class Application // Keyboard handling
 
         AddCommand (
                     Command.NextView,
-                    () =>
-                    {
-                        ApplicationNavigation.MoveNextView ();
-
-                        return true;
-                    }
-                   );
+                    static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));
 
         AddCommand (
                     Command.PreviousView,
-                    () =>
-                    {
-                        ApplicationNavigation.MovePreviousView ();
-
-                        return true;
-                    }
-                   );
+                    static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop));
 
         AddCommand (
                     Command.NextViewOrTop,
-                    () =>
+                    static () =>
                     {
-                        ApplicationNavigation.MoveNextViewOrTop ();
+                        // TODO: This OverlapppedTop tomfoolery goes away in addressing #2491
+                        if (ApplicationOverlapped.OverlappedTop is { })
+                        {
+                            ApplicationOverlapped.OverlappedMoveNext ();
 
-                        return true;
+                            return true;
+                        }
+
+                        return Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
                     }
                    );
 
         AddCommand (
                     Command.PreviousViewOrTop,
-                    () =>
+                    static () =>
                     {
-                        ApplicationNavigation.MovePreviousViewOrTop ();
+                        // TODO: This OverlapppedTop tomfoolery goes away in addressing #2491
+                        if (ApplicationOverlapped.OverlappedTop is { })
+                        {
+                            ApplicationOverlapped.OverlappedMovePrevious ();
 
-                        return true;
+                            return true;
+                        }
+
+                        return Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup);
                     }
                    );
 
         AddCommand (
                     Command.Refresh,
-                    () =>
+                    static () =>
                     {
                         Refresh ();
 
@@ -401,8 +362,8 @@ public static partial class Application // Keyboard handling
         KeyBindings.Add (NextTabKey, KeyBindingScope.Application, Command.NextView);
         KeyBindings.Add (PrevTabKey, KeyBindingScope.Application, Command.PreviousView);
 
-        KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextViewOrTop); // Needed on Unix
-        KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousViewOrTop); // Needed on Unix
+        KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextViewOrTop);
+        KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousViewOrTop);
 
         // TODO: Refresh Key should be configurable
         KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
@@ -412,13 +373,6 @@ public static partial class Application // Keyboard handling
         {
             KeyBindings.Add (Key.Z.WithCtrl, KeyBindingScope.Application, Command.Suspend);
         }
-
-#if UNIX_KEY_BINDINGS
-        KeyBindings.Add (Key.L.WithCtrl, Command.Refresh); // Unix
-        KeyBindings.Add (Key.F.WithCtrl, Command.NextView); // Unix
-        KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix
-        KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView); // Unix
-#endif
     }
 
     /// <summary>
@@ -438,30 +392,43 @@ public static partial class Application // Keyboard handling
                           .ToList ();
     }
 
-    ///// <summary>
-    /////     Gets the list of Views that have <see cref="KeyBindingScope.Application"/> key bindings for the specified key.
-    ///// </summary>
-    ///// <remarks>
-    /////     This is an internal method used by the <see cref="View"/> class to add Application key bindings.
-    ///// </remarks>
-    ///// <param name="key">The key to check.</param>
-    ///// <param name="views">Outputs the list of views bound to <paramref name="key"/></param>
-    ///// <returns><see langword="True"/> if successful.</returns>
-    //internal static bool TryGetKeyBindings (Key key, out List<View> views) { return _keyBindings.TryGetValue (key, out views); }
-
     /// <summary>
-    ///     Removes all <see cref="KeyBindingScope.Application"/> scoped key bindings for the specified view.
+    ///     <para>
+    ///         Sets the function that will be invoked for a <see cref="Command"/>.
+    ///     </para>
+    ///     <para>
+    ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
+    ///         replace the old one.
+    ///     </para>
     /// </summary>
     /// <remarks>
-    ///     This is an internal method used by the <see cref="View"/> class to remove Application key bindings.
+    ///     <para>
+    ///         This version of AddCommand is for commands that do not require a <see cref="CommandContext"/>.
+    ///     </para>
     /// </remarks>
-    /// <param name="view">The view that is bound to the key.</param>
-    internal static void RemoveKeyBindings (View view)
+    /// <param name="command">The command.</param>
+    /// <param name="f">The function.</param>
+    private static void AddCommand (Command command, Func<bool?> f) { CommandImplementations! [command] = ctx => f (); }
+
+    /// <summary>
+    ///     Commands for Application.
+    /// </summary>
+    private static Dictionary<Command, Func<CommandContext, bool?>>? CommandImplementations { get; set; }
+
+    private static void ReplaceKey (Key oldKey, Key newKey)
     {
-        List<KeyBinding> list = KeyBindings.Bindings
-                                           .Where (kv => kv.Value.Scope != KeyBindingScope.Application)
-                                           .Select (kv => kv.Value)
-                                           .Distinct ()
-                                           .ToList ();
+        if (KeyBindings.Bindings.Count == 0)
+        {
+            return;
+        }
+
+        if (newKey == Key.Empty)
+        {
+            KeyBindings.Remove (oldKey);
+        }
+        else
+        {
+            KeyBindings.ReplaceKey (oldKey, newKey);
+        }
     }
 }

+ 60 - 44
Terminal.Gui/Application/Application.Mouse.cs

@@ -1,5 +1,6 @@
-#nullable enable
+#nullable enable
 namespace Terminal.Gui;
+
 public static partial class Application // Mouse handling
 {
     #region Mouse handling
@@ -36,16 +37,13 @@ public static partial class Application // Mouse handling
     /// <param name="view">View that will receive all mouse events until <see cref="UngrabMouse"/> is invoked.</param>
     public static void GrabMouse (View? view)
     {
-        if (view is null)
+        if (view is null || OnGrabbingMouse (view))
         {
             return;
         }
 
-        if (!OnGrabbingMouse (view))
-        {
-            OnGrabbedMouse (view);
-            MouseGrabView = view;
-        }
+        OnGrabbedMouse (view);
+        MouseGrabView = view;
     }
 
     /// <summary>Releases the mouse grab, so mouse events will be routed to the view on which the mouse is.</summary>
@@ -56,6 +54,10 @@ public static partial class Application // Mouse handling
             return;
         }
 
+#if DEBUG_IDISPOSABLE
+        ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView);
+#endif
+
         if (!OnUnGrabbingMouse (MouseGrabView))
         {
             View view = MouseGrabView;
@@ -64,6 +66,7 @@ public static partial class Application // Mouse handling
         }
     }
 
+    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
     private static bool OnGrabbingMouse (View? view)
     {
         if (view is null)
@@ -77,6 +80,7 @@ public static partial class Application // Mouse handling
         return evArgs.Cancel;
     }
 
+    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
     private static bool OnUnGrabbingMouse (View? view)
     {
         if (view is null)
@@ -90,6 +94,7 @@ public static partial class Application // Mouse handling
         return evArgs.Cancel;
     }
 
+    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
     private static void OnGrabbedMouse (View? view)
     {
         if (view is null)
@@ -100,6 +105,7 @@ public static partial class Application // Mouse handling
         GrabbedMouse?.Invoke (view, new (view));
     }
 
+    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
     private static void OnUnGrabbedMouse (View? view)
     {
         if (view is null)
@@ -110,8 +116,6 @@ public static partial class Application // Mouse handling
         UnGrabbedMouse?.Invoke (view, new (view));
     }
 
-#nullable enable
-
     // Used by OnMouseEvent to track the last view that was clicked on.
     internal static View? MouseEnteredView { get; set; }
 
@@ -139,6 +143,12 @@ public static partial class Application // Mouse handling
 
         if (view is { })
         {
+#if DEBUG_IDISPOSABLE
+            if (view.WasDisposed)
+            {
+                throw new ObjectDisposedException (view.GetType ().FullName);
+            }
+#endif
             mouseEvent.View = view;
         }
 
@@ -151,6 +161,13 @@ public static partial class Application // Mouse handling
 
         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.Position);
@@ -160,57 +177,62 @@ public static partial class Application // Mouse handling
                 Position = frameLoc,
                 Flags = mouseEvent.Flags,
                 ScreenPosition = mouseEvent.Position,
-                View = MouseGrabView
+                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
-                MouseEnteredView?.NewMouseLeaveEvent (mouseEvent);
+                MouseGrabView.NewMouseLeaveEvent (mouseEvent);
             }
 
             //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
-            if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true)
+            if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
             {
                 return;
             }
-        }
 
-        if (view is { WantContinuousButtonPressed: true })
-        {
-            WantContinuousButtonPressedView = view;
-        }
-        else
-        {
-            WantContinuousButtonPressedView = null;
+            // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+            if (MouseGrabView is null && view is Adornment)
+            {
+                // The view that grabbed the mouse has been disposed
+                return;
+            }
         }
 
-        if (view is not Adornment)
+        // 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)
         {
-            if ((view is null || view == ApplicationOverlapped.OverlappedTop)
-                && Current is { Modal: false }
-                && ApplicationOverlapped.OverlappedTop != null
-                && mouseEvent.Flags != MouseFlags.ReportMousePosition
-                && mouseEvent.Flags != 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 { })
             {
-                // 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);
-                }
+                ApplicationOverlapped.MoveCurrent ((Toplevel)top);
             }
         }
 
+        // 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)
         {
             return;
         }
 
-        MouseEvent? me = null;
+        MouseEvent? me;
 
         if (view is Adornment adornment)
         {
@@ -236,8 +258,7 @@ public static partial class Application // Mouse handling
                 View = view
             };
         }
-
-        if (me is null)
+        else
         {
             return;
         }
@@ -263,13 +284,8 @@ public static partial class Application // Mouse handling
 
         //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
 
-        while (view.NewMouseEvent (me) != true)
+        while (view.NewMouseEvent (me) is not true && MouseGrabView is not { })
         {
-            if (MouseGrabView is { })
-            {
-                break;
-            }
-
             if (view is Adornment adornmentView)
             {
                 view = adornmentView.Parent!.SuperView;

+ 9 - 3
Terminal.Gui/Application/Application.Run.cs

@@ -99,7 +99,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
             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);
+                //Top!.OnLeave (toplevel);
             }
 
             // BUGBUG: We should not depend on `Id` internally.
@@ -186,7 +186,14 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
         toplevel.LayoutSubviews ();
         toplevel.PositionToplevels ();
-        toplevel.FocusFirst (null);
+
+        // TODO: Should this use FindDeepestFocusableView instead?
+        // Try to set initial focus to any TabStop
+        if (!toplevel.HasFocus)
+        {
+            toplevel.SetFocus ();
+        }
+
         ApplicationOverlapped.BringOverlappedTopToFront ();
 
         if (refreshDriver)
@@ -858,7 +865,6 @@ public static partial class Application // Run (Begin, Run, End, Stop)
                 if (Current is { HasFocus: false })
                 {
                     Current.SetFocus ();
-                    Current.RestoreFocus ();
                 }
             }
 

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

@@ -32,7 +32,7 @@ public static partial class Application
     /// <returns>A string representation of the Application </returns>
     public new static string ToString ()
     {
-        ConsoleDriver driver = Driver;
+        ConsoleDriver? driver = Driver;
 
         if (driver is null)
         {
@@ -47,13 +47,17 @@ public static partial class Application
     /// </summary>
     /// <param name="driver">The driver to use to render the contents.</param>
     /// <returns>A string representation of the Application </returns>
-    public static string ToString (ConsoleDriver driver)
+    public static string ToString (ConsoleDriver? driver)
     {
+        if (driver is null)
+        {
+            return string.Empty;
+        }
         var sb = new StringBuilder ();
 
-        Cell [,] contents = driver.Contents;
+        Cell [,] contents = driver?.Contents!;
 
-        for (var r = 0; r < driver.Rows; r++)
+        for (var r = 0; r < driver!.Rows; r++)
         {
             for (var c = 0; c < driver.Cols; c++)
             {
@@ -134,6 +138,8 @@ public static partial class Application
     // starts running and after Shutdown returns.
     internal static void ResetState (bool ignoreDisposed = false)
     {
+        Application.Navigation = new ApplicationNavigation ();
+
         // Shutdown is the bookend for Init. As such it needs to clean up all resources
         // Init created. Apps that do any threading will need to code defensively for this.
         // e.g. see Issue #537

+ 33 - 159
Terminal.Gui/Application/ApplicationNavigation.cs

@@ -7,7 +7,6 @@ namespace Terminal.Gui;
 /// </summary>
 public class ApplicationNavigation
 {
-
     /// <summary>
     ///     Initializes a new instance of the <see cref="ApplicationNavigation"/> class.
     /// </summary>
@@ -16,38 +15,17 @@ public class ApplicationNavigation
         // TODO: Move navigation key bindings here from AddApplicationKeyBindings
     }
 
-    private View? _focused = null;
-
-    /// <summary>
-    ///     Gets the most focused <see cref="View"/> in the application, if there is one.
-    /// </summary>
-    public View? GetFocused () { return _focused; }
-
-    /// <summary>
-    ///     INTERNAL method to record the most focused <see cref="View"/> in the application.
-    /// </summary>
-    /// <remarks>
-    ///     Raises <see cref="FocusedChanged"/>.
-    /// </remarks>
-    internal void SetFocused (View? value)
-    {
-        if (_focused == value)
-        {
-            return;
-        }
-
-        _focused = value;
-
-        FocusedChanged?.Invoke (null, EventArgs.Empty);
-
-        return;
-    }
+    private View? _focused;
 
     /// <summary>
     ///     Raised when the most focused <see cref="View"/> in the application has changed.
     /// </summary>
     public event EventHandler<EventArgs>? FocusedChanged;
 
+    /// <summary>
+    ///     Gets the most focused <see cref="View"/> in the application, if there is one.
+    /// </summary>
+    public View? GetFocused () { return _focused; }
 
     /// <summary>
     ///     Gets whether <paramref name="view"/> is in the Subview hierarchy of <paramref name="start"/>.
@@ -55,14 +33,14 @@ public class ApplicationNavigation
     /// <param name="start"></param>
     /// <param name="view"></param>
     /// <returns></returns>
-    public static bool IsInHierarchy (View start, View? view)
+    public static bool IsInHierarchy (View? start, View? view)
     {
         if (view is null)
         {
             return false;
         }
 
-        if (view == start)
+        if (view == start || start is null)
         {
             return true;
         }
@@ -74,7 +52,8 @@ public class ApplicationNavigation
                 return true;
             }
 
-            var found = IsInHierarchy (subView, view);
+            bool found = IsInHierarchy (subView, view);
+
             if (found)
             {
                 return found;
@@ -84,146 +63,41 @@ public class ApplicationNavigation
         return false;
     }
 
-
-    /// <summary>
-    ///     Gets the deepest focused subview of the specified <paramref name="view"/>.
-    /// </summary>
-    /// <param name="view"></param>
-    /// <returns></returns>
-    internal static View? GetDeepestFocusedSubview (View? view)
-    {
-        if (view is null)
-        {
-            return null;
-        }
-
-        foreach (View v in view.Subviews)
-        {
-            if (v.HasFocus)
-            {
-                return GetDeepestFocusedSubview (v);
-            }
-        }
-
-        return view;
-    }
-
     /// <summary>
-    ///     Moves the focus to the next focusable view.
-    ///     Honors <see cref="ViewArrangement.Overlapped"/> and will only move to the next subview
-    ///     if the current and next subviews are not overlapped.
+    ///     INTERNAL method to record the most focused <see cref="View"/> in the application.
     /// </summary>
-    internal static void MoveNextView ()
+    /// <remarks>
+    ///     Raises <see cref="FocusedChanged"/>.
+    /// </remarks>
+    internal void SetFocused (View? value)
     {
-        View? old = GetDeepestFocusedSubview (Application.Current!.Focused);
-
-        if (!Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop))
-        {
-            Application.Current.AdvanceFocus (NavigationDirection.Forward, null);
-        }
-
-        if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
-        {
-            old?.SetNeedsDisplay ();
-            Application.Current.Focused?.SetNeedsDisplay ();
-        }
-        else
+        if (_focused == value)
         {
-            ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward);
+            return;
         }
-    }
-
-    /// <summary>
-    ///     Moves the focus to the next <see cref="Toplevel"/> subview or the next subview that has
-    ///     <see cref="ApplicationOverlapped.OverlappedTop"/> set.
-    /// </summary>
-    internal static void MoveNextViewOrTop ()
-    {
-        if (ApplicationOverlapped.OverlappedTop is null)
-        {
-            Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top;
-
-            if (!Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup))
-            {
-                Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
 
-                if (Application.Current.Focused is null)
-                {
-                    Application.Current.RestoreFocus ();
-                }
-            }
-
-            if (top != Application.Current.Focused && top != Application.Current.Focused?.Focused)
-            {
-                top?.SetNeedsDisplay ();
-                Application.Current.Focused?.SetNeedsDisplay ();
-            }
-            else
-            {
-                ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward);
-            }
-
-            //top!.AdvanceFocus (NavigationDirection.Forward);
-
-            //if (top.Focused is null)
-            //{
-            //    top.AdvanceFocus (NavigationDirection.Forward);
-            //}
+        _focused = value;
 
-            //top.SetNeedsDisplay ();
-            ApplicationOverlapped.BringOverlappedTopToFront ();
-        }
-        else
-        {
-            ApplicationOverlapped.OverlappedMoveNext ();
-        }
+        FocusedChanged?.Invoke (null, EventArgs.Empty);
     }
 
-    // TODO: These methods should return bool to indicate if the focus was moved or not.
-
     /// <summary>
-    ///     Moves the focus to the next view. Honors <see cref="ViewArrangement.Overlapped"/> and will only move to the next
-    ///     subview
-    ///     if the current and next subviews are not overlapped.
+    ///     Advances the focus to the next or previous view in the focus chain, based on
+    ///     <paramref name="direction"/>.
     /// </summary>
-    internal static void MovePreviousView ()
-    {
-        View? old = GetDeepestFocusedSubview (Application.Current!.Focused);
-
-        if (!Application.Current.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop))
-        {
-            Application.Current.AdvanceFocus (NavigationDirection.Backward, null);
-        }
-
-        if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
-        {
-            old?.SetNeedsDisplay ();
-            Application.Current.Focused?.SetNeedsDisplay ();
-        }
-        else
-        {
-            ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes?.Reverse (), NavigationDirection.Backward);
-        }
-    }
-
-    internal static void MovePreviousViewOrTop ()
+    /// <remarks>
+    ///     <para>
+    ///         If there is no next/previous view, the focus remains on the current view.
+    ///     </para>
+    /// </remarks>
+    /// <param name="direction">The direction to advance.</param>
+    /// <param name="behavior">The tab behavior.</param>
+    /// <returns>
+    ///     <see langword="true"/> if focus was changed to another subview (or stayed on this one), <see langword="false"/>
+    ///     otherwise.
+    /// </returns>
+    public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
     {
-        if (ApplicationOverlapped.OverlappedTop is null)
-        {
-            Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top;
-            top!.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup);
-
-            if (top.Focused is null)
-            {
-                top.AdvanceFocus (NavigationDirection.Backward, null);
-            }
-
-            top.SetNeedsDisplay ();
-            ApplicationOverlapped.BringOverlappedTopToFront ();
-        }
-        else
-        {
-            ApplicationOverlapped.OverlappedMovePrevious ();
-        }
+        return Application.Current is { } && Application.Current.AdvanceFocus (direction, behavior);
     }
 }

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

@@ -79,7 +79,7 @@ public static class ApplicationOverlapped
 
         if (top is Toplevel && Application.Top?.Subviews.Count > 1 && Application.Top.Subviews [^1] != top)
         {
-            Application.Top.BringSubviewToFront (top);
+            Application.Top.MoveSubviewToStart (top);
         }
     }
 
@@ -96,12 +96,12 @@ public static class ApplicationOverlapped
 
         foreach (Toplevel top in OverlappedChildren)
         {
-            if (type is { } && top.GetType () == type && exclude?.Contains (top.Data.ToString ()) == false)
+            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)
+            if ((type is { } && top.GetType () != type) || exclude?.Contains (top.Data?.ToString ()) == true)
             {
                 continue;
             }

+ 1 - 0
Terminal.Gui/Application/MainLoop.cs

@@ -296,6 +296,7 @@ internal class MainLoop : IDisposable
     ///     Invoked when a new timeout is added. To be used in the case when
     ///     <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
     /// </summary>
+    [CanBeNull]
     internal event EventHandler<TimeoutEventArgs> TimeoutAdded;
 
     /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input.</summary>

+ 6 - 6
Terminal.Gui/Configuration/SourceGenerationContext.cs

@@ -7,17 +7,17 @@ namespace Terminal.Gui;
 /// </summary>
 [JsonSerializable (typeof (Attribute))]
 [JsonSerializable (typeof (Color))]
-[JsonSerializable (typeof (ThemeScope))]
-[JsonSerializable (typeof (ColorScheme))]
-[JsonSerializable (typeof (SettingsScope))]
 [JsonSerializable (typeof (AppScope))]
+[JsonSerializable (typeof (SettingsScope))]
 [JsonSerializable (typeof (Key))]
 [JsonSerializable (typeof (GlyphDefinitions))]
-[JsonSerializable (typeof (ConfigProperty))]
+[JsonSerializable (typeof (Alignment))]
+[JsonSerializable (typeof (AlignmentModes))]
+[JsonSerializable (typeof (LineStyle))]
 [JsonSerializable (typeof (ShadowStyle))]
-[JsonSerializable (typeof (string))]
-[JsonSerializable (typeof (bool))]
 [JsonSerializable (typeof (bool?))]
 [JsonSerializable (typeof (Dictionary<ColorName, string>))]
+[JsonSerializable (typeof (Dictionary<string, ThemeScope>))]
+[JsonSerializable (typeof (Dictionary<string, ColorScheme>))]
 internal partial class SourceGenerationContext : JsonSerializerContext
 { }

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

@@ -2343,7 +2343,22 @@ internal class WindowsClipboard : ClipboardBase
 {
     private const uint CF_UNICODE_TEXT = 13;
 
-    public override bool IsSupported { get; } = IsClipboardFormatAvailable (CF_UNICODE_TEXT);
+    public override bool IsSupported { get; } = CheckClipboardIsAvailable ();
+
+    private static bool CheckClipboardIsAvailable ()
+    {
+        // Attempt to open the clipboard
+        if (OpenClipboard (nint.Zero))
+        {
+            // Clipboard is available
+            // Close the clipboard after use
+            CloseClipboard ();
+
+            return true;
+        }
+        // Clipboard is not available
+        return false;
+    }
 
     protected override string GetClipboardDataImpl ()
     {

+ 2 - 1
Terminal.Gui/Drawing/Alignment.cs

@@ -1,10 +1,11 @@
-
+using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
 /// <summary>
 ///     Determines the position of items when arranged in a container.
 /// </summary>
+[JsonConverter (typeof (JsonStringEnumConverter<Alignment>))]
 public enum Alignment
 {
     /// <summary>

+ 2 - 1
Terminal.Gui/Drawing/AlignmentModes.cs

@@ -1,10 +1,11 @@
-
+using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
 /// <summary>
 ///     Determines alignment modes for <see cref="Alignment"/>.
 /// </summary>
+[JsonConverter (typeof (JsonStringEnumConverter<AlignmentModes>))]
 [Flags]
 public enum AlignmentModes
 {

+ 1 - 1
Terminal.Gui/Drawing/Color.Formatting.cs

@@ -284,7 +284,7 @@ public readonly partial record struct Color
                                                                                               ),
 
                    // Any string too short to possibly be any supported format.
-                   { Length: > 0 and < 4 } => throw new ColorParseException (
+                   { Length: > 0 and < 3 } => throw new ColorParseException (
                                                                              in text,
                                                                              "Text was too short to be any possible supported format.",
                                                                              in text

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

@@ -1,6 +1,7 @@
 #nullable enable
 using System.Collections.Frozen;
 using System.Diagnostics.Contracts;
+using System.Drawing;
 using System.Globalization;
 using System.Numerics;
 using System.Runtime.CompilerServices;
@@ -331,4 +332,4 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
     public const ColorName White = ColorName.White;
 
     #endregion
-}
+}

+ 11 - 0
Terminal.Gui/Drawing/ColorEventArgs.cs

@@ -0,0 +1,11 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>Event arguments for the <see cref="Color"/> events.</summary>
+public class ColorEventArgs : EventArgs<Color>
+{
+    /// <summary>Initializes a new instance of <see cref="ColorEventArgs"/>
+    /// <paramref name="newColor"/>The value that is being changed to.</summary>
+    public ColorEventArgs (Color newColor) :base(newColor) { }
+}

+ 25 - 0
Terminal.Gui/Drawing/ColorModel.cs

@@ -0,0 +1,25 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Describes away of modelling color e.g. Hue
+/// Saturation Lightness.
+/// </summary>
+public enum ColorModel
+{
+    /// <summary>
+    /// Color modelled by storing Red, Green and Blue as (0-255) ints
+    /// </summary>
+    RGB,
+
+    /// <summary>
+    /// Color modelled by storing Hue (360 degrees), Saturation (100%) and Value (100%)
+    /// </summary>
+    HSV,
+
+    /// <summary>
+    /// Color modelled by storing Hue (360 degrees), Saturation (100%) and Lightness (100%)
+    /// </summary>
+    HSL
+}

+ 79 - 0
Terminal.Gui/Drawing/ColorStrings.cs

@@ -0,0 +1,79 @@
+#nullable enable
+using System.Collections;
+using System.Globalization;
+using Terminal.Gui.Resources;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Provides a mapping between <see cref="Color"/> and the W3C standard color name strings.
+/// </summary>
+public static class ColorStrings
+{
+    /// <summary>
+    ///     Gets the W3C standard string for <paramref name="color"/>.
+    /// </summary>
+    /// <param name="color">The color.</param>
+    /// <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);
+    }
+
+    /// <summary>
+    ///     Returns the list of W3C standard color names.
+    /// </summary>
+    /// <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;
+
+                                                                              return e.Value is string && keyName.StartsWith ('#');
+                                                                          })!)
+        {
+            yield return (entry.Value as string)!;
+        }
+    }
+
+    /// <summary>
+    ///     Parses <paramref name="name"/> and returns <paramref name="color"/> if name is a W3C standard named color.
+    /// </summary>
+    /// <param name="name">The name to parse.</param>
+    /// <param name="color">If successful, the color.</param>
+    /// <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;
+
+                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 true;
+                    }
+                }
+            }
+        }
+
+        color = default (Color);
+
+        return false;
+    }
+}

+ 34 - 0
Terminal.Gui/Drawing/IColorNameResolver.cs

@@ -0,0 +1,34 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     When implemented by a class, allows mapping <see cref="Color"/> to
+///     human understandable name (e.g. w3c color names) and vice versa.
+/// </summary>
+public interface IColorNameResolver
+{
+    /// <summary>
+    ///     Returns the names of all known colors.
+    /// </summary>
+    /// <returns></returns>
+    IEnumerable<string> GetColorNames ();
+
+    /// <summary>
+    ///     Returns <see langword="true"/> if <paramref name="color"/> is a recognized
+    ///     color. In which case <paramref name="name"/> will be the name of the color and
+    ///     return value will be true otherwise false.
+    /// </summary>
+    /// <param name="color"></param>
+    /// <param name="name"></param>
+    /// <returns></returns>
+    bool TryNameColor (Color color, out string name);
+
+    /// <summary>
+    ///     Returns <see langword="true"/> if <paramref name="name"/> is a recognized
+    ///     color. In which case <paramref name="color"/> will be the color the name corresponds
+    ///     to otherwise returns false.
+    /// </summary>
+    /// <param name="name"></param>
+    /// <param name="color"></param>
+    /// <returns></returns>
+    bool TryParseColor (string name, out Color color);
+}

+ 3 - 0
Terminal.Gui/Drawing/LineStyle.cs

@@ -1,7 +1,10 @@
 #nullable enable
+using System.Text.Json.Serialization;
+
 namespace Terminal.Gui;
 
 /// <summary>Defines the style of lines for a <see cref="LineCanvas"/>.</summary>
+[JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
 public enum LineStyle
 {
     /// <summary>No border is drawn.</summary>

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

@@ -61,7 +61,7 @@ public record struct Thickness
     [JsonInclude]
     public int Bottom
     {
-        get => (int)_sides.W;
+        readonly get => (int)_sides.W;
         set => _sides.W = value;
     }
 
@@ -249,7 +249,7 @@ public record struct Thickness
     [JsonInclude]
     public int Left
     {
-        get => (int)_sides.X;
+        readonly get => (int)_sides.X;
         set => _sides.X = value;
     }
 
@@ -265,7 +265,7 @@ public record struct Thickness
     [JsonInclude]
     public int Right
     {
-        get => (int)_sides.Z;
+        readonly get => (int)_sides.Z;
         set => _sides.Z = value;
     }
 
@@ -273,7 +273,7 @@ public record struct Thickness
     [JsonInclude]
     public int Top
     {
-        get => (int)_sides.Y;
+        readonly get => (int)_sides.Y;
         set => _sides.Y = value;
     }
 

+ 24 - 0
Terminal.Gui/Drawing/W3CColors.cs

@@ -0,0 +1,24 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     Helper class that resolves w3c color names to their hex values
+///     Based on https://www.w3schools.com/colors/color_tryit.asp
+/// </summary>
+public class W3CColors : IColorNameResolver
+{
+    /// <inheritdoc/>
+    public IEnumerable<string> GetColorNames () { return ColorStrings.GetW3CColorNames (); }
+
+    /// <inheritdoc/>
+    public bool TryParseColor (string name, out Color color) { return ColorStrings.TryParseW3CColorName (name, out color); }
+
+    /// <inheritdoc/>
+    public bool TryNameColor (Color color, out string name)
+    {
+        string answer = ColorStrings.GetW3CColorName (color);
+
+        name = answer ?? string.Empty;
+
+        return answer != null;
+    }
+}

+ 21 - 4
Terminal.Gui/Input/Key.cs

@@ -1,5 +1,6 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
+using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
@@ -448,9 +449,9 @@ public class Key : EventArgs, IEquatable<Key>
 
     #region String conversion
 
-    /// <summary>Pretty prints the KeyEvent</summary>
+    /// <summary>Pretty prints the Key.</summary>
     /// <returns></returns>
-    public override string ToString () { return ToString (KeyCode, (Rune)'+'); }
+    public override string ToString () { return ToString (KeyCode, Separator); }
 
     private static string GetKeyString (KeyCode key)
     {
@@ -483,7 +484,7 @@ public class Key : EventArgs, IEquatable<Key>
     ///     The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key
     ///     name will be returned.
     /// </returns>
-    public static string ToString (KeyCode key) { return ToString (key, (Rune)'+'); }
+    public static string ToString (KeyCode key) { return ToString (key, Separator); }
 
     /// <summary>Formats a <see cref="KeyCode"/> as a string.</summary>
     /// <param name="key">The key to format.</param>
@@ -584,7 +585,7 @@ public class Key : EventArgs, IEquatable<Key>
         key = null;
 
         // Split the string into parts
-        string [] parts = text.Split ('+', '-');
+        string [] parts = text.Split ('+', '-', (char)Separator.Value);
 
         if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
         {
@@ -971,4 +972,20 @@ public class Key : EventArgs, IEquatable<Key>
     public static Key F24 => new (KeyCode.F24);
 
     #endregion
+
+    private static Rune _separator = new ('+');
+
+    /// <summary>Gets or sets the separator character used when parsing and printing Keys. E.g. Ctrl+A. The default is '+'.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static Rune Separator
+    {
+        get => _separator;
+        set
+        {
+            if (_separator != value)
+            {
+                _separator = value == default (Rune) ? new ('+') : value;
+            }
+        }
+    }
 }

+ 1 - 0
Terminal.Gui/Input/Responder.cs

@@ -26,6 +26,7 @@ public class Responder : IDisposable
     }
 
     /// <summary>Event raised when <see cref="Dispose()"/> has been called to signal that this object is being disposed.</summary>
+    [CanBeNull]
     public event EventHandler Disposing;
 
     /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>

+ 2 - 2
Terminal.Gui/Input/ShortcutHelper.cs

@@ -23,7 +23,7 @@ public class ShortcutHelper
     }
 
     /// <summary>The keystroke combination used in the <see cref="Shortcut"/> as string.</summary>
-    public virtual string ShortcutTag => Key.ToString (shortcut, MenuBar.ShortcutDelimiter);
+    public virtual string ShortcutTag => Key.ToString (shortcut, Key.Separator);
 
     /// <summary>Lookup for a <see cref="KeyCode"/> on range of keys.</summary>
     /// <param name="key">The source key.</param>
@@ -59,7 +59,7 @@ public class ShortcutHelper
         //var hasCtrl = false;
         if (delimiter == default (Rune))
         {
-            delimiter = MenuBar.ShortcutDelimiter;
+            delimiter = Key.Separator;
         }
 
         string [] keys = sCut.Split (delimiter.ToString ());

+ 70 - 0
Terminal.Gui/Resources/GlobalResources.cs

@@ -0,0 +1,70 @@
+#nullable enable
+
+using System.Collections;
+using System.Globalization;
+using System.Resources;
+
+namespace Terminal.Gui.Resources;
+
+/// <summary>
+///     Provide static access to the ResourceManagerWrapper
+/// </summary>
+public static class GlobalResources
+{
+    private static readonly ResourceManagerWrapper _resourceManagerWrapper;
+
+    static GlobalResources ()
+    {
+        // Initialize the ResourceManagerWrapper once
+        var resourceManager = new ResourceManager (typeof (Strings));
+        _resourceManagerWrapper = new (resourceManager);
+    }
+
+    /// <summary>
+    ///     Looks up a resource value for a particular name.  Looks in the specified CultureInfo, and if not found, all parent
+    ///     CultureInfos.
+    /// </summary>
+    /// <param name="name"></param>
+    /// <param name="culture"></param>
+    /// <returns>Null if the resource was not found in the current culture or the invariant culture.</returns>
+    public static object GetObject (string name, CultureInfo culture = null!) { return _resourceManagerWrapper.GetObject (name, culture); }
+
+    /// <summary>
+    ///     Looks up a set of resources for a particular CultureInfo. This is not useful for most users of the ResourceManager
+    ///     - call GetString() or GetObject() instead. The parameters let you control whether the ResourceSet is created if it
+    ///     hasn't yet been loaded and if parent CultureInfos should be loaded as well for resource inheritance.
+    /// </summary>
+    /// <param name="culture"></param>
+    /// <param name="createIfNotExists"></param>
+    /// <param name="tryParents"></param>
+    /// <returns></returns>
+    public static ResourceSet? GetResourceSet (CultureInfo culture, bool createIfNotExists, bool tryParents)
+    {
+        return _resourceManagerWrapper.GetResourceSet (culture, createIfNotExists, tryParents)!;
+    }
+
+    /// <summary>
+    ///     Looks up a set of resources for a particular CultureInfo. This is not useful for most users of the ResourceManager
+    ///     - call GetString() or GetObject() instead. The parameters let you control whether the ResourceSet is created if it
+    ///     hasn't yet been loaded and if parent CultureInfos should be loaded as well for resource inheritance. Allows
+    ///     filtering of resources.
+    /// </summary>
+    /// <param name="culture"></param>
+    /// <param name="createIfNotExists"></param>
+    /// <param name="tryParents"></param>
+    /// <param name="filter"></param>
+    /// <returns></returns>
+    public static ResourceSet? GetResourceSet (CultureInfo culture, bool createIfNotExists, bool tryParents, Func<DictionaryEntry, bool>? filter)
+    {
+        return _resourceManagerWrapper.GetResourceSet (culture, createIfNotExists, tryParents, filter)!;
+    }
+
+    /// <summary>
+    ///     Looks up a resource value for a particular name. Looks in the specified CultureInfo, and if not found, all parent
+    ///     CultureInfos.
+    /// </summary>
+    /// <param name="name"></param>
+    /// <param name="culture"></param>
+    /// <returns>Null if the resource was not found in the current culture or the invariant culture.</returns>
+    public static string GetString (string name, CultureInfo? culture = null!) { return _resourceManagerWrapper.GetString (name, culture); }
+}

+ 112 - 0
Terminal.Gui/Resources/ResourceManagerWrapper.cs

@@ -0,0 +1,112 @@
+#nullable enable
+
+using System.Collections;
+using System.Globalization;
+using System.Resources;
+
+namespace Terminal.Gui.Resources;
+
+internal class ResourceManagerWrapper (ResourceManager resourceManager)
+{
+    private readonly ResourceManager _resourceManager = resourceManager ?? throw new ArgumentNullException (nameof (resourceManager));
+
+    // Optionally, expose other ResourceManager methods as needed
+    public object GetObject (string name, CultureInfo culture = null!)
+    {
+        object value = _resourceManager.GetObject (name, culture)!;
+
+        if (Equals (culture, CultureInfo.InvariantCulture))
+        {
+            return value;
+        }
+
+        if (value is null)
+        {
+            value = _resourceManager.GetObject (name, CultureInfo.InvariantCulture)!;
+        }
+
+        return value;
+    }
+
+    public ResourceSet? GetResourceSet (CultureInfo culture, bool createIfNotExists, bool tryParents)
+    {
+        ResourceSet value = _resourceManager.GetResourceSet (culture, createIfNotExists, tryParents)!;
+
+        if (Equals (culture, CultureInfo.InvariantCulture))
+        {
+            return value;
+        }
+
+        if (value!.Cast<DictionaryEntry> ().Any ())
+        {
+            value = _resourceManager.GetResourceSet (CultureInfo.InvariantCulture, createIfNotExists, tryParents)!;
+        }
+
+        return value;
+    }
+
+    public ResourceSet? GetResourceSet (CultureInfo culture, bool createIfNotExists, bool tryParents, Func<DictionaryEntry, bool>? filter)
+    {
+        ResourceSet value = _resourceManager.GetResourceSet (culture, createIfNotExists, tryParents)!;
+
+        IEnumerable<DictionaryEntry> filteredEntries = value.Cast<DictionaryEntry> ().Where (filter ?? (_ => true));
+
+        ResourceSet? filteredValue = ConvertToResourceSet (filteredEntries);
+
+        if (Equals (culture, CultureInfo.InvariantCulture))
+        {
+            return filteredValue;
+        }
+
+        if (!filteredValue!.Cast<DictionaryEntry> ().Any ())
+        {
+            filteredValue = GetResourceSet (CultureInfo.InvariantCulture, createIfNotExists, tryParents, filter)!;
+        }
+
+        return filteredValue;
+    }
+
+    public string GetString (string name, CultureInfo? culture = null!)
+    {
+        // Attempt to get the string for the specified culture
+        string value = _resourceManager.GetString (name, culture)!;
+
+        // If it's already using the invariant culture return
+        if (Equals (culture, CultureInfo.InvariantCulture))
+        {
+            return value;
+        }
+
+        // If the string is empty or null, fall back to the invariant culture
+        if (string.IsNullOrEmpty (value))
+        {
+            value = _resourceManager.GetString (name, CultureInfo.InvariantCulture)!;
+        }
+
+        return value;
+    }
+
+    private static ResourceSet? ConvertToResourceSet (IEnumerable<DictionaryEntry> entries)
+    {
+        using var memoryStream = new MemoryStream ();
+
+        using var resourceWriter = new ResourceWriter (memoryStream);
+
+        // Add each DictionaryEntry to the ResourceWriter
+        foreach (DictionaryEntry entry in entries)
+        {
+            resourceWriter.AddResource ((string)entry.Key, entry.Value);
+        }
+
+        // Finish writing to the stream
+        resourceWriter.Generate ();
+
+        // Reset the stream position to the beginning
+        memoryStream.Position = 0;
+
+        // Create a ResourceSet from the MemoryStream
+        var resourceSet = new ResourceSet (memoryStream);
+
+        return resourceSet;
+    }
+}

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

@@ -60,6 +60,1257 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Black.
+        /// </summary>
+        internal static string _000000 {
+            get {
+                return ResourceManager.GetString("#000000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Navy.
+        /// </summary>
+        internal static string _000080 {
+            get {
+                return ResourceManager.GetString("#000080", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkBlue.
+        /// </summary>
+        internal static string _00008B {
+            get {
+                return ResourceManager.GetString("#00008B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumBlue.
+        /// </summary>
+        internal static string _0000CD {
+            get {
+                return ResourceManager.GetString("#0000CD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Blue.
+        /// </summary>
+        internal static string _0000FF {
+            get {
+                return ResourceManager.GetString("#0000FF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkGreen.
+        /// </summary>
+        internal static string _006400 {
+            get {
+                return ResourceManager.GetString("#006400", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Green.
+        /// </summary>
+        internal static string _008000 {
+            get {
+                return ResourceManager.GetString("#008000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Teal.
+        /// </summary>
+        internal static string _008080 {
+            get {
+                return ResourceManager.GetString("#008080", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkCyan.
+        /// </summary>
+        internal static string _008B8B {
+            get {
+                return ResourceManager.GetString("#008B8B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DeepSkyBlue.
+        /// </summary>
+        internal static string _00BFFF {
+            get {
+                return ResourceManager.GetString("#00BFFF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkTurquoise.
+        /// </summary>
+        internal static string _00CED1 {
+            get {
+                return ResourceManager.GetString("#00CED1", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumSpringGreen.
+        /// </summary>
+        internal static string _00FA9A {
+            get {
+                return ResourceManager.GetString("#00FA9A", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Lime.
+        /// </summary>
+        internal static string _00FF00 {
+            get {
+                return ResourceManager.GetString("#00FF00", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SpringGreen.
+        /// </summary>
+        internal static string _00FF7F {
+            get {
+                return ResourceManager.GetString("#00FF7F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Cyan.
+        /// </summary>
+        internal static string _00FFFF {
+            get {
+                return ResourceManager.GetString("#00FFFF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MidnightBlue.
+        /// </summary>
+        internal static string _191970 {
+            get {
+                return ResourceManager.GetString("#191970", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DodgerBlue.
+        /// </summary>
+        internal static string _1E90FF {
+            get {
+                return ResourceManager.GetString("#1E90FF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightSeaGreen.
+        /// </summary>
+        internal static string _20B2AA {
+            get {
+                return ResourceManager.GetString("#20B2AA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to ForestGreen.
+        /// </summary>
+        internal static string _228B22 {
+            get {
+                return ResourceManager.GetString("#228B22", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SeaGreen.
+        /// </summary>
+        internal static string _2E8B57 {
+            get {
+                return ResourceManager.GetString("#2E8B57", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkSlateGrey.
+        /// </summary>
+        internal static string _2F4F4F {
+            get {
+                return ResourceManager.GetString("#2F4F4F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LimeGreen.
+        /// </summary>
+        internal static string _32CD32 {
+            get {
+                return ResourceManager.GetString("#32CD32", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumSeaGreen.
+        /// </summary>
+        internal static string _3CB371 {
+            get {
+                return ResourceManager.GetString("#3CB371", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Turquoise.
+        /// </summary>
+        internal static string _40E0D0 {
+            get {
+                return ResourceManager.GetString("#40E0D0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to RoyalBlue.
+        /// </summary>
+        internal static string _4169E1 {
+            get {
+                return ResourceManager.GetString("#4169E1", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SteelBlue.
+        /// </summary>
+        internal static string _4682B4 {
+            get {
+                return ResourceManager.GetString("#4682B4", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkSlateBlue.
+        /// </summary>
+        internal static string _483D8B {
+            get {
+                return ResourceManager.GetString("#483D8B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumTurquoise.
+        /// </summary>
+        internal static string _48D1CC {
+            get {
+                return ResourceManager.GetString("#48D1CC", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Indigo.
+        /// </summary>
+        internal static string _4B0082 {
+            get {
+                return ResourceManager.GetString("#4B0082", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkOliveGreen.
+        /// </summary>
+        internal static string _556B2F {
+            get {
+                return ResourceManager.GetString("#556B2F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to CadetBlue.
+        /// </summary>
+        internal static string _5F9EA0 {
+            get {
+                return ResourceManager.GetString("#5F9EA0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to CornflowerBlue.
+        /// </summary>
+        internal static string _6495ED {
+            get {
+                return ResourceManager.GetString("#6495ED", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to RebeccaPurple.
+        /// </summary>
+        internal static string _663399 {
+            get {
+                return ResourceManager.GetString("#663399", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumAquaMarine.
+        /// </summary>
+        internal static string _66CDAA {
+            get {
+                return ResourceManager.GetString("#66CDAA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DimGray.
+        /// </summary>
+        internal static string _696969 {
+            get {
+                return ResourceManager.GetString("#696969", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SlateBlue.
+        /// </summary>
+        internal static string _6A5ACD {
+            get {
+                return ResourceManager.GetString("#6A5ACD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to OliveDrab.
+        /// </summary>
+        internal static string _6B8E23 {
+            get {
+                return ResourceManager.GetString("#6B8E23", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SlateGray.
+        /// </summary>
+        internal static string _708090 {
+            get {
+                return ResourceManager.GetString("#708090", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightSlateGrey.
+        /// </summary>
+        internal static string _778899 {
+            get {
+                return ResourceManager.GetString("#778899", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumSlateBlue.
+        /// </summary>
+        internal static string _7B68EE {
+            get {
+                return ResourceManager.GetString("#7B68EE", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LawnGreen.
+        /// </summary>
+        internal static string _7CFC00 {
+            get {
+                return ResourceManager.GetString("#7CFC00", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Chartreuse.
+        /// </summary>
+        internal static string _7FFF00 {
+            get {
+                return ResourceManager.GetString("#7FFF00", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Aquamarine.
+        /// </summary>
+        internal static string _7FFFD4 {
+            get {
+                return ResourceManager.GetString("#7FFFD4", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Maroon.
+        /// </summary>
+        internal static string _800000 {
+            get {
+                return ResourceManager.GetString("#800000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Purple.
+        /// </summary>
+        internal static string _800080 {
+            get {
+                return ResourceManager.GetString("#800080", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Olive.
+        /// </summary>
+        internal static string _808000 {
+            get {
+                return ResourceManager.GetString("#808000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Gray.
+        /// </summary>
+        internal static string _808080 {
+            get {
+                return ResourceManager.GetString("#808080", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SkyBlue.
+        /// </summary>
+        internal static string _87CEEB {
+            get {
+                return ResourceManager.GetString("#87CEEB", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightSkyBlue.
+        /// </summary>
+        internal static string _87CEFA {
+            get {
+                return ResourceManager.GetString("#87CEFA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to BlueViolet.
+        /// </summary>
+        internal static string _8A2BE2 {
+            get {
+                return ResourceManager.GetString("#8A2BE2", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkRed.
+        /// </summary>
+        internal static string _8B0000 {
+            get {
+                return ResourceManager.GetString("#8B0000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkMagenta.
+        /// </summary>
+        internal static string _8B008B {
+            get {
+                return ResourceManager.GetString("#8B008B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SaddleBrown.
+        /// </summary>
+        internal static string _8B4513 {
+            get {
+                return ResourceManager.GetString("#8B4513", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkSeaGreen.
+        /// </summary>
+        internal static string _8FBC8F {
+            get {
+                return ResourceManager.GetString("#8FBC8F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightGreen.
+        /// </summary>
+        internal static string _90EE90 {
+            get {
+                return ResourceManager.GetString("#90EE90", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumPurple.
+        /// </summary>
+        internal static string _9370DB {
+            get {
+                return ResourceManager.GetString("#9370DB", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkViolet.
+        /// </summary>
+        internal static string _9400D3 {
+            get {
+                return ResourceManager.GetString("#9400D3", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PaleGreen.
+        /// </summary>
+        internal static string _98FB98 {
+            get {
+                return ResourceManager.GetString("#98FB98", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkOrchid.
+        /// </summary>
+        internal static string _9932CC {
+            get {
+                return ResourceManager.GetString("#9932CC", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to YellowGreen.
+        /// </summary>
+        internal static string _9ACD32 {
+            get {
+                return ResourceManager.GetString("#9ACD32", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Sienna.
+        /// </summary>
+        internal static string _A0522D {
+            get {
+                return ResourceManager.GetString("#A0522D", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Brown.
+        /// </summary>
+        internal static string _A52A2A {
+            get {
+                return ResourceManager.GetString("#A52A2A", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkGrey.
+        /// </summary>
+        internal static string _A9A9A9 {
+            get {
+                return ResourceManager.GetString("#A9A9A9", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightBlue.
+        /// </summary>
+        internal static string _ADD8E6 {
+            get {
+                return ResourceManager.GetString("#ADD8E6", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to GreenYellow.
+        /// </summary>
+        internal static string _ADFF2F {
+            get {
+                return ResourceManager.GetString("#ADFF2F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PaleTurquoise.
+        /// </summary>
+        internal static string _AFEEEE {
+            get {
+                return ResourceManager.GetString("#AFEEEE", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightSteelBlue.
+        /// </summary>
+        internal static string _B0C4DE {
+            get {
+                return ResourceManager.GetString("#B0C4DE", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PowderBlue.
+        /// </summary>
+        internal static string _B0E0E6 {
+            get {
+                return ResourceManager.GetString("#B0E0E6", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to FireBrick.
+        /// </summary>
+        internal static string _B22222 {
+            get {
+                return ResourceManager.GetString("#B22222", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkGoldenRod.
+        /// </summary>
+        internal static string _B8860B {
+            get {
+                return ResourceManager.GetString("#B8860B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumOrchid.
+        /// </summary>
+        internal static string _BA55D3 {
+            get {
+                return ResourceManager.GetString("#BA55D3", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to RosyBrown.
+        /// </summary>
+        internal static string _BC8F8F {
+            get {
+                return ResourceManager.GetString("#BC8F8F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkKhaki.
+        /// </summary>
+        internal static string _BDB76B {
+            get {
+                return ResourceManager.GetString("#BDB76B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Silver.
+        /// </summary>
+        internal static string _C0C0C0 {
+            get {
+                return ResourceManager.GetString("#C0C0C0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumVioletRed.
+        /// </summary>
+        internal static string _C71585 {
+            get {
+                return ResourceManager.GetString("#C71585", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to IndianRed.
+        /// </summary>
+        internal static string _CD5C5C {
+            get {
+                return ResourceManager.GetString("#CD5C5C", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Peru.
+        /// </summary>
+        internal static string _CD853F {
+            get {
+                return ResourceManager.GetString("#CD853F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Chocolate.
+        /// </summary>
+        internal static string _D2691E {
+            get {
+                return ResourceManager.GetString("#D2691E", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Tan.
+        /// </summary>
+        internal static string _D2B48C {
+            get {
+                return ResourceManager.GetString("#D2B48C", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightGray.
+        /// </summary>
+        internal static string _D3D3D3 {
+            get {
+                return ResourceManager.GetString("#D3D3D3", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Thistle.
+        /// </summary>
+        internal static string _D8BFD8 {
+            get {
+                return ResourceManager.GetString("#D8BFD8", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Orchid.
+        /// </summary>
+        internal static string _DA70D6 {
+            get {
+                return ResourceManager.GetString("#DA70D6", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to GoldenRod.
+        /// </summary>
+        internal static string _DAA520 {
+            get {
+                return ResourceManager.GetString("#DAA520", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PaleVioletRed.
+        /// </summary>
+        internal static string _DB7093 {
+            get {
+                return ResourceManager.GetString("#DB7093", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Crimson.
+        /// </summary>
+        internal static string _DC143C {
+            get {
+                return ResourceManager.GetString("#DC143C", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Gainsboro.
+        /// </summary>
+        internal static string _DCDCDC {
+            get {
+                return ResourceManager.GetString("#DCDCDC", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Plum.
+        /// </summary>
+        internal static string _DDA0DD {
+            get {
+                return ResourceManager.GetString("#DDA0DD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to BurlyWood.
+        /// </summary>
+        internal static string _DEB887 {
+            get {
+                return ResourceManager.GetString("#DEB887", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightCyan.
+        /// </summary>
+        internal static string _E0FFFF {
+            get {
+                return ResourceManager.GetString("#E0FFFF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Lavender.
+        /// </summary>
+        internal static string _E6E6FA {
+            get {
+                return ResourceManager.GetString("#E6E6FA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkSalmon.
+        /// </summary>
+        internal static string _E9967A {
+            get {
+                return ResourceManager.GetString("#E9967A", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Violet.
+        /// </summary>
+        internal static string _EE82EE {
+            get {
+                return ResourceManager.GetString("#EE82EE", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PaleGoldenRod.
+        /// </summary>
+        internal static string _EEE8AA {
+            get {
+                return ResourceManager.GetString("#EEE8AA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightCoral.
+        /// </summary>
+        internal static string _F08080 {
+            get {
+                return ResourceManager.GetString("#F08080", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Khaki.
+        /// </summary>
+        internal static string _F0E68C {
+            get {
+                return ResourceManager.GetString("#F0E68C", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to AliceBlue.
+        /// </summary>
+        internal static string _F0F8FF {
+            get {
+                return ResourceManager.GetString("#F0F8FF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to HoneyDew.
+        /// </summary>
+        internal static string _F0FFF0 {
+            get {
+                return ResourceManager.GetString("#F0FFF0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Azure.
+        /// </summary>
+        internal static string _F0FFFF {
+            get {
+                return ResourceManager.GetString("#F0FFFF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SandyBrown.
+        /// </summary>
+        internal static string _F4A460 {
+            get {
+                return ResourceManager.GetString("#F4A460", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Wheat.
+        /// </summary>
+        internal static string _F5DEB3 {
+            get {
+                return ResourceManager.GetString("#F5DEB3", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Beige.
+        /// </summary>
+        internal static string _F5F5DC {
+            get {
+                return ResourceManager.GetString("#F5F5DC", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to WhiteSmoke.
+        /// </summary>
+        internal static string _F5F5F5 {
+            get {
+                return ResourceManager.GetString("#F5F5F5", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MintCream.
+        /// </summary>
+        internal static string _F5FFFA {
+            get {
+                return ResourceManager.GetString("#F5FFFA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to GhostWhite.
+        /// </summary>
+        internal static string _F8F8FF {
+            get {
+                return ResourceManager.GetString("#F8F8FF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Salmon.
+        /// </summary>
+        internal static string _FA8072 {
+            get {
+                return ResourceManager.GetString("#FA8072", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to AntiqueWhite.
+        /// </summary>
+        internal static string _FAEBD7 {
+            get {
+                return ResourceManager.GetString("#FAEBD7", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Linen.
+        /// </summary>
+        internal static string _FAF0E6 {
+            get {
+                return ResourceManager.GetString("#FAF0E6", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightGoldenRodYellow.
+        /// </summary>
+        internal static string _FAFAD2 {
+            get {
+                return ResourceManager.GetString("#FAFAD2", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to OldLace.
+        /// </summary>
+        internal static string _FDF5E6 {
+            get {
+                return ResourceManager.GetString("#FDF5E6", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Red.
+        /// </summary>
+        internal static string _FF0000 {
+            get {
+                return ResourceManager.GetString("#FF0000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Magenta.
+        /// </summary>
+        internal static string _FF00FF {
+            get {
+                return ResourceManager.GetString("#FF00FF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DeepPink.
+        /// </summary>
+        internal static string _FF1493 {
+            get {
+                return ResourceManager.GetString("#FF1493", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to OrangeRed.
+        /// </summary>
+        internal static string _FF4500 {
+            get {
+                return ResourceManager.GetString("#FF4500", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Tomato.
+        /// </summary>
+        internal static string _FF6347 {
+            get {
+                return ResourceManager.GetString("#FF6347", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to HotPink.
+        /// </summary>
+        internal static string _FF69B4 {
+            get {
+                return ResourceManager.GetString("#FF69B4", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Coral.
+        /// </summary>
+        internal static string _FF7F50 {
+            get {
+                return ResourceManager.GetString("#FF7F50", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkOrange.
+        /// </summary>
+        internal static string _FF8C00 {
+            get {
+                return ResourceManager.GetString("#FF8C00", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightSalmon.
+        /// </summary>
+        internal static string _FFA07A {
+            get {
+                return ResourceManager.GetString("#FFA07A", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Orange.
+        /// </summary>
+        internal static string _FFA500 {
+            get {
+                return ResourceManager.GetString("#FFA500", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightPink.
+        /// </summary>
+        internal static string _FFB6C1 {
+            get {
+                return ResourceManager.GetString("#FFB6C1", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Pink.
+        /// </summary>
+        internal static string _FFC0CB {
+            get {
+                return ResourceManager.GetString("#FFC0CB", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Gold.
+        /// </summary>
+        internal static string _FFD700 {
+            get {
+                return ResourceManager.GetString("#FFD700", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PeachPuff.
+        /// </summary>
+        internal static string _FFDAB9 {
+            get {
+                return ResourceManager.GetString("#FFDAB9", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to NavajoWhite.
+        /// </summary>
+        internal static string _FFDEAD {
+            get {
+                return ResourceManager.GetString("#FFDEAD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Moccasin.
+        /// </summary>
+        internal static string _FFE4B5 {
+            get {
+                return ResourceManager.GetString("#FFE4B5", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Bisque.
+        /// </summary>
+        internal static string _FFE4C4 {
+            get {
+                return ResourceManager.GetString("#FFE4C4", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MistyRose.
+        /// </summary>
+        internal static string _FFE4E1 {
+            get {
+                return ResourceManager.GetString("#FFE4E1", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to BlanchedAlmond.
+        /// </summary>
+        internal static string _FFEBCD {
+            get {
+                return ResourceManager.GetString("#FFEBCD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PapayaWhip.
+        /// </summary>
+        internal static string _FFEFD5 {
+            get {
+                return ResourceManager.GetString("#FFEFD5", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LavenderBlush.
+        /// </summary>
+        internal static string _FFF0F5 {
+            get {
+                return ResourceManager.GetString("#FFF0F5", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SeaShell.
+        /// </summary>
+        internal static string _FFF5EE {
+            get {
+                return ResourceManager.GetString("#FFF5EE", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Cornsilk.
+        /// </summary>
+        internal static string _FFF8DC {
+            get {
+                return ResourceManager.GetString("#FFF8DC", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LemonChiffon.
+        /// </summary>
+        internal static string _FFFACD {
+            get {
+                return ResourceManager.GetString("#FFFACD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to FloralWhite.
+        /// </summary>
+        internal static string _FFFAF0 {
+            get {
+                return ResourceManager.GetString("#FFFAF0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Snow.
+        /// </summary>
+        internal static string _FFFAFA {
+            get {
+                return ResourceManager.GetString("#FFFAFA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Yellow.
+        /// </summary>
+        internal static string _FFFF00 {
+            get {
+                return ResourceManager.GetString("#FFFF00", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightYellow.
+        /// </summary>
+        internal static string _FFFFE0 {
+            get {
+                return ResourceManager.GetString("#FFFFE0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Ivory.
+        /// </summary>
+        internal static string _FFFFF0 {
+            get {
+                return ResourceManager.GetString("#FFFFF0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to White.
+        /// </summary>
+        internal static string _FFFFFF {
+            get {
+                return ResourceManager.GetString("#FFFFFF", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to _Cancel.
         /// </summary>

+ 527 - 110
Terminal.Gui/Resources/Strings.resx

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <root>
-  <!-- 
+    <!-- 
     Microsoft ResX Schema 
     
     Version 2.0
@@ -59,225 +59,642 @@
             : 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:complexType>
-        <xsd:choice maxOccurs="unbounded">
-          <xsd:element name="metadata">
+    <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: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: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: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: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: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">
+    <value>AliceBlue</value>
+  </data>
+    <data name="#FAEBD7" xml:space="preserve">
+    <value>AntiqueWhite</value>
+  </data>
+    <data name="#7FFFD4" xml:space="preserve">
+    <value>Aquamarine</value>
+  </data>
+    <data name="#F0FFFF" xml:space="preserve">
+    <value>Azure</value>
+  </data>
+    <data name="#F5F5DC" xml:space="preserve">
+    <value>Beige</value>
+  </data>
+    <data name="#FFE4C4" xml:space="preserve">
+    <value>Bisque</value>
+  </data>
+    <data name="#000000" xml:space="preserve">
+    <value>Black</value>
+  </data>
+    <data name="#FFEBCD" xml:space="preserve">
+    <value>BlanchedAlmond</value>
+  </data>
+    <data name="#0000FF" xml:space="preserve">
+    <value>Blue</value>
+  </data>
+    <data name="#8A2BE2" xml:space="preserve">
+    <value>BlueViolet</value>
+  </data>
+    <data name="#A52A2A" xml:space="preserve">
+    <value>Brown</value>
+  </data>
+    <data name="#DEB887" xml:space="preserve">
+    <value>BurlyWood</value>
+  </data>
+    <data name="#5F9EA0" xml:space="preserve">
+    <value>CadetBlue</value>
+  </data>
+    <data name="#7FFF00" xml:space="preserve">
+    <value>Chartreuse</value>
+  </data>
+    <data name="#D2691E" xml:space="preserve">
+    <value>Chocolate</value>
+  </data>
+    <data name="#FF7F50" xml:space="preserve">
+    <value>Coral</value>
+  </data>
+    <data name="#6495ED" xml:space="preserve">
+    <value>CornflowerBlue</value>
+  </data>
+    <data name="#FFF8DC" xml:space="preserve">
+    <value>Cornsilk</value>
+  </data>
+    <data name="#DC143C" xml:space="preserve">
+    <value>Crimson</value>
+  </data>
+    <data name="#00FFFF" xml:space="preserve">
+    <value>Cyan</value>
+  </data>
+    <data name="#00008B" xml:space="preserve">
+    <value>DarkBlue</value>
+  </data>
+    <data name="#008B8B" xml:space="preserve">
+    <value>DarkCyan</value>
+  </data>
+    <data name="#B8860B" xml:space="preserve">
+    <value>DarkGoldenRod</value>
+  </data>
+    <data name="#A9A9A9" xml:space="preserve">
+    <value>DarkGrey</value>
+  </data>
+    <data name="#006400" xml:space="preserve">
+    <value>DarkGreen</value>
+  </data>
+    <data name="#BDB76B" xml:space="preserve">
+    <value>DarkKhaki</value>
+  </data>
+    <data name="#8B008B" xml:space="preserve">
+    <value>DarkMagenta</value>
+  </data>
+    <data name="#556B2F" xml:space="preserve">
+    <value>DarkOliveGreen</value>
+  </data>
+    <data name="#FF8C00" xml:space="preserve">
+    <value>DarkOrange</value>
+  </data>
+    <data name="#9932CC" xml:space="preserve">
+    <value>DarkOrchid</value>
+  </data>
+    <data name="#8B0000" xml:space="preserve">
+    <value>DarkRed</value>
+  </data>
+    <data name="#E9967A" xml:space="preserve">
+    <value>DarkSalmon</value>
+  </data>
+    <data name="#8FBC8F" xml:space="preserve">
+    <value>DarkSeaGreen</value>
+  </data>
+    <data name="#483D8B" xml:space="preserve">
+    <value>DarkSlateBlue</value>
+  </data>
+    <data name="#2F4F4F" xml:space="preserve">
+    <value>DarkSlateGrey</value>
+  </data>
+    <data name="#00CED1" xml:space="preserve">
+    <value>DarkTurquoise</value>
+  </data>
+    <data name="#9400D3" xml:space="preserve">
+    <value>DarkViolet</value>
+  </data>
+    <data name="#FF1493" xml:space="preserve">
+    <value>DeepPink</value>
+  </data>
+    <data name="#00BFFF" xml:space="preserve">
+    <value>DeepSkyBlue</value>
+  </data>
+    <data name="#696969" xml:space="preserve">
+    <value>DimGray</value>
+  </data>
+    <data name="#1E90FF" xml:space="preserve">
+    <value>DodgerBlue</value>
+  </data>
+    <data name="#B22222" xml:space="preserve">
+    <value>FireBrick</value>
+  </data>
+    <data name="#FFFAF0" xml:space="preserve">
+    <value>FloralWhite</value>
+  </data>
+    <data name="#228B22" xml:space="preserve">
+    <value>ForestGreen</value>
+  </data>
+    <data name="#DCDCDC" xml:space="preserve">
+    <value>Gainsboro</value>
+  </data>
+    <data name="#F8F8FF" xml:space="preserve">
+    <value>GhostWhite</value>
+  </data>
+    <data name="#FFD700" xml:space="preserve">
+    <value>Gold</value>
+  </data>
+    <data name="#DAA520" xml:space="preserve">
+    <value>GoldenRod</value>
+  </data>
+    <data name="#808080" xml:space="preserve">
+    <value>Gray</value>
+  </data>
+    <data name="#008000" xml:space="preserve">
+    <value>Green</value>
+  </data>
+    <data name="#ADFF2F" xml:space="preserve">
+    <value>GreenYellow</value>
+  </data>
+    <data name="#F0FFF0" xml:space="preserve">
+    <value>HoneyDew</value>
+  </data>
+    <data name="#FF69B4" xml:space="preserve">
+    <value>HotPink</value>
+  </data>
+    <data name="#CD5C5C" xml:space="preserve">
+    <value>IndianRed</value>
+  </data>
+    <data name="#4B0082" xml:space="preserve">
+    <value>Indigo</value>
+  </data>
+    <data name="#FFFFF0" xml:space="preserve">
+    <value>Ivory</value>
+  </data>
+    <data name="#F0E68C" xml:space="preserve">
+    <value>Khaki</value>
+  </data>
+    <data name="#E6E6FA" xml:space="preserve">
+    <value>Lavender</value>
+  </data>
+    <data name="#FFF0F5" xml:space="preserve">
+    <value>LavenderBlush</value>
+  </data>
+    <data name="#7CFC00" xml:space="preserve">
+    <value>LawnGreen</value>
+  </data>
+    <data name="#FFFACD" xml:space="preserve">
+    <value>LemonChiffon</value>
+  </data>
+    <data name="#ADD8E6" xml:space="preserve">
+    <value>LightBlue</value>
+  </data>
+    <data name="#F08080" xml:space="preserve">
+    <value>LightCoral</value>
+  </data>
+    <data name="#E0FFFF" xml:space="preserve">
+    <value>LightCyan</value>
+  </data>
+    <data name="#FAFAD2" xml:space="preserve">
+    <value>LightGoldenRodYellow</value>
+  </data>
+    <data name="#D3D3D3" xml:space="preserve">
+    <value>LightGray</value>
+  </data>
+    <data name="#90EE90" xml:space="preserve">
+    <value>LightGreen</value>
+  </data>
+    <data name="#FFB6C1" xml:space="preserve">
+    <value>LightPink</value>
+  </data>
+    <data name="#FFA07A" xml:space="preserve">
+    <value>LightSalmon</value>
+  </data>
+    <data name="#20B2AA" xml:space="preserve">
+    <value>LightSeaGreen</value>
+  </data>
+    <data name="#87CEFA" xml:space="preserve">
+    <value>LightSkyBlue</value>
+  </data>
+    <data name="#778899" xml:space="preserve">
+    <value>LightSlateGrey</value>
+  </data>
+    <data name="#B0C4DE" xml:space="preserve">
+    <value>LightSteelBlue</value>
+  </data>
+    <data name="#FFFFE0" xml:space="preserve">
+    <value>LightYellow</value>
+  </data>
+    <data name="#00FF00" xml:space="preserve">
+    <value>Lime</value>
+  </data>
+    <data name="#32CD32" xml:space="preserve">
+    <value>LimeGreen</value>
+  </data>
+    <data name="#FAF0E6" xml:space="preserve">
+    <value>Linen</value>
+  </data>
+    <data name="#FF00FF" xml:space="preserve">
+    <value>Magenta</value>
+  </data>
+    <data name="#800000" xml:space="preserve">
+    <value>Maroon</value>
+  </data>
+    <data name="#66CDAA" xml:space="preserve">
+    <value>MediumAquaMarine</value>
+  </data>
+    <data name="#0000CD" xml:space="preserve">
+    <value>MediumBlue</value>
+  </data>
+    <data name="#BA55D3" xml:space="preserve">
+    <value>MediumOrchid</value>
+  </data>
+    <data name="#9370DB" xml:space="preserve">
+    <value>MediumPurple</value>
+  </data>
+    <data name="#3CB371" xml:space="preserve">
+    <value>MediumSeaGreen</value>
+  </data>
+    <data name="#7B68EE" xml:space="preserve">
+    <value>MediumSlateBlue</value>
+  </data>
+    <data name="#00FA9A" xml:space="preserve">
+    <value>MediumSpringGreen</value>
+  </data>
+    <data name="#48D1CC" xml:space="preserve">
+    <value>MediumTurquoise</value>
+  </data>
+    <data name="#C71585" xml:space="preserve">
+    <value>MediumVioletRed</value>
+  </data>
+    <data name="#191970" xml:space="preserve">
+    <value>MidnightBlue</value>
+  </data>
+    <data name="#F5FFFA" xml:space="preserve">
+    <value>MintCream</value>
+  </data>
+    <data name="#FFE4E1" xml:space="preserve">
+    <value>MistyRose</value>
+  </data>
+    <data name="#FFE4B5" xml:space="preserve">
+    <value>Moccasin</value>
+  </data>
+    <data name="#FFDEAD" xml:space="preserve">
+    <value>NavajoWhite</value>
+  </data>
+    <data name="#000080" xml:space="preserve">
+    <value>Navy</value>
+  </data>
+    <data name="#FDF5E6" xml:space="preserve">
+    <value>OldLace</value>
+  </data>
+    <data name="#808000" xml:space="preserve">
+    <value>Olive</value>
+  </data>
+    <data name="#6B8E23" xml:space="preserve">
+    <value>OliveDrab</value>
+  </data>
+    <data name="#FFA500" xml:space="preserve">
+    <value>Orange</value>
+  </data>
+    <data name="#FF4500" xml:space="preserve">
+    <value>OrangeRed</value>
+  </data>
+    <data name="#DA70D6" xml:space="preserve">
+    <value>Orchid</value>
+  </data>
+    <data name="#EEE8AA" xml:space="preserve">
+    <value>PaleGoldenRod</value>
+  </data>
+    <data name="#98FB98" xml:space="preserve">
+    <value>PaleGreen</value>
+  </data>
+    <data name="#AFEEEE" xml:space="preserve">
+    <value>PaleTurquoise</value>
+  </data>
+    <data name="#DB7093" xml:space="preserve">
+    <value>PaleVioletRed</value>
+  </data>
+    <data name="#FFEFD5" xml:space="preserve">
+    <value>PapayaWhip</value>
+  </data>
+    <data name="#FFDAB9" xml:space="preserve">
+    <value>PeachPuff</value>
+  </data>
+    <data name="#CD853F" xml:space="preserve">
+    <value>Peru</value>
+  </data>
+    <data name="#FFC0CB" xml:space="preserve">
+    <value>Pink</value>
+  </data>
+    <data name="#DDA0DD" xml:space="preserve">
+    <value>Plum</value>
+  </data>
+    <data name="#B0E0E6" xml:space="preserve">
+    <value>PowderBlue</value>
+  </data>
+    <data name="#800080" xml:space="preserve">
+    <value>Purple</value>
+  </data>
+    <data name="#663399" xml:space="preserve">
+    <value>RebeccaPurple</value>
+  </data>
+    <data name="#FF0000" xml:space="preserve">
+    <value>Red</value>
+  </data>
+    <data name="#BC8F8F" xml:space="preserve">
+    <value>RosyBrown</value>
+  </data>
+    <data name="#4169E1" xml:space="preserve">
+    <value>RoyalBlue</value>
+  </data>
+    <data name="#8B4513" xml:space="preserve">
+    <value>SaddleBrown</value>
+  </data>
+    <data name="#FA8072" xml:space="preserve">
+    <value>Salmon</value>
+  </data>
+    <data name="#F4A460" xml:space="preserve">
+    <value>SandyBrown</value>
+  </data>
+    <data name="#2E8B57" xml:space="preserve">
+    <value>SeaGreen</value>
+  </data>
+    <data name="#FFF5EE" xml:space="preserve">
+    <value>SeaShell</value>
+  </data>
+    <data name="#A0522D" xml:space="preserve">
+    <value>Sienna</value>
+  </data>
+    <data name="#C0C0C0" xml:space="preserve">
+    <value>Silver</value>
+  </data>
+    <data name="#87CEEB" xml:space="preserve">
+    <value>SkyBlue</value>
+  </data>
+    <data name="#6A5ACD" xml:space="preserve">
+    <value>SlateBlue</value>
+  </data>
+    <data name="#708090" xml:space="preserve">
+    <value>SlateGray</value>
+  </data>
+    <data name="#FFFAFA" xml:space="preserve">
+    <value>Snow</value>
+  </data>
+    <data name="#00FF7F" xml:space="preserve">
+    <value>SpringGreen</value>
+  </data>
+    <data name="#4682B4" xml:space="preserve">
+    <value>SteelBlue</value>
+  </data>
+    <data name="#D2B48C" xml:space="preserve">
+    <value>Tan</value>
+  </data>
+    <data name="#008080" xml:space="preserve">
+    <value>Teal</value>
+  </data>
+    <data name="#D8BFD8" xml:space="preserve">
+    <value>Thistle</value>
+  </data>
+    <data name="#FF6347" xml:space="preserve">
+    <value>Tomato</value>
+  </data>
+    <data name="#40E0D0" xml:space="preserve">
+    <value>Turquoise</value>
+  </data>
+    <data name="#EE82EE" xml:space="preserve">
+    <value>Violet</value>
+  </data>
+    <data name="#F5DEB3" xml:space="preserve">
+    <value>Wheat</value>
+  </data>
+    <data name="#FFFFFF" xml:space="preserve">
+    <value>White</value>
+  </data>
+    <data name="#F5F5F5" xml:space="preserve">
+    <value>WhiteSmoke</value>
+  </data>
+    <data name="#FFFF00" xml:space="preserve">
+    <value>Yellow</value>
+  </data>
+    <data name="#9ACD32" xml:space="preserve">
+    <value>YellowGreen</value>
+  </data>
 </root>

+ 1 - 0
Terminal.Gui/Resources/config.json

@@ -22,6 +22,7 @@
   "Application.NextTabGroupKey": "F6",
   "Application.PrevTabGroupKey": "Shift+F6",
   "Application.QuitKey": "Esc",
+  "Key.Separator": "+",
 
   "Theme": "Default",
   "Themes": [

+ 3 - 6
Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs

@@ -36,17 +36,14 @@ public abstract class AutocompleteBase : IAutocomplete
     /// <inheritdoc/>
     public abstract ColorScheme ColorScheme { get; set; }
 
-    // TODO: Update to use Key instead of KeyCode
     /// <inheritdoc/>
-    public virtual KeyCode SelectionKey { get; set; } = KeyCode.Enter;
+    public virtual Key SelectionKey { get; set; } = Key.Enter;
 
-    // TODO: Update to use Key instead of KeyCode
     /// <inheritdoc/>
-    public virtual KeyCode CloseKey { get; set; } = KeyCode.Esc;
+    public virtual Key CloseKey { get; set; } = Key.Esc;
 
-    // TODO: Update to use Key instead of KeyCode
     /// <inheritdoc/>
-    public virtual KeyCode Reopen { get; set; } = (KeyCode)Key.Space.WithCtrl.WithAlt;
+    public virtual Key Reopen { get; set; } = Key.Space.WithCtrl.WithAlt;
 
     /// <inheritdoc/>
     public virtual AutocompleteContext Context { get; set; }

+ 33 - 36
Terminal.Gui/Text/Autocomplete/IAutocomplete.cs

@@ -8,9 +8,11 @@ namespace Terminal.Gui;
 /// </summary>
 public interface IAutocomplete
 {
-    // TODO: Update to use Key instead of KeyCode
+    /// <summary>Clears <see cref="Suggestions"/></summary>
+    void ClearSuggestions ();
+
     /// <summary>The key that the user can press to close the currently popped autocomplete menu</summary>
-    KeyCode CloseKey { get; set; }
+    Key CloseKey { get; set; }
 
     /// <summary>
     ///     The colors to use to render the overlay. Accessing this property before the Application has been initialized
@@ -21,6 +23,12 @@ public interface IAutocomplete
     /// <summary>The context used by the autocomplete menu.</summary>
     AutocompleteContext Context { get; set; }
 
+    /// <summary>
+    ///     Populates <see cref="Suggestions"/> with all <see cref="Suggestion"/> proposed by
+    ///     <see cref="SuggestionGenerator"/> at the given <paramref name="context"/> (cursor position)
+    /// </summary>
+    void GenerateSuggestions (AutocompleteContext context);
+
     /// <summary>The host control that will use autocomplete.</summary>
     View HostControl { get; set; }
 
@@ -30,19 +38,38 @@ public interface IAutocomplete
     /// <summary>The maximum width of the autocomplete dropdown</summary>
     int MaxWidth { get; set; }
 
+    /// <summary>
+    ///     Handle mouse events before <see cref="HostControl"/> e.g. to make mouse events like report/click apply to the
+    ///     autocomplete control instead of changing the cursor position in the underlying text view.
+    /// </summary>
+    /// <param name="me">The mouse event.</param>
+    /// <param name="fromHost">If was called from the popup or from the host.</param>
+    /// <returns><c>true</c>if the mouse can be handled <c>false</c>otherwise.</returns>
+    bool OnMouseEvent (MouseEvent me, bool fromHost = false);
+
     /// <summary>Gets or sets where the popup will be displayed.</summary>
     bool PopupInsideContainer { get; set; }
 
-    // TODO: Update to use Key instead of KeyCode
+    /// <summary>
+    ///     Handle key events before <see cref="HostControl"/> e.g. to make key events like up/down apply to the
+    ///     autocomplete control instead of changing the cursor position in the underlying text view.
+    /// </summary>
+    /// <param name="a">The key event.</param>
+    /// <returns><c>true</c>if the key can be handled <c>false</c>otherwise.</returns>
+    bool ProcessKey (Key a);
+
+    /// <summary>Renders the autocomplete dialog inside the given <see cref="HostControl"/> at the given point.</summary>
+    /// <param name="renderAt"></param>
+    void RenderOverlay (Point renderAt);
+
     /// <summary>The key that the user can press to reopen the currently popped autocomplete menu</summary>
-    KeyCode Reopen { get; set; }
+    Key Reopen { get; set; }
 
     /// <summary>The currently selected index into <see cref="Suggestions"/> that the user has highlighted</summary>
     int SelectedIdx { get; set; }
 
-    // TODO: Update to use Key instead of KeyCode
     /// <summary>The key that the user must press to accept the currently selected autocomplete suggestion</summary>
-    KeyCode SelectionKey { get; set; }
+    Key SelectionKey { get; set; }
 
     /// <summary>
     ///     Gets or Sets the class responsible for generating <see cref="Suggestions"/> based on a given
@@ -55,34 +82,4 @@ public interface IAutocomplete
 
     /// <summary>True if the autocomplete should be considered open and visible</summary>
     bool Visible { get; set; }
-
-    /// <summary>Clears <see cref="Suggestions"/></summary>
-    void ClearSuggestions ();
-
-    /// <summary>
-    ///     Populates <see cref="Suggestions"/> with all <see cref="Suggestion"/> proposed by
-    ///     <see cref="SuggestionGenerator"/> at the given <paramref name="context"/> (cursor position)
-    /// </summary>
-    void GenerateSuggestions (AutocompleteContext context);
-
-    /// <summary>
-    ///     Handle mouse events before <see cref="HostControl"/> e.g. to make mouse events like report/click apply to the
-    ///     autocomplete control instead of changing the cursor position in the underlying text view.
-    /// </summary>
-    /// <param name="me">The mouse event.</param>
-    /// <param name="fromHost">If was called from the popup or from the host.</param>
-    /// <returns><c>true</c>if the mouse can be handled <c>false</c>otherwise.</returns>
-    bool OnMouseEvent (MouseEvent me, bool fromHost = false);
-
-    /// <summary>
-    ///     Handle key events before <see cref="HostControl"/> e.g. to make key events like up/down apply to the
-    ///     autocomplete control instead of changing the cursor position in the underlying text view.
-    /// </summary>
-    /// <param name="a">The key event.</param>
-    /// <returns><c>true</c>if the key can be handled <c>false</c>otherwise.</returns>
-    bool ProcessKey (Key a);
-
-    /// <summary>Renders the autocomplete dialog inside the given <see cref="HostControl"/> at the given point.</summary>
-    /// <param name="renderAt"></param>
-    void RenderOverlay (Point renderAt);
 }

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

@@ -5,16 +5,15 @@ public abstract partial class PopupAutocomplete
 {
     private sealed class Popup : View
     {
-        private readonly PopupAutocomplete _autoComplete;
-
         public Popup (PopupAutocomplete autoComplete)
         {
-            this._autoComplete = autoComplete;
+            _autoComplete = autoComplete;
             CanFocus = true;
+            TabStop = TabBehavior.NoStop;
             WantMousePositionReports = true;
         }
 
-        protected internal override bool OnMouseEvent  (MouseEvent mouseEvent) { return _autoComplete.OnMouseEvent (mouseEvent); }
+        private readonly PopupAutocomplete _autoComplete;
 
         public override void OnDrawContent (Rectangle viewport)
         {
@@ -25,5 +24,7 @@ public abstract partial class PopupAutocomplete
 
             _autoComplete.RenderOverlay (_autoComplete.LastPopupPos.Value);
         }
+
+        protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { return _autoComplete.OnMouseEvent (mouseEvent); }
     }
 }

+ 98 - 70
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs

@@ -1,3 +1,5 @@
+using System.Diagnostics;
+
 namespace Terminal.Gui;
 
 /// <summary>
@@ -6,11 +8,12 @@ namespace Terminal.Gui;
 /// </summary>
 public abstract partial class PopupAutocomplete : AutocompleteBase
 {
-    private bool closed;
-    private ColorScheme colorScheme;
-    private View hostControl;
-    private View top, popup;
-    private int toRenderLength;
+    private bool _closed;
+    private ColorScheme _colorScheme;
+    private View _hostControl;
+    private View _top;  // The _hostControl's SuperView
+    private View _popup;
+    private int _toRenderLength;
 
     /// <summary>Creates a new instance of the <see cref="PopupAutocomplete"/> class.</summary>
     public PopupAutocomplete () { PopupInsideContainer = true; }
@@ -23,43 +26,59 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
     {
         get
         {
-            if (colorScheme is null)
+            if (_colorScheme is null)
             {
-                colorScheme = Colors.ColorSchemes ["Menu"];
+                _colorScheme = Colors.ColorSchemes ["Menu"];
             }
 
-            return colorScheme;
+            return _colorScheme;
         }
-        set => colorScheme = value;
+        set => _colorScheme = value;
     }
 
     /// <summary>The host control to handle.</summary>
     public override View HostControl
     {
-        get => hostControl;
+        get => _hostControl;
         set
         {
-            hostControl = value;
-            top = hostControl.SuperView;
+            if (value == _hostControl)
+            {
+                return;
+            }
+
+            _hostControl = value;
+
+            if (_hostControl is null)
+            {
+                RemovePopupFromTop();
+                _top.Removed -= _top_Removed;
+                _top = null;
+
+                return;
+            }
 
-            if (top is { })
+            _top = _hostControl.SuperView;
+
+            if (_top is { })
             {
-                top.DrawContent += Top_DrawContent;
-                top.DrawContentComplete += Top_DrawContentComplete;
-                top.Removed += Top_Removed;
+                if (_top.IsInitialized)
+                {
+                    AddPopupToTop ();
+                }
+                else
+                {
+                    _top.Initialized += _top_Initialized;
+                }
+                _top.Removed += _top_Removed;
             }
         }
     }
 
-    /// <summary>
-    ///     When more suggestions are available than can be rendered the user can scroll down the dropdown list. This
-    ///     indicates how far down they have gone
-    /// </summary>
-    public virtual int ScrollOffset { get; set; }
-
-    #nullable enable
-    private Point? LastPopupPos { get; set; }
-    #nullable restore
+    private void _top_Added (object sender, SuperViewChangedEventArgs e)
+    {
+        throw new NotImplementedException ();
+    }
 
     /// <inheritdoc/>
     public override void EnsureSelectedIdxIsValid ()
@@ -73,7 +92,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         }
 
         // if user moved selection down past bottom of current scroll window
-        while (toRenderLength > 0 && SelectedIdx >= ScrollOffset + toRenderLength)
+        while (_toRenderLength > 0 && SelectedIdx >= ScrollOffset + _toRenderLength)
         {
             ScrollOffset++;
         }
@@ -119,7 +138,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
             if (Visible && HostControl is { })
             {
                 Visible = false;
-                closed = false;
+                _closed = false;
             }
 
             HostControl?.SetNeedsDisplay ();
@@ -127,9 +146,10 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
             return false;
         }
 
-        if (popup is null || Suggestions.Count == 0)
+        if (_popup is null || Suggestions.Count == 0)
         {
-            ManipulatePopup ();
+            //AddPopupToTop ();
+            //Debug.Fail ("popup is null");
 
             return false;
         }
@@ -176,8 +196,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         if (SuggestionGenerator.IsWordChar ((Rune)key))
         {
             Visible = true;
-            ManipulatePopup ();
-            closed = false;
+            _closed = false;
 
             return false;
         }
@@ -189,11 +208,11 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
             return ReopenSuggestions ();
         }
 
-        if (closed || Suggestions.Count == 0)
+        if (_closed || Suggestions.Count == 0)
         {
             Visible = false;
 
-            if (!closed)
+            if (!_closed)
             {
                 Close ();
             }
@@ -249,7 +268,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
     {
         if (!Context.Canceled && Suggestions.Count > 0 && !Visible && HostControl?.HasFocus == true)
         {
-            ProcessKey (new Key (Suggestions [0].Title [0]));
+            ProcessKey (new (Suggestions [0].Title [0]));
         }
         else if (!Visible || HostControl?.HasFocus == false || Suggestions.Count == 0)
         {
@@ -287,13 +306,13 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         else
         {
             // don't overspill vertically
-            height = Math.Min (Math.Min (top.Viewport.Height - HostControl.Frame.Bottom, MaxHeight), Suggestions.Count);
+            height = Math.Min (Math.Min (_top.Viewport.Height - HostControl.Frame.Bottom, MaxHeight), Suggestions.Count);
 
             // There is no space below, lets see if can popup on top
-            if (height < Suggestions.Count && HostControl.Frame.Y - top.Frame.Y >= height)
+            if (height < Suggestions.Count && HostControl.Frame.Y - _top.Frame.Y >= height)
             {
                 // Verifies that the upper limit available is greater than the lower limit
-                if (HostControl.Frame.Y > top.Viewport.Height - HostControl.Frame.Y)
+                if (HostControl.Frame.Y > _top.Viewport.Height - HostControl.Frame.Y)
                 {
                     renderAt.Y = Math.Max (HostControl.Frame.Y - Math.Min (Suggestions.Count, MaxHeight), 0);
                     height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), HostControl.Frame.Y);
@@ -311,7 +330,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         }
 
         Suggestion [] toRender = Suggestions.Skip (ScrollOffset).Take (height).ToArray ();
-        toRenderLength = toRender.Length;
+        _toRenderLength = toRender.Length;
 
         if (toRender.Length == 0)
         {
@@ -340,37 +359,37 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         else
         {
             // don't overspill horizontally, let's see if it can be displayed on the left
-            if (width > top.Viewport.Width - (renderAt.X + HostControl.Frame.X))
+            if (width > _top.Viewport.Width - (renderAt.X + HostControl.Frame.X))
             {
                 // Verifies that the left limit available is greater than the right limit
-                if (renderAt.X + HostControl.Frame.X > top.Viewport.Width - (renderAt.X + HostControl.Frame.X))
+                if (renderAt.X + HostControl.Frame.X > _top.Viewport.Width - (renderAt.X + HostControl.Frame.X))
                 {
                     renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
                     width = Math.Min (width, LastPopupPos.Value.X);
                 }
                 else
                 {
-                    width = Math.Min (width, top.Viewport.Width - renderAt.X);
+                    width = Math.Min (width, _top.Viewport.Width - renderAt.X);
                 }
             }
         }
 
         if (PopupInsideContainer)
         {
-            popup.Frame = new (
+            _popup.Frame = new (
                                new (HostControl.Frame.X + renderAt.X, HostControl.Frame.Y + renderAt.Y),
                                new (width, height)
                               );
         }
         else
         {
-            popup.Frame = new (
+            _popup.Frame = new (
                                renderAt with { X = HostControl.Frame.X + renderAt.X },
                                new (width, height)
                               );
         }
 
-        popup.Move (0, 0);
+        _popup.Move (0, 0);
 
         for (var i = 0; i < toRender.Length; i++)
         {
@@ -383,7 +402,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
                 Application.Driver?.SetAttribute (ColorScheme.Normal);
             }
 
-            popup.Move (0, i);
+            _popup.Move (0, i);
 
             string text = TextFormatter.ClipOrPad (toRender [i].Title, width);
 
@@ -391,6 +410,12 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         }
     }
 
+    /// <summary>
+    ///     When more suggestions are available than can be rendered the user can scroll down the dropdown list. This
+    ///     indicates how far down they have gone
+    /// </summary>
+    public virtual int ScrollOffset { get; set; }
+
     /// <summary>
     ///     Closes the Autocomplete context menu if it is showing and <see cref="IAutocomplete.ClearSuggestions"/>
     /// </summary>
@@ -398,9 +423,9 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
     {
         ClearSuggestions ();
         Visible = false;
-        closed = true;
+        _closed = true;
         HostControl?.SetNeedsDisplay ();
-        ManipulatePopup ();
+        //RemovePopupFromTop ();
     }
 
     /// <summary>Deletes the text backwards before insert the selected text in the <see cref="HostControl"/>.</summary>
@@ -486,7 +511,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         if (Suggestions.Count > 0)
         {
             Visible = true;
-            closed = false;
+            _closed = false;
             HostControl?.SetNeedsDisplay ();
 
             return true;
@@ -516,42 +541,45 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
     /// <param name="column"></param>
     protected abstract void SetCursorPosition (int column);
 
-    private void ManipulatePopup ()
-    {
-        if (Visible && popup is null)
-        {
-            popup = new Popup (this) { Frame = Rectangle.Empty };
-            top?.Add (popup);
-        }
+#nullable enable
+    private Point? LastPopupPos { get; set; }
+#nullable restore
 
-        if (!Visible && popup is { })
+    private void AddPopupToTop ()
+    {
+        if (_popup is null)
         {
-            top?.Remove (popup);
-            popup.Dispose ();
-            popup = null;
+            _popup = new Popup (this)
+            {
+                CanFocus = false
+            };
+            _top?.Add (_popup);
         }
     }
 
-    private void Top_DrawContent (object sender, DrawEventArgs e)
+    private void RemovePopupFromTop ()
     {
-        if (!closed)
+        if (_popup is { } && _top.Subviews.Contains (_popup))
         {
-            ReopenSuggestions ();
-        }
+            _top?.Remove (_popup);
+            _popup.Dispose ();
+            _popup = null;
 
-        ManipulatePopup ();
+        }
+    }
 
-        if (Visible)
+    private void _top_Initialized (object sender, EventArgs e)
+    {
+        if (_top is null)
         {
-            top.BringSubviewToFront (popup);
+            _top = sender as View;
         }
+        AddPopupToTop ();
     }
 
-    private void Top_DrawContentComplete (object sender, DrawEventArgs e) { ManipulatePopup (); }
-
-    private void Top_Removed (object sender, SuperViewChangedEventArgs e)
+    private void _top_Removed (object sender, SuperViewChangedEventArgs e)
     {
         Visible = false;
-        ManipulatePopup ();
+        RemovePopupFromTop ();
     }
 }

+ 1 - 0
Terminal.Gui/Text/CollectionNavigatorBase.cs

@@ -147,6 +147,7 @@ public abstract class CollectionNavigatorBase
     public virtual void OnSearchStringChanged (KeystrokeNavigatorEventArgs e) { SearchStringChanged?.Invoke (this, e); }
 
     /// <summary>This event is invoked when <see cref="SearchString"/>  changes. Useful for debugging.</summary>
+    [CanBeNull]
     public event EventHandler<KeystrokeNavigatorEventArgs> SearchStringChanged;
 
     /// <summary>Returns the collection being navigated element at <paramref name="idx"/>.</summary>

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

@@ -86,7 +86,7 @@ public class Adornment : View
     ///     Adornments cannot be used as sub-views (see <see cref="Parent"/>); setting this property will throw
     ///     <see cref="InvalidOperationException"/>.
     /// </summary>
-    public override View SuperView
+    public override View? SuperView
     {
         get => null!;
         set => throw new InvalidOperationException (@"Adornments can not be Subviews or have SuperViews. Use Parent instead.");

+ 4 - 1
Terminal.Gui/View/Adornment/ShadowStyle.cs

@@ -1,8 +1,11 @@
-namespace Terminal.Gui;
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
 
 /// <summary>
 ///     Defines the style of shadow to be drawn on the right and bottom sides of the <see cref="View"/>.
 /// </summary>
+[JsonConverter (typeof (JsonStringEnumConverter<ShadowStyle>))]
 public enum ShadowStyle
 {
     /// <summary>

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

@@ -109,7 +109,7 @@ internal class ShadowView : View
         Rectangle screen = ViewportToScreen (viewport);
 
         // Fill the rest of the rectangle - note we skip the last since vertical will draw it
-        for (int i = screen.X; i < screen.X + screen.Width - 1; i++)
+        for (int i = screen.X + 1; i < screen.X + screen.Width - 1; i++)
         {
             Driver.Move (i, screen.Y);
 

+ 26 - 28
Terminal.Gui/View/Layout/Dim.cs

@@ -1,8 +1,8 @@
 #nullable enable
-using System.Diagnostics;
-
 namespace Terminal.Gui;
 
+using System.Numerics;
+
 /// <summary>
 ///     <para>
 ///         A Dim object describes the dimensions of a <see cref="View"/>. Dim is the type of the
@@ -78,7 +78,7 @@ namespace Terminal.Gui;
 ///     </para>
 ///     <para></para>
 /// </remarks>
-public abstract class Dim
+public abstract record Dim : IEqualityOperators<Dim, Dim, bool>
 {
     #region static Dim creation methods
 
@@ -113,20 +113,24 @@ public abstract class Dim
     /// <param name="maximumContentDim">The maximum dimension the View's ContentSize will be fit to.</param>
     public static Dim? Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim? minimumContentDim = null, Dim? maximumContentDim = null)
     {
-        return new DimAuto ()
-        {
-            MinimumContentDim = minimumContentDim,
-            MaximumContentDim = maximumContentDim,
-            Style = style
-        };
+        return new DimAuto (
+                            MinimumContentDim: minimumContentDim,
+                            MaximumContentDim: maximumContentDim,
+                            Style: style);
     }
 
+    /// <summary>
+    ///     Creates a <see cref="Dim"/> object that fills the dimension, leaving no margin.
+    /// </summary>
+    /// <returns>The Fill dimension.</returns>
+    public static Dim? Fill () { return new DimFill (0); }
+
     /// <summary>
     ///     Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified margin.
     /// </summary>
     /// <returns>The Fill dimension.</returns>
     /// <param name="margin">Margin to use.</param>
-    public static Dim? Fill (int margin = 0) { return new DimFill (margin); }
+    public static Dim? Fill (Dim margin) { return new DimFill (margin); }
 
     /// <summary>
     ///     Creates a function <see cref="Dim"/> object that computes the dimension by executing the provided function.
@@ -172,28 +176,22 @@ public abstract class Dim
 
     #endregion static Dim creation methods
 
+
     /// <summary>
-    ///     Indicates whether the specified type is in the hierarchy of this Dim object.
+    ///     Indicates whether the specified type <typeparamref name="T"/> is in the hierarchy of this Dim object.
     /// </summary>
-    /// <param name="type"></param>
-    /// <param name="dim"></param>
+    /// <param name="dim">A reference to this <see cref="Dim"/> instance.</param>
     /// <returns></returns>
-    public bool Has (Type type, out Dim dim)
+    public bool Has<T> (out Dim dim) where T : Dim
     {
         dim = this;
-        if (type == GetType ())
-        {
-            return true;
-        }
-
-        // If we are a PosCombine, we have to check the left and right
-        // to see if they are of the type we are looking for.
-        if (this is DimCombine { } combine && (combine.Left.Has (type, out dim) || combine.Right.Has (type, out dim)))
-        {
-            return true;
-        }
 
-        return false;
+        return this switch
+               {
+                   DimCombine combine => combine.Left.Has<T> (out dim) || combine.Right.Has<T> (out dim),
+                   T => true,
+                   _ => false
+               };
     }
 
     #region virtual methods
@@ -208,7 +206,7 @@ public abstract class Dim
     ///     subclass of Dim that is used. For example, DimAbsolute returns a fixed dimension, DimFactor returns a
     ///     dimension that is a certain percentage of the super view's size, and so on.
     /// </returns>
-    internal virtual int GetAnchor (int size) { return 0; }
+    internal abstract int GetAnchor (int size);
 
     /// <summary>
     ///     Calculates and returns the dimension of a <see cref="View"/> object. It takes into account the location of the
@@ -228,7 +226,7 @@ public abstract class Dim
     /// </returns>
     internal virtual int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
     {
-        return Math.Max (GetAnchor (superviewContentSize - location), 0);
+        return Math.Clamp (GetAnchor (superviewContentSize - location), 0, short.MaxValue);
     }
 
     /// <summary>

+ 3 - 9
Terminal.Gui/View/Layout/DimAbsolute.cs

@@ -10,19 +10,13 @@ namespace Terminal.Gui;
 ///         methods on the <see cref="Dim"/> class to create <see cref="Dim"/> objects instead.
 ///     </para>
 /// </remarks>
-/// <param name="size"></param>
-public class DimAbsolute (int size) : Dim
+/// <param name="Size"></param>
+public record DimAbsolute (int Size) : Dim
 {
-    /// <inheritdoc/>
-    public override bool Equals (object? other) { return other is DimAbsolute abs && abs.Size == Size; }
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return Size.GetHashCode (); }
-
     /// <summary>
     ///     Gets the size of the dimension.
     /// </summary>
-    public int Size { get; } = size;
+    public int Size { get; } = Size;
 
     /// <inheritdoc/>
     public override string ToString () { return $"Absolute({Size})"; }

+ 32 - 74
Terminal.Gui/View/Layout/DimAuto.cs

@@ -15,64 +15,17 @@ namespace Terminal.Gui;
 ///         methods on the <see cref="Dim"/> class to create <see cref="Dim"/> objects instead.
 ///     </para>
 /// </remarks>
-public class DimAuto : Dim
+/// <param name="MaximumContentDim">The maximum dimension the View's ContentSize will be fit to.</param>
+/// <param name="MinimumContentDim">The minimum dimension the View's ContentSize will be constrained to.</param>
+/// <param name="Style">The <see cref="DimAutoStyle"/> of the <see cref="DimAuto"/>.</param>
+public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoStyle Style) : Dim
 {
-    private readonly Dim? _maximumContentDim;
-
-    private readonly Dim? _minimumContentDim;
-
-    private readonly DimAutoStyle _style;
-
-    /// <inheritdoc/>
-    public override bool Equals (object? other)
-    {
-        if (other is not DimAuto auto)
-        {
-            return false;
-        }
-
-        return auto.MinimumContentDim == MinimumContentDim && auto.MaximumContentDim == MaximumContentDim && auto.Style == Style;
-    }
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return HashCode.Combine (MinimumContentDim, MaximumContentDim, Style); }
-
-    /// <summary>
-    ///     Gets the maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED.
-    /// </summary>
-
-    // ReSharper disable once ConvertToAutoProperty
-    public required Dim? MaximumContentDim
-    {
-        get => _maximumContentDim;
-        init => _maximumContentDim = value;
-    }
-
-    /// <summary>
-    ///     Gets the minimum dimension the View's ContentSize will be constrained to.
-    /// </summary>
-
-    // ReSharper disable once ConvertToAutoProperty
-    public required Dim? MinimumContentDim
-    {
-        get => _minimumContentDim;
-        init => _minimumContentDim = value;
-    }
-
-    /// <summary>
-    ///     Gets the style of the DimAuto.
-    /// </summary>
-
-    // ReSharper disable once ConvertToAutoProperty
-    public required DimAutoStyle Style
-    {
-        get => _style;
-        init => _style = value;
-    }
-
     /// <inheritdoc/>
     public override string ToString () { return $"Auto({Style},{MinimumContentDim},{MaximumContentDim})"; }
 
+    /// <inheritdoc />
+    internal override int GetAnchor (int size) => 0;
+
     internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
     {
         var textSize = 0;
@@ -178,11 +131,11 @@ public class DimAuto : Dim
                     notDependentSubViews = includedSubviews.Where (
                                                                    v => v.Width is { }
                                                                         && (v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) // BUGBUG: We should use v.X.Has and v.Width.Has?
-                                                                        && !v.X.Has (typeof (PosAnchorEnd), out _)
-                                                                        && !v.X.Has (typeof (PosAlign), out _)
-                                                                        && !v.X.Has (typeof (PosCenter), out _)
-                                                                        && !v.Width.Has (typeof (DimFill), out _)
-                                                                        && !v.Width.Has (typeof (DimPercent), out _)
+                                                                        && !v.X.Has<PosAnchorEnd> (out _)
+                                                                        && !v.X.Has<PosAlign> (out _)
+                                                                        && !v.X.Has<PosCenter> (out _)
+                                                                        && !v.Width.Has<DimFill> (out _)
+                                                                        && !v.Width.Has<DimPercent> (out _)
                                                                   )
                                                            .ToList ();
                 }
@@ -191,15 +144,20 @@ public class DimAuto : Dim
                     notDependentSubViews = includedSubviews.Where (
                                                                    v => v.Height is { }
                                                                         && (v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) // BUGBUG: We should use v.Y.Has and v.Height.Has?
-                                                                        && !v.Y.Has (typeof (PosAnchorEnd), out _)
-                                                                        && !v.Y.Has (typeof (PosAlign), out _)
-                                                                        && !v.Y.Has (typeof (PosCenter), out _)
-                                                                        && !v.Height.Has (typeof (DimFill), out _)
-                                                                        && !v.Height.Has (typeof (DimPercent), out _)
+                                                                        && !v.Y.Has<PosAnchorEnd> (out _)
+                                                                        && !v.Y.Has<PosAlign> (out _)
+                                                                        && !v.Y.Has<PosCenter> (out _)
+                                                                        && !v.Height.Has<DimFill> (out _)
+                                                                        && !v.Height.Has<DimPercent> (out _)
                                                                   )
                                                            .ToList ();
                 }
 
+                foreach (View notDependentSubView in notDependentSubViews)
+                {
+                    notDependentSubView.SetRelativeLayout (us.GetContentSize ());
+                }
+
                 for (var i = 0; i < notDependentSubViews.Count; i++)
                 {
                     View v = notDependentSubViews [i];
@@ -232,11 +190,11 @@ public class DimAuto : Dim
 
                 if (dimension == Dimension.Width)
                 {
-                    centeredSubViews = us.Subviews.Where (v => v.X.Has (typeof (PosCenter), out _)).ToList ();
+                    centeredSubViews = us.Subviews.Where (v => v.X.Has<PosCenter> (out _)).ToList ();
                 }
                 else
                 {
-                    centeredSubViews = us.Subviews.Where (v => v.Y.Has (typeof (PosCenter), out _)).ToList ();
+                    centeredSubViews = us.Subviews.Where (v => v.Y.Has<PosCenter> (out _)).ToList ();
                 }
 
                 viewsNeedingLayout.AddRange (centeredSubViews);
@@ -281,14 +239,14 @@ public class DimAuto : Dim
                                                               {
                                                                   if (dimension == Dimension.Width)
                                                                   {
-                                                                      if (v.X.Has (typeof (PosAlign), out Pos posAlign))
+                                                                      if (v.X.Has<PosAlign> (out Pos posAlign))
                                                                       {
                                                                           return ((PosAlign)posAlign).GroupId;
                                                                       }
                                                                   }
                                                                   else
                                                                   {
-                                                                      if (v.Y.Has (typeof (PosAlign), out Pos posAlign))
+                                                                      if (v.Y.Has<PosAlign> (out Pos posAlign))
                                                                       {
                                                                           return ((PosAlign)posAlign).GroupId;
                                                                       }
@@ -336,11 +294,11 @@ public class DimAuto : Dim
 
                 if (dimension == Dimension.Width)
                 {
-                    anchoredSubViews = includedSubviews.Where (v => v.X.Has (typeof (PosAnchorEnd), out _)).ToList ();
+                    anchoredSubViews = includedSubviews.Where (v => v.X.Has<PosAnchorEnd> (out _)).ToList ();
                 }
                 else
                 {
-                    anchoredSubViews = includedSubviews.Where (v => v.Y.Has (typeof (PosAnchorEnd), out _)).ToList ();
+                    anchoredSubViews = includedSubviews.Where (v => v.Y.Has<PosAnchorEnd> (out _)).ToList ();
                 }
 
                 viewsNeedingLayout.AddRange (anchoredSubViews);
@@ -378,11 +336,11 @@ public class DimAuto : Dim
 
                 if (dimension == Dimension.Width)
                 {
-                    posViewSubViews = includedSubviews.Where (v => v.X.Has (typeof (PosView), out _)).ToList ();
+                    posViewSubViews = includedSubviews.Where (v => v.X.Has<PosView> (out _)).ToList ();
                 }
                 else
                 {
-                    posViewSubViews = includedSubviews.Where (v => v.Y.Has (typeof (PosView), out _)).ToList ();
+                    posViewSubViews = includedSubviews.Where (v => v.Y.Has<PosView> (out _)).ToList ();
                 }
 
                 for (var i = 0; i < posViewSubViews.Count; i++)
@@ -419,11 +377,11 @@ public class DimAuto : Dim
 
                 if (dimension == Dimension.Width)
                 {
-                    dimViewSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has (typeof (DimView), out _)).ToList ();
+                    dimViewSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has<DimView> (out _)).ToList ();
                 }
                 else
                 {
-                    dimViewSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has (typeof (DimView), out _)).ToList ();
+                    dimViewSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has<DimView> (out _)).ToList ();
                 }
 
                 for (var i = 0; i < dimViewSubViews.Count; i++)

+ 7 - 7
Terminal.Gui/View/Layout/DimCombine.cs

@@ -4,31 +4,31 @@ namespace Terminal.Gui;
 /// <summary>
 ///     Represents a dimension that is a combination of two other dimensions.
 /// </summary>
-/// <param name="add">
+/// <param name="Add">
 ///     Indicates whether the two dimensions are added or subtracted.
 /// </param>
 /// <remarks>
 ///     This is a low-level API that is typically used internally by the layout system. Use the various static
 ///     methods on the <see cref="Dim"/> class to create <see cref="Dim"/> objects instead.
 /// </remarks>
-/// <param name="left">The left dimension.</param>
-/// <param name="right">The right dimension.</param>
-public class DimCombine (AddOrSubtract add, Dim left, Dim right) : Dim
+/// <param name="Left">The left dimension.</param>
+/// <param name="Right">The right dimension.</param>
+public record DimCombine (AddOrSubtract Add, Dim Left, Dim Right) : Dim
 {
     /// <summary>
     ///     Gets whether the two dimensions are added or subtracted.
     /// </summary>
-    public AddOrSubtract Add { get; } = add;
+    public AddOrSubtract Add { get; } = Add;
 
     /// <summary>
     ///     Gets the left dimension.
     /// </summary>
-    public Dim Left { get; } = left;
+    public Dim Left { get; } = Left;
 
     /// <summary>
     ///     Gets the right dimension.
     /// </summary>
-    public Dim Right { get; } = right;
+    public Dim Right { get; } = Right;
 
     /// <inheritdoc/>
     public override string ToString () { return $"Combine({Left}{(Add == AddOrSubtract.Add ? '+' : '-')}{Right})"; }

+ 3 - 14
Terminal.Gui/View/Layout/DimFill.cs

@@ -8,22 +8,11 @@ namespace Terminal.Gui;
 ///     This is a low-level API that is typically used internally by the layout system. Use the various static
 ///     methods on the <see cref="Dim"/> class to create <see cref="Dim"/> objects instead.
 /// </remarks>
-/// <param name="margin">The margin to not fill.</param>
-public class DimFill (int margin) : Dim
+/// <param name="Margin">The margin to not fill.</param>
+public record DimFill (Dim Margin) : Dim
 {
-    /// <inheritdoc/>
-    public override bool Equals (object? other) { return other is DimFill fill && fill.Margin == Margin; }
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return Margin.GetHashCode (); }
-
-    /// <summary>
-    ///     Gets the margin to not fill.
-    /// </summary>
-    public int Margin { get; } = margin;
-
     /// <inheritdoc/>
     public override string ToString () { return $"Fill({Margin})"; }
 
-    internal override int GetAnchor (int size) { return size - Margin; }
+    internal override int GetAnchor (int size) { return size - Margin.GetAnchor(0); }
 }

+ 7 - 13
Terminal.Gui/View/Layout/DimFunc.cs

@@ -2,28 +2,22 @@
 namespace Terminal.Gui;
 
 /// <summary>
-///     Represents a function <see cref="Dim"/> object that computes the dimension by executing the provided function.
+///     Represents a function <see cref="Gui.Dim"/> object that computes the dimension by executing the provided function.
 /// </summary>
 /// <remarks>
 ///     This is a low-level API that is typically used internally by the layout system. Use the various static
-///     methods on the <see cref="Dim"/> class to create <see cref="Dim"/> objects instead.
+///     methods on the <see cref="Gui.Dim"/> class to create <see cref="Gui.Dim"/> objects instead.
 /// </remarks>
-/// <param name="dim"></param>
-public class DimFunc (Func<int> dim) : Dim
+/// <param name="Fn">The function that computes the dimension.</param>
+public record DimFunc (Func<int> Fn) : Dim
 {
-    /// <inheritdoc/>
-    public override bool Equals (object? other) { return other is DimFunc f && f.Func () == Func (); }
-
     /// <summary>
     ///     Gets the function that computes the dimension.
     /// </summary>
-    public new Func<int> Func { get; } = dim;
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return Func.GetHashCode (); }
+    public Func<int> Fn { get; } = Fn;
 
     /// <inheritdoc/>
-    public override string ToString () { return $"DimFunc({Func ()})"; }
+    public override string ToString () { return $"DimFunc({Fn ()})"; }
 
-    internal override int GetAnchor (int size) { return Func (); }
+    internal override int GetAnchor (int size) { return Fn (); }
 }

+ 6 - 17
Terminal.Gui/View/Layout/DimPercent.cs

@@ -8,35 +8,24 @@ namespace Terminal.Gui;
 ///     This is a low-level API that is typically used internally by the layout system. Use the various static
 ///     methods on the <see cref="Dim"/> class to create <see cref="Dim"/> objects instead.
 /// </remarks>
-/// <param name="percent">The percentage.</param>
-/// <param name="mode">
+/// <param name="Percentage">The percentage.</param>
+/// <param name="Mode">
 ///     If <see cref="DimPercentMode.Position"/> the dimension is computed using the View's position (<see cref="View.X"/> or
 ///     <see cref="View.Y"/>); otherwise, the dimension is computed using the View's <see cref="View.GetContentSize ()"/>.
 /// </param>
-public class DimPercent (int percent, DimPercentMode mode = DimPercentMode.ContentSize) : Dim
+public record DimPercent (int Percentage, DimPercentMode Mode = DimPercentMode.ContentSize) : Dim
 {
-    /// <inheritdoc/>
-    public override bool Equals (object? other) { return other is DimPercent f && f.Percent == Percent && f.Mode == Mode; }
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return Percent.GetHashCode (); }
-
-    /// <summary>
-    ///     Gets the percentage.
-    /// </summary>
-    public new int Percent { get; } = percent;
-
     /// <summary>
     /// </summary>
     /// <returns></returns>
-    public override string ToString () { return $"Percent({Percent},{Mode})"; }
+    public override string ToString () { return $"Percent({Percentage},{Mode})"; }
 
     /// <summary>
     ///     Gets whether the dimension is computed using the View's position or GetContentSize ().
     /// </summary>
-    public DimPercentMode Mode { get; } = mode;
+    public DimPercentMode Mode { get; } = Mode;
 
-    internal override int GetAnchor (int size) { return (int)(size * (Percent / 100f)); }
+    internal override int GetAnchor (int size) { return (int)(size * (Percentage / 100f)); }
 
     internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
     {

+ 1 - 7
Terminal.Gui/View/Layout/DimView.cs

@@ -8,7 +8,7 @@ namespace Terminal.Gui;
 ///     This is a low-level API that is typically used internally by the layout system. Use the various static
 ///     methods on the <see cref="Dim"/> class to create <see cref="Dim"/> objects instead.
 /// </remarks>
-public class DimView : Dim
+public record DimView : Dim
 {
     /// <summary>
     ///     Initializes a new instance of the <see cref="DimView"/> class.
@@ -26,12 +26,6 @@ public class DimView : Dim
     /// </summary>
     public Dimension Dimension { get; }
 
-    /// <inheritdoc/>
-    public override bool Equals (object? other) { return other is DimView abs && abs.Target == Target && abs.Dimension == Dimension; }
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return Target!.GetHashCode (); }
-
     /// <summary>
     ///     Gets the View the dimension is anchored to.
     /// </summary>

+ 10 - 17
Terminal.Gui/View/Layout/Pos.cs

@@ -131,7 +131,7 @@ namespace Terminal.Gui;
 ///         </list>
 ///     </para>
 /// </remarks>
-public abstract class Pos
+public abstract record Pos
 {
     #region static Pos creation methods
 
@@ -331,27 +331,20 @@ public abstract class Pos
     internal virtual bool ReferencesOtherViews () { return false; }
 
     /// <summary>
-    ///     Indicates whether the specified type is in the hierarchy of this Pos object.
+    ///     Indicates whether the specified type <typeparamref name="T"/> is in the hierarchy of this Pos object.
     /// </summary>
-    /// <param name="type"></param>
-    /// <param name="pos"></param>
+    /// <param name="pos">A reference to this <see cref="Pos}"/> instance.</param>
     /// <returns></returns>
-    public bool Has (Type type, out Pos pos)
+    public bool Has<T> (out Pos pos) where T : Pos
     {
         pos = this;
-        if (type == GetType ())
-        {
-            return true;
-        }
-
-        // If we are a PosCombine, we have to check the left and right
-        // to see if they are of the type we are looking for.
-        if (this is PosCombine { } combine && (combine.Left.Has (type, out pos) || combine.Right.Has (type, out pos)))
-        {
-            return true;
-        }
 
-        return false;
+        return this switch
+               {
+                   PosCombine combine => combine.Left.Has<T> (out pos) || combine.Right.Has<T> (out pos),
+                   T => true,
+                   _ => false
+               };
     }
 
     #endregion virtual methods

+ 3 - 9
Terminal.Gui/View/Layout/PosAbsolute.cs

@@ -10,19 +10,13 @@ namespace Terminal.Gui;
 ///         methods on the <see cref="Pos"/> class to create <see cref="Pos"/> objects instead.
 ///     </para>
 /// </remarks>
-/// <param name="position"></param>
-public class PosAbsolute (int position) : Pos
+/// <param name="Position"></param>
+public record PosAbsolute (int Position) : Pos
 {
     /// <summary>
     ///     The position of the <see cref="View"/> in the layout.
     /// </summary>
-    public int Position { get; } = position;
-
-    /// <inheritdoc/>
-    public override bool Equals (object? other) { return other is PosAbsolute abs && abs.Position == Position; }
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return Position.GetHashCode (); }
+    public int Position { get; } = Position;
 
     /// <inheritdoc/>
     public override string ToString () { return $"Absolute({Position})"; }

+ 93 - 106
Terminal.Gui/View/Layout/PosAlign.cs

@@ -1,7 +1,6 @@
 #nullable enable
 
 using System.ComponentModel;
-using System.Drawing;
 
 namespace Terminal.Gui;
 
@@ -24,19 +23,13 @@ namespace Terminal.Gui;
 ///         The alignment is applied to all views with the same <see cref="GroupId"/>.
 ///     </para>
 /// </remarks>
-public class PosAlign : Pos
+public record PosAlign : Pos
 {
     /// <summary>
     ///     The cached location. Used to store the calculated location to minimize recalculating it.
     /// </summary>
     public int? _cachedLocation;
 
-    /// <summary>
-    ///     Gets the identifier of a set of views that should be aligned together. When only a single
-    ///     set of views in a SuperView is aligned, setting <see cref="GroupId"/> is not needed because it defaults to 0.
-    /// </summary>
-    public int GroupId { get; init; }
-
     private readonly Aligner? _aligner;
 
     /// <summary>
@@ -57,103 +50,64 @@ public class PosAlign : Pos
         }
     }
 
+    // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this.
     /// <summary>
-    ///     Aligns the views in <paramref name="views"/> that have the same group ID as <paramref name="groupId"/>.
-    ///     Updates each view's cached _location.
+    ///     Returns the minimum size a group of views with the same <paramref name="groupId"/> can be.
     /// </summary>
     /// <param name="groupId"></param>
     /// <param name="views"></param>
     /// <param name="dimension"></param>
-    /// <param name="size"></param>
-    private static void AlignAndUpdateGroup (int groupId, IList<View> views, Dimension dimension, int size)
+    /// <returns></returns>
+    public static int CalculateMinDimension (int groupId, IList<View> views, Dimension dimension)
     {
         List<int> dimensionsList = new ();
 
         // PERF: If this proves a perf issue, consider caching a ref to this list in each item
-        List<PosAlign?> posAligns = views.Select (
-                                                v =>
-                                                {
-                                                    switch (dimension)
-                                                    {
-                                                        case Dimension.Width when v.X.Has (typeof (PosAlign), out var pos):
-
-                                                            if (pos is PosAlign posAlignX && posAlignX.GroupId == groupId)
-                                                            {
-                                                                return posAlignX;
-                                                            }
-
-                                                            break;
-                                                        case Dimension.Height when v.Y.Has (typeof (PosAlign), out var pos):
-                                                            if (pos is PosAlign posAlignY && posAlignY.GroupId == groupId)
-                                                            {
-                                                                return posAlignY;
-                                                            }
-
-                                                            break;
-                                                    }
-
-                                                    return null;
-                                                })
+        List<View> viewsInGroup = views.Where (
+                                               v =>
+                                               {
+                                                   return dimension switch
+                                                   {
+                                                       Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId,
+                                                       Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId,
+                                                       _ => false
+                                                   };
+                                               })
                                        .ToList ();
 
-        // PERF: We iterate over viewsInGroup multiple times here.
-
-        Aligner? firstInGroup = null;
-
-        // Update the dimensionList with the sizes of the views
-        for (var index = 0; index < posAligns.Count; index++)
+        if (viewsInGroup.Count == 0)
         {
-            if (posAligns [index] is { })
-            {
-                if (firstInGroup is null)
-                {
-                    firstInGroup = posAligns [index]!.Aligner;
-                }
-
-                dimensionsList.Add (dimension == Dimension.Width ? views [index].Frame.Width : views [index].Frame.Height);
-            }
+            return 0;
         }
 
-        if (firstInGroup is null)
-        {
-            return;
-        }
+        // PERF: We iterate over viewsInGroup multiple times here.
 
-        // Update the first item in the group with the new container size.
-        firstInGroup.ContainerSize = size;
+        // Update the dimensionList with the sizes of the views
+        for (var index = 0; index < viewsInGroup.Count; index++)
+        {
+            View view = viewsInGroup [index];
 
-        // Align
-        int [] locations = firstInGroup.Align (dimensionsList.ToArray ());
+            PosAlign? posAlign = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign;
 
-        // Update the cached location for each item
-        for (int posIndex = 0, locIndex = 0; posIndex < posAligns.Count; posIndex++)
-        {
-            if (posAligns [posIndex] is { })
+            if (posAlign is { })
             {
-                posAligns [posIndex]!._cachedLocation = locations [locIndex++];
+                dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height);
             }
         }
-    }
 
-    private void Aligner_PropertyChanged (object? sender, PropertyChangedEventArgs e) { _cachedLocation = null; }
-
-    /// <inheritdoc/>
-    public override bool Equals (object? other)
-    {
-        return other is PosAlign align
-               && GroupId == align.GroupId
-               && align.Aligner.Alignment == Aligner.Alignment
-               && align.Aligner.AlignmentModes == Aligner.AlignmentModes;
+        // Align
+        return dimensionsList.Sum ();
     }
 
-    /// <inheritdoc/>
-    public override int GetHashCode () { return HashCode.Combine (Aligner, GroupId); }
+    /// <summary>
+    ///     Gets the identifier of a set of views that should be aligned together. When only a single
+    ///     set of views in a SuperView is aligned, setting <see cref="GroupId"/> is not needed because it defaults to 0.
+    /// </summary>
+    public int GroupId { get; init; }
 
     /// <inheritdoc/>
     public override string ToString () { return $"Align(alignment={Aligner.Alignment},modes={Aligner.AlignmentModes},groupId={GroupId})"; }
 
-    internal override int GetAnchor (int width) { return _cachedLocation ?? 0 - width; }
-
     internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension)
     {
         if (_cachedLocation.HasValue && Aligner.ContainerSize == superviewDimension)
@@ -176,52 +130,85 @@ public class PosAlign : Pos
         return 0;
     }
 
-    // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this.
+    internal override int GetAnchor (int width) { return _cachedLocation ?? 0 - width; }
+
     /// <summary>
-    /// Returns the minimum size a group of views with the same <paramref name="groupId"/> can be.
+    ///     Aligns the views in <paramref name="views"/> that have the same group ID as <paramref name="groupId"/>.
+    ///     Updates each view's cached _location.
     /// </summary>
     /// <param name="groupId"></param>
     /// <param name="views"></param>
     /// <param name="dimension"></param>
-    /// <returns></returns>
-    public static int CalculateMinDimension (int groupId, IList<View> views, Dimension dimension)
+    /// <param name="size"></param>
+    private static void AlignAndUpdateGroup (int groupId, IList<View> views, Dimension dimension, int size)
     {
         List<int> dimensionsList = new ();
 
         // PERF: If this proves a perf issue, consider caching a ref to this list in each item
-        List<View> viewsInGroup = views.Where (
-                                               v =>
-                                               {
-                                                   return dimension switch
-                                                   {
-                                                       Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId,
-                                                       Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId,
-                                                       _ => false
-                                                   };
-                                               })
-                                       .ToList ();
-
-        if (viewsInGroup.Count == 0)
-        {
-            return 0;
-        }
+        List<PosAlign?> posAligns = views.Select (
+                                                  v =>
+                                                  {
+                                                      switch (dimension)
+                                                      {
+                                                          case Dimension.Width when v.X.Has<PosAlign> (out Pos pos):
+
+                                                              if (pos is PosAlign posAlignX && posAlignX.GroupId == groupId)
+                                                              {
+                                                                  return posAlignX;
+                                                              }
+
+                                                              break;
+                                                          case Dimension.Height when v.Y.Has<PosAlign> (out Pos pos):
+                                                              if (pos is PosAlign posAlignY && posAlignY.GroupId == groupId)
+                                                              {
+                                                                  return posAlignY;
+                                                              }
+
+                                                              break;
+                                                      }
+
+                                                      return null;
+                                                  })
+                                         .ToList ();
 
         // PERF: We iterate over viewsInGroup multiple times here.
 
+        Aligner? firstInGroup = null;
+
         // Update the dimensionList with the sizes of the views
-        for (var index = 0; index < viewsInGroup.Count; index++)
+        for (var index = 0; index < posAligns.Count; index++)
         {
-            View view = viewsInGroup [index];
-
-            PosAlign? posAlign = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign;
-
-            if (posAlign is { })
+            if (posAligns [index] is { })
             {
-                dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height);
+                if (firstInGroup is null)
+                {
+                    firstInGroup = posAligns [index]!.Aligner;
+                }
+
+                dimensionsList.Add (dimension == Dimension.Width ? views [index].Frame.Width : views [index].Frame.Height);
             }
         }
 
+        if (firstInGroup is null)
+        {
+            return;
+        }
+
+        // Update the first item in the group with the new container size.
+        firstInGroup.ContainerSize = size;
+
         // Align
-        return dimensionsList.Sum ();
+        int [] locations = firstInGroup.Align (dimensionsList.ToArray ());
+
+        // Update the cached location for each item
+        for (int posIndex = 0, locIndex = 0; posIndex < posAligns.Count; posIndex++)
+        {
+            if (posAligns [posIndex] is { })
+            {
+                posAligns [posIndex]!._cachedLocation = locations [locIndex++];
+            }
+        }
     }
+
+    private void Aligner_PropertyChanged (object? sender, PropertyChangedEventArgs e) { _cachedLocation = null; }
 }

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

@@ -10,7 +10,7 @@ namespace Terminal.Gui;
 ///         methods on the <see cref="Pos"/> class to create <see cref="Pos"/> objects instead.
 ///     </para>
 /// </remarks>
-public class PosAnchorEnd : Pos
+public record PosAnchorEnd : Pos
 {
     /// <summary>
     ///     Gets the offset of the position from the right/bottom.
@@ -30,12 +30,6 @@ public class PosAnchorEnd : Pos
     /// <param name="offset"></param>
     public PosAnchorEnd (int offset) { Offset = offset; }
 
-    /// <inheritdoc/>
-    public override bool Equals (object? other) { return other is PosAnchorEnd anchorEnd && anchorEnd.Offset == Offset; }
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return Offset.GetHashCode (); }
-
     /// <summary>
     ///     If true, the offset is the width of the view, if false, the offset is the offset value.
     /// </summary>

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

@@ -4,7 +4,7 @@ namespace Terminal.Gui;
 /// <summary>
 ///     Represents a position that is centered.
 /// </summary>
-public class PosCenter : Pos
+public record PosCenter : Pos
 {
     /// <inheritdoc/>
     public override string ToString () { return "Center"; }

+ 7 - 7
Terminal.Gui/View/Layout/PosCombine.cs

@@ -10,27 +10,27 @@ namespace Terminal.Gui;
 ///         methods on the <see cref="Pos"/> class to create <see cref="Pos"/> objects instead.
 ///     </para>
 /// </remarks>
-/// <param name="add">
+/// <param name="Add">
 ///     Indicates whether the two positions are added or subtracted.
 /// </param>
-/// <param name="left">The left position.</param>
-/// <param name="right">The right position.</param>
-public class PosCombine (AddOrSubtract add, Pos left, Pos right) : Pos
+/// <param name="Left">The left position.</param>
+/// <param name="Right">The right position.</param>
+public record PosCombine (AddOrSubtract Add, Pos Left, Pos Right) : Pos
 {
     /// <summary>
     ///     Gets whether the two positions are added or subtracted.
     /// </summary>
-    public AddOrSubtract Add { get; } = add;
+    public AddOrSubtract Add { get; } = Add;
 
     /// <summary>
     ///     Gets the left position.
     /// </summary>
-    public new Pos Left { get; } = left;
+    public new Pos Left { get; } = Left;
 
     /// <summary>
     ///     Gets the right position.
     /// </summary>
-    public new Pos Right { get; } = right;
+    public new Pos Right { get; } = Right;
 
     /// <inheritdoc/>
     public override string ToString () { return $"Combine({Left}{(Add == AddOrSubtract.Add ? '+' : '-')}{Right})"; }

+ 4 - 21
Terminal.Gui/View/Layout/PosFunc.cs

@@ -4,28 +4,11 @@ namespace Terminal.Gui;
 /// <summary>
 ///     Represents a position that is computed by executing a function that returns an integer position.
 /// </summary>
-/// <remarks>
-///     <para>
-///         This is a low-level API that is typically used internally by the layout system. Use the various static
-///         methods on the <see cref="Pos"/> class to create <see cref="Pos"/> objects instead.
-///     </para>
-/// </remarks>
-/// <param name="pos">The position.</param>
-public class PosFunc (Func<int> pos) : Pos
+/// <param name="Fn">The function that computes the position.</param>
+public record PosFunc (Func<int> Fn) : Pos
 {
-    /// <summary>
-    ///     Gets the function that computes the position.
-    /// </summary>
-    public new Func<int> Func { get; } = pos;
-
-    /// <inheritdoc/>
-    public override bool Equals (object? other) { return other is PosFunc f && f.Func () == Func (); }
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return Func.GetHashCode (); }
-
     /// <inheritdoc/>
-    public override string ToString () { return $"PosFunc({Func ()})"; }
+    public override string ToString () { return $"PosFunc({Fn ()})"; }
 
-    internal override int GetAnchor (int size) { return Func (); }
+    internal override int GetAnchor (int size) { return Fn (); }
 }

+ 3 - 9
Terminal.Gui/View/Layout/PosPercent.cs

@@ -10,19 +10,13 @@ namespace Terminal.Gui;
 ///         methods on the <see cref="Pos"/> class to create <see cref="Pos"/> objects instead.
 ///     </para>
 /// </remarks>
-/// <param name="percent"></param>
-public class PosPercent (int percent) : Pos
+/// <param name="Percent"></param>
+public record PosPercent (int Percent) : Pos
 {
     /// <summary>
     ///     Gets the percentage of the width or height of the SuperView.
     /// </summary>
-    public new int Percent { get; } = percent;
-
-    /// <inheritdoc/>
-    public override bool Equals (object? other) { return other is PosPercent i && i.Percent == Percent; }
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return Percent.GetHashCode (); }
+    public new int Percent { get; } = Percent;
 
     /// <inheritdoc/>
     public override string ToString () { return $"Percent({Percent})"; }

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

@@ -10,25 +10,19 @@ namespace Terminal.Gui;
 ///         methods on the <see cref="Pos"/> class to create <see cref="Pos"/> objects instead.
 ///     </para>
 /// </remarks>
-/// <param name="view">The View the position is anchored to.</param>
-/// <param name="side">The side of the View the position is anchored to.</param>
-public class PosView (View? view, Side side) : Pos
+/// <param name="View">The View the position is anchored to.</param>
+/// <param name="Side">The side of the View the position is anchored to.</param>
+public record PosView (View? View, Side Side) : Pos
 {
     /// <summary>
     ///     Gets the View the position is anchored to.
     /// </summary>
-    public View? Target { get; } = view;
+    public View? Target { get; } = View;
 
     /// <summary>
     ///     Gets the side of the View the position is anchored to.
     /// </summary>
-    public Side Side { get; } = side;
-
-    /// <inheritdoc/>
-    public override bool Equals (object? other) { return other is PosView abs && abs.Target == Target && abs.Side == Side; }
-
-    /// <inheritdoc/>
-    public override int GetHashCode () { return Target!.GetHashCode (); }
+    public Side Side { get; } = Side;
 
     /// <inheritdoc/>
     public override string ToString ()

+ 15 - 19
Terminal.Gui/View/Navigation/FocusEventArgs.cs

@@ -1,27 +1,23 @@
 namespace Terminal.Gui;
 
-/// <summary>Defines the event arguments for <see cref="View.SetFocus()"/></summary>
-public class FocusEventArgs : EventArgs
+/// <summary>The event arguments for <see cref="View.HasFocus"/> events.</summary>
+public class HasFocusEventArgs : CancelEventArgs<bool>
 {
-    /// <summary>Constructs.</summary>
-    /// <param name="leaving">The view that is losing focus.</param>
-    /// <param name="entering">The view that is gaining focus.</param>
-    public FocusEventArgs (View leaving, View entering) {
-        Leaving = leaving;
-        Entering = entering;
+    /// <summary>Initializes a new instance.</summary>
+    /// <param name="currentHasFocus">The current value of <see cref="View.HasFocus"/>.</param>
+    /// <param name="newHasFocus">The value <see cref="View.HasFocus"/> will have if the event is not cancelled.</param>
+    /// <param name="currentFocused">The view that is losing focus.</param>
+    /// <param name="newFocused">The view that is gaining focus.</param>
+    public HasFocusEventArgs (bool currentHasFocus, bool newHasFocus, View currentFocused, View newFocused) : base (ref currentHasFocus, ref newHasFocus)
+    {
+        CurrentFocused = currentFocused;
+        NewFocused = newFocused;
     }
 
-    /// <summary>
-    ///     Indicates if the current focus 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
-    ///     subscriber method.
-    /// </summary>
-    public bool Handled { get; set; }
+    /// <summary>Gets or sets the view that is losing focus.</summary>
+    public View CurrentFocused { get; set; }
 
-    /// <summary>Indicates the view that is losing focus.</summary>
-    public View Leaving { get; set; }
-
-    /// <summary>Indicates the view that is gaining focus.</summary>
-    public View Entering { get; set; }
+    /// <summary>Gets or sets the view that is gaining focus.</summary>
+    public View NewFocused { get; set; }
 
 }

+ 10 - 2
Terminal.Gui/View/Navigation/TabBehavior.cs

@@ -8,6 +8,13 @@ public enum TabBehavior
     /// <summary>
     ///     The View will not be a stop-poknt for keyboard-based navigation.
     /// </summary>
+    /// <remarks>
+    ///     <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>
     NoStop = 0,
 
     /// <summary>
@@ -16,7 +23,8 @@ public enum TabBehavior
     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,
+    TabGroup = 2
 }

+ 4 - 3
Terminal.Gui/View/Orientation/IOrientation.cs

@@ -1,4 +1,5 @@
-
+#nullable enable
+
 namespace Terminal.Gui;
 using System;
 
@@ -18,7 +19,7 @@ public interface IOrientation
     /// <summary>
     ///     Raised when <see cref="Orientation"/> is changing. Can be cancelled.
     /// </summary>
-    public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
+    public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
 
     /// <summary>
     ///     Called when <see cref="Orientation"/> is changing.
@@ -31,7 +32,7 @@ public interface IOrientation
     /// <summary>
     ///     Raised when <see cref="Orientation"/> has changed.
     /// </summary>
-    public event EventHandler<EventArgs<Orientation>> OrientationChanged;
+    public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
 
     /// <summary>
     ///     Called when <see cref="Orientation"/> has been changed.

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

@@ -1,4 +1,5 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 /// <summary>Direction of an element (horizontal or vertical)</summary>
 public enum Orientation

+ 4 - 3
Terminal.Gui/View/Orientation/OrientationHelper.cs

@@ -1,4 +1,5 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 /// <summary>
 ///     Helper class for implementing <see cref="IOrientation"/>.
@@ -119,7 +120,7 @@ public class OrientationHelper
     ///         it was not canceled).
     ///     </para>
     /// </remarks>
-    public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
+    public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
 
     /// <summary>
     ///     Raised when the orientation has changed.
@@ -134,5 +135,5 @@ public class OrientationHelper
     ///         This event will be raised after the <see cref="IOrientation.OnOrientationChanged"/> method is called.
     ///     </para>
     /// </remarks>
-    public event EventHandler<EventArgs<Orientation>> OrientationChanged;
+    public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
 }

+ 7 - 9
Terminal.Gui/View/SuperViewChangedEventArgs.cs

@@ -7,22 +7,20 @@
 public class SuperViewChangedEventArgs : EventArgs
 {
     /// <summary>Creates a new instance of the <see cref="SuperViewChangedEventArgs"/> class.</summary>
-    /// <param name="parent"></param>
-    /// <param name="child"></param>
-    public SuperViewChangedEventArgs (View parent, View child)
+    /// <param name="superView"></param>
+    /// <param name="subView"></param>
+    public SuperViewChangedEventArgs (View superView, View subView)
     {
-        Parent = parent;
-        Child = child;
+        SuperView = superView;
+        SubView = subView;
     }
 
-    // TODO: Child is the wrong name. It should be View.
     /// <summary>The view that is having it's <see cref="View.SuperView"/> changed</summary>
-    public View Child { get; }
+    public View SubView { get; }
 
-    // TODO: Parent is the wrong name. It should be SuperView.
     /// <summary>
     ///     The parent.  For <see cref="View.Removed"/> this is the old parent (new parent now being null).  For
     ///     <see cref="View.Added"/> it is the new parent to whom view now belongs.
     /// </summary>
-    public View Parent { get; }
+    public View SuperView { get; }
 }

+ 1 - 0
Terminal.Gui/View/View.Adornments.cs

@@ -194,6 +194,7 @@ public partial class View // Adornments
     /// <summary>
     ///     Fired when the <see cref="BorderStyle"/> is changing. Allows the event to be cancelled.
     /// </summary>
+    [CanBeNull]
     public event EventHandler<CancelEventArgs<LineStyle>> BorderStyleChanging;
 
     /// <summary>

+ 4 - 4
Terminal.Gui/View/View.Content.cs

@@ -1,4 +1,5 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 public partial class View
 {
@@ -153,7 +154,7 @@ public partial class View
     /// <summary>
     ///     Event raised when the <see cref="GetContentSize ()"/> changes.
     /// </summary>
-    public event EventHandler<SizeChangedEventArgs> ContentSizeChanged;
+    public event EventHandler<SizeChangedEventArgs>? ContentSizeChanged;
 
     /// <summary>
     ///     Converts a Content-relative location to a Screen-relative location.
@@ -356,8 +357,7 @@ public partial class View
     ///     Fired when the <see cref="Viewport"/> changes. This event is fired after the <see cref="Viewport"/> has been
     ///     updated.
     /// </summary>
-    [CanBeNull]
-    public event EventHandler<DrawEventArgs> ViewportChanged;
+    public event EventHandler<DrawEventArgs>? ViewportChanged;
 
     /// <summary>
     ///     Called when the <see cref="Viewport"/> changes. Invokes the <see cref="ViewportChanged"/> event.

+ 1 - 0
Terminal.Gui/View/View.Cursor.cs

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

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

@@ -1,6 +1,4 @@
-
-
-
+#nullable enable
 namespace Terminal.Gui;
 
 /// <summary>Enables diagnostic functions for <see cref="View"/>.</summary>

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

@@ -202,11 +202,6 @@ public partial class View // Drawing APIs
     /// </remarks>
     public void Draw ()
     {
-        if (!CanBeVisible (this))
-        {
-            return;
-        }
-
         OnDrawAdornments ();
 
         if (ColorScheme is { })
@@ -262,6 +257,7 @@ public partial class View // Drawing APIs
     ///         <see cref="View"/> .
     ///     </para>
     /// </remarks>
+    [CanBeNull]
     public event EventHandler<DrawEventArgs> DrawContent;
 
     /// <summary>Event invoked when the content area of the View is completed drawing.</summary>
@@ -272,6 +268,7 @@ public partial class View // Drawing APIs
     ///         <see cref="View"/> .
     ///     </para>
     /// </remarks>
+    [CanBeNull]
     public event EventHandler<DrawEventArgs> DrawContentComplete;
 
     /// <summary>Utility function to draw strings that contain a hotkey.</summary>
@@ -475,6 +472,11 @@ public partial class View // Drawing APIs
                 Clear ();
             }
 
+            if (!CanBeVisible (this))
+            {
+                return;
+            }
+
             if (!string.IsNullOrEmpty (TextFormatter.Text))
             {
                 if (TextFormatter is { })
@@ -505,7 +507,7 @@ public partial class View // Drawing APIs
             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 = _tabIndexes.Where (
+                subviewsNeedingDraw = _subviews.Where (
                                                        view => view.Visible
                                                                && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
                                                       ).Reverse ();

+ 95 - 85
Terminal.Gui/View/View.Hierarchy.cs

@@ -1,3 +1,4 @@
+#nullable enable
 using System.Diagnostics;
 
 namespace Terminal.Gui;
@@ -5,27 +6,31 @@ namespace Terminal.Gui;
 public partial class View // SuperView/SubView hierarchy management (SuperView, SubViews, Add, Remove, etc.)
 {
     private static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
-    private List<View> _subviews; // This is null, and allocated on demand.
-    private View _superView;
 
-    /// <summary>Indicates whether the view was added to <see cref="SuperView"/>.</summary>
-    public bool IsAdded { get; private set; }
+    private List<View>? _subviews; // This is null, and allocated on demand.
+
+    // Internally, we use InternalSubviews rather than subviews, as we do not expect us
+    // to make the same mistakes our users make when they poke at the Subviews.
+    internal IList<View> InternalSubviews => _subviews ?? _empty;
 
     /// <summary>This returns a list of the subviews contained by this view.</summary>
     /// <value>The subviews.</value>
     public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
 
+    private View? _superView;
+
     /// <summary>Returns the container for this view, or null if this view has not been added to a container.</summary>
     /// <value>The super view.</value>
-    public virtual View SuperView
+    public virtual View? SuperView
     {
-        get => _superView;
+        get => _superView!;
         set => throw new NotImplementedException ();
     }
 
-    // Internally, we use InternalSubviews rather than subviews, as we do not expect us
-    // to make the same mistakes our users make when they poke at the Subviews.
-    internal IList<View> InternalSubviews => _subviews ?? _empty;
+    #region AddRemove
+
+    /// <summary>Indicates whether the view was added to <see cref="SuperView"/>.</summary>
+    public bool IsAdded { get; private set; }
 
     /// <summary>Adds a subview (child) to this view.</summary>
     /// <remarks>
@@ -42,42 +47,26 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
     /// <returns>The view that was added.</returns>
     public virtual View Add (View view)
     {
-        if (view is null)
-        {
-            return view;
-        }
-
         if (_subviews is null)
         {
-            _subviews = new ();
+            _subviews = [];
         }
 
-        if (_tabIndexes is null)
-        {
-            _tabIndexes = new ();
-        }
+        Debug.WriteLineIf (_subviews.Contains (view), $"WARNING: {view} has already been added to {this}.");
+
+        // TileView likes to add views that were previously added and have HasFocus = true. No bueno.
+        view.HasFocus = false;
 
-        Debug.WriteLineIf (_subviews.Contains (view), $"BUGBUG: {view} has already been added to {this}.");
         _subviews.Add (view);
-        _tabIndexes.Add (view);
         view._superView = this;
 
-        if (view.CanFocus)
+        if (view is { Enabled: true, Visible: true, CanFocus: true })
         {
-            // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Instead, callers to Add should be explicit about what they want.
-            _addingViewSoCanFocusAlsoUpdatesSuperView = true;
-
-            if (SuperView?.CanFocus == false)
+            // Add will cause the newly added subview to gain focus if it's focusable
+            if (HasFocus)
             {
-                SuperView._addingViewSoCanFocusAlsoUpdatesSuperView = true;
-                SuperView.CanFocus = true;
-                SuperView._addingViewSoCanFocusAlsoUpdatesSuperView = false;
+                view.SetFocus ();
             }
-
-            // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying.
-            CanFocus = true;
-            view._tabIndex = _tabIndexes.IndexOf (view);
-            _addingViewSoCanFocusAlsoUpdatesSuperView = false;
         }
 
         if (view.Enabled && !Enabled)
@@ -113,7 +102,7 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
     ///         the lifecycle of the subviews to be transferred to this View.
     ///     </para>
     /// </remarks>
-    public void Add (params View [] views)
+    public void Add (params View []? views)
     {
         if (views is null)
         {
@@ -127,32 +116,13 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
     }
 
     /// <summary>Event fired when this view is added to another.</summary>
-    public event EventHandler<SuperViewChangedEventArgs> Added;
-
-    /// <summary>Get the top superview of a given <see cref="View"/>.</summary>
-    /// <returns>The superview view.</returns>
-    public View GetTopSuperView (View view = null, View superview = null)
-    {
-        View top = superview ?? Application.Top;
-
-        for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
-        {
-            top = v;
-
-            if (top == superview)
-            {
-                break;
-            }
-        }
-
-        return top;
-    }
+    public event EventHandler<SuperViewChangedEventArgs>? Added;
 
     /// <summary>Method invoked when a subview is being added to this view.</summary>
     /// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
     public virtual void OnAdded (SuperViewChangedEventArgs e)
     {
-        View view = e.Child;
+        View view = e.SubView;
         view.IsAdded = true;
         view.OnResizeNeeded ();
         view.Added?.Invoke (this, e);
@@ -162,7 +132,7 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
     /// <param name="e">Event args describing the subview being removed.</param>
     public virtual void OnRemoved (SuperViewChangedEventArgs e)
     {
-        View view = e.Child;
+        View view = e.SubView;
         view.IsAdded = false;
         view.Removed?.Invoke (this, e);
     }
@@ -175,18 +145,27 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
     ///         lifecycle to be transferred to the caller; the caller muse call <see cref="Dispose"/>.
     ///     </para>
     /// </remarks>
-    public virtual View Remove (View view)
+    /// <returns>
+    ///     The removed View. <see langword="null"/> if the View could not be removed.
+    /// </returns>
+    public virtual View? Remove (View view)
     {
-        if (view is null || _subviews is null)
+        if (_subviews is null)
         {
             return view;
         }
 
         Rectangle touched = view.Frame;
+
+        // If a view being removed is focused, it should lose focus.
+        if (view.HasFocus)
+        {
+            view.HasFocus = false;
+        }
+
         _subviews.Remove (view);
-        _tabIndexes.Remove (view);
-        view._superView = null;
-        //view._tabIndex = -1;
+        view._superView = null; // Null this AFTER removing focus
+
         SetNeedsLayout ();
         SetNeedsDisplay ();
 
@@ -198,13 +177,13 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
             }
         }
 
-        OnRemoved (new (this, view));
-
-        if (Focused == view)
+        if (HasFocus)
         {
-            Focused = null;
+            FocusDeepest (NavigationDirection.Forward, TabStop);
         }
 
+        OnRemoved (new (this, view));
+
         return view;
     }
 
@@ -233,18 +212,43 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
     }
 
     /// <summary>Event fired when this view is removed from another.</summary>
-    public event EventHandler<SuperViewChangedEventArgs> Removed;
+    public event EventHandler<SuperViewChangedEventArgs>? Removed;
 
+    #endregion AddRemove
 
-    /// <summary>Moves <paramref name="subview"/> one position towards the start of the <see cref="Subviews"/> list</summary>
-    /// <param name="subview">The subview to move forward.</param>
-    public void BringSubviewForward (View subview)
+    // TODO: Mark as internal. Or nuke.
+    /// <summary>Get the top superview of a given <see cref="View"/>.</summary>
+    /// <returns>The superview view.</returns>
+    public View? GetTopSuperView (View? view = null, View? superview = null)
+    {
+        View? top = superview ?? Application.Top;
+
+        for (View? v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
+        {
+            top = v;
+
+            if (top == superview)
+            {
+                break;
+            }
+        }
+
+        return top;
+    }
+
+    #region SubViewOrdering
+
+    /// <summary>
+    ///     Moves <paramref name="subview"/> one position towards the end of the <see cref="Subviews"/> list.
+    /// </summary>
+    /// <param name="subview">The subview to move.</param>
+    public void MoveSubviewTowardsEnd (View subview)
     {
         PerformActionForSubview (
                                  subview,
                                  x =>
                                  {
-                                     int idx = _subviews.IndexOf (x);
+                                     int idx = _subviews!.IndexOf (x);
 
                                      if (idx + 1 < _subviews.Count)
                                      {
@@ -255,30 +259,33 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
                                 );
     }
 
-    /// <summary>Moves <paramref name="subview"/> to the start of the <see cref="Subviews"/> list.</summary>
-    /// <param name="subview">The subview to send to the start.</param>
-    public void BringSubviewToFront (View subview)
+    /// <summary>
+    ///     Moves <paramref name="subview"/> to the end of the <see cref="Subviews"/> list.
+    /// </summary>
+    /// <param name="subview">The subview to move.</param>
+    public void MoveSubviewToEnd (View subview)
     {
         PerformActionForSubview (
                                  subview,
                                  x =>
                                  {
-                                     _subviews.Remove (x);
+                                     _subviews!.Remove (x);
                                      _subviews.Add (x);
                                  }
                                 );
     }
 
-
-    /// <summary>Moves <paramref name="subview"/> one position towards the end of the <see cref="Subviews"/> list</summary>
-    /// <param name="subview">The subview to move backwards.</param>
-    public void SendSubviewBackwards (View subview)
+    /// <summary>
+    ///     Moves <paramref name="subview"/> one position towards the start of the <see cref="Subviews"/> list.
+    /// </summary>
+    /// <param name="subview">The subview to move.</param>
+    public void MoveSubviewTowardsStart (View subview)
     {
         PerformActionForSubview (
                                  subview,
                                  x =>
                                  {
-                                     int idx = _subviews.IndexOf (x);
+                                     int idx = _subviews!.IndexOf (x);
 
                                      if (idx > 0)
                                      {
@@ -289,15 +296,17 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
                                 );
     }
 
-    /// <summary>Moves <paramref name="subview"/> to the end of the <see cref="Subviews"/> list.</summary>
-    /// <param name="subview">The subview to send to the end.</param>
-    public void SendSubviewToBack (View subview)
+    /// <summary>
+    ///     Moves <paramref name="subview"/> to the start of the <see cref="Subviews"/> list.
+    /// </summary>
+    /// <param name="subview">The subview to move.</param>
+    public void MoveSubviewToStart (View subview)
     {
         PerformActionForSubview (
                                  subview,
                                  x =>
                                  {
-                                     _subviews.Remove (x);
+                                     _subviews!.Remove (x);
                                      _subviews.Insert (0, subview);
                                  }
                                 );
@@ -310,7 +319,7 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
     /// <param name="action"></param>
     private void PerformActionForSubview (View subview, Action<View> action)
     {
-        if (_subviews.Contains (subview))
+        if (_subviews!.Contains (subview))
         {
             action (subview);
         }
@@ -320,4 +329,5 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
         subview.SetNeedsDisplay ();
     }
 
+    #endregion SubViewOrdering
 }

+ 47 - 51
Terminal.Gui/View/View.Keyboard.cs

@@ -1,12 +1,12 @@
-using System.ComponentModel;
+#nullable enable
 using System.Diagnostics;
 
 namespace Terminal.Gui;
 
-public partial class View  // Keyboard APIs
+public partial class View // Keyboard APIs
 {
     /// <summary>
-    ///  Helper to configure all things keyboard related for a View. Called from the View constructor.
+    ///     Helper to configure all things keyboard related for a View. Called from the View constructor.
     /// </summary>
     private void SetupKeyboard ()
     {
@@ -22,18 +22,14 @@ public partial class View  // Keyboard APIs
     }
 
     /// <summary>
-    ///    Helper to dispose all things keyboard related for a View. Called from the View Dispose method.
+    ///     Helper to dispose all things keyboard related for a View. Called from the View Dispose method.
     /// </summary>
-    private void DisposeKeyboard ()
-    {
-        TitleTextFormatter.HotKeyChanged -= TitleTextFormatter_HotKeyChanged;
-        Application.RemoveKeyBindings (this);
-    }
+    private void DisposeKeyboard () { TitleTextFormatter.HotKeyChanged -= TitleTextFormatter_HotKeyChanged; }
 
     #region HotKey Support
 
     /// <summary>
-    /// Called when the HotKey command (<see cref="Command.HotKey"/>) is invoked. Causes this view to be focused.
+    ///     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 ()
@@ -41,6 +37,7 @@ public partial class View  // Keyboard APIs
         if (CanFocus)
         {
             SetFocus ();
+
             return true;
         }
 
@@ -48,10 +45,10 @@ public partial class View  // Keyboard APIs
     }
 
     /// <summary>Invoked when the <see cref="HotKey"/> is changed.</summary>
-    public event EventHandler<KeyChangedEventArgs> HotKeyChanged;
+    public event EventHandler<KeyChangedEventArgs>? HotKeyChanged;
 
     private Key _hotKey = new ();
-    private void TitleTextFormatter_HotKeyChanged (object sender, KeyChangedEventArgs e) { HotKeyChanged?.Invoke (this, e); }
+    private void TitleTextFormatter_HotKeyChanged (object? sender, KeyChangedEventArgs e) { HotKeyChanged?.Invoke (this, e); }
 
     /// <summary>
     ///     Gets or sets the hot key defined for this view. Pressing the hot key on the keyboard while this view has focus will
@@ -118,7 +115,8 @@ public partial class View  // Keyboard APIs
     /// <remarks>
     ///     <para>
     ///         By default, key bindings are added for both the base key (e.g. <see cref="Key.D3"/>) and the Alt-shifted key
-    ///         (e.g. <c>Key.D3.WithAlt</c>) This behavior can be overriden by overriding <see cref="AddKeyBindingsForHotKey"/>.
+    ///         (e.g. <c>Key.D3.WithAlt</c>) This behavior can be overriden by overriding <see cref="AddKeyBindingsForHotKey"/>
+    ///         .
     ///     </para>
     ///     <para>
     ///         By default, when <paramref name="hotKey"/> is <see cref="Key.A"/> through <see cref="Key.Z"/> key bindings
@@ -132,7 +130,7 @@ public partial class View  // Keyboard APIs
     /// <param name="context">Arbitrary context that can be associated with this key binding.</param>
     /// <returns><see langword="true"/> if the HotKey bindings were added.</returns>
     /// <exception cref="ArgumentException"></exception>
-    public virtual bool AddKeyBindingsForHotKey (Key prevHotKey, Key hotKey, [CanBeNull] object context = null)
+    public virtual bool AddKeyBindingsForHotKey (Key prevHotKey, Key hotKey, object? context = null)
     {
         if (_hotKey == hotKey)
         {
@@ -196,6 +194,7 @@ public partial class View  // Keyboard APIs
         if (newKey != Key.Empty)
         {
             KeyBinding keyBinding = new ([Command.HotKey], KeyBindingScope.HotKey, context);
+
             // Add the base and Alt key
             KeyBindings.Remove (newKey);
             KeyBindings.Add (newKey, keyBinding);
@@ -221,10 +220,7 @@ public partial class View  // Keyboard APIs
     /// </summary>
     public virtual Rune HotKeySpecifier
     {
-        get
-        {
-            return TitleTextFormatter.HotKeySpecifier;
-        }
+        get => TitleTextFormatter.HotKeySpecifier;
         set
         {
             TitleTextFormatter.HotKeySpecifier = TextFormatter.HotKeySpecifier = value;
@@ -364,7 +360,7 @@ public partial class View  // Keyboard APIs
     ///     </para>
     ///     <para>See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see></para>
     /// </remarks>
-    public event EventHandler<Key> KeyDown;
+    public event EventHandler<Key>? KeyDown;
 
     /// <summary>
     ///     Low-level API called when the user presses a key, allowing views do things during key down events. This is
@@ -412,7 +408,7 @@ public partial class View  // Keyboard APIs
     ///     </para>
     ///     <para>See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see></para>
     /// </remarks>
-    public event EventHandler<Key> ProcessKeyDown;
+    public event EventHandler<Key>? ProcessKeyDown;
 
     #endregion KeyDown Event
 
@@ -503,7 +499,7 @@ public partial class View  // Keyboard APIs
     ///         <para>See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see></para>
     ///     </remarks>
     /// </summary>
-    public event EventHandler<Key> KeyUp;
+    public event EventHandler<Key>? KeyUp;
 
     #endregion KeyUp Event
 
@@ -512,7 +508,7 @@ public partial class View  // Keyboard APIs
     #region Key Bindings
 
     /// <summary>Gets the key bindings for this view.</summary>
-    public KeyBindings KeyBindings { get; internal set; }
+    public KeyBindings KeyBindings { get; internal set; } = null!;
 
     private Dictionary<Command, Func<CommandContext, bool?>> CommandImplementations { get; } = new ();
 
@@ -536,11 +532,11 @@ public partial class View  // Keyboard APIs
         if (KeyBindings.TryGet (keyEvent, scope, out KeyBinding kb))
         {
             InvokingKeyBindings?.Invoke (this, keyEvent);
+
             if (keyEvent.Handled)
             {
                 return true;
             }
-
         }
 
         // * If no key binding was found, `InvokeKeyBindings` returns `null`.
@@ -615,6 +611,7 @@ public partial class View  // Keyboard APIs
             {
                 continue;
             }
+
             if (subview.KeyBindings.TryGet (keyEvent, scope, out KeyBinding binding))
             {
                 if (binding.Scope == KeyBindingScope.Focused && !subview.HasFocus)
@@ -632,6 +629,7 @@ public partial class View  // Keyboard APIs
                 if (subViewHandled is { })
                 {
                     handled = subViewHandled;
+
                     if ((bool)subViewHandled)
                     {
                         return true;
@@ -640,6 +638,7 @@ public partial class View  // Keyboard APIs
             }
 
             bool recurse = subview.ProcessSubViewKeyBindings (keyEvent, scope, ref handled, invoke);
+
             if (recurse || (handled is { } && (bool)handled))
             {
                 return true;
@@ -653,12 +652,12 @@ public partial class View  // Keyboard APIs
     // TODO: A better approach would be to have Application hold a list of bound Hotkeys, similar to
     // TODO: how Application holds a list of Application Scoped key bindings and then check that list.
     /// <summary>
-    /// Returns true if Key is bound in this view hierarchy. For debugging
+    ///     Returns true if Key is bound in this view hierarchy. For debugging
     /// </summary>
     /// <param name="key">The key to test.</param>
     /// <param name="boundView">Returns the view the key is bound to.</param>
     /// <returns></returns>
-    public bool IsHotKeyKeyBound (Key key, out View boundView)
+    public bool IsHotKeyKeyBound (Key key, out View? boundView)
     {
         // recurse through the subviews to find the views that has the key bound
         boundView = null;
@@ -668,6 +667,7 @@ public partial class View  // Keyboard APIs
             if (subview.KeyBindings.TryGet (key, KeyBindingScope.HotKey, out _))
             {
                 boundView = subview;
+
                 return true;
             }
 
@@ -675,8 +675,8 @@ public partial class View  // Keyboard APIs
             {
                 return true;
             }
-
         }
+
         return false;
     }
 
@@ -684,7 +684,7 @@ public partial class View  // Keyboard APIs
     ///     Invoked 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;
+    public event EventHandler<Key>? InvokingKeyBindings;
 
     /// <summary>
     ///     Invokes any binding that is registered on this <see cref="View"/> and matches the <paramref name="key"/>
@@ -714,20 +714,19 @@ public partial class View  // Keyboard APIs
             //var boundView = views [0];
             //var commandBinding = boundView.KeyBindings.Get (key);
             Debug.WriteLine (
-                             $"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command.");//{commandBinding.Commands [0]}: {boundView}.");
+                             $"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command."); //{commandBinding.Commands [0]}: {boundView}.");
         }
 
         // TODO: This is a "prototype" debug check. It may be too annoying vs. useful.
         // Scour the bindings up our View hierarchy
         // to ensure that the key is not already bound to a different set of commands.
-        if (SuperView?.IsHotKeyKeyBound (key, out View previouslyBoundView) ?? false)
+        if (SuperView?.IsHotKeyKeyBound (key, out View? previouslyBoundView) ?? false)
         {
             Debug.WriteLine ($"WARNING: InvokeKeyBindings ({key}) - A subview or peer has bound this Key and will not see it: {previouslyBoundView}.");
         }
 
 #endif
 
-
         foreach (Command command in binding.Commands)
         {
             if (!CommandImplementations.ContainsKey (command))
@@ -764,7 +763,7 @@ public partial class View  // Keyboard APIs
     ///     <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, [CanBeNull] Key key = null, [CanBeNull] KeyBinding? keyBinding = null)
+    public bool? InvokeCommands (Command [] commands, Key? key = null, KeyBinding? keyBinding = null)
     {
         bool? toReturn = null;
 
@@ -799,11 +798,12 @@ public partial class View  // Keyboard APIs
     ///     <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, [CanBeNull] Key key = null, [CanBeNull] KeyBinding? keyBinding = null)
+    public bool? InvokeCommand (Command command, Key? key = null, KeyBinding? keyBinding = null)
     {
-        if (CommandImplementations.TryGetValue (command, out Func<CommandContext, bool?> implementation))
+        if (CommandImplementations.TryGetValue (command, out Func<CommandContext, bool?>? implementation))
         {
             var context = new CommandContext (command, key, keyBinding); // Create the context here
+
             return implementation (context);
         }
 
@@ -813,7 +813,7 @@ public partial class View  // Keyboard APIs
     /// <summary>
     ///     <para>
     ///         Sets the function that will be invoked for a <see cref="Command"/>. Views should call
-    ///        AddCommand for each command they support.
+    ///         AddCommand for each command they support.
     ///     </para>
     ///     <para>
     ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
@@ -821,22 +821,20 @@ public partial class View  // Keyboard APIs
     ///     </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>
+    ///     <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;
-    }
+    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.
+    ///         AddCommand for each command they support.
     ///     </para>
     ///     <para>
     ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
@@ -844,17 +842,15 @@ public partial class View  // Keyboard APIs
     ///     </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>
+    ///     <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 ();
-    }
+    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>

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

@@ -121,7 +121,7 @@ public partial class View // Layout APIs
         View? superView;
         statusBar = null!;
 
-        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
+        if (viewToMove is not Toplevel || viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         {
             maxDimension = Driver.Cols;
             superView = Application.Top;
@@ -163,7 +163,7 @@ public partial class View // Layout APIs
         }
         else
         {
-            View t = viewToMove!.SuperView;
+            View? t = viewToMove!.SuperView;
 
             while (t is { } and not Toplevel)
             {
@@ -194,7 +194,7 @@ public partial class View // Layout APIs
         }
         else
         {
-            View t = viewToMove!.SuperView;
+            View? t = viewToMove!.SuperView;
 
             while (t is { } and not Toplevel)
             {
@@ -311,7 +311,7 @@ public partial class View // Layout APIs
     public virtual Rectangle FrameToScreen ()
     {
         Rectangle screen = Frame;
-        View current = SuperView;
+        View? current = SuperView;
 
         while (current is { })
         {
@@ -474,7 +474,7 @@ public partial class View // Layout APIs
                 return;
             }
 
-            if (_height is { } && _height.Has (typeof (DimAuto), out _))
+            if (_height is { } && _height.Has<DimAuto> (out _))
             {
                 // Reset ContentSize to Viewport
                 _contentSize = null;
@@ -523,7 +523,7 @@ public partial class View // Layout APIs
                 return;
             }
 
-            if (_width is { } && _width.Has (typeof (DimAuto), out _))
+            if (_width is { } && _width.Has<DimAuto> (out _))
             {
                 // Reset ContentSize to Viewport
                 _contentSize = null;
@@ -547,14 +547,14 @@ public partial class View // Layout APIs
     ///     Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has
     ///     otherwise changed.
     /// </remarks>
-    public event EventHandler<LayoutEventArgs> LayoutComplete;
+    public event EventHandler<LayoutEventArgs>? LayoutComplete;
 
     /// <summary>Fired after the View's <see cref="LayoutSubviews"/> method has completed.</summary>
     /// <remarks>
     ///     Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has
     ///     otherwise changed.
     /// </remarks>
-    public event EventHandler<LayoutEventArgs> LayoutStarted;
+    public event EventHandler<LayoutEventArgs>? LayoutStarted;
 
     /// <summary>
     ///     Adjusts <see cref="Frame"/> given the SuperView's ContentSize (nominally the same as
@@ -694,7 +694,7 @@ public partial class View // Layout APIs
         HashSet<View> nodes = new ();
         HashSet<(View, View)> edges = new ();
         CollectAll (this, ref nodes, ref edges);
-        List<View> ordered = TopologicalSort (SuperView, nodes, edges);
+        List<View> ordered = TopologicalSort (SuperView!, nodes, edges);
 
         foreach (View v in ordered)
         {

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

@@ -1,17 +1,17 @@
-using System.ComponentModel;
+#nullable enable
+using System.ComponentModel;
 
 namespace Terminal.Gui;
 
 public partial class View // Mouse APIs
 {
-    [CanBeNull]
-    private ColorScheme _savedHighlightColorScheme;
+    private ColorScheme? _savedHighlightColorScheme;
 
     /// <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.
     /// </summary>
-    public event EventHandler<CancelEventArgs<HighlightStyle>> Highlight;
+    public event EventHandler<CancelEventArgs<HighlightStyle>>? Highlight;
 
     /// <summary>
     ///     Gets or sets whether the <see cref="View"/> will be highlighted visually while the mouse button is
@@ -29,10 +29,10 @@ public partial class View // Mouse APIs
     ///         The coordinates are relative to <see cref="View.Viewport"/>.
     ///     </para>
     /// </remarks>
-    public event EventHandler<MouseEventEventArgs> MouseClick;
+    public event EventHandler<MouseEventEventArgs>? MouseClick;
 
     /// <summary>Event fired when the mouse moves into the View's <see cref="Viewport"/>.</summary>
-    public event EventHandler<MouseEventEventArgs> MouseEnter;
+    public event EventHandler<MouseEventEventArgs>? MouseEnter;
 
     /// <summary>Event fired when a mouse event occurs.</summary>
     /// <remarks>
@@ -40,10 +40,10 @@ public partial class View // Mouse APIs
     ///         The coordinates are relative to <see cref="View.Viewport"/>.
     ///     </para>
     /// </remarks>
-    public event EventHandler<MouseEventEventArgs> MouseEvent;
+    public event EventHandler<MouseEventEventArgs>? MouseEvent;
 
     /// <summary>Event fired when the mouse leaves the View's <see cref="Viewport"/>.</summary>
-    public event EventHandler<MouseEventEventArgs> MouseLeave;
+    public event EventHandler<MouseEventEventArgs>? MouseLeave;
 
     /// <summary>
     ///     Processes a <see cref="MouseEvent"/>. This method is called by <see cref="Application.OnMouseEvent"/> when a mouse
@@ -87,7 +87,7 @@ public partial class View // Mouse APIs
             return mouseEvent.Handled = true;
         }
 
-        if (HighlightStyle != HighlightStyle.None || WantContinuousButtonPressed)
+        if (HighlightStyle != HighlightStyle.None || (WantContinuousButtonPressed && WantMousePositionReports))
         {
             if (HandlePressed (mouseEvent))
             {
@@ -244,7 +244,7 @@ public partial class View // Mouse APIs
         if (!Enabled)
         {
             // QUESTION: Is this right? Should a disabled view eat mouse clicks?
-            return args.Handled = true;
+            return args.Handled = false;
         }
 
         MouseClick?.Invoke (this, args);

Dosya farkı çok büyük olduğundan ihmal edildi
+ 423 - 540
Terminal.Gui/View/View.Navigation.cs


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

@@ -4,7 +4,7 @@ namespace Terminal.Gui;
 
 public partial class View // Text Property APIs
 {
-    private string _text;
+    private string _text = null!;
 
     /// <summary>
     ///     Called when the <see cref="Text"/> has changed. Fires the <see cref="TextChanged"/> event.

+ 41 - 25
Terminal.Gui/View/View.cs

@@ -1,4 +1,5 @@
-using System.ComponentModel;
+#nullable enable
+using System.ComponentModel;
 using System.Diagnostics;
 
 namespace Terminal.Gui;
@@ -112,11 +113,11 @@ public partial class View : Responder, ISupportInitializeNotification
     ///     <see cref="HandledEventArgs.Handled"/>
     ///     to cancel the event.
     /// </summary>
-    public event EventHandler<HandledEventArgs> Accept;
+    public event EventHandler<HandledEventArgs>? Accept;
 
     /// <summary>Gets or sets arbitrary data for the view.</summary>
     /// <remarks>This property is not used internally.</remarks>
-    public object Data { get; set; }
+    public object? Data { get; set; }
 
     /// <summary>Gets or sets an identifier for the view;</summary>
     /// <value>The identifier.</value>
@@ -168,7 +169,7 @@ public partial class View : Responder, ISupportInitializeNotification
     ///     Points to the current driver in use by the view, it is a convenience property for simplifying the development
     ///     of new views.
     /// </summary>
-    public static ConsoleDriver Driver => Application.Driver;
+    public static ConsoleDriver Driver => Application.Driver!;
 
     /// <summary>Initializes a new instance of <see cref="View"/>.</summary>
     /// <remarks>
@@ -191,7 +192,7 @@ public partial class View : Responder, ISupportInitializeNotification
     ///     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>
-    public event EventHandler Initialized;
+    public event EventHandler? Initialized;
 
     /// <summary>
     ///     Get or sets if  the <see cref="View"/> has been initialized (via <see cref="ISupportInitialize.BeginInit"/>
@@ -228,9 +229,10 @@ public partial class View : Responder, ISupportInitializeNotification
         {
             throw new InvalidOperationException ("The view is already initialized.");
         }
-
+#if AUTO_CANFOCUS
         _oldCanFocus = CanFocus;
         _oldTabIndex = _tabIndex;
+#endif
 
         BeginInitAdornments ();
 
@@ -285,15 +287,17 @@ public partial class View : Responder, ISupportInitializeNotification
         Initialized?.Invoke (this, EventArgs.Empty);
     }
 
-    #endregion Constructors and Initialization
+#endregion Constructors and Initialization
 
     #region Visibility
 
     private bool _enabled = true;
+
+    // This is a cache of the Enabled property so that we can restore it when the superview is re-enabled.
     private bool _oldEnabled;
 
     /// <summary>Gets or sets a value indicating whether this <see cref="Responder"/> can respond to user interaction.</summary>
-    public virtual bool Enabled
+    public bool Enabled
     {
         get => _enabled;
         set
@@ -307,7 +311,13 @@ public partial class View : Responder, ISupportInitializeNotification
 
             if (!_enabled && HasFocus)
             {
-                SetHasFocus (false, this);
+                HasFocus = false;
+            }
+
+            if (_enabled && CanFocus && Visible && !HasFocus
+                && SuperView is null or { HasFocus: true, Visible: true, Enabled: true, Focused: null })
+            {
+                SetFocus ();
             }
 
             OnEnabledChanged ();
@@ -328,14 +338,16 @@ public partial class View : Responder, ISupportInitializeNotification
                 else
                 {
                     view.Enabled = view._oldEnabled;
+#if AUTO_CANFOCUS
                     view._addingViewSoCanFocusAlsoUpdatesSuperView = _enabled;
+#endif
                 }
             }
         }
     }
 
     /// <summary>Event fired when the <see cref="Enabled"/> value is being changed.</summary>
-    public event EventHandler EnabledChanged;
+    public event EventHandler? EnabledChanged;
 
     /// <summary>Method invoked when the <see cref="Enabled"/> property from a view is changed.</summary>
     public virtual void OnEnabledChanged () { EnabledChanged?.Invoke (this, EventArgs.Empty); }
@@ -359,13 +371,14 @@ public partial class View : Responder, ISupportInitializeNotification
             {
                 if (HasFocus)
                 {
-                    SetHasFocus (false, this);
+                    HasFocus = false;
                 }
+            }
 
-                if (IsInitialized && ClearOnVisibleFalse)
-                {
-                    Clear ();
-                }
+            if (_visible && CanFocus && Enabled && !HasFocus
+                && SuperView is null or { HasFocus: true, Visible: true, Enabled: true, Focused: null })
+            {
+                SetFocus ();
             }
 
             OnVisibleChanged ();
@@ -376,20 +389,23 @@ public partial class View : Responder, ISupportInitializeNotification
     /// <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>Gets or sets whether a view is cleared if the <see cref="Visible"/> property is <see langword="false"/>.</summary>
-    public bool ClearOnVisibleFalse { get; set; } = true;
-
     /// <summary>Event fired when the <see cref="Visible"/> value is being changed.</summary>
-    public event EventHandler VisibleChanged;
+    public event EventHandler? VisibleChanged;
 
-    private static bool CanBeVisible (View view)
+    // 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>
+    internal static bool CanBeVisible (View view)
     {
         if (!view.Visible)
         {
             return false;
         }
 
-        for (View c = view.SuperView; c != null; c = c.SuperView)
+        for (View? c = view.SuperView; c != null; c = c.SuperView)
         {
             if (!c.Visible)
             {
@@ -400,7 +416,7 @@ public partial class View : Responder, ISupportInitializeNotification
         return true;
     }
 
-    #endregion Visibility
+#endregion Visibility
 
     #region Title
 
@@ -463,7 +479,7 @@ public partial class View : Responder, ISupportInitializeNotification
                 SetHotKeyFromTitle ();
                 SetNeedsDisplay ();
 #if DEBUG
-                if (_title is { } && string.IsNullOrEmpty (Id))
+                if (string.IsNullOrEmpty (Id))
                 {
                     Id = _title;
                 }
@@ -504,13 +520,13 @@ public partial class View : Responder, ISupportInitializeNotification
     }
 
     /// <summary>Event fired after the <see cref="View.Title"/> has been changed.</summary>
-    public event EventHandler<EventArgs<string>> TitleChanged;
+    public event EventHandler<EventArgs<string>>? TitleChanged;
 
     /// <summary>
     ///     Event fired 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;
+    public event EventHandler<CancelEventArgs<string>>? TitleChanging;
 
     #endregion
 }

+ 27 - 0
Terminal.Gui/Views/BBar.cs

@@ -0,0 +1,27 @@
+#nullable enable
+
+using ColorHelper;
+
+namespace Terminal.Gui;
+
+internal class BBar : ColorBar
+{
+    public GBar? GBar { get; set; }
+    public RBar? RBar { get; set; }
+
+    /// <inheritdoc/>
+    protected override Color GetColor (double fraction)
+    {
+        if (RBar == null || GBar == null)
+        {
+            throw new ($"{nameof (BBar)} has not been set up correctly before drawing");
+        }
+
+        var rgb = new RGB ((byte)RBar.Value, (byte)GBar.Value, (byte)(MaxValue * fraction));
+
+        return new (rgb.R, rgb.G, rgb.B);
+    }
+
+    /// <inheritdoc/>
+    protected override int MaxValue => 255;
+}

+ 7 - 6
Terminal.Gui/Views/Bar.cs

@@ -1,3 +1,4 @@
+#nullable enable
 namespace Terminal.Gui;
 
 /// <summary>
@@ -43,7 +44,7 @@ public class Bar : View, IOrientation, IDesignable
         }
     }
 
-    private void Bar_Initialized (object sender, EventArgs e) { ColorScheme = Colors.ColorSchemes ["Menu"]; }
+    private void Bar_Initialized (object? sender, EventArgs e) { ColorScheme = Colors.ColorSchemes ["Menu"]; }
 
     /// <inheritdoc/>
     public override void SetBorderStyle (LineStyle value)
@@ -72,10 +73,10 @@ public class Bar : View, IOrientation, IDesignable
     }
 
     /// <inheritdoc/>
-    public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
+    public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
 
     /// <inheritdoc/>
-    public event EventHandler<EventArgs<Orientation>> OrientationChanged;
+    public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
 
     /// <summary>Called when <see cref="Orientation"/> has changed.</summary>
     /// <param name="newOrientation"></param>
@@ -132,9 +133,9 @@ public class Bar : View, IOrientation, IDesignable
     /// <summary>Removes a <see cref="Shortcut"/> at specified index of <see cref="View.Subviews"/>.</summary>
     /// <param name="index">The zero-based index of the item to remove.</param>
     /// <returns>The <see cref="Shortcut"/> removed.</returns>
-    public Shortcut RemoveShortcut (int index)
+    public Shortcut? RemoveShortcut (int index)
     {
-        View toRemove = null;
+        View? toRemove = null;
 
         for (var i = 0; i < Subviews.Count; i++)
         {
@@ -158,7 +159,7 @@ public class Bar : View, IOrientation, IDesignable
     {
         base.OnLayoutStarted (args);
 
-        View prevBarItem = null;
+        View? prevBarItem = null;
 
         switch (Orientation)
         {

+ 0 - 4
Terminal.Gui/Views/Button.cs

@@ -5,8 +5,6 @@
 //   Miguel de Icaza ([email protected])
 //
 
-using System.Text.Json.Serialization;
-
 namespace Terminal.Gui;
 
 /// <summary>Button is a <see cref="View"/> that provides an item that invokes raises the <see cref="View.Accept"/> event.</summary>
@@ -39,8 +37,6 @@ public class Button : View, IDesignable
     /// Gets or sets whether <see cref="Button"/>s are shown with a shadow effect by default.
     /// </summary>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<ShadowStyle>))]
-
     public static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.None;
 
     /// <summary>Initializes a new instance of <see cref="Button"/>.</summary>

+ 235 - 0
Terminal.Gui/Views/ColorBar.cs

@@ -0,0 +1,235 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     A bar representing a single component of a <see cref="Color"/> e.g.
+///     the Red portion of a <see cref="ColorModel.RGB"/>.
+/// </summary>
+internal abstract class ColorBar : View, IColorBar
+{
+    /// <summary>
+    ///     Creates a new instance of the <see cref="ColorBar"/> class.
+    /// </summary>
+    protected ColorBar ()
+    {
+        Height = 1;
+        Width = Dim.Fill ();
+        CanFocus = true;
+
+        AddCommand (Command.Left, _ => Adjust (-1));
+        AddCommand (Command.Right, _ => Adjust (1));
+
+        AddCommand (Command.LeftExtend, _ => Adjust (-MaxValue / 20));
+        AddCommand (Command.RightExtend, _ => Adjust (MaxValue / 20));
+
+        AddCommand (Command.LeftHome, _ => 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.End, Command.RightEnd);
+    }
+
+    /// <summary>
+    ///     X coordinate that the bar starts at excluding any label.
+    /// </summary>
+    private int _barStartsAt;
+
+    /// <summary>
+    ///     0-1 for how much of the color element is present currently (HSL)
+    /// </summary>
+    private int _value;
+
+    /// <summary>
+    ///     The amount of <see cref="Value"/> represented by each cell width on the bar
+    ///     Can be less than 1 e.g. if Saturation (0-100) and width > 100
+    /// </summary>
+    private double _cellValue = 1d;
+
+    /// <summary>
+    ///     Last known width of the bar as passed to <see cref="DrawBar"/>.
+    /// </summary>
+    private int _barWidth;
+
+    /// <summary>
+    ///     The currently selected amount of the color component stored by this class e.g.
+    ///     the amount of Hue in a <see cref="ColorModel.HSL"/>.
+    /// </summary>
+    public int Value
+    {
+        get => _value;
+        set
+        {
+            int clampedValue = Math.Clamp (value, 0, MaxValue);
+
+            if (_value != clampedValue)
+            {
+                _value = clampedValue;
+                OnValueChanged ();
+            }
+        }
+    }
+
+    /// <inheritdoc/>
+    void IColorBar.SetValueWithoutRaisingEvent (int v)
+    {
+        _value = v;
+        SetNeedsDisplay ();
+    }
+
+    /// <inheritdoc/>
+    public override void OnDrawContent (Rectangle viewport)
+    {
+        base.OnDrawContent (viewport);
+
+        var xOffset = 0;
+
+        if (!string.IsNullOrWhiteSpace (Text))
+        {
+            Move (0, 0);
+            Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
+            Driver.AddStr (Text);
+
+            // TODO: is there a better method than this? this is what it is in TableView
+            xOffset = Text.EnumerateRunes ().Sum (c => c.GetColumns ());
+        }
+
+        _barWidth = viewport.Width - xOffset;
+        _barStartsAt = xOffset;
+
+        DrawBar (xOffset, 0, _barWidth);
+    }
+
+    /// <summary>
+    ///     Event fired when <see cref="Value"/> is changed to a new value
+    /// </summary>
+    public event EventHandler<EventArgs<int>>? ValueChanged;
+
+    /// <inheritdoc/>
+    protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
+    {
+        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
+        {
+            if (mouseEvent.Position.X >= _barStartsAt)
+            {
+                double v = MaxValue * ((double)mouseEvent.Position.X - _barStartsAt) / (_barWidth - 1);
+                Value = Math.Clamp ((int)v, 0, MaxValue);
+            }
+
+            mouseEvent.Handled = true;
+            SetFocus ();
+
+            return true;
+        }
+
+        return base.OnMouseEvent (mouseEvent);
+    }
+
+    /// <summary>
+    ///     When overriden in a derived class, returns the <see cref="Color"/> to
+    ///     render at <paramref name="fraction"/> proportion of the full bars width.
+    ///     e.g. 0.5 fraction of Saturation is 50% because Saturation goes from 0-100.
+    /// </summary>
+    /// <param name="fraction"></param>
+    /// <returns></returns>
+    protected abstract Color GetColor (double fraction);
+
+    /// <summary>
+    ///     The maximum value allowed for this component e.g. Saturation allows up to 100 as it
+    ///     is a percentage while Hue allows up to 360 as it is measured in degrees.
+    /// </summary>
+    protected abstract int MaxValue { get; }
+
+    /// <summary>
+    ///     The last drawn location in View's viewport where the Triangle appeared.
+    ///     Used exclusively for tests.
+    /// </summary>
+    internal int TrianglePosition { get; private set; }
+
+    private bool? Adjust (int delta)
+    {
+        var change = (int)(delta * _cellValue);
+
+        // Ensure that the change is at least 1 or -1 if delta is non-zero
+        if (change == 0 && delta != 0)
+        {
+            change = delta > 0 ? 1 : -1;
+        }
+
+        Value += change;
+
+        return true;
+    }
+
+    private void DrawBar (int xOffset, int yOffset, int width)
+    {
+        // Each 1 unit of X in the bar corresponds to this much of Value
+        _cellValue = (double)MaxValue / (width - 1);
+
+        for (var x = 0; x < width; x++)
+        {
+            double fraction = (double)x / (width - 1);
+            Color color = GetColor (fraction);
+
+            // Adjusted isSelectedCell calculation
+            double cellBottomThreshold = (x - 1) * _cellValue;
+            double cellTopThreshold = x * _cellValue;
+
+            if (x == width - 1)
+            {
+                cellTopThreshold = MaxValue;
+            }
+
+            bool isSelectedCell = Value > cellBottomThreshold && Value <= cellTopThreshold;
+
+            // Check the brightness of the background color
+            double brightness = (0.299 * color.R + 0.587 * color.G + 0.114 * color.B) / 255;
+
+            Color triangleColor = Color.Black;
+
+            if (brightness < 0.15) // Threshold to determine if the color is too close to black
+            {
+                triangleColor = Color.DarkGray;
+            }
+
+            if (isSelectedCell)
+            {
+                // Draw the triangle at the closest position
+                Application.Driver?.SetAttribute (new (triangleColor, color));
+                AddRune (x + xOffset, yOffset, new ('▲'));
+
+                // Record for tests
+                TrianglePosition = x + xOffset;
+            }
+            else
+            {
+                Application.Driver?.SetAttribute (new (color, color));
+                AddRune (x + xOffset, yOffset, new ('█'));
+            }
+        }
+    }
+
+    private void OnValueChanged ()
+    {
+        ValueChanged?.Invoke (this, new (in _value));
+        SetNeedsDisplay ();
+    }
+
+    private bool? SetMax ()
+    {
+        Value = MaxValue;
+
+        return true;
+    }
+
+    private bool? SetZero ()
+    {
+        Value = 0;
+
+        return true;
+    }
+}

+ 167 - 0
Terminal.Gui/Views/ColorModelStrategy.cs

@@ -0,0 +1,167 @@
+#nullable enable
+
+using ColorHelper;
+using ColorConverter = ColorHelper.ColorConverter;
+
+namespace Terminal.Gui;
+
+internal class ColorModelStrategy
+{
+    public IEnumerable<ColorBar> CreateBars (ColorModel model)
+    {
+        switch (model)
+        {
+            case ColorModel.RGB:
+                return CreateRgbBars ();
+            case ColorModel.HSV:
+                return CreateHsvBars ();
+            case ColorModel.HSL:
+                return CreateHslBars ();
+            default:
+                throw new ArgumentOutOfRangeException (nameof (model), model, null);
+        }
+    }
+
+    public Color GetColorFromBars (IList<IColorBar> bars, ColorModel model)
+    {
+        switch (model)
+        {
+            case ColorModel.RGB:
+                return ToColor (new ((byte)bars [0].Value, (byte)bars [1].Value, (byte)bars [2].Value));
+            case ColorModel.HSV:
+                return ToColor (
+                                ColorConverter.HsvToRgb (new (bars [0].Value, (byte)bars [1].Value, (byte)bars [2].Value))
+                               );
+            case ColorModel.HSL:
+                return ToColor (
+                                ColorConverter.HslToRgb (new (bars [0].Value, (byte)bars [1].Value, (byte)bars [2].Value))
+                               );
+            default:
+                throw new ArgumentOutOfRangeException (nameof (model), model, null);
+        }
+    }
+
+    public void SetBarsToColor (IList<IColorBar> bars, Color newValue, ColorModel model)
+    {
+        if (bars.Count == 0)
+        {
+            return;
+        }
+        switch (model)
+        {
+            case ColorModel.RGB:
+                bars [0].SetValueWithoutRaisingEvent (newValue.R);
+                bars [1].SetValueWithoutRaisingEvent (newValue.G);
+                bars [2].SetValueWithoutRaisingEvent (newValue.B);
+
+                break;
+            case ColorModel.HSV:
+                HSV newHsv = ColorConverter.RgbToHsv (new (newValue.R, newValue.G, newValue.B));
+                bars [0].SetValueWithoutRaisingEvent (newHsv.H);
+                bars [1].SetValueWithoutRaisingEvent (newHsv.S);
+                bars [2].SetValueWithoutRaisingEvent (newHsv.V);
+
+                break;
+            case ColorModel.HSL:
+
+                HSL newHsl = ColorConverter.RgbToHsl (new (newValue.R, newValue.G, newValue.B));
+                bars [0].SetValueWithoutRaisingEvent (newHsl.H);
+                bars [1].SetValueWithoutRaisingEvent (newHsl.S);
+                bars [2].SetValueWithoutRaisingEvent (newHsl.L);
+
+                break;
+            default:
+                throw new ArgumentOutOfRangeException (nameof (model), model, null);
+        }
+    }
+
+    private IEnumerable<ColorBar> CreateHslBars ()
+    {
+        var h = new HueBar
+        {
+            Text = "H:"
+        };
+
+        yield return h;
+
+        var s = new SaturationBar
+        {
+            Text = "S:"
+        };
+
+        var l = new LightnessBar
+        {
+            Text = "L:"
+        };
+
+        s.HBar = h;
+        s.LBar = l;
+
+        l.HBar = h;
+        l.SBar = s;
+
+        yield return s;
+        yield return l;
+    }
+
+    private IEnumerable<ColorBar> CreateHsvBars ()
+    {
+        var h = new HueBar
+        {
+            Text = "H:"
+        };
+
+        yield return h;
+
+        var s = new SaturationBar
+        {
+            Text = "S:"
+        };
+
+        var v = new ValueBar
+        {
+            Text = "V:"
+        };
+
+        s.HBar = h;
+        s.VBar = v;
+
+        v.HBar = h;
+        v.SBar = s;
+
+        yield return s;
+        yield return v;
+    }
+
+    private IEnumerable<ColorBar> CreateRgbBars ()
+    {
+        var r = new RBar
+        {
+            Text = "R:"
+        };
+
+        var g = new GBar
+        {
+            Text = "G:"
+        };
+
+        var b = new BBar
+        {
+            Text = "B:"
+        };
+        r.GBar = g;
+        r.BBar = b;
+
+        g.RBar = r;
+        g.BBar = b;
+
+        b.RBar = r;
+        b.GBar = g;
+
+        yield return r;
+        yield return g;
+        yield return b;
+    }
+
+    private Color ToColor (RGB rgb) { return new (rgb.R, rgb.G, rgb.B); }
+}

+ 277 - 186
Terminal.Gui/Views/ColorPicker.cs

@@ -1,279 +1,370 @@
-namespace Terminal.Gui;
+#nullable enable
 
-/// <summary>Event arguments for the <see cref="Color"/> events.</summary>
-public class ColorEventArgs : EventArgs
-{
-    /// <summary>Initializes a new instance of <see cref="ColorEventArgs"/></summary>
-    public ColorEventArgs () { }
-
-    /// <summary>The new Thickness.</summary>
-    public Color Color { get; set; }
+using System;
 
-    /// <summary>The previous Thickness.</summary>
-    public Color PreviousColor { get; set; }
-}
+namespace Terminal.Gui;
 
-/// <summary>The <see cref="ColorPicker"/> <see cref="View"/> Color picker.</summary>
+/// <summary>
+///     True color picker using HSL
+/// </summary>
 public class ColorPicker : View
 {
-    /// <summary>Columns of color boxes</summary>
-    private readonly int _cols = 8;
+    /// <summary>
+    ///     Creates a new instance of <see cref="ColorPicker"/>. Use
+    ///     <see cref="Style"/> to change color model. Use <see cref="SelectedColor"/>
+    ///     to change initial <see cref="Color"/>.
+    /// </summary>
+    public ColorPicker ()
+    {
+        CanFocus = true;
+        TabStop = TabBehavior.TabStop;
+        Height = Dim.Auto ();
+        Width = Dim.Auto ();
+        ApplyStyleChanges ();
+    }
+
+    private readonly Dictionary<IColorBar, TextField> _textFields = new ();
+    private readonly ColorModelStrategy _strategy = new ();
+    private TextField? _tfHex;
+    private Label? _lbHex;
 
-    /// <summary>Rows of color boxes</summary>
-    private readonly int _rows = 2;
+    private TextField? _tfName;
+    private Label? _lbName;
 
-    private int _boxHeight = 2;
-    private int _boxWidth = 4;
-    private int _selectColorIndex = (int)Color.Black;
+    private Color _selectedColor = Color.Black;
 
-    /// <summary>Initializes a new instance of <see cref="ColorPicker"/>.</summary>
-    public ColorPicker () { SetInitialProperties (); }
+    // TODO: Add interface
+    private readonly IColorNameResolver _colorNameResolver = new W3CColors ();
 
-    private void SetInitialProperties ()
+    private List<IColorBar> _bars = new ();
+
+    /// <summary>
+    ///     Rebuild the user interface to reflect the new state of <see cref="Style"/>.
+    /// </summary>
+    public void ApplyStyleChanges ()
     {
-        HighlightStyle = Gui.HighlightStyle.PressedOutside | Gui.HighlightStyle.Pressed;
+        Color oldValue = _selectedColor;
+        DisposeOldViews ();
 
-        CanFocus = true;
-        AddCommands ();
-        AddKeyBindings ();
+        var y = 0;
+        const int textFieldWidth = 4;
 
-        Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
-        Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
-        SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
+        foreach (ColorBar bar in _strategy.CreateBars (Style.ColorModel))
+        {
+            bar.Y = y;
+            bar.Width = Dim.Fill (Style.ShowTextFields ? textFieldWidth : 0);
 
-        MouseClick += ColorPicker_MouseClick;
-    }
+            TextField? tfValue = null;
+            if (Style.ShowTextFields)
+            {
+                tfValue = new TextField
+                {
+                    X = Pos.AnchorEnd (textFieldWidth),
+                    Y = y,
+                    Width = textFieldWidth
+                };
+                tfValue.HasFocusChanged += UpdateSingleBarValueFromTextField;
+                tfValue.Accept += (s, _)=>UpdateSingleBarValueFromTextField(s);
+                _textFields.Add (bar, tfValue);
+            }
 
-    // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
+            y++;
 
-    private void ColorPicker_MouseClick (object sender, MouseEventEventArgs me)
-    {
-       // if (CanFocus)
+            bar.ValueChanged += RebuildColorFromBar;
+
+            _bars.Add (bar);
+
+            Add (bar);
+
+            if (tfValue is { })
+            {
+                Add (tfValue);
+            }
+        }
+
+        if (Style.ShowColorName)
         {
-            Cursor = new Point (me.MouseEvent.Position.X / _boxWidth, me.MouseEvent.Position.Y / _boxHeight);
-            SetFocus ();
-            me.Handled = true;
+            CreateNameField ();
         }
-    }
 
-    /// <summary>Height of a color box</summary>
-    public int BoxHeight
-    {
-        get => _boxHeight;
-        set
+        CreateTextField ();
+        SelectedColor = oldValue;
+
+        if (IsInitialized)
         {
-            if (_boxHeight != value)
-            {
-                _boxHeight = value;
-                Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
-                Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
-                SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
-                SetNeedsLayout ();
-            }
+            LayoutSubviews ();
         }
     }
 
-    /// <summary>Width of a color box</summary>
-    public int BoxWidth
+    /// <summary>
+    ///     Fired when color is changed.
+    /// </summary>
+    public event EventHandler<ColorEventArgs>? ColorChanged;
+
+    /// <inheritdoc/>
+    public override void OnDrawContent (Rectangle viewport)
     {
-        get => _boxWidth;
-        set
+        base.OnDrawContent (viewport);
+        Attribute normal = GetNormalColor ();
+        Driver.SetAttribute (new (SelectedColor, normal.Background));
+        int y = _bars.Count + (Style.ShowColorName ? 1 : 0);
+        AddRune (13, y, (Rune)'■');
+    }
+
+    /// <summary>
+    ///     The color selected in the picker
+    /// </summary>
+    public Color SelectedColor
+    {
+        get => _selectedColor;
+        set => SetSelectedColor (value, true);
+    }
+
+    /// <summary>
+    ///     Style settings for the color picker.  After making changes ensure you call
+    ///     <see cref="ApplyStyleChanges"/>.
+    /// </summary>
+    public ColorPickerStyle Style { get; set; } = new ();
+
+    private void CreateNameField ()
+    {
+        _lbName = new ()
         {
-            if (_boxWidth != value)
-            {
-                _boxWidth = value;
-                Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
-                Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
-                SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
-                SetNeedsLayout ();
-            }
-        }
+            Text = "Name:",
+            X = 0,
+            Y = 3
+        };
+
+        _tfName = new ()
+        {
+            Y = 3,
+            X = 6,
+            Width = 20 // width of "LightGoldenRodYellow" - the longest w3c color name
+        };
+
+        Add (_lbName);
+        Add (_tfName);
+
+        var auto = new AppendAutocomplete (_tfName);
+
+        auto.SuggestionGenerator = new SingleWordSuggestionGenerator
+        {
+            AllSuggestions = _colorNameResolver.GetColorNames ().ToList ()
+        };
+        _tfName.Autocomplete = auto;
+
+        _tfName.HasFocusChanged += UpdateValueFromName;
+        _tfName.Accept += (_s, _) => UpdateValueFromName ();
     }
 
-    /// <summary>Cursor for the selected color.</summary>
-    public Point Cursor
+    private void CreateTextField ()
     {
-        get => new (_selectColorIndex % _cols, _selectColorIndex / _cols);
-        set
+        int y = _bars.Count;
+
+        if (Style.ShowColorName)
         {
-            int colorIndex = value.Y * _cols + value.X;
-            SelectedColor = (ColorName)colorIndex;
+            y++;
         }
+
+        _lbHex = new ()
+        {
+            Text = "Hex:",
+            X = 0,
+            Y = y
+        };
+
+        _tfHex = new ()
+        {
+            Y = y,
+            X = 4,
+            Width = 8,
+        };
+
+        Add (_lbHex);
+        Add (_tfHex);
+
+        _tfHex.HasFocusChanged += UpdateValueFromTextField;
+        _tfHex.Accept += (_,_)=> UpdateValueFromTextField();
     }
 
-    /// <summary>Selected color.</summary>
-    public ColorName SelectedColor
+    private void DisposeOldViews ()
     {
-        get => (ColorName)_selectColorIndex;
-        set
+        foreach (ColorBar bar in _bars.Cast<ColorBar> ())
         {
-            if (value == (ColorName)_selectColorIndex)
+            bar.ValueChanged -= RebuildColorFromBar;
+
+            if (_textFields.TryGetValue (bar, out TextField? tf))
             {
-                return;
+                Remove (tf);
+                tf.Dispose ();
             }
-            var prev = (ColorName)_selectColorIndex;
-            _selectColorIndex = (int)value;
 
-            ColorChanged?.Invoke (
-                                  this,
-                                  new ColorEventArgs { PreviousColor = new Color (prev), Color = new Color (value) }
-                                 );
-            SetNeedsDisplay ();
+            Remove (bar);
+            bar.Dispose ();
         }
-    }
 
-    /// <summary>Fired when a color is picked.</summary>
-    public event EventHandler<ColorEventArgs> ColorChanged;
+        _bars = new ();
+        _textFields.Clear ();
 
+        if (_lbHex != null)
+        {
+            Remove (_lbHex);
+            _lbHex.Dispose ();
+            _lbHex = null;
+        }
 
-    /// <summary>Moves the selected item index to the previous column.</summary>
-    /// <returns></returns>
-    public virtual bool MoveLeft ()
-    {
-        if (Cursor.X > 0)
+        if (_tfHex != null)
+        {
+            Remove (_tfHex);
+            _tfHex.Dispose ();
+            _tfHex = null;
+        }
+
+        if (_lbName != null)
         {
-            SelectedColor--;
+            Remove (_lbName);
+            _lbName.Dispose ();
+            _lbName = null;
         }
 
-        return true;
+        if (_tfName != null)
+        {
+            Remove (_tfName);
+            _tfName.Dispose ();
+            _tfName = null;
+        }
     }
 
-    /// <summary>Moves the selected item index to the next column.</summary>
-    /// <returns></returns>
-    public virtual bool MoveRight ()
+    private void RebuildColorFromBar (object? sender, EventArgs<int> e) { SetSelectedColor (_strategy.GetColorFromBars (_bars, Style.ColorModel), false); }
+
+    private void SetSelectedColor (Color value, bool syncBars)
     {
-        if (Cursor.X < _cols - 1)
+        if (_selectedColor != value)
         {
-            SelectedColor++;
+            Color old = _selectedColor;
+            _selectedColor = value;
+
+            ColorChanged?.Invoke (
+                                  this,
+                                  new (value));
         }
 
-        return true;
+        SyncSubViewValues (syncBars);
     }
 
-    /// <summary>Moves the selected item index to the previous row.</summary>
-    /// <returns></returns>
-    public virtual bool MoveUp ()
+    private void SyncSubViewValues (bool syncBars)
     {
-        if (Cursor.Y > 0)
+        if (syncBars)
         {
-            SelectedColor -= _cols;
+            _strategy.SetBarsToColor (_bars, _selectedColor, Style.ColorModel);
         }
 
-        return true;
-    }
+        foreach (KeyValuePair<IColorBar, TextField> kvp in _textFields)
+        {
+            kvp.Value.Text = kvp.Key.Value.ToString ();
+        }
 
-    /// <summary>Moves the selected item index to the next row.</summary>
-    /// <returns></returns>
-    public virtual bool MoveDown ()
-    {
-        if (Cursor.Y < _rows - 1)
+        var colorHex = _selectedColor.ToString ($"#{SelectedColor.R:X2}{SelectedColor.G:X2}{SelectedColor.B:X2}");
+
+        if (_tfName != null)
         {
-            SelectedColor += _cols;
+            _tfName.Text = _colorNameResolver.TryNameColor (_selectedColor, out string name) ? name : string.Empty;
         }
 
-        return true;
+        if (_tfHex != null)
+        {
+            _tfHex.Text = colorHex;
+        }
     }
 
-    ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
+    private void UpdateSingleBarValueFromTextField (object? sender, HasFocusEventArgs e)
     {
-        base.OnDrawContent (viewport);
+        // if the new value of Focused is true then it is an enter event so ignore
+        if (e.NewValue)
+        {
+            return;
+        }
 
-        Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
-        var colorIndex = 0;
+        // it is a leave event so update
+        UpdateSingleBarValueFromTextField (sender);
+    }
+    private void UpdateSingleBarValueFromTextField (object? sender)
+    {
 
-        for (var y = 0; y < Math.Max (2, viewport.Height / BoxHeight); y++)
+        foreach (KeyValuePair<IColorBar, TextField> kvp in _textFields)
         {
-            for (var x = 0; x < Math.Max (8, viewport.Width / BoxWidth); x++)
+            if (kvp.Value == sender)
             {
-                int foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
-                Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
-                bool selected = x == Cursor.X && y == Cursor.Y;
-                DrawColorBox (x, y, selected);
-                colorIndex++;
+                if (int.TryParse (kvp.Value.Text, out int v))
+                {
+                    kvp.Key.Value = v;
+                }
             }
         }
     }
 
-
-    /// <summary>Add the commands.</summary>
-    private void AddCommands ()
+    private void UpdateValueFromName (object sender, HasFocusEventArgs e)
     {
-        AddCommand (Command.Left, () => MoveLeft ());
-        AddCommand (Command.Right, () => MoveRight ());
-        AddCommand (Command.LineUp, () => MoveUp ());
-        AddCommand (Command.LineDown, () => MoveDown ());
-    }
+        // if the new value of Focused is true then it is an enter event so ignore
+        if (e.NewValue)
+        {
+            return;
+        }
 
-    /// <summary>Add the KeyBindinds.</summary>
-    private void AddKeyBindings ()
-    {
-        KeyBindings.Add (Key.CursorLeft, Command.Left);
-        KeyBindings.Add (Key.CursorRight, Command.Right);
-        KeyBindings.Add (Key.CursorUp, Command.LineUp);
-        KeyBindings.Add (Key.CursorDown, Command.LineDown);
+        // it is a leave event so update
+        UpdateValueFromName();
     }
-
-    /// <summary>Draw a box for one color.</summary>
-    /// <param name="x">X location.</param>
-    /// <param name="y">Y location</param>
-    /// <param name="selected"></param>
-    private void DrawColorBox (int x, int y, bool selected)
+    private void UpdateValueFromName ()
     {
-        var index = 0;
-
-        for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++)
+        if (_tfName == null)
         {
-            for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++)
-            {
-                Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY);
-                Driver.AddRune ((Rune)' ');
-                index++;
-            }
+            return;
         }
 
-        if (selected)
+        if (_colorNameResolver.TryParseColor (_tfName.Text, out Color newColor))
         {
-            DrawFocusRect (new (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight));
+            SelectedColor = newColor;
+        }
+        else
+        {
+            // value is invalid, revert the value in the text field back to current state
+            SyncSubViewValues (false);
         }
     }
 
-    private void DrawFocusRect (Rectangle rect)
+    private void UpdateValueFromTextField (object? sender, HasFocusEventArgs e)
     {
-        var lc = new LineCanvas ();
+        // if the new value of Focused is true then it is an enter event so ignore
+        if (e.NewValue)
+        {
+            return;
+        }
 
-        if (rect.Width == 1)
+        // it is a leave event so update
+        UpdateValueFromTextField ();
+    }
+    private void UpdateValueFromTextField ()
+    {
+        if (_tfHex == null)
         {
-            lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
+            return;
         }
-        else if (rect.Height == 1)
+
+        if (Color.TryParse (_tfHex.Text, out Color? newColor))
         {
-            lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+            SelectedColor = newColor.Value;
         }
         else
         {
-            lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
-
-            lc.AddLine (
-                        rect.Location with { Y = rect.Location.Y + rect.Height - 1 },
-                        rect.Width,
-                        Orientation.Horizontal,
-                        LineStyle.Dotted
-                       );
-
-            lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
-
-            lc.AddLine (
-                        rect.Location with { X = rect.Location.X + rect.Width - 1 },
-                        rect.Height,
-                        Orientation.Vertical,
-                        LineStyle.Dotted
-                       );
+            // value is invalid, revert the value in the text field back to current state
+            SyncSubViewValues (false);
         }
+    }
 
-        foreach (KeyValuePair<Point, Rune> p in lc.GetMap ())
-        {
-            AddRune (p.Key.X, p.Key.Y, p.Value);
-        }
+    /// <inheritdoc />
+    protected override void Dispose (bool disposing)
+    {
+        DisposeOldViews ();
+        base.Dispose (disposing);
     }
 }

+ 271 - 0
Terminal.Gui/Views/ColorPicker16.cs

@@ -0,0 +1,271 @@
+namespace Terminal.Gui;
+
+/// <summary>The <see cref="ColorPicker16"/> <see cref="View"/> Color picker.</summary>
+public class ColorPicker16 : View
+{
+    /// <summary>Initializes a new instance of <see cref="ColorPicker16"/>.</summary>
+    public ColorPicker16 () { SetInitialProperties (); }
+
+    /// <summary>Columns of color boxes</summary>
+    private readonly int _cols = 8;
+
+    /// <summary>Rows of color boxes</summary>
+    private readonly int _rows = 2;
+
+    private int _boxHeight = 2;
+    private int _boxWidth = 4;
+    private int _selectColorIndex = (int)Color.Black;
+
+    /// <summary>Height of a color box</summary>
+    public int BoxHeight
+    {
+        get => _boxHeight;
+        set
+        {
+            if (_boxHeight != value)
+            {
+                _boxHeight = value;
+                Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
+                Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
+                SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
+                SetNeedsLayout ();
+            }
+        }
+    }
+
+    /// <summary>Width of a color box</summary>
+    public int BoxWidth
+    {
+        get => _boxWidth;
+        set
+        {
+            if (_boxWidth != value)
+            {
+                _boxWidth = value;
+                Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
+                Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
+                SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
+                SetNeedsLayout ();
+            }
+        }
+    }
+
+    /// <summary>Fired when a color is picked.</summary>
+    [CanBeNull]
+    public event EventHandler<ColorEventArgs> ColorChanged;
+
+    /// <summary>Cursor for the selected color.</summary>
+    public Point Cursor
+    {
+        get => new (_selectColorIndex % _cols, _selectColorIndex / _cols);
+        set
+        {
+            int colorIndex = value.Y * _cols + value.X;
+            SelectedColor = (ColorName)colorIndex;
+        }
+    }
+
+    /// <summary>Moves the selected item index to the next row.</summary>
+    /// <returns></returns>
+    public virtual bool MoveDown ()
+    {
+        if (Cursor.Y < _rows - 1)
+        {
+            SelectedColor += _cols;
+        }
+
+        return true;
+    }
+
+    /// <summary>Moves the selected item index to the previous column.</summary>
+    /// <returns></returns>
+    public virtual bool MoveLeft ()
+    {
+        if (Cursor.X > 0)
+        {
+            SelectedColor--;
+        }
+
+        return true;
+    }
+
+    /// <summary>Moves the selected item index to the next column.</summary>
+    /// <returns></returns>
+    public virtual bool MoveRight ()
+    {
+        if (Cursor.X < _cols - 1)
+        {
+            SelectedColor++;
+        }
+
+        return true;
+    }
+
+    /// <summary>Moves the selected item index to the previous row.</summary>
+    /// <returns></returns>
+    public virtual bool MoveUp ()
+    {
+        if (Cursor.Y > 0)
+        {
+            SelectedColor -= _cols;
+        }
+
+        return true;
+    }
+
+    ///<inheritdoc/>
+    public override void OnDrawContent (Rectangle viewport)
+    {
+        base.OnDrawContent (viewport);
+
+        Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
+        var colorIndex = 0;
+
+        for (var y = 0; y < Math.Max (2, viewport.Height / BoxHeight); y++)
+        {
+            for (var x = 0; x < Math.Max (8, viewport.Width / BoxWidth); x++)
+            {
+                int foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
+
+                if (foregroundColorIndex > 15 || colorIndex > 15)
+                {
+                    continue;
+                }
+
+                Driver.SetAttribute (new ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
+                bool selected = x == Cursor.X && y == Cursor.Y;
+                DrawColorBox (x, y, selected);
+                colorIndex++;
+            }
+        }
+    }
+
+    /// <summary>Selected color.</summary>
+    public ColorName SelectedColor
+    {
+        get => (ColorName)_selectColorIndex;
+        set
+        {
+            if (value == (ColorName)_selectColorIndex)
+            {
+                return;
+            }
+
+            _selectColorIndex = (int)value;
+
+            ColorChanged?.Invoke (
+                                  this,
+                                  new (value)
+                                 );
+            SetNeedsDisplay ();
+        }
+    }
+
+    /// <summary>Add the commands.</summary>
+    private void AddCommands ()
+    {
+        AddCommand (Command.Left, () => MoveLeft ());
+        AddCommand (Command.Right, () => MoveRight ());
+        AddCommand (Command.LineUp, () => MoveUp ());
+        AddCommand (Command.LineDown, () => MoveDown ());
+    }
+
+    /// <summary>Add the KeyBindinds.</summary>
+    private void AddKeyBindings ()
+    {
+        KeyBindings.Add (Key.CursorLeft, Command.Left);
+        KeyBindings.Add (Key.CursorRight, Command.Right);
+        KeyBindings.Add (Key.CursorUp, Command.LineUp);
+        KeyBindings.Add (Key.CursorDown, Command.LineDown);
+    }
+
+    // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
+
+    private void ColorPicker_MouseClick (object sender, MouseEventEventArgs me)
+    {
+        // if (CanFocus)
+        {
+            Cursor = new (me.MouseEvent.Position.X / _boxWidth, me.MouseEvent.Position.Y / _boxHeight);
+            SetFocus ();
+            me.Handled = true;
+        }
+    }
+
+    /// <summary>Draw a box for one color.</summary>
+    /// <param name="x">X location.</param>
+    /// <param name="y">Y location</param>
+    /// <param name="selected"></param>
+    private void DrawColorBox (int x, int y, bool selected)
+    {
+        var index = 0;
+
+        for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++)
+        {
+            for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++)
+            {
+                Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY);
+                Driver.AddRune ((Rune)' ');
+                index++;
+            }
+        }
+
+        if (selected)
+        {
+            DrawFocusRect (new (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight));
+        }
+    }
+
+    private void DrawFocusRect (Rectangle rect)
+    {
+        var lc = new LineCanvas ();
+
+        if (rect.Width == 1)
+        {
+            lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
+        }
+        else if (rect.Height == 1)
+        {
+            lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+        }
+        else
+        {
+            lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+
+            lc.AddLine (
+                        rect.Location with { Y = rect.Location.Y + rect.Height - 1 },
+                        rect.Width,
+                        Orientation.Horizontal,
+                        LineStyle.Dotted
+                       );
+
+            lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
+
+            lc.AddLine (
+                        rect.Location with { X = rect.Location.X + rect.Width - 1 },
+                        rect.Height,
+                        Orientation.Vertical,
+                        LineStyle.Dotted
+                       );
+        }
+
+        foreach (KeyValuePair<Point, Rune> p in lc.GetMap ())
+        {
+            AddRune (p.Key.X, p.Key.Y, p.Value);
+        }
+    }
+
+    private void SetInitialProperties ()
+    {
+        HighlightStyle = HighlightStyle.PressedOutside | HighlightStyle.Pressed;
+
+        CanFocus = true;
+        AddCommands ();
+        AddKeyBindings ();
+
+        Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
+        Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
+        SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
+
+        MouseClick += ColorPicker_MouseClick;
+    }
+}

+ 25 - 0
Terminal.Gui/Views/ColorPickerStyle.cs

@@ -0,0 +1,25 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Contains style settings for <see cref="ColorPicker"/> e.g. which <see cref="ColorModel"/>
+/// to use.
+/// </summary>
+public class ColorPickerStyle
+{
+    /// <summary>
+    ///     The color model for picking colors by RGB, HSV, etc.
+    /// </summary>
+    public ColorModel ColorModel { get; set; } = ColorModel.HSV;
+
+    /// <summary>
+    ///     True to put the numerical value of bars on the right of the color bar
+    /// </summary>
+    public bool ShowTextFields { get; set; } = true;
+
+    /// <summary>
+    ///     True to show an editable text field indicating the w3c/console color name of selected color.
+    /// </summary>
+    public bool ShowColorName { get; set; } = false;
+}

+ 51 - 50
Terminal.Gui/Views/ComboBox.cs

@@ -8,6 +8,7 @@
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Diagnostics;
+using System.Threading.Channels;
 
 namespace Terminal.Gui;
 
@@ -28,9 +29,10 @@ public class ComboBox : View, IDesignable
     /// <summary>Public constructor</summary>
     public ComboBox ()
     {
+        CanFocus = true;
         _search = new TextField () { CanFocus = true, TabStop = TabBehavior.NoStop };
 
-        _listview = new ComboListView (this, HideDropdownListOnClick) { CanFocus = true, TabStop = TabBehavior.NoStop};
+        _listview = new ComboListView (this, HideDropdownListOnClick) { CanFocus = true, TabStop = TabBehavior.NoStop };
 
         _search.TextChanged += Search_Changed;
         _search.Accept += Search_Accept;
@@ -298,44 +300,41 @@ public class ComboBox : View, IDesignable
         Driver.AddRune (Glyphs.DownArrow);
     }
 
-    /// <inheritdoc/>
-    public override bool OnEnter (View view)
-    {
-        if (!_search.HasFocus && !_listview.HasFocus)
-        {
-            _search.SetFocus ();
-        }
-
-        _search.CursorPosition = _search.Text.GetRuneCount ();
-
-        return base.OnEnter (view);
-    }
 
     /// <summary>Virtual method which invokes the <see cref="Expanded"/> event.</summary>
     public virtual void OnExpanded () { Expanded?.Invoke (this, EventArgs.Empty); }
 
     /// <inheritdoc/>
-    public override bool OnLeave (View view)
+    protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
     {
-        if (_source?.Count > 0
-            && _selectedItem > -1
-            && _selectedItem < _source.Count - 1
-            && _text != _source.ToList () [_selectedItem].ToString ())
+        if (newHasFocus)
         {
-            SetValue (_source.ToList () [_selectedItem].ToString ());
+            if (!_search.HasFocus && !_listview.HasFocus)
+            {
+                _search.SetFocus ();
+            }
+            _search.CursorPosition = _search.Text.GetRuneCount ();
         }
+        else
+        { 
+            if (_source?.Count > 0
+              && _selectedItem > -1
+              && _selectedItem < _source.Count - 1
+              && _text != _source.ToList () [_selectedItem].ToString ())
+            {
+                SetValue (_source.ToList () [_selectedItem].ToString ());
+            }
 
-        if (_autoHide && IsShow && view != this && view != _search && view != _listview)
-        {
-            IsShow = false;
-            HideList ();
-        }
-        else if (_listview.TabStop?.HasFlag (TabBehavior.TabStop) ?? false)
-        {
-            _listview.TabStop = TabBehavior.NoStop;
+            if (_autoHide && IsShow && view != this && view != _search && view != _listview)
+            {
+                IsShow = false;
+                HideList ();
+            }
+            else if (_listview.TabStop?.HasFlag (TabBehavior.TabStop) ?? false)
+            {
+                _listview.TabStop = TabBehavior.NoStop;
+            }
         }
-
-        return base.OnLeave (view);
     }
 
     /// <summary>Invokes the OnOpenSelectedItem event if it is defined.</summary>
@@ -415,7 +414,10 @@ public class ComboBox : View, IDesignable
 
     private bool CancelSelected ()
     {
-        _search.SetFocus ();
+        if (HasFocus)
+        {
+            _search.SetFocus ();
+        }
 
         if (ReadOnly || HideDropdownListOnClick)
         {
@@ -493,7 +495,7 @@ public class ComboBox : View, IDesignable
         Reset (true);
         _listview.Clear ();
         _listview.TabStop = TabBehavior.NoStop;
-        SuperView?.SendSubviewToBack (this);
+        SuperView?.MoveSubviewToStart (this);
         Rectangle rect = _listview.ViewportToScreen (_listview.IsInitialized ? _listview.Viewport : Rectangle.Empty);
         SuperView?.SetNeedsDisplay (rect);
         OnCollapsed ();
@@ -563,7 +565,7 @@ public class ComboBox : View, IDesignable
     {
         if (HasItems ())
         {
-           return  _listview.MoveUp ();
+            return _listview.MoveUp ();
         }
 
         return false;
@@ -793,7 +795,7 @@ public class ComboBox : View, IDesignable
 
         _listview.Clear ();
         _listview.Height = CalculateHeight ();
-        SuperView?.BringSubviewToFront (this);
+        SuperView?.MoveSubviewToStart (this);
     }
 
     private bool UnixEmulation ()
@@ -824,6 +826,7 @@ public class ComboBox : View, IDesignable
             set => _hideDropdownListOnClick = WantContinuousButtonPressed = value;
         }
 
+        // BUGBUG: OnMouseEvent is internal!
         protected internal override bool OnMouseEvent (MouseEvent me)
         {
             var res = false;
@@ -940,28 +943,26 @@ public class ComboBox : View, IDesignable
             }
         }
 
-        public override bool OnEnter (View view)
+        protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew)
         {
-            if (_hideDropdownListOnClick)
+            if (newHasFocus)
             {
-                _isFocusing = true;
-                _highlighted = _container.SelectedItem;
-                Application.GrabMouse (this);
+                if (_hideDropdownListOnClick)
+                {
+                    _isFocusing = true;
+                    _highlighted = _container.SelectedItem;
+                    Application.GrabMouse (this);
+                }
             }
-
-            return base.OnEnter (view);
-        }
-
-        public override bool OnLeave (View view)
-        {
-            if (_hideDropdownListOnClick)
+            else
             {
-                _isFocusing = false;
-                _highlighted = _container.SelectedItem;
-                Application.UngrabMouse ();
+                if (_hideDropdownListOnClick)
+                {
+                    _isFocusing = false;
+                    _highlighted = _container.SelectedItem;
+                    Application.UngrabMouse ();
+                }
             }
-
-            return base.OnLeave (view);
         }
 
         public override bool OnSelectedChanged ()

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

@@ -13,7 +13,7 @@ namespace Terminal.Gui;
 public class DatePicker : View
 {
     private TableView _calendar;
-    private DateTime _date = DateTime.Now;
+    private DateTime _date;
     private DateField _dateField;
     private Label _dateLabel;
     private Button _nextMonthButton;
@@ -21,7 +21,7 @@ public class DatePicker : View
     private DataTable _table;
 
     /// <summary>Initializes a new instance of <see cref="DatePicker"/>.</summary>
-    public DatePicker () { SetInitialProperties (_date); }
+    public DatePicker () { SetInitialProperties (DateTime.Now); }
 
     /// <summary>Initializes a new instance of <see cref="DatePicker"/> with the specified date.</summary>
     public DatePicker (DateTime date) { SetInitialProperties (date); }
@@ -183,14 +183,16 @@ public class DatePicker : View
 
     private void SetInitialProperties (DateTime date)
     {
+        _date = date;
         Title = "Date Picker";
         BorderStyle = LineStyle.Single;
         Date = date;
         _dateLabel = new Label { X = 0, Y = 0, Text = "Date: " };
-        TabStop = TabBehavior.TabGroup;
+        CanFocus = true;
 
         _calendar = new TableView
         {
+            Id = "_calendar",
             X = 0,
             Y = Pos.Bottom (_dateLabel),
             Height = 11,
@@ -205,6 +207,7 @@ public class DatePicker : View
 
         _dateField = new DateField (DateTime.Now)
         {
+            Id = "_dateField",
             X = Pos.Right (_dateLabel),
             Y = 0,
             Width = Dim.Width (_calendar) - Dim.Width (_dateLabel),
@@ -214,6 +217,7 @@ public class DatePicker : View
 
         _previousMonthButton = new Button
         {
+            Id = "_previousMonthButton",
             X = Pos.Center () - 2,
             Y = Pos.Bottom (_calendar) - 1,
             Width = 2,
@@ -233,6 +237,7 @@ public class DatePicker : View
 
         _nextMonthButton = new Button
         {
+            Id = "_nextMonthButton",
             X = Pos.Right (_previousMonthButton) + 2,
             Y = Pos.Bottom (_calendar) - 1,
             Width = 2,

+ 18 - 15
Terminal.Gui/Views/Dialog.cs

@@ -1,6 +1,4 @@
-using System.Text.Json.Serialization;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
 ///     The <see cref="Dialog"/> <see cref="View"/> is a <see cref="Window"/> that by default is centered and contains
@@ -19,13 +17,11 @@ public class Dialog : Window
     /// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
     /// <remarks>This property can be set in a Theme.</remarks>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<Alignment>))]
     public static Alignment DefaultButtonAlignment { get; set; } = Alignment.End; // Default is set in config.json
 
-    /// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
+    /// <summary>The default <see cref="AlignmentModes"/> for <see cref="Dialog"/>.</summary>
     /// <remarks>This property can be set in a Theme.</remarks>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<AlignmentModes>))]
     public static AlignmentModes DefaultButtonAlignmentModes { get; set; } = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems;
 
     /// <summary>
@@ -47,7 +43,6 @@ public class Dialog : Window
     /// Gets or sets whether all <see cref="Window"/>s are shown with a shadow effect by default.
     /// </summary>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<ShadowStyle>))]
     public new static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.None; // Default is set in config.json
 
     /// <summary>
@@ -56,7 +51,6 @@ public class Dialog : Window
     /// </summary>
 
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
     public new static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single; // Default is set in config.json
 
     private readonly List<Button> _buttons = new ();
@@ -99,6 +93,22 @@ public class Dialog : Window
         KeyBindings.Add (Key.Esc, Command.QuitToplevel);
     }
 
+    // BUGBUG: We override GetNormal/FocusColor because "Dialog" ColorScheme is goofy.
+    // BUGBUG: By defn, a Dialog is Modal, and thus HasFocus is always true. OnDrawContent
+    // BUGBUG: Calls these methods.
+    // TODO: Fix this in https://github.com/gui-cs/Terminal.Gui/issues/2381
+    /// <inheritdoc />
+    public override Attribute GetNormalColor ()
+    {
+        return ColorScheme.Normal;
+    }
+
+    /// <inheritdoc />
+    public override Attribute GetFocusColor ()
+    {
+        return ColorScheme.Normal;
+    }
+
     private bool _canceled;
 
     /// <summary>Gets a value indicating whether the <see cref="Dialog"/> was canceled.</summary>
@@ -172,12 +182,5 @@ public class Dialog : Window
 
         _buttons.Add (button);
         Add (button);
-
-        SetNeedsDisplay ();
-
-        if (IsInitialized)
-        {
-            LayoutSubviews ();
-        }
     }
 }

+ 66 - 149
Terminal.Gui/Views/FileDialog.cs

@@ -10,6 +10,9 @@ namespace Terminal.Gui;
 /// </summary>
 public class FileDialog : Dialog
 {
+    private const int alignmentGroupInput = 32;
+    private const int alignmentGroupComplete = 55;
+
     /// <summary>Gets the Path separators for the operating system</summary>
     internal static char [] Separators =
     [
@@ -71,28 +74,25 @@ public class FileDialog : Dialog
 
         _btnOk = new Button
         {
-            Y = Pos.AnchorEnd (1), X = Pos.Func (CalculateOkButtonPosX), IsDefault = true, Text = Style.OkButtonText
+            X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, alignmentGroupComplete),
+            Y = Pos.AnchorEnd (),
+            IsDefault = true, Text = Style.OkButtonText
         };
         _btnOk.Accept += (s, e) => Accept (true);
 
-        _btnOk.KeyDown += (s, k) =>
-                          {
-                              NavigateIf (k, KeyCode.CursorLeft, _btnCancel);
-                              NavigateIf (k, KeyCode.CursorUp, _tableView);
-                          };
-
-        _btnCancel = new Button { Y = Pos.AnchorEnd (1), X = Pos.Right (_btnOk) + 1, Text = Strings.btnCancel };
-
-        _btnCancel.KeyDown += (s, k) =>
-                              {
-                                  NavigateIf (k, KeyCode.CursorLeft, _btnToggleSplitterCollapse);
-                                  NavigateIf (k, KeyCode.CursorUp, _tableView);
-                                  NavigateIf (k, KeyCode.CursorRight, _btnOk);
-                              };
-        _btnCancel.Accept += (s, e) => {
-                                 Canceled = true;
-                                 Application.RequestStop ();
-                             };
+
+        _btnCancel = new Button
+        {
+            X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, alignmentGroupComplete),
+            Y = Pos.AnchorEnd(),
+            Text = Strings.btnCancel
+        };
+
+        _btnCancel.Accept += (s, e) =>
+        {
+            Canceled = true;
+            Application.RequestStop ();
+        };
 
         _btnUp = new Button { X = 0, Y = 1, NoPadding = true };
         _btnUp.Text = GetUpButtonText ();
@@ -120,7 +120,13 @@ public class FileDialog : Dialog
         _tbPath.Autocomplete = new AppendAutocomplete (_tbPath);
         _tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
 
-        _splitContainer = new TileView { X = 0, Y = 2, Width = Dim.Fill (), Height = Dim.Fill (1) };
+        _splitContainer = new TileView
+        {
+            X = 0,
+            Y = Pos.Bottom (_btnBack),
+            Width = Dim.Fill (),
+            Height = Dim.Fill (Dim.Func (() => IsInitialized ? _btnOk.Frame.Height : 1)),
+        };
 
         Initialized += (s, e) =>
                        {
@@ -128,7 +134,7 @@ public class FileDialog : Dialog
                            _splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
                        };
 
-        //			this.splitContainer.Border.BorderStyle = BorderStyle.None;
+        // this.splitContainer.Border.BorderStyle = BorderStyle.None;
 
         _tableView = new TableView
         {
@@ -157,28 +163,7 @@ public class FileDialog : Dialog
         ColumnStyle typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3);
         typeStyle.MinWidth = 6;
         typeStyle.ColorGetter = ColorGetter;
-
-        _tableView.KeyDown += (s, k) =>
-                              {
-                                  if (_tableView.SelectedRow <= 0)
-                                  {
-                                      NavigateIf (k, KeyCode.CursorUp, _tbPath);
-                                  }
-
-                                  if (_tableView.SelectedRow == _tableView.Table.Rows - 1)
-                                  {
-                                      NavigateIf (k, KeyCode.CursorDown, _btnToggleSplitterCollapse);
-                                  }
-
-                                  if (_splitContainer.Tiles.First ().ContentView.Visible && _tableView.SelectedColumn == 0)
-                                  {
-                                      NavigateIf (k, KeyCode.CursorLeft, _treeView);
-                                  }
-
-                                  if (k.Handled)
-                                  { }
-                              };
-
+        
         _treeView = new TreeView<IFileSystemInfo> { Width = Dim.Fill (), Height = Dim.Fill () };
 
         var fileDialogTreeBuilder = new FileSystemTreeBuilder ();
@@ -191,7 +176,11 @@ public class FileDialog : Dialog
         _splitContainer.Tiles.ElementAt (0).ContentView.Add (_treeView);
         _splitContainer.Tiles.ElementAt (1).ContentView.Add (_tableView);
 
-        _btnToggleSplitterCollapse = new Button { Y = Pos.AnchorEnd (1), Text = GetToggleSplitterText (false) };
+        _btnToggleSplitterCollapse = new Button
+        {
+            X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput),
+            Y = Pos.AnchorEnd (), Text = GetToggleSplitterText (false)
+        };
 
         _btnToggleSplitterCollapse.Accept += (s, e) =>
                                               {
@@ -205,13 +194,13 @@ public class FileDialog : Dialog
 
         _tbFind = new TextField
         {
-            X = Pos.Right (_btnToggleSplitterCollapse) + 1,
+            X = Pos.Align (Alignment.Start,AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput),
             CaptionColor = new Color (Color.Black),
             Width = 30,
-            Y = Pos.AnchorEnd (1),
+            Y = Pos.Top (_btnToggleSplitterCollapse),
             HotKey = Key.F.WithAlt
         };
-        _spinnerView = new SpinnerView { X = Pos.Right (_tbFind) + 1, Y = Pos.AnchorEnd (1), Visible = false };
+        _spinnerView = new SpinnerView { X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput), Y = Pos.AnchorEnd (1), Visible = false };
 
         _tbFind.TextChanged += (s, o) => RestartSearch ();
 
@@ -230,16 +219,6 @@ public class FileDialog : Dialog
                                        o.Handled = true;
                                    }
                                }
-
-                               if (_tbFind.CursorIsAtEnd ())
-                               {
-                                   NavigateIf (o, KeyCode.CursorRight, _btnCancel);
-                               }
-
-                               if (_tbFind.CursorIsAtStart ())
-                               {
-                                   NavigateIf (o, KeyCode.CursorLeft, _btnToggleSplitterCollapse);
-                               }
                            };
 
         _tableView.Style.ShowHorizontalHeaderOverline = true;
@@ -261,46 +240,22 @@ public class FileDialog : Dialog
         _tableView.KeyBindings.ReplaceCommands (Key.End, Command.BottomEnd);
         _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.TopHomeExtend);
         _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.BottomEndExtend);
-
-        _treeView.KeyDown += (s, k) =>
-                             {
-                                 IFileSystemInfo selected = _treeView.SelectedObject;
-
-                                 if (selected is { })
-                                 {
-                                     if (!_treeView.CanExpand (selected) || _treeView.IsExpanded (selected))
-                                     {
-                                         NavigateIf (k, KeyCode.CursorRight, _tableView);
-                                     }
-                                     else if (_treeView.GetObjectRow (selected) == 0)
-                                     {
-                                         NavigateIf (k, KeyCode.CursorUp, _tbPath);
-                                     }
-                                 }
-
-                                 if (k.Handled)
-                                 {
-                                     return;
-                                 }
-
-                                 k.Handled = TreeView_KeyDown (k);
-                             };
-
+        
         AllowsMultipleSelection = false;
 
         UpdateNavigationVisibility ();
 
-        // Determines tab order
-        Add (_btnToggleSplitterCollapse);
-        Add (_tbFind);
-        Add (_spinnerView);
-        Add (_btnOk);
-        Add (_btnCancel);
+        Add (_tbPath);
         Add (_btnUp);
         Add (_btnBack);
         Add (_btnForward);
-        Add (_tbPath);
         Add (_splitContainer);
+        Add (_btnToggleSplitterCollapse);
+        Add (_tbFind);
+        Add (_spinnerView);
+
+        Add(_btnOk);
+        Add(_btnCancel);
     }
 
     /// <summary>
@@ -458,19 +413,6 @@ public class FileDialog : Dialog
         _btnForward.Text = GetForwardButtonText ();
         _btnToggleSplitterCollapse.Text = GetToggleSplitterText (false);
 
-        if (Style.FlipOkCancelButtonLayoutOrder)
-        {
-            _btnCancel.X = Pos.Func (CalculateOkButtonPosX);
-            _btnOk.X = Pos.Right (_btnCancel) + 1;
-
-            // Flip tab order too for consistency
-            int? p1 = _btnOk.TabIndex;
-            int? p2 = _btnCancel.TabIndex;
-
-            _btnOk.TabIndex = p2;
-            _btnCancel.TabIndex = p1;
-        }
-
         _tbPath.Caption = Style.PathCaption;
         _tbFind.Caption = Style.SearchCaption;
 
@@ -518,8 +460,7 @@ public class FileDialog : Dialog
             };
             AllowedTypeMenuClicked (0);
 
-            _allowedTypeMenuBar.Enter += (s, e) => { _allowedTypeMenuBar.OpenMenu (0); };
-
+            // TODO: Using v1's menu bar here is a hack. Need to upgrade this.
             _allowedTypeMenuBar.DrawContentComplete += (s, e) =>
                                                        {
                                                            _allowedTypeMenuBar.Move (e.NewViewport.Width - 1, 0);
@@ -538,7 +479,7 @@ public class FileDialog : Dialog
         // to streamline user experience and allow direct typing of paths
         // with zero navigation we start with focus in the text box and any
         // default/current path fully selected and ready to be overwritten
-        _tbPath.FocusFirst (null);
+        _tbPath.SetFocus ();
         _tbPath.SelectAll ();
 
         if (string.IsNullOrEmpty (Title))
@@ -546,6 +487,12 @@ public class FileDialog : Dialog
             Title = GetDefaultTitle ();
         }
 
+        if (Style.FlipOkCancelButtonLayoutOrder)
+        {
+            _btnCancel.X = Pos.Func (CalculateOkButtonPosX);
+            _btnOk.X = Pos.Right (_btnCancel) + 1;
+            MoveSubviewTowardsStart (_btnCancel);
+        }
         LayoutSubviews ();
     }
 
@@ -588,7 +535,7 @@ public class FileDialog : Dialog
 
     internal void ApplySort ()
     {
-        FileSystemInfoStats [] stats = State?.Children ?? new FileSystemInfoStats[0];
+        FileSystemInfoStats [] stats = State?.Children ?? new FileSystemInfoStats [0];
 
         // This portion is never reordered (always .. at top then folders)
         IOrderedEnumerable<FileSystemInfoStats> forcedOrder = stats
@@ -1046,23 +993,6 @@ public class FileDialog : Dialog
         return toReturn;
     }
 
-    private bool NavigateIf (Key keyEvent, KeyCode isKey, View to)
-    {
-        if (keyEvent.KeyCode == isKey)
-        {
-            to.FocusFirst (null);
-
-            if (to == _tbPath)
-            {
-                _tbPath.MoveEnd ();
-            }
-
-            return true;
-        }
-
-        return false;
-    }
-
     private void New ()
     {
         if (State is { })
@@ -1277,19 +1207,19 @@ public class FileDialog : Dialog
 
         var contextMenu = new ContextMenu
         {
-            Position = new Point (e.MouseEvent.Position.X + 1, e.MouseEvent.Position.Y + 1),
-            MenuItems = new MenuBarItem (
+            Position = new Point (e.MouseEvent.Position.X + 1, e.MouseEvent.Position.Y + 1)
+        };
+
+        var menuItems = new MenuBarItem (
                                          [
                                              new MenuItem (Strings.fdCtxNew, string.Empty, New),
                                              new MenuItem (Strings.fdCtxRename, string.Empty, Rename),
                                              new MenuItem (Strings.fdCtxDelete, string.Empty, Delete)
                                          ]
-                                        )
-        };
-
+                                        );
         _tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false);
 
-        contextMenu.Show ();
+        contextMenu.Show (menuItems);
     }
 
     private void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e)
@@ -1298,8 +1228,10 @@ public class FileDialog : Dialog
 
         var contextMenu = new ContextMenu
         {
-            Position = new Point (e.MouseEvent.Position.X + 1, e.MouseEvent.Position.Y + 1),
-            MenuItems = new MenuBarItem (
+            Position = new Point (e.MouseEvent.Position.X + 1, e.MouseEvent.Position.Y + 1)
+        };
+
+        var menuItems = new MenuBarItem (
                                          [
                                              new MenuItem (
                                                            string.Format (
@@ -1314,10 +1246,8 @@ public class FileDialog : Dialog
                                                            string.Empty,
                                                            () => SortColumn (clickedCol, isAsc))
                                          ]
-                                        )
-        };
-
-        contextMenu.Show ();
+                                        );
+        contextMenu.Show (menuItems);
     }
 
     private void SortColumn (int clickedCol)
@@ -1435,19 +1365,6 @@ public class FileDialog : Dialog
         }
     }
 
-    private bool TreeView_KeyDown (Key keyEvent)
-    {
-        if (_treeView.HasFocus && Separators.Contains ((char)keyEvent))
-        {
-            _tbPath.FocusFirst (null);
-
-            // let that keystroke go through on the tbPath instead
-            return true;
-        }
-
-        return false;
-    }
-
     private void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
     {
         if (e.NewValue is null)
@@ -1546,7 +1463,7 @@ public class FileDialog : Dialog
         public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : base (dir, parent)
         {
             parent.SearchMatcher.Initialize (searchTerms);
-            Children = new FileSystemInfoStats[0];
+            Children = new FileSystemInfoStats [0];
             BeginSearch ();
         }
 

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

@@ -1,6 +1,4 @@
-using System.Text.Json.Serialization;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
 ///     The FrameView is a container View with a border around it. 
@@ -38,6 +36,5 @@ public class FrameView : View
     ///     <see cref="FrameView"/>s.
     /// </remarks>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
     public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
 }

+ 27 - 0
Terminal.Gui/Views/GBar.cs

@@ -0,0 +1,27 @@
+#nullable enable
+
+using ColorHelper;
+
+namespace Terminal.Gui;
+
+internal class GBar : ColorBar
+{
+    public BBar? BBar { get; set; }
+    public RBar? RBar { get; set; }
+
+    /// <inheritdoc/>
+    protected override Color GetColor (double fraction)
+    {
+        if (RBar == null || BBar == null)
+        {
+            throw new ($"{nameof (GBar)} has not been set up correctly before drawing");
+        }
+
+        var rgb = new RGB ((byte)RBar.Value, (byte)(MaxValue * fraction), (byte)BBar.Value);
+
+        return new (rgb.R, rgb.G, rgb.B);
+    }
+
+    /// <inheritdoc/>
+    protected override int MaxValue => 255;
+}

+ 4 - 0
Terminal.Gui/Views/HexView.cs

@@ -760,6 +760,10 @@ public class HexView : View
 
     private void RedisplayLine (long pos)
     {
+        if (bytesPerLine == 0)
+        {
+            return;
+        }
         var delta = (int)(pos - DisplayStart);
         int line = delta / bytesPerLine;
 

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor