2
0
Эх сурвалжийг харах

Merge branch 'v2_develop' into v2_release

Tig 1 жил өмнө
parent
commit
9825cb6079
100 өөрчлөгдсөн 6167 нэмэгдсэн , 3475 устгасан
  1. 6 6
      .github/workflows/api-docs.yml
  2. 3 3
      .github/workflows/publish.yml
  3. 1 1
      CommunityToolkitExample/Program.cs
  4. 14 11
      GitVersion.yml
  5. 0 3
      ReactiveExample/FodyWeavers.xml
  6. 0 26
      ReactiveExample/FodyWeavers.xsd
  7. 126 174
      ReactiveExample/LoginView.cs
  8. 38 43
      ReactiveExample/LoginViewModel.cs
  9. 1 1
      ReactiveExample/Program.cs
  10. 1 1
      ReactiveExample/README.md
  11. 1 1
      ReactiveExample/ReactiveExample.csproj
  12. 24 0
      ReactiveExample/ViewExtensions.cs
  13. 25 2
      SelfContained/Program.cs
  14. 1 1
      SelfContained/README.md
  15. 1 1
      SelfContained/SelfContained.csproj
  16. 29 0
      Terminal.Gui/Application/Application.Driver.cs
  17. 214 0
      Terminal.Gui/Application/Application.Initialization.cs
  18. 467 0
      Terminal.Gui/Application/Application.Keyboard.cs
  19. 28 28
      Terminal.Gui/Application/Application.Mouse.cs
  20. 10 0
      Terminal.Gui/Application/Application.Navigation.cs
  21. 883 0
      Terminal.Gui/Application/Application.Run.cs
  22. 53 0
      Terminal.Gui/Application/Application.Screen.cs
  23. 56 0
      Terminal.Gui/Application/Application.Toplevel.cs
  24. 102 1390
      Terminal.Gui/Application/Application.cs
  25. 0 303
      Terminal.Gui/Application/ApplicationKeyboard.cs
  26. 229 0
      Terminal.Gui/Application/ApplicationNavigation.cs
  27. 444 0
      Terminal.Gui/Application/ApplicationOverlapped.cs
  28. 1 1
      Terminal.Gui/Application/MainLoopSyncContext.cs
  29. 11 22
      Terminal.Gui/Clipboard/Clipboard.cs
  30. 4 4
      Terminal.Gui/Clipboard/IClipboard.cs
  31. 1 1
      Terminal.Gui/Configuration/ConfigurationManager.cs
  32. 1 1
      Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs
  33. 1 1
      Terminal.Gui/Configuration/SerializableConfigurationProperty.cs
  34. 1 1
      Terminal.Gui/Configuration/ThemeManager.cs
  35. 120 149
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  36. 1 1
      Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
  37. 1 1
      Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs
  38. 2 2
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  39. 1 1
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs
  40. 44 0
      Terminal.Gui/ConsoleDrivers/CursorVisibility.cs
  41. 1 1
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs
  42. 4 4
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
  43. 2 2
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  44. 1 1
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  45. 16 16
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  46. 1 1
      Terminal.Gui/Drawing/ColorScheme.cs
  47. 3 3
      Terminal.Gui/Drawing/Glyphs.cs
  48. 4 4
      Terminal.Gui/Drawing/LineCanvas.cs
  49. 1 1
      Terminal.Gui/Drawing/LineStyle.cs
  50. 4 4
      Terminal.Gui/Drawing/Ruler.cs
  51. 13 7
      Terminal.Gui/Drawing/Thickness.cs
  52. 2 2
      Terminal.Gui/FileServices/AllowedType.cs
  53. 1 1
      Terminal.Gui/FileServices/FileDialogState.cs
  54. 4 4
      Terminal.Gui/FileServices/FileDialogStyle.cs
  55. 7 4
      Terminal.Gui/Input/Command.cs
  56. 1 1
      Terminal.Gui/Input/Key.cs
  57. 16 0
      Terminal.Gui/Input/KeyBinding.cs
  58. 1 1
      Terminal.Gui/Input/KeyBindingScope.cs
  59. 174 43
      Terminal.Gui/Input/KeyBindings.cs
  60. 1 1
      Terminal.Gui/Input/MouseEventEventArgs.cs
  61. 9 6
      Terminal.Gui/Resources/config.json
  62. 1 1
      Terminal.Gui/StackExtensions.cs
  63. 3 3
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs
  64. 6 6
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  65. 437 349
      Terminal.Gui/Text/TextFormatter.cs
  66. 1 1
      Terminal.Gui/View/Adornment/Adornment.cs
  67. 11 9
      Terminal.Gui/View/Adornment/Border.cs
  68. 22 7
      Terminal.Gui/View/Adornment/Margin.cs
  69. 14 3
      Terminal.Gui/View/Adornment/ShadowView.cs
  70. 29 0
      Terminal.Gui/View/DrawEventArgs.cs
  71. 1 1
      Terminal.Gui/View/EventArgs.cs
  72. 31 10
      Terminal.Gui/View/Layout/Dim.cs
  73. 332 195
      Terminal.Gui/View/Layout/DimAuto.cs
  74. 4 0
      Terminal.Gui/View/Layout/DimAutoStyle.cs
  75. 3 22
      Terminal.Gui/View/Layout/DimCombine.cs
  76. 5 5
      Terminal.Gui/View/Layout/DimView.cs
  77. 12 0
      Terminal.Gui/View/Layout/LayoutEventArgs.cs
  78. 10 16
      Terminal.Gui/View/Layout/Pos.cs
  79. 2 1
      Terminal.Gui/View/Layout/PosCenter.cs
  80. 7 7
      Terminal.Gui/View/Layout/PosView.cs
  81. 27 0
      Terminal.Gui/View/Navigation/FocusEventArgs.cs
  82. 22 0
      Terminal.Gui/View/Navigation/TabBehavior.cs
  83. 13 0
      Terminal.Gui/View/NavigationDirection.cs
  84. 42 0
      Terminal.Gui/View/Orientation/IOrientation.cs
  85. 0 0
      Terminal.Gui/View/Orientation/Orientation.cs
  86. 138 0
      Terminal.Gui/View/Orientation/OrientationHelper.cs
  87. 1 1
      Terminal.Gui/View/View.Adornments.cs
  88. 15 0
      Terminal.Gui/View/View.Arrangement.cs
  89. 0 0
      Terminal.Gui/View/View.Content.cs
  90. 35 0
      Terminal.Gui/View/View.Cursor.cs
  91. 0 0
      Terminal.Gui/View/View.Diagnostics.cs
  92. 24 9
      Terminal.Gui/View/View.Drawing.cs
  93. 323 0
      Terminal.Gui/View/View.Hierarchy.cs
  94. 43 134
      Terminal.Gui/View/View.Keyboard.cs
  95. 371 317
      Terminal.Gui/View/View.Layout.cs
  96. 1 1
      Terminal.Gui/View/View.Mouse.cs
  97. 875 0
      Terminal.Gui/View/View.Navigation.cs
  98. 74 67
      Terminal.Gui/View/View.Text.cs
  99. 4 8
      Terminal.Gui/View/View.cs
  100. 16 15
      Terminal.Gui/View/ViewArrangement.cs

+ 6 - 6
.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'
+      #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:

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

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

+ 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

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

+ 25 - 2
SelfContained/Program.cs

