Browse Source

Resolving merge conflicts.

BDisp 1 year ago
parent
commit
4a68fc0ffb
100 changed files with 4106 additions and 2819 deletions
  1. 8 8
      .github/workflows/api-docs.yml
  2. 3 3
      .github/workflows/publish.yml
  3. 5 5
      CONTRIBUTING.md
  4. 1 1
      CommunityToolkitExample/Program.cs
  5. 0 6
      Directory.Build.props
  6. 0 5
      Directory.Build.targets
  7. 21 20
      Example/Example.cs
  8. 3 0
      FSharpExample/FSharpExample.fsproj
  9. 36 442
      FSharpExample/Program.fs
  10. 14 11
      GitVersion.yml
  11. 22 0
      NativeAot/NativeAot.csproj
  12. 113 0
      NativeAot/Program.cs
  13. 18 0
      NativeAot/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Debug.pubxml
  14. 18 0
      NativeAot/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Release.pubxml
  15. 5 0
      NativeAot/Publish_linux-x64_Debug.sh
  16. 5 0
      NativeAot/Publish_linux-x64_Release.sh
  17. 5 0
      NativeAot/Publish_osx-x64_Debug.sh
  18. 5 0
      NativeAot/Publish_osx-x64_Release.sh
  19. 10 0
      NativeAot/README.md
  20. 122 17
      README.md
  21. 0 3
      ReactiveExample/FodyWeavers.xml
  22. 0 26
      ReactiveExample/FodyWeavers.xsd
  23. 126 174
      ReactiveExample/LoginView.cs
  24. 38 43
      ReactiveExample/LoginViewModel.cs
  25. 1 1
      ReactiveExample/Program.cs
  26. 1 1
      ReactiveExample/README.md
  27. 1 1
      ReactiveExample/ReactiveExample.csproj
  28. 24 0
      ReactiveExample/ViewExtensions.cs
  29. 2 2
      Release.ps1
  30. 112 0
      SelfContained/Program.cs
  31. 16 0
      SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_linux-x64_Debug.pubxml
  32. 16 0
      SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_linux-x64_Release.pubxml
  33. 16 0
      SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_osx-x64_Debug.pubxml
  34. 16 0
      SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_osx-x64_Release.pubxml
  35. 17 0
      SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Debug.pubxml
  36. 17 0
      SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Release.pubxml
  37. 9 0
      SelfContained/README.md
  38. 25 0
      SelfContained/SelfContained.csproj
  39. 31 0
      Showcase.md
  40. 29 0
      Terminal.Gui/Application/Application.Driver.cs
  41. 214 0
      Terminal.Gui/Application/Application.Initialization.cs
  42. 462 0
      Terminal.Gui/Application/Application.Keyboard.cs
  43. 28 28
      Terminal.Gui/Application/Application.Mouse.cs
  44. 10 0
      Terminal.Gui/Application/Application.Navigation.cs
  45. 883 0
      Terminal.Gui/Application/Application.Run.cs
  46. 53 0
      Terminal.Gui/Application/Application.Screen.cs
  47. 56 0
      Terminal.Gui/Application/Application.Toplevel.cs
  48. 102 1380
      Terminal.Gui/Application/Application.cs
  49. 0 303
      Terminal.Gui/Application/ApplicationKeyboard.cs
  50. 229 0
      Terminal.Gui/Application/ApplicationNavigation.cs
  51. 444 0
      Terminal.Gui/Application/ApplicationOverlapped.cs
  52. 1 1
      Terminal.Gui/Application/MainLoopSyncContext.cs
  53. 11 22
      Terminal.Gui/Clipboard/Clipboard.cs
  54. 4 4
      Terminal.Gui/Clipboard/IClipboard.cs
  55. 3 3
      Terminal.Gui/Configuration/AttributeJsonConverter.cs
  56. 1 1
      Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs
  57. 23 5
      Terminal.Gui/Configuration/ConfigurationManager.cs
  58. 1 1
      Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs
  59. 3 3
      Terminal.Gui/Configuration/DictionaryJsonConverter.cs
  60. 1 1
      Terminal.Gui/Configuration/KeyCodeJsonConverter.cs
  61. 13 5
      Terminal.Gui/Configuration/ScopeJsonConverter.cs
  62. 1 1
      Terminal.Gui/Configuration/SerializableConfigurationProperty.cs
  63. 10 1
      Terminal.Gui/Configuration/SettingsScope.cs
  64. 23 0
      Terminal.Gui/Configuration/SourceGenerationContext.cs
  65. 114 1
      Terminal.Gui/Configuration/ThemeManager.cs
  66. 120 149
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  67. 1 1
      Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
  68. 1 1
      Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs
  69. 2 2
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  70. 3 1
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs
  71. 44 0
      Terminal.Gui/ConsoleDrivers/CursorVisibility.cs
  72. 1 1
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs
  73. 4 4
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
  74. 2 2
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  75. 4 1
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  76. 35 20
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  77. 2 1
      Terminal.Gui/Drawing/Alignment.cs
  78. 2 1
      Terminal.Gui/Drawing/AlignmentModes.cs
  79. 1 1
      Terminal.Gui/Drawing/Color.Formatting.cs
  80. 1 1
      Terminal.Gui/Drawing/ColorScheme.cs
  81. 3 3
      Terminal.Gui/Drawing/Glyphs.cs
  82. 4 4
      Terminal.Gui/Drawing/LineCanvas.cs
  83. 4 1
      Terminal.Gui/Drawing/LineStyle.cs
  84. 4 4
      Terminal.Gui/Drawing/Ruler.cs
  85. 13 7
      Terminal.Gui/Drawing/Thickness.cs
  86. 2 2
      Terminal.Gui/FileServices/AllowedType.cs
  87. 1 1
      Terminal.Gui/FileServices/FileDialogState.cs
  88. 7 5
      Terminal.Gui/FileServices/FileDialogStyle.cs
  89. 7 4
      Terminal.Gui/Input/Command.cs
  90. 22 5
      Terminal.Gui/Input/Key.cs
  91. 16 0
      Terminal.Gui/Input/KeyBinding.cs
  92. 1 1
      Terminal.Gui/Input/KeyBindingScope.cs
  93. 174 43
      Terminal.Gui/Input/KeyBindings.cs
  94. 1 1
      Terminal.Gui/Input/MouseEventEventArgs.cs
  95. 2 2
      Terminal.Gui/Input/ShortcutHelper.cs
  96. 2 4
      Terminal.Gui/README.md
  97. 10 6
      Terminal.Gui/Resources/config.json
  98. 1 1
      Terminal.Gui/StackExtensions.cs
  99. 5 7
      Terminal.Gui/Terminal.Gui.csproj
  100. 3 3
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs

+ 8 - 8
.github/workflows/api-docs.yml

@@ -2,7 +2,7 @@ name: Build and publish API docs
 
 on:
   push:
-    branches: [main, v2_develop]
+    branches: [v1_release, v2_develop]
 
 permissions:
   id-token: write 
@@ -17,11 +17,11 @@ jobs:
     runs-on: windows-latest
     steps:
     - name: Checkout
-      if: github.ref_name == 'main' ||  github.ref_name == 'develop'
+      #if: github.ref_name == 'v1_release' ||  github.ref_name == 'v1_develop'
       uses: actions/checkout@v4
 
     - name: DocFX Build
-      if: github.ref_name == 'main' ||  github.ref_name == 'develop'
+      #if: github.ref_name == 'v1_release' ||  github.ref_name == 'v1_develop'
       working-directory: docfx
       run: |
         dotnet tool install -g docfx
@@ -31,17 +31,17 @@ jobs:
       continue-on-error: false
 
     - name: Setup Pages
-      if: github.ref_name == 'main' ||  github.ref_name == 'develop'
-      uses: actions/configure-pages@v4
+      #if: github.ref_name == 'v1_release' ||  github.ref_name == 'v1_develop'
+      uses: actions/configure-pages@v5
       
     - name: Upload artifact
-      if: github.ref_name == 'main' ||  github.ref_name == 'develop'
+      #if: github.ref_name == 'v1_release' ||  github.ref_name == 'v1_develop'
       uses: actions/upload-pages-artifact@v3
       with:
         path: docfx/_site
        
     - name: Deploy to GitHub Pages
-      if: github.ref_name == 'main' ||  github.ref_name == 'develop'
+      if: github.ref_name == 'v1_release' ||  github.ref_name == 'v1_develop'
       id: deployment
       uses: actions/deploy-pages@v4
       with:
@@ -49,7 +49,7 @@ jobs:
 
     - name: v2_develop Repository Dispatch ${{ github.ref_name }}
       if: github.ref_name == 'v2_develop'
-      uses: peter-evans/repository-dispatch@v2
+      uses: peter-evans/repository-dispatch@v3
       with:
         token: ${{ secrets.V2DOCS_TOKEN }}
         repository: gui-cs/Terminal.GuiV2Docs

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

@@ -2,7 +2,7 @@ name: Publish Terminal.Gui
 
 on:
   push:
-    branches: [ main, develop, v2_release, v2_develop ]
+    branches: [ v1_release, v1_develop, v2_release, v2_develop ]
     tags:
       - v*
     paths-ignore:
@@ -19,13 +19,13 @@ jobs:
         fetch-depth: 0 # fetch-depth is needed for GitVersion
 
     - name: Install GitVersion 
-      uses: gittools/actions/gitversion/setup@v0
+      uses: gittools/actions/gitversion/setup@v2
       with:
           versionSpec: '5.x'
           includePrerelease: true
 
     - name: Determine Version
-      uses: gittools/actions/gitversion/execute@v1
+      uses: gittools/actions/gitversion/execute@v2
       with:
         useConfigFile: true
         #additionalArguments: /b develop

+ 5 - 5
CONTRIBUTING.md

@@ -10,8 +10,8 @@ We welcome contributions from the community. See [Issues](https://github.com/gui
 
 Terminal.Gui uses the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. 
 
-* The `main` branch is always stable, and always matches the most recently released Nuget package.
-* The `develop` branch is where new development and bug-fixes happen. It is the default branch.
+* The `v1_release_` and `v2_release` branches are always stable, and always matches the most recently released Nuget package.
+* The `v1__develop` and `v2_develop` branches are where new development and bug-fixes happen. `v2_develop` is the default Github branch.
 
 ### Forking Terminal.Gui
 
@@ -33,11 +33,11 @@ You now have your own fork and a local repo that references it as `origin`. Your
 
 ### Starting to Make a Change
 
-Ensure your local `develop` (for v1) or `v2_develop` (for v2) branch is up-to-date with `upstream` (`github.com/gui-cs/Terminal.Gui`):
+Ensure your local `v1_develop` (for v1) or `v2_develop` (for v2) branch is up-to-date with `upstream` (`github.com/gui-cs/Terminal.Gui`):
 ```powershell
 cd ./Terminal.Gui
-git checkout develop
-git pull upstream develop
+git checkout v2_develop
+git pull upstream v2_develop
 ```
 
 Create a new local branch:

+ 1 - 1
CommunityToolkitExample/Program.cs

@@ -12,7 +12,7 @@ public static class Program
         Services = ConfigureServices ();
         Application.Init ();
         Application.Run (Services.GetRequiredService<LoginView> ());
-        Application.Top.Dispose();
+        Application.Top?.Dispose();
         Application.Shutdown ();
     }
 

+ 0 - 6
Directory.Build.props

@@ -1,6 +0,0 @@
-<Project>
-  <ItemGroup>
-    <PackageReference Include="JetBrains.Annotations" Version="[2024,)" PrivateAssets="all" />
-    <PackageReference Include="JetBrains.ExternalAnnotations" Version="[10.2.149,)" PrivateAssets="all" />
-  </ItemGroup>
-</Project>

+ 0 - 5
Directory.Build.targets

@@ -1,5 +0,0 @@
-<Project>
-  <PropertyGroup>
-    <DefineConstants>$(DefineConstants);DIMAUTO</DefineConstants>
-  </PropertyGroup>
-</Project>

+ 21 - 20
Example/Example.cs

@@ -6,19 +6,19 @@
 using System;
 using Terminal.Gui;
 
-var app = Application.Run<ExampleWindow> ();
-
-Console.WriteLine ($"Username: {app.UserNameText.Text}");
-
-app.Dispose ();
+Application.Run<ExampleWindow> ().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 TextField UserNameText;
+    public static string UserName;
 
     public ExampleWindow ()
     {
@@ -27,7 +27,7 @@ public class ExampleWindow : Window
         // Create input components and labels
         var usernameLabel = new Label { Text = "Username:" };
 
-        UserNameText = new TextField
+        var userNameText = new TextField
         {
             // Position text field adjacent to the label
             X = Pos.Right (usernameLabel) + 1,
@@ -46,7 +46,7 @@ public class ExampleWindow : Window
             Secret = true,
 
             // align with the text box above
-            X = Pos.Left (UserNameText),
+            X = Pos.Left (userNameText),
             Y = Pos.Top (passwordLabel),
             Width = Dim.Fill ()
         };
@@ -64,19 +64,20 @@ public class ExampleWindow : Window
 
         // 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");
-                                    Application.RequestStop ();
-                                }
-                                else
-                                {
-                                    MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");
-                                }
-                            };
+                           {
+                               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);
+        Add (usernameLabel, userNameText, passwordLabel, passwordText, btnLogin);
     }
 }

+ 3 - 0
FSharpExample/FSharpExample.fsproj

@@ -13,4 +13,7 @@
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
   </ItemGroup>
+  <ItemGroup>
+    <PackageReference Update="FSharp.Core" Version="8.0.300" />
+  </ItemGroup>
 </Project>

+ 36 - 442
FSharpExample/Program.fs

@@ -1,454 +1,48 @@
-open System
-open System.Diagnostics
-open System.Globalization
-open System.IO
-open NStack
-open Terminal.Gui
+open Terminal.Gui
 
-let ustr (x: string) = ustring.Make(x)
-let mutable ml2 = Unchecked.defaultof<Label>
-let mutable ml = Unchecked.defaultof<Label>
-let mutable menu = Unchecked.defaultof<MenuBar>
-let mutable menuKeysStyle = Unchecked.defaultof<CheckBox>
-let mutable menuAutoMouseNav = Unchecked.defaultof<CheckBox>
-
-type Box10x (x: int, y: int) =
-    inherit View (Rect(x, y, 20, 10))
-    let w = 40
-    let h = 50
-
-    new () =
-        new Box10x ()
-
-    member _.GetContentSize () =
-        Size (w, h)
-
-    member _.SetCursorPosition (_ : Point) =
-        raise (NotImplementedException())
-
-    override this.Redraw (_: Rect) =
-        Application.Driver.SetAttribute this.ColorScheme.Focus
-        do
-        let mutable y = 0
-        while y < h do
-        this.Move (0, y)
-        Application.Driver.AddStr (ustr (string y))
-        do
-        let mutable x = 0
-        while x < w - (y.ToString ()).Length do
-            if (string y).Length < w
-            then Application.Driver.AddStr (ustr " ")
-            x <- x + 1
-        y <- y + 1
-
-type Filler (rect: Rect) =
-    inherit View(rect)
-    new () =
-        new Filler ()
-
-    override this.Redraw (_: Rect) =
-        Application.Driver.SetAttribute this.ColorScheme.Focus
-        let mutable f = this.Frame
-        do
-        let mutable y = 0
-        while y < f.Width do
-        this.Move (0, y)
-        do
-        let mutable x = 0
-        while x < f.Height do
-            let r =
-                match x % 3 with
-                | 0 ->
-                    Application.Driver.AddRune ((Rune ((string y).ToCharArray (0, 1)).[0]))
-                    if y > 9 then
-                        Application.Driver.AddRune ((Rune ((string y).ToCharArray (1, 1)).[0]))
-                    Rune '.'
-                | 1 -> Rune 'o'
-                | _ -> Rune 'O'
-            Application.Driver.AddRune r
-            x <- x + 1
-        y <- y + 1
-
-let ShowTextAlignments () =
-    let okButton = new Button (ustr "Ok", true)
-    okButton.add_Clicked (Action (Application.RequestStop))
-    let cancelButton = new Button (ustr "Cancel", true)
-    cancelButton.add_Clicked (Action (Application.RequestStop))
-
-    let container = new Dialog (ustr "Text Alignments", 50, 20, okButton, cancelButton)
-    let txt = "Hello world, how are you doing today"
-    container.Add (
-        new Label (Rect(0, 1, 40, 3), ustr ((sprintf "%O-%O" 1) txt), TextAlignment = TextAlignment.Left),
-        new Label (Rect(0, 3, 40, 3), ustr ((sprintf "%O-%O" 2) txt), TextAlignment = TextAlignment.Right),
-        new Label (Rect(0, 5, 40, 3), ustr ((sprintf "%O-%O" 3) txt), TextAlignment = TextAlignment.Centered),
-        new Label (Rect(0, 7, 40, 3), ustr ((sprintf "%O-%O" 4) txt), TextAlignment = TextAlignment.Justified))
-    Application.Run container
-
-let ShowEntries (container: View) =
-    let scrollView = 
-        new ScrollView (Rect (50, 10, 20, 8),
-            ContentSize = Size (20, 50),
-            ShowVerticalScrollIndicator = true,
-            ShowHorizontalScrollIndicator = true)
-    scrollView.Add (new Filler (Rect (0, 0, 40, 40)))
-    let scrollView2 = 
-        new ScrollView (Rect (72, 10, 3, 3),
-            ContentSize = Size (100, 100),
-            ShowVerticalScrollIndicator = true,
-            ShowHorizontalScrollIndicator = true)
-    scrollView2.Add (new Box10x (0, 0))
-    let progress = new ProgressBar (Rect(68, 1, 10, 1))
-    let timer = Func<MainLoop, bool> (fun _ ->
-        progress.Pulse ()
-        true)
-
-    Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300.), timer) |> ignore
-
-    let login =
-        new Label (ustr "Login: ",
-            X = Pos.At 3,
-            Y = Pos.At 6)
-    let password =
-        new Label (ustr "Password: ",
-            X = Pos.Left login,
-            Y = Pos.Bottom login + Pos.At 1)
-    let loginText =
-        new TextField (ustr "",
-            X = Pos.Right password,
-            Y = Pos.Top login,
-            Width = Dim.op_Implicit 40)
-    let passText =
-        new TextField (ustr "",
-            Secret = true,
-            X = Pos.Left loginText,
-            Y = Pos.Top password,
-            Width = Dim.Width loginText)
-    let tf = new Button (3, 19, ustr "Ok")
-    container.Add (login, loginText, password, passText,
-        new FrameView (Rect (3, 10, 25, 6), ustr "Options",
-            [| new CheckBox (1, 0, ustr "Remember me")
-               new RadioGroup (1, 2, 
-                [| ustr "_Personal"; ustr "_Company"|])|]),
-        new ListView (Rect (59, 6, 16, 4),
-                [| "First row"
-                   "<>"
-                   "This is a very long row that should overflow what is shown"
-                   "4th"
-                   "There is an empty slot on the second row"
-                   "Whoa"
-                   "This is so cool" |]),
-        scrollView, scrollView2, tf,
-        new Button (10, 19, ustr "Cancel"),
-        new TimeField (3, 20, DateTime.Now.TimeOfDay),
-        new TimeField (23, 20, DateTime.Now.TimeOfDay, true),
-        new DateField (3, 22, DateTime.Now),
-        new DateField (23, 22, DateTime.Now, true),
-        progress,
-        new Label (3, 24, ustr "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar"),
-        menuKeysStyle,
-        menuAutoMouseNav)
-    container.SendSubviewToBack tf
-
-let NewFile () =
-    let okButton = new Button (ustr "Ok", true)
-    okButton.add_Clicked (Action (Application.RequestStop))
-    let cancelButton = new Button (ustr "Cancel", true)
-    cancelButton.add_Clicked (Action (Application.RequestStop))
-
-    let d = new Dialog (ustr "New File", 50, 20, okButton, cancelButton)
-    ml2 <- new Label (1, 1, ustr "Mouse Debug Line")
-    d.Add ml2
-    Application.Run d
-
-let GetFileName () =
-    let mutable fname = Unchecked.defaultof<_>
-    for s in [| "/etc/passwd"; "c:\\windows\\win.ini" |] do
-        if File.Exists s
-        then fname <- s
-    fname
-
-let Editor (top: Toplevel) =
-    let tframe = top.Frame
-    let ntop = new Toplevel(tframe)
-    let menu = 
-        new MenuBar(
-            [| MenuBarItem (ustr "_File",
-                 [| MenuItem (ustr "_Close", ustring.Empty, (fun () -> Application.RequestStop ())) |]);
-                    MenuBarItem (ustr "_Edit",
-                        [| MenuItem (ustr "_Copy", ustring.Empty, Unchecked.defaultof<_>)
-                           MenuItem (ustr "C_ut", ustring.Empty, Unchecked.defaultof<_>)
-                           MenuItem (ustr "_Paste", ustring.Empty, Unchecked.defaultof<_>) |]) |])
-    ntop.Add menu
-    let fname = GetFileName ()
-    let win = 
-        new Window (
-            ustr (if not (isNull fname) then fname else "Untitled"),
-            X = Pos.At 0,
-            Y = Pos.At 1,
-            Width = Dim.Fill (),
-            Height = Dim.Fill ())
-    ntop.Add win
-    let text = new TextView (Rect(0, 0, (tframe.Width - 2), (tframe.Height - 3)))
-    if fname <> Unchecked.defaultof<_>
-    then text.Text <- ustr (File.ReadAllText fname)
-    win.Add text
-    Application.Run ntop
-
-let Quit () =
-    MessageBox.Query (50, 7, ustr "Quit Demo", ustr "Are you sure you want to quit this demo?", ustr "Yes", ustr "No") = 0
-
-let Close () =
-    MessageBox.ErrorQuery (50, 7, ustr "Error", ustr "There is nothing to close", ustr "Ok")
-    |> ignore
-
-let Open () =
-    let d = new OpenDialog (ustr "Open", ustr "Open a file", AllowsMultipleSelection = true)
-    Application.Run d
-    if not d.Canceled
-        then MessageBox.Query (50, 7, ustr "Selected File", ustr (String.Join (", ", d.FilePaths)), ustr "Ok") |> ignore
-
-let ShowHex (top: Toplevel) =
-    let tframe = top.Frame
-    let ntop = new Toplevel (tframe)
-    let menu = 
-        new MenuBar (
-            [| MenuBarItem (ustr "_File",
-                 [| MenuItem (ustr "_Close", ustring.Empty, (fun () -> Application.RequestStop ())) |]) |])
-    ntop.Add menu
-    let win =
-        new Window (ustr "/etc/passwd",
-            X = Pos.At 0,
-            Y = Pos.At 1,
-            Width = Dim.Fill (),
-            Height = Dim.Fill ())
-    ntop.Add win
-    let fname = GetFileName ()
-    let source = File.OpenRead fname
-    let hex = 
-        new HexView (source,
-            X = Pos.At 0,
-            Y = Pos.At 0,
-            Width = Dim.Fill (),
-            Height = Dim.Fill ())
-    win.Add hex
-    Application.Run ntop
-
-type MenuItemDetails () =
-    inherit MenuItem ()
-    new (title: ustring, help: ustring, action: Action) as this =
-        MenuItemDetails ()
-        then
-            this.Title <- title
-            this.Help <- help
-            this.Action <- action
-
-    static member Instance (mi: MenuItem) =
-        (mi.GetMenuItem ()) :?> MenuItemDetails
-
-type MenuItemDelegate = delegate of MenuItemDetails -> MenuItem
-
-let ShowMenuItem (mi: MenuItemDetails) =
-    MessageBox.Query (70, 7, ustr (mi.Title.ToString ()),
-        ustr ((sprintf "%O selected. Is from submenu: %O" (mi.Title.ToString ())) (mi.GetMenuBarItem ())), ustr "Ok")
-    |> ignore
-
-let MenuKeysStyleToggled (_: bool) =
-    menu.UseKeysUpDownAsKeysLeftRight <- menuKeysStyle.Checked
-
-let MenuAutoMouseNavToggled (_: bool) =
-    menu.WantMousePositionReports <- menuAutoMouseNav.Checked
-
-let Copy () =
-    let textField = menu.LastFocused :?> TextField
-    if textField <> Unchecked.defaultof<_> && textField.SelectedLength <> 0
-    then textField.Copy ()
+type ExampleWindow() as this =
+    inherit Window()
+    
+    do
+        this.Title <- sprintf "Example App (%O to quit)" Application.QuitKey
 
-let Cut () =
-    let textField = menu.LastFocused :?> TextField
-    if textField <> Unchecked.defaultof<_> && textField.SelectedLength <> 0
-    then textField.Cut ()
+        // Create input components and labels
+        let usernameLabel = new Label(Text = "Username:")
 
-let Paste () =
-    let textField = menu.LastFocused :?> TextField
-    if textField <> Unchecked.defaultof<_>
-    then textField.Paste ()
+        let userNameText = new TextField(X = Pos.Right(usernameLabel) + Pos.op_Implicit(1), Width = Dim.Fill())
 
-let Help () =
-    MessageBox.Query (50, 7, ustr "Help", ustr "This is a small help\nBe kind.", ustr "Ok")
-    |> ignore
+        let passwordLabel = new Label(Text = "Password:", X = Pos.Left(usernameLabel), Y = Pos.Bottom(usernameLabel) +  Pos.op_Implicit(1))
 
-let Load () =
-    MessageBox.Query (50, 7, ustr "Load", ustr "This is a small load\nBe kind.", ustr "Ok")
-    |> ignore
+        let passwordText = new TextField(Secret = true, X = Pos.Left(userNameText), Y = Pos.Top(passwordLabel), Width = Dim.Fill())
 
-let Save () =
-    MessageBox.Query (50, 7, ustr "Save ", ustr "This is a small save\nBe kind.", ustr "Ok")
-    |> ignore
+        // Create login button
+        let btnLogin = new Button(Text = "Login", Y = Pos.Bottom(passwordLabel) +  Pos.op_Implicit(1), X = Pos.Center(), IsDefault = true)
 
-let ListSelectionDemo (multiple: bool) =
-    let okButton = new Button (ustr "Ok", true)
-    okButton.add_Clicked (Action (Application.RequestStop))
-    let cancelButton = new Button (ustr "Cancel")
-    cancelButton.add_Clicked (Action (Application.RequestStop))
+        // When login button is clicked display a message popup
+        btnLogin.Accept.Add(fun _ ->
+            if userNameText.Text = "admin" && passwordText.Text = "password" then
+                MessageBox.Query("Logging In", "Login Successful", "Ok") |> ignore
+                ExampleWindow.UserName <- userNameText.Text.ToString()
+                Application.RequestStop()
+            else
+                MessageBox.ErrorQuery("Logging In", "Incorrect username or password", "Ok") |> ignore
+        )
 
-    let d = new Dialog (ustr "Selection Demo", 60, 20, okButton, cancelButton)
-    let animals = ResizeArray<_> ()
-    animals.AddRange([| "Alpaca"; "Llama"; "Lion"; "Shark"; "Goat" |])
-    let msg =
-        new Label (ustr "Use space bar or control-t to toggle selection",
-            X = Pos.At 1,
-            Y = Pos.At 1,
-            Width = Dim.Fill () - Dim.op_Implicit 1,
-            Height = Dim.op_Implicit 1)
-    let list =
-        new ListView (animals,
-            X = Pos.At 1,
-            Y = Pos.At 3,
-            Width = Dim.Fill () - Dim.op_Implicit 4,
-            Height = Dim.Fill () - Dim.op_Implicit 4,
-            AllowsMarking = true,
-            AllowsMultipleSelection = multiple)
-    d.Add (msg, list)
-    Application.Run d
-    let mutable result = ""
-    do
-        let mutable i = 0
-        while i < animals.Count do
-        if list.Source.IsMarked i
-        then result <- result + animals.[i] + " "
-        i <- i + 1
-    MessageBox.Query (60, 10, ustr "Selected Animals", ustr (if result = "" then "No animals selected" else result), ustr "Ok") |> ignore
+        // Add the views to the Window
+        this.Add(usernameLabel, userNameText, passwordLabel, passwordText, btnLogin)
 
-let OnKeyDownPressUpDemo () =
-    let closeButton = new Button (ustr "Close")
-    closeButton.add_Clicked (Action (Application.RequestStop))
+    static member val UserName = "" with get, set
 
-    let container = new Dialog (ustr "KeyDown & KeyPress & KeyUp demo", 80, 20, closeButton, Width = Dim.Fill (), Height = Dim.Fill ())
+[<EntryPoint>]
+let main argv =
+    Application.Init()
+    Application.Run<ExampleWindow>().Dispose()
     
-    let list = ResizeArray<_> ()
-    let listView = 
-        new ListView (list,
-            X = Pos.At 0,
-            Y = Pos.At 0,
-            Width = Dim.Fill () - Dim.op_Implicit 1,
-            Height = Dim.Fill () - Dim.op_Implicit 2,
-            ColorScheme = Colors.TopLevel)
-    container.Add (listView)
+    // Before the application exits, reset Terminal.Gui for clean shutdown
+    Application.Shutdown()
     