@@ -1,6 +1,8 @@
-// This is a simple example application for a self-contained single file.
+// 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;
@@ -10,7 +12,28 @@ public static class Program
     [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Run<T>(Func<Exception, Boolean>, ConsoleDriver)")]
     private static void Main (string [] args)
     {
-        Application.Run<ExampleWindow> ().Dispose ();
+        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 ();

+ 1 - 1
SelfContained/README.md

@@ -1,6 +1,6 @@
 # Terminal.Gui C# SelfContained
 
-This example shows how to use the `Terminal.Gui` library to create a simple `self-contained` `single file` GUI application in C#.
+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`.
 

+ 1 - 1
SelfContained/SelfContained.csproj

@@ -8,7 +8,7 @@
     <PublishTrimmed>true</PublishTrimmed>
     <TrimMode>Link</TrimMode>
     <PublishSingleFile>true</PublishSingleFile>
-    <InvariantGlobalization>true</InvariantGlobalization>
+    <InvariantGlobalization>false</InvariantGlobalization>
     <DebugType>embedded</DebugType>
   </PropertyGroup>
 

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

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

@@ -0,0 +1,467 @@
+#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))]
+    [JsonConverter (typeof (KeyJsonConverter))]
+    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))]
+    [JsonConverter (typeof (KeyJsonConverter))]
+    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))]
+    [JsonConverter (typeof (KeyJsonConverter))]
+    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))]
+    [JsonConverter (typeof (KeyJsonConverter))]
+    public static Key PrevTabGroupKey
+    {
+        get => _prevTabGroupKey;
+        set
+        {
+            if (_prevTabGroupKey != value)
+            {
+                ReplaceKey (_prevTabGroupKey, value);
+                _prevTabGroupKey = value;
+            }
+        }
+    }
+
+    private static Key _quitKey = Key.Esc; // Resources/config.json overrrides
+
+    /// <summary>Gets or sets the key to quit the application.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    [JsonConverter (typeof (KeyJsonConverter))]
+    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 - 1390
Terminal.Gui/Application/Application.cs

@@ -1,7 +1,9 @@
+#nullable enable
 using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Reflection;
+using System.Resources;
+using Terminal.Gui.Resources;
 
 namespace Terminal.Gui;
 
@@ -9,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 ();
@@ -57,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 (AppContext.BaseDirectory)}.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
@@ -82,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
 
@@ -110,7 +165,7 @@ public static partial class Application
         // MainLoop stuff
         MainLoop?.Dispose ();
         MainLoop = null;
-        _mainThreadId = -1;
+        MainThreadId = -1;
         Iteration = null;
         EndAfterFirstIteration = false;
 
@@ -134,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;
@@ -146,1365 +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>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    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.
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    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>
-    [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 ();
-        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>
-    [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 (!_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);

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

@@ -125,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>

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

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

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

@@ -9,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

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

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

@@ -291,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;

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

@@ -1669,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

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

@@ -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)
         {
@@ -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)
         {

+ 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

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

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

+ 4 - 4
Terminal.Gui/FileServices/FileDialogStyle.cs

@@ -24,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 ();
@@ -100,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>
@@ -163,7 +163,7 @@ public class FileDialogStyle
         }
         catch (Exception)
         {
-            // Cannot get the system disks thats fine
+            // Cannot get the system disks, that's fine
         }
 
         try
@@ -195,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>

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

@@ -487,7 +487,7 @@ public class Key : EventArgs, IEquatable<Key>
 
     /// <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.

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

+ 9 - 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,8 +17,10 @@
   // 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",
 
   "Theme": "Default",
@@ -29,8 +31,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>

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

+ 6 - 6
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs

@@ -322,7 +322,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
 
         if (PopupInsideContainer)
         {
-            // don't overspill horizontally, let's see if can be displayed on the left
+            // don't overspill horizontally, let's see if it can be displayed on the left
             if (width > HostControl.Viewport.Width - renderAt.X)
             {
                 // Verifies that the left limit available is greater than the right limit
@@ -339,7 +339,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         }
         else
         {
-            // don't overspill horizontally, let's see if can be displayed on the left
+            // don't overspill horizontally, let's see if it can be displayed on the left
             if (width > top.Viewport.Width - (renderAt.X + HostControl.Frame.X))
             {
                 // Verifies that the left limit available is greater than the right limit
@@ -376,18 +376,18 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         {
             if (i == SelectedIdx - ScrollOffset)
             {
-                Application.Driver.SetAttribute (ColorScheme.Focus);
+                Application.Driver?.SetAttribute (ColorScheme.Focus);
             }
             else
             {
-                Application.Driver.SetAttribute (ColorScheme.Normal);
+                Application.Driver?.SetAttribute (ColorScheme.Normal);
             }
 
             popup.Move (0, i);
 
             string text = TextFormatter.ClipOrPad (toRender [i].Title, width);
 
-            Application.Driver.AddStr (text);
+            Application.Driver?.AddStr (text);
         }
     }
 
@@ -408,7 +408,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
 
     /// <summary>
     ///     Called when the user confirms a selection at the current cursor location in the <see cref="HostControl"/>. The
-    ///     <paramref name="accepted"/> string is the full autocomplete word to be inserted. Typically a host will have to
+    ///     <paramref name="accepted"/> string is the full autocomplete word to be inserted. Typically, a host will have to
     ///     remove some characters such that the <paramref name="accepted"/> string completes the word instead of simply being
     ///     appended.
     /// </summary>

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 437 - 349
Terminal.Gui/Text/TextFormatter.cs


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

@@ -165,7 +165,7 @@ public class Adornment : View
         {
             if (TextFormatter is { })
             {
-                TextFormatter.Size = Frame.Size;
+                TextFormatter.ConstrainToSize = Frame.Size;
                 TextFormatter.NeedsFormat = true;
             }
         }

+ 11 - 9
Terminal.Gui/View/Adornment/Border.cs

@@ -74,7 +74,7 @@ public class Border : Adornment
     public override void BeginInit ()
     {
 #if HOVER
-        // TOOD: Hack - make Arragnement overidable
+        // TOOD: Hack - make Arrangement overridable
         if ((Parent?.Arrangement & ViewArrangement.Movable) != 0)
         {
             HighlightStyle |= HighlightStyle.Hover;
@@ -149,8 +149,9 @@ public class Border : Adornment
         }
     }
 
-    private Rectangle GetBorderRectangle (Rectangle screenRect)
+    internal Rectangle GetBorderRectangle ()
     {
+        Rectangle screenRect = ViewportToScreen (Viewport);
         return new (
                     screenRect.X + Math.Max (0, Thickness.Left - 1),
                     screenRect.Y + Math.Max (0, Thickness.Top - 1),
@@ -279,10 +280,11 @@ public class Border : Adornment
             return true;
         }
 
-        if (!Parent.CanFocus)
-        {
-            return false;
-        }
+        // BUGBUG: Shouldn't non-focusable views be draggable??
+        //if (!Parent.CanFocus)
+        //{
+        //    return false;
+        //}
 
         if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable))
         {
@@ -293,7 +295,7 @@ public class Border : Adornment
         if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
         {
             Parent.SetFocus ();
-            Application.BringOverlappedTopToFront ();
+            ApplicationOverlapped.BringOverlappedTopToFront ();
 
             // Only start grabbing if the user clicks in the Thickness area
             // Adornment.Contains takes Parent SuperView=relative coords.
@@ -407,7 +409,7 @@ public class Border : Adornment
         // ...thickness extends outward (border/title is always as far in as possible)
         // PERF: How about a call to Rectangle.Offset?
 
-        Rectangle borderBounds = GetBorderRectangle (screenBounds);
+        Rectangle borderBounds = GetBorderRectangle ();
         int topTitleLineY = borderBounds.Y;
         int titleY = borderBounds.Y;
         var titleBarsLength = 0; // the little vertical thingies
@@ -420,7 +422,7 @@ public class Border : Adornment
                                                )
                                      );
 
-        Parent.TitleTextFormatter.Size = new (maxTitleWidth, 1);
+        Parent.TitleTextFormatter.ConstrainToSize = new (maxTitleWidth, 1);
 
         int sideLineLength = borderBounds.Height;
         bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 };

+ 22 - 7
Terminal.Gui/View/Adornment/Margin.cs

@@ -44,7 +44,7 @@ public class Margin : Adornment
         ShadowStyle = base.ShadowStyle;
 
         Add (
-             _rightShadow = new()
+             _rightShadow = new ()
              {
                  X = Pos.AnchorEnd (1),
                  Y = 0,
@@ -53,7 +53,7 @@ public class Margin : Adornment
                  ShadowStyle = ShadowStyle,
                  Orientation = Orientation.Vertical
              },
-             _bottomShadow = new()
+             _bottomShadow = new ()
              {
                  X = 0,
                  Y = Pos.AnchorEnd (1),
@@ -220,12 +220,27 @@ public class Margin : Adornment
     private void Margin_LayoutStarted (object? sender, LayoutEventArgs e)
     {
         // Adjust the shadow such that it is drawn aligned with the Border
-        if (ShadowStyle != ShadowStyle.None && _rightShadow is { } && _bottomShadow is { })
+        if (_rightShadow is { } && _bottomShadow is { })
         {
-            _rightShadow.Y = Parent.Border.Thickness.Top > 0
-                                 ? Parent.Border.Thickness.Top - (Parent.Border.Thickness.Top > 2 && Parent.Border.Settings.FastHasFlags (BorderSettings.Title) ? 1 : 0)
-                                 : 1;
-            _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? Parent.Border.Thickness.Left : 1;
+            switch (ShadowStyle)
+            {
+                case ShadowStyle.Transparent:
+                    // BUGBUG: This doesn't work right for all Border.Top sizes - Need an API on Border that gives top-right location of line corner.
+                    _rightShadow.Y = Parent!.Border.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
+                    break;
+
+                case ShadowStyle.Opaque:
+                    // BUGBUG: This doesn't work right for all Border.Top sizes - Need an API on Border that gives top-right location of line corner.
+                    _rightShadow.Y = Parent!.Border.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
+                    _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).X + 1 : 0;
+                    break;
+
+                case ShadowStyle.None:
+                default:
+                    _rightShadow.Y = 0;
+                    _bottomShadow.X = 0;
+                    break;
+            }
         }
     }
 }

+ 14 - 3
Terminal.Gui/View/Adornment/ShadowView.cs

@@ -1,4 +1,7 @@
 #nullable enable
+using Microsoft.VisualBasic;
+using System.Diagnostics;
+
 namespace Terminal.Gui;
 
 /// <summary>
@@ -15,7 +18,7 @@ internal class ShadowView : View
         {
             var attr = Attribute.Default;
 
-            if (adornment.Parent.SuperView is { })
+            if (adornment.Parent?.SuperView is { })
             {
                 attr = adornment.Parent.SuperView.GetNormalColor ();
             }
@@ -109,7 +112,11 @@ internal class ShadowView : View
         for (int i = screen.X; i < screen.X + screen.Width - 1; i++)
         {
             Driver.Move (i, screen.Y);
-            Driver.AddRune (Driver.Contents [screen.Y, i].Rune);
+
+            if (i < Driver.Contents!.GetLength (1) && screen.Y < Driver.Contents.GetLength (0))
+            {
+                Driver.AddRune (Driver.Contents [screen.Y, i].Rune);
+            }
         }
     }
 
@@ -133,7 +140,11 @@ internal class ShadowView : View
         for (int i = screen.Y; i < screen.Y + viewport.Height; i++)
         {
             Driver.Move (screen.X, i);
-            Driver.AddRune (Driver.Contents [i, screen.X].Rune);
+
+            if (Driver.Contents is { } && screen.X < Driver.Contents.GetLength (1) && i < Driver.Contents.GetLength (0))
+            {
+                Driver.AddRune (Driver.Contents [i, screen.X].Rune);
+            }
         }
     }
 }

+ 29 - 0
Terminal.Gui/View/DrawEventArgs.cs

@@ -0,0 +1,29 @@
+namespace Terminal.Gui;
+
+/// <summary>Event args for draw events</summary>
+public class DrawEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="DrawEventArgs"/> class.</summary>
+    /// <param name="newViewport">
+    ///     The Content-relative rectangle describing the new visible viewport into the
+    ///     <see cref="View"/>.
+    /// </param>
+    /// <param name="oldViewport">
+    ///     The Content-relative rectangle describing the old visible viewport into the
+    ///     <see cref="View"/>.
+    /// </param>
+    public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport)
+    {
+        NewViewport = newViewport;
+        OldViewport = oldViewport;
+    }
+
+    /// <summary>If set to true, the draw operation will be canceled, if applicable.</summary>
+    public bool Cancel { get; set; }
+
+    /// <summary>Gets the Content-relative rectangle describing the old visible viewport into the <see cref="View"/>.</summary>
+    public Rectangle OldViewport { get; }
+
+    /// <summary>Gets the Content-relative rectangle describing the currently visible viewport into the <see cref="View"/>.</summary>
+    public Rectangle NewViewport { get; }
+}

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

@@ -11,7 +11,7 @@ public class EventArgs<T> : EventArgs where T : notnull
     /// <summary>Initializes a new instance of the <see cref="EventArgs{T}"/> class.</summary>
     /// <param name="currentValue">The current value of the property.</param>
     /// <typeparam name="T">The type of the value.</typeparam>
-    public EventArgs (ref readonly T currentValue) { CurrentValue = currentValue; }
+    public EventArgs (in T currentValue) { CurrentValue = currentValue; }
 
     /// <summary>The current value of the property.</summary>
     public T CurrentValue { get; }

+ 31 - 10
Terminal.Gui/View/Layout/Dim.cs

@@ -139,7 +139,7 @@ public abstract class Dim
     /// <summary>Creates a <see cref="Dim"/> object that tracks the Height of the specified <see cref="View"/>.</summary>
     /// <returns>The height <see cref="Dim"/> of the other <see cref="View"/>.</returns>
     /// <param name="view">The view that will be tracked.</param>
-    public static Dim Height (View view) { return new DimView (view, Dimension.Height); }
+    public static Dim Height (View? view) { return new DimView (view, Dimension.Height); }
 
     /// <summary>Creates a percentage <see cref="Dim"/> object that is a percentage of the width or height of the SuperView.</summary>
     /// <returns>The percent <see cref="Dim"/> object.</returns>
@@ -160,10 +160,7 @@ public abstract class Dim
     /// </example>
     public static Dim? Percent (int percent, DimPercentMode mode = DimPercentMode.ContentSize)
     {
-        if (percent is < 0 /*or > 100*/)
-        {
-            throw new ArgumentException ("Percent value must be positive.");
-        }
+        ArgumentOutOfRangeException.ThrowIfNegative (percent, nameof (percent));
 
         return new DimPercent (percent, mode);
     }
@@ -171,10 +168,34 @@ public abstract class Dim
     /// <summary>Creates a <see cref="Dim"/> object that tracks the Width of the specified <see cref="View"/>.</summary>
     /// <returns>The width <see cref="Dim"/> of the other <see cref="View"/>.</returns>
     /// <param name="view">The view that will be tracked.</param>
-    public static Dim Width (View view) { return new DimView (view, Dimension.Width); }
+    public static Dim Width (View? view) { return new DimView (view, Dimension.Width); }
 
     #endregion static Dim creation methods
 
+    /// <summary>
+    ///     Indicates whether the specified type is in the hierarchy of this Dim object.
+    /// </summary>
+    /// <param name="type"></param>
+    /// <param name="dim"></param>
+    /// <returns></returns>
+    public bool Has (Type type, out Dim dim)
+    {
+        dim = this;
+        if (type == GetType ())
+        {
+            return true;
+        }
+
+        // If we are a PosCombine, we have to check the left and right
+        // to see if they are of the type we are looking for.
+        if (this is DimCombine { } combine && (combine.Left.Has (type, out dim) || combine.Right.Has (type, out dim)))
+        {
+            return true;
+        }
+
+        return false;
+    }
+
     #region virtual methods
 
     /// <summary>
@@ -224,7 +245,7 @@ public abstract class Dim
     /// <param name="left">The first <see cref="Dim"/> to add.</param>
     /// <param name="right">The second <see cref="Dim"/> to add.</param>
     /// <returns>The <see cref="Dim"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
-    public static Dim operator + (Dim? left, Dim? right)
+    public static Dim operator + (Dim left, Dim right)
     {
         if (left is DimAbsolute && right is DimAbsolute)
         {
@@ -232,7 +253,7 @@ public abstract class Dim
         }
 
         var newDim = new DimCombine (AddOrSubtract.Add, left, right);
-        (left as DimView)?.Target.SetNeedsLayout ();
+        (left as DimView)?.Target?.SetNeedsLayout ();
 
         return newDim;
     }
@@ -249,7 +270,7 @@ public abstract class Dim
     /// <param name="left">The <see cref="Dim"/> to subtract from (the minuend).</param>
     /// <param name="right">The <see cref="Dim"/> to subtract (the subtrahend).</param>
     /// <returns>The <see cref="Dim"/> that is the <c>left</c> minus <c>right</c>.</returns>
-    public static Dim operator - (Dim? left, Dim? right)
+    public static Dim operator - (Dim left, Dim right)
     {
         if (left is DimAbsolute && right is DimAbsolute)
         {
@@ -257,7 +278,7 @@ public abstract class Dim
         }
 
         var newDim = new DimCombine (AddOrSubtract.Subtract, left, right);
-        (left as DimView)?.Target.SetNeedsLayout ();
+        (left as DimView)?.Target?.SetNeedsLayout ();
 
         return newDim;
     }

+ 332 - 195
Terminal.Gui/View/Layout/DimAuto.cs

@@ -1,5 +1,5 @@
 #nullable enable
-using System.Drawing;
+using System.Diagnostics;
 
 namespace Terminal.Gui;
 
@@ -15,13 +15,32 @@ namespace Terminal.Gui;
 ///         methods on the <see cref="Dim"/> class to create <see cref="Dim"/> objects instead.
 ///     </para>
 /// </remarks>
-public class DimAuto () : Dim
+public class DimAuto : Dim
 {
     private readonly Dim? _maximumContentDim;
 
+    private readonly Dim? _minimumContentDim;
+
+    private readonly DimAutoStyle _style;
+
+    /// <inheritdoc/>
+    public override bool Equals (object? other)
+    {
+        if (other is not DimAuto auto)
+        {
+            return false;
+        }
+
+        return auto.MinimumContentDim == MinimumContentDim && auto.MaximumContentDim == MaximumContentDim && auto.Style == Style;
+    }
+
+    /// <inheritdoc/>
+    public override int GetHashCode () { return HashCode.Combine (MinimumContentDim, MaximumContentDim, Style); }
+
     /// <summary>
     ///     Gets the maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED.
     /// </summary>
+
     // ReSharper disable once ConvertToAutoProperty
     public required Dim? MaximumContentDim
     {
@@ -29,11 +48,10 @@ public class DimAuto () : Dim
         init => _maximumContentDim = value;
     }
 
-    private readonly Dim? _minimumContentDim;
-
     /// <summary>
     ///     Gets the minimum dimension the View's ContentSize will be constrained to.
     /// </summary>
+
     // ReSharper disable once ConvertToAutoProperty
     public required Dim? MinimumContentDim
     {
@@ -41,11 +59,10 @@ public class DimAuto () : Dim
         init => _minimumContentDim = value;
     }
 
-    private readonly DimAutoStyle _style;
-
     /// <summary>
     ///     Gets the style of the DimAuto.
     /// </summary>
+
     // ReSharper disable once ConvertToAutoProperty
     public required DimAutoStyle Style
     {
@@ -59,242 +76,386 @@ public class DimAuto () : Dim
     internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
     {
         var textSize = 0;
-        var subviewsSize = 0;
+        var maxCalculatedSize = 0;
 
         int autoMin = MinimumContentDim?.GetAnchor (superviewContentSize) ?? 0;
-        int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? int.MaxValue;
+        int screenX4 = dimension == Dimension.Width ? Application.Screen.Width * 4 : Application.Screen.Height * 4;
+        int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? screenX4;
+
+        Debug.Assert (autoMin <= autoMax, "MinimumContentDim must be less than or equal to MaximumContentDim.");
 
         if (Style.FastHasFlags (DimAutoStyle.Text))
         {
-            textSize = int.Max (autoMin, dimension == Dimension.Width ? us.TextFormatter.Size.Width : us.TextFormatter.Size.Height);
+            if (dimension == Dimension.Width)
+            {
+                if (us.TextFormatter.ConstrainToWidth is null)
+                {
+                    // Set BOTH width and height (by setting Size). We do this because we will be called again, next
+                    // for Dimension.Height. We need to know the width to calculate the height.
+                    us.TextFormatter.ConstrainToSize = us.TextFormatter.FormatAndGetSize (new (int.Min (autoMax, screenX4), screenX4));
+                }
+
+                textSize = us.TextFormatter.ConstrainToWidth!.Value;
+            }
+            else
+            {
+                if (us.TextFormatter.ConstrainToHeight is null)
+                {
+                    // Set just the height. It is assumed that the width has already been set.
+                    // TODO: There may be cases where the width is not set. We may need to set it here.
+                    textSize = us.TextFormatter.FormatAndGetSize (new (us.TextFormatter.ConstrainToWidth ?? screenX4, int.Min (autoMax, screenX4))).Height;
+                    us.TextFormatter.ConstrainToHeight = textSize;
+                }
+                else
+                {
+                    textSize = us.TextFormatter.ConstrainToHeight.Value;
+                }
+            }
         }
 
+        List<View> viewsNeedingLayout = new ();
+
         if (Style.FastHasFlags (DimAutoStyle.Content))
         {
             if (!us.ContentSizeTracksViewport)
             {
-                // ContentSize was explicitly set. Ignore subviews.
-                subviewsSize = dimension == Dimension.Width ? us.GetContentSize ().Width : us.GetContentSize ().Height;
+                // ContentSize was explicitly set. Use `us.ContentSize` to determine size.
+                maxCalculatedSize = dimension == Dimension.Width ? us.GetContentSize ().Width : us.GetContentSize ().Height;
             }
             else
             {
-                // ContentSize was NOT explicitly set. Use subviews to determine size.
-
-                // TODO: This whole body of code is a WIP (for https://github.com/gui-cs/Terminal.Gui/pull/3451).
-                subviewsSize = 0;
-
-                List<View> includedSubviews = us.Subviews.ToList ();//.Where (v => !v.ExcludeFromLayout).ToList ();
-                List<View> subviews;
-
-                #region Not Anchored and Are Not Dependent
-                // Start with subviews that are not anchored to the end, aligned, or dependent on content size
-                // [x] PosAnchorEnd
-                // [x] PosAlign
-                // [ ] PosCenter
-                // [ ] PosPercent
-                // [ ] PosView
-                // [ ] PosFunc
-                // [x] DimFill
-                // [ ] DimPercent
-                // [ ] DimFunc
-                // [ ] DimView
+                maxCalculatedSize = textSize;
+
+                // TOOD: All the below is a naive implementation. It may be possible to optimize this.
+
+                List<View> includedSubviews = us.Subviews.ToList ();
+
+                // If [x] it can cause `us.ContentSize` to change.
+                // If [ ] it doesn't need special processing for us to determine `us.ContentSize`.
+
+                // -------------------- Pos types that are dependent on `us.Subviews`
+                // [ ] PosAlign     - Position is dependent on other views with `GroupId` AND `us.ContentSize`
+                // [x] PosView      - Position is dependent on `subview.Target` - it can cause a change in `us.ContentSize`
+                // [x] PosCombine   - Position is dependent if `Pos.Has [one of the above]` - it can cause a change in `us.ContentSize`
+
+                // -------------------- Pos types that are dependent on `us.ContentSize`
+                // [ ] PosAlign     - Position is dependent on other views with `GroupId` AND `us.ContentSize`
+                // [x] PosAnchorEnd - Position is dependent on `us.ContentSize` AND `subview.Frame` - it can cause a change in `us.ContentSize`
+                // [ ] PosCenter    - Position is dependent `us.ContentSize` AND `subview.Frame` - 
+                // [ ] PosPercent   - Position is dependent `us.ContentSize` - Will always be 0 if there is no other content that makes the superview have a size.
+                // [x] PosCombine   - Position is dependent if `Pos.Has [one of the above]` - it can cause a change in `us.ContentSize`
+
+                // -------------------- Pos types that are not dependent on either `us.Subviews` or `us.ContentSize`
+                // [ ] PosAbsolute  - Position is fixed.
+                // [ ] PosFunc      - Position is internally calculated.
+
+                // -------------------- Dim types that are dependent on `us.Subviews`
+                // [x] DimView      - Dimension is dependent on `subview.Target`
+                // [x] DimCombine   - Dimension is dependent if `Dim.Has [one of the above]` - it can cause a change in `us.ContentSize`
+
+                // -------------------- Dim types that are dependent on `us.ContentSize`
+                // [ ] DimFill      - Dimension is dependent on `us.ContentSize` - Will always be 0 if there is no other content that makes the superview have a size.
+                // [ ] DimPercent   - Dimension is dependent on `us.ContentSize` - Will always be 0 if there is no other content that makes the superview have a size.
+                // [ ] DimCombine   - Dimension is dependent if `Dim.Has [one of the above]`
+
+                // -------------------- Dim types that are not dependent on either `us.Subviews` or `us.ContentSize`
+                // [ ] DimAuto      - Dimension is internally calculated
+                // [ ] DimAbsolute  - Dimension is fixed
+                // [ ] DimFunc      - Dimension is internally calculated
+
+                // ======================================================
+                // Do the easy stuff first - subviews whose position and size are not dependent on other views or content size
+                // ======================================================
+                // [ ] PosAbsolute  - Position is fixed.
+                // [ ] PosFunc      - Position is internally calculated
+                // [ ] DimAuto      - Dimension is internally calculated
+                // [ ] DimAbsolute  - Dimension is fixed
+                // [ ] DimFunc      - Dimension is internally calculated
+                List<View> notDependentSubViews;
+
                 if (dimension == Dimension.Width)
                 {
-                    subviews = includedSubviews.Where (v => v.X is not PosAnchorEnd
-                                                           && v.X is not PosAlign
-                                                            // && v.X is not PosCenter
-                                                            && v.Width is not DimAuto
-                                                           && v.Width is not DimFill).ToList ();
+                    notDependentSubViews = includedSubviews.Where (
+                                                                   v => v.Width is { }
+                                                                        && (v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) // BUGBUG: We should use v.X.Has and v.Width.Has?
+                                                                        && !v.X.Has (typeof (PosAnchorEnd), out _)
+                                                                        && !v.X.Has (typeof (PosAlign), out _)
+                                                                        && !v.X.Has (typeof (PosCenter), out _)
+                                                                        && !v.Width.Has (typeof (DimFill), out _)
+                                                                        && !v.Width.Has (typeof (DimPercent), out _)
+                                                                  )
+                                                           .ToList ();
                 }
                 else
                 {
-                    subviews = includedSubviews.Where (v => v.Y is not PosAnchorEnd
-                                                           && v.Y is not PosAlign
-                                                            // && v.Y is not PosCenter
-                                                            && v.Height is not DimAuto
-                                                           && v.Height is not DimFill).ToList ();
+                    notDependentSubViews = includedSubviews.Where (
+                                                                   v => v.Height is { }
+                                                                        && (v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) // BUGBUG: We should use v.Y.Has and v.Height.Has?
+                                                                        && !v.Y.Has (typeof (PosAnchorEnd), out _)
+                                                                        && !v.Y.Has (typeof (PosAlign), out _)
+                                                                        && !v.Y.Has (typeof (PosCenter), out _)
+                                                                        && !v.Height.Has (typeof (DimFill), out _)
+                                                                        && !v.Height.Has (typeof (DimPercent), out _)
+                                                                  )
+                                                           .ToList ();
                 }
 
-                for (var i = 0; i < subviews.Count; i++)
+                for (var i = 0; i < notDependentSubViews.Count; i++)
                 {
-                    View v = subviews [i];
+                    View v = notDependentSubViews [i];
 
-                    int size = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
+                    var size = 0;
 
-                    if (size > subviewsSize)
+                    if (dimension == Dimension.Width)
                     {
-                        // BUGBUG: Should we break here? Or choose min/max?
-                        subviewsSize = size;
+                        int width = v.Width!.Calculate (0, superviewContentSize, v, dimension);
+                        size = v.X.GetAnchor (0) + width;
+                    }
+                    else
+                    {
+                        int height = v.Height!.Calculate (0, superviewContentSize, v, dimension);
+                        size = v.Y!.GetAnchor (0) + height;
+                    }
+
+                    if (size > maxCalculatedSize)
+                    {
+                        maxCalculatedSize = size;
                     }
                 }
-                #endregion Not Anchored and Are Not Dependent
 
-                #region Anchored
-                // Now, handle subviews that are anchored to the end
-                // [x] PosAnchorEnd
+                // ************** We now have some idea of `us.ContentSize` ***************
+
+                #region Centered
+
+                // [ ] PosCenter    - Position is dependent `us.ContentSize` AND `subview.Frame`
+                List<View> centeredSubViews;
+
                 if (dimension == Dimension.Width)
                 {
-                    subviews = includedSubviews.Where (v => v.X is PosAnchorEnd).ToList ();
+                    centeredSubViews = us.Subviews.Where (v => v.X.Has (typeof (PosCenter), out _)).ToList ();
                 }
                 else
                 {
-                    subviews = includedSubviews.Where (v => v.Y is PosAnchorEnd).ToList ();
+                    centeredSubViews = us.Subviews.Where (v => v.Y.Has (typeof (PosCenter), out _)).ToList ();
                 }
 
-                int maxAnchorEnd = 0;
-                for (var i = 0; i < subviews.Count; i++)
+                viewsNeedingLayout.AddRange (centeredSubViews);
+
+                var maxCentered = 0;
+
+                for (var i = 0; i < centeredSubViews.Count; i++)
                 {
-                    View v = subviews [i];
-                    maxAnchorEnd = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
+                    View v = centeredSubViews [i];
+
+                    if (dimension == Dimension.Width)
+                    {
+                        int width = v.Width!.Calculate (0, screenX4, v, dimension);
+                        maxCentered = v.X.GetAnchor (0) + width;
+                    }
+                    else
+                    {
+                        int height = v.Height!.Calculate (0, screenX4, v, dimension);
+                        maxCentered = v.Y.GetAnchor (0) + height;
+                    }
                 }
 
-                subviewsSize += maxAnchorEnd;
-                #endregion Anchored
+                maxCalculatedSize = int.Max (maxCalculatedSize, maxCentered);
+
+                #endregion Centered
+
+                #region Percent
+
+                // [ ] DimPercent   - Dimension is dependent on `us.ContentSize`
+                // No need to do anything.
+
+                #endregion Percent
+
+                #region Aligned
+
+                // [ ] PosAlign     - Position is dependent on other views with `GroupId` AND `us.ContentSize`
+                var maxAlign = 0;
+
+                // Use Linq to get a list of distinct GroupIds from the subviews
+                List<int> groupIds = includedSubviews.Select (
+                                                              v =>
+                                                              {
+                                                                  if (dimension == Dimension.Width)
+                                                                  {
+                                                                      if (v.X.Has (typeof (PosAlign), out Pos posAlign))
+                                                                      {
+                                                                          return ((PosAlign)posAlign).GroupId;
+                                                                      }
+                                                                  }
+                                                                  else
+                                                                  {
+                                                                      if (v.Y.Has (typeof (PosAlign), out Pos posAlign))
+                                                                      {
+                                                                          return ((PosAlign)posAlign).GroupId;
+                                                                      }
+                                                                  }
+
+                                                                  return -1;
+                                                              })
+                                                     .Distinct ()
+                                                     .ToList ();
+
+                foreach (int groupId in groupIds.Where (g => g != -1))
+                {
+                    // PERF: If this proves a perf issue, consider caching a ref to this list in each item
+                    List<PosAlign?> posAlignsInGroup = includedSubviews.Where (
+                                                                               v =>
+                                                                               {
+                                                                                   return dimension switch
+                                                                                   {
+                                                                                       Dimension.Width when v.X is PosAlign alignX => alignX.GroupId
+                                                                                           == groupId,
+                                                                                       Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId
+                                                                                           == groupId,
+                                                                                       _ => false
+                                                                                   };
+                                                                               })
+                                                                       .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign)
+                                                                       .ToList ();
+
+                    if (posAlignsInGroup.Count == 0)
+                    {
+                        continue;
+                    }
+
+                    maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension);
+                }
+
+                maxCalculatedSize = int.Max (maxCalculatedSize, maxAlign);
+
+                #endregion Aligned
+
+                #region Anchored
 
-                //#region Aligned
-
-                //// Now, handle subviews that are anchored to the end
-                //// [x] PosAnchorEnd
-                //int maxAlign = 0;
-                //if (dimension == Dimension.Width)
-                //{
-                //    // Use Linq to get a list of distinct GroupIds from the subviews
-                //    List<int> groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList ();
-
-                //    foreach (var groupId in groupIds)
-                //    {
-                //        List<int> dimensionsList = new ();
-
-                //        // PERF: If this proves a perf issue, consider caching a ref to this list in each item
-                //        List<PosAlign?> posAlignsInGroup = includedSubviews.Where (
-                //            v =>
-                //            {
-                //                return dimension switch
-                //                {
-                //                    Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId,
-                //                    Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId,
-                //                    _ => false
-                //                };
-                //            })
-                //            .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign)
-                //            .ToList ();
-
-                //        if (posAlignsInGroup.Count == 0)
-                //        {
-                //            continue;
-                //        }
-
-                //        maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension);
-                //    }
-                //}
-                //else
-                //{
-                //    subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList ();
-                //}
-
-                //subviewsSize = int.Max (subviewsSize, maxAlign);
-                //#endregion Aligned
-
-
-                #region Auto
+                // [x] PosAnchorEnd - Position is dependent on `us.ContentSize` AND `subview.Frame` 
+                List<View> anchoredSubViews;
 
                 if (dimension == Dimension.Width)
                 {
-                    subviews = includedSubviews.Where (v => v.Width is DimAuto).ToList ();
+                    anchoredSubViews = includedSubviews.Where (v => v.X.Has (typeof (PosAnchorEnd), out _)).ToList ();
                 }
                 else
                 {
-                    subviews = includedSubviews.Where (v => v.Height is DimAuto).ToList ();
+                    anchoredSubViews = includedSubviews.Where (v => v.Y.Has (typeof (PosAnchorEnd), out _)).ToList ();
                 }
 
-                int maxAuto = 0;
-                for (var i = 0; i < subviews.Count; i++)
+                viewsNeedingLayout.AddRange (anchoredSubViews);
+
+                var maxAnchorEnd = 0;
+
+                for (var i = 0; i < anchoredSubViews.Count; i++)
                 {
-                    View v = subviews [i];
-
-                    //if (dimension == Dimension.Width)
-                    //{
-                    //    v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0));
-                    //}
-                    //else
-                    //{
-                    //    v.SetRelativeLayout (new Size (0, autoMax - subviewsSize));
-                    //}
-                    maxAuto = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
-
-                    if (maxAuto > subviewsSize)
+                    View v = anchoredSubViews [i];
+
+                    // Need to set the relative layout for PosAnchorEnd subviews to calculate the size
+                    // TODO: Figure out a way to not have Calculate change the state of subviews (calling SRL).
+                    if (dimension == Dimension.Width)
                     {
-                        // BUGBUG: Should we break here? Or choose min/max?
-                        subviewsSize = maxAuto;
+                        v.SetRelativeLayout (new (maxCalculatedSize, screenX4));
                     }
+                    else
+                    {
+                        v.SetRelativeLayout (new (screenX4, maxCalculatedSize));
+                    }
+
+                    maxAnchorEnd = dimension == Dimension.Width
+                                       ? v.X.GetAnchor (maxCalculatedSize + v.Frame.Width)
+                                       : v.Y.GetAnchor (maxCalculatedSize + v.Frame.Height);
                 }
 
-//                subviewsSize += maxAuto;
-
-                #endregion Auto
-
-                //#region Center
-                //// Now, handle subviews that are Centered
-                //if (dimension == Dimension.Width)
-                //{
-                //    subviews = us.Subviews.Where (v => v.X is PosCenter).ToList ();
-                //}
-                //else
-                //{
-                //    subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList ();
-                //}
-
-                //int maxCenter = 0;
-                //for (var i = 0; i < subviews.Count; i++)
-                //{
-                //    View v = subviews [i];
-                //    maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
-                //}
-
-                //subviewsSize += maxCenter;
-                //#endregion Center
-
-                #region Are Dependent
-                // Now, go back to those that are dependent on content size
-                // [x] DimFill
-                // [ ] DimPercent
+                maxCalculatedSize = Math.Max (maxCalculatedSize, maxAnchorEnd);
+
+                #endregion Anchored
+
+                #region PosView
+
+                // [x] PosView      - Position is dependent on `subview.Target` - it can cause a change in `us.ContentSize`
+                List<View> posViewSubViews;
+
                 if (dimension == Dimension.Width)
                 {
-                    subviews = includedSubviews.Where (v => v.Width is DimFill).ToList ();
+                    posViewSubViews = includedSubviews.Where (v => v.X.Has (typeof (PosView), out _)).ToList ();
                 }
                 else
                 {
-                    subviews = includedSubviews.Where (v => v.Height is DimFill).ToList ();
+                    posViewSubViews = includedSubviews.Where (v => v.Y.Has (typeof (PosView), out _)).ToList ();
                 }
 
-                int maxFill = 0;
-                for (var i = 0; i < subviews.Count; i++)
+                for (var i = 0; i < posViewSubViews.Count; i++)
                 {
-                    View v = subviews [i];
+                    View v = posViewSubViews [i];
 
-                    if (autoMax == int.MaxValue)
+                    // BUGBUG: The order may not be correct. May need to call TopologicalSort?
+                    // TODO: Figure out a way to not have Calculate change the state of subviews (calling SRL).
+                    if (dimension == Dimension.Width)
                     {
-                        autoMax = superviewContentSize;
+                        v.SetRelativeLayout (new (maxCalculatedSize, 0));
                     }
+                    else
+                    {
+                        v.SetRelativeLayout (new (0, maxCalculatedSize));
+                    }
+
+                    int maxPosView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
+
+                    if (maxPosView > maxCalculatedSize)
+                    {
+                        maxCalculatedSize = maxPosView;
+                    }
+                }
+
+                #endregion PosView
+
+                // [x] PosCombine   - Position is dependent if `Pos.Has ([one of the above]` - it can cause a change in `us.ContentSize`
+
+                #region DimView
+
+                // [x] DimView      - Dimension is dependent on `subview.Target` - it can cause a change in `us.ContentSize`
+                List<View> dimViewSubViews;
+
+                if (dimension == Dimension.Width)
+                {
+                    dimViewSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has (typeof (DimView), out _)).ToList ();
+                }
+                else
+                {
+                    dimViewSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has (typeof (DimView), out _)).ToList ();
+                }
+
+                for (var i = 0; i < dimViewSubViews.Count; i++)
+                {
+                    View v = dimViewSubViews [i];
+
+                    // BUGBUG: The order may not be correct. May need to call TopologicalSort?
+                    // TODO: Figure out a way to not have Calculate change the state of subviews (calling SRL).
                     if (dimension == Dimension.Width)
                     {
-                        v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0));
+                        v.SetRelativeLayout (new (maxCalculatedSize, 0));
                     }
                     else
                     {
-                        v.SetRelativeLayout (new Size (0, autoMax - subviewsSize));
+                        v.SetRelativeLayout (new (0, maxCalculatedSize));
+                    }
+
+                    int maxDimView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
+
+                    if (maxDimView > maxCalculatedSize)
+                    {
+                        maxCalculatedSize = maxDimView;
                     }
-                    maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
                 }
 
-                subviewsSize += maxFill;
-                #endregion Are Dependent
+                #endregion DimView
             }
         }
 
         // All sizes here are content-relative; ignoring adornments.
         // We take the largest of text and content.
-        int max = int.Max (textSize, subviewsSize);
+        int max = int.Max (textSize, maxCalculatedSize);
 
         // And, if min: is set, it wins if larger
         max = int.Max (max, autoMin);
@@ -302,9 +463,9 @@ public class DimAuto () : Dim
         // And, if max: is set, it wins if smaller
         max = int.Min (max, autoMax);
 
-        // Factor in adornments
         Thickness thickness = us.GetAdornmentsThickness ();
-        max += dimension switch
+
+        int adornmentThickness = dimension switch
         {
             Dimension.Width => thickness.Horizontal,
             Dimension.Height => thickness.Vertical,
@@ -312,32 +473,8 @@ public class DimAuto () : Dim
             _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null)
         };
 
-        return max;
-    }
-
-    internal override bool ReferencesOtherViews ()
-    {
-        // BUGBUG: This is not correct. _contentSize may be null.
-        return false; //_style.HasFlag (DimAutoStyle.Content);
-    }
+        max += adornmentThickness;
 
-    /// <inheritdoc/>
-    public override bool Equals (object? other)
-    {
-        if (other is not DimAuto auto)
-        {
-            return false;
-        }
-
-        return auto.MinimumContentDim == MinimumContentDim &&
-               auto.MaximumContentDim == MaximumContentDim &&
-               auto.Style == Style;
-    }
-
-    /// <inheritdoc/>
-    public override int GetHashCode ()
-    {
-        return HashCode.Combine (MinimumContentDim, MaximumContentDim, Style);
+        return max;
     }
-
-}
+}

+ 4 - 0
Terminal.Gui/View/Layout/DimAutoStyle.cs

@@ -33,6 +33,10 @@ public enum DimAutoStyle
     ///     <para>
     ///         The corresponding dimensions of <see cref="View.GetContentSize ()"/> and/or <see cref="View.Subviews"/> will be ignored.
     ///     </para>
+    ///     <para>
+    ///         If <see cref="DimAuto.MaximumContentDim"/> is set, the dimension will be the maximum of the formatted text and the
+    ///         demension provided by <see cref="DimAuto.MaximumContentDim"/>. Otherwise, the dimension will be that of the formatted text.
+    ///     </para>
     /// </summary>
     Text = 2,
 

+ 3 - 22
Terminal.Gui/View/Layout/DimCombine.cs

@@ -13,7 +13,7 @@ namespace Terminal.Gui;
 /// </remarks>
 /// <param name="left">The left dimension.</param>
 /// <param name="right">The right dimension.</param>
-public class DimCombine (AddOrSubtract add, Dim? left, Dim? right) : Dim
+public class DimCombine (AddOrSubtract add, Dim left, Dim right) : Dim
 {
     /// <summary>
     ///     Gets whether the two dimensions are added or subtracted.
@@ -23,12 +23,12 @@ public class DimCombine (AddOrSubtract add, Dim? left, Dim? right) : Dim
     /// <summary>
     ///     Gets the left dimension.
     /// </summary>
-    public Dim? Left { get; } = left;
+    public Dim Left { get; } = left;
 
     /// <summary>
     ///     Gets the right dimension.
     /// </summary>
-    public Dim? Right { get; } = right;
+    public Dim Right { get; } = right;
 
     /// <inheritdoc/>
     public override string ToString () { return $"Combine({Left}{(Add == AddOrSubtract.Add ? '+' : '-')}{Right})"; }
@@ -61,23 +61,4 @@ public class DimCombine (AddOrSubtract add, Dim? left, Dim? right) : Dim
 
         return newDimension;
     }
-
-    /// <summary>
-    ///     Diagnostics API to determine if this Dim object references other views.
-    /// </summary>
-    /// <returns></returns>
-    internal override bool ReferencesOtherViews ()
-    {
-        if (Left!.ReferencesOtherViews ())
-        {
-            return true;
-        }
-
-        if (Right!.ReferencesOtherViews ())
-        {
-            return true;
-        }
-
-        return false;
-    }
 }

+ 5 - 5
Terminal.Gui/View/Layout/DimView.cs

@@ -15,7 +15,7 @@ public class DimView : Dim
     /// </summary>
     /// <param name="view">The view the dimension is anchored to.</param>
     /// <param name="dimension">Indicates which dimension is tracked.</param>
-    public DimView (View view, Dimension dimension)
+    public DimView (View? view, Dimension dimension)
     {
         Target = view;
         Dimension = dimension;
@@ -30,12 +30,12 @@ public class DimView : Dim
     public override bool Equals (object? other) { return other is DimView abs && abs.Target == Target && abs.Dimension == Dimension; }
 
     /// <inheritdoc/>
-    public override int GetHashCode () { return Target.GetHashCode (); }
+    public override int GetHashCode () { return Target!.GetHashCode (); }
 
     /// <summary>
     ///     Gets the View the dimension is anchored to.
     /// </summary>
-    public View Target { get; init; }
+    public View? Target { get; init; }
 
     /// <inheritdoc/>
     public override string ToString ()
@@ -52,8 +52,8 @@ public class DimView : Dim
     {
         return Dimension switch
                {
-                   Dimension.Height => Target.Frame.Height,
-                   Dimension.Width => Target.Frame.Width,
+                   Dimension.Height => Target!.Frame.Height,
+                   Dimension.Width => Target!.Frame.Width,
                    _ => 0
                };
     }

+ 12 - 0
Terminal.Gui/View/Layout/LayoutEventArgs.cs

@@ -0,0 +1,12 @@
+namespace Terminal.Gui;
+
+/// <summary>Event arguments for the <see cref="View.LayoutComplete"/> event.</summary>
+public class LayoutEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="Terminal.Gui.LayoutEventArgs"/> class.</summary>
+    /// <param name="oldContentSize">The view that the event is about.</param>
+    public LayoutEventArgs (Size oldContentSize) { OldContentSize = oldContentSize; }
+
+    /// <summary>The viewport of the <see cref="View"/> before it was laid out.</summary>
+    public Size OldContentSize { get; set; }
+}

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

@@ -197,10 +197,7 @@ public abstract class Pos
     /// </example>
     public static Pos AnchorEnd (int offset)
     {
-        if (offset < 0)
-        {
-            throw new ArgumentException (@"Must be positive", nameof (offset));
-        }
+        ArgumentOutOfRangeException.ThrowIfNegative (offset, nameof (offset));
 
         return new PosAnchorEnd (offset);
     }
@@ -246,10 +243,7 @@ public abstract class Pos
     /// </example>
     public static Pos Percent (int percent)
     {
-        if (percent is < 0)
-        {
-            throw new ArgumentException ("Percent value must be positive.");
-        }
+        ArgumentOutOfRangeException.ThrowIfNegative (percent, nameof (percent));
 
         return new PosPercent (percent);
     }
@@ -257,22 +251,22 @@ public abstract class Pos
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Top (View view) { return new PosView (view, Side.Top); }
+    public static Pos Top (View? view) { return new PosView (view, Side.Top); }
 
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Y (View view) { return new PosView (view, Side.Top); }
+    public static Pos Y (View? view) { return new PosView (view, Side.Top); }
 
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Left (View view) { return new PosView (view, Side.Left); }
+    public static Pos Left (View? view) { return new PosView (view, Side.Left); }
 
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos X (View view) { return new PosView (view, Side.Left); }
+    public static Pos X (View? view) { return new PosView (view, Side.Left); }
 
     /// <summary>
     ///     Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the specified
@@ -280,7 +274,7 @@ public abstract class Pos
     /// </summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Bottom (View view) { return new PosView (view, Side.Bottom); }
+    public static Pos Bottom (View? view) { return new PosView (view, Side.Bottom); }
 
     /// <summary>
     ///     Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the specified
@@ -288,7 +282,7 @@ public abstract class Pos
     /// </summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Right (View view) { return new PosView (view, Side.Right); }
+    public static Pos Right (View? view) { return new PosView (view, Side.Right); }
 
     #endregion static Pos creation methods
 
@@ -379,7 +373,7 @@ public abstract class Pos
 
         if (left is PosView view)
         {
-            view.Target.SetNeedsLayout ();
+            view.Target?.SetNeedsLayout ();
         }
 
         return newPos;
@@ -408,7 +402,7 @@ public abstract class Pos
 
         if (left is PosView view)
         {
-            view.Target.SetNeedsLayout ();
+            view.Target?.SetNeedsLayout ();
         }
 
         return newPos;

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

@@ -13,8 +13,9 @@ public class PosCenter : Pos
 
     internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension)
     {
+        // Protect against negative dimensions
         int newDimension = Math.Max (dim.Calculate (0, superviewDimension, us, dimension), 0);
 
-        return GetAnchor (superviewDimension - newDimension);
+        return (superviewDimension - newDimension) / 2;
     }
 }

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

@@ -12,12 +12,12 @@ namespace Terminal.Gui;
 /// </remarks>
 /// <param name="view">The View the position is anchored to.</param>
 /// <param name="side">The side of the View the position is anchored to.</param>
-public class PosView (View view, Side side) : Pos
+public class PosView (View? view, Side side) : Pos
 {
     /// <summary>
     ///     Gets the View the position is anchored to.
     /// </summary>
-    public View Target { get; } = view;
+    public View? Target { get; } = view;
 
     /// <summary>
     ///     Gets the side of the View the position is anchored to.
@@ -28,7 +28,7 @@ public class PosView (View view, Side side) : Pos
     public override bool Equals (object? other) { return other is PosView abs && abs.Target == Target && abs.Side == Side; }
 
     /// <inheritdoc/>
-    public override int GetHashCode () { return Target.GetHashCode (); }
+    public override int GetHashCode () { return Target!.GetHashCode (); }
 
     /// <inheritdoc/>
     public override string ToString ()
@@ -47,10 +47,10 @@ public class PosView (View view, Side side) : Pos
     {
         return Side switch
                {
-                   Side.Left => Target.Frame.X,
-                   Side.Top => Target.Frame.Y,
-                   Side.Right => Target.Frame.Right,
-                   Side.Bottom => Target.Frame.Bottom,
+                   Side.Left => Target!.Frame.X,
+                   Side.Top => Target!.Frame.Y,
+                   Side.Right => Target!.Frame.Right,
+                   Side.Bottom => Target!.Frame.Bottom,
                    _ => 0
                };
     }

+ 27 - 0
Terminal.Gui/View/Navigation/FocusEventArgs.cs

@@ -0,0 +1,27 @@
+namespace Terminal.Gui;
+
+/// <summary>Defines the event arguments for <see cref="View.SetFocus()"/></summary>
+public class FocusEventArgs : EventArgs
+{
+    /// <summary>Constructs.</summary>
+    /// <param name="leaving">The view that is losing focus.</param>
+    /// <param name="entering">The view that is gaining focus.</param>
+    public FocusEventArgs (View leaving, View entering) {
+        Leaving = leaving;
+        Entering = entering;
+    }
+
+    /// <summary>
+    ///     Indicates if the current focus event has already been processed and the driver should stop notifying any other
+    ///     event subscriber. It's important to set this value to true specially when updating any View's layout from inside the
+    ///     subscriber method.
+    /// </summary>
+    public bool Handled { get; set; }
+
+    /// <summary>Indicates the view that is losing focus.</summary>
+    public View Leaving { get; set; }
+
+    /// <summary>Indicates the view that is gaining focus.</summary>
+    public View Entering { get; set; }
+
+}

+ 22 - 0
Terminal.Gui/View/Navigation/TabBehavior.cs

@@ -0,0 +1,22 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     Describes how <see cref="View.TabStop"/> behaves. A TabStop is a stop-point for keyboard navigation between Views.
+/// </summary>
+public enum TabBehavior
+{
+    /// <summary>
+    ///     The View will not be a stop-poknt for keyboard-based navigation.
+    /// </summary>
+    NoStop = 0,
+
+    /// <summary>
+    ///     The View will be a stop-point for keybaord-based navigation across Views (e.g. if the user presses `Tab`).
+    /// </summary>
+    TabStop = 1,
+
+    /// <summary>
+    ///     The View will be a stop-point for keyboard-based navigation across groups (e.g. if the user presses <see cref="Application.NextTabGroupKey"/> (`Ctrl-PageDown`).
+    /// </summary>
+    TabGroup = 2,
+}

+ 13 - 0
Terminal.Gui/View/NavigationDirection.cs

@@ -0,0 +1,13 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     Indicates navigation direction.
+/// </summary>
+public enum NavigationDirection
+{
+    /// <summary>Navigate forward.</summary>
+    Forward,
+
+    /// <summary>Navigate backwards.</summary>
+    Backward
+}

+ 42 - 0
Terminal.Gui/View/Orientation/IOrientation.cs

@@ -0,0 +1,42 @@
+
+namespace Terminal.Gui;
+using System;
+
+/// <summary>
+///     Implement this interface to provide orientation support.
+/// </summary>
+/// <remarks>
+///     See <see cref="OrientationHelper"/> for a helper class that implements this interface.
+/// </remarks>
+public interface IOrientation
+{
+    /// <summary>
+    ///     Gets or sets the orientation of the View.
+    /// </summary>
+    Orientation Orientation { get; set; }
+
+    /// <summary>
+    ///     Raised when <see cref="Orientation"/> is changing. Can be cancelled.
+    /// </summary>
+    public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
+
+    /// <summary>
+    ///     Called when <see cref="Orientation"/> is changing.
+    /// </summary>
+    /// <param name="currentOrientation">The current orientation.</param>
+    /// <param name="newOrientation">The new orientation.</param>
+    /// <returns><see langword="true"/> to cancel the change.</returns>
+    public bool OnOrientationChanging (Orientation currentOrientation, Orientation newOrientation) { return false; }
+
+    /// <summary>
+    ///     Raised when <see cref="Orientation"/> has changed.
+    /// </summary>
+    public event EventHandler<EventArgs<Orientation>> OrientationChanged;
+
+    /// <summary>
+    ///     Called when <see cref="Orientation"/> has been changed.
+    /// </summary>
+    /// <param name="newOrientation"></param>
+    /// <returns></returns>
+    public void OnOrientationChanged (Orientation newOrientation) { return; }
+}

+ 0 - 0
Terminal.Gui/Views/GraphView/Orientation.cs → Terminal.Gui/View/Orientation/Orientation.cs


+ 138 - 0
Terminal.Gui/View/Orientation/OrientationHelper.cs

@@ -0,0 +1,138 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     Helper class for implementing <see cref="IOrientation"/>.
+/// </summary>
+/// <remarks>
+///     <para>
+///         Implements the standard pattern for changing/changed events.
+///     </para>
+/// </remarks>
+/// <example>
+///     <code>
+/// private class OrientedView : View, IOrientation
+/// {
+///     private readonly OrientationHelper _orientationHelper;
+/// 
+///     public OrientedView ()
+///     {
+///         _orientationHelper = new (this);
+///         Orientation = Orientation.Vertical;
+///         _orientationHelper.OrientationChanging += (sender, e) =&gt; OrientationChanging?.Invoke (this, e);
+///         _orientationHelper.OrientationChanged += (sender, e) =&gt; OrientationChanged?.Invoke (this, e);
+///     }
+/// 
+///     public Orientation Orientation
+///     {
+///         get =&gt; _orientationHelper.Orientation;
+///         set =&gt; _orientationHelper.Orientation = value;
+///     }
+/// 
+///     public event EventHandler&lt;CancelEventArgs&lt;Orientation&gt;&gt; OrientationChanging;
+///     public event EventHandler&lt;EventArgs&lt;Orientation&gt;&gt; OrientationChanged;
+/// 
+///     public bool OnOrientationChanging (Orientation currentOrientation, Orientation newOrientation)
+///     {
+///        // Custom logic before orientation changes
+///        return false; // Return true to cancel the change
+///     }
+/// 
+///     public void OnOrientationChanged (Orientation newOrientation)
+///     {
+///         // Custom logic after orientation has changed
+///     }
+/// }
+/// </code>
+/// </example>
+public class OrientationHelper
+{
+    private Orientation _orientation;
+    private readonly IOrientation _owner;
+
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="OrientationHelper"/> class.
+    /// </summary>
+    /// <param name="owner">Specifies the object that owns this helper instance and implements <see cref="IOrientation"/>.</param>
+    public OrientationHelper (IOrientation owner) { _owner = owner; }
+
+    /// <summary>
+    ///     Gets or sets the orientation of the View.
+    /// </summary>
+    public Orientation Orientation
+    {
+        get => _orientation;
+        set
+        {
+            if (_orientation == value)
+            {
+                return;
+            }
+
+            // Best practice is to invoke the virtual method first.
+            // This allows derived classes to handle the event and potentially cancel it.
+            if (_owner?.OnOrientationChanging (value, _orientation) ?? false)
+            {
+                return;
+            }
+
+            // If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
+            CancelEventArgs<Orientation> args = new (in _orientation, ref value);
+            OrientationChanging?.Invoke (_owner, args);
+
+            if (args.Cancel)
+            {
+                return;
+            }
+
+            // If the event is not canceled, update the value.
+            Orientation old = _orientation;
+
+            if (_orientation != value)
+            {
+                _orientation = value;
+
+                if (_owner is { })
+                {
+                    _owner.Orientation = value;
+                }
+            }
+
+            // Best practice is to invoke the virtual method first.
+            _owner?.OnOrientationChanged (_orientation);
+
+            // Even though Changed is not cancelable, it is still a good practice to raise the event after.
+            OrientationChanged?.Invoke (_owner, new (in _orientation));
+        }
+    }
+
+    /// <summary>
+    ///     Raised when the orientation is changing. This is cancelable.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Views that implement <see cref="IOrientation"/> should raise <see cref="IOrientation.OrientationChanging"/>
+    ///         after the orientation has changed
+    ///         (<code>_orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);</code>).
+    ///     </para>
+    ///     <para>
+    ///         This event will be raised after the <see cref="IOrientation.OnOrientationChanging"/> method is called (assuming
+    ///         it was not canceled).
+    ///     </para>
+    /// </remarks>
+    public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
+
+    /// <summary>
+    ///     Raised when the orientation has changed.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Views that implement <see cref="IOrientation"/> should raise <see cref="IOrientation.OrientationChanged"/>
+    ///         after the orientation has changed
+    ///         (<code>_orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);</code>).
+    ///     </para>
+    ///     <para>
+    ///         This event will be raised after the <see cref="IOrientation.OnOrientationChanged"/> method is called.
+    ///     </para>
+    /// </remarks>
+    public event EventHandler<EventArgs<Orientation>> OrientationChanged;
+}

+ 1 - 1
Terminal.Gui/View/ViewAdornments.cs → Terminal.Gui/View/View.Adornments.cs

@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Adornments
 {
     /// <summary>
     ///    Initializes the Adornments of the View. Called by the constructor.

+ 15 - 0
Terminal.Gui/View/View.Arrangement.cs

@@ -0,0 +1,15 @@
+namespace Terminal.Gui;
+
+public partial class View
+{
+    /// <summary>
+    ///    Gets or sets the user actions that are enabled for the view within it's <see cref="SuperView"/>.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    ///     Sizing or moving a view is only possible if the <see cref="View"/> is part of a <see cref="SuperView"/> and
+    ///     the relevant position and dimensions of the <see cref="View"/> are independent of other SubViews
+    /// </para>
+    /// </remarks>
+    public ViewArrangement Arrangement { get; set; }
+}

+ 0 - 0
Terminal.Gui/View/ViewContent.cs → Terminal.Gui/View/View.Content.cs


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

@@ -0,0 +1,35 @@
+namespace Terminal.Gui;
+
+public partial class View
+{
+    /// <summary>
+    ///     Gets or sets the cursor style to be used when the view is focused. The default is
+    ///     <see cref="CursorVisibility.Invisible"/>.
+    /// </summary>
+    public CursorVisibility CursorVisibility { get; set; } = CursorVisibility.Invisible;
+
+    /// <summary>
+    ///     Positions the cursor in the right position based on the currently focused view in the chain.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Views that are focusable should override <see cref="PositionCursor()"/> to make sure that the cursor is
+    ///         placed in a location that makes sense. Some terminals do not have a way of hiding the cursor, so it can be
+    ///         distracting to have the cursor left at the last focused view. So views should make sure that they place the
+    ///         cursor in a visually sensible place. The default implementation of <see cref="PositionCursor()"/> will place the
+    ///         cursor at either the hotkey (if defined) or <c>0,0</c>.
+    ///     </para>
+    /// </remarks>
+    /// <returns>Viewport-relative cursor position. Return <see langword="null"/> to ensure the cursor is not visible.</returns>
+    public virtual Point? PositionCursor ()
+    {
+        if (IsInitialized && CanFocus && HasFocus)
+        {
+            // By default, position the cursor at the hotkey (if any) or 0, 0.
+            Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
+        }
+
+        // Returning null will hide the cursor.
+        return null;
+    }
+}

+ 0 - 0
Terminal.Gui/View/ViewDiagnostics.cs → Terminal.Gui/View/View.Diagnostics.cs


+ 24 - 9
Terminal.Gui/View/ViewDrawing.cs → Terminal.Gui/View/View.Drawing.cs

@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Drawing APIs
 {
     private ColorScheme _colorScheme;
 
@@ -288,19 +288,19 @@ public partial class View
     public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
     {
         Rune hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
-        Application.Driver.SetAttribute (normalColor);
+        Application.Driver?.SetAttribute (normalColor);
 
         foreach (Rune rune in text.EnumerateRunes ())
         {
             if (rune == new Rune (hotkeySpec.Value))
             {
-                Application.Driver.SetAttribute (hotColor);
+                Application.Driver?.SetAttribute (hotColor);
 
                 continue;
             }
 
-            Application.Driver.AddRune (rune);
-            Application.Driver.SetAttribute (normalColor);
+            Application.Driver?.AddRune (rune);
+            Application.Driver?.SetAttribute (normalColor);
         }
     }
 
@@ -501,16 +501,31 @@ public partial class View
         // TODO: Implement OnDrawSubviews (cancelable);
         if (_subviews is { } && SubViewNeedsDisplay)
         {
-            IEnumerable<View> subviewsNeedingDraw = _subviews.Where (
-                                                                     view => view.Visible
-                                                                             && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
-                                                                    );
+            IEnumerable<View> subviewsNeedingDraw;
+            if (TabStop == TabBehavior.TabGroup && _subviews.Count(v => v.Arrangement.HasFlag (ViewArrangement.Overlapped)) > 0)
+            {
+                // TODO: This is a temporary hack to make overlapped non-Toplevels have a zorder. See also View.SetFocus
+                subviewsNeedingDraw = _tabIndexes.Where (
+                                                       view => view.Visible
+                                                               && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
+                                                      ).Reverse ();
+
+            }
+            else
+            {
+                subviewsNeedingDraw = _subviews.Where (
+                                                                         view => view.Visible
+                                                                                 && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
+                                                                        );
+
+            }
             foreach (View view in subviewsNeedingDraw)
             {
                 if (view.LayoutNeeded)
                 {
                     view.LayoutSubviews ();
                 }
+
                 view.Draw ();
             }
         }

+ 323 - 0
Terminal.Gui/View/View.Hierarchy.cs

@@ -0,0 +1,323 @@
+using System.Diagnostics;
+
+namespace Terminal.Gui;
+
+public partial class View // SuperView/SubView hierarchy management (SuperView, SubViews, Add, Remove, etc.)
+{
+    private static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
+    private List<View> _subviews; // This is null, and allocated on demand.
+    private View _superView;
+
+    /// <summary>Indicates whether the view was added to <see cref="SuperView"/>.</summary>
+    public bool IsAdded { get; private set; }
+
+    /// <summary>This returns a list of the subviews contained by this view.</summary>
+    /// <value>The subviews.</value>
+    public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
+
+    /// <summary>Returns the container for this view, or null if this view has not been added to a container.</summary>
+    /// <value>The super view.</value>
+    public virtual View SuperView
+    {
+        get => _superView;
+        set => throw new NotImplementedException ();
+    }
+
+    // Internally, we use InternalSubviews rather than subviews, as we do not expect us
+    // to make the same mistakes our users make when they poke at the Subviews.
+    internal IList<View> InternalSubviews => _subviews ?? _empty;
+
+    /// <summary>Adds a subview (child) to this view.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. See also
+    ///         <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+    ///     </para>
+    ///     <para>
+    ///         Subviews will be disposed when this View is disposed. In other-words, calling this method causes
+    ///         the lifecycle of the subviews to be transferred to this View.
+    ///     </para>
+    /// </remarks>
+    /// <param name="view">The view to add.</param>
+    /// <returns>The view that was added.</returns>
+    public virtual View Add (View view)
+    {
+        if (view is null)
+        {
+            return view;
+        }
+
+        if (_subviews is null)
+        {
+            _subviews = new ();
+        }
+
+        if (_tabIndexes is null)
+        {
+            _tabIndexes = new ();
+        }
+
+        Debug.WriteLineIf (_subviews.Contains (view), $"BUGBUG: {view} has already been added to {this}.");
+        _subviews.Add (view);
+        _tabIndexes.Add (view);
+        view._superView = this;
+
+        if (view.CanFocus)
+        {
+            // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Instead, callers to Add should be explicit about what they want.
+            _addingViewSoCanFocusAlsoUpdatesSuperView = true;
+
+            if (SuperView?.CanFocus == false)
+            {
+                SuperView._addingViewSoCanFocusAlsoUpdatesSuperView = true;
+                SuperView.CanFocus = true;
+                SuperView._addingViewSoCanFocusAlsoUpdatesSuperView = false;
+            }
+
+            // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying.
+            CanFocus = true;
+            view._tabIndex = _tabIndexes.IndexOf (view);
+            _addingViewSoCanFocusAlsoUpdatesSuperView = false;
+        }
+
+        if (view.Enabled && !Enabled)
+        {
+            view._oldEnabled = true;
+            view.Enabled = false;
+        }
+
+        OnAdded (new (this, view));
+
+        if (IsInitialized && !view.IsInitialized)
+        {
+            view.BeginInit ();
+            view.EndInit ();
+        }
+
+        CheckDimAuto ();
+        SetNeedsLayout ();
+        SetNeedsDisplay ();
+
+        return view;
+    }
+
+    /// <summary>Adds the specified views (children) to the view.</summary>
+    /// <param name="views">Array of one or more views (can be optional parameter).</param>
+    /// <remarks>
+    ///     <para>
+    ///         The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. See also
+    ///         <seealso cref="Remove(View)"/> and <seealso cref="RemoveAll"/>.
+    ///     </para>
+    ///     <para>
+    ///         Subviews will be disposed when this View is disposed. In other-words, calling this method causes
+    ///         the lifecycle of the subviews to be transferred to this View.
+    ///     </para>
+    /// </remarks>
+    public void Add (params View [] views)
+    {
+        if (views is null)
+        {
+            return;
+        }
+
+        foreach (View view in views)
+        {
+            Add (view);
+        }
+    }
+
+    /// <summary>Event fired when this view is added to another.</summary>
+    public event EventHandler<SuperViewChangedEventArgs> Added;
+
+    /// <summary>Get the top superview of a given <see cref="View"/>.</summary>
+    /// <returns>The superview view.</returns>
+    public View GetTopSuperView (View view = null, View superview = null)
+    {
+        View top = superview ?? Application.Top;
+
+        for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
+        {
+            top = v;
+
+            if (top == superview)
+            {
+                break;
+            }
+        }
+
+        return top;
+    }
+
+    /// <summary>Method invoked when a subview is being added to this view.</summary>
+    /// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
+    public virtual void OnAdded (SuperViewChangedEventArgs e)
+    {
+        View view = e.Child;
+        view.IsAdded = true;
+        view.OnResizeNeeded ();
+        view.Added?.Invoke (this, e);
+    }
+
+    /// <summary>Method invoked when a subview is being removed from this view.</summary>
+    /// <param name="e">Event args describing the subview being removed.</param>
+    public virtual void OnRemoved (SuperViewChangedEventArgs e)
+    {
+        View view = e.Child;
+        view.IsAdded = false;
+        view.Removed?.Invoke (this, e);
+    }
+
+    /// <summary>Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the
+    ///         Subview's
+    ///         lifecycle to be transferred to the caller; the caller muse call <see cref="Dispose"/>.
+    ///     </para>
+    /// </remarks>
+    public virtual View Remove (View view)
+    {
+        if (view is null || _subviews is null)
+        {
+            return view;
+        }
+
+        Rectangle touched = view.Frame;
+        _subviews.Remove (view);
+        _tabIndexes.Remove (view);
+        view._superView = null;
+        //view._tabIndex = -1;
+        SetNeedsLayout ();
+        SetNeedsDisplay ();
+
+        foreach (View v in _subviews)
+        {
+            if (v.Frame.IntersectsWith (touched))
+            {
+                view.SetNeedsDisplay ();
+            }
+        }
+
+        OnRemoved (new (this, view));
+
+        if (Focused == view)
+        {
+            Focused = null;
+        }
+
+        return view;
+    }
+
+    /// <summary>
+    ///     Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the
+    ///         Subview's
+    ///         lifecycle to be transferred to the caller; the caller must call <see cref="Dispose"/> on any Views that were
+    ///         added.
+    ///     </para>
+    /// </remarks>
+    public virtual void RemoveAll ()
+    {
+        if (_subviews is null)
+        {
+            return;
+        }
+
+        while (_subviews.Count > 0)
+        {
+            Remove (_subviews [0]);
+        }
+    }
+
+    /// <summary>Event fired when this view is removed from another.</summary>
+    public event EventHandler<SuperViewChangedEventArgs> Removed;
+
+
+    /// <summary>Moves <paramref name="subview"/> one position towards the start of the <see cref="Subviews"/> list</summary>
+    /// <param name="subview">The subview to move forward.</param>
+    public void BringSubviewForward (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     int idx = _subviews.IndexOf (x);
+
+                                     if (idx + 1 < _subviews.Count)
+                                     {
+                                         _subviews.Remove (x);
+                                         _subviews.Insert (idx + 1, x);
+                                     }
+                                 }
+                                );
+    }
+
+    /// <summary>Moves <paramref name="subview"/> to the start of the <see cref="Subviews"/> list.</summary>
+    /// <param name="subview">The subview to send to the start.</param>
+    public void BringSubviewToFront (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     _subviews.Remove (x);
+                                     _subviews.Add (x);
+                                 }
+                                );
+    }
+
+
+    /// <summary>Moves <paramref name="subview"/> one position towards the end of the <see cref="Subviews"/> list</summary>
+    /// <param name="subview">The subview to move backwards.</param>
+    public void SendSubviewBackwards (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     int idx = _subviews.IndexOf (x);
+
+                                     if (idx > 0)
+                                     {
+                                         _subviews.Remove (x);
+                                         _subviews.Insert (idx - 1, x);
+                                     }
+                                 }
+                                );
+    }
+
+    /// <summary>Moves <paramref name="subview"/> to the end of the <see cref="Subviews"/> list.</summary>
+    /// <param name="subview">The subview to send to the end.</param>
+    public void SendSubviewToBack (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     _subviews.Remove (x);
+                                     _subviews.Insert (0, subview);
+                                 }
+                                );
+    }
+
+    /// <summary>
+    ///     Internal API that runs <paramref name="action"/> on a subview if it is part of the <see cref="Subviews"/> list.
+    /// </summary>
+    /// <param name="subview"></param>
+    /// <param name="action"></param>
+    private void PerformActionForSubview (View subview, Action<View> action)
+    {
+        if (_subviews.Contains (subview))
+        {
+            action (subview);
+        }
+
+        // BUGBUG: this is odd. Why is this needed?
+        SetNeedsDisplay ();
+        subview.SetNeedsDisplay ();
+    }
+
+}

+ 43 - 134
Terminal.Gui/View/ViewKeyboard.cs → Terminal.Gui/View/View.Keyboard.cs

@@ -3,7 +3,7 @@ using System.Diagnostics;
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View  // Keyboard APIs
 {
     /// <summary>
     ///  Helper to configure all things keyboard related for a View. Called from the View constructor.
@@ -27,7 +27,7 @@ public partial class View
     private void DisposeKeyboard ()
     {
         TitleTextFormatter.HotKeyChanged -= TitleTextFormatter_HotKeyChanged;
-        KeyBindings.Clear ();
+        Application.RemoveKeyBindings (this);
     }
 
     #region HotKey Support
@@ -57,8 +57,7 @@ public partial class View
     ///     Gets or sets the hot key defined for this view. Pressing the hot key on the keyboard while this view has focus will
     ///     invoke the <see cref="Command.HotKey"/> and <see cref="Command.Accept"/> commands. <see cref="Command.HotKey"/>
     ///     causes the view to be focused and <see cref="Command.Accept"/> does nothing. By default, the HotKey is
-    ///     automatically set to the first character of <see cref="Text"/> that is prefixed with with
-    ///     <see cref="HotKeySpecifier"/>.
+    ///     automatically set to the first character of <see cref="Text"/> that is prefixed with <see cref="HotKeySpecifier"/>.
     ///     <para>
     ///         A HotKey is a keypress that selects a visible UI item. For selecting items across <see cref="View"/>`s (e.g.a
     ///         <see cref="Button"/> in a <see cref="Dialog"/>) the keypress must include the <see cref="Key.WithAlt"/>
@@ -198,13 +197,17 @@ public partial class View
         {
             KeyBinding keyBinding = new ([Command.HotKey], KeyBindingScope.HotKey, context);
             // Add the base and Alt key
+            KeyBindings.Remove (newKey);
             KeyBindings.Add (newKey, keyBinding);
+            KeyBindings.Remove (newKey.WithAlt);
             KeyBindings.Add (newKey.WithAlt, keyBinding);
 
             // If the Key is A..Z, add ShiftMask and AltMask | ShiftMask
             if (newKey.IsKeyCodeAtoZ)
             {
+                KeyBindings.Remove (newKey.WithShift);
                 KeyBindings.Add (newKey.WithShift, keyBinding);
+                KeyBindings.Remove (newKey.WithShift.WithAlt);
                 KeyBindings.Add (newKey.WithShift.WithAlt, keyBinding);
             }
         }
@@ -251,119 +254,6 @@ public partial class View
 
     #endregion HotKey Support
 
-    #region Tab/Focus Handling
-
-    // This is null, and allocated on demand.
-    private List<View> _tabIndexes;
-
-    /// <summary>Gets a list of the subviews that are <see cref="TabStop"/>s.</summary>
-    /// <value>The tabIndexes.</value>
-    public IList<View> TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty;
-
-    private int _tabIndex = -1;
-    private int _oldTabIndex;
-
-    /// <summary>
-    ///     Indicates the index of the current <see cref="View"/> from the <see cref="TabIndexes"/> list. See also:
-    ///     <seealso cref="TabStop"/>.
-    /// </summary>
-    public int TabIndex
-    {
-        get => _tabIndex;
-        set
-        {
-            if (!CanFocus)
-            {
-                _tabIndex = -1;
-
-                return;
-            }
-
-            if (SuperView?._tabIndexes is null || SuperView?._tabIndexes.Count == 1)
-            {
-                _tabIndex = 0;
-
-                return;
-            }
-
-            if (_tabIndex == value && TabIndexes.IndexOf (this) == value)
-            {
-                return;
-            }
-
-            _tabIndex = value > SuperView._tabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 :
-                        value < 0 ? 0 : value;
-            _tabIndex = GetTabIndex (_tabIndex);
-
-            if (SuperView._tabIndexes.IndexOf (this) != _tabIndex)
-            {
-                SuperView._tabIndexes.Remove (this);
-                SuperView._tabIndexes.Insert (_tabIndex, this);
-                SetTabIndex ();
-            }
-        }
-    }
-
-    private int GetTabIndex (int idx)
-    {
-        var i = 0;
-
-        foreach (View v in SuperView._tabIndexes)
-        {
-            if (v._tabIndex == -1 || v == this)
-            {
-                continue;
-            }
-
-            i++;
-        }
-
-        return Math.Min (i, idx);
-    }
-
-    private void SetTabIndex ()
-    {
-        var i = 0;
-
-        foreach (View v in SuperView._tabIndexes)
-        {
-            if (v._tabIndex == -1)
-            {
-                continue;
-            }
-
-            v._tabIndex = i;
-            i++;
-        }
-    }
-
-    private bool _tabStop = true;
-
-    /// <summary>
-    ///     Gets or sets whether the view is a stop-point for keyboard navigation of focus. Will be <see langword="true"/>
-    ///     only if the <see cref="CanFocus"/> is also <see langword="true"/>. Set to <see langword="false"/> to prevent the
-    ///     view from being a stop-point for keyboard navigation.
-    /// </summary>
-    /// <remarks>
-    ///     The default keyboard navigation keys are <c>Key.Tab</c> and <c>Key>Tab.WithShift</c>. These can be changed by
-    ///     modifying the key bindings (see <see cref="KeyBindings.Add(Key, Command[])"/>) of the SuperView.
-    /// </remarks>
-    public bool TabStop
-    {
-        get => _tabStop;
-        set
-        {
-            if (_tabStop == value)
-            {
-                return;
-            }
-
-            _tabStop = CanFocus && value;
-        }
-    }
-
-    #endregion Tab/Focus Handling
-
     #region Low-level Key handling
 
     #region Key Down Event
@@ -642,7 +532,7 @@ public partial class View
     /// </returns>
     public virtual bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope)
     {
-        // fire event only if there's an hotkey binding for the key
+        // fire event only if there's a hotkey binding for the key
         if (KeyBindings.TryGet (keyEvent, scope, out KeyBinding kb))
         {
             InvokingKeyBindings?.Invoke (this, keyEvent);
@@ -693,13 +583,23 @@ public partial class View
 
     private bool ProcessAdornmentKeyBindings (Adornment adornment, Key keyEvent, KeyBindingScope scope, ref bool? handled)
     {
-        foreach (View subview in adornment?.Subviews)
+        if (adornment?.Subviews is null)
+        {
+            return false;
+        }
+
+        foreach (View subview in adornment.Subviews)
         {
-            handled = subview.OnInvokingKeyBindings (keyEvent, scope);
+            bool? subViewHandled = subview.OnInvokingKeyBindings (keyEvent, scope);
 
-            if (handled is { } && (bool)handled)
+            if (subViewHandled is { })
             {
-                return true;
+                handled = subViewHandled;
+
+                if ((bool)subViewHandled)
+                {
+                    return true;
+                }
             }
         }
 
@@ -711,6 +611,10 @@ public partial class View
         // Now, process any key bindings in the subviews that are tagged to KeyBindingScope.HotKey.
         foreach (View subview in Subviews)
         {
+            if (subview == Focused)
+            {
+                continue;
+            }
             if (subview.KeyBindings.TryGet (keyEvent, scope, out KeyBinding binding))
             {
                 if (binding.Scope == KeyBindingScope.Focused && !subview.HasFocus)
@@ -723,11 +627,15 @@ public partial class View
                     return true;
                 }
 
-                handled = subview.OnInvokingKeyBindings (keyEvent, scope);
+                bool? subViewHandled = subview.OnInvokingKeyBindings (keyEvent, scope);
 
-                if (handled is { } && (bool)handled)
+                if (subViewHandled is { })
                 {
-                    return true;
+                    handled = subViewHandled;
+                    if ((bool)subViewHandled)
+                    {
+                        return true;
+                    }
                 }
             }
 
@@ -741,11 +649,11 @@ public partial class View
         return false;
     }
 
-    // TODO: This is a "prototype" debug check. It may be too annyoing vs. useful.
-    // TODO: A better approach would be have Application hold a list of bound Hotkeys, similar to
+    // TODO: This is a "prototype" debug check. It may be too annoying vs. useful.
+    // TODO: A better approach would be to have Application hold a list of bound Hotkeys, similar to
     // TODO: how Application holds a list of Application Scoped key bindings and then check that list.
     /// <summary>
-    /// Returns true if Key is bound in this view heirarchy. For debugging
+    /// Returns true if Key is bound in this view hierarchy. For debugging
     /// </summary>
     /// <param name="key">The key to test.</param>
     /// <param name="boundView">Returns the view the key is bound to.</param>
@@ -801,15 +709,16 @@ public partial class View
 #if DEBUG
 
         // TODO: Determine if App scope bindings should be fired first or last (currently last).
-        if (Application.TryGetKeyBindings (key, out List<View> views))
+        if (Application.KeyBindings.TryGet (key, KeyBindingScope.Focused | KeyBindingScope.HotKey, out KeyBinding b))
         {
-            var boundView = views [0];
-            var commandBinding = boundView.KeyBindings.Get (key);
-            Debug.WriteLine ($"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command.{commandBinding.Commands [0]}: {boundView}.");
+            //var boundView = views [0];
+            //var commandBinding = boundView.KeyBindings.Get (key);
+            Debug.WriteLine (
+                             $"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command.");//{commandBinding.Commands [0]}: {boundView}.");
         }
 
-        // TODO: This is a "prototype" debug check. It may be too annyoing vs. useful.
-        // Scour the bindings up our View heirarchy
+        // TODO: This is a "prototype" debug check. It may be too annoying vs. useful.
+        // Scour the bindings up our View hierarchy
         // to ensure that the key is not already bound to a different set of commands.
         if (SuperView?.IsHotKeyKeyBound (key, out View previouslyBoundView) ?? false)
         {

+ 371 - 317
Terminal.Gui/View/Layout/ViewLayout.cs → Terminal.Gui/View/View.Layout.cs

@@ -3,8 +3,244 @@ using System.Diagnostics;
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Layout APIs
 {
+    /// <summary>
+    ///     Indicates whether the specified SuperView-relative coordinates are within the View's <see cref="Frame"/>.
+    /// </summary>
+    /// <param name="location">SuperView-relative coordinate</param>
+    /// <returns><see langword="true"/> if the specified SuperView-relative coordinates are within the View.</returns>
+    public virtual bool Contains (in Point location) { return Frame.Contains (location); }
+
+    /// <summary>Finds the first Subview of <paramref name="start"/> that is visible at the provided location.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Used to determine what view the mouse is over.
+    ///     </para>
+    /// </remarks>
+    /// <param name="start">The view to scope the search by.</param>
+    /// <param name="location"><paramref name="start"/>.SuperView-relative coordinate.</param>
+    /// <returns>
+    ///     The view that was found at the <paramref name="location"/> coordinate.
+    ///     <see langword="null"/> if no view was found.
+    /// </returns>
+
+    // CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
+    internal static View? FindDeepestView (View? start, in Point location)
+    {
+        Point currentLocation = location;
+
+        while (start is { Visible: true } && start.Contains (currentLocation))
+        {
+            Adornment? found = null;
+
+            if (start.Margin.Contains (currentLocation))
+            {
+                found = start.Margin;
+            }
+            else if (start.Border.Contains (currentLocation))
+            {
+                found = start.Border;
+            }
+            else if (start.Padding.Contains (currentLocation))
+            {
+                found = start.Padding;
+            }
+
+            Point viewportOffset = start.GetViewportOffsetFromFrame ();
+
+            if (found is { })
+            {
+                start = found;
+                viewportOffset = found.Parent?.Frame.Location ?? Point.Empty;
+            }
+
+            int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X);
+            int startOffsetY = currentLocation.Y - (start.Frame.Y + viewportOffset.Y);
+
+            View? subview = null;
+
+            for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
+            {
+                if (start.InternalSubviews [i].Visible
+                    && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y)))
+                {
+                    subview = start.InternalSubviews [i];
+                    currentLocation.X = startOffsetX + start.Viewport.X;
+                    currentLocation.Y = startOffsetY + start.Viewport.Y;
+
+                    // start is the deepest subview under the mouse; stop searching the subviews
+                    break;
+                }
+            }
+
+            if (subview is null)
+            {
+                // No subview was found that's under the mouse, so we're done
+                return start;
+            }
+
+            // We found a subview of start that's under the mouse, continue...
+            start = subview;
+        }
+
+        return null;
+    }
+
+    // BUGBUG: This method interferes with Dialog/MessageBox default min/max size.
+
+    /// <summary>
+    ///     Gets a new location of the <see cref="View"/> that is within the Viewport of the <paramref name="viewToMove"/>'s
+    ///     <see cref="View.SuperView"/> (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates.
+    /// </summary>
+    /// <remarks>
+    ///     If <paramref name="viewToMove"/> does not have a <see cref="View.SuperView"/> or it's SuperView is not
+    ///     <see cref="Application.Top"/> the position will be bound by the <see cref="ConsoleDriver.Cols"/> and
+    ///     <see cref="ConsoleDriver.Rows"/>.
+    /// </remarks>
+    /// <param name="viewToMove">The View that is to be moved.</param>
+    /// <param name="targetX">The target x location.</param>
+    /// <param name="targetY">The target y location.</param>
+    /// <param name="nx">The new x location that will ensure <paramref name="viewToMove"/> will be fully visible.</param>
+    /// <param name="ny">The new y location that will ensure <paramref name="viewToMove"/> will be fully visible.</param>
+    /// <param name="statusBar">The new top most statusBar</param>
+    /// <returns>
+    ///     Either <see cref="Application.Top"/> (if <paramref name="viewToMove"/> does not have a Super View) or
+    ///     <paramref name="viewToMove"/>'s SuperView. This can be used to ensure LayoutSubviews is called on the correct View.
+    /// </returns>
+    internal static View? GetLocationEnsuringFullVisibility (
+        View viewToMove,
+        int targetX,
+        int targetY,
+        out int nx,
+        out int ny,
+        out StatusBar? statusBar
+    )
+    {
+        int maxDimension;
+        View? superView;
+        statusBar = null!;
+
+        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
+        {
+            maxDimension = Driver.Cols;
+            superView = Application.Top;
+        }
+        else
+        {
+            // Use the SuperView's Viewport, not Frame
+            maxDimension = viewToMove!.SuperView.Viewport.Width;
+            superView = viewToMove.SuperView;
+        }
+
+        if (superView?.Margin is { } && superView == viewToMove!.SuperView)
+        {
+            maxDimension -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right;
+        }
+
+        if (viewToMove!.Frame.Width <= maxDimension)
+        {
+            nx = Math.Max (targetX, 0);
+            nx = nx + viewToMove.Frame.Width > maxDimension ? Math.Max (maxDimension - viewToMove.Frame.Width, 0) : nx;
+
+            if (nx > viewToMove.Frame.X + viewToMove.Frame.Width)
+            {
+                nx = Math.Max (viewToMove.Frame.Right, 0);
+            }
+        }
+        else
+        {
+            nx = targetX;
+        }
+
+        //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
+        var menuVisible = false;
+        var statusVisible = false;
+
+        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
+        {
+            menuVisible = Application.Top?.MenuBar?.Visible == true;
+        }
+        else
+        {
+            View t = viewToMove!.SuperView;
+
+            while (t is { } and not Toplevel)
+            {
+                t = t.SuperView;
+            }
+
+            if (t is Toplevel topLevel)
+            {
+                menuVisible = topLevel.MenuBar?.Visible == true;
+            }
+        }
+
+        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
+        {
+            maxDimension = menuVisible ? 1 : 0;
+        }
+        else
+        {
+            maxDimension = 0;
+        }
+
+        ny = Math.Max (targetY, maxDimension);
+
+        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
+        {
+            statusVisible = Application.Top?.StatusBar?.Visible == true;
+            statusBar = Application.Top?.StatusBar!;
+        }
+        else
+        {
+            View t = viewToMove!.SuperView;
+
+            while (t is { } and not Toplevel)
+            {
+                t = t.SuperView;
+            }
+
+            if (t is Toplevel topLevel)
+            {
+                statusVisible = topLevel.StatusBar?.Visible == true;
+                statusBar = topLevel.StatusBar!;
+            }
+        }
+
+        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
+        {
+            maxDimension = statusVisible ? Driver.Rows - 1 : Driver.Rows;
+        }
+        else
+        {
+            maxDimension = statusVisible ? viewToMove!.SuperView.Viewport.Height - 1 : viewToMove!.SuperView.Viewport.Height;
+        }
+
+        if (superView?.Margin is { } && superView == viewToMove?.SuperView)
+        {
+            maxDimension -= superView.GetAdornmentsThickness ().Top + superView.GetAdornmentsThickness ().Bottom;
+        }
+
+        ny = Math.Min (ny, maxDimension);
+
+        if (viewToMove?.Frame.Height <= maxDimension)
+        {
+            ny = ny + viewToMove.Frame.Height > maxDimension
+                     ? Math.Max (maxDimension - viewToMove.Frame.Height, menuVisible ? 1 : 0)
+                     : ny;
+
+            if (ny > viewToMove.Frame.Y + viewToMove.Frame.Height)
+            {
+                ny = Math.Max (viewToMove.Frame.Bottom, 0);
+            }
+        }
+
+        //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
+
+        return superView!;
+    }
+
     #region Frame
 
     private Rectangle _frame;
@@ -15,7 +251,10 @@ public partial class View
     ///     <see cref="SuperView"/>'s Content, which is bound by <see cref="GetContentSize ()"/>.
     /// </value>
     /// <remarks>
-    ///     <para>Frame is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="GetContentSize ()"/>.</para>
+    ///     <para>
+    ///         Frame is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="GetContentSize ()"/>
+    ///         .
+    ///     </para>
     ///     <para>
     ///         Setting Frame will set <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> to the
     ///         values of the corresponding properties of the <paramref name="value"/> parameter.
@@ -45,7 +284,6 @@ public partial class View
             _width = _frame.Width;
             _height = _frame.Height;
 
-            // TODO: Figure out if the below can be optimized.
             if (IsInitialized)
             {
                 OnResizeNeeded ();
@@ -65,8 +303,6 @@ public partial class View
         // This is the only place where _frame should be set directly. Use Frame = or SetFrame instead.
         _frame = frame;
 
-        SetTextFormatterSize ();
-
         OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport));
     }
 
@@ -133,7 +369,8 @@ public partial class View
     /// <value>The <see cref="Pos"/> object representing the X position.</value>
     /// <remarks>
     ///     <para>
-    ///         The position is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="GetContentSize ()"/>.
+    ///         The position is relative to the <see cref="SuperView"/>'s Content, which is bound by
+    ///         <see cref="GetContentSize ()"/>.
     ///     </para>
     ///     <para>
     ///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the view has been
@@ -171,7 +408,8 @@ public partial class View
     /// <value>The <see cref="Pos"/> object representing the Y position.</value>
     /// <remarks>
     ///     <para>
-    ///         The position is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="GetContentSize ()"/>.
+    ///         The position is relative to the <see cref="SuperView"/>'s Content, which is bound by
+    ///         <see cref="GetContentSize ()"/>.
     ///     </para>
     ///     <para>
     ///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the view has been
@@ -208,7 +446,8 @@ public partial class View
     /// <value>The <see cref="Dim"/> object representing the height of the view (the number of rows).</value>
     /// <remarks>
     ///     <para>
-    ///         The dimension is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="GetContentSize ()"/>
+    ///         The dimension is relative to the <see cref="SuperView"/>'s Content, which is bound by
+    ///         <see cref="GetContentSize ()"/>
     ///         .
     ///     </para>
     ///     <para>
@@ -235,7 +474,7 @@ public partial class View
                 return;
             }
 
-            if (_height is DimAuto)
+            if (_height is { } && _height.Has (typeof (DimAuto), out _))
             {
                 // Reset ContentSize to Viewport
                 _contentSize = null;
@@ -243,6 +482,9 @@ public partial class View
 
             _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null");
 
+            // Reset TextFormatter - Will be recalculated in SetTextFormatterSize
+            TextFormatter.ConstrainToHeight = null;
+
             OnResizeNeeded ();
         }
     }
@@ -253,7 +495,8 @@ public partial class View
     /// <value>The <see cref="Dim"/> object representing the width of the view (the number of columns).</value>
     /// <remarks>
     ///     <para>
-    ///         The dimension is relative to the <see cref="SuperView"/>'s Content, which is bound by <see cref="GetContentSize ()"/>
+    ///         The dimension is relative to the <see cref="SuperView"/>'s Content, which is bound by
+    ///         <see cref="GetContentSize ()"/>
     ///         .
     ///     </para>
     ///     <para>
@@ -280,7 +523,7 @@ public partial class View
                 return;
             }
 
-            if (_width is DimAuto)
+            if (_width is { } && _width.Has (typeof (DimAuto), out _))
             {
                 // Reset ContentSize to Viewport
                 _contentSize = null;
@@ -288,6 +531,9 @@ public partial class View
 
             _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null");
 
+            // Reset TextFormatter - Will be recalculated in SetTextFormatterSize
+            TextFormatter.ConstrainToWidth = null;
+
             OnResizeNeeded ();
         }
     }
@@ -296,265 +542,134 @@ public partial class View
 
     #region Layout Engine
 
-    #endregion Layout Engine
-
-    /// <summary>
-    ///     Indicates whether the specified SuperView-relative coordinates are within the View's <see cref="Frame"/>.
-    /// </summary>
-    /// <param name="location">SuperView-relative coordinate</param>
-    /// <returns><see langword="true"/> if the specified SuperView-relative coordinates are within the View.</returns>
-    public virtual bool Contains (in Point location) { return Frame.Contains (location); }
-
-    /// <summary>Finds the first Subview of <paramref name="start"/> that is visible at the provided location.</summary>
+    /// <summary>Fired after the View's <see cref="LayoutSubviews"/> method has completed.</summary>
     /// <remarks>