-    let keyDownPressUp (keyEvent: KeyEvent, updown: string) =
-        match updown with
-        | "Down"
-        | "Up"
-        | "Press" -> 
-            list.Add (keyEvent.ToString ())    
-        | _ -> failwithf "Unknown: %s" updown
-        listView.MoveDown ()
-
-    container.add_KeyDown(Action<View.KeyEventEventArgs> (fun (e: View.KeyEventEventArgs) -> keyDownPressUp (e.KeyEvent, "Down") |> ignore))
-    container.add_KeyPress(Action<View.KeyEventEventArgs> (fun (e: View.KeyEventEventArgs) -> keyDownPressUp (e.KeyEvent, "Press") |> ignore))
-    container.add_KeyUp(Action<View.KeyEventEventArgs> (fun (e: View.KeyEventEventArgs) -> keyDownPressUp (e.KeyEvent, "Up") |> ignore))
-    Application.Run (container)
-
-let Main () =
-    if Debugger.IsAttached then
-        CultureInfo.DefaultThreadCurrentUICulture <- CultureInfo.GetCultureInfo ("en-US")
-    Application.Init()
-    let top = Application.Top
-    let margin = 3
-    let win = 
-        new Window (ustr "Hello",
-            X = Pos.At 1,
-            Y = Pos.At 1,
-            Width = Dim.Fill () - Dim.op_Implicit margin,
-            Height = Dim.Fill () - Dim.op_Implicit margin)
-    let menuItems =
-        [|MenuItemDetails (ustr "F_ind",ustr "", Unchecked.defaultof<_>);
-            MenuItemDetails (ustr "_Replace", ustr "", Unchecked.defaultof<_>);
-            MenuItemDetails (ustr "_Item1", ustr "", Unchecked.defaultof<_>);
-            MenuItemDetails (ustr "_Also From Sub Menu", ustr "", Unchecked.defaultof<_>)|]
-    menuItems.[0].Action <- fun _ -> ShowMenuItem (menuItems.[0])
-    menuItems.[1].Action <- fun _ -> ShowMenuItem (menuItems.[1])
-    menuItems.[2].Action <- fun _ -> ShowMenuItem (menuItems.[2])
-    menuItems.[3].Action <- fun _ -> ShowMenuItem (menuItems.[3])
-    menu <-
-        new MenuBar (
-            [| MenuBarItem(ustr "_File",
-                    [| MenuItem (ustr "Text _Editor Demo", ustring.Empty, (fun () -> Editor top))
-                       MenuItem (ustr "_New", ustr "Creates new file", fun () -> NewFile())
-                       MenuItem (ustr "_Open", ustring.Empty, fun () -> Open())
-                       MenuItem (ustr "_Hex", ustring.Empty, (fun () -> ShowHex top))
-                       MenuItem (ustr "_Close", ustring.Empty, (fun () -> Close()))
-                       MenuItem (ustr "_Disabled", ustring.Empty, (fun () -> ()), (fun () -> false))
-                       Unchecked.defaultof<_>
-                       MenuItem (ustr "_Quit", ustring.Empty, (fun () -> if Quit() then top.Running <- false)) |])
-               MenuBarItem (ustr "_Edit",
-                    [| MenuItem (ustr "_Copy", ustring.Empty, fun () -> Copy())
-                       MenuItem (ustr "C_ut", ustring.Empty, fun () -> Cut())
-                       MenuItem (ustr "_Paste", ustring.Empty, fun () -> Paste())
-                       MenuBarItem (ustr "_Find and Replace",
-                           [| menuItems.[0] :> MenuItem
-                              menuItems.[1] :> MenuItem |]) :> MenuItem
-                       menuItems.[3] :> MenuItem
-                    |])
-               MenuBarItem (ustr "_List Demos", 
-                    [| MenuItem (ustr "Select _Multiple Items", ustring.Empty, (fun () -> ListSelectionDemo true))
-                       MenuItem (ustr "Select _Single Item", ustring.Empty, (fun () -> ListSelectionDemo false)) |])   
-               MenuBarItem (ustr "A_ssorted",
-                    [| MenuItem (ustr "_Show text alignments", ustring.Empty, (fun () -> ShowTextAlignments())) 
-                       MenuItem (ustr "_OnKeyDown/Press/Up", ustring.Empty, (fun () -> OnKeyDownPressUpDemo())) |])
-               MenuBarItem (ustr "_Test Menu and SubMenus",
-                    [| MenuBarItem (ustr "SubMenu1Item_1",
-                            [| MenuBarItem (ustr "SubMenu2Item_1",
-                                    [| MenuBarItem (ustr "SubMenu3Item_1",
-                                            [| menuItems.[2] :> MenuItem |]) :> MenuItem
-                                    |]) :> MenuItem
-                            |]) :> MenuItem
-                    |])
-               MenuBarItem (ustr "_About...", ustr "Demonstrates top-level menu item", (fun () -> MessageBox.ErrorQuery (50, 7, ustr "Error", ustr "This is a demo app for gui.cs", ustr "Ok") |> ignore)) |])
-    menuKeysStyle <- new CheckBox (3, 25, ustr "UseKeysUpDownAsKeysLeftRight", true)
-    menuKeysStyle.add_Toggled (Action<bool> (MenuKeysStyleToggled))
-    menuAutoMouseNav <- new CheckBox (40, 25, ustr "UseMenuAutoNavigation", true)
-    menuAutoMouseNav.add_Toggled (Action<bool> (MenuAutoMouseNavToggled))
-    ShowEntries win
-    let mutable count = 0
-    ml <- new Label (Rect (3, 17, 47, 1), ustr "Mouse: ")
-    Application.RootMouseEvent <- Action<MouseEvent> (
-            fun (me: MouseEvent) ->
-                ml.Text <- ustr (
-                     (((sprintf "Mouse: (%O,%O) - %O %O" me.X) me.Y) me.Flags) (count <- count + 1; count)))
-    let test = new Label (3, 18, ustr "Se iniciará el análisis")
-    win.Add test
-    win.Add ml
-    let drag = new Label (ustr "Drag: ", X = Pos.At 70, Y = Pos.At 24)
-    let dragText = 
-        new TextField (ustr "",
-            X = Pos.Right drag,
-            Y = Pos.Top drag,
-            Width = Dim.op_Implicit 40)
-    let statusBar = new StatusBar ([|
-        StatusItem(Key.F1, ustr "~F1~ Help", Action Help)
-        StatusItem(Key.F2, ustr "~F2~ Load", Action Load)
-        StatusItem(Key.F3, ustr "~F3~ Save", Action Save)
-        StatusItem(Key.Q, ustr "~^Q~ Quit", fun () -> if (Quit()) then top.Running <- false) |])
-    win.Add (drag, dragText)
-    let bottom = new Label (ustr "This should go on the bottom of the same top-level!")
-    win.Add bottom
-    let bottom2 = new Label (ustr "This should go on the bottom of another top-level!")
-    top.Add bottom2
-    top.add_LayoutComplete (Action<View.LayoutEventArgs>
-        (fun e ->
-            bottom.X <- win.X
-            bottom.Y <- Pos.Bottom win - Pos.Top win - Pos.At margin
-            bottom2.X <- Pos.Left win
-            bottom2.Y <- Pos.Bottom win)
-        )
-    top.Add win
-    top.Add (menu, statusBar)
-    Application.Run ()
-    Application.Shutdown ();
-
-module Demo =
-    [<EntryPoint>]
-    let main _ =
-        Main ()
-        0
+    // To see this output on the screen it must be done after shutdown,
+    // which restores the previous screen.
+    printfn "Username: %s" ExampleWindow.UserName
+    
+    0 // return an integer exit code

+ 14 - 11
GitVersion.yml

@@ -2,14 +2,6 @@ mode: ContinuousDeployment
 tag-prefix: '[vV]'
 continuous-delivery-fallback-tag: dev
 branches:
-  develop:
-    mode: ContinuousDeployment
-    tag: dev
-    regex: develop
-    source-branches:
-    - main
-    pre-release-weight: 100
-
   v2_develop:
     mode: ContinuousDeployment
     tag: dev
@@ -25,6 +17,20 @@ branches:
     is-release-branch: true
     source-branches: ['v2_develop']
 
+  v1_develop:
+    mode: ContinuousDeployment
+    tag: dev
+    regex: v1_develop
+    source-branches:
+    - v1_release
+    pre-release-weight: 100
+
+  v1_release:
+    mode: ContinuousDeployment
+    regex: v1_release
+    is-release-branch: true
+    source-branches: ['v1_develop']
+
   pull-request:
     mode: ContinuousDeployment
     tag: PullRequest.{BranchName}
@@ -32,9 +38,6 @@ branches:
     tag-number-pattern: '[/-](?<number>\d+)'
     regex: ^(pull|pull\-requests|pr)[/-]
     source-branches:
-    - develop
-    - main
-    - release
     - v2_develop
     - v2_release
     - feature

+ 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-pre.1788,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>

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

+ 122 - 17
README.md