-    ///     <para>
-    ///         Used to determine what view the mouse is over.
-    ///     </para>
+    ///     Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has
+    ///     otherwise changed.
     /// </remarks>
-    /// <param name="start">The view to scope the search by.</param>
-    /// <param name="location"><paramref name="start"/>.SuperView-relative coordinate.</param>
-    /// <returns>
-    ///     The view that was found at the <paramref name="location"/> coordinate.
-    ///     <see langword="null"/> if no view was found.
-    /// </returns>
-
-    // CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
-    internal static View? FindDeepestView (View? start, in Point location)
-    {
-        Point currentLocation = location;
-        while (start is { Visible: true } && start.Contains (currentLocation))
-        {
-            Adornment? found = null;
-
-            if (start.Margin.Contains (currentLocation))
-            {
-                found = start.Margin;
-            }
-            else if (start.Border.Contains (currentLocation))
-            {
-                found = start.Border;
-            }
-            else if (start.Padding.Contains (currentLocation))
-            {
-                found = start.Padding;
-            }
-
-            Point viewportOffset = start.GetViewportOffsetFromFrame ();
-
-            if (found is { })
-            {
-                start = found;
-                viewportOffset = found.Parent.Frame.Location;
-            }
-
-            int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X);
-            int startOffsetY = currentLocation.Y - (start.Frame.Y + viewportOffset.Y);
-
-            View? subview = null;
-
-            for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
-            {
-                if (start.InternalSubviews [i].Visible
-                    && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y)))
-                {
-                    subview = start.InternalSubviews [i];
-                    currentLocation.X = startOffsetX + start.Viewport.X;
-                    currentLocation.Y = startOffsetY + start.Viewport.Y;
-
-                    // start is the deepest subview under the mouse; stop searching the subviews
-                    break;
-                }
-            }
-
-            if (subview is null)
-            {
-                // No subview was found that's under the mouse, so we're done
-                return start;
-            }
-
-            // We found a subview of start that's under the mouse, continue...
-            start = subview;
-        }
+    public event EventHandler<LayoutEventArgs> LayoutComplete;
 
-        return null;
-    }
+    /// <summary>Fired after the View's <see cref="LayoutSubviews"/> method has completed.</summary>
+    /// <remarks>
+    ///     Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has
+    ///     otherwise changed.
+    /// </remarks>
+    public event EventHandler<LayoutEventArgs> LayoutStarted;
 
     /// <summary>
-    ///     Gets a new location of the <see cref="View"/> that is within the Viewport of the <paramref name="viewToMove"/>'s
-    ///     <see cref="View.SuperView"/> (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates.
+    ///     Adjusts <see cref="Frame"/> given the SuperView's ContentSize (nominally the same as
+    ///     <c>this.SuperView.GetContentSize ()</c>)
+    ///     and the position (<see cref="X"/>, <see cref="Y"/>) and dimension (<see cref="Width"/>, and
+    ///     <see cref="Height"/>).
     /// </summary>
     /// <remarks>
-    ///     If <paramref name="viewToMove"/> does not have a <see cref="View.SuperView"/> or it's SuperView is not
-    ///     <see cref="Application.Top"/> the position will be bound by the <see cref="ConsoleDriver.Cols"/> and
-    ///     <see cref="ConsoleDriver.Rows"/>.
+    ///     <para>
+    ///         If <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, or <see cref="Height"/> are
+    ///         absolute, they will be updated to reflect the new size and position of the view. Otherwise, they
+    ///         are left unchanged.
+    ///     </para>
+    ///     <para>
+    ///         If any of the view's subviews have a position or dimension dependent on either <see cref="GetContentSize"/> or
+    ///         other subviews, <see cref="LayoutSubview"/> on
+    ///         will be called for that subview.
+    ///     </para>
     /// </remarks>