@@ -1,17 +1,16 @@
 ![Terminal.Gui](https://socialify.git.ci/gui-cs/Terminal.Gui/image?description=1&font=Rokkitt&forks=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2Fgui-cs%2FTerminal.Gui%2Fdevelop%2Fdocfx%2Fimages%2Flogo.png&name=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Auto)
 ![.NET Core](https://github.com/gui-cs/Terminal.Gui/workflows/.NET%20Core/badge.svg?branch=develop)
-![Code scanning - action](https://github.com/gui-cs/Terminal.Gui/workflows/Code%20scanning%20-%20action/badge.svg)
 [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui)
 ![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27/raw/code-coverage.json)
 [![Downloads](https://img.shields.io/nuget/dt/Terminal.Gui)](https://www.nuget.org/packages/Terminal.Gui)
 [![License](https://img.shields.io/github/license/gui-cs/gui.cs.svg)](LICENSE)
 ![Bugs](https://img.shields.io/github/issues/gui-cs/gui.cs/bug)
 
-***The current, stable, release of Terminal.Gui is [v1.x](https://www.nuget.org/packages/Terminal.Gui). It is stable, rich, and broadly used. The team is now focused on designing and building a significant upgrade we're referring to as `v2`. Therefore:***
- * *`v1` is now in maintenance mode, meaning we will accept PRs for v1.x (the `develop` branch) only for issues impacting existing functionality.*
- * *All new development happens on the `v2_develop` branch. See the V2 discussion [here](https://github.com/gui-cs/Terminal.GuiV2Docs/discussions/1940).*
- * *Developers are encouraged to continue building on [v1.x](https://www.nuget.org/packages/Terminal.Gui) until we announce `v2` is stable.*
-
+* The current, stable, release of Terminal.Gui v1 is [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui).
+* The current `prealpha` release of Terminal.Gui v2 can be found on [Nuget](https://www.nuget.org/packages/Terminal.Gui).
+* Developers starting new TUI projects are encouraged to target `v2`. The API is signifcantly changed, and significantly improved. There will be breaking changes in the API before Beta, but the core API is stable.
+* `v1` is in maintenance mode and we will only accept PRs for issues impacting existing functionality.
+ 
 **Terminal.Gui**: A toolkit for building rich console apps for .NET, .NET Core, and Mono that works on Windows, the Mac, and Linux/Unix.
 
 ![Sample app](docfx/images/sample.gif)
@@ -36,23 +35,129 @@ 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))_
+
+See the [`Terminal.Gui/` README](https://github.com/gui-cs/Terminal.Gui/tree/master/Terminal.Gui) for an overview of how the library is structured. 
+
 ## Showcase & Examples
 
-* **[UI Catalog](https://github.com/gui-cs/Terminal.GuiV2Docs/tree/master/UICatalog)** - The UI Catalog project provides an easy to use and extend sample illustrating the capabilities of **Terminal.Gui**. Run `dotnet run --project UICatalog` to run the UI Catalog.
-* **[C# Example](https://github.com/gui-cs/Terminal.GuiV2Docs/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the C# Example.
-* **[F# Example](https://github.com/gui-cs/Terminal.GuiV2Docs/tree/master/FSharpExample)** - An example showing how to build a Terminal.Gui app using F#.
-* **[Reactive Example](https://github.com/gui-cs/Terminal.GuiV2Docs/tree/master/ReactiveExample)** - A sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [Pharmacist](https://github.com/reactiveui/pharmacist) — a tool that converts all events in a NuGet package into observable wrappers.
-* **[PowerShell's `Out-ConsoleGridView`](https://github.com/PowerShell/GraphicalTools)** - `OCGV` sends the output from a command to an interactive table. 
-* **[F7History](https://github.com/gui-cs/F7History)** - Graphical Command History for PowerShell (built on PowerShell's `Out-ConsoleGridView`).
-* **[PoshRedisViewer](https://github.com/En3Tho/PoshRedisViewer)** - A compact Redis viewer module for PowerShell written in F#.
-* **[PoshDotnetDumpAnalyzeViewer](https://github.com/En3Tho/PoshDotnetDumpAnalyzeViewer)** - dotnet-dump UI module for PowerShell.
-* **[TerminalGuiDesigner](https://github.com/tznind/TerminalGuiDesigner)** - Cross platform view designer for building Terminal.Gui applications.
+**Terminal.Gui** can be used with any .Net language to create feature rich and robust applications.  
+[Showcase](https://github.com/gui-cs/Terminal.Gui/blob/develop/Showcase.md) is a place where you can find all kind of projects from simple examples to advanced real world apps that fully utilize capabilities of the toolkit.  
+The team is looking forward to seeing new amazing projects made by the community to be added there!
+
+## Sample Usage in C#
+
+The following example shows a basic Terminal.Gui application in C#:
+
+```csharp
+// This is a simple example application.  For the full range of functionality
+// see the UICatalog project
+
+// A simple Terminal.Gui example in C# - using C# 9.0 Top-level statements
+
+using System;
+using Terminal.Gui;
+
+Application.Run<ExampleWindow> ().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);
+    }
+}
+```
+
+When run the application looks as follows:
+
+![Simple Usage app](./docfx/images/Example.png)
+
+## Installing
+
+Use NuGet to install the `Terminal.Gui` NuGet package: https://www.nuget.org/packages/Terminal.Gui
+
+### Installation in .NET Core Projects
+
+To install Terminal.Gui into a .NET Core project, use the `dotnet` CLI tool with this command.
+
+```
+dotnet add package Terminal.Gui
+```
+
+Or, you can use the [Terminal.Gui.Templates](https://github.com/gui-cs/Terminal.Gui.templates).
 
 ## Contributing
 
-See [CONTRIBUTING.md](https://github.com/gui-cs/Terminal.GuiV2Docs/blob/master/CONTRIBUTING.md).
+See [CONTRIBUTING.md](./CONTRIBUTING.md).
 
-Debates on architecture and design can be found in Issues tagged with [design](https://github.com/gui-cs/Terminal.GuiV2Docs/issues?q=is%3Aopen+is%3Aissue+label%3Adesign).
+Debates on architecture and design can be found in Issues tagged with [design](https://github.com/gui-cs/Terminal.Gui/issues?q=is%3Aopen+is%3Aissue+label%3Av2+label%3Adesign).
 
 ## History
 

+ 0 - 3
ReactiveExample/FodyWeavers.xml

@@ -1,3 +0,0 @@
-<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
-  <ReactiveUI />
-</Weavers>

+ 0 - 26
ReactiveExample/FodyWeavers.xsd

@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
-  <!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
-  <xs:element name="Weavers">
-    <xs:complexType>
-      <xs:all>
-        <xs:element name="ReactiveUI" minOccurs="0" maxOccurs="1" type="xs:anyType" />
-      </xs:all>
-      <xs:attribute name="VerifyAssembly" type="xs:boolean">
-        <xs:annotation>
-          <xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
-        </xs:annotation>
-      </xs:attribute>
-      <xs:attribute name="VerifyIgnoreCodes" type="xs:string">
-        <xs:annotation>
-          <xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
-        </xs:annotation>
-      </xs:attribute>
-      <xs:attribute name="GenerateXsd" type="xs:boolean">
-        <xs:annotation>
-          <xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
-        </xs:annotation>
-      </xs:attribute>
-    </xs:complexType>
-  </xs:element>
-</xs:schema>

+ 126 - 174
ReactiveExample/LoginView.cs

@@ -8,20 +8,137 @@ namespace ReactiveExample;
 
 public class LoginView : Window, IViewFor<LoginViewModel>
 {
-    private readonly CompositeDisposable _disposable = new ();
+    private const string SuccessMessage = "The input is valid!";
+    private const string ErrorMessage = "Please enter a valid user name and password.";
+    private const string ProgressMessage = "Logging in...";
+    private const string IdleMessage = "Press 'Login' to log in.";
+
+    private readonly CompositeDisposable _disposable = [];
 
     public LoginView (LoginViewModel viewModel)
     {
         Title = $"Reactive Extensions Example - {Application.QuitKey} to Exit";
         ViewModel = viewModel;
-        Label usernameLengthLabel = UsernameLengthLabel (TitleLabel ());
-        TextField usernameInput = UsernameInput (usernameLengthLabel);
-        Label passwordLengthLabel = PasswordLengthLabel (usernameLengthLabel);
-        TextField passwordInput = PasswordInput (passwordLengthLabel);
-        Label validationLabel = ValidationLabel (passwordInput);
-        Button loginButton = LoginButton (validationLabel);
-        Button clearButton = ClearButton (loginButton);
-        LoginProgressLabel (clearButton);
+        var title = this.AddControl<Label> (x => x.Text = "Login Form");
+        var unLengthLabel = title.AddControlAfter<Label> ((previous, unLength) =>
+            {
+                unLength.X = Pos.Left (previous);
+                unLength.Y = Pos.Top (previous) + 1;
+
+                ViewModel
+                    .WhenAnyValue (x => x.UsernameLength)
+                    .Select (length => $"_Username ({length} characters):")
+                    .BindTo (unLength, x => x.Text)
+                    .DisposeWith (_disposable);
+            });
+        unLengthLabel.AddControlAfter<TextField> ((previous, unInput) =>
+            {
+                unInput.X = Pos.Right (previous) + 1;
+                unInput.Y = Pos.Top (previous);
+                unInput.Width = 40;
+                unInput.Text = ViewModel.Username;
+
+                ViewModel
+                    .WhenAnyValue (x => x.Username)
+                    .BindTo (unInput, x => x.Text)
+                    .DisposeWith (_disposable);
+
+                unInput
+                    .Events ()
+                    .TextChanged
+                    .Select (_ => unInput.Text)
+                    .DistinctUntilChanged ()
+                    .BindTo (ViewModel, x => x.Username)
+                    .DisposeWith (_disposable);
+            });
+        unLengthLabel.AddControlAfter<Label> ((previous, pwLength) =>
+            {
+                pwLength.X = Pos.Left (previous);
+                pwLength.Y = Pos.Top (previous) + 1;
+
+                ViewModel
+                    .WhenAnyValue (x => x.PasswordLength)
+                    .Select (length => $"_Password ({length} characters):")
+                    .BindTo (pwLength, x => x.Text)
+                    .DisposeWith (_disposable);
+            })
+            .AddControlAfter<TextField> ((previous, pwInput) =>
+            {
+                pwInput.X = Pos.Right (previous) + 1;
+                pwInput.Y = Pos.Top (previous);
+                pwInput.Width = 40;
+                pwInput.Text = ViewModel.Password;
+
+                ViewModel
+                    .WhenAnyValue (x => x.Password)
+                    .BindTo (pwInput, x => x.Text)
+                    .DisposeWith (_disposable);
+
+                pwInput
+                    .Events ()
+                    .TextChanged
+                    .Select (_ => pwInput.Text)
+                    .DistinctUntilChanged ()
+                    .BindTo (ViewModel, x => x.Password)
+                    .DisposeWith (_disposable);
+            })
+            .AddControlAfter<Label> ((previous, validation) =>
+            {
+                validation.X = Pos.Left (previous);
+                validation.Y = Pos.Top (previous) + 1;
+                validation.Text = ErrorMessage;
+
+                ViewModel
+                    .WhenAnyValue (x => x.IsValid)
+                    .Select (valid => valid ? SuccessMessage : ErrorMessage)
+                    .BindTo (validation, x => x.Text)
+                    .DisposeWith (_disposable);
+
+                ViewModel
+                    .WhenAnyValue (x => x.IsValid)
+                    .Select (valid => valid ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"])
+                    .BindTo (validation, x => x.ColorScheme)
+                    .DisposeWith (_disposable);
+            })
+            .AddControlAfter<Button> ((previous, login) =>
+            {
+                login.X = Pos.Left (previous);
+                login.Y = Pos.Top (previous) + 1;
+                login.Text = "_Login";
+
+                login
+                    .Events ()
+                    .Accept
+                    .InvokeCommand (ViewModel, x => x.Login)
+                    .DisposeWith (_disposable);
+            })
+            .AddControlAfter<Button> ((previous, clear) =>
+            {
+                clear.X = Pos.Left (previous);
+                clear.Y = Pos.Top (previous) + 1;
+                clear.Text = "_Clear";
+
+                clear
+                    .Events ()
+                    .Accept
+                    .InvokeCommand (ViewModel, x => x.ClearCommand)
+                    .DisposeWith (_disposable);
+            })
+            .AddControlAfter<Label> ((previous, progress) =>
+            {
+                progress.X = Pos.Left (previous);
+                progress.Y = Pos.Top (previous) + 1;
+                progress.Width = 40;
+                progress.Height = 1;
+                progress.Text = IdleMessage;
+
+                ViewModel
+                    .WhenAnyObservable (x => x.Login.IsExecuting)
+                    .Select (executing => executing ? ProgressMessage : IdleMessage)
+                    .ObserveOn (RxApp.MainThreadScheduler)
+                    .BindTo (progress, x => x.Text)
+                    .DisposeWith (_disposable);
+            });
     }
 
     public LoginViewModel ViewModel { get; set; }
@@ -37,169 +154,4 @@ public class LoginView : Window, IViewFor<LoginViewModel>
         _disposable.Dispose ();
         base.Dispose (disposing);
     }
-
-    private Button ClearButton (View previous)
-    {
-        var clearButton = new Button
-        {
-            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Text = "_Clear"
-        };
-
-        clearButton
-            .Events ()
-            .Accept
-            .InvokeCommand (ViewModel, x => x.Clear)
-            .DisposeWith (_disposable);
-        Add (clearButton);
-
-        return clearButton;
-    }
-
-    private Button LoginButton (View previous)
-    {
-        var loginButton = new Button
-        {
-            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Text = "_Login"
-        };
-
-        loginButton
-            .Events ()
-            .Accept
-            .InvokeCommand (ViewModel, x => x.Login)
-            .DisposeWith (_disposable);
-        Add (loginButton);
-
-        return loginButton;
-    }
-
-    private Label LoginProgressLabel (View previous)
-    {
-        var progress = "Logging in...";
-        var idle = "Press 'Login' to log in.";
-
-        var loginProgressLabel = new Label
-        {
-            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Width = 40, Height = 1, Text = idle
-        };
-
-        ViewModel
-            .WhenAnyObservable (x => x.Login.IsExecuting)
-            .Select (executing => executing ? progress : idle)
-            .ObserveOn (RxApp.MainThreadScheduler)
-            .BindTo (loginProgressLabel, x => x.Text)
-            .DisposeWith (_disposable);
-        Add (loginProgressLabel);
-
-        return loginProgressLabel;
-    }
-
-    private TextField PasswordInput (View previous)
-    {
-        var passwordInput = new TextField
-        {
-            X = Pos.Right (previous) + 1, Y = Pos.Top (previous), Width = 40, Text = ViewModel.Password
-        };
-
-        ViewModel
-            .WhenAnyValue (x => x.Password)
-            .BindTo (passwordInput, x => x.Text)
-            .DisposeWith (_disposable);
-
-        passwordInput
-            .Events ()
-            .TextChanged
-            .Select (old => passwordInput.Text)
-            .DistinctUntilChanged ()
-            .BindTo (ViewModel, x => x.Password)
-            .DisposeWith (_disposable);
-        Add (passwordInput);
-
-        return passwordInput;
-    }
-
-    private Label PasswordLengthLabel (View previous)
-    {
-        var passwordLengthLabel = new Label { X = Pos.Left (previous), Y = Pos.Top (previous) + 1, };
-
-        ViewModel
-            .WhenAnyValue (x => x.PasswordLength)
-            .Select (length => $"_Password ({length} characters):")
-            .BindTo (passwordLengthLabel, x => x.Text)
-            .DisposeWith (_disposable);
-        Add (passwordLengthLabel);
-
-        return passwordLengthLabel;
-    }
-
-    private Label TitleLabel ()
-    {
-        var label = new Label { Text = "Login Form" };
-        Add (label);
-
-        return label;
-    }
-
-    private TextField UsernameInput (View previous)
-    {
-        var usernameInput = new TextField
-        {
-            X = Pos.Right (previous) + 1, Y = Pos.Top (previous), Width = 40, Text = ViewModel.Username
-        };
-
-        ViewModel
-            .WhenAnyValue (x => x.Username)
-            .BindTo (usernameInput, x => x.Text)
-            .DisposeWith (_disposable);
-
-        usernameInput
-            .Events ()
-            .TextChanged
-            .Select (old => usernameInput.Text)
-            .DistinctUntilChanged ()
-            .BindTo (ViewModel, x => x.Username)
-            .DisposeWith (_disposable);
-        Add (usernameInput);
-
-        return usernameInput;
-    }
-
-    private Label UsernameLengthLabel (View previous)
-    {
-        var usernameLengthLabel = new Label { X = Pos.Left (previous), Y = Pos.Top (previous) + 1 };
-
-        ViewModel
-            .WhenAnyValue (x => x.UsernameLength)
-            .Select (length => $"_Username ({length} characters):")
-            .BindTo (usernameLengthLabel, x => x.Text)
-            .DisposeWith (_disposable);
-        Add (usernameLengthLabel);
-
-        return usernameLengthLabel;
-    }
-
-    private Label ValidationLabel (View previous)
-    {
-        var error = "Please enter a valid user name and password.";
-        var success = "The input is valid!";
-
-        var validationLabel = new Label
-        {
-           X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Text = error
-        };
-
-        ViewModel
-            .WhenAnyValue (x => x.IsValid)
-            .Select (valid => valid ? success : error)
-            .BindTo (validationLabel, x => x.Text)
-            .DisposeWith (_disposable);
-
-        ViewModel
-            .WhenAnyValue (x => x.IsValid)
-            .Select (valid => valid ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"])
-            .BindTo (validationLabel, x => x.ColorScheme)
-            .DisposeWith (_disposable);
-        Add (validationLabel);
-
-        return validationLabel;
-    }
 }

+ 38 - 43
ReactiveExample/LoginViewModel.cs

@@ -5,7 +5,7 @@ using System.Reactive.Linq;
 using System.Runtime.Serialization;
 using System.Threading.Tasks;
 using ReactiveUI;
-using ReactiveUI.Fody.Helpers;
+using ReactiveUI.SourceGenerators;
 
 namespace ReactiveExample;
 
@@ -21,41 +21,53 @@ namespace ReactiveExample;
 // See also: https://www.reactiveui.net../docs/handbook/data-persistence/
 //
 [DataContract]
-public class LoginViewModel : ReactiveObject
+public partial class LoginViewModel : ReactiveObject
 {
-    private readonly ObservableAsPropertyHelper<bool> _isValid;
-    private readonly ObservableAsPropertyHelper<int> _passwordLength;
-    private readonly ObservableAsPropertyHelper<int> _usernameLength;
+    [IgnoreDataMember]
+    [ObservableAsProperty] private bool _isValid;
 
-    public LoginViewModel ()
-    {
-        IObservable<bool> canLogin = this.WhenAnyValue (
-                                                        x => x.Username,
-                                                        x => x.Password,
-                                                        (username, password) =>
-                                                            !string.IsNullOrEmpty (username) && !string.IsNullOrEmpty (password)
-                                                       );
+    [IgnoreDataMember]
+    [ObservableAsProperty] private int _passwordLength;
 
-        _isValid = canLogin.ToProperty (this, x => x.IsValid);
+    [IgnoreDataMember]
+    [ObservableAsProperty] private int _usernameLength;
 
-        Login = ReactiveCommand.CreateFromTask<CancelEventArgs> (
-                                                                 e => Task.Delay (TimeSpan.FromSeconds (1)),
-                                                                 canLogin
-                                                                );
+    [DataMember]
+    [Reactive] private string _password = string.Empty;
 
-        _usernameLength = this
+    [DataMember]
+    [Reactive] private string _username = string.Empty;
+
+    public LoginViewModel ()
+    {
+        InitializeCommands ();
+        IObservable<bool> canLogin = this.WhenAnyValue
+            (
+                x => x.Username,
+                x => x.Password,
+                (username, password) =>
+                    !string.IsNullOrEmpty (username) && !string.IsNullOrEmpty (password)
+            );
+
+        _isValidHelper = canLogin.ToProperty (this, x => x.IsValid);
+
+        Login = ReactiveCommand.CreateFromTask<HandledEventArgs>
+            (
+                e => Task.Delay (TimeSpan.FromSeconds (1)),
+                canLogin
+            );
+
+        _usernameLengthHelper = this
                           .WhenAnyValue (x => x.Username)
                           .Select (name => name.Length)
                           .ToProperty (this, x => x.UsernameLength);
 
-        _passwordLength = this
+        _passwordLengthHelper = this
                           .WhenAnyValue (x => x.Password)
                           .Select (password => password.Length)
                           .ToProperty (this, x => x.PasswordLength);
 
-        Clear = ReactiveCommand.Create<CancelEventArgs> (e => { });
-
-        Clear.Subscribe (
+        ClearCommand.Subscribe (
                          unit =>
                          {
                              Username = string.Empty;
@@ -64,26 +76,9 @@ public class LoginViewModel : ReactiveObject
                         );
     }
 
-    [IgnoreDataMember]
-    public ReactiveCommand<CancelEventArgs, Unit> Clear { get; }
-
-    [IgnoreDataMember]
-    public bool IsValid => _isValid.Value;
-
-    [IgnoreDataMember]
-    public ReactiveCommand<CancelEventArgs, Unit> Login { get; }
-
-    [Reactive]
-    [DataMember]
-    public string Password { get; set; } = string.Empty;
-
-    [IgnoreDataMember]
-    public int PasswordLength => _passwordLength.Value;
-
-    [Reactive]
-    [DataMember]
-    public string Username { get; set; } = string.Empty;
+    [ReactiveCommand]
+    public void Clear (HandledEventArgs args) { }
 
     [IgnoreDataMember]
-    public int UsernameLength => _usernameLength.Value;
+    public ReactiveCommand<HandledEventArgs, Unit> Login { get; }
 }

+ 1 - 1
ReactiveExample/Program.cs

@@ -12,7 +12,7 @@ public static class Program
         RxApp.MainThreadScheduler = TerminalScheduler.Default;
         RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
         Application.Run (new LoginView (new LoginViewModel ()));
-        Application.Top.Dispose();
+        Application.Top.Dispose ();
         Application.Shutdown ();
     }
 }

+ 1 - 1
ReactiveExample/README.md

@@ -1,4 +1,4 @@
-This is a sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [Pharmacist](https://github.com/reactiveui/pharmacist) — a tool that converts all events in a NuGet package into observable wrappers.
+This is a sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [ObservableEvents](https://github.com/reactivemarbles/ObservableEvents) — a Source Generator that turns events into observable wrappers.
 
 <img src="https://user-images.githubusercontent.com/6759207/94748621-646a7280-038a-11eb-8ea0-34629dc799b3.gif" width="450">
 

+ 1 - 1
ReactiveExample/ReactiveExample.csproj

@@ -11,9 +11,9 @@
     <InformationalVersion>2.0</InformationalVersion>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="ReactiveUI.Fody" Version="[19.5.41,21)" />
     <PackageReference Include="ReactiveUI" Version="[20.1.1,21)" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="[1.3.1,2)" PrivateAssets="all" />
+    <PackageReference Include="ReactiveUI.SourceGenerators" Version="[1.0.3,2)" PrivateAssets="all" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />

+ 24 - 0
ReactiveExample/ViewExtensions.cs

@@ -0,0 +1,24 @@
+using System;
+using Terminal.Gui;
+
+namespace ReactiveExample;
+public static class ViewExtensions
+{
+    public static (Window MainView, TOut LastControl) AddControl<TOut> (this Window view, Action<TOut> action)
+        where TOut : View, new()
+    {
+        TOut result = new ();
+        action (result);
+        view.Add (result);
+        return (view, result);
+    }
+
+    public static (Window MainView, TOut LastControl) AddControlAfter<TOut> (this (Window MainView, View LastControl) view, Action<View, TOut> action)
+        where TOut : View, new()
+    {
+        TOut result = new ();
+        action (view.LastControl, result);
+        view.MainView.Add (result);
+        return (view.MainView, result);
+    }
+}

+ 2 - 2
Release.ps1

@@ -5,8 +5,8 @@ param(
     [int]$Version
 )
 
-$branch = "v2_develop"
-$tag = "v2.0.0-alpha.$Version"
+$branch = "v2_release"
+$tag = "$Version-prealpha"
 $releaseMessage = "Release $tag"
 
 try {

+ 112 - 0
SelfContained/Program.cs

@@ -0,0 +1,112 @@
+// This is a test application for a self-contained single file.
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Terminal.Gui;
+
+namespace SelfContained;
+
+public static class Program
+{
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Run<T>(Func<Exception, Boolean>, ConsoleDriver)")]
+    private static void Main (string [] args)
+    {
+        Application.Init ();
+
+        #region The code in this region is not intended for use in a self-contained single-file. It's just here to make sure there is no functionality break with localization in Terminal.Gui using single-file
+
+        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);
+    }
+}

+ 16 - 0
SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_linux-x64_Debug.pubxml

@@ -0,0 +1,16 @@
+<?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\linux-x64\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+    <TargetFramework>net8.0</TargetFramework>
+    <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
+    <SelfContained>true</SelfContained>
+  </PropertyGroup>
+</Project>

+ 16 - 0
SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_linux-x64_Release.pubxml

@@ -0,0 +1,16 @@
+<?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\linux-x64\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+    <TargetFramework>net8.0</TargetFramework>
+    <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
+    <SelfContained>true</SelfContained>
+  </PropertyGroup>
+</Project>

+ 16 - 0
SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_osx-x64_Debug.pubxml

@@ -0,0 +1,16 @@
+<?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\osx-x64\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+    <TargetFramework>net8.0</TargetFramework>
+    <RuntimeIdentifier>osx-x64</RuntimeIdentifier>
+    <SelfContained>true</SelfContained>
+  </PropertyGroup>
+</Project>

+ 16 - 0
SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_osx-x64_Release.pubxml

@@ -0,0 +1,16 @@
+<?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\osx-x64\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+    <TargetFramework>net8.0</TargetFramework>
+    <RuntimeIdentifier>osx-x64</RuntimeIdentifier>
+    <SelfContained>true</SelfContained>
+  </PropertyGroup>
+</Project>

+ 17 - 0
SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Debug.pubxml

@@ -0,0 +1,17 @@
+<?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>
+    <PublishReadyToRun>false</PublishReadyToRun>
+  </PropertyGroup>
+</Project>

+ 17 - 0
SelfContained/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Release.pubxml

@@ -0,0 +1,17 @@
+<?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>
+    <PublishReadyToRun>false</PublishReadyToRun>
+  </PropertyGroup>
+</Project>

+ 9 - 0
SelfContained/README.md

@@ -0,0 +1,9 @@
+# Terminal.Gui C# SelfContained
+
+This project aims to test the `Terminal.Gui` library to create a simple `self-contained` `single-file` 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`.
+
+To publish the self-contained single 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`.
+
+When executing the file directly from the self-contained single file and needing to debug it, it will be necessary to attach it to the debugger, just like any other standalone application. However, when trying to attach the file running on `Linux` or `macOS` to the debugger, it will issue the error "`Failed to attach to process: Unknown Error: 0x80131c3c`". This issue has already been reported on [Developer Community](https://developercommunity.visualstudio.com/t/Failed-to-attach-to-process:-Unknown-Err/10694351). Maybe it would be a good idea to vote in favor of this fix because I think `Visual Studio for macOS` is going to be discontinued and we need this fix to remotely attach a process running on `Linux` or `macOS` to `Windows 11`.

+ 25 - 0
SelfContained/SelfContained.csproj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <PublishTrimmed>true</PublishTrimmed>
+    <TrimMode>Link</TrimMode>
+    <PublishSingleFile>true</PublishSingleFile>
+    <InvariantGlobalization>false</InvariantGlobalization>
+    <DebugType>embedded</DebugType>
+  </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-pre.1788,3)" />
+    <TrimmerRootAssembly Include="Terminal.Gui" />
+  </ItemGroup>
+
+</Project>

+ 31 - 0
Showcase.md

@@ -0,0 +1,31 @@
+# Showcase #
+
+* **[UI Catalog](https://github.com/gui-cs/Terminal.Gui/tree/master/UICatalog)** - The UI Catalog project provides an easy to use and extend sample illustrating the capabilities of **Terminal.Gui**. Run `dotnet run --project UICatalog` to run the UI Catalog.
+  ![Sample app](docfx/images/sample.gif)  
+  ⠀
+* **[PowerShell's `Out-ConsoleGridView`](https://github.com/PowerShell/GraphicalTools)** - `OCGV` sends the output from a command to an interactive table.
+  ![OutConsoleGridView.png](docfx/images/OutConsoleGridView.png)  
+  ⠀
+* **[F7History](https://github.com/gui-cs/F7History)** - Graphical Command History for PowerShell (built on PowerShell's `Out-ConsoleGridView`).
+  ![F7History.gif](docfx/images/F7History.gif)  
+  ⠀
+* **[PoshRedisViewer](https://github.com/En3Tho/PoshRedisViewer)** - A compact Redis viewer module for PowerShell written in F#.
+  ![PoshRedisViewer.png](docfx/images/PoshRedisViewer.png)  
+  ⠀
+* **[PoshDotnetDumpAnalyzeViewer](https://github.com/En3Tho/PoshDotnetDumpAnalyzeViewer)** - dotnet-dump UI module for PowerShell.
+  ![PoshDotnetDumpAnalyzerViewer.png](docfx/images/PoshDotnetDumpAnalyzerViewer.png)  
+  ⠀
+* **[TerminalGuiDesigner](https://github.com/tznind/TerminalGuiDesigner)** - Cross platform view designer for building Terminal.Gui applications.
+  ![TerminalGuiDesigner.gif](docfx/images/TerminalGuiDesigner.gif)
+
+* **[Capital and Cargo](https://github.com/dhorions/Capital-and-Cargo)** - A retro console game where you buy, sell, produce and transport goods built with Terminal.Gui
+ ![image](https://github.com/gui-cs/Terminal.Gui/assets/1682004/ed89f3d6-020f-4a8a-ae18-e057514f4c43)
+
+  
+# Examples #
+
+* **[C# Example](https://github.com/gui-cs/Terminal.Gui/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the C# Example.
+
+* **[F# Example](https://github.com/gui-cs/Terminal.Gui/tree/master/FSharpExample)** - An example showing how to build a Terminal.Gui app using F#.
+
+* **[Reactive Example](https://github.com/gui-cs/Terminal.Gui/tree/master/ReactiveExample)** - A sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [Pharmacist](https://github.com/reactiveui/pharmacist) — a tool that converts all events in a NuGet package into observable wrappers. 

+ 29 - 0
Terminal.Gui/Application/Application.Driver.cs

@@ -0,0 +1,29 @@
+#nullable enable
+namespace Terminal.Gui;
+
+public static partial class Application // Driver abstractions
+{
+    internal static bool _forceFakeConsole;
+
+    /// <summary>Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.</summary>
+    public static ConsoleDriver? Driver { get; internal set; }
+
+    /// <summary>
+    ///     Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in
+    ///     <see cref="ColorName"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be output
+    ///     as long as the selected <see cref="ConsoleDriver"/> supports TrueColor.
+    /// </summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static bool Force16Colors { get; set; }
+
+    /// <summary>
+    ///     Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If not
+    ///     specified, the driver is selected based on the platform.
+    /// </summary>
+    /// <remarks>
+    ///     Note, <see cref="Application.Init(ConsoleDriver, string)"/> will override this configuration setting if called
+    ///     with either `driver` or `driverName` specified.
+    /// </remarks>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static string ForceDriver { get; set; } = string.Empty;
+}

+ 214 - 0
Terminal.Gui/Application/Application.Initialization.cs

@@ -0,0 +1,214 @@
+#nullable enable
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+
+namespace Terminal.Gui;
+
+public static partial class Application // Initialization (Init/Shutdown)
+{
+    /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
+    /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
+    /// <para>
+    ///     This function loads the right <see cref="ConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
+    ///     assigns it to <see cref="Top"/>
+    /// </para>
+    /// <para>
+    ///     <see cref="Shutdown"/> must be called when the application is closing (typically after
+    ///     <see cref="Run{T}"/> has returned) to ensure resources are cleaned up and
+    ///     terminal settings
+    ///     restored.
+    /// </para>
+    /// <para>
+    ///     The <see cref="Run{T}"/> function combines
+    ///     <see cref="Init(Terminal.Gui.ConsoleDriver,string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
+    ///     into a single
+    ///     call. An application cam use <see cref="Run{T}"/> without explicitly calling
+    ///     <see cref="Init(Terminal.Gui.ConsoleDriver,string)"/>.
+    /// </para>
+    /// <param name="driver">
+    ///     The <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or
+    ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
+    /// </param>
+    /// <param name="driverName">
+    ///     The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the
+    ///     <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
+    ///     specified the default driver for the platform will be used.
+    /// </param>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public static void Init (ConsoleDriver? driver = null, string? driverName = null) { InternalInit (driver, driverName); }
+
+    internal static bool IsInitialized { get; set; }
+    internal static int MainThreadId { get; set; } = -1;
+
+    // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop.
+    //
+    // Called from:
+    //
+    // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset.
+    // Run<T>() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run<T>() to be called without calling Init first.
+    // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset.
+    //
+    // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset.
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    internal static void InternalInit (
+        ConsoleDriver? driver = null,
+        string? driverName = null,
+        bool calledViaRunT = false
+    )
+    {
+        if (IsInitialized && driver is null)
+        {
+            return;
+        }
+
+        if (IsInitialized)
+        {
+            throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown.");
+        }
+
+        if (!calledViaRunT)
+        {
+            // Reset all class variables (Application is a singleton).
+            ResetState ();
+        }
+
+        Navigation = new ();
+
+        // For UnitTests
+        if (driver is { })
+        {
+            Driver = driver;
+        }
+
+        // Start the process of configuration management.
+        // Note that we end up calling LoadConfigurationFromAllSources
+        // multiple times. We need to do this because some settings are only
+        // valid after a Driver is loaded. In this case we need just
+        // `Settings` so we can determine which driver to use.
+        // Don't reset, so we can inherit the theme from the previous run.
+        Load ();
+        Apply ();
+
+        AddApplicationKeyBindings ();
+
+        // Ignore Configuration for ForceDriver if driverName is specified
+        if (!string.IsNullOrEmpty (driverName))
+        {
+            ForceDriver = driverName;
+        }
+
+        if (Driver is null)
+        {
+            PlatformID p = Environment.OSVersion.Platform;
+
+            if (string.IsNullOrEmpty (ForceDriver))
+            {
+                if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+                {
+                    Driver = new WindowsDriver ();
+                }
+                else
+                {
+                    Driver = new CursesDriver ();
+                }
+            }
+            else
+            {
+                List<Type?> drivers = GetDriverTypes ();
+                Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase));
+
+                if (driverType is { })
+                {
+                    Driver = (ConsoleDriver)Activator.CreateInstance (driverType)!;
+                }
+                else
+                {
+                    throw new ArgumentException (
+                                                 $"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t!.Name))}"
+                                                );
+                }
+            }
+        }
+
+        try
+        {
+            MainLoop = Driver!.Init ();
+        }
+        catch (InvalidOperationException ex)
+        {
+            // This is a case where the driver is unable to initialize the console.
+            // This can happen if the console is already in use by another process or
+            // if running in unit tests.
+            // In this case, we want to throw a more specific exception.
+            throw new InvalidOperationException (
+                                                 "Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.",
+                                                 ex
+                                                );
+        }
+
+        Driver.SizeChanged += Driver_SizeChanged;
+        Driver.KeyDown += Driver_KeyDown;
+        Driver.KeyUp += Driver_KeyUp;
+        Driver.MouseEvent += Driver_MouseEvent;
+
+        SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
+
+        SupportedCultures = GetSupportedCultures ();
+        MainThreadId = Thread.CurrentThread.ManagedThreadId;
+        bool init = IsInitialized = true;
+        InitializedChanged?.Invoke (null, new (init));
+    }
+
+    private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
+    private static void Driver_KeyDown (object? sender, Key e) { OnKeyDown (e); }
+    private static void Driver_KeyUp (object? sender, Key e) { OnKeyUp (e); }
+    private static void Driver_MouseEvent (object? sender, MouseEvent e) { OnMouseEvent (e); }
+
+    /// <summary>Gets of list of <see cref="ConsoleDriver"/> types that are available.</summary>
+    /// <returns></returns>
+    [RequiresUnreferencedCode ("AOT")]
+    public static List<Type?> GetDriverTypes ()
+    {
+        // use reflection to get the list of drivers
+        List<Type?> driverTypes = new ();
+
+        foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ())
+        {
+            foreach (Type? type in asm.GetTypes ())
+            {
+                if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract)
+                {
+                    driverTypes.Add (type);
+                }
+            }
+        }
+
+        return driverTypes;
+    }
+
+    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    /// <remarks>
+    ///     Shutdown must be called for every call to <see cref="Init"/> or
+    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
+    ///     up (Disposed)
+    ///     and terminal settings are restored.
+    /// </remarks>
+    public static void Shutdown ()
+    {
+        // TODO: Throw an exception if Init hasn't been called.
+        ResetState ();
+        PrintJsonErrors ();
+        bool init = IsInitialized;
+        InitializedChanged?.Invoke (null, new (in init));
+    }
+
+    /// <summary>
+    ///     This event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
+    /// </summary>
+    /// <remarks>
+    ///     Intended to support unit tests that need to know when the application has been initialized.
+    /// </remarks>
+    public static event EventHandler<EventArgs<bool>>? InitializedChanged;
+}

+ 462 - 0
Terminal.Gui/Application/Application.Keyboard.cs

@@ -0,0 +1,462 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
+
+public static partial class Application // Keyboard handling
+{
+    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))]
+    public static Key NextTabKey
+    {
+        get => _nextTabKey;
+        set
+        {
+            if (_nextTabKey != value)
+            {
+                ReplaceKey (_nextTabKey, value);
+                _nextTabKey = value;
+            }
+        }
+    }
+
+    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))]
+    public static Key PrevTabKey
+    {
+        get => _prevTabKey;
+        set
+        {
+            if (_prevTabKey != value)
+            {
+                ReplaceKey (_prevTabKey, value);
+                _prevTabKey = value;
+            }
+        }
+    }
+
+    private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrrides
+
+    /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static Key NextTabGroupKey
+    {
+        get => _nextTabGroupKey;
+        set
+        {
+            if (_nextTabGroupKey != value)
+            {
+                ReplaceKey (_nextTabGroupKey, value);
+                _nextTabGroupKey = value;
+            }
+        }
+    }
+
+    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))]
+    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>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static Key QuitKey
+    {
+        get => _quitKey;
+        set
+        {
+            if (_quitKey != value)
+            {
+                ReplaceKey (_quitKey, value);
+                _quitKey = 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
+    ///     before <see cref="OnKeyUp"/>.
+    /// </summary>
+    /// <remarks>Can be used to simulate key press events.</remarks>
+    /// <param name="keyEvent"></param>
+    /// <returns><see langword="true"/> if the key was handled.</returns>
+    public static bool OnKeyDown (Key keyEvent)
+    {
+        //if (!IsInitialized)
+        //{
+        //    return true;
+        //}
+
+        KeyDown?.Invoke (null, keyEvent);
+
+        if (keyEvent.Handled)
+        {
+            return true;
+        }
+
+        if (Current is null)
+        {
+            foreach (Toplevel topLevel in TopLevels.ToList ())
+            {
+                if (topLevel.NewKeyDownEvent (keyEvent))
+                {
+                    return true;
+                }
+
+                if (topLevel.Modal)
+                {
+                    break;
+                }
+            }
+        }
+        else
+        {
+            if (Current.NewKeyDownEvent (keyEvent))
+            {
+                return true;
+            }
+        }
+
+        // Invoke any Application-scoped KeyBindings.
+        // The first view that handles the key will stop the loop.
+        foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.Bindings.Where (b => b.Key == keyEvent.KeyCode))
+        {
+            if (binding.Value.BoundView is { })
+            {
+                bool? handled = binding.Value.BoundView?.InvokeCommands (binding.Value.Commands, binding.Key, binding.Value);
+
+                if (handled != null && (bool)handled)
+                {
+                    return true;
+                }
+            }
+            else
+            {
+                if (!KeyBindings.TryGet (keyEvent, KeyBindingScope.Application, out KeyBinding appBinding))
+                {
+                    continue;
+                }
+
+                bool? toReturn = null;
+
+                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;
+                    }
+                }
+
+                return toReturn ?? true;
+            }
+        }
+
+        return false;
+    }
+
+    /// <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>
+    ///     Called by the <see cref="ConsoleDriver"/> when the user releases a key. Fires the <see cref="KeyUp"/> event
+    ///     then calls <see cref="View.NewKeyUpEvent"/> on all top level views. Called after <see cref="OnKeyDown"/>.
+    /// </summary>
+    /// <remarks>Can be used to simulate key press events.</remarks>
+    /// <param name="a"></param>
+    /// <returns><see langword="true"/> if the key was handled.</returns>
+    public static bool OnKeyUp (Key a)
+    {
+        if (!IsInitialized)
+        {
+            return true;
+        }
+
+        KeyUp?.Invoke (null, a);
+
+        if (a.Handled)
+        {
+            return true;
+        }
+
+        foreach (Toplevel topLevel in TopLevels.ToList ())
+        {
+            if (topLevel.NewKeyUpEvent (a))
+            {
+                return true;
+            }
+
+            if (topLevel.Modal)
+            {
+                break;
+            }
+        }
+
+        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>
+    ///     <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 (); }
+
+    static Application () { AddApplicationKeyBindings (); }
+
+    internal static void AddApplicationKeyBindings ()
+    {
+        CommandImplementations = new ();
+
+        // Things this view knows how to do
+        AddCommand (
+                    Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic.
+                    () =>
+                    {
+                        if (ApplicationOverlapped.OverlappedTop is { })
+                        {
+                            RequestStop (Current!);
+                        }
+                        else
+                        {
+                            RequestStop ();
+                        }
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Suspend,
+                    () =>
+                    {
+                        Driver?.Suspend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.NextView,
+                    () =>
+                    {
+                        ApplicationNavigation.MoveNextView ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.PreviousView,
+                    () =>
+                    {
+                        ApplicationNavigation.MovePreviousView ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.NextViewOrTop,
+                    () =>
+                    {
+                        ApplicationNavigation.MoveNextViewOrTop ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.PreviousViewOrTop,
+                    () =>
+                    {
+                        ApplicationNavigation.MovePreviousViewOrTop ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Refresh,
+                    () =>
+                    {
+                        Refresh ();
+
+                        return true;
+                    }
+                   );
+
+        KeyBindings.Clear ();
+
+        // Resources/config.json overrrides
+        NextTabKey = Key.Tab;
+        PrevTabKey = Key.Tab.WithShift;
+        NextTabGroupKey = Key.F6;
+        PrevTabGroupKey = Key.F6.WithShift;
+        QuitKey = Key.Esc;
+
+        KeyBindings.Add (QuitKey, KeyBindingScope.Application, Command.QuitToplevel);
+
+        KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextView);
+        KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextView);
+        KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousView);
+        KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousView);
+        KeyBindings.Add (NextTabKey, KeyBindingScope.Application, Command.NextView);
+        KeyBindings.Add (PrevTabKey, KeyBindingScope.Application, Command.PreviousView);
+
+        KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextViewOrTop); // Needed on Unix
+        KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousViewOrTop); // Needed on Unix
+
+        // TODO: Refresh Key should be configurable
+        KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
+
+        // TODO: Suspend Key should be configurable
+        if (Environment.OSVersion.Platform == PlatformID.Unix)
+        {
+            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>
+    ///     Gets the list of Views that have <see cref="KeyBindingScope.Application"/> key bindings.
+    /// </summary>
+    /// <remarks>
+    ///     This is an internal method used by the <see cref="View"/> class to add Application key bindings.
+    /// </remarks>
+    /// <returns>The list of Views that have Application-scoped key bindings.</returns>
+    internal static List<KeyBinding> GetViewKeyBindings ()
+    {
+        // Get the list of views that do not have Application-scoped key bindings
+        return KeyBindings.Bindings
+                          .Where (kv => kv.Value.Scope != KeyBindingScope.Application)
+                          .Select (kv => kv.Value)
+                          .Distinct ()
+                          .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.
+    /// </summary>
+    /// <remarks>
+    ///     This is an internal method used by the <see cref="View"/> class to remove Application key bindings.
+    /// </remarks>
+    /// <param name="view">The view that is bound to the key.</param>
+    internal static void RemoveKeyBindings (View view)
+    {
+        List<KeyBinding> list = KeyBindings.Bindings
+                                           .Where (kv => kv.Value.Scope != KeyBindingScope.Application)
+                                           .Select (kv => kv.Value)
+                                           .Distinct ()
+                                           .ToList ();
+    }
+}

+ 28 - 28
Terminal.Gui/Application/ApplicationMouse.cs → Terminal.Gui/Application/Application.Mouse.cs

@@ -1,6 +1,6 @@
-namespace Terminal.Gui;
-
-partial class Application
+#nullable enable
+namespace Terminal.Gui;
+public static partial class Application // Mouse handling
 {
     #region Mouse handling
 
@@ -9,32 +9,32 @@ partial class Application
     public static bool IsMouseDisabled { get; set; }
 
     /// <summary>The current <see cref="View"/> object that wants continuous mouse button pressed events.</summary>
-    public static View WantContinuousButtonPressedView { get; private set; }
+    public static View? WantContinuousButtonPressedView { get; private set; }
 
     /// <summary>
     ///     Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be routed to
     ///     this view until the view calls <see cref="UngrabMouse"/> or the mouse is released.
     /// </summary>
-    public static View MouseGrabView { get; private set; }
+    public static View? MouseGrabView { get; private set; }
 
     /// <summary>Invoked when a view wants to grab the mouse; can be canceled.</summary>
-    public static event EventHandler<GrabMouseEventArgs> GrabbingMouse;
+    public static event EventHandler<GrabMouseEventArgs>? GrabbingMouse;
 
     /// <summary>Invoked when a view wants un-grab the mouse; can be canceled.</summary>
-    public static event EventHandler<GrabMouseEventArgs> UnGrabbingMouse;
+    public static event EventHandler<GrabMouseEventArgs>? UnGrabbingMouse;
 
     /// <summary>Invoked after a view has grabbed the mouse.</summary>
-    public static event EventHandler<ViewEventArgs> GrabbedMouse;
+    public static event EventHandler<ViewEventArgs>? GrabbedMouse;
 
     /// <summary>Invoked after a view has un-grabbed the mouse.</summary>
-    public static event EventHandler<ViewEventArgs> UnGrabbedMouse;
+    public static event EventHandler<ViewEventArgs>? UnGrabbedMouse;
 
     /// <summary>
     ///     Grabs the mouse, forcing all mouse events to be routed to the specified view until <see cref="UngrabMouse"/>
     ///     is called.
     /// </summary>
     /// <param name="view">View that will receive all mouse events until <see cref="UngrabMouse"/> is invoked.</param>
-    public static void GrabMouse (View view)
+    public static void GrabMouse (View? view)
     {
         if (view is null)
         {
@@ -64,7 +64,7 @@ partial class Application
         }
     }
 
-    private static bool OnGrabbingMouse (View view)
+    private static bool OnGrabbingMouse (View? view)
     {
         if (view is null)
         {
@@ -77,7 +77,7 @@ partial class Application
         return evArgs.Cancel;
     }
 
-    private static bool OnUnGrabbingMouse (View view)
+    private static bool OnUnGrabbingMouse (View? view)
     {
         if (view is null)
         {
@@ -90,7 +90,7 @@ partial class Application
         return evArgs.Cancel;
     }
 
-    private static void OnGrabbedMouse (View view)
+    private static void OnGrabbedMouse (View? view)
     {
         if (view is null)
         {
@@ -100,7 +100,7 @@ partial class Application
         GrabbedMouse?.Invoke (view, new (view));
     }
 
-    private static void OnUnGrabbedMouse (View view)
+    private static void OnUnGrabbedMouse (View? view)
     {
         if (view is null)
         {
@@ -113,7 +113,7 @@ partial class Application
 #nullable enable
 
     // Used by OnMouseEvent to track the last view that was clicked on.
-    internal static View? _mouseEnteredView;
+    internal static View? MouseEnteredView { get; set; }
 
     /// <summary>Event fired when a mouse move or click occurs. Coordinates are screen relative.</summary>
     /// <remarks>
@@ -166,7 +166,7 @@ partial class Application
             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);
+                MouseEnteredView?.NewMouseLeaveEvent (mouseEvent);
             }
 
             //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
@@ -187,20 +187,20 @@ partial class Application
 
         if (view is not Adornment)
         {
-            if ((view is null || view == OverlappedTop)
+            if ((view is null || view == ApplicationOverlapped.OverlappedTop)
                 && Current is { Modal: false }
-                && OverlappedTop != null
+                && 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 = FindDeepestTop (Top, mouseEvent.Position);
+                View? top = ApplicationOverlapped.FindDeepestTop (Top!, mouseEvent.Position);
                 view = View.FindDeepestView (top, mouseEvent.Position);
 
-                if (view is { } && view != OverlappedTop && top != Current && top is { })
+                if (view is { } && view != ApplicationOverlapped.OverlappedTop && top != Current && top is { })
                 {
-                    MoveCurrent ((Toplevel)top);
+                    ApplicationOverlapped.MoveCurrent ((Toplevel)top);
                 }
             }
         }
@@ -242,16 +242,16 @@ partial class Application
             return;
         }
 
-        if (_mouseEnteredView is null)
+        if (MouseEnteredView is null)
         {
-            _mouseEnteredView = view;
+            MouseEnteredView = view;
             view.NewMouseEnterEvent (me);
         }
-        else if (_mouseEnteredView != view)
+        else if (MouseEnteredView != view)
         {
-            _mouseEnteredView.NewMouseLeaveEvent (me);
+            MouseEnteredView.NewMouseLeaveEvent (me);
             view.NewMouseEnterEvent (me);
-            _mouseEnteredView = view;
+            MouseEnteredView = view;
         }
 
         if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
@@ -272,7 +272,7 @@ partial class Application
 
             if (view is Adornment adornmentView)
             {
-                view = adornmentView.Parent.SuperView;
+                view = adornmentView.Parent!.SuperView;
             }
             else
             {
@@ -295,7 +295,7 @@ partial class Application
             };
         }
 
-        BringOverlappedTopToFront ();
+        ApplicationOverlapped.BringOverlappedTopToFront ();
     }
 
     #endregion Mouse handling

+ 10 - 0
Terminal.Gui/Application/Application.Navigation.cs

@@ -0,0 +1,10 @@
+#nullable enable
+namespace Terminal.Gui;
+
+public static partial class Application // Navigation stuff
+{
+    /// <summary>
+    ///     Gets the <see cref="ApplicationNavigation"/> instance for the current <see cref="Application"/>.
+    /// </summary>
+    public static ApplicationNavigation? Navigation { get; internal set; }
+}

+ 883 - 0
Terminal.Gui/Application/Application.Run.cs

@@ -0,0 +1,883 @@
+#nullable enable
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Terminal.Gui;
+
+public static partial class Application // Run (Begin, Run, End, Stop)
+{
+    // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`.
+    // This variable is set in `End` in this case so that `Begin` correctly sets `Top`.
+    private static Toplevel? _cachedRunStateToplevel;
+
+    /// <summary>
+    ///     Notify that a new <see cref="RunState"/> was created (<see cref="Begin(Toplevel)"/> was called). The token is
+    ///     created in <see cref="Begin(Toplevel)"/> and this event will be fired before that function exits.
+    /// </summary>
+    /// <remarks>
+    ///     If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to <see cref="Begin(Toplevel)"/>
+    ///     must also subscribe to <see cref="NotifyStopRunState"/> and manually dispose of the <see cref="RunState"/> token
+    ///     when the application is done.
+    /// </remarks>
+    public static event EventHandler<RunStateEventArgs>? NotifyNewRunState;
+
+    /// <summary>Notify that an existent <see cref="RunState"/> is stopping (<see cref="End(RunState)"/> was called).</summary>
+    /// <remarks>
+    ///     If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to <see cref="Begin(Toplevel)"/>
+    ///     must also subscribe to <see cref="NotifyStopRunState"/> and manually dispose of the <see cref="RunState"/> token
+    ///     when the application is done.
+    /// </remarks>
+    public static event EventHandler<ToplevelEventArgs>? NotifyStopRunState;
+
+    /// <summary>Building block API: Prepares the provided <see cref="Toplevel"/> for execution.</summary>
+    /// <returns>
+    ///     The <see cref="RunState"/> handle that needs to be passed to the <see cref="End(RunState)"/> method upon
+    ///     completion.
+    /// </returns>
+    /// <param name="toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
+    /// <remarks>
+    ///     This method prepares the provided <see cref="Toplevel"/> for running with the focus, it adds this to the list
+    ///     of <see cref="Toplevel"/>s, lays out the Subviews, focuses the first element, and draws the <see cref="Toplevel"/>
+    ///     in the screen. This is usually followed by executing the <see cref="RunLoop"/> method, and then the
+    ///     <see cref="End(RunState)"/> method upon termination which will undo these changes.
+    /// </remarks>
+    public static RunState Begin (Toplevel toplevel)
+    {
+        ArgumentNullException.ThrowIfNull (toplevel);
+
+#if DEBUG_IDISPOSABLE
+        Debug.Assert (!toplevel.WasDisposed);
+
+        if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel)
+        {
+            Debug.Assert (_cachedRunStateToplevel.WasDisposed);
+        }
+#endif
+
+        if (toplevel.IsOverlappedContainer && ApplicationOverlapped.OverlappedTop != toplevel && ApplicationOverlapped.OverlappedTop is { })
+        {
+            throw new InvalidOperationException ("Only one Overlapped Container is allowed.");
+        }
+
+        // Ensure the mouse is ungrabbed.
+        MouseGrabView = null;
+
+        var rs = new RunState (toplevel);
+
+        // View implements ISupportInitializeNotification which is derived from ISupportInitialize
+        if (!toplevel.IsInitialized)
+        {
+            toplevel.BeginInit ();
+            toplevel.EndInit ();
+        }
+
+#if DEBUG_IDISPOSABLE
+        if (Top is { } && toplevel != Top && !TopLevels.Contains (Top))
+        {
+            // This assertion confirm if the Top was already disposed
+            Debug.Assert (Top.WasDisposed);
+            Debug.Assert (Top == _cachedRunStateToplevel);
+        }
+#endif
+
+        lock (TopLevels)
+        {
+            if (Top is { } && toplevel != Top && !TopLevels.Contains (Top))
+            {
+                // If Top was already disposed and isn't on the Toplevels Stack,
+                // clean it up here if is the same as _cachedRunStateToplevel
+                if (Top == _cachedRunStateToplevel)
+                {
+                    Top = null;
+                }
+                else
+                {
+                    // Probably this will never hit
+                    throw new ObjectDisposedException (Top.GetType ().FullName);
+                }
+            }
+            else if (ApplicationOverlapped.OverlappedTop is { } && toplevel != Top && TopLevels.Contains (Top!))
+            {
+                // BUGBUG: Don't call OnLeave/OnEnter directly! Set HasFocus to false and let the system handle it.
+                Top!.OnLeave (toplevel);
+            }
+
+            // BUGBUG: We should not depend on `Id` internally.
+            // BUGBUG: It is super unclear what this code does anyway.
+            if (string.IsNullOrEmpty (toplevel.Id))
+            {
+                var count = 1;
+                var id = (TopLevels.Count + count).ToString ();
+
+                while (TopLevels.Count > 0 && TopLevels.FirstOrDefault (x => x.Id == id) is { })
+                {
+                    count++;
+                    id = (TopLevels.Count + count).ToString ();
+                }
+
+                toplevel.Id = (TopLevels.Count + count).ToString ();
+
+                TopLevels.Push (toplevel);
+            }
+            else
+            {
+                Toplevel? dup = TopLevels.FirstOrDefault (x => x.Id == toplevel.Id);
+
+                if (dup is null)
+                {
+                    TopLevels.Push (toplevel);
+                }
+            }
+
+            if (TopLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0)
+            {
+                throw new ArgumentException ("There are duplicates Toplevel IDs");
+            }
+        }
+
+        if (Top is null || toplevel.IsOverlappedContainer)
+        {
+            Top = toplevel;
+        }
+
+        var refreshDriver = true;
+
+        if (ApplicationOverlapped.OverlappedTop is null
+            || toplevel.IsOverlappedContainer
+            || (Current?.Modal == false && toplevel.Modal)
+            || (Current?.Modal == false && !toplevel.Modal)
+            || (Current?.Modal == true && toplevel.Modal))
+        {
+            if (toplevel.Visible)
+            {
+                if (Current is { HasFocus: true })
+                {
+                    Current.HasFocus = false;
+                }
+
+                Current?.OnDeactivate (toplevel);
+                Toplevel previousCurrent = Current!;
+
+                Current = toplevel;
+                Current.OnActivate (previousCurrent);
+
+                ApplicationOverlapped.SetCurrentOverlappedAsTop ();
+            }
+            else
+            {
+                refreshDriver = false;
+            }
+        }
+        else if ((toplevel != ApplicationOverlapped.OverlappedTop
+                  && Current?.Modal == true
+                  && !TopLevels.Peek ().Modal)
+                 || (toplevel != ApplicationOverlapped.OverlappedTop && Current?.Running == false))
+        {
+            refreshDriver = false;
+            ApplicationOverlapped.MoveCurrent (toplevel);
+        }
+        else
+        {
+            refreshDriver = false;
+            ApplicationOverlapped.MoveCurrent (Current!);
+        }
+
+        toplevel.SetRelativeLayout (Driver!.Screen.Size);
+
+        toplevel.LayoutSubviews ();
+        toplevel.PositionToplevels ();
+        toplevel.FocusFirst (null);
+        ApplicationOverlapped.BringOverlappedTopToFront ();
+
+        if (refreshDriver)
+        {
+            ApplicationOverlapped.OverlappedTop?.OnChildLoaded (toplevel);
+            toplevel.OnLoaded ();
+            toplevel.SetNeedsDisplay ();
+            toplevel.Draw ();
+            Driver.UpdateScreen ();
+
+            if (PositionCursor (toplevel))
+            {
+                Driver.UpdateCursor ();
+            }
+        }
+
+        NotifyNewRunState?.Invoke (toplevel, new (rs));
+
+        return rs;
+    }
+
+    /// <summary>
+    ///     Calls <see cref="View.PositionCursor"/> on the most focused view in the view starting with <paramref name="view"/>.
+    /// </summary>
+    /// <remarks>
+    ///     Does nothing if <paramref name="view"/> is <see langword="null"/> or if the most focused view is not visible or
+    ///     enabled.
+    ///     <para>
+    ///         If the most focused view is not visible within it's superview, the cursor will be hidden.
+    ///     </para>
+    /// </remarks>
+    /// <returns><see langword="true"/> if a view positioned the cursor and the position is visible.</returns>
+    internal static bool PositionCursor (View view)
+    {
+        // Find the most focused view and position the cursor there.
+        View? mostFocused = view?.MostFocused;
+
+        if (mostFocused is null)
+        {
+            if (view is { HasFocus: true })
+            {
+                mostFocused = view;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        // If the view is not visible or enabled, don't position the cursor
+        if (!mostFocused.Visible || !mostFocused.Enabled)
+        {
+            Driver!.GetCursorVisibility (out CursorVisibility current);
+
+            if (current != CursorVisibility.Invisible)
+            {
+                Driver.SetCursorVisibility (CursorVisibility.Invisible);
+            }
+
+            return false;
+        }
+
+        // If the view is not visible within it's superview, don't position the cursor
+        Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty });
+        Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver!.Screen;
+
+        if (!superViewViewport.IntersectsWith (mostFocusedViewport))
+        {
+            return false;
+        }
+
+        Point? cursor = mostFocused.PositionCursor ();
+
+        Driver!.GetCursorVisibility (out CursorVisibility currentCursorVisibility);
+
+        if (cursor is { })
+        {
+            // Convert cursor to screen coords
+            cursor = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = cursor.Value }).Location;
+
+            // If the cursor is not in a visible location in the SuperView, hide it
+            if (!superViewViewport.Contains (cursor.Value))
+            {
+                if (currentCursorVisibility != CursorVisibility.Invisible)
+                {
+                    Driver.SetCursorVisibility (CursorVisibility.Invisible);
+                }
+
+                return false;
+            }
+
+            // Show it
+            if (currentCursorVisibility == CursorVisibility.Invisible)
+            {
+                Driver.SetCursorVisibility (mostFocused.CursorVisibility);
+            }
+
+            return true;
+        }
+
+        if (currentCursorVisibility != CursorVisibility.Invisible)
+        {
+            Driver.SetCursorVisibility (CursorVisibility.Invisible);
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
+    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         The caller is responsible for disposing the object returned by this method.
+    ///     </para>
+    /// </remarks>
+    /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public static Toplevel Run (Func<Exception, bool>? errorHandler = null, ConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
+
+    /// <summary>
+    ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
+    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         The caller is responsible for disposing the object returned by this method.
+    ///     </para>
+    /// </remarks>
+    /// <param name="errorHandler"></param>
+    /// <param name="driver">
+    ///     The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the platform will
+    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
+    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
+    /// </param>
+    /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public static T Run<T> (Func<Exception, bool>? errorHandler = null, ConsoleDriver? driver = null)
+        where T : Toplevel, new()
+    {
+        if (!IsInitialized)
+        {
+            // Init() has NOT been called.
+            InternalInit (driver, null, true);
+        }
+
+        var top = new T ();
+
+        Run (top, errorHandler);
+
+        return top;
+    }
+
+    /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This method is used to start processing events for the main application, but it is also used to run other
+    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
+    ///     </para>
+    ///     <para>
+    ///         To make a <see cref="Run(Terminal.Gui.Toplevel,System.Func{System.Exception,bool})"/> stop execution, call
+    ///         <see cref="Application.RequestStop"/>.
+    ///     </para>
+    ///     <para>
+    ///         Calling <see cref="Run(Terminal.Gui.Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
+    ///         <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState)"/>, and then calling
+    ///         <see cref="End(RunState)"/>.
+    ///     </para>
+    ///     <para>
+    ///         Alternatively, to have a program control the main loop and process events manually, call
+    ///         <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
+    ///         <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
+    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
+    ///         return control immediately.
+    ///     </para>
+    ///     <para>When using <see cref="Run{T}"/> or
+    ///         <see cref="Run(System.Func{System.Exception,bool},Terminal.Gui.ConsoleDriver)"/>
+    ///         <see cref="Init"/> will be called automatically.
+    ///     </para>
+    ///     <para>
+    ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
+    ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
+    ///         returns <see langword="true"/> the <see cref="RunLoop(RunState)"/> will resume; otherwise this method will
+    ///         exit.
+    ///     </para>
+    /// </remarks>
+    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
+    /// <param name="errorHandler">
+    ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
+    ///     rethrows when null).
+    /// </param>
+    public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
+    {
+        ArgumentNullException.ThrowIfNull (view);
+
+        if (IsInitialized)
+        {
+            if (Driver is null)
+            {
+                // Disposing before throwing
+                view.Dispose ();
+
+                // This code path should be impossible because Init(null, null) will select the platform default driver
+                throw new InvalidOperationException (
+                                                     "Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called."
+                                                    );
+            }
+        }
+        else
+        {
+            // Init() has NOT been called.
+            throw new InvalidOperationException (
+                                                 "Init() has not been called. Only Run() or Run<T>() can be used without calling Init()."
+                                                );
+        }
+
+        var resume = true;
+
+        while (resume)
+        {
+#if !DEBUG
+            try
+            {
+#endif
+            resume = false;
+            RunState runState = Begin (view);
+
+            // If EndAfterFirstIteration is true then the user must dispose of the runToken
+            // by using NotifyStopRunState event.
+            RunLoop (runState);
+
+            if (runState.Toplevel is null)
+            {
+#if DEBUG_IDISPOSABLE
+                Debug.Assert (TopLevels.Count == 0);
+#endif
+                runState.Dispose ();
+
+                return;
+            }
+
+            if (!EndAfterFirstIteration)
+            {
+                End (runState);
+            }
+#if !DEBUG
+            }
+            catch (Exception error)
+            {
+                if (errorHandler is null)
+                {
+                    throw;
+                }
+
+                resume = errorHandler (error);
+            }
+#endif
+        }
+    }
+
+    /// <summary>Adds a timeout to the application.</summary>
+    /// <remarks>
+    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
+    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
+    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
+    /// </remarks>
+    public static object AddTimeout (TimeSpan time, Func<bool> callback) { return MainLoop!.AddTimeout (time, callback); }
+
+    /// <summary>Removes a previously scheduled timeout</summary>
+    /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
+    /// Returns
+    /// <c>true</c>
+    /// if the timeout is successfully removed; otherwise,
+    /// <c>false</c>
+    /// .
+    /// This method also returns
+    /// <c>false</c>
+    /// if the timeout is not found.
+    public static bool RemoveTimeout (object token) { return MainLoop?.RemoveTimeout (token) ?? false; }
+
+    /// <summary>Runs <paramref name="action"/> on the thread that is processing events</summary>
+    /// <param name="action">the action to be invoked on the main processing thread.</param>
+    public static void Invoke (Action action)
+    {
+        MainLoop?.AddIdle (
+                           () =>
+                           {
+                               action ();
+
+                               return false;
+                           }
+                          );
+    }
+
+    // TODO: Determine if this is really needed. The only code that calls WakeUp I can find
+    // is ProgressBarStyles, and it's not clear it needs to.
+
+    /// <summary>Wakes up the running application that might be waiting on input.</summary>
+    public static void Wakeup () { MainLoop?.Wakeup (); }
+
+    /// <summary>Triggers a refresh of the entire display.</summary>
+    public static void Refresh ()
+    {
+        // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear
+        Driver!.ClearContents ();
+
+        foreach (Toplevel v in TopLevels.Reverse ())
+        {
+            if (v.Visible)
+            {
+                v.SetNeedsDisplay ();
+                v.SetSubViewNeedsDisplay ();
+                v.Draw ();
+            }
+        }
+
+        Driver.Refresh ();
+    }
+
+    /// <summary>This event is raised on each iteration of the main loop.</summary>
+    /// <remarks>See also <see cref="Timeout"/></remarks>
+    public static event EventHandler<IterationEventArgs>? Iteration;
+
+    /// <summary>The <see cref="MainLoop"/> driver for the application</summary>
+    /// <value>The main loop.</value>
+    internal static MainLoop? MainLoop { get; private set; }
+
+    /// <summary>
+    ///     Set to true to cause <see cref="End"/> to be called after the first iteration. Set to false (the default) to
+    ///     cause the application to continue running until Application.RequestStop () is called.
+    /// </summary>
+    public static bool EndAfterFirstIteration { get; set; }
+
+    /// <summary>Building block API: Runs the main loop for the created <see cref="Toplevel"/>.</summary>
+    /// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
+    public static void RunLoop (RunState state)
+    {
+        ArgumentNullException.ThrowIfNull (state);
+        ObjectDisposedException.ThrowIf (state.Toplevel is null, "state");
+
+        var firstIteration = true;
+
+        for (state.Toplevel.Running = true; state.Toplevel?.Running == true;)
+        {
+            MainLoop!.Running = true;
+
+            if (EndAfterFirstIteration && !firstIteration)
+            {
+                return;
+            }
+
+            RunIteration (ref state, ref firstIteration);
+        }
+
+        MainLoop!.Running = false;
+
+        // Run one last iteration to consume any outstanding input events from Driver
+        // This is important for remaining OnKeyUp events.
+        RunIteration (ref state, ref firstIteration);
+    }
+
+    /// <summary>Run one application iteration.</summary>
+    /// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
+    /// <param name="firstIteration">
+    ///     Set to <see langword="true"/> if this is the first run loop iteration. Upon return, it
+    ///     will be set to <see langword="false"/> if at least one iteration happened.
+    /// </param>
+    public static void RunIteration (ref RunState state, ref bool firstIteration)
+    {
+        if (MainLoop!.Running && MainLoop.EventsPending ())
+        {
+            // Notify Toplevel it's ready
+            if (firstIteration)
+            {
+                state.Toplevel.OnReady ();
+            }
+
+            MainLoop.RunIteration ();
+            Iteration?.Invoke (null, new ());
+            EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
+
+            // TODO: Overlapped - Move elsewhere
+            if (state.Toplevel != Current)
+            {
+                ApplicationOverlapped.OverlappedTop?.OnDeactivate (state.Toplevel);
+                state.Toplevel = Current;
+                ApplicationOverlapped.OverlappedTop?.OnActivate (state.Toplevel!);
+                Top!.SetSubViewNeedsDisplay ();
+                Refresh ();
+            }
+        }
+
+        firstIteration = false;
+
+        if (Current == null)
+        {
+            return;
+        }
+
+        if (state.Toplevel != Top && (Top!.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
+        {
+            state.Toplevel!.SetNeedsDisplay (state.Toplevel.Frame);
+            Top.Draw ();
+
+            foreach (Toplevel top in TopLevels.Reverse ())
+            {
+                if (top != Top && top != state.Toplevel)
+                {
+                    top.SetNeedsDisplay ();
+                    top.SetSubViewNeedsDisplay ();
+                    top.Draw ();
+                }
+            }
+        }
+
+        if (TopLevels.Count == 1
+            && state.Toplevel == Top
+            && (Driver!.Cols != state.Toplevel!.Frame.Width
+                || Driver!.Rows != state.Toplevel.Frame.Height)
+            && (state.Toplevel.NeedsDisplay
+                || state.Toplevel.SubViewNeedsDisplay
+                || state.Toplevel.LayoutNeeded))
+        {
+            Driver.ClearContents ();
+        }
+
+        if (state.Toplevel!.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || ApplicationOverlapped.OverlappedChildNeedsDisplay ())
+        {
+            state.Toplevel.SetNeedsDisplay ();
+            state.Toplevel.Draw ();
+            Driver!.UpdateScreen ();
+
+            //Driver.UpdateCursor ();
+        }
+
+        if (PositionCursor (state.Toplevel))
+        {
+            Driver!.UpdateCursor ();
+        }
+
+        //        else
+        {
+            //if (PositionCursor (state.Toplevel))
+            //{
+            //    Driver.Refresh ();
+            //}
+            //Driver.UpdateCursor ();
+        }
+
+        if (state.Toplevel != Top && !state.Toplevel.Modal && (Top!.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
+        {
+            Top.Draw ();
+        }
+    }
+
+    /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
+    /// <param name="top">The <see cref="Toplevel"/> to stop.</param>
+    /// <remarks>
+    ///     <para>This will cause <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to return.</para>
+    ///     <para>
+    ///         Calling <see cref="RequestStop(Terminal.Gui.Toplevel)"/> is equivalent to setting the <see cref="Toplevel.Running"/>
+    ///         property on the currently running <see cref="Toplevel"/> to false.
+    ///     </para>
+    /// </remarks>
+    public static void RequestStop (Toplevel? top = null)
+    {
+        if (ApplicationOverlapped.OverlappedTop is null || top is null)
+        {
+            top = Current;
+        }
+
+        if (ApplicationOverlapped.OverlappedTop != null
+            && top!.IsOverlappedContainer
+            && top?.Running == true
+            && (Current?.Modal == false || Current is { Modal: true, Running: false }))
+        {
+            ApplicationOverlapped.OverlappedTop.RequestStop ();
+        }
+        else if (ApplicationOverlapped.OverlappedTop != null
+                 && top != Current
+                 && Current is { Running: true, Modal: true }
+                 && top!.Modal
+                 && top.Running)
+        {
+            var ev = new ToplevelClosingEventArgs (Current);
+            Current.OnClosing (ev);
+
+            if (ev.Cancel)
+            {
+                return;
+            }
+
+            ev = new (top);
+            top.OnClosing (ev);
+
+            if (ev.Cancel)
+            {
+                return;
+            }
+
+            Current.Running = false;
+            OnNotifyStopRunState (Current);
+            top.Running = false;
+            OnNotifyStopRunState (top);
+        }
+        else if ((ApplicationOverlapped.OverlappedTop != null
+                  && top != ApplicationOverlapped.OverlappedTop
+                  && top != Current
+                  && Current is { Modal: false, Running: true }
+                  && !top!.Running)
+                 || (ApplicationOverlapped.OverlappedTop != null
+                     && top != ApplicationOverlapped.OverlappedTop
+                     && top != Current
+                     && Current is { Modal: false, Running: false }
+                     && !top!.Running
+                     && TopLevels.ToArray () [1].Running))
+        {
+            ApplicationOverlapped.MoveCurrent (top);
+        }
+        else if (ApplicationOverlapped.OverlappedTop != null
+                 && Current != top
+                 && Current?.Running == true
+                 && !top!.Running
+                 && Current?.Modal == true
+                 && top.Modal)
+        {
+            // The Current and the top are both modal so needed to set the Current.Running to false too.
+            Current.Running = false;
+            OnNotifyStopRunState (Current);
+        }
+        else if (ApplicationOverlapped.OverlappedTop != null
+                 && Current == top
+                 && ApplicationOverlapped.OverlappedTop?.Running == true
+                 && Current?.Running == true
+                 && top!.Running
+                 && Current?.Modal == true
+                 && top!.Modal)
+        {
+            // The OverlappedTop was requested to stop inside a modal Toplevel which is the Current and top,
+            // both are the same, so needed to set the Current.Running to false too.
+            Current.Running = false;
+            OnNotifyStopRunState (Current);
+        }
+        else
+        {
+            Toplevel currentTop;
+
+            if (top == Current || (Current?.Modal == true && !top!.Modal))
+            {
+                currentTop = Current!;
+            }
+            else
+            {
+                currentTop = top!;
+            }
+
+            if (!currentTop.Running)
+            {
+                return;
+            }
+
+            var ev = new ToplevelClosingEventArgs (currentTop);
+            currentTop.OnClosing (ev);
+
+            if (ev.Cancel)
+            {
+                return;
+            }
+
+            currentTop.Running = false;
+            OnNotifyStopRunState (currentTop);
+        }
+    }
+
+    private static void OnNotifyStopRunState (Toplevel top)
+    {
+        if (EndAfterFirstIteration)
+        {
+            NotifyStopRunState?.Invoke (top, new (top));
+        }
+    }
+
+    /// <summary>
+    ///     Building block API: completes the execution of a <see cref="Toplevel"/> that was started with
+    ///     <see cref="Begin(Toplevel)"/> .
+    /// </summary>
+    /// <param name="runState">The <see cref="RunState"/> returned by the <see cref="Begin(Toplevel)"/> method.</param>
+    public static void End (RunState runState)
+    {
+        ArgumentNullException.ThrowIfNull (runState);
+
+        if (ApplicationOverlapped.OverlappedTop is { })
+        {
+            ApplicationOverlapped.OverlappedTop.OnChildUnloaded (runState.Toplevel);
+        }
+        else
+        {
+            runState.Toplevel.OnUnloaded ();
+        }
+
+        // End the RunState.Toplevel
+        // First, take it off the Toplevel Stack
+        if (TopLevels.Count > 0)
+        {
+            if (TopLevels.Peek () != runState.Toplevel)
+            {
+                // If the top of the stack is not the RunState.Toplevel then
+                // this call to End is not balanced with the call to Begin that started the RunState
+                throw new ArgumentException ("End must be balanced with calls to Begin");
+            }
+
+            TopLevels.Pop ();
+        }
+
+        // Notify that it is closing
+        runState.Toplevel?.OnClosed (runState.Toplevel);
+
+        // If there is a OverlappedTop that is not the RunState.Toplevel then RunState.Toplevel
+        // is a child of MidTop, and we should notify the OverlappedTop that it is closing
+        if (ApplicationOverlapped.OverlappedTop is { } && !runState.Toplevel!.Modal && runState.Toplevel != ApplicationOverlapped.OverlappedTop)
+        {
+            ApplicationOverlapped.OverlappedTop.OnChildClosed (runState.Toplevel);
+        }
+
+        // Set Current and Top to the next TopLevel on the stack
+        if (TopLevels.Count == 0)
+        {
+            if (Current is { HasFocus: true })
+            {
+                Current.HasFocus = false;
+            }
+            Current = null;
+        }
+        else
+        {
+            if (TopLevels.Count > 1 && TopLevels.Peek () == ApplicationOverlapped.OverlappedTop && ApplicationOverlapped.OverlappedChildren?.Any (t => t.Visible) != null)
+            {
+                ApplicationOverlapped.OverlappedMoveNext ();
+            }
+
+            Current = TopLevels.Peek ();
+
+            if (TopLevels.Count == 1 && Current == ApplicationOverlapped.OverlappedTop)
+            {
+                ApplicationOverlapped.OverlappedTop.OnAllChildClosed ();
+            }
+            else
+            {
+                ApplicationOverlapped.SetCurrentOverlappedAsTop ();
+                // BUGBUG: We should not call OnEnter/OnLeave directly; they should only be called by SetHasFocus
+                if (runState.Toplevel is { HasFocus: true })
+                {
+                    runState.Toplevel.HasFocus = false;
+                }
+
+                if (Current is { HasFocus: false })
+                {
+                    Current.SetFocus ();
+                    Current.RestoreFocus ();
+                }
+            }
+
+            Refresh ();
+        }
+
+        // Don't dispose runState.Toplevel. It's up to caller dispose it
+        // If it's not the same as the current in the RunIteration,
+        // it will be fixed later in the next RunIteration.
+        if (ApplicationOverlapped.OverlappedTop is { } && !TopLevels.Contains (ApplicationOverlapped.OverlappedTop))
+        {
+            _cachedRunStateToplevel = ApplicationOverlapped.OverlappedTop;
+        }
+        else
+        {
+            _cachedRunStateToplevel = runState.Toplevel;
+        }
+
+        runState.Toplevel = null;
+        runState.Dispose ();
+    }
+}

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

@@ -0,0 +1,53 @@
+#nullable enable
+namespace Terminal.Gui;
+
+public static partial class Application // Screen related stuff
+{
+    /// <summary>
+    ///     Gets the size of the screen. This is the size of the screen as reported by the <see cref="ConsoleDriver"/>.
+    /// </summary>
+    /// <remarks>
+    ///     If the <see cref="ConsoleDriver"/> has not been initialized, this will return a default size of 2048x2048; useful for unit tests.
+    /// </remarks>
+    public static Rectangle Screen => Driver?.Screen ?? new (0, 0, 2048, 2048);
+
+    /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
+    /// <remarks>
+    ///     Event handlers can set <see cref="SizeChangedEventArgs.Cancel"/> to <see langword="true"/> to prevent
+    ///     <see cref="Application"/> from changing it's size to match the new terminal size.
+    /// </remarks>
+    public static event EventHandler<SizeChangedEventArgs>? SizeChanging;
+
+    /// <summary>
+    ///     Called when the application's size changes. Sets the size of all <see cref="Toplevel"/>s and fires the
+    ///     <see cref="SizeChanging"/> event.
+    /// </summary>
+    /// <param name="args">The new size.</param>
+    /// <returns><see lanword="true"/>if the size was changed.</returns>
+    public static bool OnSizeChanging (SizeChangedEventArgs args)
+    {
+        SizeChanging?.Invoke (null, args);
+
+        if (args.Cancel || args.Size is null)
+        {
+            return false;
+        }
+
+        foreach (Toplevel t in TopLevels)
+        {
+            t.SetRelativeLayout (args.Size.Value);
+            t.LayoutSubviews ();
+            t.PositionToplevels ();
+            t.OnSizeChanging (new (args.Size));
+
+            if (PositionCursor (t))
+            {
+                Driver?.UpdateCursor ();
+            }
+        }
+
+        Refresh ();
+
+        return true;
+    }
+}

+ 56 - 0
Terminal.Gui/Application/Application.Toplevel.cs

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

+ 102 - 1380
Terminal.Gui/Application/Application.cs

@@ -1,6 +1,9 @@
+#nullable enable
 using System.Diagnostics;
 using System.Globalization;
 using System.Reflection;
+using System.Resources;
+using Terminal.Gui.Resources;
 
 namespace Terminal.Gui;
 
@@ -8,46 +11,97 @@ namespace Terminal.Gui;
 /// <example>
 ///     <code>
 ///     Application.Init();
-///     var win = new Window ($"Example App ({Application.QuitKey} to quit)");
+///     var win = new Window()
+///     {
+///         Title = $"Example App ({Application.QuitKey} to quit)"
+///     };
 ///     Application.Run(win);
 ///     win.Dispose();
 ///     Application.Shutdown();
 ///     </code>
 /// </example>
-/// <remarks>TODO: Flush this out.</remarks>
+/// <remarks></remarks>
 public static partial class Application
 {
-    // For Unit testing - ignores UseSystemConsole
-    internal static bool _forceFakeConsole;
-
-    /// <summary>Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.</summary>
-    public static ConsoleDriver Driver { get; internal set; }
+    /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
+    public static List<CultureInfo>? SupportedCultures { get; private set; }
 
     /// <summary>
-    ///     Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in
-    ///     <see cref="ColorName"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be output
-    ///     as long as the selected <see cref="ConsoleDriver"/> supports TrueColor.
+    ///     Gets a string representation of the Application as rendered by <see cref="Driver"/>.
     /// </summary>
-    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    public static bool Force16Colors { get; set; }
+    /// <returns>A string representation of the Application </returns>
+    public new static string ToString ()
+    {
+        ConsoleDriver driver = Driver;
+
+        if (driver is null)
+        {
+            return string.Empty;
+        }
+
+        return ToString (driver);
+    }
 
     /// <summary>
-    ///     Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If not
-    ///     specified, the driver is selected based on the platform.
+    ///     Gets a string representation of the Application rendered by the provided <see cref="ConsoleDriver"/>.
     /// </summary>
-    /// <remarks>
-    ///     Note, <see cref="Application.Init(ConsoleDriver, string)"/> will override this configuration setting if called
-    ///     with either `driver` or `driverName` specified.
-    /// </remarks>
-    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    public static string ForceDriver { get; set; } = string.Empty;
+    /// <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)
+    {
+        var sb = new StringBuilder ();
 
-    /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
-    public static List<CultureInfo> SupportedCultures { get; private set; }
+        Cell [,] contents = driver.Contents;
+
+        for (var r = 0; r < driver.Rows; r++)
+        {
+            for (var c = 0; c < driver.Cols; c++)
+            {
+                Rune rune = contents [r, c].Rune;
+
+                if (rune.DecodeSurrogatePair (out char [] sp))
+                {
+                    sb.Append (sp);
+                }
+                else
+                {
+                    sb.Append ((char)rune.Value);
+                }
+
+                if (rune.GetColumns () > 1)
+                {
+                    c++;
+                }
+
+                // See Issue #2616
+                //foreach (var combMark in contents [r, c].CombiningMarks) {
+                //	sb.Append ((char)combMark.Value);
+                //}
+            }
+
+            sb.AppendLine ();
+        }
+
+        return sb.ToString ();
+    }
+
+    internal static List<CultureInfo> GetAvailableCulturesFromEmbeddedResources ()
+    {
+        ResourceManager rm = new (typeof (Strings));
+
+        CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
+
+        return cultures.Where (
+                               cultureInfo =>
+                                   !cultureInfo.Equals (CultureInfo.InvariantCulture)
+                                   && rm.GetResourceSet (cultureInfo, true, false) is { }
+                              )
+                       .ToList ();
+    }
 
     internal static List<CultureInfo> GetSupportedCultures ()
     {
-        CultureInfo [] culture = CultureInfo.GetCultures (CultureTypes.AllCultures);
+        CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
 
         // Get the assembly
         var assembly = Assembly.GetExecutingAssembly ();
@@ -56,20 +110,22 @@ public static partial class Application
         string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory;
 
         // Find the resource file name of the assembly
-        var resourceFilename = $"{Path.GetFileNameWithoutExtension (assembly.Location)}.resources.dll";
+        var resourceFilename = $"{assembly.GetName ().Name}.resources.dll";
 
-        // Return all culture for which satellite folder found with culture code.
-        return culture.Where (
-                              cultureInfo =>
-                                  Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name))
-                                  && File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename))
-                             )
-                      .ToList ();
-    }
+        if (cultures.Length > 1 && Directory.Exists (Path.Combine (assemblyLocation, "pt-PT")))
+        {
+            // Return all culture for which satellite folder found with culture code.
+            return cultures.Where (
+                                   cultureInfo =>
+                                       Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name))
+                                       && File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename))
+                                  )
+                           .ToList ();
+        }
 
-    // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`.
-    // This variable is set in `End` in this case so that `Begin` correctly sets `Top`.
-    private static Toplevel _cachedRunStateToplevel;
+        // It's called from a self-contained single-file and get available cultures from the embedded resources strings.
+        return GetAvailableCulturesFromEmbeddedResources ();
+    }
 
     // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
     // Encapsulate all setting of initial state for Application; Having
@@ -81,12 +137,12 @@ public static partial class Application
         // 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
-        foreach (Toplevel t in _topLevels)
+        foreach (Toplevel? t in TopLevels)
         {
-            t.Running = false;
+            t!.Running = false;
         }
 
-        _topLevels.Clear ();
+        TopLevels.Clear ();
         Current = null;
 #if DEBUG_IDISPOSABLE
 
@@ -109,7 +165,7 @@ public static partial class Application
         // MainLoop stuff
         MainLoop?.Dispose ();
         MainLoop = null;
-        _mainThreadId = -1;
+        MainThreadId = -1;
         Iteration = null;
         EndAfterFirstIteration = false;
 
@@ -133,10 +189,10 @@ public static partial class Application
         NotifyNewRunState = null;
         NotifyStopRunState = null;
         MouseGrabView = null;
-        _initialized = false;
+        IsInitialized = false;
 
         // Mouse
-        _mouseEnteredView = null;
+        MouseEnteredView = null;
         WantContinuousButtonPressedView = null;
         MouseEvent = null;
         GrabbedMouse = null;
@@ -145,1356 +201,22 @@ public static partial class Application
         UnGrabbedMouse = null;
 
         // Keyboard
-        AlternateBackwardKey = Key.Empty;
-        AlternateForwardKey = Key.Empty;
-        QuitKey = Key.Empty;
         KeyDown = null;
         KeyUp = null;
         SizeChanging = null;
-        ClearKeyBindings ();
+
+        Navigation = null;
+
+        AddApplicationKeyBindings ();
 
         Colors.Reset ();
 
         // Reset synchronization context to allow the user to run async/await,
-        // as the main loop has been ended, the synchronization context from 
+        // as the main loop has been ended, the synchronization context from
         // gui.cs does no longer process any callbacks. See #1084 for more details:
         // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
         SynchronizationContext.SetSynchronizationContext (null);
     }
 
-    #region Initialization (Init/Shutdown)
-
-    /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
-    /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
-    /// <para>
-    ///     This function loads the right <see cref="ConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
-    ///     assigns it to <see cref="Top"/>
-    /// </para>
-    /// <para>
-    ///     <see cref="Shutdown"/> must be called when the application is closing (typically after
-    ///     <see cref="Run{T}"/> has returned) to ensure resources are cleaned up and
-    ///     terminal settings
-    ///     restored.
-    /// </para>
-    /// <para>
-    ///     The <see cref="Run{T}"/> function combines
-    ///     <see cref="Init(ConsoleDriver, string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
-    ///     into a single
-    ///     call. An application cam use <see cref="Run{T}"/> without explicitly calling
-    ///     <see cref="Init(ConsoleDriver, string)"/>.
-    /// </para>
-    /// <param name="driver">
-    ///     The <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or
-    ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
-    /// </param>
-    /// <param name="driverName">
-    ///     The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the
-    ///     <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
-    ///     specified the default driver for the platform will be used.
-    /// </param>
-    public static void Init (ConsoleDriver driver = null, string driverName = null) { InternalInit (driver, driverName); }
-
-    internal static bool _initialized;
-    internal static int _mainThreadId = -1;
-
-    // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop.
-    //
-    // Called from:
-    // 
-    // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset.
-    // Run<T>() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run<T>() to be called without calling Init first.
-    // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset.
-    // 
-    // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset.
-    internal static void InternalInit (
-        ConsoleDriver driver = null,
-        string driverName = null,
-        bool calledViaRunT = false
-    )
-    {
-        if (_initialized && driver is null)
-        {
-            return;
-        }
-
-        if (_initialized)
-        {
-            throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown.");
-        }
-
-        if (!calledViaRunT)
-        {
-            // Reset all class variables (Application is a singleton).
-            ResetState ();
-        }
-
-        // For UnitTests
-        if (driver is { })
-        {
-            Driver = driver;
-        }
-
-        // Start the process of configuration management.
-        // Note that we end up calling LoadConfigurationFromAllSources
-        // multiple times. We need to do this because some settings are only
-        // valid after a Driver is loaded. In this cases we need just 
-        // `Settings` so we can determine which driver to use.
-        // Don't reset, so we can inherit the theme from the previous run.
-        Load ();
-        Apply ();
-
-        // Ignore Configuration for ForceDriver if driverName is specified
-        if (!string.IsNullOrEmpty (driverName))
-        {
-            ForceDriver = driverName;
-        }
-
-        if (Driver is null)
-        {
-            PlatformID p = Environment.OSVersion.Platform;
-
-            if (string.IsNullOrEmpty (ForceDriver))
-            {
-                if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
-                {
-                    Driver = new WindowsDriver ();
-                }
-                else
-                {
-                    Driver = new CursesDriver ();
-                }
-            }
-            else
-            {
-                List<Type> drivers = GetDriverTypes ();
-                Type driverType = drivers.FirstOrDefault (t => t.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase));
-
-                if (driverType is { })
-                {
-                    Driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-                }
-                else
-                {
-                    throw new ArgumentException (
-                                                 $"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t.Name))}"
-                                                );
-                }
-            }
-        }
-
-        try
-        {
-            MainLoop = Driver.Init ();
-        }
-        catch (InvalidOperationException ex)
-        {
-            // This is a case where the driver is unable to initialize the console.
-            // This can happen if the console is already in use by another process or
-            // if running in unit tests.
-            // In this case, we want to throw a more specific exception.
-            throw new InvalidOperationException (
-                                                 "Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.",
-                                                 ex
-                                                );
-        }
-
-        Driver.SizeChanged += (s, args) => OnSizeChanging (args);
-        Driver.KeyDown += (s, args) => OnKeyDown (args);
-        Driver.KeyUp += (s, args) => OnKeyUp (args);
-        Driver.MouseEvent += (s, args) => OnMouseEvent (args);
-
-        SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
-
-        SupportedCultures = GetSupportedCultures ();
-        _mainThreadId = Thread.CurrentThread.ManagedThreadId;
-        _initialized = true;
-        InitializedChanged?.Invoke (null, new (in _initialized));
-    }
-
-    private static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
-    private static void Driver_KeyDown (object sender, Key e) { OnKeyDown (e); }
-    private static void Driver_KeyUp (object sender, Key e) { OnKeyUp (e); }
-    private static void Driver_MouseEvent (object sender, MouseEvent e) { OnMouseEvent (e); }
-
-    /// <summary>Gets of list of <see cref="ConsoleDriver"/> types that are available.</summary>
-    /// <returns></returns>
-    public static List<Type> GetDriverTypes ()
-    {
-        // use reflection to get the list of drivers
-        List<Type> driverTypes = new ();
-
-        foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ())
-        {
-            foreach (Type type in asm.GetTypes ())
-            {
-                if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract)
-                {
-                    driverTypes.Add (type);
-                }
-            }
-        }
-
-        return driverTypes;
-    }
-
-    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
-    /// <remarks>
-    ///     Shutdown must be called for every call to <see cref="Init"/> or
-    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
-    ///     up (Disposed)
-    ///     and terminal settings are restored.
-    /// </remarks>
-    public static void Shutdown ()
-    {
-        // TODO: Throw an exception if Init hasn't been called.
-        ResetState ();
-        PrintJsonErrors ();
-        InitializedChanged?.Invoke (null, new (in _initialized));
-    }
-
-#nullable enable
-    /// <summary>
-    ///     This event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
-    /// </summary>
-    /// <remarks>
-    ///     Intended to support unit tests that need to know when the application has been initialized.
-    /// </remarks>
-    public static event EventHandler<EventArgs<bool>>? InitializedChanged;
-#nullable restore
-
-    #endregion Initialization (Init/Shutdown)
-
-    #region Run (Begin, Run, End, Stop)
-
-    /// <summary>
-    ///     Notify that a new <see cref="RunState"/> was created (<see cref="Begin(Toplevel)"/> was called). The token is
-    ///     created in <see cref="Begin(Toplevel)"/> and this event will be fired before that function exits.
-    /// </summary>
-    /// <remarks>
-    ///     If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to <see cref="Begin(Toplevel)"/>
-    ///     must also subscribe to <see cref="NotifyStopRunState"/> and manually dispose of the <see cref="RunState"/> token
-    ///     when the application is done.
-    /// </remarks>
-    public static event EventHandler<RunStateEventArgs> NotifyNewRunState;
-
-    /// <summary>Notify that a existent <see cref="RunState"/> is stopping (<see cref="End(RunState)"/> was called).</summary>
-    /// <remarks>
-    ///     If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to <see cref="Begin(Toplevel)"/>
-    ///     must also subscribe to <see cref="NotifyStopRunState"/> and manually dispose of the <see cref="RunState"/> token
-    ///     when the application is done.
-    /// </remarks>
-    public static event EventHandler<ToplevelEventArgs> NotifyStopRunState;
-
-    /// <summary>Building block API: Prepares the provided <see cref="Toplevel"/> for execution.</summary>
-    /// <returns>
-    ///     The <see cref="RunState"/> handle that needs to be passed to the <see cref="End(RunState)"/> method upon
-    ///     completion.
-    /// </returns>
-    /// <param name="toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
-    /// <remarks>
-    ///     This method prepares the provided <see cref="Toplevel"/> for running with the focus, it adds this to the list
-    ///     of <see cref="Toplevel"/>s, lays out the Subviews, focuses the first element, and draws the <see cref="Toplevel"/>
-    ///     in the screen. This is usually followed by executing the <see cref="RunLoop"/> method, and then the
-    ///     <see cref="End(RunState)"/> method upon termination which will undo these changes.
-    /// </remarks>
-    public static RunState Begin (Toplevel toplevel)
-    {
-        ArgumentNullException.ThrowIfNull (toplevel);
-
-#if DEBUG_IDISPOSABLE
-        Debug.Assert (!toplevel.WasDisposed);
-
-        if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel)
-        {
-            Debug.Assert (_cachedRunStateToplevel.WasDisposed);
-        }
-#endif
-
-        if (toplevel.IsOverlappedContainer && OverlappedTop != toplevel && OverlappedTop is { })
-        {
-            throw new InvalidOperationException ("Only one Overlapped Container is allowed.");
-        }
-
-        // Ensure the mouse is ungrabbed.
-        MouseGrabView = null;
-
-        var rs = new RunState (toplevel);
-
-        // View implements ISupportInitializeNotification which is derived from ISupportInitialize
-        if (!toplevel.IsInitialized)
-        {
-            toplevel.BeginInit ();
-            toplevel.EndInit ();
-        }
-
-#if DEBUG_IDISPOSABLE
-        if (Top is { } && toplevel != Top && !_topLevels.Contains (Top))
-        {
-            // This assertion confirm if the Top was already disposed
-            Debug.Assert (Top.WasDisposed);
-            Debug.Assert (Top == _cachedRunStateToplevel);
-        }
-#endif
-
-        lock (_topLevels)
-        {
-            if (Top is { } && toplevel != Top && !_topLevels.Contains (Top))
-            {
-                // If Top was already disposed and isn't on the Toplevels Stack,
-                // clean it up here if is the same as _cachedRunStateToplevel
-                if (Top == _cachedRunStateToplevel)
-                {
-                    Top = null;
-                }
-                else
-                {
-                    // Probably this will never hit
-                    throw new ObjectDisposedException (Top.GetType ().FullName);
-                }
-            }
-            else if (OverlappedTop is { } && toplevel != Top && _topLevels.Contains (Top))
-            {
-                Top.OnLeave (toplevel);
-            }
-
-            // BUGBUG: We should not depend on `Id` internally. 
-            // BUGBUG: It is super unclear what this code does anyway.
-            if (string.IsNullOrEmpty (toplevel.Id))
-            {
-                var count = 1;
-                var id = (_topLevels.Count + count).ToString ();
-
-                while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) is { })
-                {
-                    count++;
-                    id = (_topLevels.Count + count).ToString ();
-                }
-
-                toplevel.Id = (_topLevels.Count + count).ToString ();
-
-                _topLevels.Push (toplevel);
-            }
-            else
-            {
-                Toplevel dup = _topLevels.FirstOrDefault (x => x.Id == toplevel.Id);
-
-                if (dup is null)
-                {
-                    _topLevels.Push (toplevel);
-                }
-            }
-
-            if (_topLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0)
-            {
-                throw new ArgumentException ("There are duplicates Toplevel IDs");
-            }
-        }
-
-        if (Top is null || toplevel.IsOverlappedContainer)
-        {
-            Top = toplevel;
-        }
-
-        var refreshDriver = true;
-
-        if (OverlappedTop is null
-            || toplevel.IsOverlappedContainer
-            || (Current?.Modal == false && toplevel.Modal)
-            || (Current?.Modal == false && !toplevel.Modal)
-            || (Current?.Modal == true && toplevel.Modal))
-        {
-            if (toplevel.Visible)
-            {
-                Current?.OnDeactivate (toplevel);
-                Toplevel previousCurrent = Current;
-                Current = toplevel;
-                Current.OnActivate (previousCurrent);
-
-                SetCurrentOverlappedAsTop ();
-            }
-            else
-            {
-                refreshDriver = false;
-            }
-        }
-        else if ((OverlappedTop != null
-                  && toplevel != OverlappedTop
-                  && Current?.Modal == true
-                  && !_topLevels.Peek ().Modal)
-                 || (OverlappedTop is { } && toplevel != OverlappedTop && Current?.Running == false))
-        {
-            refreshDriver = false;
-            MoveCurrent (toplevel);
-        }
-        else
-        {
-            refreshDriver = false;
-            MoveCurrent (Current);
-        }
-
-        toplevel.SetRelativeLayout (Driver.Screen.Size);
-
-        toplevel.LayoutSubviews ();
-        toplevel.PositionToplevels ();
-        toplevel.FocusFirst ();
-        BringOverlappedTopToFront ();
-
-        if (refreshDriver)
-        {
-            OverlappedTop?.OnChildLoaded (toplevel);
-            toplevel.OnLoaded ();
-            toplevel.SetNeedsDisplay ();
-            toplevel.Draw ();
-            Driver.UpdateScreen ();
-
-            if (PositionCursor (toplevel))
-            {
-                Driver.UpdateCursor ();
-            }
-        }
-
-        NotifyNewRunState?.Invoke (toplevel, new (rs));
-
-        return rs;
-    }
-
-    /// <summary>
-    ///     Calls <see cref="View.PositionCursor"/> on the most focused view in the view starting with <paramref name="view"/>.
-    /// </summary>
-    /// <remarks>
-    ///     Does nothing if <paramref name="view"/> is <see langword="null"/> or if the most focused view is not visible or
-    ///     enabled.
-    ///     <para>
-    ///         If the most focused view is not visible within it's superview, the cursor will be hidden.
-    ///     </para>
-    /// </remarks>
-    /// <returns><see langword="true"/> if a view positioned the cursor and the position is visible.</returns>
-    internal static bool PositionCursor (View view)
-    {
-        // Find the most focused view and position the cursor there.
-        View mostFocused = view?.MostFocused;
-
-        if (mostFocused is null)
-        {
-            if (view is { HasFocus: true })
-            {
-                mostFocused = view;
-            }
-            else
-            {
-                return false;
-            }
-        }
-
-        // If the view is not visible or enabled, don't position the cursor
-        if (!mostFocused.Visible || !mostFocused.Enabled)
-        {
-            Driver.GetCursorVisibility (out CursorVisibility current);
-
-            if (current != CursorVisibility.Invisible)
-            {
-                Driver.SetCursorVisibility (CursorVisibility.Invisible);
-            }
-
-            return false;
-        }
-
-        // If the view is not visible within it's superview, don't position the cursor
-        Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty });
-        Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver.Screen;
-
-        if (!superViewViewport.IntersectsWith (mostFocusedViewport))
-        {
-            return false;
-        }
-
-        Point? cursor = mostFocused.PositionCursor ();
-
-        Driver.GetCursorVisibility (out CursorVisibility currentCursorVisibility);
-
-        if (cursor is { })
-        {
-            // Convert cursor to screen coords
-            cursor = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = cursor.Value }).Location;
-
-            // If the cursor is not in a visible location in the SuperView, hide it
-            if (!superViewViewport.Contains (cursor.Value))
-            {
-                if (currentCursorVisibility != CursorVisibility.Invisible)
-                {
-                    Driver.SetCursorVisibility (CursorVisibility.Invisible);
-                }
-
-                return false;
-            }
-
-            // Show it
-            if (currentCursorVisibility == CursorVisibility.Invisible)
-            {
-                Driver.SetCursorVisibility (mostFocused.CursorVisibility);
-            }
-
-            return true;
-        }
-
-        if (currentCursorVisibility != CursorVisibility.Invisible)
-        {
-            Driver.SetCursorVisibility (CursorVisibility.Invisible);
-        }
-
-        return false;
-    }
-
-    /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
-    /// </summary>
-    /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
-    ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
-    ///         ensure resources are cleaned up and terminal settings restored.
-    ///     </para>
-    ///     <para>
-    ///         The caller is responsible for disposing the object returned by this method.
-    ///     </para>
-    /// </remarks>
-    /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
-    public static Toplevel Run (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null) { return Run<Toplevel> (errorHandler, driver); }
-
-    /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
-    /// </summary>
-    /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
-    ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
-    ///         ensure resources are cleaned up and terminal settings restored.
-    ///     </para>
-    ///     <para>
-    ///         The caller is responsible for disposing the object returned by this method.
-    ///     </para>
-    /// </remarks>
-    /// <param name="errorHandler"></param>
-    /// <param name="driver">
-    ///     The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the platform will
-    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
-    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
-    /// </param>
-    /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
-    public static T Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null)
-        where T : Toplevel, new()
-    {
-        if (!_initialized)
-        {
-            // Init() has NOT been called.
-            InternalInit (driver, null, true);
-        }
-
-        var top = new T ();
-
-        Run (top, errorHandler);
-
-        return top;
-    }
-
-    /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         This method is used to start processing events for the main application, but it is also used to run other
-    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
-    ///     </para>
-    ///     <para>
-    ///         To make a <see cref="Run(Toplevel, Func{Exception, bool})"/> stop execution, call
-    ///         <see cref="Application.RequestStop"/>.
-    ///     </para>
-    ///     <para>
-    ///         Calling <see cref="Run(Toplevel, Func{Exception, bool})"/> is equivalent to calling
-    ///         <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState)"/>, and then calling
-    ///         <see cref="End(RunState)"/>.
-    ///     </para>
-    ///     <para>
-    ///         Alternatively, to have a program control the main loop and process events manually, call
-    ///         <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
-    ///         <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
-    ///         return control immediately.
-    ///     </para>
-    ///     <para>When using <see cref="Run{T}"/> or
-    ///         <see cref="Run(System.Func{System.Exception,bool},Terminal.Gui.ConsoleDriver)"/>
-    ///         <see cref="Init"/> will be called automatically.
-    ///     </para>
-    ///     <para>
-    ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
-    ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
-    ///         returns <see langword="true"/> the <see cref="RunLoop(RunState)"/> will resume; otherwise this method will
-    ///         exit.
-    ///     </para>
-    /// </remarks>
-    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
-    /// <param name="errorHandler">
-    ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
-    ///     rethrows when null).
-    /// </param>
-    public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
-    {
-        ArgumentNullException.ThrowIfNull (view);
-
-        if (_initialized)
-        {
-            if (Driver is null)
-            {
-                // Disposing before throwing
-                view.Dispose ();
-
-                // This code path should be impossible because Init(null, null) will select the platform default driver
-                throw new InvalidOperationException (
-                                                     "Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called."
-                                                    );
-            }
-        }
-        else
-        {
-            // Init() has NOT been called.
-            throw new InvalidOperationException (
-                                                 "Init() has not been called. Only Run() or Run<T>() can be used without calling Init()."
-                                                );
-        }
-
-        var resume = true;
-
-        while (resume)
-        {
-#if !DEBUG
-            try
-            {
-#endif
-            resume = false;
-            RunState runState = Begin (view);
-
-            // If EndAfterFirstIteration is true then the user must dispose of the runToken
-            // by using NotifyStopRunState event.
-            RunLoop (runState);
-
-            if (runState.Toplevel is null)
-            {
-#if DEBUG_IDISPOSABLE
-                Debug.Assert (_topLevels.Count == 0);
-#endif
-                runState.Dispose ();
-
-                return;
-            }
-
-            if (!EndAfterFirstIteration)
-            {
-                End (runState);
-            }
-#if !DEBUG
-            }
-            catch (Exception error)
-            {
-                if (errorHandler is null)
-                {
-                    throw;
-                }
-
-                resume = errorHandler (error);
-            }
-#endif
-        }
-    }
-
-    /// <summary>Adds a timeout to the application.</summary>
-    /// <remarks>
-    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
-    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
-    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
-    /// </remarks>
-    public static object AddTimeout (TimeSpan time, Func<bool> callback) { return MainLoop?.AddTimeout (time, callback); }
-
-    /// <summary>Removes a previously scheduled timeout</summary>
-    /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
-    /// Returns
-    /// <c>true</c>
-    /// if the timeout is successfully removed; otherwise,
-    /// <c>false</c>
-    /// .
-    /// This method also returns
-    /// <c>false</c>
-    /// if the timeout is not found.
-    public static bool RemoveTimeout (object token) { return MainLoop?.RemoveTimeout (token) ?? false; }
-
-    /// <summary>Runs <paramref name="action"/> on the thread that is processing events</summary>
-    /// <param name="action">the action to be invoked on the main processing thread.</param>
-    public static void Invoke (Action action)
-    {
-        MainLoop?.AddIdle (
-                           () =>
-                           {
-                               action ();
-
-                               return false;
-                           }
-                          );
-    }
-
-    // TODO: Determine if this is really needed. The only code that calls WakeUp I can find
-    // is ProgressBarStyles and it's not clear it needs to.
-    /// <summary>Wakes up the running application that might be waiting on input.</summary>
-    public static void Wakeup () { MainLoop?.Wakeup (); }
-
-    /// <summary>Triggers a refresh of the entire display.</summary>
-    public static void Refresh ()
-    {
-        // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear
-        Driver.ClearContents ();
-        View last = null;
-
-        foreach (Toplevel v in _topLevels.Reverse ())
-        {
-            if (v.Visible)
-            {
-                v.SetNeedsDisplay ();
-                v.SetSubViewNeedsDisplay ();
-                v.Draw ();
-            }
-
-            last = v;
-        }
-
-        Driver.Refresh ();
-    }
-
-    /// <summary>This event is raised on each iteration of the main loop.</summary>
-    /// <remarks>See also <see cref="Timeout"/></remarks>
-    public static event EventHandler<IterationEventArgs> Iteration;
-
-    /// <summary>The <see cref="MainLoop"/> driver for the application</summary>
-    /// <value>The main loop.</value>
-    internal static MainLoop MainLoop { get; private set; }
-
-    /// <summary>
-    ///     Set to true to cause <see cref="End"/> to be called after the first iteration. Set to false (the default) to
-    ///     cause the application to continue running until Application.RequestStop () is called.
-    /// </summary>
-    public static bool EndAfterFirstIteration { get; set; }
-
-    /// <summary>Building block API: Runs the main loop for the created <see cref="Toplevel"/>.</summary>
-    /// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
-    public static void RunLoop (RunState state)
-    {
-        ArgumentNullException.ThrowIfNull (state);
-        ObjectDisposedException.ThrowIf (state.Toplevel is null, "state");
-
-        var firstIteration = true;
-
-        for (state.Toplevel.Running = true; state.Toplevel?.Running == true;)
-        {
-            MainLoop.Running = true;
-
-            if (EndAfterFirstIteration && !firstIteration)
-            {
-                return;
-            }
-
-            RunIteration (ref state, ref firstIteration);
-        }
-
-        MainLoop.Running = false;
-
-        // Run one last iteration to consume any outstanding input events from Driver
-        // This is important for remaining OnKeyUp events.
-        RunIteration (ref state, ref firstIteration);
-    }
-
-    /// <summary>Run one application iteration.</summary>
-    /// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
-    /// <param name="firstIteration">
-    ///     Set to <see langword="true"/> if this is the first run loop iteration. Upon return, it
-    ///     will be set to <see langword="false"/> if at least one iteration happened.
-    /// </param>
-    public static void RunIteration (ref RunState state, ref bool firstIteration)
-    {
-        if (MainLoop.Running && MainLoop.EventsPending ())
-        {
-            // Notify Toplevel it's ready
-            if (firstIteration)
-            {
-                state.Toplevel.OnReady ();
-            }
-
-            MainLoop.RunIteration ();
-            Iteration?.Invoke (null, new ());
-            EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
-
-            if (state.Toplevel != Current)
-            {
-                OverlappedTop?.OnDeactivate (state.Toplevel);
-                state.Toplevel = Current;
-                OverlappedTop?.OnActivate (state.Toplevel);
-                Top.SetSubViewNeedsDisplay ();
-                Refresh ();
-            }
-        }
-
-        firstIteration = false;
-
-        if (Current == null)
-        {
-            return;
-        }
-
-        if (state.Toplevel != Top && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
-        {
-            state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame);
-            Top.Draw ();
-
-            foreach (Toplevel top in _topLevels.Reverse ())
-            {
-                if (top != Top && top != state.Toplevel)
-                {
-                    top.SetNeedsDisplay ();
-                    top.SetSubViewNeedsDisplay ();
-                    top.Draw ();
-                }
-            }
-        }
-
-        if (_topLevels.Count == 1
-            && state.Toplevel == Top
-            && (Driver.Cols != state.Toplevel.Frame.Width
-                || Driver.Rows != state.Toplevel.Frame.Height)
-            && (state.Toplevel.NeedsDisplay
-                || state.Toplevel.SubViewNeedsDisplay
-                || state.Toplevel.LayoutNeeded))
-        {
-            Driver.ClearContents ();
-        }
-
-        if (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ())
-        {
-            state.Toplevel.SetNeedsDisplay ();
-            state.Toplevel.Draw ();
-            Driver.UpdateScreen ();
-
-            //Driver.UpdateCursor ();
-        }
-
-        if (PositionCursor (state.Toplevel))
-        {
-            Driver.UpdateCursor ();
-        }
-
-        //        else
-        {
-            //if (PositionCursor (state.Toplevel))
-            //{
-            //    Driver.Refresh ();
-            //}
-            //Driver.UpdateCursor ();
-        }
-
-        if (state.Toplevel != Top && !state.Toplevel.Modal && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
-        {
-            Top.Draw ();
-        }
-    }
-
-    /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
-    /// <param name="top">The <see cref="Toplevel"/> to stop.</param>
-    /// <remarks>
-    ///     <para>This will cause <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to return.</para>
-    ///     <para>
-    ///         Calling <see cref="Application.RequestStop"/> is equivalent to setting the <see cref="Toplevel.Running"/>
-    ///         property on the currently running <see cref="Toplevel"/> to false.
-    ///     </para>
-    /// </remarks>
-    public static void RequestStop (Toplevel top = null)
-    {
-        if (OverlappedTop is null || top is null || (OverlappedTop is null && top is { }))
-        {
-            top = Current;
-        }
-
-        if (OverlappedTop != null
-            && top.IsOverlappedContainer
-            && top?.Running == true
-            && (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false)))
-        {
-            OverlappedTop.RequestStop ();
-        }
-        else if (OverlappedTop != null
-                 && top != Current
-                 && Current?.Running == true
-                 && Current?.Modal == true
-                 && top.Modal
-                 && top.Running)
-        {
-            var ev = new ToplevelClosingEventArgs (Current);
-            Current.OnClosing (ev);
-
-            if (ev.Cancel)
-            {
-                return;
-            }
-
-            ev = new (top);
-            top.OnClosing (ev);
-
-            if (ev.Cancel)
-            {
-                return;
-            }
-
-            Current.Running = false;
-            OnNotifyStopRunState (Current);
-            top.Running = false;
-            OnNotifyStopRunState (top);
-        }
-        else if ((OverlappedTop != null
-                  && top != OverlappedTop
-                  && top != Current
-                  && Current?.Modal == false
-                  && Current?.Running == true
-                  && !top.Running)
-                 || (OverlappedTop != null
-                     && top != OverlappedTop
-                     && top != Current
-                     && Current?.Modal == false
-                     && Current?.Running == false
-                     && !top.Running
-                     && _topLevels.ToArray () [1].Running))
-        {
-            MoveCurrent (top);
-        }
-        else if (OverlappedTop != null
-                 && Current != top
-                 && Current?.Running == true
-                 && !top.Running
-                 && Current?.Modal == true
-                 && top.Modal)
-        {
-            // The Current and the top are both modal so needed to set the Current.Running to false too.
-            Current.Running = false;
-            OnNotifyStopRunState (Current);
-        }
-        else if (OverlappedTop != null
-                 && Current == top
-                 && OverlappedTop?.Running == true
-                 && Current?.Running == true
-                 && top.Running
-                 && Current?.Modal == true
-                 && top.Modal)
-        {
-            // The OverlappedTop was requested to stop inside a modal Toplevel which is the Current and top,
-            // both are the same, so needed to set the Current.Running to false too.
-            Current.Running = false;
-            OnNotifyStopRunState (Current);
-        }
-        else
-        {
-            Toplevel currentTop;
-
-            if (top == Current || (Current?.Modal == true && !top.Modal))
-            {
-                currentTop = Current;
-            }
-            else
-            {
-                currentTop = top;
-            }
-
-            if (!currentTop.Running)
-            {
-                return;
-            }
-
-            var ev = new ToplevelClosingEventArgs (currentTop);
-            currentTop.OnClosing (ev);
-
-            if (ev.Cancel)
-            {
-                return;
-            }
-
-            currentTop.Running = false;
-            OnNotifyStopRunState (currentTop);
-        }
-    }
-
-    private static void OnNotifyStopRunState (Toplevel top)
-    {
-        if (EndAfterFirstIteration)
-        {
-            NotifyStopRunState?.Invoke (top, new (top));
-        }
-    }
-
-    /// <summary>
-    ///     Building block API: completes the execution of a <see cref="Toplevel"/> that was started with
-    ///     <see cref="Begin(Toplevel)"/> .
-    /// </summary>
-    /// <param name="runState">The <see cref="RunState"/> returned by the <see cref="Begin(Toplevel)"/> method.</param>
-    public static void End (RunState runState)
-    {
-        ArgumentNullException.ThrowIfNull (runState);
-
-        if (OverlappedTop is { })
-        {
-            OverlappedTop.OnChildUnloaded (runState.Toplevel);
-        }
-        else
-        {
-            runState.Toplevel.OnUnloaded ();
-        }
-
-        // End the RunState.Toplevel 
-        // First, take it off the Toplevel Stack
-        if (_topLevels.Count > 0)
-        {
-            if (_topLevels.Peek () != runState.Toplevel)
-            {
-                // If there the top of the stack is not the RunState.Toplevel then
-                // this call to End is not balanced with the call to Begin that started the RunState
-                throw new ArgumentException ("End must be balanced with calls to Begin");
-            }
-
-            _topLevels.Pop ();
-        }
-
-        // Notify that it is closing
-        runState.Toplevel?.OnClosed (runState.Toplevel);
-
-        // If there is a OverlappedTop that is not the RunState.Toplevel then runstate.TopLevel 
-        // is a child of MidTop and we should notify the OverlappedTop that it is closing
-        if (OverlappedTop is { } && !runState.Toplevel.Modal && runState.Toplevel != OverlappedTop)
-        {
-            OverlappedTop.OnChildClosed (runState.Toplevel);
-        }
-
-        // Set Current and Top to the next TopLevel on the stack
-        if (_topLevels.Count == 0)
-        {
-            Current = null;
-        }
-        else
-        {
-            if (_topLevels.Count > 1 && _topLevels.Peek () == OverlappedTop && OverlappedChildren.Any (t => t.Visible) is { })
-            {
-                OverlappedMoveNext ();
-            }
-
-            Current = _topLevels.Peek ();
-
-            if (_topLevels.Count == 1 && Current == OverlappedTop)
-            {
-                OverlappedTop.OnAllChildClosed ();
-            }
-            else
-            {
-                SetCurrentOverlappedAsTop ();
-                runState.Toplevel.OnLeave (Current);
-                Current.OnEnter (runState.Toplevel);
-            }
-
-            Refresh ();
-        }
-
-        // Don't dispose runState.Toplevel. It's up to caller dispose it
-        // If it's not the same as the current in the RunIteration,
-        // it will be fixed later in the next RunIteration.
-        if (OverlappedTop is { } && !_topLevels.Contains (OverlappedTop))
-        {
-            _cachedRunStateToplevel = OverlappedTop;
-        }
-        else
-        {
-            _cachedRunStateToplevel = runState.Toplevel;
-        }
-
-        runState.Toplevel = null;
-        runState.Dispose ();
-    }
-
-    #endregion Run (Begin, Run, End)
-
-    #region Toplevel handling
-
-    /// <summary>Holds the stack of TopLevel views.</summary>
-
-    // BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What
-    // about TopLevels that are just a SubView of another View?
-    internal static readonly Stack<Toplevel> _topLevels = new ();
-
-    /// <summary>The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)</summary>
-    /// <value>The top.</value>
-    public static Toplevel Top { get; private set; }
-
-    /// <summary>
-    ///     The current <see cref="Toplevel"/> object. This is updated in <see cref="Application.Begin"/> enters and leaves to
-    ///     point to the current
-    ///     <see cref="Toplevel"/> .
-    /// </summary>
-    /// <remarks>
-    ///     Only relevant in scenarios where <see cref="Toplevel.IsOverlappedContainer"/> is <see langword="true"/>.
-    /// </remarks>
-    /// <value>The current.</value>
-    public static Toplevel Current { get; private set; }
-
-    private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel)
-    {
-        if (!topLevel.Running
-            || (topLevel == Current && topLevel.Visible)
-            || OverlappedTop == null
-            || _topLevels.Peek ().Modal)
-        {
-            return;
-        }
-
-        foreach (Toplevel top in _topLevels.Reverse ())
-        {
-            if (top.Modal && top != Current)
-            {
-                MoveCurrent (top);
-
-                return;
-            }
-        }
-
-        if (!topLevel.Visible && topLevel == Current)
-        {
-            OverlappedMoveNext ();
-        }
-    }
-
-#nullable enable
-    private static Toplevel? FindDeepestTop (Toplevel start, in Point location)
-    {
-        if (!start.Frame.Contains (location))
-        {
-            return null;
-        }
-
-        if (_topLevels is { Count: > 0 })
-        {
-            int rx = location.X - start.Frame.X;
-            int ry = location.Y - start.Frame.Y;
-
-            foreach (Toplevel t in _topLevels)
-            {
-                if (t != Current)
-                {
-                    if (t != start && t.Visible && t.Frame.Contains (rx, ry))
-                    {
-                        start = t;
-
-                        break;
-                    }
-                }
-            }
-        }
-
-        return start;
-    }
-#nullable restore
-
-    private static View FindTopFromView (View view)
-    {
-        View top = view?.SuperView is { } && view?.SuperView != Top
-                       ? view.SuperView
-                       : view;
-
-        while (top?.SuperView is { } && top?.SuperView != Top)
-        {
-            top = top.SuperView;
-        }
-
-        return top;
-    }
-
-#nullable enable
-
     // Only return true if the Current has changed.
-    private static bool MoveCurrent (Toplevel? top)
-    {
-        // The Current is modal and the top is not modal Toplevel then
-        // the Current must be moved above the first not modal Toplevel.
-        if (OverlappedTop is { }
-            && top != OverlappedTop
-            && top != Current
-            && Current?.Modal == true
-            && !_topLevels.Peek ().Modal)
-        {
-            lock (_topLevels)
-            {
-                _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
-            }
-
-            var index = 0;
-            Toplevel [] savedToplevels = _topLevels.ToArray ();
-
-            foreach (Toplevel t in savedToplevels)
-            {
-                if (!t.Modal && t != Current && t != top && t != savedToplevels [index])
-                {
-                    lock (_topLevels)
-                    {
-                        _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ());
-                    }
-                }
-
-                index++;
-            }
-
-            return false;
-        }
-
-        // The Current and the top are both not running Toplevel then
-        // the top must be moved above the first not running Toplevel.
-        if (OverlappedTop is { }
-            && top != OverlappedTop
-            && top != Current
-            && Current?.Running == false
-            && top?.Running == false)
-        {
-            lock (_topLevels)
-            {
-                _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
-            }
-
-            var index = 0;
-
-            foreach (Toplevel t in _topLevels.ToArray ())
-            {
-                if (!t.Running && t != Current && index > 0)
-                {
-                    lock (_topLevels)
-                    {
-                        _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
-                    }
-                }
-
-                index++;
-            }
-
-            return false;
-        }
-
-        if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top)
-            || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop)
-            || (OverlappedTop is { } && Current?.Modal == false && top != Current)
-            || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop))
-        {
-            lock (_topLevels)
-            {
-                _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
-                Current = top;
-            }
-        }
-
-        return true;
-    }
-#nullable restore
-
-    /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
-    /// <remarks>
-    ///     Event handlers can set <see cref="SizeChangedEventArgs.Cancel"/> to <see langword="true"/> to prevent
-    ///     <see cref="Application"/> from changing it's size to match the new terminal size.
-    /// </remarks>
-    public static event EventHandler<SizeChangedEventArgs> SizeChanging;
-
-    /// <summary>
-    ///     Called when the application's size changes. Sets the size of all <see cref="Toplevel"/>s and fires the
-    ///     <see cref="SizeChanging"/> event.
-    /// </summary>
-    /// <param name="args">The new size.</param>
-    /// <returns><see lanword="true"/>if the size was changed.</returns>
-    public static bool OnSizeChanging (SizeChangedEventArgs args)
-    {
-        SizeChanging?.Invoke (null, args);
-
-        if (args.Cancel || args.Size is null)
-        {
-            return false;
-        }
-
-        foreach (Toplevel t in _topLevels)
-        {
-            t.SetRelativeLayout (args.Size.Value);
-            t.LayoutSubviews ();
-            t.PositionToplevels ();
-            t.OnSizeChanging (new (args.Size));
-
-            if (PositionCursor (t))
-            {
-                Driver.UpdateCursor ();
-            }
-        }
-
-        Refresh ();
-
-        return true;
-    }
-
-    #endregion Toplevel handling
-
-    /// <summary>
-    ///     Gets a string representation of the Application as rendered by <see cref="Driver"/>.
-    /// </summary>
-    /// <returns>A string representation of the Application </returns>
-    public new static string ToString ()
-    {
-        ConsoleDriver driver = Driver;
-
-        if (driver is null)
-        {
-            return string.Empty;
-        }
-
-        return ToString (driver);
-    }
-
-    /// <summary>
-    ///     Gets a string representation of the Application rendered by the provided <see cref="ConsoleDriver"/>.
-    /// </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)
-    {
-        var sb = new StringBuilder ();
-
-        Cell [,] contents = driver.Contents;
-
-        for (var r = 0; r < driver.Rows; r++)
-        {
-            for (var c = 0; c < driver.Cols; c++)
-            {
-                Rune rune = contents [r, c].Rune;
-
-                if (rune.DecodeSurrogatePair (out char [] sp))
-                {
-                    sb.Append (sp);
-                }
-                else
-                {
-                    sb.Append ((char)rune.Value);
-                }
-
-                if (rune.GetColumns () > 1)
-                {
-                    c++;
-                }
-
-                // See Issue #2616
-                //foreach (var combMark in contents [r, c].CombiningMarks) {
-                //	sb.Append ((char)combMark.Value);
-                //}
-            }
-
-            sb.AppendLine ();
-        }
-        return sb.ToString ();
-    }
 }

+ 0 - 303
Terminal.Gui/Application/ApplicationKeyboard.cs

@@ -1,303 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace Terminal.Gui;
-
-partial class Application
-{
-    private static Key _alternateForwardKey = Key.Empty; // Defined in config.json
-
-    /// <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 AlternateForwardKey
-    {
-        get => _alternateForwardKey;
-        set
-        {
-            if (_alternateForwardKey != value)
-            {
-                Key oldKey = _alternateForwardKey;
-                _alternateForwardKey = value;
-                OnAlternateForwardKeyChanged (new (oldKey, value));
-            }
-        }
-    }
-
-    private static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
-    {
-        foreach (Toplevel top in _topLevels.ToArray ())
-        {
-            top.OnAlternateForwardKeyChanged (e);
-        }
-    }
-
-    private static Key _alternateBackwardKey = Key.Empty; // Defined in config.json
-
-    /// <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 AlternateBackwardKey
-    {
-        get => _alternateBackwardKey;
-        set
-        {
-            if (_alternateBackwardKey != value)
-            {
-                Key oldKey = _alternateBackwardKey;
-                _alternateBackwardKey = value;
-                OnAlternateBackwardKeyChanged (new (oldKey, value));
-            }
-        }
-    }
-
-    private static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey)
-    {
-        foreach (Toplevel top in _topLevels.ToArray ())
-        {
-            top.OnAlternateBackwardKeyChanged (oldKey);
-        }
-    }
-
-    private static Key _quitKey = Key.Empty; // Defined in config.json
-
-    /// <summary>Gets or sets the key to quit the application.</summary>
-    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
-    public static Key QuitKey
-    {
-        get => _quitKey;
-        set
-        {
-            if (_quitKey != value)
-            {
-                Key oldKey = _quitKey;
-                _quitKey = value;
-                OnQuitKeyChanged (new (oldKey, value));
-            }
-        }
-    }
-
-    private static void OnQuitKeyChanged (KeyChangedEventArgs e)
-    {
-        // Duplicate the list so if it changes during enumeration we're safe
-        foreach (Toplevel top in _topLevels.ToArray ())
-        {
-            top.OnQuitKeyChanged (e);
-        }
-    }
-
-    /// <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
-    ///     before <see cref="OnKeyUp"/>.
-    /// </summary>
-    /// <remarks>Can be used to simulate key press events.</remarks>
-    /// <param name="keyEvent"></param>
-    /// <returns><see langword="true"/> if the key was handled.</returns>
-    public static bool OnKeyDown (Key keyEvent)
-    {
-        if (!_initialized)
-        {
-            return true;
-        }
-
-        KeyDown?.Invoke (null, keyEvent);
-
-        if (keyEvent.Handled)
-        {
-            return true;
-        }
-
-        foreach (Toplevel topLevel in _topLevels.ToList ())
-        {
-            if (topLevel.NewKeyDownEvent (keyEvent))
-            {
-                return true;
-            }
-
-            if (topLevel.Modal)
-            {
-                break;
-            }
-        }
-
-        // Invoke any global (Application-scoped) KeyBindings.
-        // The first view that handles the key will stop the loop.
-        foreach (KeyValuePair<Key, List<View>> binding in _keyBindings.Where (b => b.Key == keyEvent.KeyCode))
-        {
-            foreach (View view in binding.Value)
-            {
-                if (view is {} && view.KeyBindings.TryGet (binding.Key, (KeyBindingScope)0xFFFF, out KeyBinding kb))
-                {
-                    //bool? handled = view.InvokeCommands (kb.Commands, binding.Key, kb);
-                    bool? handled = view?.OnInvokingKeyBindings (keyEvent, kb.Scope);
-
-                    if (handled != null && (bool)handled)
-                    {
-                        return true;
-                    }
-
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /// <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>
-    ///     Called by the <see cref="ConsoleDriver"/> when the user releases a key. Fires the <see cref="KeyUp"/> event
-    ///     then calls <see cref="View.NewKeyUpEvent"/> on all top level views. Called after <see cref="OnKeyDown"/>.
-    /// </summary>
-    /// <remarks>Can be used to simulate key press events.</remarks>
-    /// <param name="a"></param>
-    /// <returns><see langword="true"/> if the key was handled.</returns>
-    public static bool OnKeyUp (Key a)
-    {
-        if (!_initialized)
-        {
-            return true;
-        }
-
-        KeyUp?.Invoke (null, a);
-
-        if (a.Handled)
-        {
-            return true;
-        }
-
-        foreach (Toplevel topLevel in _topLevels.ToList ())
-        {
-            if (topLevel.NewKeyUpEvent (a))
-            {
-                return true;
-            }
-
-            if (topLevel.Modal)
-            {
-                break;
-            }
-        }
-
-        return false;
-    }
-
-    /// <summary>
-    ///     The <see cref="KeyBindingScope.Application"/> key bindings.
-    /// </summary>
-    private static readonly Dictionary<Key, List<View>> _keyBindings = new ();
-
-    /// <summary>
-    /// Gets the list of <see cref="KeyBindingScope.Application"/> key bindings.
-    /// </summary>
-    public static Dictionary<Key, List<View>> GetKeyBindings () { return _keyBindings; }
-
-    /// <summary>
-    ///     Adds an  <see cref="KeyBindingScope.Application"/> scoped key binding.
-    /// </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 being bound.</param>
-    /// <param name="view">The view that is bound to the key.</param>
-    internal static void AddKeyBinding (Key key, View view)
-    {
-        if (!_keyBindings.ContainsKey (key))
-        {
-            _keyBindings [key] = [];
-        }
-
-        _keyBindings [key].Add (view);
-    }
-
-    /// <summary>
-    ///     Gets the list of Views that have <see cref="KeyBindingScope.Application"/> key bindings.
-    /// </summary>
-    /// <remarks>
-    ///     This is an internal method used by the <see cref="View"/> class to add Application key bindings.
-    /// </remarks>
-    /// <returns>The list of Views that have Application-scoped key bindings.</returns>
-    internal static List<View> GetViewsWithKeyBindings () { return _keyBindings.Values.SelectMany (v => v).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 an <see cref="KeyBindingScope.Application"/> scoped key binding.
-    /// </summary>
-    /// <remarks>
-    ///     This is an internal method used by the <see cref="View"/> class to remove Application key bindings.
-    /// </remarks>
-    /// <param name="key">The key that was bound.</param>
-    /// <param name="view">The view that is bound to the key.</param>
-    internal static void RemoveKeyBinding (Key key, View view)
-    {
-        if (_keyBindings.TryGetValue (key, out List<View> views))
-        {
-            views.Remove (view);
-
-            if (views.Count == 0)
-            {
-                _keyBindings.Remove (key);
-            }
-        }
-    }
-
-    /// <summary>
-    ///     Removes all <see cref="KeyBindingScope.Application"/> scoped key bindings for the specified view.
-    /// </summary>
-    /// <remarks>
-    ///     This is an internal method used by the <see cref="View"/> class to remove Application key bindings.
-    /// </remarks>
-    /// <param name="view">The view that is bound to the key.</param>
-    internal static void ClearKeyBindings (View view)
-    {
-        foreach (Key key in _keyBindings.Keys)
-        {
-            _keyBindings [key].Remove (view);
-        }
-    }
-
-    /// <summary>
-    ///     Removes all <see cref="KeyBindingScope.Application"/> scoped key bindings for the specified view.
-    /// </summary>
-    /// <remarks>
-    ///     This is an internal method used by the <see cref="View"/> class to remove Application key bindings.
-    /// </remarks>
-    internal static void ClearKeyBindings () { _keyBindings.Clear (); }
-}

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

@@ -0,0 +1,229 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Helper class for <see cref="Application"/> navigation. Held by <see cref="Application.Navigation"/>
+/// </summary>
+public class ApplicationNavigation
+{
+
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="ApplicationNavigation"/> class.
+    /// </summary>
+    public 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;
+    }
+
+    /// <summary>
+    ///     Raised when the most focused <see cref="View"/> in the application has changed.
+    /// </summary>
+    public event EventHandler<EventArgs>? FocusedChanged;
+
+
+    /// <summary>
+    ///     Gets whether <paramref name="view"/> is in the Subview hierarchy of <paramref name="start"/>.
+    /// </summary>
+    /// <param name="start"></param>
+    /// <param name="view"></param>
+    /// <returns></returns>
+    public static bool IsInHierarchy (View start, View? view)
+    {
+        if (view is null)
+        {
+            return false;
+        }
+
+        if (view == start)
+        {
+            return true;
+        }
+
+        foreach (View subView in start.Subviews)
+        {
+            if (view == subView)
+            {
+                return true;
+            }
+
+            var found = IsInHierarchy (subView, view);
+            if (found)
+            {
+                return found;
+            }
+        }
+
+        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.
+    /// </summary>
+    internal static void MoveNextView ()
+    {
+        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
+        {
+            ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward);
+        }
+    }
+
+    /// <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);
+            //}
+
+            //top.SetNeedsDisplay ();
+            ApplicationOverlapped.BringOverlappedTopToFront ();
+        }
+        else
+        {
+            ApplicationOverlapped.OverlappedMoveNext ();
+        }
+    }
+
+    // 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.
+    /// </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 ()
+    {
+        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 ();
+        }
+    }
+}

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

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

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

@@ -23,7 +23,7 @@ internal sealed class MainLoopSyncContext : SynchronizationContext
     //_mainLoop.Driver.Wakeup ();
     public override void Send (SendOrPostCallback d, object state)
     {
-        if (Thread.CurrentThread.ManagedThreadId == Application._mainThreadId)
+        if (Thread.CurrentThread.ManagedThreadId == Application.MainThreadId)
         {
             d (state);
         }

+ 11 - 22
Terminal.Gui/Clipboard/Clipboard.cs

@@ -31,11 +31,11 @@ public static class Clipboard
             {
                 if (IsSupported)
                 {
-                    string clipData = Application.Driver.Clipboard.GetClipboardData ();
+                    string clipData = Application.Driver?.Clipboard.GetClipboardData ();
 
                     if (clipData is null)
                     {
-                        // throw new InvalidOperationException ($"{Application.Driver.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
+                        // throw new InvalidOperationException ($"{Application.Driver?.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
                         clipData = string.Empty;
                     }
 
@@ -60,7 +60,7 @@ public static class Clipboard
                         value = string.Empty;
                     }
 
-                    Application.Driver.Clipboard.SetClipboardData (value);
+                    Application.Driver?.Clipboard.SetClipboardData (value);
                 }
 
                 _contents = value;
@@ -74,19 +74,16 @@ public static class Clipboard
 
     /// <summary>Returns true if the environmental dependencies are in place to interact with the OS clipboard.</summary>
     /// <remarks></remarks>
-    public static bool IsSupported => Application.Driver.Clipboard.IsSupported;
+    public static bool IsSupported => Application.Driver?.Clipboard.IsSupported ?? false;
 
     /// <summary>Copies the _contents of the OS clipboard to <paramref name="result"/> if possible.</summary>
     /// <param name="result">The _contents of the OS clipboard if successful, <see cref="string.Empty"/> if not.</param>
     /// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
     public static bool TryGetClipboardData (out string result)
     {
-        if (IsSupported && Application.Driver.Clipboard.TryGetClipboardData (out result))
+        if (IsSupported && Application.Driver!.Clipboard.TryGetClipboardData (out result))
         {
-            if (_contents != result)
-            {
-                _contents = result;
-            }
+            _contents = result;
 
             return true;
         }
@@ -101,7 +98,7 @@ public static class Clipboard
     /// <returns><see langword="true"/> the OS clipboard was set, <see langword="false"/> otherwise.</returns>
     public static bool TrySetClipboardData (string text)
     {
-        if (IsSupported && Application.Driver.Clipboard.TrySetClipboardData (text))
+        if (IsSupported && Application.Driver!.Clipboard.TrySetClipboardData (text))
         {
             _contents = text;
 
@@ -155,7 +152,7 @@ internal static class ClipboardProcessRunner
 
         using (var process = new Process
                {
-                   StartInfo = new ProcessStartInfo
+                   StartInfo = new()
                    {
                        FileName = cmd,
                        Arguments = arguments,
@@ -191,17 +188,9 @@ internal static class ClipboardProcessRunner
 
             if (process.ExitCode > 0)
             {
-                output = $@"Process failed to run. Command line: {
-                    cmd
-                } {
-                    arguments
-                }.
-										Output: {
-                                            output
-                                        }
-										Error: {
-                                            process.StandardError.ReadToEnd ()
-                                        }";
+                output = $@"Process failed to run. Command line: {cmd} {arguments}.
+										Output: {output}
+										Error: {process.StandardError.ReadToEnd ()}";
             }
 
             return (process.ExitCode, output);

+ 4 - 4
Terminal.Gui/Clipboard/IClipboard.cs

@@ -6,21 +6,21 @@ public interface IClipboard
     /// <summary>Returns true if the environmental dependencies are in place to interact with the OS clipboard.</summary>
     bool IsSupported { get; }
 
-    /// <summary>Get the operation system clipboard.</summary>
+    /// <summary>Get the operating system clipboard.</summary>
     /// <exception cref="NotSupportedException">Thrown if it was not possible to read the clipboard contents.</exception>
     string GetClipboardData ();
 
-    /// <summary>Sets the operation system clipboard.</summary>
+    /// <summary>Sets the operating system clipboard.</summary>
     /// <param name="text"></param>
     /// <exception cref="NotSupportedException">Thrown if it was not possible to set the clipboard contents.</exception>
     void SetClipboardData (string text);
 
-    /// <summary>Gets the operation system clipboard if possible.</summary>
+    /// <summary>Gets the operating system clipboard if possible.</summary>
     /// <param name="result">Clipboard contents read</param>
     /// <returns>true if it was possible to read the OS clipboard.</returns>
     bool TryGetClipboardData (out string result);
 
-    /// <summary>Sets the operation system clipboard if possible.</summary>
+    /// <summary>Sets the operating system clipboard if possible.</summary>
     /// <param name="text"></param>
     /// <returns>True if the clipboard content was set successfully.</returns>
     bool TrySetClipboardData (string text);

+ 3 - 3
Terminal.Gui/Configuration/AttributeJsonConverter.cs

@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
-/// <summary>Json converter fro the <see cref="Attribute"/> class.</summary>
+/// <summary>Json converter from the <see cref="Attribute"/> class.</summary>
 internal class AttributeJsonConverter : JsonConverter<Attribute>
 {
     private static AttributeJsonConverter _instance;
@@ -57,11 +57,11 @@ internal class AttributeJsonConverter : JsonConverter<Attribute>
             switch (propertyName?.ToLower ())
             {
                 case "foreground":
-                    foreground = JsonSerializer.Deserialize<Color> (color, options);
+                    foreground = JsonSerializer.Deserialize (color, _serializerContext.Color);
 
                     break;
                 case "background":
-                    background = JsonSerializer.Deserialize<Color> (color, options);
+                    background = JsonSerializer.Deserialize (color, _serializerContext.Color);
 
                     break;
 

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

@@ -59,7 +59,7 @@ internal class ColorSchemeJsonConverter : JsonConverter<ColorScheme>
 
             string propertyName = reader.GetString ();
             reader.Read ();
-            var attribute = JsonSerializer.Deserialize<Attribute> (ref reader, options);
+            var attribute = JsonSerializer.Deserialize (ref reader, _serializerContext.Attribute);
 
             switch (propertyName.ToLower ())
             {

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

@@ -106,9 +106,12 @@ public static class ConfigurationManager
         },
 
         // Enables Key to be "Ctrl+Q" vs "Ctrl\u002BQ"
-        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+        TypeInfoResolver = SourceGenerationContext.Default
     };
 
+    internal static readonly SourceGenerationContext _serializerContext = new (_serializerOptions);
+
     [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
     internal static StringBuilder _jsonErrors = new ();
 
@@ -122,7 +125,7 @@ public static class ConfigurationManager
     /// </remarks>
     private static SettingsScope? _settings;
 
-    /// <summary>Name of the running application. By default this property is set to the application's assembly name.</summary>
+    /// <summary>Name of the running application. By default, this property is set to the application's assembly name.</summary>
     public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
 
     /// <summary>Application-specific configuration settings scope.</summary>
@@ -150,6 +153,8 @@ public static class ConfigurationManager
     /// </summary>
     public static SettingsScope? Settings
     {
+        [RequiresUnreferencedCode ("AOT")]
+        [RequiresDynamicCode ("AOT")]
         get
         {
             if (_settings is null)
@@ -181,6 +186,8 @@ public static class ConfigurationManager
     public static event EventHandler<ConfigurationManagerEventArgs>? Applied;
 
     /// <summary>Applies the configuration settings to the running <see cref="Application"/> instance.</summary>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     public static void Apply ()
     {
         var settings = false;
@@ -222,7 +229,7 @@ public static class ConfigurationManager
         var emptyScope = new SettingsScope ();
         emptyScope.Clear ();
 
-        return JsonSerializer.Serialize (emptyScope, _serializerOptions);
+        return JsonSerializer.Serialize (emptyScope, typeof (SettingsScope), _serializerContext);
     }
 
     /// <summary>
@@ -235,6 +242,8 @@ public static class ConfigurationManager
     ///     If <see langword="true"/> the state of <see cref="ConfigurationManager"/> will be reset to the
     ///     defaults.
     /// </param>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     public static void Load (bool reset = false)
     {
         Debug.WriteLine ("ConfigurationManager.Load()");
@@ -322,6 +331,8 @@ public static class ConfigurationManager
     ///     <see langword="true"/>.
     /// </summary>
     /// <remarks></remarks>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     public static void Reset ()
     {
         Debug.WriteLine (@"ConfigurationManager.Reset()");
@@ -475,6 +486,8 @@ public static class ConfigurationManager
     ///         make sure you copy the Theme definitions from the existing <c>Terminal.Gui.Resources.config.json</c> file.
     ///     </para>
     /// </remarks>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     internal static void GetHardCodedDefaults ()
     {
         if (_allConfigProperties is null)
@@ -496,6 +509,7 @@ public static class ConfigurationManager
     ///     Initializes the internal state of ConfigurationManager. Nominally called once as part of application startup
     ///     to initialize global state. Also called from some Unit Tests to ensure correctness (e.g. Reset()).
     /// </summary>
+    [RequiresUnreferencedCode ("AOT")]
     internal static void Initialize ()
     {
         _allConfigProperties = new ();
@@ -585,16 +599,20 @@ public static class ConfigurationManager
 
     /// <summary>Creates a JSON document with the configuration specified.</summary>
     /// <returns></returns>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     internal static string ToJson ()
     {
         //Debug.WriteLine ("ConfigurationManager.ToJson()");
 
-        return JsonSerializer.Serialize (Settings!, _serializerOptions);
+        return JsonSerializer.Serialize (Settings!, typeof (SettingsScope), _serializerContext);
     }
 
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     internal static Stream ToStream ()
     {
-        string json = JsonSerializer.Serialize (Settings!, _serializerOptions);
+        string json = JsonSerializer.Serialize (Settings!, typeof (SettingsScope), _serializerContext);
 
         // turn it into a stream
         var stream = new MemoryStream ();

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

@@ -15,6 +15,6 @@ public class ThemeManagerEventArgs : EventArgs
     /// <summary>Initializes a new instance of <see cref="ThemeManagerEventArgs"/></summary>
     public ThemeManagerEventArgs (string newTheme) { NewTheme = newTheme; }
 
-    /// <summary>The name of the new active theme..</summary>
+    /// <summary>The name of the new active theme.</summary>
     public string NewTheme { get; set; } = string.Empty;
 }

+ 3 - 3
Terminal.Gui/Configuration/DictionaryJsonConverter.cs

@@ -28,8 +28,8 @@ internal class DictionaryJsonConverter<T> : JsonConverter<Dictionary<string, T>>
                 {
                     string key = reader.GetString ();
                     reader.Read ();
-                    var value = JsonSerializer.Deserialize<T> (ref reader, options);
-                    dictionary.Add (key, value);
+                    var value = JsonSerializer.Deserialize (ref reader, typeof (T), _serializerContext);
+                    dictionary.Add (key, (T)value);
                 }
             }
             else if (reader.TokenType == JsonTokenType.EndArray)
@@ -51,7 +51,7 @@ internal class DictionaryJsonConverter<T> : JsonConverter<Dictionary<string, T>>
 
             //writer.WriteString (item.Key, item.Key);
             writer.WritePropertyName (item.Key);
-            JsonSerializer.Serialize (writer, item.Value, options);
+            JsonSerializer.Serialize (writer, item.Value, typeof (T), _serializerContext);
             writer.WriteEndObject ();
         }
 

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

@@ -42,7 +42,7 @@ internal class KeyCodeJsonConverter : JsonConverter<KeyCode>
                                 }
 
                                 // The enum uses "D0..D9" for the number keys
-                                if (Enum.TryParse (reader.GetString ().TrimStart ('D', 'd'), false, out key))
+                                if (Enum.TryParse (reader.GetString ()!.TrimStart ('D', 'd'), false, out key))
                                 {
                                     break;
                                 }

+ 13 - 5
Terminal.Gui/Configuration/ScopeJsonConverter.cs

@@ -1,5 +1,6 @@
 #nullable enable
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.Json.Serialization;
@@ -11,9 +12,12 @@ namespace Terminal.Gui;
 ///     data to/from <see cref="ConfigurationManager"/> JSON documents.
 /// </summary>
 /// <typeparam name="scopeT"></typeparam>
-internal class ScopeJsonConverter<scopeT> : JsonConverter<scopeT> where scopeT : Scope<scopeT>
+internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] scopeT> : JsonConverter<scopeT> where scopeT : Scope<scopeT>
 {
+    [RequiresDynamicCode ("Calls System.Type.MakeGenericType(params Type[])")]
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public override scopeT Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     {
         if (reader.TokenType != JsonTokenType.StartObject)
         {
@@ -85,7 +89,7 @@ internal class ScopeJsonConverter<scopeT> : JsonConverter<scopeT> where scopeT :
                     try
                     {
                         scope! [propertyName].PropertyValue =
-                            JsonSerializer.Deserialize (ref reader, propertyType!, options);
+                            JsonSerializer.Deserialize (ref reader, propertyType!, _serializerContext);
                     }
                     catch (Exception ex)
                     {
@@ -133,7 +137,7 @@ internal class ScopeJsonConverter<scopeT> : JsonConverter<scopeT> where scopeT :
                 if (property is { })
                 {
                     PropertyInfo prop = scope.GetType ().GetProperty (propertyName!)!;
-                    prop.SetValue (scope, JsonSerializer.Deserialize (ref reader, prop.PropertyType, options));
+                    prop.SetValue (scope, JsonSerializer.Deserialize (ref reader, prop.PropertyType, _serializerContext));
                 }
                 else
                 {
@@ -160,7 +164,8 @@ internal class ScopeJsonConverter<scopeT> : JsonConverter<scopeT> where scopeT :
         foreach (PropertyInfo p in properties)
         {
             writer.WritePropertyName (ConfigProperty.GetJsonPropertyName (p));
-            JsonSerializer.Serialize (writer, scope.GetType ().GetProperty (p.Name)?.GetValue (scope), options);
+            object? prop = scope.GetType ().GetProperty (p.Name)?.GetValue (scope);
+            JsonSerializer.Serialize (writer, prop, prop!.GetType (), _serializerContext);
         }
 
         foreach (KeyValuePair<string, ConfigProperty> p in from p in scope
@@ -205,7 +210,8 @@ internal class ScopeJsonConverter<scopeT> : JsonConverter<scopeT> where scopeT :
             }
             else
             {
-                JsonSerializer.Serialize (writer, p.Value.PropertyValue, options);
+                object? prop = p.Value.PropertyValue;
+                JsonSerializer.Serialize (writer, prop, prop!.GetType (), _serializerContext);
             }
         }
 
@@ -221,6 +227,8 @@ internal class ScopeJsonConverter<scopeT> : JsonConverter<scopeT> where scopeT :
     internal class ReadHelper<converterT> : ReadHelper
     {
         private readonly ReadDelegate _readDelegate;
+
+        [RequiresUnreferencedCode ("Calls System.Delegate.CreateDelegate(Type, Object, String)")]
         public ReadHelper (object converter) { _readDelegate = (ReadDelegate)Delegate.CreateDelegate (typeof (ReadDelegate), converter, "Read"); }
 
         public override object? Read (ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)

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

@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-/// <summary>An attribute that can be applied to a property to indicate that it should included in the configuration file.</summary>
+/// <summary>An attribute that can be applied to a property to indicate that it should be included in the configuration file.</summary>
 /// <example>
 ///     [SerializableConfigurationProperty(Scope = typeof(Configuration.ThemeManager.ThemeScope)), JsonConverter
 ///     (typeof (JsonStringEnumConverter))] public static LineStyle DefaultBorderStyle { ...

+ 10 - 1
Terminal.Gui/Configuration/SettingsScope.cs

@@ -1,5 +1,6 @@
 #nullable enable
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.Json.Serialization;
@@ -37,12 +38,14 @@ public class SettingsScope : Scope<SettingsScope>
     /// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON string.</summary>
     /// <param name="stream">Json document to update the settings with.</param>
     /// <param name="source">The source (filename/resource name) the Json document was read from.</param>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     public SettingsScope? Update (Stream stream, string source)
     {
         // Update the existing settings with the new settings.
         try
         {
-            Update (JsonSerializer.Deserialize<SettingsScope> (stream, _serializerOptions)!);
+            Update ((SettingsScope)JsonSerializer.Deserialize (stream, typeof (SettingsScope), _serializerOptions)!);
             OnUpdated ();
             Debug.WriteLine ($"ConfigurationManager: Read configuration from \"{source}\"");
             if (!Sources.Contains (source))
@@ -67,6 +70,8 @@ public class SettingsScope : Scope<SettingsScope>
 
     /// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON file.</summary>
     /// <param name="filePath"></param>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     public SettingsScope? Update (string filePath)
     {
         string realPath = filePath.Replace ("~", Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
@@ -93,6 +98,8 @@ public class SettingsScope : Scope<SettingsScope>
     /// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON string.</summary>
     /// <param name="json">Json document to update the settings with.</param>
     /// <param name="source">The source (filename/resource name) the Json document was read from.</param>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     public SettingsScope? Update (string json, string source)
     {
         var stream = new MemoryStream ();
@@ -107,6 +114,8 @@ public class SettingsScope : Scope<SettingsScope>
     /// <summary>Updates the <see cref="SettingsScope"/> with the settings from a Json resource.</summary>
     /// <param name="assembly"></param>
     /// <param name="resourceName"></param>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     public SettingsScope? UpdateFromResource (Assembly assembly, string resourceName)
     {
         if (resourceName is null || string.IsNullOrEmpty (resourceName))

+ 23 - 0
Terminal.Gui/Configuration/SourceGenerationContext.cs

@@ -0,0 +1,23 @@
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Allow AOT and self-contained single file applications with the <see cref="System.Text.Json.Serialization"/>
+/// </summary>
+[JsonSerializable (typeof (Attribute))]
+[JsonSerializable (typeof (Color))]
+[JsonSerializable (typeof (AppScope))]
+[JsonSerializable (typeof (SettingsScope))]
+[JsonSerializable (typeof (Key))]
+[JsonSerializable (typeof (GlyphDefinitions))]
+[JsonSerializable (typeof (Alignment))]
+[JsonSerializable (typeof (AlignmentModes))]
+[JsonSerializable (typeof (LineStyle))]
+[JsonSerializable (typeof (ShadowStyle))]
+[JsonSerializable (typeof (bool?))]
+[JsonSerializable (typeof (Dictionary<ColorName, string>))]
+[JsonSerializable (typeof (Dictionary<string, ThemeScope>))]
+[JsonSerializable (typeof (Dictionary<string, ColorScheme>))]
+internal partial class SourceGenerationContext : JsonSerializerContext
+{ }

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

@@ -1,6 +1,7 @@
 #nullable enable
 using System.Collections;
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
@@ -8,7 +9,7 @@ namespace Terminal.Gui;
 /// <summary>Contains a dictionary of the <see cref="ThemeManager.Theme"/>s for a Terminal.Gui application.</summary>
 /// <remarks>
 ///     <para>A Theme is a collection of settings that are named. The default theme is named "Default".</para>
-///     <para>The <see cref="ThemeManager.Theme"/> property is used to detemrine the currently active theme.</para>
+///     <para>The <see cref="ThemeManager.Theme"/> property is used to determine the currently active theme.</para>
 /// </remarks>
 /// <para>
 ///     <see cref="ThemeManager"/> is a singleton class. It is created when the first <see cref="ThemeManager"/> property
@@ -64,6 +65,9 @@ public class ThemeManager : IDictionary<string, ThemeScope>
     public string Theme
     {
         get => SelectedTheme;
+
+        [RequiresUnreferencedCode ("AOT")]
+        [RequiresDynamicCode ("AOT")]
         set => SelectedTheme = value;
     }
 
@@ -73,9 +77,14 @@ public class ThemeManager : IDictionary<string, ThemeScope>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
     public static Dictionary<string, ThemeScope>? Themes
     {
+        [RequiresUnreferencedCode ("AOT")]
+        [RequiresDynamicCode ("AOT")]
         get => Settings? ["Themes"]
                        ?.PropertyValue as
                    Dictionary<string, ThemeScope>; // themes ?? new Dictionary<string, ThemeScope> ();
+
+        [RequiresUnreferencedCode ("AOT")]
+        [RequiresDynamicCode ("AOT")]
         set =>
 
             //if (themes is null || value is null) {
@@ -93,6 +102,9 @@ public class ThemeManager : IDictionary<string, ThemeScope>
     internal static string SelectedTheme
     {
         get => _theme;
+
+        [RequiresUnreferencedCode ("Calls Terminal.Gui.ConfigurationManager.Settings")]
+        [RequiresDynamicCode ("Calls Terminal.Gui.ConfigurationManager.Settings")]
         set
         {
             string oldTheme = _theme;
@@ -109,6 +121,8 @@ public class ThemeManager : IDictionary<string, ThemeScope>
     /// <summary>Event fired he selected theme has changed. application.</summary>
     public event EventHandler<ThemeManagerEventArgs>? ThemeChanged;
 
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.ThemeManager.Themes")]
+    [RequiresDynamicCode ("Calls Terminal.Gui.ThemeManager.Themes")]
     internal static void GetHardCodedDefaults ()
     {
         //Debug.WriteLine ("Themes.GetHardCodedDefaults()");
@@ -129,6 +143,8 @@ public class ThemeManager : IDictionary<string, ThemeScope>
         ThemeChanged?.Invoke (this, new ThemeManagerEventArgs (theme));
     }
 
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.ThemeManager.Themes")]
+    [RequiresDynamicCode ("Calls Terminal.Gui.ThemeManager.Themes")]
     internal static void Reset ()
     {
         Debug.WriteLine ("Themes.Reset()");
@@ -140,33 +156,130 @@ public class ThemeManager : IDictionary<string, ThemeScope>
     #region IDictionary
 
 #pragma warning disable 1591
+    [UnconditionalSuppressMessage ("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
+    [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
     public ICollection<string> Keys => ((IDictionary<string, ThemeScope>)Themes!).Keys;
+
+    [UnconditionalSuppressMessage ("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
+    [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
     public ICollection<ThemeScope> Values => ((IDictionary<string, ThemeScope>)Themes!).Values;
+
+    [UnconditionalSuppressMessage ("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
+    [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
     public int Count => ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Count;
+
+    [UnconditionalSuppressMessage ("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
+    [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
     public bool IsReadOnly => ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).IsReadOnly;
 
     public ThemeScope this [string key]
     {
+        [RequiresUnreferencedCode ("AOT")]
+        [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
         get => ((IDictionary<string, ThemeScope>)Themes!) [key];
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+
+        [RequiresUnreferencedCode ("AOT")]
+        [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
         set => ((IDictionary<string, ThemeScope>)Themes!) [key] = value;
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
     }
 
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public void Add (string key, ThemeScope value) { ((IDictionary<string, ThemeScope>)Themes!).Add (key, value); }
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public bool ContainsKey (string key) { return ((IDictionary<string, ThemeScope>)Themes!).ContainsKey (key); }
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public bool Remove (string key) { return ((IDictionary<string, ThemeScope>)Themes!).Remove (key); }
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public bool TryGetValue (string key, out ThemeScope value) { return ((IDictionary<string, ThemeScope>)Themes!).TryGetValue (key, out value!); }
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public void Add (KeyValuePair<string, ThemeScope> item) { ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Add (item); }
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public void Clear () { ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Clear (); }
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public bool Contains (KeyValuePair<string, ThemeScope> item) { return ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Contains (item); }
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
 
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public void CopyTo (KeyValuePair<string, ThemeScope> [] array, int arrayIndex)
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
     {
         ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).CopyTo (array, arrayIndex);
     }
 
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public bool Remove (KeyValuePair<string, ThemeScope> item) { return ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Remove (item); }
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     public IEnumerator<KeyValuePair<string, ThemeScope>> GetEnumerator () { return ((IEnumerable<KeyValuePair<string, ThemeScope>>)Themes!).GetEnumerator (); }
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.ThemeManager.Themes")]
+    [RequiresDynamicCode ("Calls Terminal.Gui.ThemeManager.Themes")]
+#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
     IEnumerator IEnumerable.GetEnumerator () { return ((IEnumerable)Themes!).GetEnumerator (); }
+#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.
+#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.
 #pragma warning restore 1591
 
     #endregion

+ 120 - 149
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -1,3 +1,4 @@
+#nullable enable
 //
 // ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations.
 //
@@ -16,11 +17,11 @@ public abstract class ConsoleDriver
 {
     // As performance is a concern, we keep track of the dirty lines and only refresh those.
     // This is in addition to the dirty flag on each cell.
-    internal bool [] _dirtyLines;
+    internal bool []? _dirtyLines;
 
     // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application?
     /// <summary>Gets the location and size of the terminal screen.</summary>
-    public Rectangle Screen => new (0, 0, Cols, Rows);
+    internal Rectangle Screen => new (0, 0, Cols, Rows);
 
     private Rectangle _clip;
 
@@ -45,7 +46,7 @@ public abstract class ConsoleDriver
     }
 
     /// <summary>Get the operating system clipboard.</summary>
-    public IClipboard Clipboard { get; internal set; }
+    public IClipboard? Clipboard { get; internal set; }
 
     /// <summary>
     ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
@@ -69,7 +70,7 @@ public abstract class ConsoleDriver
     ///     <see cref="UpdateScreen"/> is called.
     ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
     /// </summary>
-    public Cell [,] Contents { get; internal set; }
+    public Cell [,]? Contents { get; internal set; }
 
     /// <summary>The leftmost column in the terminal.</summary>
     public virtual int Left { get; internal set; } = 0;
@@ -124,125 +125,133 @@ public abstract class ConsoleDriver
         int runeWidth = -1;
         bool validLocation = IsValidLocation (Col, Row);
 
+        if (Contents is null)
+        {
+            return;
+        }
+
         if (validLocation)
         {
             rune = rune.MakePrintable ();
             runeWidth = rune.GetColumns ();
 
-            if (runeWidth == 0 && rune.IsCombiningMark ())
+            lock (Contents)
             {
-                // AtlasEngine does not support NON-NORMALIZED combining marks in a way
-                // compatible with the driver architecture. Any CMs (except in the first col)
-                // are correctly combined with the base char, but are ALSO treated as 1 column
-                // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
-                // 
-                // Until this is addressed (see Issue #), we do our best by 
-                // a) Attempting to normalize any CM with the base char to it's left
-                // b) Ignoring any CMs that don't normalize
-                if (Col > 0)
+                if (runeWidth == 0 && rune.IsCombiningMark ())
                 {
-                    if (Contents [Row, Col - 1].CombiningMarks.Count > 0)
-                    {
-                        // Just add this mark to the list
-                        Contents [Row, Col - 1].CombiningMarks.Add (rune);
-
-                        // Ignore. Don't move to next column (let the driver figure out what to do).
-                    }
-                    else
+                    // AtlasEngine does not support NON-NORMALIZED combining marks in a way
+                    // compatible with the driver architecture. Any CMs (except in the first col)
+                    // are correctly combined with the base char, but are ALSO treated as 1 column
+                    // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
+                    // 
+                    // Until this is addressed (see Issue #), we do our best by 
+                    // a) Attempting to normalize any CM with the base char to it's left
+                    // b) Ignoring any CMs that don't normalize
+                    if (Col > 0)
                     {
-                        // Attempt to normalize the cell to our left combined with this mark
-                        string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
-
-                        // Normalize to Form C (Canonical Composition)
-                        string normalized = combined.Normalize (NormalizationForm.FormC);
-
-                        if (normalized.Length == 1)
+                        if (Contents [Row, Col - 1].CombiningMarks.Count > 0)
                         {
-                            // It normalized! We can just set the Cell to the left with the
-                            // normalized codepoint 
-                            Contents [Row, Col - 1].Rune = (Rune)normalized [0];
+                            // Just add this mark to the list
+                            Contents [Row, Col - 1].CombiningMarks.Add (rune);
 
-                            // Ignore. Don't move to next column because we're already there
+                            // Ignore. Don't move to next column (let the driver figure out what to do).
                         }
                         else
                         {
-                            // It didn't normalize. Add it to the Cell to left's CM list
-                            Contents [Row, Col - 1].CombiningMarks.Add (rune);
-
-                            // Ignore. Don't move to next column (let the driver figure out what to do).
+                            // Attempt to normalize the cell to our left combined with this mark
+                            string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
+
+                            // Normalize to Form C (Canonical Composition)
+                            string normalized = combined.Normalize (NormalizationForm.FormC);
+
+                            if (normalized.Length == 1)
+                            {
+                                // It normalized! We can just set the Cell to the left with the
+                                // normalized codepoint 
+                                Contents [Row, Col - 1].Rune = (Rune)normalized [0];
+
+                                // Ignore. Don't move to next column because we're already there
+                            }
+                            else
+                            {
+                                // It didn't normalize. Add it to the Cell to left's CM list
+                                Contents [Row, Col - 1].CombiningMarks.Add (rune);
+
+                                // Ignore. Don't move to next column (let the driver figure out what to do).
+                            }
                         }
-                    }
 
-                    Contents [Row, Col - 1].Attribute = CurrentAttribute;
-                    Contents [Row, Col - 1].IsDirty = true;
+                        Contents [Row, Col - 1].Attribute = CurrentAttribute;
+                        Contents [Row, Col - 1].IsDirty = true;
+                    }
+                    else
+                    {
+                        // Most drivers will render a combining mark at col 0 as the mark
+                        Contents [Row, Col].Rune = rune;
+                        Contents [Row, Col].Attribute = CurrentAttribute;
+                        Contents [Row, Col].IsDirty = true;
+                        Col++;
+                    }
                 }
                 else
                 {
-                    // Most drivers will render a combining mark at col 0 as the mark
-                    Contents [Row, Col].Rune = rune;
                     Contents [Row, Col].Attribute = CurrentAttribute;
                     Contents [Row, Col].IsDirty = true;
-                    Col++;
-                }
-            }
-            else
-            {
-                Contents [Row, Col].Attribute = CurrentAttribute;
-                Contents [Row, Col].IsDirty = true;
 
-                if (Col > 0)
-                {
-                    // Check if cell to left has a wide glyph
-                    if (Contents [Row, Col - 1].Rune.GetColumns () > 1)
+                    if (Col > 0)
                     {
-                        // Invalidate cell to left
-                        Contents [Row, Col - 1].Rune = Rune.ReplacementChar;
-                        Contents [Row, Col - 1].IsDirty = true;
+                        // Check if cell to left has a wide glyph
+                        if (Contents [Row, Col - 1].Rune.GetColumns () > 1)
+                        {
+                            // Invalidate cell to left
+                            Contents [Row, Col - 1].Rune = Rune.ReplacementChar;
+                            Contents [Row, Col - 1].IsDirty = true;
+                        }
                     }
-                }
 
-                if (runeWidth < 1)
-                {
-                    Contents [Row, Col].Rune = Rune.ReplacementChar;
-                }
-                else if (runeWidth == 1)
-                {
-                    Contents [Row, Col].Rune = rune;
-
-                    if (Col < Clip.Right - 1)
+                    if (runeWidth < 1)
                     {
-                        Contents [Row, Col + 1].IsDirty = true;
-                    }
-                }
-                else if (runeWidth == 2)
-                {
-                    if (Col == Clip.Right - 1)
-                    {
-                        // We're at the right edge of the clip, so we can't display a wide character.
-                        // TODO: Figure out if it is better to show a replacement character or ' '
                         Contents [Row, Col].Rune = Rune.ReplacementChar;
                     }
-                    else
+                    else if (runeWidth == 1)
                     {
                         Contents [Row, Col].Rune = rune;
 
                         if (Col < Clip.Right - 1)
                         {
-                            // Invalidate cell to right so that it doesn't get drawn
-                            // TODO: Figure out if it is better to show a replacement character or ' '
-                            Contents [Row, Col + 1].Rune = Rune.ReplacementChar;
                             Contents [Row, Col + 1].IsDirty = true;
                         }
                     }
-                }
-                else
-                {
-                    // This is a non-spacing character, so we don't need to do anything
-                    Contents [Row, Col].Rune = (Rune)' ';
-                    Contents [Row, Col].IsDirty = false;
-                }
+                    else if (runeWidth == 2)
+                    {
+                        if (Col == Clip.Right - 1)
+                        {
+                            // We're at the right edge of the clip, so we can't display a wide character.
+                            // TODO: Figure out if it is better to show a replacement character or ' '
+                            Contents [Row, Col].Rune = Rune.ReplacementChar;
+                        }
+                        else
+                        {
+                            Contents [Row, Col].Rune = rune;
+
+                            if (Col < Clip.Right - 1)
+                            {
+                                // Invalidate cell to right so that it doesn't get drawn
+                                // TODO: Figure out if it is better to show a replacement character or ' '
+                                Contents [Row, Col + 1].Rune = Rune.ReplacementChar;
+                                Contents [Row, Col + 1].IsDirty = true;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        // This is a non-spacing character, so we don't need to do anything
+                        Contents [Row, Col].Rune = (Rune)' ';
+                        Contents [Row, Col].IsDirty = false;
+                    }
 
-                _dirtyLines [Row] = true;
+                    _dirtyLines! [Row] = true;
+                }
             }
         }
 
@@ -257,14 +266,17 @@ public abstract class ConsoleDriver
 
             if (validLocation && Col < Clip.Right)
             {
-                // This is a double-width character, and we are not at the end of the line.
-                // Col now points to the second column of the character. Ensure it doesn't
-                // Get rendered.
-                Contents [Row, Col].IsDirty = false;
-                Contents [Row, Col].Attribute = CurrentAttribute;
-
-                // TODO: Determine if we should wipe this out (for now now)
-                //Contents [Row, Col].Rune = (Rune)' ';
+                lock (Contents!)
+                {
+                    // This is a double-width character, and we are not at the end of the line.
+                    // Col now points to the second column of the character. Ensure it doesn't
+                    // Get rendered.
+                    Contents [Row, Col].IsDirty = false;
+                    Contents [Row, Col].Attribute = CurrentAttribute;
+
+                    // TODO: Determine if we should wipe this out (for now now)
+                    //Contents [Row, Col].Rune = (Rune)' ';
+                }
             }
 
             Col++;
@@ -331,7 +343,7 @@ public abstract class ConsoleDriver
     /// </summary>
     public void SetContentsAsDirty ()
     {
-        lock (Contents)
+        lock (Contents!)
         {
             for (var row = 0; row < Rows; row++)
             {
@@ -339,7 +351,7 @@ public abstract class ConsoleDriver
                 {
                     Contents [row, c].IsDirty = true;
                 }
-                _dirtyLines [row] = true;
+                _dirtyLines! [row] = true;
             }
         }
     }
@@ -357,7 +369,7 @@ public abstract class ConsoleDriver
     public void FillRect (Rectangle rect, Rune rune = default)
     {
         rect = Rectangle.Intersect (rect, Clip);
-        lock (Contents)
+        lock (Contents!)
         {
             for (int r = rect.Y; r < rect.Y + rect.Height; r++)
             {
@@ -368,7 +380,7 @@ public abstract class ConsoleDriver
                         Rune = (rune != default ? rune : (Rune)' '),
                         Attribute = CurrentAttribute, IsDirty = true
                     };
-                    _dirtyLines [r] = true;
+                    _dirtyLines! [r] = true;
                 }
             }
         }
@@ -426,6 +438,7 @@ public abstract class ConsoleDriver
     /// <param name="row">Row to move to.</param>
     public virtual void Move (int col, int row)
     {
+        //Debug.Assert (col >= 0 && row >= 0 && col < Contents.GetLength(1) && row < Contents.GetLength(0));
         Col = col;
         Row = row;
     }
@@ -443,7 +456,7 @@ public abstract class ConsoleDriver
     public abstract bool SetCursorVisibility (CursorVisibility visibility);
 
     /// <summary>The event fired when the terminal is resized.</summary>
-    public event EventHandler<SizeChangedEventArgs> SizeChanged;
+    public event EventHandler<SizeChangedEventArgs>? SizeChanged;
 
     /// <summary>Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.</summary>
     /// <remarks>This is only implemented in <see cref="CursesDriver"/>.</remarks>
@@ -549,7 +562,7 @@ public abstract class ConsoleDriver
     #region Mouse and Keyboard
 
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
-    public event EventHandler<Key> KeyDown;
+    public event EventHandler<Key>? KeyDown;
 
     /// <summary>
     ///     Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
@@ -563,18 +576,18 @@ public abstract class ConsoleDriver
     ///     Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is
     ///     complete.
     /// </remarks>
-    public event EventHandler<Key> KeyUp;
+    public event EventHandler<Key>? KeyUp;
 
     /// <summary>Called when a key is released. Fires the <see cref="KeyUp"/> event.</summary>
     /// <remarks>
-    ///     Drivers that do not support key release events will calls this method after <see cref="OnKeyDown"/> processing
+    ///     Drivers that do not support key release events will call this method after <see cref="OnKeyDown"/> processing
     ///     is complete.
     /// </remarks>
     /// <param name="a"></param>
     public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
 
     /// <summary>Event fired when a mouse event occurs.</summary>
-    public event EventHandler<MouseEvent> MouseEvent;
+    public event EventHandler<MouseEvent>? MouseEvent;
 
     /// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
     /// <param name="a"></param>
@@ -591,48 +604,6 @@ public abstract class ConsoleDriver
     #endregion
 }
 
-/// <summary>Terminal Cursor Visibility settings.</summary>
-/// <remarks>
-///     Hex value are set as 0xAABBCCDD where : AA stand for the TERMINFO DECSUSR parameter value to be used under
-///     Linux and MacOS BB stand for the NCurses curs_set parameter value to be used under Linux and MacOS CC stand for the
-///     CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows DD stand for the CONSOLE_CURSOR_INFO.dwSize
-///     parameter value to be used under Windows
-/// </remarks>
-public enum CursorVisibility
-{
-    /// <summary>Cursor caret has default</summary>
-    /// <remarks>
-    ///     Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/>. This default directly
-    ///     depends of the XTerm user configuration settings so it could be Block, I-Beam, Underline with possible blinking.
-    /// </remarks>
-    Default = 0x00010119,
-
-    /// <summary>Cursor caret is hidden</summary>
-    Invisible = 0x03000019,
-
-    /// <summary>Cursor caret is normally shown as a blinking underline bar _</summary>
-    Underline = 0x03010119,
-
-    /// <summary>Cursor caret is normally shown as a underline bar _</summary>
-    /// <remarks>Under Windows, this is equivalent to <see ref="UnderscoreBlinking"/></remarks>
-    UnderlineFix = 0x04010119,
-
-    /// <summary>Cursor caret is displayed a blinking vertical bar |</summary>
-    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
-    Vertical = 0x05010119,
-
-    /// <summary>Cursor caret is displayed a blinking vertical bar |</summary>
-    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
-    VerticalFix = 0x06010119,
-
-    /// <summary>Cursor caret is displayed as a blinking block ▉</summary>
-    Box = 0x01020164,
-
-    /// <summary>Cursor caret is displayed a block ▉</summary>
-    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
-    BoxFix = 0x02020164
-}
-
 /// <summary>
 ///     The <see cref="KeyCode"/> enumeration encodes key information from <see cref="ConsoleDriver"/>s and provides a
 ///     consistent way for application code to specify keys and receive key events.
@@ -681,7 +652,7 @@ public enum KeyCode : uint
 
     /// <summary>
     ///     If the <see cref="SpecialMask"/> is set, then the value is that of the special mask, otherwise, the value is
-    ///     in the the lower bits (as extracted by <see cref="CharMask"/>).
+    ///     in the lower bits (as extracted by <see cref="CharMask"/>).
     /// </summary>
     SpecialMask = 0x_fff0_0000,
 
@@ -838,9 +809,9 @@ public enum KeyCode : uint
     //Delete = 127,
 
     // --- Special keys ---
-    // The values below are common non-alphanum keys. Their values are 
+    // The values below are common non-alphanum keys. Their values are
     // based on the .NET ConsoleKey values, which, in-turn are based on the
-    // VK_ values from the Windows API. 
+    // VK_ values from the Windows API.
     // We add MaxCodePoint to avoid conflicts with the Unicode values.
 
     /// <summary>The maximum Unicode codepoint value. Used to encode the non-alphanumeric control keys.</summary>

+ 1 - 1
Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs

@@ -1435,7 +1435,7 @@ public static class ConsoleKeyMapping
                              (VK)'2',
                              ConsoleModifiers.Shift,
                              '\"'
-                            ), // BUGBUG: This is true for Portugese keyboard, but not ENG (@) or DEU (")
+                            ), // BUGBUG: This is true for Portuguese keyboard, but not ENG (@) or DEU (")
         new ScanCodeMapping (
                              3,
                              (VK)'2',

+ 1 - 1
Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs

@@ -89,7 +89,7 @@ internal class CursesClipboard : ClipboardBase
 
 /// <summary>
 ///     A clipboard implementation for MacOSX. This implementation uses the Mac clipboard API (via P/Invoke) to
-///     copy/paste. The existance of the Mac pbcopy and pbpaste commands is used to determine if copy/paste is supported.
+///     copy/paste. The existence of the Mac pbcopy and pbpaste commands is used to determine if copy/paste is supported.
 /// </summary>
 internal class MacOSXClipboard : ClipboardBase
 {

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

@@ -317,7 +317,7 @@ internal class CursesDriver : ConsoleDriver
             Curses.doupdate ();
 
             // 
-            // We are setting Invisible as default so we could ignore XTerm DECSUSR setting
+            // We are setting Invisible as default, so we could ignore XTerm DECSUSR setting
             //
             switch (Curses.curs_set (0))
             {
@@ -977,7 +977,7 @@ internal static class Platform
     private static int _suspendSignal;
 
     /// <summary>Suspends the process by sending SIGTSTP to itself</summary>
-    /// <returns>The suspend.</returns>
+    /// <returns>True if the suspension was successful.</returns>
     public static bool Suspend ()
     {
         int signal = GetSuspendSignal ();

+ 3 - 1
Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs

@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #define GUICS
+using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
 
 namespace Unix.Terminal;
@@ -69,6 +70,7 @@ internal class UnmanagedLibrary
         }
     }
 
+    [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
     static UnmanagedLibrary ()
     {
         PlatformID platform = Environment.OSVersion.Platform;
@@ -289,7 +291,7 @@ internal class UnmanagedLibrary
     }
 
     /// <summary>
-    ///     On Linux systems, using using dlopen and dlsym results in DllNotFoundException("libdl.so not found") if
+    ///     On Linux systems, using dlopen and dlsym results in DllNotFoundException("libdl.so not found") if
     ///     libc6-dev is not installed. As a workaround, we load symbols for dlopen and dlsym from the current process as on
     ///     Linux Mono sure is linked against these symbols.
     /// </summary>

+ 44 - 0
Terminal.Gui/ConsoleDrivers/CursorVisibility.cs

@@ -0,0 +1,44 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>Terminal Cursor Visibility settings.</summary>
+/// <remarks>
+///     Hex value are set as 0xAABBCCDD where : AA stand for the TERMINFO DECSUSR parameter value to be used under
+///     Linux and MacOS BB stand for the NCurses curs_set parameter value to be used under Linux and MacOS CC stand for the
+///     CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows DD stand for the CONSOLE_CURSOR_INFO.dwSize
+///     parameter value to be used under Windows
+/// </remarks>
+public enum CursorVisibility
+{
+    /// <summary>Cursor caret has default</summary>
+    /// <remarks>
+    ///     Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/>. This default directly
+    ///     depends on the XTerm user configuration settings, so it could be Block, I-Beam, Underline with possible blinking.
+    /// </remarks>
+    Default = 0x00010119,
+
+    /// <summary>Cursor caret is hidden</summary>
+    Invisible = 0x03000019,
+
+    /// <summary>Cursor caret is normally shown as a blinking underline bar _</summary>
+    Underline = 0x03010119,
+
+    /// <summary>Cursor caret is normally shown as a underline bar _</summary>
+    /// <remarks>Under Windows, this is equivalent to <see ref="UnderscoreBlinking"/></remarks>
+    UnderlineFix = 0x04010119,
+
+    /// <summary>Cursor caret is displayed a blinking vertical bar |</summary>
+    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
+    Vertical = 0x05010119,
+
+    /// <summary>Cursor caret is displayed a blinking vertical bar |</summary>
+    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
+    VerticalFix = 0x06010119,
+
+    /// <summary>Cursor caret is displayed as a blinking block ▉</summary>
+    Box = 0x01020164,
+
+    /// <summary>Cursor caret is displayed a block ▉</summary>
+    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
+    BoxFix = 0x02020164
+}

+ 1 - 1
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs

@@ -22,7 +22,7 @@ public class EscSeqReqStatus
     /// <summary>Gets the number of requests.</summary>
     public int NumRequests { get; }
 
-    /// <summary>Gets the Escape Sequence Termintor (e.g. ESC[8t ... t is the terminator).</summary>
+    /// <summary>Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator).</summary>
     public string Terminator { get; }
 }
 

+ 4 - 4
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

@@ -159,10 +159,10 @@ public static class EscSeqUtils
     ///     Decodes an ANSI escape sequence.
     /// </summary>
     /// <param name="escSeqRequests">The <see cref="EscSeqRequests"/> which may contain a request.</param>
-    /// <param name="newConsoleKeyInfo">The <see cref="ConsoleKeyInfo"/> which may changes.</param>
-    /// <param name="key">The <see cref="ConsoleKey"/> which may changes.</param>
+    /// <param name="newConsoleKeyInfo">The <see cref="ConsoleKeyInfo"/> which may change.</param>
+    /// <param name="key">The <see cref="ConsoleKey"/> which may change.</param>
     /// <param name="cki">The <see cref="ConsoleKeyInfo"/> array.</param>
-    /// <param name="mod">The <see cref="ConsoleModifiers"/> which may changes.</param>
+    /// <param name="mod">The <see cref="ConsoleModifiers"/> which may change.</param>
     /// <param name="c1Control">The control returned by the <see cref="GetC1ControlChar"/> method.</param>
     /// <param name="code">The code returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
     /// <param name="values">The values returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
@@ -426,7 +426,7 @@ public static class EscSeqUtils
     #nullable restore
 
     /// <summary>
-    ///     Gets all the needed information about a escape sequence.
+    ///     Gets all the needed information about an escape sequence.
     /// </summary>
     /// <param name="kChar">The array with all chars.</param>
     /// <returns>

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

@@ -198,7 +198,7 @@ public class FakeDriver : ConsoleDriver
         FakeConsole.CursorTop = 0;
         FakeConsole.CursorLeft = 0;
 
-        //SetCursorVisibility (savedVisibitity);
+        //SetCursorVisibility (savedVisibility);
 
         void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
         {
@@ -437,7 +437,7 @@ public class FakeDriver : ConsoleDriver
     {
         if (FakeConsole.WindowHeight > 0)
         {
-            // Can raise an exception while is still resizing.
+            // Can raise an exception while it is still resizing.
             try
             {
                 FakeConsole.CursorTop = 0;

+ 4 - 1
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -3,6 +3,7 @@
 //
 
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
 using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 using static Terminal.Gui.NetEvents;
@@ -452,6 +453,7 @@ internal class NetEvents : IDisposable
         HandleKeyboardEvent (newConsoleKeyInfo);
     }
 
+    [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
     private MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
     {
         MouseButtonState mbs = default;
@@ -1249,6 +1251,7 @@ internal class NetDriver : ConsoleDriver
     #region Color Handling
 
     // Cache the list of ConsoleColor values.
+    [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
     private static readonly HashSet<int> ConsoleColorValues = new (
                                                                    Enum.GetValues (typeof (ConsoleColor))
                                                                        .OfType<ConsoleColor> ()
@@ -1666,7 +1669,7 @@ internal class NetDriver : ConsoleDriver
 
 /// <summary>
 ///     Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is
-///     cross platform but lacks things like file descriptor monitoring.
+///     cross-platform but lacks things like file descriptor monitoring.
 /// </summary>
 /// <remarks>This implementation is used for NetDriver.</remarks>
 internal class NetMainLoop : IMainLoopDriver

+ 35 - 20
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -116,7 +116,7 @@ internal class WindowsConsole
 
             var s = _stringBuilder.ToString ();
 
-            result = WriteConsole (_screenBuffer, s, (uint)s.Length, out uint _, null);
+            result = WriteConsole (_screenBuffer, s, (uint)s.Length, out uint _, nint.Zero);
         }
 
         if (!result)
@@ -134,7 +134,7 @@ internal class WindowsConsole
 
     public bool WriteANSI (string ansi)
     {
-        return WriteConsole (_screenBuffer, ansi, (uint)ansi.Length, out uint _, null);
+        return WriteConsole (_screenBuffer, ansi, (uint)ansi.Length, out uint _, nint.Zero);
     }
 
     public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window)
@@ -659,7 +659,7 @@ internal class WindowsConsole
     {
         public char Char { get; set; }
         public Attribute Attribute { get; set; }
-        public bool Empty { get; set; } // TODO: Temp hack until virutal terminal sequences
+        public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences
 
         public ExtendedCharInfo (char character, Attribute attribute)
         {
@@ -804,7 +804,7 @@ internal class WindowsConsole
         string lpbufer,
         uint NumberOfCharsToWriten,
         out uint lpNumberOfCharsWritten,
-        object lpReserved
+        nint lpReserved
     );
 
     [DllImport ("kernel32.dll")]
@@ -901,7 +901,7 @@ internal class WindowsConsole
         }
     }
 
-#if false // Not needed on the constructor. Perhaps could be used on resizing. To study.                                                                                     
+#if false // Not needed on the constructor. Perhaps could be used on resizing. To study.
 		[DllImport ("kernel32.dll", ExactSpelling = true)]
 		static extern IntPtr GetConsoleWindow ();
 
@@ -1266,18 +1266,18 @@ internal class WindowsDriver : ConsoleDriver
             return WinConsole?.WriteANSI (sb.ToString ()) ?? false;
         }
 
-        if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
-        {
-            GetCursorVisibility (out CursorVisibility cursorVisibility);
-            _cachedCursorVisibility = cursorVisibility;
-            SetCursorVisibility (CursorVisibility.Invisible);
+        //if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
+        //{
+        //    GetCursorVisibility (out CursorVisibility cursorVisibility);
+        //    _cachedCursorVisibility = cursorVisibility;
+        //    SetCursorVisibility (CursorVisibility.Invisible);
 
-            return false;
-        }
+        //    return false;
+        //}
 
-        SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default);
+        //SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default);
 
-        return _cachedCursorVisibility == CursorVisibility.Default;
+        //return _cachedCursorVisibility == CursorVisibility.Default;
     }
 
     #endregion Cursor Handling
@@ -1397,7 +1397,7 @@ internal class WindowsDriver : ConsoleDriver
             {
                 if (WinConsole is { })
                 {
-                    // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. 
+                    // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init.
                     // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED
                     Size winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
                     Cols = winSize.Width;
@@ -1642,7 +1642,7 @@ internal class WindowsDriver : ConsoleDriver
 
                     if (keyInfo.Modifiers != 0)
                     {
-                        // These Oem keys have well defined chars. We ensure the representative char is used.
+                        // These Oem keys have well-defined chars. We ensure the representative char is used.
                         // If we don't do this, then on some keyboard layouts the wrong char is 
                         // returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important
                         // for key persistence ("Ctrl++" vs. "Ctrl+=").
@@ -1656,7 +1656,7 @@ internal class WindowsDriver : ConsoleDriver
                         };
                     }
 
-                    // Return the mappedChar with they modifiers. Because mappedChar is un-shifted, if Shift was down
+                    // Return the mappedChar with modifiers. Because mappedChar is un-shifted, if Shift was down
                     // we should keep it
                     return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)mappedChar);
                 }
@@ -1901,8 +1901,8 @@ internal class WindowsDriver : ConsoleDriver
 
         // The ButtonState member of the MouseEvent structure has bit corresponding to each mouse button.
         // This will tell when a mouse button is pressed. When the button is released this event will
-        // be fired with it's bit set to 0. So when the button is up ButtonState will be 0.
-        // To map to the correct driver events we save the last pressed mouse button so we can
+        // be fired with its bit set to 0. So when the button is up ButtonState will be 0.
+        // To map to the correct driver events we save the last pressed mouse button, so we can
         // map to the correct clicked event.
         if ((_lastMouseButtonPressed is { } || _isButtonReleased) && mouseEvent.ButtonState != 0)
         {
@@ -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

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

@@ -77,7 +77,7 @@ public record ColorScheme : IEqualityOperators<ColorScheme, ColorScheme, bool>
         init => _focus = value;
     }
 
-    /// <summary>The foreground and background color for for text in a focused view that indicates a <see cref="View.HotKey"/>.</summary>
+    /// <summary>The foreground and background color for text in a focused view that indicates a <see cref="View.HotKey"/>.</summary>
     public Attribute HotFocus
     {
         get => _hotFocus;

+ 3 - 3
Terminal.Gui/Drawing/Glyphs.cs

@@ -348,7 +348,7 @@ public class GlyphDefinitions
     /// <summary>Box Drawings Left Tee - Heavy Vertical and Heavy Horizontal (U+2527) - ┣</summary>
     public Rune LeftTeeHvDblH { get; set; } = (Rune)'┣';
 
-    /// <summary>Box Drawings Righ Tee - Single Vertical and Single Horizontal (U+2524) - ┤</summary>
+    /// <summary>Box Drawings Right Tee - Single Vertical and Single Horizontal (U+2524) - ┤</summary>
     public Rune RightTee { get; set; } = (Rune)'┤';
 
     /// <summary>Box Drawings Right Tee - Single Vertical and Double Horizontal (U+2561) - ╡</summary>
@@ -442,13 +442,13 @@ public class GlyphDefinitions
 
 
     /// <summary>Shadow - Vertical Start - Left Half Block - ▌ U+0258c</summary>
-    public Rune ShadowVerticalStart { get; set; } =  (Rune)''; // Half: '\u2596'  ▖;
+    public Rune ShadowVerticalStart { get; set; } =  (Rune)''; // Half: '\u2596'  ▖;
 
     /// <summary>Shadow - Vertical - Left Half Block - ▌ U+0258c</summary>
     public Rune ShadowVertical { get; set; } = (Rune)'▌';
 
     /// <summary>Shadow - Horizontal Start - Upper Half Block - ▀ U+02580</summary>
-    public Rune ShadowHorizontalStart { get; set; } = (Rune)''; // Half: ▝ U+0259d;
+    public Rune ShadowHorizontalStart { get; set; } = (Rune)''; // Half: ▝ U+0259d;
 
     /// <summary>Shadow - Horizontal - Upper Half Block - ▀ U+02580</summary>
     public Rune ShadowHorizontal { get; set; } = (Rune)'▀';

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

@@ -199,7 +199,7 @@ public class LineCanvas : IDisposable
     }
 
     // TODO: Unless there's an obvious use case for this API we should delete it in favor of the
-    // simpler version that doensn't take an area.
+    // simpler version that doesn't take an area.
     /// <summary>
     ///     Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their
     ///     locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate
@@ -336,7 +336,7 @@ public class LineCanvas : IDisposable
         return Fill != null ? Fill.GetAttribute (intersects [0]!.Point) : intersects [0]!.Line.Attribute;
     }
 
-    private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
+    private Cell? GetCellForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
     {
         if (!intersects.Any ())
         {
@@ -356,7 +356,7 @@ public class LineCanvas : IDisposable
         return cell;
     }
 
-    private Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
+    private Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
     {
         if (!intersects.Any ())
         {
@@ -679,7 +679,7 @@ public class LineCanvas : IDisposable
         internal Rune _thickV;
         public IntersectionRuneResolver () { SetGlyphs (); }
 
-        public Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
+        public Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
         {
             bool useRounded = intersects.Any (
                                               i => i?.Line.Length != 0

+ 4 - 1
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>
@@ -41,7 +44,7 @@ public enum LineStyle
     RoundedDotted
 
     // TODO: Support Ruler
-    ///// <summary> 
+    ///// <summary>
     ///// The border is drawn as a diagnostic ruler ("|123456789...").
     ///// </summary>
     //Ruler

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

@@ -39,8 +39,8 @@ public class Ruler
                 _hTemplate.Repeat ((int)Math.Ceiling (Length + 2 / (double)_hTemplate.Length)) [start..(Length + start)];
 
             // Top
-            Application.Driver.Move (location.X, location.Y);
-            Application.Driver.AddStr (hrule);
+            Application.Driver?.Move (location.X, location.Y);
+            Application.Driver?.AddStr (hrule);
         }
         else
         {
@@ -50,8 +50,8 @@ public class Ruler
 
             for (int r = location.Y; r < location.Y + Length; r++)
             {
-                Application.Driver.Move (location.X, r);
-                Application.Driver.AddRune ((Rune)vrule [r - location.Y]);
+                Application.Driver?.Move (location.X, r);
+                Application.Driver?.AddRune ((Rune)vrule [r - location.Y]);
             }
         }
     }

+ 13 - 7
Terminal.Gui/Drawing/Thickness.cs

@@ -119,20 +119,20 @@ public record struct Thickness
         // Draw the Top side
         if (Top > 0)
         {
-            Application.Driver.FillRect (rect with { Height = Math.Min (rect.Height, Top) }, topChar);
+            Application.Driver?.FillRect (rect with { Height = Math.Min (rect.Height, Top) }, topChar);
         }
 
         // Draw the Left side
         // Draw the Left side
         if (Left > 0)
         {
-            Application.Driver.FillRect (rect with { Width = Math.Min (rect.Width, Left) }, leftChar);
+            Application.Driver?.FillRect (rect with { Width = Math.Min (rect.Width, Left) }, leftChar);
         }
 
         // Draw the Right side
         if (Right > 0)
         {
-            Application.Driver.FillRect (
+            Application.Driver?.FillRect (
                                          rect with
                                          {
                                              X = Math.Max (0, rect.X + rect.Width - Right),
@@ -145,7 +145,7 @@ public record struct Thickness
         // Draw the Bottom side
         if (Bottom > 0)
         {
-            Application.Driver.FillRect (
+            Application.Driver?.FillRect (
                                          rect with
                                          {
                                              Y = rect.Y + Math.Max (0, rect.Height - Bottom),
@@ -190,14 +190,20 @@ public record struct Thickness
         if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.Padding))
         {
             // Draw the diagnostics label on the bottom
+            string text = label is null ? string.Empty : $"{label} {this}";
             var tf = new TextFormatter
             {
-                Text = label is null ? string.Empty : $"{label} {this}",
+                Text = text,
                 Alignment = Alignment.Center,
                 VerticalAlignment = Alignment.End,
-                AutoSize = true
+                ConstrainToWidth = text.GetColumns (),
+                ConstrainToHeight = 1
             };
-            tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect);
+
+            if (Application.Driver?.CurrentAttribute is { })
+            {
+                tf.Draw (rect, Application.Driver!.CurrentAttribute, Application.Driver!.CurrentAttribute, rect);
+            }
         }
 
         return GetInside (rect);

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

@@ -33,7 +33,7 @@ public class AllowedTypeAny : IAllowedType
 public class AllowedType : IAllowedType
 {
     /// <summary>Initializes a new instance of the <see cref="AllowedType"/> class.</summary>
-    /// <param name="description">The human readable text to display.</param>
+    /// <param name="description">The human-readable text to display.</param>
     /// <param name="extensions">Extension(s) to match e.g. .csv.</param>
     public AllowedType (string description, params string [] extensions)
     {
@@ -46,7 +46,7 @@ public class AllowedType : IAllowedType
         Extensions = extensions;
     }
 
-    /// <summary>Gets or Sets the human readable description for the file type e.g. "Comma Separated Values".</summary>
+    /// <summary>Gets or Sets the human-readable description for the file type e.g. "Comma Separated Values".</summary>
     public string Description { get; set; }
 
     /// <summary>Gets or Sets the permitted file extension(s) (e.g. ".csv").</summary>

+ 1 - 1
Terminal.Gui/FileServices/FileDialogState.cs

@@ -54,7 +54,7 @@ internal class FileDialogState
                                    .ToList ();
             }
 
-            // if theres a UI filter in place too
+            // if there's a UI filter in place too
             if (Parent.CurrentFilter is { })
             {
                 children = children.Where (MatchesApiFilter).ToList ();

+ 7 - 5
Terminal.Gui/FileServices/FileDialogStyle.cs

@@ -1,4 +1,5 @@
-using System.Globalization;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
 using System.IO.Abstractions;
 using Terminal.Gui.Resources;
 using static System.Environment;
@@ -23,7 +24,7 @@ public class FileDialogStyle
     public string CancelButtonText { get; set; } = Strings.btnCancel;
 
     /// <summary>
-    ///     Gets or sets the class thatis responsible for determining which color to use to represent files and
+    ///     Gets or sets the class that is responsible for determining which color to use to represent files and
     ///     directories when <see cref="UseColors"/> is <see langword="true"/>.
     /// </summary>
     public FileSystemColorProvider ColorProvider { get; set; } = new ();
@@ -99,7 +100,7 @@ public class FileDialogStyle
     /// <summary>Gets or sets the header text displayed in the Modified column of the files table.</summary>
     public string ModifiedColumnName { get; set; } = Strings.fdModified;
 
-    /// <summary>Gets or sets the text on the 'Ok' button.  Typically you may want to change this to "Open" or "Save" etc.</summary>
+    /// <summary>Gets or sets the text on the 'Ok' button.  Typically, you may want to change this to "Open" or "Save" etc.</summary>
     public string OkButtonText { get; set; } = Strings.btnOk;
 
     /// <summary>Gets or sets the text displayed in the 'Path' text box when user has not supplied any input yet.</summary>
@@ -143,6 +144,7 @@ public class FileDialogStyle
     /// </summary>
     public string WrongFileTypeFeedback { get; set; } = Strings.fdWrongFileTypeFeedback;
 
+    [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
     private Dictionary<IDirectoryInfo, string> DefaultTreeRootGetter ()
     {
         Dictionary<IDirectoryInfo, string> roots = new ();
@@ -161,7 +163,7 @@ public class FileDialogStyle
         }
         catch (Exception)
         {
-            // Cannot get the system disks thats fine
+            // Cannot get the system disks, that's fine
         }
 
         try
@@ -193,7 +195,7 @@ public class FileDialogStyle
         }
         catch (Exception)
         {
-            // Cannot get the special files for this OS oh well
+            // Cannot get the special files for this OS, oh well
         }
 
         return roots;

+ 7 - 4
Terminal.Gui/Input/Command.cs

@@ -113,7 +113,7 @@ public enum Command
     /// <summary>Move one page down.</summary>
     PageDown,
 
-    /// <summary>Move one page page extending the selection to cover revealed objects/characters.</summary>
+    /// <summary>Move one page down extending the selection to cover revealed objects/characters.</summary>
     PageDownExtend,
 
     /// <summary>Move one page up.</summary>
@@ -137,7 +137,7 @@ public enum Command
     /// <summary>Open the selected item.</summary>
     OpenSelectedItem,
 
-    /// <summary>Toggles the Expanded or collapsed state of a a list or item (with subitems).</summary>
+    /// <summary>Toggles the Expanded or collapsed state of a list or item (with subitems).</summary>
     ToggleExpandCollapse,
 
     /// <summary>Expands a list or item (with subitems).</summary>
@@ -221,16 +221,19 @@ public enum Command
     /// <summary>Pastes the current selection.</summary>
     Paste,
 
+    /// TODO: IRunnable: Rename to Command.Quit to make more generic.
     /// <summary>Quit a <see cref="Toplevel"/>.</summary>
     QuitToplevel,
 
-    /// <summary>Suspend a application (Only implemented in <see cref="CursesDriver"/>).</summary>
+    /// TODO: Overlapped: Add Command.ShowHide
+
+    /// <summary>Suspend an application (Only implemented in <see cref="CursesDriver"/>).</summary>
     Suspend,
 
     /// <summary>Moves focus to the next view.</summary>
     NextView,
 
-    /// <summary>Moves focuss to the previous view.</summary>
+    /// <summary>Moves focus to the previous view.</summary>
     PreviousView,
 
     /// <summary>Moves focus to the next view or Toplevel (case of Overlapped).</summary>

+ 22 - 5
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,11 +484,11 @@ 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>
-    /// <param name="separator">The character to use as a separator between modifier keys and and the key itself.</param>
+    /// <param name="separator">The character to use as a separator between modifier keys and the key itself.</param>
     /// <returns>
     ///     The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key
     ///     name will be returned.
@@ -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;
+            }
+        }
+    }
 }

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

@@ -21,12 +21,28 @@ public record struct KeyBinding
         Context = context;
     }
 
+    /// <summary>Initializes a new instance.</summary>
+    /// <param name="commands">The commands this key binding will invoke.</param>
+    /// <param name="scope">The scope of the <see cref="Commands"/>.</param>
+    /// <param name="boundView">The view the key binding is bound to.</param>
+    /// <param name="context">Arbitrary context that can be associated with this key binding.</param>
+    public KeyBinding (Command [] commands, KeyBindingScope scope, View? boundView, object? context = null)
+    {
+        Commands = commands;
+        Scope = scope;
+        BoundView = boundView;
+        Context = context;
+    }
+
     /// <summary>The commands this key binding will invoke.</summary>
     public Command [] Commands { get; set; }
 
     /// <summary>The scope of the <see cref="Commands"/>.</summary>
     public KeyBindingScope Scope { get; set; }
 
+    /// <summary>The view the key binding is bound to.</summary>
+    public View? BoundView { get; set; }
+
     /// <summary>
     ///     Arbitrary context that can be associated with this key binding.
     /// </summary>

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

@@ -45,5 +45,5 @@ public enum KeyBindingScope
     ///         any of its subviews, and if the key was not bound to a <see cref="View.HotKey"/>.
     ///     </para>
     /// </remarks>
-    Application = 4
+    Application = 4,
 }

+ 174 - 43
Terminal.Gui/Input/KeyBindings.cs

@@ -1,50 +1,49 @@
 #nullable enable
 
-using System.Diagnostics;
-
 namespace Terminal.Gui;
 
 /// <summary>
-/// Provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.
+///     Provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.
 /// </summary>
 public class KeyBindings
 {
     /// <summary>
     ///     Initializes a new instance. This constructor is used when the <see cref="KeyBindings"/> are not bound to a
-    ///     <see cref="View"/>, such as in unit tests.
+    ///     <see cref="View"/>. This is used for Application.KeyBindings and unit tests.
     /// </summary>
     public KeyBindings () { }
 
     /// <summary>Initializes a new instance bound to <paramref name="boundView"/>.</summary>
     public KeyBindings (View boundView) { BoundView = boundView; }
 
-    /// <summary>
-    ///     The view that the <see cref="KeyBindings"/> are bound to.
-    /// </summary>
-    public View? BoundView { get; }
-
-    // TODO: Add a dictionary comparer that ignores Scope
-    // TODO: This should not be public!
-    /// <summary>The collection of <see cref="KeyBinding"/> objects.</summary>
-    public Dictionary<Key, KeyBinding> Bindings { get; } = new ();
-
     /// <summary>Adds a <see cref="KeyBinding"/> to the collection.</summary>
     /// <param name="key"></param>
     /// <param name="binding"></param>
-    public void Add (Key key, KeyBinding binding)
+    /// <param name="boundViewForAppScope">Optional View for <see cref="KeyBindingScope.Application"/> bindings.</param>
+    public void Add (Key key, KeyBinding binding, View? boundViewForAppScope = null)
     {
+        if (BoundView is { } && binding.Scope.FastHasFlags (KeyBindingScope.Application))
+        {
+            throw new ArgumentException ("Application scoped KeyBindings must be added via Application.KeyBindings.Add");
+        }
+
         if (TryGet (key, out KeyBinding _))
         {
-            Bindings [key] = binding;
+            throw new InvalidOperationException (@$"A key binding for {key} exists ({binding}).");
+
+            //Bindings [key] = binding;
+        }
+
+        if (BoundView is { })
+        {
+            binding.BoundView = BoundView;
         }
         else
         {
-            Bindings.Add (key, binding);
-            if (binding.Scope.HasFlag (KeyBindingScope.Application))
-            {
-                Application.AddKeyBinding (key, BoundView);
-            }
+            binding.BoundView = boundViewForAppScope;
         }
+
+        Bindings.Add (key, binding);
     }
 
     /// <summary>
@@ -60,13 +59,19 @@ public class KeyBindings
     /// </remarks>
     /// <param name="key">The key to check.</param>
     /// <param name="scope">The scope for the command.</param>
+    /// <param name="boundViewForAppScope">Optional View for <see cref="KeyBindingScope.Application"/> bindings.</param>
     /// <param name="commands">
     ///     The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
     ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
     ///     consumed if any took effect.
     /// </param>
-    public void Add (Key key, KeyBindingScope scope, params Command [] commands)
+    public void Add (Key key, KeyBindingScope scope, View? boundViewForAppScope = null, params Command [] commands)
     {
+        if (BoundView is { } && scope.FastHasFlags (KeyBindingScope.Application))
+        {
+            throw new ArgumentException ("Application scoped KeyBindings must be added via Application.KeyBindings.Add");
+        }
+
         if (key is null || !key.IsValid)
         {
             //throw new ArgumentException ("Invalid Key", nameof (commands));
@@ -78,14 +83,57 @@ public class KeyBindings
             throw new ArgumentException (@"At least one command must be specified", nameof (commands));
         }
 
-        if (TryGet (key, out KeyBinding _))
+        if (TryGet (key, out KeyBinding binding))
         {
-            Bindings [key] = new (commands, scope);
+            throw new InvalidOperationException (@$"A key binding for {key} exists ({binding}).");
+
+            //Bindings [key] = new (commands, scope, BoundView);
         }
-        else
+
+        Add (key, new KeyBinding (commands, scope, BoundView), boundViewForAppScope);
+    }
+
+    /// <summary>
+    ///     <para>Adds a new key combination that will trigger the commands in <paramref name="commands"/>.</para>
+    ///     <para>
+    ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
+    ///         <paramref name="commands"/>.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
+    ///     focus to another view and perform multiple commands there).
+    /// </remarks>
+    /// <param name="key">The key to check.</param>
+    /// <param name="scope">The scope for the command.</param>
+    /// <param name="commands">
+    ///     The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
+    ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
+    ///     consumed if any took effect.
+    /// </param>
+    public void Add (Key key, KeyBindingScope scope, params Command [] commands)
+    {
+        if (BoundView is { } && scope.FastHasFlags (KeyBindingScope.Application))
         {
-            Add (key, new KeyBinding (commands, scope));
+            throw new ArgumentException ("Application scoped KeyBindings must be added via Application.KeyBindings.Add");
+        }
+
+        if (key == Key.Empty || !key.IsValid)
+        {
+            throw new ArgumentException (@"Invalid Key", nameof (commands));
+        }
+
+        if (commands.Length == 0)
+        {
+            throw new ArgumentException (@"At least one command must be specified", nameof (commands));
         }
+
+        if (TryGet (key, out KeyBinding binding))
+        {
+            throw new InvalidOperationException (@$"A key binding for {key} exists ({binding}).");
+        }
+
+        Add (key, new KeyBinding (commands, scope, BoundView));
     }
 
     /// <summary>
@@ -94,8 +142,9 @@ public class KeyBindings
     ///         View - see <see cref="View.GetSupportedCommands"/>).
     ///     </para>
     ///     <para>
-    ///         This is a helper function for <see cref="Add(Key,KeyBindingScope,Terminal.Gui.Command[])"/> for
-    ///         <see cref="KeyBindingScope.Focused"/> scoped commands.
+    ///         This is a helper function for <see cref="Add(Key,KeyBinding,View?)"/>. If used for a View (
+    ///         <see cref="BoundView"/> is set), the scope will be set to <see cref="KeyBindingScope.Focused"/>.
+    ///         Otherwise, it will be set to <see cref="KeyBindingScope.Application"/>.
     ///     </para>
     ///     <para>
     ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
@@ -107,24 +156,74 @@ public class KeyBindings
     ///     focus to another view and perform multiple commands there).
     /// </remarks>
     /// <param name="key">The key to check.</param>
+    /// <param name="boundViewForAppScope">Optional View for <see cref="KeyBindingScope.Application"/> bindings.</param>
     /// <param name="commands">
     ///     The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
     ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
     ///     consumed if any took effect.
     /// </param>
-    public void Add (Key key, params Command [] commands)
+    public void Add (Key key, View? boundViewForAppScope = null, params Command [] commands)
     {
-        Add (key, KeyBindingScope.Focused, commands);
+        if (BoundView is null && boundViewForAppScope is null)
+        {
+            throw new ArgumentException (@"Application scoped KeyBindings must provide a bound view to Add.", nameof (boundViewForAppScope));
+        }
+
+        Add (key, BoundView is { } ? KeyBindingScope.Focused : KeyBindingScope.Application, boundViewForAppScope, commands);
     }
 
-    /// <summary>Removes all <see cref="KeyBinding"/> objects from the collection.</summary>
-    public void Clear ()
+    /// <summary>
+    ///     <para>
+    ///         Adds a new key combination that will trigger the commands in <paramref name="commands"/> (if supported by the
+    ///         View - see <see cref="View.GetSupportedCommands"/>).
+    ///     </para>
+    ///     <para>
+    ///         This is a helper function for <see cref="Add(Key,KeyBinding,View?)"/>. If used for a View (
+    ///         <see cref="BoundView"/> is set), the scope will be set to <see cref="KeyBindingScope.Focused"/>.
+    ///         Otherwise, it will be set to <see cref="KeyBindingScope.Application"/>.
+    ///     </para>
+    ///     <para>
+    ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
+    ///         <paramref name="commands"/>.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
+    ///     focus to another view and perform multiple commands there).
+    /// </remarks>
+    /// <param name="key">The key to check.</param>
+    /// <param name="commands">
+    ///     The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
+    ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
+    ///     consumed if any took effect.
+    /// </param>
+    public void Add (Key key, params Command [] commands)
     {
-        Application.ClearKeyBindings (BoundView);
+        if (BoundView is null)
+        {
+            throw new ArgumentException (@"Application scoped KeyBindings must provide a boundViewForAppScope to Add.");
+        }
 
-        Bindings.Clear ();
+        Add (key, BoundView is { } ? KeyBindingScope.Focused : KeyBindingScope.Application, null, commands);
     }
 
+    // TODO: Add a dictionary comparer that ignores Scope
+    // TODO: This should not be public!
+    /// <summary>The collection of <see cref="KeyBinding"/> objects.</summary>
+    public Dictionary<Key, KeyBinding> Bindings { get; } = new ();
+
+    /// <summary>
+    ///     The view that the <see cref="KeyBindings"/> are bound to.
+    /// </summary>
+    /// <remarks>
+    ///     If <see langword="null"/>, the <see cref="KeyBindings"/> are not bound to a <see cref="View"/>. This is used for
+    ///     Application.KeyBindings.
+    /// </remarks>
+    public View? BoundView { get; }
+
+    /// <summary>Removes all <see cref="KeyBinding"/> objects from the collection.</summary>
+    public void Clear () { Bindings.Clear (); }
+
     /// <summary>
     ///     Removes all key bindings that trigger the given command set. Views can have multiple different keys bound to
     ///     the same command sets and this method will clear all of them.
@@ -151,6 +250,7 @@ public class KeyBindings
         {
             return binding;
         }
+
         throw new InvalidOperationException ($"Key {key} is not bound.");
     }
 
@@ -164,6 +264,7 @@ public class KeyBindings
         {
             return binding;
         }
+
         throw new InvalidOperationException ($"Key {key}/{scope} is not bound.");
     }
 
@@ -192,21 +293,51 @@ public class KeyBindings
 
     /// <summary>Removes a <see cref="KeyBinding"/> from the collection.</summary>
     /// <param name="key"></param>
-    public void Remove (Key key)
+    /// <param name="boundViewForAppScope">Optional View for <see cref="KeyBindingScope.Application"/> bindings.</param>
+    public void Remove (Key key, View? boundViewForAppScope = null)
     {
+        if (!TryGet (key, out KeyBinding binding))
+        {
+            return;
+        }
+
         Bindings.Remove (key);
-        Application.RemoveKeyBinding (key, BoundView);
+    }
+
+    /// <summary>Replaces the commands already bound to a key.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         If the key is not already bound, it will be added.
+    ///     </para>
+    /// </remarks>
+    /// <param name="key">The key bound to the command to be replaced.</param>
+    /// <param name="commands">The set of commands to replace the old ones with.</param>
+    public void ReplaceCommands (Key key, params Command [] commands)
+    {
+        if (TryGet (key, out KeyBinding binding))
+        {
+            binding.Commands = commands;
+        }
+        else
+        {
+            Add (key, commands);
+        }
     }
 
     /// <summary>Replaces a key combination already bound to a set of <see cref="Command"/>s.</summary>
     /// <remarks></remarks>
     /// <param name="oldKey">The key to be replaced.</param>
-    /// <param name="newKey">The new key to be used.</param>
-    public void Replace (Key oldKey, Key newKey)
+    /// <param name="newKey">The new key to be used. If <see cref="Key.Empty"/> no action will be taken.</param>
+    public void ReplaceKey (Key oldKey, Key newKey)
     {
         if (!TryGet (oldKey, out KeyBinding _))
         {
-            return;
+            throw new InvalidOperationException ($"Key {oldKey} is not bound.");
+        }
+
+        if (!newKey.IsValid)
+        {
+            throw new InvalidOperationException ($"Key {newKey} is is not valid.");
         }
 
         KeyBinding value = Bindings [oldKey];
@@ -224,13 +355,13 @@ public class KeyBindings
     /// <returns><see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.</returns>
     public bool TryGet (Key key, out KeyBinding binding)
     {
+        binding = new (Array.Empty<Command> (), KeyBindingScope.Disabled, null);
+
         if (key.IsValid)
         {
             return Bindings.TryGetValue (key, out binding);
         }
 
-        binding = new (Array.Empty<Command> (), KeyBindingScope.Focused);
-
         return false;
     }
 
@@ -245,6 +376,8 @@ public class KeyBindings
     /// <returns><see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.</returns>
     public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding)
     {
+        binding = new (Array.Empty<Command> (), KeyBindingScope.Disabled, null);
+
         if (key.IsValid && Bindings.TryGetValue (key, out binding))
         {
             if (scope.HasFlag (binding.Scope))
@@ -253,8 +386,6 @@ public class KeyBindings
             }
         }
 
-        binding = new (Array.Empty<Command> (), KeyBindingScope.Focused);
-
         return false;
     }
 }

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

@@ -13,7 +13,7 @@ public class MouseEventEventArgs : EventArgs
 
     /// <summary>
     ///     Indicates if the current mouse event has already been processed and the driver should stop notifying any other
-    ///     event subscriber. Its important to set this value to true specially when updating any View's layout from inside the
+    ///     event subscriber. It's important to set this value to true specially when updating any View's layout from inside the
     ///     subscriber method.
     /// </summary>
     /// <remarks>

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

+ 2 - 4
Terminal.Gui/README.md

@@ -6,9 +6,7 @@ All files required to build the **Terminal.Gui** library (and NuGet package).
 
 - `\` - The root folder contains the source code for the library.
 	- `Terminal.Gui.sln` - The Visual Studio solution
-	- `Application.cs` - A `static` class that provides the base 'application driver'. Given it defines a **Terminal.Gui** application it is both logically and literally (because `static`) a singleton. It has direct dependencies on `MainLoop`, `Events.cs` `NetDriver`, `CursesDriver`, `WindowsDriver`, `Responder`, `View`, and `TopLevel` (and nothing else).
-	- `MainLoop.cs` - Defines `IMainLoopDriver` and implements the `MainLoop` class.
-	- A few supporting class files
+	- `Application\` - The core `Application` logic, including `Application.cs`, which is is a `static` class that provides the base 'application engine', `RunState`, and `MainLoop`.
 
 - `ConsoleDrivers\`
 	- `ConsoleDriver.cs` - Definition for the Console Driver API.
@@ -38,7 +36,7 @@ All files required to build the **Terminal.Gui** library (and NuGet package).
 	- `Dialog` -
 	- etc...
 
-- `Types/` - A folder (not namespace) containing implementations of `Point`, `Rect`, and `Size` which are ancient versions of the modern `System.Drawing.Point`, `System.Drawing.Size`, and `System.Drawning.Rectangle`.
+- `FileServcies/` - File services classes.
 
 ## Version numbers
 

+ 10 - 6
Terminal.Gui/Resources/config.json

@@ -1,9 +1,9 @@
 {
   // Specifies the "source of truth" for default values for all Terminal.Gui settings managed by
   // ConfigurationManager. It is automatically loaded, and applied, each time Application.Init
-  // is run (via the ConfiguraitonManager.Reset method). 
+  // is run (via the ConfigurationManager.Reset method).
   //
-  // In otherwords, initial values set in the the codebase are always overwritten by the contents of this 
+  // In other words, initial values set in the the codebase are always overwritten by the contents of this 
   // resource embedded in the Terminal.Gui.dll assembly.
   //
   // The Unit Test method "ConfigurationManagerTests.SaveDefaults" can be used to re-create the base of this file, but
@@ -17,9 +17,12 @@
   // to throw exceptions. 
   "ConfigurationManager.ThrowOnJsonErrors": false,
 
-  "Application.AlternateBackwardKey": "Ctrl+PageUp",
-  "Application.AlternateForwardKey": "Ctrl+PageDown",
+  "Application.NextTabKey": "Tab",
+  "Application.PrevTabKey": "Shift+Tab",
+  "Application.NextTabGroupKey": "F6",
+  "Application.PrevTabGroupKey": "Shift+F6",
   "Application.QuitKey": "Esc",
+  "Key.Separator": "+",
 
   "Theme": "Default",
   "Themes": [
@@ -29,8 +32,9 @@
         "Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
         "FrameView.DefaultBorderStyle": "Single",
         "Window.DefaultBorderStyle": "Single",
-        "Dialog.DefaultBorderStyle": "Single",
-        "MessageBox.DefaultBorderStyle": "Double",
+        "Dialog.DefaultBorderStyle": "Heavy",
+        "MessageBox.DefaultButtonAlignment": "Center",
+        "MessageBox.DefaultBorderStyle": "Heavy",
         "Button.DefaultShadow": "None",
         "ColorSchemes": [
           {

+ 1 - 1
Terminal.Gui/StackExtensions.cs

@@ -160,7 +160,7 @@ public static class StackExtensions
         }
     }
 
-    /// <summary>Replaces an stack object values that match with the value to replace.</summary>
+    /// <summary>Replaces a stack object values that match with the value to replace.</summary>
     /// <typeparam name="T">The stack object type.</typeparam>
     /// <param name="stack">The stack object.</param>
     /// <param name="valueToReplace">Value to replace.</param>

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

@@ -25,10 +25,13 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <DefineTrace>true</DefineTrace>
     <DebugType>portable</DebugType>
-    <DefineConstants>$(DefineConstants);JETBRAINS_ANNOTATIONS;CONTRACTS_FULL;CODE_ANALYSIS</DefineConstants>
+    <DefineConstants>$(DefineConstants);CONTRACTS_FULL;CODE_ANALYSIS</DefineConstants>
     <ImplicitUsings>enable</ImplicitUsings>
     <NoLogo>true</NoLogo>
     <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
+    <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
+    <IsTrimmable>true</IsTrimmable>
+    <IsAotCompatible>true</IsAotCompatible>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
     <DefineDebug>true</DefineDebug>
@@ -50,7 +53,7 @@
   <!-- =================================================================== -->
   <ItemGroup>
     <PackageReference Include="ColorHelper" Version="[1.8.1,2)" />
-    <PackageReference Include="JetBrains.Annotations" Version="[2024,)" PrivateAssets="all" />
+    <PackageReference Include="JetBrains.Annotations" Version="[2024.2.0,)" PrivateAssets="all" />
     <PackageReference Include="Microsoft.CodeAnalysis" Version="[4.10,5)" PrivateAssets="all" />
     <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="[4.10,5)" PrivateAssets="all" />
     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="[4.10,5)" PrivateAssets="all" />
@@ -59,11 +62,6 @@
     <PackageReference Include="System.IO.Abstractions" Version="[21.0.22,22)" />
     <PackageReference Include="System.Text.Json" Version="[8.0.4,9)" />
     <PackageReference Include="Wcwidth" Version="[2,3)" />
-    <ProjectReference Include="..\Analyzers\Terminal.Gui.Analyzers.Internal\Terminal.Gui.Analyzers.Internal.csproj">
-      <PrivateAssets>all</PrivateAssets>
-      <OutputItemType>Analyzer</OutputItemType>
-      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
-    </ProjectReference>
   </ItemGroup>
   <!-- =================================================================== -->
   <!-- Global Usings and Type Aliases -->

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

@@ -105,8 +105,8 @@ public class AppendAutocomplete : AutocompleteBase
             return;
         }
 
-        // draw it like its selected even though its not
-        Application.Driver.SetAttribute (
+        // draw it like it's selected, even though it's not
+        Application.Driver?.SetAttribute (
                                          new Attribute (
                                                         ColorScheme.Normal.Foreground,
                                                         textField.ColorScheme.Focus.Background
@@ -128,7 +128,7 @@ public class AppendAutocomplete : AutocompleteBase
                                   );
         }
 
-        Application.Driver.AddStr (fragment);
+        Application.Driver?.AddStr (fragment);
     }
 
     /// <summary>

Some files were not shown because too many files changed in this diff