-    /// <param name="viewToMove">The View that is to be moved.</param>
-    /// <param name="targetX">The target x location.</param>
-    /// <param name="targetY">The target y location.</param>
-    /// <param name="nx">The new x location that will ensure <paramref name="viewToMove"/> will be fully visible.</param>
-    /// <param name="ny">The new y location that will ensure <paramref name="viewToMove"/> will be fully visible.</param>
-    /// <param name="statusBar">The new top most statusBar</param>
-    /// <returns>
-    ///     Either <see cref="Application.Top"/> (if <paramref name="viewToMove"/> does not have a Super View) or
-    ///     <paramref name="viewToMove"/>'s SuperView. This can be used to ensure LayoutSubviews is called on the correct View.
-    /// </returns>
-    internal static View GetLocationEnsuringFullVisibility (
-        View viewToMove,
-        int targetX,
-        int targetY,
-        out int nx,
-        out int ny,
-        out StatusBar statusBar
-    )
+    /// <param name="superviewContentSize">
+    ///     The size of the SuperView's content (nominally the same as <c>this.SuperView.GetContentSize ()</c>).
+    /// </param>
+    internal void SetRelativeLayout (Size superviewContentSize)
     {
-        int maxDimension;
-        View superView;
-        statusBar = null!;
+        Debug.Assert (_x is { });
+        Debug.Assert (_y is { });
+        Debug.Assert (_width is { });
+        Debug.Assert (_height is { });
 
-        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
+        CheckDimAuto ();
+        SetTextFormatterSize ();
+
+        int newX, newW, newY, newH;
+
+        // Calculate the new X, Y, Width, and Height
+        // If the Width or Height is Dim.Auto, calculate the Width or Height first. Otherwise, calculate the X or Y first.
+        if (_width is DimAuto)
         {
-            maxDimension = Driver.Cols;
-            superView = Application.Top;
+            newW = _width.Calculate (0, superviewContentSize.Width, this, Dimension.Width);
+            newX = _x.Calculate (superviewContentSize.Width, newW, this, Dimension.Width);
         }
         else
         {
-            // Use the SuperView's Viewport, not Frame
-            maxDimension = viewToMove!.SuperView.Viewport.Width;
-            superView = viewToMove.SuperView;
-        }
-
-        if (superView?.Margin is { } && superView == viewToMove!.SuperView)
-        {
-            maxDimension -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right;
+            newX = _x.Calculate (superviewContentSize.Width, _width, this, Dimension.Width);
+            newW = _width.Calculate (newX, superviewContentSize.Width, this, Dimension.Width);
         }
 
-        if (viewToMove!.Frame.Width <= maxDimension)
+        if (_height is DimAuto)
         {
-            nx = Math.Max (targetX, 0);
-            nx = nx + viewToMove.Frame.Width > maxDimension ? Math.Max (maxDimension - viewToMove.Frame.Width, 0) : nx;
-
-            if (nx > viewToMove.Frame.X + viewToMove.Frame.Width)
-            {
-                nx = Math.Max (viewToMove.Frame.Right, 0);
-            }
+            newH = _height.Calculate (0, superviewContentSize.Height, this, Dimension.Height);
+            newY = _y.Calculate (superviewContentSize.Height, newH, this, Dimension.Height);
         }
         else
         {
-            nx = targetX;
+            newY = _y.Calculate (superviewContentSize.Height, _height, this, Dimension.Height);
+            newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height);
         }
 
-        //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
-        var menuVisible = false;
-        var statusVisible = false;
+        Rectangle newFrame = new (newX, newY, newW, newH);
 
-        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
-        {
-            menuVisible = Application.Top?.MenuBar?.Visible == true;
-        }
-        else
+        if (Frame != newFrame)
         {
-            View t = viewToMove!.SuperView;
+            // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height
+            SetFrame (newFrame);
 
-            while (t is { } and not Toplevel)
+            if (_x is PosAbsolute)
             {
-                t = t.SuperView;
+                _x = Frame.X;
             }
 
-            if (t is Toplevel topLevel)
+            if (_y is PosAbsolute)
             {
-                menuVisible = topLevel.MenuBar?.Visible == true;
+                _y = Frame.Y;
             }
-        }
-
-        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
-        {
-            maxDimension = menuVisible ? 1 : 0;
-        }
-        else
-        {
-            maxDimension = 0;
-        }
-
-        ny = Math.Max (targetY, maxDimension);
 
-        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
-        {
-            statusVisible = Application.Top?.StatusBar?.Visible == true;
-            statusBar = Application.Top?.StatusBar!;
-        }
-        else
-        {
-            View t = viewToMove!.SuperView;
+            if (_width is DimAbsolute)
+            {
+                _width = Frame.Width;
+            }
 
-            while (t is { } and not Toplevel)
+            if (_height is DimAbsolute)
             {
-                t = t.SuperView;
+                _height = Frame.Height;
             }
 
-            if (t is Toplevel topLevel)
+            if (!string.IsNullOrEmpty (Title))
             {
-                statusVisible = topLevel.StatusBar?.Visible == true;
-                statusBar = topLevel.StatusBar!;
+                SetTitleTextFormatterSize ();
             }
-        }
 
-        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
-        {
-            maxDimension = statusVisible ? Driver.Rows - 1 : Driver.Rows;
-        }
-        else
-        {
-            maxDimension = statusVisible ? viewToMove!.SuperView.Viewport.Height - 1 : viewToMove!.SuperView.Viewport.Height;
+            SetNeedsLayout ();
+            SetNeedsDisplay ();
         }
 
-        if (superView?.Margin is { } && superView == viewToMove?.SuperView)
+        if (TextFormatter.ConstrainToWidth is null)
         {
-            maxDimension -= superView.GetAdornmentsThickness ().Top + superView.GetAdornmentsThickness ().Bottom;
+            TextFormatter.ConstrainToWidth = GetContentSize ().Width;
         }
 
-        ny = Math.Min (ny, maxDimension);
-
-        if (viewToMove?.Frame.Height <= maxDimension)
+        if (TextFormatter.ConstrainToHeight is null)
         {
-            ny = ny + viewToMove.Frame.Height > maxDimension
-                     ? Math.Max (maxDimension - viewToMove.Frame.Height, menuVisible ? 1 : 0)
-                     : ny;
-
-            if (ny > viewToMove.Frame.Y + viewToMove.Frame.Height)
-            {
-                ny = Math.Max (viewToMove.Frame.Bottom, 0);
-            }
+            TextFormatter.ConstrainToHeight = GetContentSize ().Height;
         }
-
-        //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
-
-        return superView!;
     }
 
-    /// <summary>Fired after the View's <see cref="LayoutSubviews"/> method has completed.</summary>
-    /// <remarks>
-    ///     Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has
-    ///     otherwise changed.
-    /// </remarks>
-    public event EventHandler<LayoutEventArgs> LayoutComplete;
-
-    /// <summary>Fired after the View's <see cref="LayoutSubviews"/> method has completed.</summary>
-    /// <remarks>
-    ///     Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has
-    ///     otherwise changed.
-    /// </remarks>
-    public event EventHandler<LayoutEventArgs> LayoutStarted;
-
     /// <summary>
-    ///     Invoked when a view starts executing or when the dimensions of the view have changed, for example in response to
-    ///     the container view or terminal resizing.
+    ///     Invoked when the dimensions of the view have changed, for example in response to the container view or terminal
+    ///     resizing.
     /// </summary>
     /// <remarks>
     ///     <para>
     ///         The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, the
     ///         behavior of this method is indeterminate if <see cref="IsInitialized"/> is <see langword="false"/>.
     ///     </para>
-    ///     <para>Raises the <see cref="LayoutComplete"/> event) before it returns.</para>
+    ///     <para>Raises the <see cref="LayoutComplete"/> event before it returns.</para>
     /// </remarks>
     public virtual void LayoutSubviews ()
     {
@@ -570,13 +685,11 @@ public partial class View
 
         CheckDimAuto ();
 
-        var contentSize = GetContentSize ();
+        Size contentSize = GetContentSize ();
         OnLayoutStarted (new (contentSize));
 
         LayoutAdornments ();
 
-        SetTextFormatterSize ();
-
         // Sort out the dependencies of the X, Y, Width, Height properties
         HashSet<View> nodes = new ();
         HashSet<(View, View)> edges = new ();
@@ -605,7 +718,7 @@ public partial class View
 
     private void LayoutSubview (View v, Size contentSize)
     {
-        // BUGBUG: Calling SetRelativeLayout before LayoutSubviews is problematic. Need to resolve.
+        // Note, SetRelativeLayout calls SetTextFormatterSize
         v.SetRelativeLayout (contentSize);
         v.LayoutSubviews ();
         v.LayoutNeeded = false;
@@ -620,9 +733,6 @@ public partial class View
     /// </summary>
     internal virtual void OnLayoutComplete (LayoutEventArgs args) { LayoutComplete?.Invoke (this, args); }
 
-    // BUGBUG: We need an API/event that is called from SetRelativeLayout instead of/in addition to 
-    // BUGBUG: OnLayoutStarted which is called from LayoutSubviews.
-
     /// <summary>
     ///     Raises the <see cref="LayoutStarted"/> event. Called from  <see cref="LayoutSubviews"/> before any subviews
     ///     have been laid out.
@@ -644,14 +754,12 @@ public partial class View
         // TODO: Identify a real-world use-case where this API should be virtual. 
         // TODO: Until then leave it `internal` and non-virtual
 
-        // Determine our container's ContentSize - 
+        // Determine our container's ContentSize -
         //  First try SuperView.Viewport, then Application.Top, then Driver.Viewport.
-        //  Finally, if none of those are valid, use int.MaxValue (for Unit tests).
+        //  Finally, if none of those are valid, use 2048 (for Unit tests).
         Size superViewContentSize = SuperView is { IsInitialized: true } ? SuperView.GetContentSize () :
-                           Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () :
-                           Application.Driver?.Screen.Size ?? new (int.MaxValue, int.MaxValue);
-
-        SetTextFormatterSize ();
+                                    Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () :
+                                    Application.Screen.Size;
 
         SetRelativeLayout (superViewContentSize);
 
@@ -689,90 +797,15 @@ public partial class View
     }
 
     /// <summary>
-    ///     Adjusts <see cref="Frame"/> given the SuperView's ContentSize (nominally the same as
-    ///     <c>this.SuperView.GetContentSize ()</c>)
-    ///     and the position (<see cref="X"/>, <see cref="Y"/>) and dimension (<see cref="Width"/>, and
-    ///     <see cref="Height"/>).
+    ///     Collects all views and their dependencies from a given starting view for layout purposes. Used by
+    ///     <see cref="TopologicalSort"/> to create an ordered list of views to layout.
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         If <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, or <see cref="Height"/> are
-    ///         absolute, they will be updated to reflect the new size and position of the view. Otherwise, they
-    ///         are left unchanged.
-    ///     </para>
-    /// </remarks>
-    /// <param name="superviewContentSize">
-    ///     The size of the SuperView's content (nominally the same as <c>this.SuperView.GetContentSize ()</c>).
+    /// <param name="from">The starting view from which to collect dependencies.</param>
+    /// <param name="nNodes">A reference to a set of views representing nodes in the layout graph.</param>
+    /// <param name="nEdges">
+    ///     A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views
+    ///     indicating a dependency.
     /// </param>
-    internal void SetRelativeLayout (Size superviewContentSize)
-    {
-        Debug.Assert (_x is { });
-        Debug.Assert (_y is { });
-        Debug.Assert (_width is { });
-        Debug.Assert (_height is { });
-
-        CheckDimAuto ();
-        int newX, newW, newY, newH;
-
-        if (_width is DimAuto)
-        {
-            newW = _width.Calculate (0, superviewContentSize.Width, this, Dimension.Width);
-            newX = _x.Calculate (superviewContentSize.Width, newW, this, Dimension.Width);
-        }
-        else
-        {
-            newX = _x.Calculate (superviewContentSize.Width, _width, this, Dimension.Width);
-            newW = _width.Calculate (newX, superviewContentSize.Width, this, Dimension.Width);
-        }
-
-        if (_height is DimAuto)
-        {
-            newH = _height.Calculate (0, superviewContentSize.Height, this, Dimension.Height);
-            newY = _y.Calculate (superviewContentSize.Height, newH, this, Dimension.Height);
-        }
-        else
-        {
-            newY = _y.Calculate (superviewContentSize.Height, _height, this, Dimension.Height);
-            newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height);
-        }
-
-        Rectangle newFrame = new (newX, newY, newW, newH);
-
-        if (Frame != newFrame)
-        {
-            // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height
-            SetFrame (newFrame);
-
-            if (_x is PosAbsolute)
-            {
-                _x = Frame.X;
-            }
-
-            if (_y is PosAbsolute)
-            {
-                _y = Frame.Y;
-            }
-
-            if (_width is DimAbsolute)
-            {
-                _width = Frame.Width;
-            }
-
-            if (_height is DimAbsolute)
-            {
-                _height = Frame.Height;
-            }
-
-            if (!string.IsNullOrEmpty (Title))
-            {
-                SetTitleTextFormatterSize ();
-            }
-
-            SetNeedsLayout ();
-            SetNeedsDisplay ();
-        }
-    }
-
     internal void CollectAll (View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
     {
         foreach (View? v in from.InternalSubviews)
@@ -785,6 +818,16 @@ public partial class View
         }
     }
 
+    /// <summary>
+    ///     Collects dimension (where Width or Height is `DimView`) dependencies for a given view.
+    /// </summary>
+    /// <param name="dim">The dimension (width or height) to collect dependencies for.</param>
+    /// <param name="from">The view for which to collect dimension dependencies.</param>
+    /// <param name="nNodes">A reference to a set of views representing nodes in the layout graph.</param>
+    /// <param name="nEdges">
+    ///     A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views
+    ///     indicating a dependency.
+    /// </param>
     internal void CollectDim (Dim? dim, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
     {
         switch (dim)
@@ -796,7 +839,7 @@ public partial class View
                 //}
                 if (dv.Target != this)
                 {
-                    nEdges.Add ((dv.Target, from));
+                    nEdges.Add ((dv.Target!, from));
                 }
 
                 return;
@@ -808,6 +851,16 @@ public partial class View
         }
     }
 
+    /// <summary>
+    ///     Collects position (where X or Y is `PosView`) dependencies for a given view.
+    /// </summary>
+    /// <param name="pos">The position (X or Y) to collect dependencies for.</param>
+    /// <param name="from">The view for which to collect position dependencies.</param>
+    /// <param name="nNodes">A reference to a set of views representing nodes in the layout graph.</param>
+    /// <param name="nEdges">
+    ///     A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views
+    ///     indicating a dependency.
+    /// </param>
     internal void CollectPos (Pos pos, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
     {
         switch (pos)
@@ -819,7 +872,7 @@ public partial class View
                 //}
                 if (pv.Target != this)
                 {
-                    nEdges.Add ((pv.Target, from));
+                    nEdges.Add ((pv.Target!, from));
                 }
 
                 return;
@@ -966,7 +1019,6 @@ public partial class View
     /// </remarks>
     public bool ValidatePosDim { get; set; }
 
-
     // TODO: Move this logic into the Pos/Dim classes
     /// <summary>
     ///     Throws an <see cref="InvalidOperationException"/> if any SubViews are using Dim objects that depend on this
@@ -980,8 +1032,8 @@ public partial class View
             return;
         }
 
-        DimAuto? widthAuto = Width as DimAuto;
-        DimAuto? heightAuto = Height as DimAuto;
+        var widthAuto = Width as DimAuto;
+        var heightAuto = Height as DimAuto;
 
         // Verify none of the subviews are using Dim objects that depend on the SuperView's dimensions.
         foreach (View view in Subviews)
@@ -1046,8 +1098,10 @@ public partial class View
                 throw new InvalidOperationException (
                                                      $"{view.GetType ().Name}.{name} = {bad.GetType ().Name} "
                                                      + $"which depends on the SuperView's dimensions and the SuperView uses Dim.Auto."
-                                                     );
+                                                    );
             }
         }
     }
+
+    #endregion Layout Engine
 }

+ 1 - 1
Terminal.Gui/View/ViewMouse.cs → Terminal.Gui/View/View.Mouse.cs

@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Mouse APIs
 {
     [CanBeNull]
     private ColorScheme _savedHighlightColorScheme;

+ 875 - 0
Terminal.Gui/View/View.Navigation.cs

@@ -0,0 +1,875 @@
+using System.Diagnostics;
+using static Terminal.Gui.FakeDriver;
+
+namespace Terminal.Gui;
+
+public partial class View // Focus and cross-view navigation management (TabStop, TabIndex, etc...)
+{
+    // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Instead, callers to Add should be explicit about what they want.
+    // Set to true in Add() to indicate that the view being added to a SuperView has CanFocus=true.
+    // Makes it so CanFocus will update the SuperView's CanFocus property.
+    internal bool _addingViewSoCanFocusAlsoUpdatesSuperView;
+
+    private NavigationDirection _focusDirection;
+
+    private bool _hasFocus;
+
+    // Used to cache CanFocus on subviews when CanFocus is set to false so that it can be restored when CanFocus is changed back to true
+    private bool _oldCanFocus;
+
+    private bool _canFocus;
+
+    /// <summary>
+    ///     Advances the focus to the next or previous view in <see cref="View.TabIndexes"/>, based on
+    ///     <paramref name="direction"/>.
+    ///     itself.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If there is no next/previous view, the focus is set to the view itself.
+    ///     </para>
+    /// </remarks>
+    /// <param name="direction"></param>
+    /// <param name="behavior"></param>
+    /// <returns>
+    ///     <see langword="true"/> if focus was changed to another subview (or stayed on this one), <see langword="false"/>
+    ///     otherwise.
+    /// </returns>
+    public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
+    {
+        if (!CanBeVisible (this))
+        {
+            return false;
+        }
+
+        FocusDirection = direction;
+
+        if (TabIndexes is null || TabIndexes.Count == 0)
+        {
+            return false;
+        }
+
+        if (Focused is null)
+        {
+            switch (direction)
+            {
+                case NavigationDirection.Forward:
+                    FocusFirst (behavior);
+
+                    break;
+                case NavigationDirection.Backward:
+                    FocusLast (behavior);
+
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException (nameof (direction), direction, null);
+            }
+
+            return Focused is { };
+        }
+
+        if (Focused is { })
+        {
+            if (Focused.AdvanceFocus (direction, behavior))
+            {
+                // TODO: Temporary hack to make Application.Navigation.FocusChanged work
+                if (Focused.Focused is null)
+                {
+                    Application.Navigation?.SetFocused (Focused);
+                }
+                return true;
+            }
+        }
+
+        var index = GetScopedTabIndexes (behavior, direction);
+        if (index.Length == 0)
+        {
+            return false;
+        }
+        var focusedIndex = index.IndexOf (Focused);
+        int next = 0;
+
+        if (focusedIndex < index.Length - 1)
+        {
+            next = focusedIndex + 1;
+        }
+        else
+        {
+            // focusedIndex is at end of list. If we are going backwards,...
+            if (behavior == TabStop)
+            {
+                // Go up the hierarchy
+                // Leave
+                Focused.SetHasFocus (false, this);
+
+                // Signal that nothing is focused, and callers should try a peer-subview
+                Focused = null;
+
+                return false;
+            }
+            // Wrap around
+            //if (SuperView is {})
+            //{
+            //    if (direction == NavigationDirection.Forward)
+            //    {
+            //        return false;
+            //    }
+            //    else
+            //    {
+            //        return false;
+
+            //        //SuperView.FocusFirst (groupOnly);
+            //    }
+            //    return true;
+            //}
+            //next = index.Length - 1;
+
+        }
+
+        View view = index [next];
+
+
+        // The subview does not have focus, but at least one other that can. Can this one be focused?
+        if (view.CanFocus && view.Visible && view.Enabled)
+        {
+            // Make Focused Leave
+            Focused.SetHasFocus (false, view);
+
+            switch (direction)
+            {
+                case NavigationDirection.Forward:
+                    view.FocusFirst (TabBehavior.TabStop);
+
+                    break;
+                case NavigationDirection.Backward:
+                    view.FocusLast (TabBehavior.TabStop);
+
+                    break;
+            }
+
+            SetFocus (view);
+
+            // TODO: Temporary hack to make Application.Navigation.FocusChanged work
+            if (view.Focused is null)
+            {
+                Application.Navigation?.SetFocused (view);
+            }
+
+            return true;
+        }
+
+        if (Focused is { })
+        {
+            // Leave
+            Focused.SetHasFocus (false, this);
+
+            // Signal that nothing is focused, and callers should try a peer-subview
+            Focused = null;
+        }
+
+        return false;
+    }
+
+    /// <summary>Gets or sets a value indicating whether this <see cref="View"/> can be focused.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         <see cref="SuperView"/> must also have <see cref="CanFocus"/> set to <see langword="true"/>.
+    ///     </para>
+    ///     <para>
+    ///         When set to <see langword="false"/>, if an attempt is made to make this view focused, the focus will be set to
+    ///         the next focusable view.
+    ///     </para>
+    ///     <para>
+    ///         When set to <see langword="false"/>, the <see cref="TabIndex"/> will be set to -1.
+    ///     </para>
+    ///     <para>
+    ///         When set to <see langword="false"/>, the values of <see cref="CanFocus"/> and <see cref="TabIndex"/> for all
+    ///         subviews will be cached so that when <see cref="CanFocus"/> is set back to <see langword="true"/>, the subviews
+    ///         will be restored to their previous values.
+    ///     </para>
+    ///     <para>
+    ///         Changing this peroperty to <see langword="true"/> will cause <see cref="TabStop"/> to be set to
+    ///         <see cref="TabBehavior.TabStop"/>" as a convenience. Changing this peroperty to
+    ///         <see langword="false"/> will have no effect on <see cref="TabStop"/>.
+    ///     </para>
+    /// </remarks>
+    public bool CanFocus
+    {
+        get => _canFocus;
+        set
+        {
+            if (!_addingViewSoCanFocusAlsoUpdatesSuperView && IsInitialized && SuperView?.CanFocus == false && value)
+            {
+                throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
+            }
+
+            if (_canFocus == value)
+            {
+                return;
+            }
+
+            _canFocus = value;
+
+            switch (_canFocus)
+            {
+                case false when _tabIndex > -1:
+                    // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Callers should adjust TabIndex explicitly.
+                    //TabIndex = -1;
+
+                    break;
+
+                case true when SuperView?.CanFocus == false && _addingViewSoCanFocusAlsoUpdatesSuperView:
+                    SuperView.CanFocus = true;
+
+                    break;
+            }
+
+            if (TabStop is null && _canFocus)
+            {
+                TabStop = TabBehavior.TabStop;
+            }
+
+            if (!_canFocus && SuperView?.Focused == this)
+            {
+                SuperView.Focused = null;
+            }
+
+            if (!_canFocus && HasFocus)
+            {
+                SetHasFocus (false, this);
+                SuperView?.RestoreFocus ();
+
+                // If EnsureFocus () didn't set focus to a view, focus the next focusable view in the application
+                if (SuperView is { Focused: null })
+                {
+                    SuperView.AdvanceFocus (NavigationDirection.Forward, null);
+
+                    if (SuperView.Focused is null && Application.Current is { })
+                    {
+                        Application.Current.AdvanceFocus (NavigationDirection.Forward, null);
+                    }
+
+                    ApplicationOverlapped.BringOverlappedTopToFront ();
+                }
+            }
+
+            if (_subviews is { } && IsInitialized)
+            {
+                foreach (View view in _subviews)
+                {
+                    if (view.CanFocus != value)
+                    {
+                        if (!value)
+                        {
+                            // Cache the old CanFocus and TabIndex so that they can be restored when CanFocus is changed back to true
+                            view._oldCanFocus = view.CanFocus;
+                            view._oldTabIndex = view._tabIndex;
+                            view.CanFocus = false;
+
+                            //view._tabIndex = -1;
+                        }
+                        else
+                        {
+                            if (_addingViewSoCanFocusAlsoUpdatesSuperView)
+                            {
+                                view._addingViewSoCanFocusAlsoUpdatesSuperView = true;
+                            }
+
+                            // Restore the old CanFocus and TabIndex to the values they held before CanFocus was set to false
+                            view.CanFocus = view._oldCanFocus;
+                            view._tabIndex = view._oldTabIndex;
+                            view._addingViewSoCanFocusAlsoUpdatesSuperView = false;
+                        }
+                    }
+                }
+
+                if (this is Toplevel && Application.Current!.Focused != this)
+                {
+                    ApplicationOverlapped.BringOverlappedTopToFront ();
+                }
+            }
+
+            OnCanFocusChanged ();
+            SetNeedsDisplay ();
+        }
+    }
+
+    /// <summary>Raised when <see cref="CanFocus"/> has been changed.</summary>
+    /// <remarks>
+    ///     Raised by the <see cref="OnCanFocusChanged"/> virtual method.
+    /// </remarks>
+    public event EventHandler CanFocusChanged;
+
+    /// <summary>Raised when the view is gaining (entering) focus. Can be cancelled.</summary>
+    /// <remarks>
+    ///     Raised by the <see cref="OnEnter"/> virtual method.
+    /// </remarks>
+    public event EventHandler<FocusEventArgs> Enter;
+
+    /// <summary>Returns the currently focused Subview inside this view, or <see langword="null"/> if nothing is focused.</summary>
+    /// <value>The currently focused Subview.</value>
+    public View Focused { get; private set; }
+
+    /// <summary>
+    ///     Focuses the first focusable view in <see cref="View.TabIndexes"/> if one exists. If there are no views in
+    ///     <see cref="View.TabIndexes"/> then the focus is set to the view itself.
+    /// </summary>
+    /// <param name="behavior"></param>
+    public void FocusFirst (TabBehavior? behavior)
+    {
+        if (!CanBeVisible (this))
+        {
+            return;
+        }
+
+        if (_tabIndexes is null)
+        {
+            SuperView?.SetFocus (this);
+
+            return;
+        }
+
+        var indicies = GetScopedTabIndexes (behavior, NavigationDirection.Forward);
+        if (indicies.Length > 0)
+        {
+            SetFocus (indicies [0]);
+        }
+    }
+
+    /// <summary>
+    ///     Focuses the last focusable view in <see cref="View.TabIndexes"/> if one exists. If there are no views in
+    ///     <see cref="View.TabIndexes"/> then the focus is set to the view itself.
+    /// </summary>
+    /// <param name="behavior"></param>
+    public void FocusLast (TabBehavior? behavior)
+    {
+        if (!CanBeVisible (this))
+        {
+            return;
+        }
+
+        if (_tabIndexes is null)
+        {
+            SuperView?.SetFocus (this);
+
+            return;
+        }
+
+        var indicies = GetScopedTabIndexes (behavior, NavigationDirection.Forward);
+        if (indicies.Length > 0)
+        {
+            SetFocus (indicies [^1]);
+        }
+    }
+
+    /// <summary>
+    ///     Gets or sets whether this view has focus.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Causes the <see cref="OnEnter"/> and <see cref="OnLeave"/> virtual methods (and <see cref="Enter"/> and
+    ///         <see cref="Leave"/> events to be raised) when the value changes.
+    ///     </para>
+    ///     <para>
+    ///         Setting this property to <see langword="false"/> will recursively set <see cref="HasFocus"/> to
+    ///         <see langword="false"/>
+    ///         for any focused subviews.
+    ///     </para>
+    /// </remarks>
+    public bool HasFocus
+    {
+        // Force the specified view to have focus
+        set => SetHasFocus (value, this, true);
+        get => _hasFocus;
+    }
+
+    /// <summary>Returns a value indicating if this View is currently on Top (Active)</summary>
+    public bool IsCurrentTop => Application.Current == this;
+
+    /// <summary>Raised when the view is losing (leaving) focus. Can be cancelled.</summary>
+    /// <remarks>
+    ///     Raised by the <see cref="OnLeave"/> virtual method.
+    /// </remarks>
+    public event EventHandler<FocusEventArgs> Leave;
+
+    /// <summary>
+    ///     Returns the most focused Subview in the chain of subviews (the leaf view that has the focus), or
+    ///     <see langword="null"/> if nothing is focused.
+    /// </summary>
+    /// <value>The most focused Subview.</value>
+    public View MostFocused
+    {
+        get
+        {
+            if (Focused is null)
+            {
+                return null;
+            }
+
+            View most = Focused.MostFocused;
+
+            if (most is { })
+            {
+                return most;
+            }
+
+            return Focused;
+        }
+    }
+
+    /// <summary>Invoked when the <see cref="CanFocus"/> property from a view is changed.</summary>
+    /// <remarks>
+    ///     Raises the <see cref="CanFocusChanged"/> event.
+    /// </remarks>
+    public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); }
+
+    // BUGBUG: The focus API is poorly defined and implemented. It deeply intertwines the view hierarchy with the tab order.
+
+    /// <summary>Invoked when this view is gaining focus (entering).</summary>
+    /// <param name="leavingView">The view that is leaving focus.</param>
+    /// <returns> <see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
+    /// <remarks>
+    ///     <para>
+    ///         Overrides must call the base class method to ensure that the <see cref="Enter"/> event is raised. If the event
+    ///         is handled, the method should return <see langword="true"/>.
+    ///     </para>
+    /// </remarks>
+    public virtual bool OnEnter (View leavingView)
+    {
+        var args = new FocusEventArgs (leavingView, this);
+        Enter?.Invoke (this, args);
+
+        if (args.Handled)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>Invoked when this view is losing focus (leaving).</summary>
+    /// <param name="enteringView">The view that is entering focus.</param>
+    /// <returns> <see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
+    /// <remarks>
+    ///     <para>
+    ///         Overrides must call the base class method to ensure that the <see cref="Leave"/> event is raised. If the event
+    ///         is handled, the method should return <see langword="true"/>.
+    ///     </para>
+    /// </remarks>
+    public virtual bool OnLeave (View enteringView)
+    {
+        // BUGBUG: _hasFocus should ALWAYS be false when this method is called. 
+        if (_hasFocus)
+        {
+            Debug.WriteLine ($"BUGBUG: HasFocus should ALWAYS be false when OnLeave is called.");
+            return true;
+        }
+        var args = new FocusEventArgs (this, enteringView);
+        Leave?.Invoke (this, args);
+
+        if (args.Handled)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Causes this view to be focused. All focusable views up the Superview hierarchy will also be focused.
+    /// </summary>
+    public void SetFocus ()
+    {
+        if (!CanBeVisible (this) || !Enabled)
+        {
+            if (HasFocus)
+            {
+                // If this view is focused, make it leave focus
+                SetHasFocus (false, this);
+            }
+
+            return;
+        }
+
+        // Recursively set focus upwards in the view hierarchy
+        if (SuperView is { })
+        {
+            SuperView.SetFocus (this);
+        }
+        else
+        {
+            SetFocus (this);
+        }
+    }
+
+    /// <summary>
+    ///     INTERNAL API that gets or sets the focus direction for this view and all subviews.
+    ///     Setting this property will set the focus direction for all views up the SuperView hierarchy.
+    /// </summary>
+    internal NavigationDirection FocusDirection
+    {
+        get => SuperView?.FocusDirection ?? _focusDirection;
+        set
+        {
+            if (SuperView is { })
+            {
+                SuperView.FocusDirection = value;
+            }
+            else
+            {
+                _focusDirection = value;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     INTERNAL helper for calling <see cref="FocusFirst"/> or <see cref="FocusLast"/> based on
+    ///     <see cref="FocusDirection"/>.
+    ///     FocusDirection is not public. This API is thus non-deterministic from a public API perspective.
+    /// </summary>
+    internal void RestoreFocus ()
+    {
+        if (Focused is null && _subviews?.Count > 0)
+        {
+            if (FocusDirection == NavigationDirection.Forward)
+            {
+                FocusFirst (null);
+            }
+            else
+            {
+                FocusLast (null);
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Internal API that causes <paramref name="viewToEnterFocus"/> to enter focus.
+    ///     <paramref name="viewToEnterFocus"/> does not need to be a subview.
+    ///     Recursively sets focus upwards in the view hierarchy.
+    /// </summary>
+    /// <param name="viewToEnterFocus"></param>
+    private void SetFocus (View viewToEnterFocus)
+    {
+        if (viewToEnterFocus is null)
+        {
+            return;
+        }
+
+        if (!viewToEnterFocus.CanFocus || !viewToEnterFocus.Visible || !viewToEnterFocus.Enabled)
+        {
+            return;
+        }
+
+        // If viewToEnterFocus is already the focused view, don't do anything
+        if (Focused?._hasFocus == true && Focused == viewToEnterFocus)
+        {
+            return;
+        }
+
+        // If a subview has focus and viewToEnterFocus is the focused view's superview OR viewToEnterFocus is this view,
+        // then make viewToEnterFocus.HasFocus = true and return
+        if ((Focused?._hasFocus == true && Focused?.SuperView == viewToEnterFocus) || viewToEnterFocus == this)
+        {
+            if (!viewToEnterFocus._hasFocus)
+            {
+                viewToEnterFocus._hasFocus = true;
+            }
+
+            return;
+        }
+
+        // Make sure that viewToEnterFocus is a subview of this view
+        View c;
+
+        for (c = viewToEnterFocus._superView; c != null; c = c._superView)
+        {
+            if (c == this)
+            {
+                break;
+            }
+        }
+
+        if (c is null)
+        {
+            throw new ArgumentException (@$"The specified view {viewToEnterFocus} is not part of the hierarchy of {this}.");
+        }
+
+        // If a subview has focus, make it leave focus
+        Focused?.SetHasFocus (false, viewToEnterFocus);
+
+        // make viewToEnterFocus Focused and enter focus
+        View f = Focused;
+        Focused = viewToEnterFocus;
+        Focused.SetHasFocus (true, f);
+
+        // Ensure on either the first or last focusable subview of Focused
+        // BUGBUG: With Groups, this means the previous focus is lost
+        Focused.RestoreFocus ();
+
+        // Recursively set focus upwards in the view hierarchy
+        if (SuperView is { })
+        {
+            SuperView.SetFocus (this);
+        }
+        else
+        {
+            // If there is no SuperView, then this is a top-level view
+            SetFocus (this);
+
+        }
+
+        // TODO: Temporary hack to make Application.Navigation.FocusChanged work
+        if (HasFocus && Focused.Focused is null)
+        {
+            Application.Navigation?.SetFocused (Focused);
+        }
+
+        // TODO: This is a temporary hack to make overlapped non-Toplevels have a zorder. See also: View.OnDrawContent.
+        if (viewToEnterFocus is { } && (viewToEnterFocus.TabStop == TabBehavior.TabGroup && viewToEnterFocus.Arrangement.HasFlag (ViewArrangement.Overlapped)))
+        {
+            viewToEnterFocus.TabIndex = 0;
+        }
+
+    }
+
+    /// <summary>
+    ///     Internal API that sets <see cref="HasFocus"/>. This method is called by <c>HasFocus_set</c> and other methods that
+    ///     need to set or remove focus from a view.
+    /// </summary>
+    /// <param name="newHasFocus">The new setting for <see cref="HasFocus"/>.</param>
+    /// <param name="view">The view that will be gaining or losing focus.</param>
+    /// <param name="force">
+    ///     <see langword="true"/> to force Enter/Leave on <paramref name="view"/> regardless of whether it
+    ///     already HasFocus or not.
+    /// </param>
+    /// <remarks>
+    ///     If <paramref name="newHasFocus"/> is <see langword="false"/> and there is a focused subview (<see cref="Focused"/>
+    ///     is not <see langword="null"/>),
+    ///     this method will recursively remove focus from any focused subviews of <see cref="Focused"/>.
+    /// </remarks>
+    private void SetHasFocus (bool newHasFocus, View view, bool force = false)
+    {
+        if (HasFocus != newHasFocus || force)
+        {
+            _hasFocus = newHasFocus;
+
+            if (newHasFocus)
+            {
+                OnEnter (view);
+            }
+            else
+            {
+                OnLeave (view);
+            }
+
+            SetNeedsDisplay ();
+        }
+
+        // Remove focus down the chain of subviews if focus is removed
+        if (!newHasFocus && Focused is { })
+        {
+            View f = Focused;
+            f.OnLeave (view);
+            f.SetHasFocus (false, view);
+            Focused = null;
+        }
+    }
+
+    #region Tab/Focus Handling
+
+#nullable enable
+
+    private List<View> _tabIndexes;
+
+    // TODO: This should be a get-only property?
+    // BUGBUG: This returns an AsReadOnly list, but isn't declared as such.
+    /// <summary>Gets a list of the subviews that are a <see cref="TabStop"/>.</summary>
+    /// <value>The tabIndexes.</value>
+    public IList<View> TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty;
+
+    /// <summary>
+    /// Gets TabIndexes that are scoped to the specified behavior and direction. If behavior is null, all TabIndexes are returned.
+    /// </summary>
+    /// <param name="behavior"></param>
+    /// <param name="direction"></param>
+    /// <returns></returns>GetScopedTabIndexes
+    private View [] GetScopedTabIndexes (TabBehavior? behavior, NavigationDirection direction)
+    {
+        IEnumerable<View> indicies;
+
+        if (behavior.HasValue)
+        {
+            indicies = _tabIndexes.Where (v => v.TabStop == behavior && v is { CanFocus: true, Visible: true, Enabled: true });
+        }
+        else
+        {
+            indicies = _tabIndexes.Where (v => v is { CanFocus: true, Visible: true, Enabled: true });
+        }
+
+        if (direction == NavigationDirection.Backward)
+        {
+            indicies = indicies.Reverse ();
+        }
+
+        return indicies.ToArray ();
+
+    }
+
+    private int? _tabIndex; // null indicates the view has not yet been added to TabIndexes
+    private int? _oldTabIndex;
+
+    /// <summary>
+    ///     Indicates the order of the current <see cref="View"/> in <see cref="TabIndexes"/> list.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If <see langword="null"/>, the view is not part of the tab order.
+    ///     </para>
+    ///     <para>
+    ///         On set, if <see cref="SuperView"/> is <see langword="null"/> or has not TabStops, <see cref="TabIndex"/> will
+    ///         be set to 0.
+    ///     </para>
+    ///     <para>
+    ///         On set, if <see cref="SuperView"/> has only one TabStop, <see cref="TabIndex"/> will be set to 0.
+    ///     </para>
+    ///     <para>
+    ///         See also <seealso cref="TabStop"/>.
+    ///     </para>
+    /// </remarks>
+    public int? TabIndex
+    {
+        get => _tabIndex;
+
+        // TOOD: This should be a get-only property. Introduce SetTabIndex (int value) (or similar).
+        set
+        {
+            // Once a view is in the tab order, it should not be removed from the tab order; set TabStop to NoStop instead.
+            Debug.Assert (value >= 0);
+            Debug.Assert (value is { });
+
+            if (SuperView?._tabIndexes is null || SuperView?._tabIndexes.Count == 1)
+            {
+                // BUGBUG: Property setters should set the property to the value passed in and not have side effects.
+                _tabIndex = 0;
+
+                return;
+            }
+
+            if (_tabIndex == value && TabIndexes.IndexOf (this) == value)
+            {
+                return;
+            }
+
+            _tabIndex = value > SuperView!.TabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 :
+                        value < 0 ? 0 : value;
+            _tabIndex = GetGreatestTabIndexInSuperView ((int)_tabIndex);
+
+            if (SuperView._tabIndexes.IndexOf (this) != _tabIndex)
+            {
+                // BUGBUG: we have to use _tabIndexes and not TabIndexes because TabIndexes returns is a read-only version of _tabIndexes
+                SuperView._tabIndexes.Remove (this);
+                SuperView._tabIndexes.Insert ((int)_tabIndex, this);
+                ReorderSuperViewTabIndexes ();
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Gets the greatest <see cref="TabIndex"/> of the <see cref="SuperView"/>'s <see cref="TabIndexes"/> that is less
+    ///     than or equal to <paramref name="idx"/>.
+    /// </summary>
+    /// <param name="idx"></param>
+    /// <returns>The minimum of <paramref name="idx"/> and the <see cref="SuperView"/>'s <see cref="TabIndexes"/>.</returns>
+    private int GetGreatestTabIndexInSuperView (int idx)
+    {
+        if (SuperView is null)
+        {
+            return 0;
+        }
+
+        var i = 0;
+
+        foreach (View superViewTabStop in SuperView._tabIndexes)
+        {
+            if (superViewTabStop._tabIndex is null || superViewTabStop == this)
+            {
+                continue;
+            }
+
+            i++;
+        }
+
+        return Math.Min (i, idx);
+    }
+
+    /// <summary>
+    ///     Re-orders the <see cref="TabIndex"/>s of the views in the <see cref="SuperView"/>'s <see cref="TabIndexes"/>.
+    /// </summary>
+    private void ReorderSuperViewTabIndexes ()
+    {
+        if (SuperView is null)
+        {
+            return;
+        }
+
+        var i = 0;
+
+        foreach (View superViewTabStop in SuperView._tabIndexes)
+        {
+            if (superViewTabStop._tabIndex is null)
+            {
+                continue;
+            }
+
+            superViewTabStop._tabIndex = i;
+            i++;
+        }
+    }
+
+    private TabBehavior? _tabStop;
+
+    /// <summary>
+    ///     Gets or sets the behavior of <see cref="AdvanceFocus"/> for keyboard navigation.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If <see langword="null"/> the tab stop has not been set and setting <see cref="CanFocus"/> to true will set it
+    ///         to
+    ///         <see cref="TabBehavior.TabStop"/>.
+    ///     </para>
+    ///     <para>
+    ///         TabStop is independent of <see cref="CanFocus"/>. If <see cref="CanFocus"/> is <see langword="false"/>, the
+    ///         view will not gain
+    ///         focus even if this property is set and vice-versa.
+    ///     </para>
+    ///     <para>
+    ///         The default <see cref="TabBehavior.TabStop"/> keys are <see cref="Application.NextTabKey"/> (<c>Key.Tab</c>) and <see cref="Application.PrevTabKey"/> (<c>Key>Tab.WithShift</c>).
+    ///     </para>
+    ///     <para>
+    ///         The default <see cref="TabBehavior.TabGroup"/> keys are <see cref="Application.NextTabGroupKey"/> (<c>Key.F6</c>) and <see cref="Application.PrevTabGroupKey"/> (<c>Key>Key.F6.WithShift</c>).
+    ///     </para>
+    /// </remarks>
+    public TabBehavior? TabStop
+    {
+        get => _tabStop;
+        set
+        {
+            if (_tabStop == value)
+            {
+                return;
+            }
+
+            Debug.Assert (value is { });
+
+            if (_tabStop is null && TabIndex is null)
+            {
+                // This view has not yet been added to TabIndexes (TabStop has not been set previously).
+                TabIndex = GetGreatestTabIndexInSuperView (SuperView is { } ? SuperView._tabIndexes.Count : 0);
+            }
+
+            _tabStop = value;
+        }
+    }
+
+    #endregion Tab/Focus Handling
+}

+ 74 - 67
Terminal.Gui/View/ViewText.cs → Terminal.Gui/View/View.Text.cs

@@ -2,19 +2,16 @@
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Text Property APIs
 {
+    private string _text;
+
     /// <summary>
-    ///    Initializes the Text of the View. Called by the constructor.
+    ///     Called when the <see cref="Text"/> has changed. Fires the <see cref="TextChanged"/> event.
     /// </summary>
-    private void SetupText ()
-    {
-        Text = string.Empty;
-        TextDirection = TextDirection.LeftRight_TopBottom;
-    }
-
-    private string _text;
+    public void OnTextChanged () { TextChanged?.Invoke (this, EventArgs.Empty); }
 
+    // TODO: Make this non-virtual. Nobody overrides it.
     /// <summary>
     ///     Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
     ///     or not when <see cref="TextFormatter.WordWrap"/> is enabled.
@@ -46,11 +43,14 @@ public partial class View
     ///         to <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
     ///     </para>
     ///     <para>
-    ///         The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="GetContentSize ()"/>'s height
+    ///         The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="GetContentSize ()"/>
+    ///         's height
     ///         is 1, the text will be clipped.
     ///     </para>
-    ///     <para>If <see cref="View.Width"/> or <see cref="View.Height"/> are using <see cref="DimAutoStyle.Text"/>,
-    ///     the <see cref="GetContentSize ()"/> will be adjusted to fit the text.</para>
+    ///     <para>
+    ///         If <see cref="View.Width"/> or <see cref="View.Height"/> are using <see cref="DimAutoStyle.Text"/>,
+    ///         the <see cref="GetContentSize ()"/> will be adjusted to fit the text.
+    ///     </para>
     ///     <para>When the text changes, the <see cref="TextChanged"/> is fired.</para>
     /// </remarks>
     public virtual string Text
@@ -73,25 +73,16 @@ public partial class View
         }
     }
 
-    /// <summary>
-    /// Called when the <see cref="Text"/> has changed. Fires the <see cref="TextChanged"/> event.
-    /// </summary>
-    public void OnTextChanged ()
-    {
-        TextChanged?.Invoke (this, EventArgs.Empty);
-    }
-
-    /// <summary>
-    ///     Text changed event, raised when the text has changed.
-    /// </summary>
-    public event EventHandler? TextChanged;
-
+    // TODO: Make this non-virtual. Nobody overrides it.
     /// <summary>
     ///     Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will
     ///     redisplay the <see cref="View"/>.
     /// </summary>
     /// <remarks>
-    ///     <para> <see cref="View.Width"/> or <see cref="View.Height"/> are using <see cref="DimAutoStyle.Text"/>, the <see cref="GetContentSize ()"/> will be adjusted to fit the text.</para>
+    ///     <para>
+    ///         <see cref="View.Width"/> or <see cref="View.Height"/> are using <see cref="DimAutoStyle.Text"/>, the
+    ///         <see cref="GetContentSize ()"/> will be adjusted to fit the text.
+    ///     </para>
     /// </remarks>
     /// <value>The text alignment.</value>
     public virtual Alignment TextAlignment
@@ -105,36 +96,45 @@ public partial class View
         }
     }
 
+    /// <summary>
+    ///     Text changed event, raised when the text has changed.
+    /// </summary>
+    public event EventHandler? TextChanged;
+
+    // TODO: Make this non-virtual. Nobody overrides it.
     /// <summary>
     ///     Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the
     ///     <see cref="View"/>.
     /// </summary>
     /// <remarks>
-    ///     <para> <see cref="View.Width"/> or <see cref="View.Height"/> are using <see cref="DimAutoStyle.Text"/>, the <see cref="GetContentSize ()"/> will be adjusted to fit the text.</para>
+    ///     <para>
+    ///         <see cref="View.Width"/> or <see cref="View.Height"/> are using <see cref="DimAutoStyle.Text"/>, the
+    ///         <see cref="GetContentSize ()"/> will be adjusted to fit the text.
+    ///     </para>
     /// </remarks>
     /// <value>The text direction.</value>
     public virtual TextDirection TextDirection
     {
         get => TextFormatter.Direction;
-        set
-        {
-            UpdateTextDirection (value);
-            TextFormatter.Direction = value;
-        }
+        set => UpdateTextDirection (value);
     }
 
     /// <summary>
     ///     Gets or sets the <see cref="Gui.TextFormatter"/> used to format <see cref="Text"/>.
     /// </summary>
-    public TextFormatter TextFormatter { get; init; } = new () { };
+    public TextFormatter TextFormatter { get; init; } = new ();
 
+    // TODO: Make this non-virtual. Nobody overrides it.
     /// <summary>
     ///     Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will
     ///     redisplay
     ///     the <see cref="View"/>.
     /// </summary>
     /// <remarks>
-    ///     <para> <see cref="View.Width"/> or <see cref="View.Height"/> are using <see cref="DimAutoStyle.Text"/>, the <see cref="GetContentSize ()"/> will be adjusted to fit the text.</para>
+    ///     <para>
+    ///         <see cref="View.Width"/> or <see cref="View.Height"/> are using <see cref="DimAutoStyle.Text"/>, the
+    ///         <see cref="GetContentSize ()"/> will be adjusted to fit the text.
+    ///     </para>
     /// </remarks>
     /// <value>The vertical text alignment.</value>
     public virtual Alignment VerticalTextAlignment
@@ -147,35 +147,26 @@ public partial class View
         }
     }
 
+    // TODO: Add a OnUpdateTextFormatterText method that invokes UpdateTextFormatterText so that overrides don't have to call base.
     /// <summary>
-    ///     Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
+    ///     Can be overridden if the <see cref="TextFormatter.Text"/> has
     ///     different format than the default.
     /// </summary>
+    /// <remarks>
+    ///     Overrides must call <c>base.UpdateTextFormatterText</c> before updating <see cref="TextFormatter.Text"/>.
+    /// </remarks>
     protected virtual void UpdateTextFormatterText ()
     {
-        if (TextFormatter is { })
-        {
-            TextFormatter.Text = _text;
-        }
-    }
-
-    /// <summary>
-    ///     Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="TextFormatter.HotKeySpecifier"/>.
-    /// </summary>
-    /// <returns></returns>
-    internal Size GetSizeNeededForTextWithoutHotKey ()
-    {
-        return new Size (
-                         TextFormatter.Size.Width - TextFormatter.GetHotKeySpecifierLength (),
-                         TextFormatter.Size.Height - TextFormatter.GetHotKeySpecifierLength (false));
+        TextFormatter.Text = _text;
+        TextFormatter.ConstrainToWidth = null;
+        TextFormatter.ConstrainToHeight = null;
     }
 
     /// <summary>
-    ///     Internal API. Sets <see cref="TextFormatter"/>.Size to the current <see cref="Viewport"/> size, adjusted for
-    ///     <see cref="TextFormatter.HotKeySpecifier"/>.
+    ///     Internal API. Sets <see cref="TextFormatter"/>.Width/Height.
     /// </summary>
     /// <remarks>
-    ///     Use this API to set <see cref="TextFormatter.Size"/> when the view has changed such that the
+    ///     Use this API to set <see cref="Gui.TextFormatter.ConstrainToWidth"/>/Height when the view has changed such that the
     ///     size required to fit the text has changed.
     ///     changes.
     /// </remarks>
@@ -187,29 +178,44 @@ public partial class View
         UpdateTextFormatterText ();
 
         // Default is to use GetContentSize ().
-        var size = GetContentSize ();
+        Size? size = _contentSize;
 
-        // TODO: This is a hack. Figure out how to move this into DimDimAuto
         // Use _width & _height instead of Width & Height to avoid debug spew
-        DimAuto? widthAuto = _width as DimAuto;
-        DimAuto? heightAuto = _height as DimAuto;
-        if ((widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text))
-            || (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)))
-        {
-            size = TextFormatter.GetAutoSize ();
+        var widthAuto = _width as DimAuto;
+        var heightAuto = _height as DimAuto;
 
-            if (widthAuto is null || !widthAuto.Style.FastHasFlags (DimAutoStyle.Text))
+        if (widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text))
+        {
+            TextFormatter.ConstrainToWidth = null;
+        }
+        else
+        {
+            if (size is { })
             {
-                size.Width = GetContentSize ().Width;
+                TextFormatter.ConstrainToWidth = size?.Width;
             }
+        }
 
-            if (heightAuto is null || !heightAuto.Style.FastHasFlags (DimAutoStyle.Text))
+        if (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text))
+        {
+            TextFormatter.ConstrainToHeight = null;
+        }
+        else
+        {
+            if (size is { })
             {
-                size.Height = GetContentSize ().Height;
+                TextFormatter.ConstrainToHeight = size?.Height;
             }
         }
+    }
 
-        TextFormatter.Size = size;
+    /// <summary>
+    ///     Initializes the Text of the View. Called by the constructor.
+    /// </summary>
+    private void SetupText ()
+    {
+        Text = string.Empty;
+        TextDirection = TextDirection.LeftRight_TopBottom;
     }
 
     private void UpdateTextDirection (TextDirection newDirection)
@@ -221,10 +227,11 @@ public partial class View
 
         if (directionChanged)
         {
+            TextFormatter.ConstrainToWidth = null;
+            TextFormatter.ConstrainToHeight = null;
             OnResizeNeeded ();
         }
 
-        SetTextFormatterSize ();
         SetNeedsDisplay ();
     }
 }

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

@@ -184,10 +184,6 @@ public partial class View : Responder, ISupportInitializeNotification
 
         //SetupMouse ();
         SetupText ();
-
-        CanFocus = false;
-        TabIndex = -1;
-        TabStop = false;
     }
 
     /// <summary>
@@ -268,7 +264,7 @@ public partial class View : Responder, ISupportInitializeNotification
 
         EndInitAdornments ();
 
-        // TODO: Move these into ViewText.cs as EndInit_Text() to consolodate.
+        // TODO: Move these into ViewText.cs as EndInit_Text() to consolidate.
         // TODO: Verify UpdateTextDirection really needs to be called here.
         // These calls were moved from BeginInit as they access Viewport which is indeterminate until EndInit is called.
         UpdateTextDirection (TextDirection);
@@ -332,7 +328,7 @@ public partial class View : Responder, ISupportInitializeNotification
                 else
                 {
                     view.Enabled = view._oldEnabled;
-                    view._addingView = _enabled;
+                    view._addingViewSoCanFocusAlsoUpdatesSuperView = _enabled;
                 }
             }
         }
@@ -479,7 +475,7 @@ public partial class View : Responder, ISupportInitializeNotification
 
     private void SetTitleTextFormatterSize ()
     {
-        TitleTextFormatter.Size = new (
+        TitleTextFormatter.ConstrainToSize = new (
                                        TextFormatter.GetWidestLineLength (TitleTextFormatter.Text)
                                        - (TitleTextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
                                               ? Math.Max (HotKeySpecifier.GetColumns (), 0)
@@ -490,7 +486,7 @@ public partial class View : Responder, ISupportInitializeNotification
     /// <summary>Called when the <see cref="View.Title"/> has been changed. Invokes the <see cref="TitleChanged"/> event.</summary>
     protected void OnTitleChanged ()
     {
-        TitleChanged?.Invoke (this, new (ref _title));
+        TitleChanged?.Invoke (this, new (in _title));
     }
 
     /// <summary>

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

@@ -1,14 +1,16 @@
 namespace Terminal.Gui;
 
 /// <summary>
-///     Describes what user actions are enabled for arranging a <see cref="View"/> within it's <see cref="View.SuperView"/>.
+///     Describes what user actions are enabled for arranging a <see cref="View"/> within it's <see cref="View.SuperView"/>
+///     .
 ///     See <see cref="View.Arrangement"/>.
 /// </summary>
 /// <remarks>
-/// <para>
-///     Sizing or moving a view is only possible if the <see cref="View"/> is part of a <see cref="View.SuperView"/> and
-///     the relevant position and dimensions of the <see cref="View"/> are independent of other SubViews
-/// </para>
+///     <para>
+///         Sizing or moving a view is only possible if the <see cref="View"/> is part of a <see cref="View.SuperView"/>
+///         and
+///         the relevant position and dimensions of the <see cref="View"/> are independent of other SubViews
+///     </para>
 /// </remarks>
 [Flags]
 public enum ViewArrangement
@@ -53,18 +55,17 @@ public enum ViewArrangement
     /// <remarks>
     ///     If <see cref="Movable"/> is also set, the top will not be resizable.
     /// </remarks>
-    Resizable = LeftResizable | RightResizable | TopResizable | BottomResizable
-}
-public partial class View
-{
+    Resizable = LeftResizable | RightResizable | TopResizable | BottomResizable,
+
     /// <summary>
-    ///    Gets or sets the user actions that are enabled for the view within it's <see cref="SuperView"/>.
+    ///     The view overlap other views.
     /// </summary>
     /// <remarks>
-    /// <para>
-    ///     Sizing or moving a view is only possible if the <see cref="View"/> is part of a <see cref="SuperView"/> and
-    ///     the relevant position and dimensions of the <see cref="View"/> are independent of other SubViews
-    /// </para>
+    ///     <para>
+    ///         When set, Tab and Shift-Tab will be constrained to the subviews of the view (normally, they will navigate to
+    ///         the next/prev view in the next/prev Tabindex).
+    ///         Use Ctrl-Tab (Ctrl-PageDown) / Ctrl-Shift-Tab (Ctrl-PageUp) to move between overlapped views.
+    ///     </para>
     /// </remarks>
-    public ViewArrangement Arrangement { get; set; }
+    Overlapped = 32,
 }

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