Browse Source

Merged.
Feature complete

Tig 1 year ago
parent
commit
126120b6dc
100 changed files with 5202 additions and 4422 deletions
  1. 1 1
      CommunityToolkitExample/Program.cs
  2. 2 2
      SelfContained/Program.cs
  3. 29 0
      Terminal.Gui/Application/Application.Driver.cs
  4. 214 0
      Terminal.Gui/Application/Application.Initialization.cs
  5. 467 0
      Terminal.Gui/Application/Application.Keyboard.cs
  6. 28 28
      Terminal.Gui/Application/Application.Mouse.cs
  7. 10 0
      Terminal.Gui/Application/Application.Navigation.cs
  8. 883 0
      Terminal.Gui/Application/Application.Run.cs
  9. 53 0
      Terminal.Gui/Application/Application.Screen.cs
  10. 56 0
      Terminal.Gui/Application/Application.Toplevel.cs
  11. 84 1402
      Terminal.Gui/Application/Application.cs
  12. 0 303
      Terminal.Gui/Application/ApplicationKeyboard.cs
  13. 229 0
      Terminal.Gui/Application/ApplicationNavigation.cs
  14. 444 0
      Terminal.Gui/Application/ApplicationOverlapped.cs
  15. 1 1
      Terminal.Gui/Application/MainLoopSyncContext.cs
  16. 11 22
      Terminal.Gui/Clipboard/Clipboard.cs
  17. 114 144
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  18. 44 0
      Terminal.Gui/ConsoleDrivers/CursorVisibility.cs
  19. 9 9
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  20. 3 3
      Terminal.Gui/Drawing/LineCanvas.cs
  21. 4 4
      Terminal.Gui/Drawing/Ruler.cs
  22. 9 5
      Terminal.Gui/Drawing/Thickness.cs
  23. 3 0
      Terminal.Gui/Input/Command.cs
  24. 16 0
      Terminal.Gui/Input/KeyBinding.cs
  25. 1 1
      Terminal.Gui/Input/KeyBindingScope.cs
  26. 174 43
      Terminal.Gui/Input/KeyBindings.cs
  27. 4 2
      Terminal.Gui/Resources/config.json
  28. 2 2
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs
  29. 3 3
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  30. 6 5
      Terminal.Gui/View/Adornment/Border.cs
  31. 2 2
      Terminal.Gui/View/Adornment/Margin.cs
  32. 3 3
      Terminal.Gui/View/Adornment/ShadowView.cs
  33. 29 0
      Terminal.Gui/View/DrawEventArgs.cs
  34. 1 1
      Terminal.Gui/View/EventArgs.cs
  35. 4 4
      Terminal.Gui/View/Layout/Dim.cs
  36. 5 5
      Terminal.Gui/View/Layout/DimView.cs
  37. 12 0
      Terminal.Gui/View/Layout/LayoutEventArgs.cs
  38. 8 8
      Terminal.Gui/View/Layout/Pos.cs
  39. 7 7
      Terminal.Gui/View/Layout/PosView.cs
  40. 27 0
      Terminal.Gui/View/Navigation/FocusEventArgs.cs
  41. 22 0
      Terminal.Gui/View/Navigation/TabBehavior.cs
  42. 13 0
      Terminal.Gui/View/NavigationDirection.cs
  43. 1 1
      Terminal.Gui/View/View.Adornments.cs
  44. 15 0
      Terminal.Gui/View/View.Arrangement.cs
  45. 0 0
      Terminal.Gui/View/View.Content.cs
  46. 35 0
      Terminal.Gui/View/View.Cursor.cs
  47. 0 0
      Terminal.Gui/View/View.Diagnostics.cs
  48. 24 9
      Terminal.Gui/View/View.Drawing.cs
  49. 323 0
      Terminal.Gui/View/View.Hierarchy.cs
  50. 37 127
      Terminal.Gui/View/View.Keyboard.cs
  51. 7 7
      Terminal.Gui/View/View.Layout.cs
  52. 1 1
      Terminal.Gui/View/View.Mouse.cs
  53. 875 0
      Terminal.Gui/View/View.Navigation.cs
  54. 1 1
      Terminal.Gui/View/View.Text.cs
  55. 2 6
      Terminal.Gui/View/View.cs
  56. 16 15
      Terminal.Gui/View/ViewArrangement.cs
  57. 1 66
      Terminal.Gui/View/ViewEventArgs.cs
  58. 0 900
      Terminal.Gui/View/ViewSubViews.cs
  59. 12 12
      Terminal.Gui/Views/ComboBox.cs
  60. 13 13
      Terminal.Gui/Views/DateField.cs
  61. 1 0
      Terminal.Gui/Views/DatePicker.cs
  62. 16 11
      Terminal.Gui/Views/FileDialog.cs
  63. 2 0
      Terminal.Gui/Views/FrameView.cs
  64. 3 3
      Terminal.Gui/Views/GraphView/Annotations.cs
  65. 7 7
      Terminal.Gui/Views/GraphView/Axis.cs
  66. 2 2
      Terminal.Gui/Views/GraphView/Series.cs
  67. 3 0
      Terminal.Gui/Views/ListView.cs
  68. 3 0
      Terminal.Gui/Views/Menu/Menu.cs
  69. 4 2
      Terminal.Gui/Views/Menu/MenuBar.cs
  70. 1 0
      Terminal.Gui/Views/Menu/MenuBarItem.cs
  71. 0 2
      Terminal.Gui/Views/MenuBarv2.cs
  72. 51 103
      Terminal.Gui/Views/NumericUpDown.cs
  73. 19 25
      Terminal.Gui/Views/RadioGroup.cs
  74. 1 0
      Terminal.Gui/Views/ScrollView.cs
  75. 128 94
      Terminal.Gui/Views/Shortcut.cs
  76. 4 0
      Terminal.Gui/Views/Slider.cs
  77. 1 2
      Terminal.Gui/Views/StatusBar.cs
  78. 1 0
      Terminal.Gui/Views/Tab.cs
  79. 41 34
      Terminal.Gui/Views/TabView.cs
  80. 1 0
      Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs
  81. 1 1
      Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs
  82. 14 5
      Terminal.Gui/Views/TableView/TableView.cs
  83. 18 25
      Terminal.Gui/Views/TextField.cs
  84. 14 5
      Terminal.Gui/Views/TextValidateField.cs
  85. 40 82
      Terminal.Gui/Views/TextView.cs
  86. 1 1
      Terminal.Gui/Views/Tile.cs
  87. 2 2
      Terminal.Gui/Views/TileView.cs
  88. 13 13
      Terminal.Gui/Views/TimeField.cs
  89. 264 517
      Terminal.Gui/Views/Toplevel.cs
  90. 0 212
      Terminal.Gui/Views/ToplevelOverlapped.cs
  91. 1 1
      Terminal.Gui/Views/TreeView/TreeView.cs
  92. 2 0
      Terminal.Gui/Views/Window.cs
  93. 3 1
      Terminal.sln.DotSettings
  94. 7 1
      UICatalog/Scenarios/AdornmentEditor.cs
  95. 81 63
      UICatalog/Scenarios/AdornmentsEditor.cs
  96. 13 13
      UICatalog/Scenarios/BackgroundWorkerCollection.cs
  97. 16 4
      UICatalog/Scenarios/Buttons.cs
  98. 14 14
      UICatalog/Scenarios/CombiningMarks.cs
  99. 7 7
      UICatalog/Scenarios/Editor.cs
  100. 2 2
      UICatalog/Scenarios/Images.cs

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

+ 2 - 2
SelfContained/Program.cs

@@ -16,14 +16,14 @@ public static class Program
 
         #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)
+        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 (Application.SupportedCultures?.Count > 0);
             Debug.Assert (Equals (CultureInfo.CurrentCulture, Thread.CurrentThread.CurrentUICulture));
         }
 

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

+ 84 - 1402
Terminal.Gui/Application/Application.cs

@@ -1,5 +1,5 @@
+#nullable enable
 using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Reflection;
 using System.Resources;
@@ -11,42 +11,93 @@ 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 ()
     {
@@ -76,32 +127,6 @@ public static partial class Application
         return GetAvailableCulturesFromEmbeddedResources ();
     }
 
-    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 ();
-    }
-
-    /// <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);
-
-    // 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;
-
     // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
     // Encapsulate all setting of initial state for Application; Having
     // this in a function like this ensures we don't make mistakes in
@@ -112,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
 
@@ -140,7 +165,7 @@ public static partial class Application
         // MainLoop stuff
         MainLoop?.Dispose ();
         MainLoop = null;
-        _mainThreadId = -1;
+        MainThreadId = -1;
         Iteration = null;
         EndAfterFirstIteration = false;
 
@@ -164,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;
@@ -176,13 +201,13 @@ 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 ();
 
@@ -193,1348 +218,5 @@ public static partial class Application
         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 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 ();
-
-        // 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 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 && 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 (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 }) ?? Application.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 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: Technically, this is not the full lst of TopLevels. There be dragons here, 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);

+ 114 - 144
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -1,3 +1,4 @@
+#nullable enable
 //
 // ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations.
 //
@@ -16,7 +17,7 @@ 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>
@@ -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;
                 }
             }
         }
@@ -444,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>
@@ -550,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
@@ -564,7 +576,7 @@ 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>
@@ -575,7 +587,7 @@ public abstract class ConsoleDriver
     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>
@@ -592,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 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
-}
-
 /// <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.

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

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

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

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

@@ -336,7 +336,7 @@ public class LineCanvas : IDisposable
         return Fill != null ? Fill.GetAttribute (intersects [0]!.Point) : intersects [0]!.Line.Attribute;
     }
 
-    private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
+    private Cell? GetCellForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
     {
         if (!intersects.Any ())
         {
@@ -356,7 +356,7 @@ public class LineCanvas : IDisposable
         return cell;
     }
 
-    private Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
+    private Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
     {
         if (!intersects.Any ())
         {
@@ -679,7 +679,7 @@ public class LineCanvas : IDisposable
         internal Rune _thickV;
         public IntersectionRuneResolver () { SetGlyphs (); }
 
-        public Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
+        public Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
         {
             bool useRounded = intersects.Any (
                                               i => i?.Line.Length != 0

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

+ 9 - 5
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),
@@ -199,7 +199,11 @@ public record struct Thickness
                 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);

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

@@ -221,9 +221,12 @@ public enum Command
     /// <summary>Pastes the current selection.</summary>
     Paste,
 
+    /// TODO: IRunnable: Rename to Command.Quit to make more generic.
     /// <summary>Quit a <see cref="Toplevel"/>.</summary>
     QuitToplevel,
 
+    /// TODO: Overlapped: Add Command.ShowHide
+
     /// <summary>Suspend an application (Only implemented in <see cref="CursesDriver"/>).</summary>
     Suspend,
 

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

+ 4 - 2
Terminal.Gui/Resources/config.json

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

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

@@ -106,7 +106,7 @@ public class AppendAutocomplete : AutocompleteBase
         }
 
         // draw it like it's selected, even though it's not
-        Application.Driver.SetAttribute (
+        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>

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

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

+ 6 - 5
Terminal.Gui/View/Adornment/Border.cs

@@ -280,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))
         {
@@ -294,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.

+ 2 - 2
Terminal.Gui/View/Adornment/Margin.cs

@@ -226,12 +226,12 @@ public class Margin : Adornment
             {
                 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;
+                    _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;
+                    _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;
 

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

@@ -18,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 ();
             }
@@ -113,7 +113,7 @@ internal class ShadowView : View
         {
             Driver.Move (i, screen.Y);
 
-            if (i < Driver.Contents.GetLength (1) && screen.Y < Driver.Contents.GetLength (0))
+            if (i < Driver.Contents!.GetLength (1) && screen.Y < Driver.Contents.GetLength (0))
             {
                 Driver.AddRune (Driver.Contents [screen.Y, i].Rune);
             }
@@ -141,7 +141,7 @@ internal class ShadowView : View
         {
             Driver.Move (screen.X, i);
 
-            if (screen.X < Driver.Contents.GetLength (1) && i < Driver.Contents.GetLength (0))
+            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; }

+ 4 - 4
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>
@@ -168,7 +168,7 @@ 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
 
@@ -253,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;
     }
@@ -278,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;
     }

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

+ 8 - 8
Terminal.Gui/View/Layout/Pos.cs

@@ -251,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
@@ -274,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
@@ -282,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
 
@@ -373,7 +373,7 @@ public abstract class Pos
 
         if (left is PosView view)
         {
-            view.Target.SetNeedsLayout ();
+            view.Target?.SetNeedsLayout ();
         }
 
         return newPos;
@@ -402,7 +402,7 @@ public abstract class Pos
 
         if (left is PosView view)
         {
-            view.Target.SetNeedsLayout ();
+            view.Target?.SetNeedsLayout ();
         }
 
         return newPos;

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

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

+ 37 - 127
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
@@ -197,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);
             }
         }
@@ -250,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
@@ -641,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);
@@ -692,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;
+                }
             }
         }
 
@@ -710,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)
@@ -722,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;
+                    }
                 }
             }
 
@@ -800,11 +709,12 @@ 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 annoying vs. useful.

+ 7 - 7
Terminal.Gui/View/Layout/ViewLayout.cs → Terminal.Gui/View/View.Layout.cs

@@ -3,7 +3,7 @@ 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"/>.
@@ -52,7 +52,7 @@ public partial class View
             if (found is { })
             {
                 start = found;
-                viewportOffset = found.Parent.Frame.Location;
+                viewportOffset = found.Parent?.Frame.Location ?? Point.Empty;
             }
 
             int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X);
@@ -108,17 +108,17 @@ public partial class View
     ///     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 (
+    internal static View? GetLocationEnsuringFullVisibility (
         View viewToMove,
         int targetX,
         int targetY,
         out int nx,
         out int ny,
-        out StatusBar statusBar
+        out StatusBar? statusBar
     )
     {
         int maxDimension;
-        View superView;
+        View? superView;
         statusBar = null!;
 
         if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
@@ -839,7 +839,7 @@ public partial class View
                 //}
                 if (dv.Target != this)
                 {
-                    nEdges.Add ((dv.Target, from));
+                    nEdges.Add ((dv.Target!, from));
                 }
 
                 return;
@@ -872,7 +872,7 @@ public partial class View
                 //}
                 if (pv.Target != this)
                 {
-                    nEdges.Add ((pv.Target, from));
+                    nEdges.Add ((pv.Target!, from));
                 }
 
                 return;

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

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

@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Text Property APIs
 {
     private string _text;
 

+ 2 - 6
Terminal.Gui/View/View.cs

@@ -184,10 +184,6 @@ public partial class View : Responder, ISupportInitializeNotification
 
         //SetupMouse ();
         SetupText ();
-
-        CanFocus = false;
-        TabIndex = -1;
-        TabStop = false;
     }
 
     /// <summary>
@@ -332,7 +328,7 @@ public partial class View : Responder, ISupportInitializeNotification
                 else
                 {
                     view.Enabled = view._oldEnabled;
-                    view._addingView = _enabled;
+                    view._addingViewSoCanFocusAlsoUpdatesSuperView = _enabled;
                 }
             }
         }
@@ -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,
 }

+ 1 - 66
Terminal.Gui/View/ViewEventArgs.cs

@@ -13,69 +13,4 @@ public class ViewEventArgs : EventArgs
     ///     child then sender may be the parent while <see cref="View"/> is the child being added.
     /// </remarks>
     public View View { get; }
-}
-
-/// <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; }
-}
-
-/// <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; }
-}
-
-/// <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; }
-
-}
+}

+ 0 - 900
Terminal.Gui/View/ViewSubViews.cs

@@ -1,900 +0,0 @@
-using System.Diagnostics;
-
-namespace Terminal.Gui;
-
-public partial class View
-{
-    private static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
-    internal bool _addingView;
-    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>Returns a value indicating if this View is currently on Top (Active)</summary>
-    public bool IsCurrentTop => Application.Current == this;
-
-    /// <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 ();
-        }
-
-        _subviews.Add (view);
-        _tabIndexes.Add (view);
-        view._superView = this;
-
-        if (view.CanFocus)
-        {
-            _addingView = true;
-
-            if (SuperView?.CanFocus == false)
-            {
-                SuperView._addingView = true;
-                SuperView.CanFocus = true;
-                SuperView._addingView = 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);
-            _addingView = 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>Moves the subview backwards in the hierarchy, only one step</summary>
-    /// <param name="subview">The subview to send backwards</param>
-    /// <remarks>If you want to send the view all the way to the back use SendSubviewToBack.</remarks>
-    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>Brings the specified subview to the front so it is drawn on top of any other views.</summary>
-    /// <param name="subview">The subview to send to the front</param>
-    /// <remarks><seealso cref="SendSubviewToBack"/>.</remarks>
-    public void BringSubviewToFront (View subview)
-    {
-        PerformActionForSubview (
-                                 subview,
-                                 x =>
-                                 {
-                                     _subviews.Remove (x);
-                                     _subviews.Add (x);
-                                 }
-                                );
-    }
-
-    /// <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 the subview backwards in the hierarchy, only one step</summary>
-    /// <param name="subview">The subview to send backwards</param>
-    /// <remarks>If you want to send the view all the way to the back use SendSubviewToBack.</remarks>
-    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>Sends the specified subview to the front so it is the first view drawn</summary>
-    /// <param name="subview">The subview to send to the front</param>
-    /// <remarks><seealso cref="BringSubviewToFront(View)"/>.</remarks>
-    public void SendSubviewToBack (View subview)
-    {
-        PerformActionForSubview (
-                                 subview,
-                                 x =>
-                                 {
-                                     _subviews.Remove (x);
-                                     _subviews.Insert (0, subview);
-                                 }
-                                );
-    }
-
-    private void PerformActionForSubview (View subview, Action<View> action)
-    {
-        if (_subviews.Contains (subview))
-        {
-            action (subview);
-        }
-
-        SetNeedsDisplay ();
-        subview.SetNeedsDisplay ();
-    }
-
-    #region Focus
-
-    /// <summary>Exposed as `internal` for unit tests. Indicates focus navigation direction.</summary>
-    internal enum NavigationDirection
-    {
-        /// <summary>Navigate forward.</summary>
-        Forward,
-
-        /// <summary>Navigate backwards.</summary>
-        Backward
-    }
-
-    /// <summary>Event fired when the view gets focus.</summary>
-    public event EventHandler<FocusEventArgs> Enter;
-
-    /// <summary>Event fired when the view looses focus.</summary>
-    public event EventHandler<FocusEventArgs> Leave;
-
-    private NavigationDirection _focusDirection;
-
-    internal NavigationDirection FocusDirection
-    {
-        get => SuperView?.FocusDirection ?? _focusDirection;
-        set
-        {
-            if (SuperView is { })
-            {
-                SuperView.FocusDirection = value;
-            }
-            else
-            {
-                _focusDirection = value;
-            }
-        }
-    }
-
-    private bool _hasFocus;
-
-    /// <inheritdoc/>
-    public bool HasFocus
-    {
-        set => SetHasFocus (value, this, true);
-        get => _hasFocus;
-    }
-
-    private void SetHasFocus (bool value, View view, bool force = false)
-    {
-        if (HasFocus != value || force)
-        {
-            _hasFocus = value;
-
-            if (value)
-            {
-                OnEnter (view);
-            }
-            else
-            {
-                OnLeave (view);
-            }
-
-            SetNeedsDisplay ();
-        }
-
-        // Remove focus down the chain of subviews if focus is removed
-        if (!value && Focused is { })
-        {
-            View f = Focused;
-            f.OnLeave (view);
-            f.SetHasFocus (false, view);
-            Focused = null;
-        }
-    }
-
-    /// <summary>Event fired when the <see cref="CanFocus"/> value is being changed.</summary>
-    public event EventHandler CanFocusChanged;
-
-    /// <summary>Method invoked when the <see cref="CanFocus"/> property from a view is changed.</summary>
-    public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); }
-
-    private bool _oldCanFocus;
-    private bool _canFocus;
-
-    /// <summary>Gets or sets a value indicating whether this <see cref="View"/> can focus.</summary>
-    public bool CanFocus
-    {
-        get => _canFocus;
-        set
-        {
-            if (!_addingView && 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:
-                    TabIndex = -1;
-
-                    break;
-                case true when SuperView?.CanFocus == false && _addingView:
-                    SuperView.CanFocus = true;
-
-                    break;
-            }
-
-            if (_canFocus && _tabIndex == -1)
-            {
-                TabIndex = SuperView is { } ? SuperView._tabIndexes.IndexOf (this) : -1;
-            }
-
-            TabStop = _canFocus;
-
-            if (!_canFocus && SuperView?.Focused == this)
-            {
-                SuperView.Focused = null;
-            }
-
-            if (!_canFocus && HasFocus)
-            {
-                SetHasFocus (false, this);
-                SuperView?.EnsureFocus ();
-
-                if (SuperView is { Focused: null })
-                {
-                    SuperView.FocusNext ();
-
-                    if (SuperView.Focused is null && Application.Current is { })
-                    {
-                        Application.Current.FocusNext ();
-                    }
-
-                    Application.BringOverlappedTopToFront ();
-                }
-            }
-
-            if (_subviews is { } && IsInitialized)
-            {
-                foreach (View view in _subviews)
-                {
-                    if (view.CanFocus != value)
-                    {
-                        if (!value)
-                        {
-                            view._oldCanFocus = view.CanFocus;
-                            view._oldTabIndex = view._tabIndex;
-                            view.CanFocus = false;
-                            view._tabIndex = -1;
-                        }
-                        else
-                        {
-                            if (_addingView)
-                            {
-                                view._addingView = true;
-                            }
-
-                            view.CanFocus = view._oldCanFocus;
-                            view._tabIndex = view._oldTabIndex;
-                            view._addingView = false;
-                        }
-                    }
-                }
-
-                if (this is Toplevel && Application.Current.Focused != this)
-                {
-                    Application.BringOverlappedTopToFront ();
-                }
-            }
-
-            OnCanFocusChanged ();
-            SetNeedsDisplay ();
-        }
-    }
-
-    /// <summary>
-    /// Called when a view gets focus.
-    /// </summary>
-    /// <param name="view">The view that is losing focus.</param>
-    /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-    public virtual bool OnEnter (View view)
-    {
-        var args = new FocusEventArgs (view, this);
-        Enter?.Invoke (this, args);
-
-        if (args.Handled)
-        {
-            return true;
-        }
-
-        return false;
-    }
-
-    /// <summary>Method invoked when a view loses focus.</summary>
-    /// <param name="view">The view that is getting focus.</param>
-    /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-    public virtual bool OnLeave (View view)
-    {
-        var args = new FocusEventArgs (this, view);
-        Leave?.Invoke (this, args);
-
-        if (args.Handled)
-        {
-            return true;
-        }
-
-        return false;
-    }
-
-    // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews.
-    /// <summary>Returns the currently focused Subview inside this view, or null if nothing is focused.</summary>
-    /// <value>The focused.</value>
-    public View Focused { get; private set; }
-
-    // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews.
-    /// <summary>Returns the most focused Subview in the chain of subviews (the leaf view that has the focus).</summary>
-    /// <value>The most focused View.</value>
-    public View MostFocused
-    {
-        get
-        {
-            if (Focused is null)
-            {
-                return null;
-            }
-
-            View most = Focused.MostFocused;
-
-            if (most is { })
-            {
-                return most;
-            }
-
-            return Focused;
-        }
-    }
-
-    /// <summary>Causes the specified subview to have focus.</summary>
-    /// <param name="view">View.</param>
-    private void SetFocus (View view)
-    {
-        if (view is null)
-        {
-            return;
-        }
-
-        //Console.WriteLine ($"Request to focus {view}");
-        if (!view.CanFocus || !view.Visible || !view.Enabled)
-        {
-            return;
-        }
-
-        if (Focused?._hasFocus == true && Focused == view)
-        {
-            return;
-        }
-
-        if ((Focused?._hasFocus == true && Focused?.SuperView == view) || view == this)
-        {
-            if (!view._hasFocus)
-            {
-                view._hasFocus = true;
-            }
-
-            return;
-        }
-
-        // Make sure that this view is a subview
-        View c;
-
-        for (c = view._superView; c != null; c = c._superView)
-        {
-            if (c == this)
-            {
-                break;
-            }
-        }
-
-        if (c is null)
-        {
-            throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
-        }
-
-        if (Focused is { })
-        {
-            Focused.SetHasFocus (false, view);
-        }
-
-        View f = Focused;
-        Focused = view;
-        Focused.SetHasFocus (true, f);
-        Focused.EnsureFocus ();
-
-        // Send focus upwards
-        if (SuperView is { })
-        {
-            SuperView.SetFocus (this);
-        }
-        else
-        {
-            SetFocus (this);
-        }
-    }
-
-    /// <summary>Causes the specified view and the entire parent hierarchy to have the focused order updated.</summary>
-    public void SetFocus ()
-    {
-        if (!CanBeVisible (this) || !Enabled)
-        {
-            if (HasFocus)
-            {
-                SetHasFocus (false, this);
-            }
-
-            return;
-        }
-
-        if (SuperView is { })
-        {
-            SuperView.SetFocus (this);
-        }
-        else
-        {
-            SetFocus (this);
-        }
-    }
-
-    /// <summary>
-    ///     Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise,
-    ///     does nothing.
-    /// </summary>
-    public void EnsureFocus ()
-    {
-        if (Focused is null && _subviews?.Count > 0)
-        {
-            if (FocusDirection == NavigationDirection.Forward)
-            {
-                FocusFirst ();
-            }
-            else
-            {
-                FocusLast ();
-            }
-        }
-    }
-
-    /// <summary>Focuses the first focusable subview if one exists.</summary>
-    public void FocusFirst ()
-    {
-        if (!CanBeVisible (this))
-        {
-            return;
-        }
-
-        if (_tabIndexes is null)
-        {
-            SuperView?.SetFocus (this);
-
-            return;
-        }
-
-        foreach (View view in _tabIndexes)
-        {
-            if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
-            {
-                SetFocus (view);
-
-                return;
-            }
-        }
-    }
-
-    /// <summary>Focuses the last focusable subview if one exists.</summary>
-    public void FocusLast ()
-    {
-        if (!CanBeVisible (this))
-        {
-            return;
-        }
-
-        if (_tabIndexes is null)
-        {
-            SuperView?.SetFocus (this);
-
-            return;
-        }
-
-        for (int i = _tabIndexes.Count; i > 0;)
-        {
-            i--;
-
-            View v = _tabIndexes [i];
-
-            if (v.CanFocus && v._tabStop && v.Visible && v.Enabled)
-            {
-                SetFocus (v);
-
-                return;
-            }
-        }
-    }
-
-    /// <summary>Focuses the previous view.</summary>
-    /// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
-    public bool FocusPrev ()
-    {
-        if (!CanBeVisible (this))
-        {
-            return false;
-        }
-
-        FocusDirection = NavigationDirection.Backward;
-
-        if (_tabIndexes is null || _tabIndexes.Count == 0)
-        {
-            return false;
-        }
-
-        if (Focused is null)
-        {
-            FocusLast ();
-
-            return Focused != null;
-        }
-
-        int focusedIdx = -1;
-
-        for (int i = _tabIndexes.Count; i > 0;)
-        {
-            i--;
-            View w = _tabIndexes [i];
-
-            if (w.HasFocus)
-            {
-                if (w.FocusPrev ())
-                {
-                    return true;
-                }
-
-                focusedIdx = i;
-
-                continue;
-            }
-
-            if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
-            {
-                Focused.SetHasFocus (false, w);
-
-                if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
-                {
-                    w.FocusLast ();
-                }
-
-                SetFocus (w);
-
-                return true;
-            }
-        }
-
-        if (Focused is { })
-        {
-            Focused.SetHasFocus (false, this);
-            Focused = null;
-        }
-
-        return false;
-    }
-
-    /// <summary>Focuses the next view.</summary>
-    /// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
-    public bool FocusNext ()
-    {
-        if (!CanBeVisible (this))
-        {
-            return false;
-        }
-
-        FocusDirection = NavigationDirection.Forward;
-
-        if (_tabIndexes is null || _tabIndexes.Count == 0)
-        {
-            return false;
-        }
-
-        if (Focused is null)
-        {
-            FocusFirst ();
-
-            return Focused != null;
-        }
-
-        int focusedIdx = -1;
-
-        for (var i = 0; i < _tabIndexes.Count; i++)
-        {
-            View w = _tabIndexes [i];
-
-            if (w.HasFocus)
-            {
-                if (w.FocusNext ())
-                {
-                    return true;
-                }
-
-                focusedIdx = i;
-
-                continue;
-            }
-
-            if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
-            {
-                Focused.SetHasFocus (false, w);
-
-                if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
-                {
-                    w.FocusFirst ();
-                }
-
-                SetFocus (w);
-
-                return true;
-            }
-        }
-
-        if (Focused is { })
-        {
-            Focused.SetHasFocus (false, this);
-            Focused = null;
-        }
-
-        return false;
-    }
-
-    private View GetMostFocused (View view)
-    {
-        if (view is null)
-        {
-            return null;
-        }
-
-        return view.Focused is { } ? GetMostFocused (view.Focused) : 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;
-    }
-
-    #endregion Focus
-}

+ 12 - 12
Terminal.Gui/Views/ComboBox.cs

@@ -28,8 +28,9 @@ public class ComboBox : View, IDesignable
     /// <summary>Public constructor</summary>
     public ComboBox ()
     {
-        _search = new TextField ();
-        _listview = new ComboListView (this, HideDropdownListOnClick) { CanFocus = true, TabStop = false };
+        _search = new TextField () { CanFocus = true, TabStop = TabBehavior.NoStop };
+
+        _listview = new ComboListView (this, HideDropdownListOnClick) { CanFocus = true, TabStop = TabBehavior.NoStop};
 
         _search.TextChanged += Search_Changed;
         _search.Accept += Search_Accept;
@@ -329,9 +330,9 @@ public class ComboBox : View, IDesignable
             IsShow = false;
             HideList ();
         }
-        else if (_listview.TabStop)
+        else if (_listview.TabStop?.HasFlag (TabBehavior.TabStop) ?? false)
         {
-            _listview.TabStop = false;
+            _listview.TabStop = TabBehavior.NoStop;
         }
 
         return base.OnLeave (view);
@@ -455,7 +456,7 @@ public class ComboBox : View, IDesignable
     private void FocusSelectedItem ()
     {
         _listview.SelectedItem = SelectedItem > -1 ? SelectedItem : 0;
-        _listview.TabStop = true;
+        _listview.TabStop = TabBehavior.TabStop;
         _listview.SetFocus ();
         OnExpanded ();
     }
@@ -491,7 +492,7 @@ public class ComboBox : View, IDesignable
 
         Reset (true);
         _listview.Clear ();
-        _listview.TabStop = false;
+        _listview.TabStop = TabBehavior.NoStop;
         SuperView?.SendSubviewToBack (this);
         Rectangle rect = _listview.ViewportToScreen (_listview.IsInitialized ? _listview.Viewport : Rectangle.Empty);
         SuperView?.SetNeedsDisplay (rect);
@@ -505,7 +506,7 @@ public class ComboBox : View, IDesignable
             // jump to list
             if (_searchSet?.Count > 0)
             {
-                _listview.TabStop = true;
+                _listview.TabStop = TabBehavior.TabStop;
                 _listview.SetFocus ();
 
                 if (_listview.SelectedItem > -1)
@@ -519,8 +520,7 @@ public class ComboBox : View, IDesignable
             }
             else
             {
-                _listview.TabStop = false;
-                SuperView?.FocusNext ();
+                return false;
             }
 
             return true;
@@ -563,10 +563,10 @@ public class ComboBox : View, IDesignable
     {
         if (HasItems ())
         {
-            _listview.MoveUp ();
+           return  _listview.MoveUp ();
         }
 
-        return true;
+        return false;
     }
 
     private bool? MoveUpList ()
@@ -721,7 +721,7 @@ public class ComboBox : View, IDesignable
     private void Selected ()
     {
         IsShow = false;
-        _listview.TabStop = false;
+        _listview.TabStop = TabBehavior.NoStop;
 
         if (_listview.Source.Count == 0 || (_searchSet?.Count ?? 0) == 0)
         {

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

@@ -400,26 +400,26 @@ public class DateField : TextField
         AddCommand (Command.RightEnd, () => MoveEnd ());
         AddCommand (Command.Right, () => MoveRight ());
 
-        // Default keybindings for this view
-        KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
-        KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
+        // Replace the commands defined in TextField
+        KeyBindings.ReplaceCommands (Key.Delete, Command.DeleteCharRight);
+        KeyBindings.ReplaceCommands (Key.D.WithCtrl, Command.DeleteCharRight);
 
-        KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
+        KeyBindings.ReplaceCommands (Key.Backspace, Command.DeleteCharLeft);
 
-        KeyBindings.Add (Key.Home, Command.LeftHome);
-        KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome);
+        KeyBindings.ReplaceCommands (Key.Home, Command.LeftHome);
+        KeyBindings.ReplaceCommands (Key.A.WithCtrl, Command.LeftHome);
 
-        KeyBindings.Add (Key.CursorLeft, Command.Left);
-        KeyBindings.Add (Key.B.WithCtrl, Command.Left);
+        KeyBindings.ReplaceCommands (Key.CursorLeft, Command.Left);
+        KeyBindings.ReplaceCommands (Key.B.WithCtrl, Command.Left);
 
-        KeyBindings.Add (Key.End, Command.RightEnd);
-        KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
+        KeyBindings.ReplaceCommands (Key.End, Command.RightEnd);
+        KeyBindings.ReplaceCommands (Key.E.WithCtrl, Command.RightEnd);
 
-        KeyBindings.Add (Key.CursorRight, Command.Right);
-        KeyBindings.Add (Key.F.WithCtrl, Command.Right);
+        KeyBindings.ReplaceCommands (Key.CursorRight, Command.Right);
+        KeyBindings.ReplaceCommands (Key.F.WithCtrl, Command.Right);
 
 #if UNIX_KEY_BINDINGS
-        KeyBindings.Add (Key.D.WithAlt, Command.DeleteCharLeft);
+        KeyBindings.ReplaceCommands (Key.D.WithAlt, Command.DeleteCharLeft);
 #endif
 
     }

+ 1 - 0
Terminal.Gui/Views/DatePicker.cs

@@ -187,6 +187,7 @@ public class DatePicker : View
         BorderStyle = LineStyle.Single;
         Date = date;
         _dateLabel = new Label { X = 0, Y = 0, Text = "Date: " };
+        TabStop = TabBehavior.TabGroup;
 
         _calendar = new TableView
         {

+ 16 - 11
Terminal.Gui/Views/FileDialog.cs

@@ -137,7 +137,7 @@ public class FileDialog : Dialog
             FullRowSelect = true,
             CollectionNavigator = new FileDialogCollectionNavigator (this)
         };
-        _tableView.KeyBindings.Add (Key.Space, Command.Select);
+        _tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select);
         _tableView.MouseClick += OnTableViewMouseClick;
         _tableView.Style.InvertSelectedCellFirstCharacter = true;
         Style.TableStyle = _tableView.Style;
@@ -257,10 +257,10 @@ public class FileDialog : Dialog
         _tableView.KeyUp += (s, k) => k.Handled = TableView_KeyUp (k);
         _tableView.SelectedCellChanged += TableView_SelectedCellChanged;
 
-        _tableView.KeyBindings.Add (Key.Home, Command.TopHome);
-        _tableView.KeyBindings.Add (Key.End, Command.BottomEnd);
-        _tableView.KeyBindings.Add (Key.Home.WithShift, Command.TopHomeExtend);
-        _tableView.KeyBindings.Add (Key.End.WithShift, Command.BottomEndExtend);
+        _tableView.KeyBindings.ReplaceCommands (Key.Home, Command.TopHome);
+        _tableView.KeyBindings.ReplaceCommands (Key.End, Command.BottomEnd);
+        _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.TopHomeExtend);
+        _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.BottomEndExtend);
 
         _treeView.KeyDown += (s, k) =>
                              {
@@ -464,8 +464,8 @@ public class FileDialog : Dialog
             _btnOk.X = Pos.Right (_btnCancel) + 1;
 
             // Flip tab order too for consistency
-            int p1 = _btnOk.TabIndex;
-            int p2 = _btnCancel.TabIndex;
+            int? p1 = _btnOk.TabIndex;
+            int? p2 = _btnCancel.TabIndex;
 
             _btnOk.TabIndex = p2;
             _btnCancel.TabIndex = p1;
@@ -513,7 +513,7 @@ public class FileDialog : Dialog
                 // TODO: Does not work, if this worked then we could tab to it instead
                 // of having to hit F9
                 CanFocus = true,
-                TabStop = true,
+                TabStop = TabBehavior.TabStop,
                 Menus = [_allowedTypeMenu]
             };
             AllowedTypeMenuClicked (0);
@@ -538,7 +538,7 @@ public class FileDialog : Dialog
         // to streamline user experience and allow direct typing of paths
         // with zero navigation we start with focus in the text box and any
         // default/current path fully selected and ready to be overwritten
-        _tbPath.FocusFirst ();
+        _tbPath.FocusFirst (null);
         _tbPath.SelectAll ();
 
         if (string.IsNullOrEmpty (Title))
@@ -811,6 +811,11 @@ public class FileDialog : Dialog
         {
             PushState (d, true);
 
+            //if (d == State?.Directory || d.FullName == State?.Directory.FullName)
+            //{
+            //    FinishAccept ();
+            //}
+
             return;
         }
 
@@ -1045,7 +1050,7 @@ public class FileDialog : Dialog
     {
         if (keyEvent.KeyCode == isKey)
         {
-            to.FocusFirst ();
+            to.FocusFirst (null);
 
             if (to == _tbPath)
             {
@@ -1434,7 +1439,7 @@ public class FileDialog : Dialog
     {
         if (_treeView.HasFocus && Separators.Contains ((char)keyEvent))
         {
-            _tbPath.FocusFirst ();
+            _tbPath.FocusFirst (null);
 
             // let that keystroke go through on the tbPath instead
             return true;

+ 2 - 0
Terminal.Gui/Views/FrameView.cs

@@ -13,6 +13,8 @@ public class FrameView : View
     /// </summary>
     public FrameView ()
     {
+        CanFocus = true;
+        TabStop = TabBehavior.TabGroup;
         Border.Thickness = new Thickness (1);
         Border.LineStyle = DefaultBorderStyle;
 

+ 3 - 3
Terminal.Gui/Views/GraphView/Annotations.cs

@@ -133,7 +133,7 @@ public class LegendAnnotation : View, IAnnotation
     {
         if (!IsInitialized)
         {
-            ColorScheme = new ColorScheme { Normal = Application.Driver.GetAttribute () };
+            ColorScheme = new ColorScheme { Normal = Application.Driver?.GetAttribute () ?? Attribute.Default};
             graph.Add (this);
         }
 
@@ -149,7 +149,7 @@ public class LegendAnnotation : View, IAnnotation
         {
             if (entry.Item1.Color.HasValue)
             {
-                Application.Driver.SetAttribute (entry.Item1.Color.Value);
+                Application.Driver?.SetAttribute (entry.Item1.Color.Value);
             }
             else
             {
@@ -166,7 +166,7 @@ public class LegendAnnotation : View, IAnnotation
             Move (1, linesDrawn);
 
             string str = TextFormatter.ClipOrPad (entry.Item2, Viewport.Width - 1);
-            Application.Driver.AddStr (str);
+            Application.Driver?.AddStr (str);
 
             linesDrawn++;
 

+ 7 - 7
Terminal.Gui/Views/GraphView/Axis.cs

@@ -103,7 +103,7 @@ public class HorizontalAxis : Axis
         graph.Move (screenPosition, y);
 
         // draw the tick on the axis
-        Application.Driver.AddRune (Glyphs.TopTee);
+        Application.Driver?.AddRune (Glyphs.TopTee);
 
         // and the label text
         if (!string.IsNullOrWhiteSpace (text))
@@ -161,7 +161,7 @@ public class HorizontalAxis : Axis
             }
 
             graph.Move (graph.Viewport.Width / 2 - toRender.Length / 2, graph.Viewport.Height - 1);
-            Application.Driver.AddStr (toRender);
+            Application.Driver?.AddStr (toRender);
         }
     }
 
@@ -222,7 +222,7 @@ public class HorizontalAxis : Axis
     protected override void DrawAxisLine (GraphView graph, int x, int y)
     {
         graph.Move (x, y);
-        Application.Driver.AddRune (Glyphs.HLine);
+        Application.Driver?.AddRune (Glyphs.HLine);
     }
 
     private IEnumerable<AxisIncrementToRender> GetLabels (GraphView graph, Rectangle viewport)
@@ -298,13 +298,13 @@ public class VerticalAxis : Axis
         graph.Move (x, screenPosition);
 
         // draw the tick on the axis
-        Application.Driver.AddRune (Glyphs.RightTee);
+        Application.Driver?.AddRune (Glyphs.RightTee);
 
         // and the label text
         if (!string.IsNullOrWhiteSpace (text))
         {
             graph.Move (Math.Max (0, x - labelThickness), screenPosition);
-            Application.Driver.AddStr (text);
+            Application.Driver?.AddStr (text);
         }
     }
 
@@ -342,7 +342,7 @@ public class VerticalAxis : Axis
             for (var i = 0; i < toRender.Length; i++)
             {
                 graph.Move (0, startDrawingAtY + i);
-                Application.Driver.AddRune ((Rune)toRender [i]);
+                Application.Driver?.AddRune ((Rune)toRender [i]);
             }
         }
     }
@@ -395,7 +395,7 @@ public class VerticalAxis : Axis
     protected override void DrawAxisLine (GraphView graph, int x, int y)
     {
         graph.Move (x, y);
-        Application.Driver.AddRune (Glyphs.VLine);
+        Application.Driver?.AddRune (Glyphs.VLine);
     }
 
     private int GetAxisYEnd (GraphView graph)

+ 2 - 2
Terminal.Gui/Views/GraphView/Series.cs

@@ -33,7 +33,7 @@ public class ScatterSeries : ISeries
     {
         if (Fill.Color.HasValue)
         {
-            Application.Driver.SetAttribute (Fill.Color.Value);
+            Application.Driver?.SetAttribute (Fill.Color.Value);
         }
 
         foreach (PointF p in Points.Where (p => graphBounds.Contains (p)))
@@ -261,7 +261,7 @@ public class BarSeries : ISeries
 
         if (adjusted.Color.HasValue)
         {
-            Application.Driver.SetAttribute (adjusted.Color.Value);
+            Application.Driver?.SetAttribute (adjusted.Color.Value);
         }
 
         graph.DrawLine (start, end, adjusted.Rune);

+ 3 - 0
Terminal.Gui/Views/ListView.cs

@@ -122,7 +122,10 @@ public class ListView : View, IDesignable
         CanFocus = true;
 
         // Things this view knows how to do
+        // 
+        // BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
         AddCommand (Command.LineUp, () => MoveUp ());
+        // BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
         AddCommand (Command.LineDown, () => MoveDown ());
         AddCommand (Command.ScrollUp, () => ScrollVertical (-1));
         AddCommand (Command.ScrollDown, () => ScrollVertical (1));

+ 3 - 0
Terminal.Gui/Views/Menu/Menu.cs

@@ -192,13 +192,16 @@ internal sealed class Menu : View
 
             if ((KeyCode)menuItem.HotKey.Value != KeyCode.Null)
             {
+                KeyBindings.Remove ((KeyCode)menuItem.HotKey.Value);
                 KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, keyBinding);
+                KeyBindings.Remove ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask);
                 KeyBindings.Add ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask, keyBinding);
             }
 
             if (menuItem.Shortcut != KeyCode.Null)
             {
                 keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem);
+                KeyBindings.Remove (menuItem.Shortcut);
                 KeyBindings.Add (menuItem.Shortcut, keyBinding);
             }
 

+ 4 - 2
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -66,6 +66,7 @@ public class MenuBar : View, IDesignable
     /// <summary>Initializes a new instance of the <see cref="MenuBar"/>.</summary>
     public MenuBar ()
     {
+        TabStop = TabBehavior.NoStop;
         X = 0;
         Y = 0;
         Width = Dim.Fill ();
@@ -173,8 +174,9 @@ public class MenuBar : View, IDesignable
 
                 if (menuBarItem?.HotKey != default (Rune))
                 {
-                    KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, i);
+                    KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.Focused, i);
                     KeyBindings.Add ((KeyCode)menuBarItem.HotKey.Value, keyBinding);
+                    keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, i);
                     KeyBindings.Add ((KeyCode)menuBarItem.HotKey.Value | KeyCode.AltMask, keyBinding);
                 }
 
@@ -1594,7 +1596,7 @@ public class MenuBar : View, IDesignable
 
 
     /// <inheritdoc />
-    public bool EnableForDesign<TContext> (in TContext context) where TContext : notnull
+    public bool EnableForDesign<TContext> (ref readonly TContext context) where TContext : notnull
     {
         if (context is not Func<string, bool> actionFn)
         {

+ 1 - 0
Terminal.Gui/Views/Menu/MenuBarItem.cs

@@ -103,6 +103,7 @@ public class MenuBarItem : MenuItem
             if (menuItem.Shortcut != KeyCode.Null)
             {
                 KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem);
+                menuBar.KeyBindings.Remove (menuItem.Shortcut);
                 menuBar.KeyBindings.Add (menuItem.Shortcut, keyBinding);
             }
 

+ 0 - 2
Terminal.Gui/Views/MenuBarv2.cs

@@ -43,8 +43,6 @@ public class MenuBarv2 : Bar
 
         if (view is Shortcut shortcut)
         {
-            shortcut.KeyBindingScope = KeyBindingScope.Application;
-
             // TODO: not happy about using AlignmentModes for this. Too implied.
             // TODO: instead, add a property (a style enum?) to Shortcut to control this
             //shortcut.AlignmentModes = AlignmentModes.EndToStart;

+ 51 - 103
Terminal.Gui/Views/NumericUpDown.cs

@@ -1,6 +1,8 @@
 #nullable enable
 using System.ComponentModel;
-using Terminal.Gui;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Terminal.Gui;
 
 /// <summary>
 ///     Enables the user to increase or decrease a value by clicking on the up or down buttons.
@@ -10,7 +12,7 @@ using Terminal.Gui;
 ///     <see cref="decimal"/>.
 ///     Supports only one digit of precision.
 /// </remarks>
-public class NumericUpDown<T> : View
+public class NumericUpDown<T> : View where T : notnull
 {
     private readonly Button _down;
 
@@ -18,56 +20,24 @@ public class NumericUpDown<T> : View
     private readonly View _number;
     private readonly Button _up;
 
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="NumericUpDown{T}"/> class.
+    /// </summary>
+    /// <exception cref="InvalidOperationException"></exception>
     public NumericUpDown ()
     {
         Type type = typeof (T);
 
-        if (!(type == typeof (int) || type == typeof (long) || type == typeof (double) || type == typeof (float) || type == typeof (double) || type == typeof (decimal)))
+        if (!(type == typeof (object) || type == typeof (int) || type == typeof (long) || type == typeof (double) || type == typeof (float) || type == typeof (double) || type == typeof (decimal)))
         {
-            // Support object for AllViewsTester
-            if (type != typeof (object))
-            {
-                throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction.");
-            }
+            throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction.");
         }
 
-        switch (typeof (T))
+        Increment = (dynamic)1;
+        // `object` is supported only for AllViewsTester
+        if (type != typeof (object))
         {
-            case { } i when i == typeof (int):
-                Minimum = (dynamic)int.MinValue;
-                Maximum = (dynamic)int.MaxValue;
-                Increment = (dynamic)1;
-
-                break;
-
-            case { } i when i == typeof (long):
-                Minimum = (dynamic)long.MinValue;
-                Maximum = (dynamic)long.MaxValue;
-                Increment = (dynamic)1;
-
-                break;
-
-            case { } i when i == typeof (double):
-                Minimum = (dynamic)double.MinValue;
-                Maximum = (dynamic)double.MaxValue;
-                Increment = (dynamic)1;
-
-                break;
-
-            case { } i when i == typeof (float):
-                Minimum = (dynamic)float.MinValue;
-                Maximum = (dynamic)float.MaxValue;
-                Increment = (dynamic)1;
-
-                break;
-
-
-            case { } i when i == typeof (decimal):
-                Minimum = (dynamic)decimal.MinValue;
-                Maximum = (dynamic)decimal.MaxValue;
-                Increment = (dynamic)1;
-
-                break;
+            Value = (dynamic)0;
         }
 
         Width = Dim.Auto (DimAutoStyle.Content); //Dim.Function (() => Digits + 2); // button + 3 for number + button
@@ -90,7 +60,7 @@ public class NumericUpDown<T> : View
             Text = Value?.ToString () ?? "Err",
             X = Pos.Right (_down),
             Y = Pos.Top (_down),
-            Width = Dim.Func (() => Digits),
+            Width = Dim.Auto (minimumContentDim: Dim.Func (() => string.Format (Format, Value).Length)),
             Height = 1,
             TextAlignment = Alignment.Center,
             CanFocus = true
@@ -98,7 +68,7 @@ public class NumericUpDown<T> : View
 
         _up = new ()
         {
-            X = Pos.AnchorEnd (),
+            X = Pos.Right (_number),
             Y = Pos.Top (_number),
             Height = 1,
             Width = 1,
@@ -128,8 +98,7 @@ public class NumericUpDown<T> : View
 
                         if (Value is { })
                         {
-                            Value = (dynamic)Value + Increment;
-                            _number.Text = Value?.ToString () ?? string.Empty;
+                            Value = (dynamic)Value + (dynamic)Increment;
                         }
 
                         return true;
@@ -146,8 +115,7 @@ public class NumericUpDown<T> : View
 
                         if (Value is { })
                         {
-                            Value = (dynamic)Value - Increment;
-                            _number.Text = Value.ToString () ?? string.Empty;
+                            Value = (dynamic)Value - (dynamic)Increment;
                         }
 
                         return true;
@@ -156,22 +124,22 @@ public class NumericUpDown<T> : View
         KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
         KeyBindings.Add (Key.CursorDown, Command.ScrollDown);
 
+        SetText ();
+
         return;
 
-        void OnDownButtonOnAccept (object s, HandledEventArgs e)
+        void OnDownButtonOnAccept (object? s, HandledEventArgs e)
         {
             InvokeCommand (Command.ScrollDown);
         }
 
-        void OnUpButtonOnAccept (object s, HandledEventArgs e)
+        void OnUpButtonOnAccept (object? s, HandledEventArgs e)
         {
             InvokeCommand (Command.ScrollUp);
         }
     }
 
-    private void _up_Enter (object sender, FocusEventArgs e) { throw new NotImplementedException (); }
-
-    private T _value;
+    private T _value = default!;
 
     /// <summary>
     ///     The value that will be incremented or decremented.
@@ -187,7 +155,7 @@ public class NumericUpDown<T> : View
             }
 
             T oldValue = value;
-            CancelEventArgs<T> args = new (ref _value, ref value);
+            CancelEventArgs<T> args = new (in _value, ref value);
             ValueChanging?.Invoke (this, args);
 
             if (args.Cancel)
@@ -195,72 +163,52 @@ public class NumericUpDown<T> : View
                 return;
             }
 
-            if (Comparer<T>.Default.Compare (value, Minimum) < 0)
-            {
-                value = Minimum;
-            }
-
-            if (Comparer<T>.Default.Compare (value, Maximum) > 0)
-            {
-                value = Maximum;
-            }
-
             _value = value;
-            _number.Text = _value?.ToString () ?? string.Empty;
-            ValueChanged?.Invoke (this, new (ref _value));
+            SetText ();
+            ValueChanged?.Invoke (this, new (in value));
         }
     }
 
-    /// <summary>
-    ///     Fired when the value is about to change. Set <see cref="CancelEventArgs{T}.Cancel"/> to true to prevent the change.
-    /// </summary>
-    [CanBeNull]
-    public event EventHandler<CancelEventArgs<T>> ValueChanging;
-
-    /// <summary>
-    ///     Fired when the value has changed.
-    /// </summary>
-    [CanBeNull]
-    public event EventHandler<EventArgs<T>> ValueChanged;
+    private string _format = "{0}";
 
     /// <summary>
-    ///     The number of digits to display. The <see cref="View.Viewport"/> will be resized to fit this number of characters
-    ///     plus the buttons. The default is 3.
+    ///     Gets or sets the format string used to display the value. The default is "{0}".
     /// </summary>
-    public int Digits { get; set; } = 3;
+    public string Format
+    {
+        get => _format;
+        set
+        {
+            if (_format == value)
+            {
+                return;
+            }
 
-    private T _minimum;
+            _format = value;
+            SetText ();
+        }
+    }
 
-    /// <summary>
-    /// 
-    /// </summary>
-    public T Minimum
+    private void SetText ()
     {
-        get { return _minimum; }
-        set { _minimum = value; }
+        _number.Text = string.Format (Format, _value);
+        Text = _number.Text;
     }
 
-    private T _maximum;
-
     /// <summary>
-    /// 
+    ///     Raised when the value is about to change. Set <see cref="CancelEventArgs{T}.Cancel"/> to true to prevent the change.
     /// </summary>
-    public T Maximum
-    {
-        get { return _maximum; }
-        set { _maximum = value; }
-    }
+    public event EventHandler<CancelEventArgs<T>>? ValueChanging;
 
-    private T _increment;
+    /// <summary>
+    ///     Raised when the value has changed.
+    /// </summary>
+    public event EventHandler<EventArgs<T>>? ValueChanged;
 
     /// <summary>
     /// 
     /// </summary>
-    public T Increment
-    {
-        get { return _increment; }
-        set { _increment = value; }
-    }
+    public T Increment { get; set; }
 }
 
 

+ 19 - 25
Terminal.Gui/Views/RadioGroup.cs

@@ -30,9 +30,7 @@ public class RadioGroup : View, IDesignable, IOrientation
                             return false;
                         }
 
-                        MoveUpLeft ();
-
-                        return true;
+                        return MoveUpLeft ();
                     }
                    );
 
@@ -44,10 +42,7 @@ public class RadioGroup : View, IDesignable, IOrientation
                         {
                             return false;
                         }
-
-                        MoveDownRight ();
-
-                        return true;
+                        return MoveDownRight ();
                     }
                    );
 
@@ -277,7 +272,7 @@ public class RadioGroup : View, IDesignable, IOrientation
 
                     if (j == hotPos && i == _cursor)
                     {
-                        Application.Driver.SetAttribute (
+                        Application.Driver?.SetAttribute (
                                                          HasFocus
                                                              ? ColorScheme.HotFocus
                                                              : GetHotNormalColor ()
@@ -285,11 +280,11 @@ public class RadioGroup : View, IDesignable, IOrientation
                     }
                     else if (j == hotPos && i != _cursor)
                     {
-                        Application.Driver.SetAttribute (GetHotNormalColor ());
+                        Application.Driver?.SetAttribute (GetHotNormalColor ());
                     }
                     else if (HasFocus && i == _cursor)
                     {
-                        Application.Driver.SetAttribute (ColorScheme.Focus);
+                        Application.Driver?.SetAttribute (ColorScheme.Focus);
                     }
 
                     if (rune == HotKeySpecifier && j + 1 < rlRunes.Length)
@@ -299,7 +294,7 @@ public class RadioGroup : View, IDesignable, IOrientation
 
                         if (i == _cursor)
                         {
-                            Application.Driver.SetAttribute (
+                            Application.Driver?.SetAttribute (
                                                              HasFocus
                                                                  ? ColorScheme.HotFocus
                                                                  : GetHotNormalColor ()
@@ -307,11 +302,11 @@ public class RadioGroup : View, IDesignable, IOrientation
                         }
                         else if (i != _cursor)
                         {
-                            Application.Driver.SetAttribute (GetHotNormalColor ());
+                            Application.Driver?.SetAttribute (GetHotNormalColor ());
                         }
                     }
 
-                    Application.Driver.AddRune (rune);
+                    Application.Driver?.AddRune (rune);
                     Driver.SetAttribute (GetNormalColor ());
                 }
             }
@@ -401,35 +396,34 @@ public class RadioGroup : View, IDesignable, IOrientation
     /// <summary>Invoked when the selected radio label has changed.</summary>
     public event EventHandler<SelectedItemChangedArgs> SelectedItemChanged;
 
-    private void MoveDownRight ()
+    private bool MoveDownRight ()
     {
         if (_cursor + 1 < _radioLabels.Count)
         {
             _cursor++;
             SetNeedsDisplay ();
+
+            return true;
         }
-        else if (_cursor > 0)
-        {
-            _cursor = 0;
-            SetNeedsDisplay ();
-        }
+
+        // Moving past should move focus to next view, not wrap
+        return false;
     }
 
     private void MoveEnd () { _cursor = Math.Max (_radioLabels.Count - 1, 0); }
     private void MoveHome () { _cursor = 0; }
 
-    private void MoveUpLeft ()
+    private bool MoveUpLeft ()
     {
         if (_cursor > 0)
         {
             _cursor--;
             SetNeedsDisplay ();
+
+            return true;
         }
-        else if (_radioLabels.Count - 1 > 0)
-        {
-            _cursor = _radioLabels.Count - 1;
-            SetNeedsDisplay ();
-        }
+        // Moving past should move focus to next view, not wrap
+        return false;
     }
 
     private void RadioGroup_LayoutStarted (object sender, EventArgs e) { SetContentSize (); }

+ 1 - 0
Terminal.Gui/Views/ScrollView.cs

@@ -70,6 +70,7 @@ public class ScrollView : View
         _horizontal.OtherScrollBarView = _vertical;
         base.Add (_contentView);
         CanFocus = true;
+        TabStop = TabBehavior.TabGroup;
 
         MouseEnter += View_MouseEnter;
         MouseLeave += View_MouseLeave;

+ 128 - 94
Terminal.Gui/Views/Shortcut.cs

@@ -38,8 +38,6 @@
 /// </remarks>
 public class Shortcut : View, IOrientation, IDesignable
 {
-    private readonly OrientationHelper _orientationHelper;
-
     /// <summary>
     ///     Creates a new instance of <see cref="Shortcut"/>.
     /// </summary>
@@ -142,44 +140,24 @@ public class Shortcut : View, IOrientation, IDesignable
     /// </summary>
     public Shortcut () : this (Key.Empty, string.Empty, null) { }
 
-    #region IOrientation members
+    private readonly OrientationHelper _orientationHelper;
 
-    /// <summary>
-    ///     Gets or sets the <see cref="Orientation"/> for this <see cref="Bar"/>. The default is
-    ///     <see cref="Orientation.Horizontal"/>.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         Horizontal orientation arranges the command, help, and key parts of each <see cref="Shortcut"/>s from right to
-    ///         left
-    ///         Vertical orientation arranges the command, help, and key parts of each <see cref="Shortcut"/>s from left to
-    ///         right.
-    ///     </para>
-    /// </remarks>
+    private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast;
 
-    public Orientation Orientation
-    {
-        get => _orientationHelper.Orientation;
-        set => _orientationHelper.Orientation = value;
-    }
+    // This is used to calculate the minimum width of the Shortcut when the width is NOT Dim.Auto
+    private int? _minimumDimAutoWidth;
 
-    /// <inheritdoc/>
-    public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
+    private Color? _savedForeColor;
 
     /// <inheritdoc/>
-    public event EventHandler<EventArgs<Orientation>> OrientationChanged;
-
-    /// <summary>Called when <see cref="Orientation"/> has changed.</summary>
-    /// <param name="newOrientation"></param>
-    public void OnOrientationChanged (Orientation newOrientation)
+    public bool EnableForDesign ()
     {
-        // TODO: Determine what, if anything, is opinionated about the orientation.
-        SetNeedsLayout ();
-    }
-
-    #endregion
+        Title = "_Shortcut";
+        HelpText = "Shortcut help";
+        Key = Key.F1;
 
-    private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast;
+        return true;
+    }
 
     /// <summary>
     ///     Gets or sets the <see cref="AlignmentModes"/> for this <see cref="Shortcut"/>.
@@ -202,6 +180,30 @@ public class Shortcut : View, IOrientation, IDesignable
         }
     }
 
+    /// <inheritdoc/>
+    protected override void Dispose (bool disposing)
+    {
+        if (disposing)
+        {
+            if (CommandView?.IsAdded == false)
+            {
+                CommandView.Dispose ();
+            }
+
+            if (HelpView?.IsAdded == false)
+            {
+                HelpView.Dispose ();
+            }
+
+            if (KeyView?.IsAdded == false)
+            {
+                KeyView.Dispose ();
+            }
+        }
+
+        base.Dispose (disposing);
+    }
+
     // When one of the subviews is "empty" we don't want to show it. So we
     // Use Add/Remove. We need to be careful to add them in the right order
     // so Pos.Align works correctly.
@@ -225,8 +227,15 @@ public class Shortcut : View, IOrientation, IDesignable
         }
     }
 
-    // This is used to calculate the minimum width of the Shortcut when the width is NOT Dim.Auto
-    private int? _minimumDimAutoWidth;
+    private Thickness GetMarginThickness ()
+    {
+        if (Orientation == Orientation.Vertical)
+        {
+            return new (1, 0, 1, 0);
+        }
+
+        return new (1, 0, 1, 0);
+    }
 
     // When layout starts, we need to adjust the layout of the HelpView and KeyView
     private void OnLayoutStarted (object sender, LayoutEventArgs e)
@@ -305,18 +314,16 @@ public class Shortcut : View, IOrientation, IDesignable
         }
     }
 
-    private Thickness GetMarginThickness ()
+    private bool? OnSelect (CommandContext ctx)
     {
-        if (Orientation == Orientation.Vertical)
+        if (CommandView.GetSupportedCommands ().Contains (Command.Select))
         {
-            return new (1, 0, 1, 0);
+            return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding);
         }
 
-        return new (1, 0, 1, 0);
+        return false;
     }
 
-    private Color? _savedForeColor;
-
     private void Shortcut_Highlight (object sender, CancelEventArgs<HighlightStyle> e)
     {
         if (e.CurrentValue.HasFlag (HighlightStyle.Pressed))
@@ -368,6 +375,43 @@ public class Shortcut : View, IOrientation, IDesignable
         // TODO: Remove. This does nothing.
     }
 
+    #region IOrientation members
+
+    /// <summary>
+    ///     Gets or sets the <see cref="Orientation"/> for this <see cref="Bar"/>. The default is
+    ///     <see cref="Orientation.Horizontal"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Horizontal orientation arranges the command, help, and key parts of each <see cref="Shortcut"/>s from right to
+    ///         left
+    ///         Vertical orientation arranges the command, help, and key parts of each <see cref="Shortcut"/>s from left to
+    ///         right.
+    ///     </para>
+    /// </remarks>
+
+    public Orientation Orientation
+    {
+        get => _orientationHelper.Orientation;
+        set => _orientationHelper.Orientation = value;
+    }
+
+    /// <inheritdoc/>
+    public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
+
+    /// <inheritdoc/>
+    public event EventHandler<EventArgs<Orientation>> OrientationChanged;
+
+    /// <summary>Called when <see cref="Orientation"/> has changed.</summary>
+    /// <param name="newOrientation"></param>
+    public void OnOrientationChanged (Orientation newOrientation)
+    {
+        // TODO: Determine what, if anything, is opinionated about the orientation.
+        SetNeedsLayout ();
+    }
+
+    #endregion
+
     #region Command
 
     private View _commandView = new ();
@@ -454,7 +498,7 @@ public class Shortcut : View, IOrientation, IDesignable
             SetHelpViewDefaultLayout ();
             SetKeyViewDefaultLayout ();
             ShowHide ();
-            UpdateKeyBinding ();
+            UpdateKeyBinding (Key.Empty);
         }
     }
 
@@ -503,7 +547,7 @@ public class Shortcut : View, IOrientation, IDesignable
         get => HelpView?.Text;
         set
         {
-            if (HelpView is {})
+            if (HelpView is { })
             {
                 HelpView.Text = value;
                 ShowHide ();
@@ -519,7 +563,7 @@ public class Shortcut : View, IOrientation, IDesignable
         get => HelpView?.Text;
         set
         {
-            if (HelpView is {})
+            if (HelpView is { })
             {
                 HelpView.Text = value;
                 ShowHide ();
@@ -546,9 +590,10 @@ public class Shortcut : View, IOrientation, IDesignable
                 throw new ArgumentNullException ();
             }
 
+            Key oldKey = _key;
             _key = value;
 
-            UpdateKeyBinding ();
+            UpdateKeyBinding (oldKey);
 
             KeyView.Text = Key == Key.Empty ? string.Empty : $"{Key}";
             ShowHide ();
@@ -565,9 +610,24 @@ public class Shortcut : View, IOrientation, IDesignable
         get => _keyBindingScope;
         set
         {
+            if (value == _keyBindingScope)
+            {
+                return;
+            }
+
+            if (_keyBindingScope == KeyBindingScope.Application)
+            {
+                Application.KeyBindings.Remove (Key);
+            }
+
+            if (_keyBindingScope is KeyBindingScope.HotKey or KeyBindingScope.Focused)
+            {
+                KeyBindings.Remove (Key);
+            }
+
             _keyBindingScope = value;
 
-            UpdateKeyBinding ();
+            UpdateKeyBinding (Key.Empty);
         }
     }
 
@@ -619,17 +679,34 @@ public class Shortcut : View, IOrientation, IDesignable
         KeyView.KeyBindings.Clear ();
     }
 
-    private void UpdateKeyBinding ()
+    private void UpdateKeyBinding (Key oldKey)
     {
-        if (Key != null)
+        if (Key != null && Key.IsValid)
         {
             // Disable the command view key bindings
             CommandView.KeyBindings.Remove (Key);
             CommandView.KeyBindings.Remove (CommandView.HotKey);
-            KeyBindings.Remove (Key);
-            KeyBindings.Add (Key, KeyBindingScope | KeyBindingScope.HotKey, Command.Accept);
 
-            //KeyBindings.Add (Key, KeyBindingScope.HotKey, Command.Accept);
+            if (KeyBindingScope.FastHasFlags (KeyBindingScope.Application))
+            {
+                if (oldKey != Key.Empty)
+                {
+                    Application.KeyBindings.Remove (oldKey);
+                }
+
+                Application.KeyBindings.Remove (Key);
+                Application.KeyBindings.Add (Key, this, Command.Accept);
+            }
+            else
+            {
+                if (oldKey != Key.Empty)
+                {
+                    KeyBindings.Remove (oldKey);
+                }
+
+                KeyBindings.Remove (Key);
+                KeyBindings.Add (Key, KeyBindingScope | KeyBindingScope.HotKey, Command.Accept);
+            }
         }
     }
 
@@ -707,16 +784,6 @@ public class Shortcut : View, IOrientation, IDesignable
 
     #endregion Accept Handling
 
-    private bool? OnSelect (CommandContext ctx)
-    {
-        if (CommandView.GetSupportedCommands ().Contains (Command.Select))
-        {
-            return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding);
-        }
-
-        return false;
-    }
-
     #region Focus
 
     /// <inheritdoc/>
@@ -786,37 +853,4 @@ public class Shortcut : View, IOrientation, IDesignable
     }
 
     #endregion Focus
-
-    /// <inheritdoc/>
-    public bool EnableForDesign ()
-    {
-        Title = "_Shortcut";
-        HelpText = "Shortcut help";
-        Key = Key.F1;
-        return true;
-    }
-
-    /// <inheritdoc/>
-    protected override void Dispose (bool disposing)
-    {
-        if (disposing)
-        {
-            if (CommandView?.IsAdded == false)
-            {
-                CommandView.Dispose ();
-            }
-
-            if (HelpView?.IsAdded == false)
-            {
-                HelpView.Dispose ();
-            }
-
-            if (KeyView?.IsAdded == false)
-            {
-                KeyView.Dispose ();
-            }
-        }
-
-        base.Dispose (disposing);
-    }
 }

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

@@ -1454,9 +1454,13 @@ public class Slider<T> : View, IOrientation
             KeyBindings.Add (Key.CursorUp.WithCtrl, Command.LeftExtend);
         }
 
+        KeyBindings.Remove (Key.Home);
         KeyBindings.Add (Key.Home, Command.LeftHome);
+        KeyBindings.Remove (Key.End);
         KeyBindings.Add (Key.End, Command.RightEnd);
+        KeyBindings.Remove (Key.Enter);
         KeyBindings.Add (Key.Enter, Command.Accept);
+        KeyBindings.Remove (Key.Space);
         KeyBindings.Add (Key.Space, Command.Select);
     }
 

+ 1 - 2
Terminal.Gui/Views/StatusBar.cs

@@ -18,6 +18,7 @@ public class StatusBar : Bar, IDesignable
     /// <inheritdoc/>
     public StatusBar (IEnumerable<Shortcut> shortcuts) : base (shortcuts)
     {
+        TabStop = TabBehavior.NoStop;
         Orientation = Orientation.Horizontal;
         Y = Pos.AnchorEnd ();
         Width = Dim.Fill ();
@@ -65,8 +66,6 @@ public class StatusBar : Bar, IDesignable
 
         if (view is Shortcut shortcut)
         {
-            shortcut.KeyBindingScope = KeyBindingScope.Application;
-
             // TODO: not happy about using AlignmentModes for this. Too implied.
             // TODO: instead, add a property (a style enum?) to Shortcut to control this
             shortcut.AlignmentModes = AlignmentModes.EndToStart;

+ 1 - 0
Terminal.Gui/Views/Tab.cs

@@ -10,6 +10,7 @@ public class Tab : View
     {
         BorderStyle = LineStyle.Rounded;
         CanFocus = true;
+        TabStop = TabBehavior.NoStop;
     }
 
     /// <summary>The text to display in a <see cref="TabView"/>.</summary>

+ 41 - 34
Terminal.Gui/Views/TabView.cs

@@ -25,8 +25,12 @@ public class TabView : View
     public TabView ()
     {
         CanFocus = true;
+        TabStop = TabBehavior.TabStop;
         _tabsBar = new TabRowView (this);
-        _contentView = new View ();
+        _contentView = new View ()
+        {
+            Id = "TabView._contentView"
+        };
 
         ApplyStyleChanges ();
 
@@ -34,25 +38,9 @@ public class TabView : View
         base.Add (_contentView);
 
         // Things this view knows how to do
-        AddCommand (
-                    Command.Left,
-                    () =>
-                    {
-                        SwitchTabBy (-1);
+        AddCommand (Command.Left, () => SwitchTabBy (-1));
 
-                        return true;
-                    }
-                   );
-
-        AddCommand (
-                    Command.Right,
-                    () =>
-                    {
-                        SwitchTabBy (1);
-
-                        return true;
-                    }
-                   );
+        AddCommand (Command.Right, () => SwitchTabBy (1));
 
         AddCommand (
                     Command.LeftHome,
@@ -80,26 +68,37 @@ public class TabView : View
                     Command.NextView,
                     () =>
                     {
+                        if (Style.TabsOnBottom)
+                        {
+                            return false;
+                        }
+
                         if (_contentView is { HasFocus: false })
                         {
                             _contentView.SetFocus ();
 
-                            return true;
+                            return _contentView.Focused is { };
                         }
 
                         return false;
                     }
                    );
 
-        AddCommand (
-                    Command.PreviousView,
-                    () =>
-                    {
-                        SuperView?.FocusPrev ();
+        AddCommand (Command.PreviousView, () =>
+                                          {
+                                              if (!Style.TabsOnBottom)
+                                              {
+                                                  return false;
+                                              }
+                                              if (_contentView is { HasFocus: false })
+                                              {
+                                                  _contentView.SetFocus ();
 
-                        return true;
-                    }
-                   );
+                                                  return _contentView.Focused is { };
+                                              }
+
+                                              return false;
+                                          });
 
         AddCommand (
                     Command.PageDown,
@@ -373,11 +372,11 @@ public class TabView : View
     ///     left.  If no tab is currently selected then the first tab will become selected.
     /// </summary>
     /// <param name="amount"></param>
-    public void SwitchTabBy (int amount)
+    public bool SwitchTabBy (int amount)
     {
         if (Tabs.Count == 0)
         {
-            return;
+            return false;
         }
 
         // if there is only one tab anyway or nothing is selected
@@ -386,7 +385,7 @@ public class TabView : View
             SelectedTab = Tabs.ElementAt (0);
             SetNeedsDisplay ();
 
-            return;
+            return SelectedTab is { };
         }
 
         int currentIdx = Tabs.IndexOf (SelectedTab);
@@ -397,15 +396,22 @@ public class TabView : View
             SelectedTab = Tabs.ElementAt (0);
             SetNeedsDisplay ();
 
-            return;
+            return true;
         }
 
         int newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1));
 
+        if (newIdx == currentIdx)
+        {
+            return false;
+        }
+
         SelectedTab = _tabs [newIdx];
         SetNeedsDisplay ();
 
         EnsureSelectedTabIsVisible ();
+
+        return true;
     }
 
     /// <summary>
@@ -564,6 +570,7 @@ public class TabView : View
             _host = host;
 
             CanFocus = true;
+            TabStop = TabBehavior.TabStop;
             Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize ().
             Width = Dim.Fill ();
 
@@ -590,7 +597,7 @@ public class TabView : View
             Add (_rightScrollIndicator, _leftScrollIndicator);
         }
 
-        protected internal override bool OnMouseEvent  (MouseEvent me)
+        protected internal override bool OnMouseEvent (MouseEvent me)
         {
             Tab hit = me.View is Tab ? (Tab)me.View : null;
 
@@ -667,7 +674,7 @@ public class TabView : View
             RenderTabLine ();
 
             RenderUnderline ();
-            Driver.SetAttribute (GetNormalColor ());
+            Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
         }
 
         public override void OnDrawContentComplete (Rectangle viewport)

+ 1 - 0
Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs

@@ -1,5 +1,6 @@
 namespace Terminal.Gui;
 
+// TOOD: SHould support Handled
 /// <summary>Defines the event arguments for <see cref="TableView.CellActivated"/> event</summary>
 public class CellActivatedEventArgs : EventArgs
 {

+ 1 - 1
Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs

@@ -26,7 +26,7 @@ public abstract class CheckBoxTableSourceWrapperBase : ITableSource
         Wrapping = toWrap;
         this.tableView = tableView;
 
-        tableView.KeyBindings.Add (Key.Space, Command.Select);
+        tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select);
 
         tableView.MouseClick += TableView_MouseClick;
         tableView.CellToggled += TableView_CellToggled;

+ 14 - 5
Terminal.Gui/Views/TableView/TableView.cs

@@ -56,6 +56,7 @@ public class TableView : View
                     Command.Right,
                     () =>
                     {
+                        // BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
                         ChangeSelectionByOffset (1, 0, false);
 
                         return true;
@@ -66,6 +67,7 @@ public class TableView : View
                     Command.Left,
                     () =>
                     {
+                        // BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
                         ChangeSelectionByOffset (-1, 0, false);
 
                         return true;
@@ -76,6 +78,7 @@ public class TableView : View
                     Command.LineUp,
                     () =>
                     {
+                        // BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
                         ChangeSelectionByOffset (0, -1, false);
 
                         return true;
@@ -86,6 +89,7 @@ public class TableView : View
                     Command.LineDown,
                     () =>
                     {
+                        // BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
                         ChangeSelectionByOffset (0, 1, false);
 
                         return true;
@@ -266,6 +270,7 @@ public class TableView : View
                     Command.Accept,
                     () =>
                     {
+                        // BUGBUG: This should return false if the event is not handled
                         OnCellActivated (new CellActivatedEventArgs (Table, SelectedColumn, SelectedRow));
 
                         return true;
@@ -319,11 +324,15 @@ public class TableView : View
         {
             if (cellActivationKey != value)
             {
-                KeyBindings.Replace (cellActivationKey, value);
+                if (KeyBindings.TryGet (cellActivationKey, out _))
+                {
+                    KeyBindings.ReplaceKey (cellActivationKey, value);
+                }
+                else
+                {
+                    KeyBindings.Add (value, Command.Accept);
+                }
 
-                // of API user is mixing and matching old and new methods of keybinding then they may have lost
-                // the old binding (e.g. with ClearKeybindings) so KeyBindings.Replace alone will fail
-                KeyBindings.Add (value, Command.Accept);
                 cellActivationKey = value;
             }
         }
@@ -787,7 +796,7 @@ public class TableView : View
     }
 
     ///<inheritdoc/>
-    protected internal override bool OnMouseEvent  (MouseEvent me)
+    protected internal override bool OnMouseEvent (MouseEvent me)
     {
         if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)
             && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)

+ 18 - 25
Terminal.Gui/Views/TextField.cs

@@ -134,15 +134,7 @@ public class TextField : View
                     }
                    );
 
-        AddCommand (
-                    Command.Left,
-                    () =>
-                    {
-                        MoveLeft ();
-
-                        return true;
-                    }
-                   );
+        AddCommand (Command.Left,  () => MoveLeft ());
 
         AddCommand (
                     Command.RightEnd,
@@ -154,15 +146,7 @@ public class TextField : View
                     }
                    );
 
-        AddCommand (
-                    Command.Right,
-                    () =>
-                    {
-                        MoveRight ();
-
-                        return true;
-                    }
-                   );
+        AddCommand (Command.Right, () => MoveRight ());
 
         AddCommand (
                     Command.CutToEndLine,
@@ -1332,7 +1316,10 @@ public class TextField : View
                                );
     }
 
-    private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode); }
+    private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e)
+    {
+        KeyBindings.ReplaceKey (e.OldKey.KeyCode, e.NewKey.KeyCode);
+    }
 
     private List<Rune> DeleteSelectedText ()
     {
@@ -1544,15 +1531,19 @@ public class TextField : View
         }
     }
 
-    private void MoveLeft ()
+    private bool MoveLeft ()
     {
-        ClearAllSelection ();
 
         if (_cursorPosition > 0)
         {
+            ClearAllSelection ();
             _cursorPosition--;
             Adjust ();
+
+            return true;
         }
+
+        return false;
     }
 
     private void MoveLeftExtend ()
@@ -1563,17 +1554,19 @@ public class TextField : View
         }
     }
 
-    private void MoveRight ()
+    private bool MoveRight ()
     {
-        ClearAllSelection ();
-
         if (_cursorPosition == _text.Count)
         {
-            return;
+            return false;
         }
 
+        ClearAllSelection ();
+
         _cursorPosition++;
         Adjust ();
+
+        return true;
     }
 
     private void MoveRightExtend ()

+ 14 - 5
Terminal.Gui/Views/TextValidateField.cs

@@ -206,7 +206,7 @@ namespace Terminal.Gui
 
                 if (result)
                 {
-                    OnTextChanged (new EventArgs<string> (ref oldValue));
+                    OnTextChanged (new EventArgs<string> (in oldValue));
                 }
 
                 return result;
@@ -220,7 +220,7 @@ namespace Terminal.Gui
 
                 if (result)
                 {
-                    OnTextChanged (new EventArgs<string> (ref oldValue));
+                    OnTextChanged (new EventArgs<string> (in oldValue));
                 }
 
                 return result;
@@ -333,7 +333,7 @@ namespace Terminal.Gui
                 {
                     string oldValue = Text;
                     _text.RemoveAt (pos);
-                    OnTextChanged (new EventArgs<string> (ref oldValue));
+                    OnTextChanged (new EventArgs<string> (in oldValue));
                 }
 
                 return true;
@@ -349,7 +349,7 @@ namespace Terminal.Gui
                 {
                     string oldValue = Text;
                     _text.Insert (pos, (Rune)ch);
-                    OnTextChanged (new EventArgs<string> (ref oldValue));
+                    OnTextChanged (new EventArgs<string> (in oldValue));
 
                     return true;
                 }
@@ -464,7 +464,6 @@ namespace Terminal.Gui
             KeyBindings.Add (Key.Home, Command.LeftHome);
             KeyBindings.Add (Key.End, Command.RightEnd);
 
-            KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
             KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
 
             KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
@@ -665,6 +664,11 @@ namespace Terminal.Gui
         /// <returns>True if moved.</returns>
         private bool CursorLeft ()
         {
+            if (_provider is null)
+            {
+                return false;
+            }
+
             int current = _cursorPosition;
             _cursorPosition = _provider.CursorLeft (_cursorPosition);
             SetNeedsDisplay ();
@@ -676,6 +680,11 @@ namespace Terminal.Gui
         /// <returns>True if moved.</returns>
         private bool CursorRight ()
         {
+            if (_provider is null)
+            {
+                return false;
+            }
+
             int current = _cursorPosition;
             _cursorPosition = _provider.CursorRight (_cursorPosition);
             SetNeedsDisplay ();

+ 40 - 82
Terminal.Gui/Views/TextView.cs

@@ -2045,15 +2045,7 @@ public class TextView : View
                     }
                    );
 
-        AddCommand (
-                    Command.LineDown,
-                    () =>
-                    {
-                        ProcessMoveDown ();
-
-                        return true;
-                    }
-                   );
+        AddCommand (Command.LineDown, () => ProcessMoveDown ());
 
         AddCommand (
                     Command.LineDownExtend,
@@ -2065,15 +2057,7 @@ public class TextView : View
                     }
                    );
 
-        AddCommand (
-                    Command.LineUp,
-                    () =>
-                    {
-                        ProcessMoveUp ();
-
-                        return true;
-                    }
-                   );
+        AddCommand (Command.LineUp, () => ProcessMoveUp ());
 
         AddCommand (
                     Command.LineUpExtend,
@@ -2369,8 +2353,6 @@ public class TextView : View
                    );
         AddCommand (Command.Tab, () => ProcessTab ());
         AddCommand (Command.BackTab, () => ProcessBackTab ());
-        AddCommand (Command.NextView, () => ProcessMoveNextView ());
-        AddCommand (Command.PreviousView, () => ProcessMovePreviousView ());
 
         AddCommand (
                     Command.Undo,
@@ -2503,12 +2485,6 @@ public class TextView : View
         KeyBindings.Add (Key.Tab, Command.Tab);
         KeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
 
-        KeyBindings.Add (Key.Tab.WithCtrl, Command.NextView);
-        KeyBindings.Add (Application.AlternateForwardKey, Command.NextView);
-
-        KeyBindings.Add (Key.Tab.WithCtrl.WithShift, Command.PreviousView);
-        KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousView);
-
         KeyBindings.Add (Key.Z.WithCtrl, Command.Undo);
         KeyBindings.Add (Key.R.WithCtrl, Command.Redo);
 
@@ -4318,7 +4294,7 @@ public class TextView : View
         DoNeededAction ();
     }
 
-    private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey, e.NewKey); }
+    private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.ReplaceKey (e.OldKey, e.NewKey); }
 
     private bool DeleteTextBackwards ()
     {
@@ -5302,7 +5278,7 @@ public class TextView : View
         MoveEnd ();
     }
 
-    private void MoveDown ()
+    private bool MoveDown ()
     {
         if (CurrentRow + 1 < _model.Count)
         {
@@ -5326,8 +5302,14 @@ public class TextView : View
         {
             Adjust ();
         }
+        else
+        {
+            return false;
+        }
 
         DoNeededAction ();
+
+        return true;
     }
 
     private void MoveEndOfLine ()
@@ -5338,7 +5320,7 @@ public class TextView : View
         DoNeededAction ();
     }
 
-    private void MoveLeft ()
+    private bool MoveLeft ()
     {
         if (CurrentColumn > 0)
         {
@@ -5359,20 +5341,16 @@ public class TextView : View
                 List<RuneCell> currentLine = GetCurrentLine ();
                 CurrentColumn = Math.Max (currentLine.Count - (ReadOnly ? 1 : 0), 0);
             }
+            else
+            {
+                return false;
+            }
         }
 
         Adjust ();
         DoNeededAction ();
-    }
-
-    private bool MoveNextView ()
-    {
-        if (Application.OverlappedTop is { })
-        {
-            return SuperView?.FocusNext () == true;
-        }
 
-        return false;
+        return true;
     }
 
     private void MovePageDown ()
@@ -5431,17 +5409,7 @@ public class TextView : View
         DoNeededAction ();
     }
 
-    private bool MovePreviousView ()
-    {
-        if (Application.OverlappedTop is { })
-        {
-            return SuperView?.FocusPrev () == true;
-        }
-
-        return false;
-    }
-
-    private void MoveRight ()
+    private bool MoveRight ()
     {
         List<RuneCell> currentLine = GetCurrentLine ();
 
@@ -5461,11 +5429,21 @@ public class TextView : View
                     _topRow++;
                     SetNeedsDisplay ();
                 }
+                else
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                return false;
             }
         }
 
         Adjust ();
         DoNeededAction ();
+
+        return true;
     }
 
     private void MoveStartOfLine ()
@@ -5500,7 +5478,7 @@ public class TextView : View
         MoveHome ();
     }
 
-    private void MoveUp ()
+    private bool MoveUp ()
     {
         if (CurrentRow > 0)
         {
@@ -5520,8 +5498,13 @@ public class TextView : View
             TrackColumn ();
             PositionCursor ();
         }
+        else
+        {
+            return false;
+        }
 
         DoNeededAction ();
+        return true;
     }
 
     private void MoveWordBackward ()
@@ -5617,7 +5600,7 @@ public class TextView : View
 
         if (!AllowsTab || _isReadOnly)
         {
-            return ProcessMovePreviousView ();
+            return false;
         }
 
         if (CurrentColumn > 0)
@@ -5824,16 +5807,15 @@ public class TextView : View
         line = r!;
     }
 
-    private void ProcessMoveDown ()
+    private bool ProcessMoveDown ()
     {
         ResetContinuousFindTrack ();
-
         if (_shiftSelecting && Selecting)
         {
             StopSelecting ();
         }
 
-        MoveDown ();
+        return MoveDown ();
     }
 
     private void ProcessMoveDownExtend ()
@@ -5890,20 +5872,6 @@ public class TextView : View
         MoveLeft ();
     }
 
-    private bool ProcessMoveNextView ()
-    {
-        ResetColumnTrack ();
-
-        return MoveNextView ();
-    }
-
-    private bool ProcessMovePreviousView ()
-    {
-        ResetColumnTrack ();
-
-        return MovePreviousView ();
-    }
-
     private bool ProcessMoveRight ()
     {
         // if the user presses Right (without any control keys)
@@ -5955,7 +5923,7 @@ public class TextView : View
         MoveStartOfLine ();
     }
 
-    private void ProcessMoveUp ()
+    private bool ProcessMoveUp ()
     {
         ResetContinuousFindTrack ();
 
@@ -5964,7 +5932,7 @@ public class TextView : View
             StopSelecting ();
         }
 
-        MoveUp ();
+        return MoveUp ();
     }
 
     private void ProcessMoveUpExtend ()
@@ -6062,7 +6030,7 @@ public class TextView : View
         Paste ();
     }
 
-    private bool? ProcessReturn ()
+    private bool ProcessReturn ()
     {
         ResetColumnTrack ();
 
@@ -6163,7 +6131,7 @@ public class TextView : View
 
         if (!AllowsTab || _isReadOnly)
         {
-            return ProcessMoveNextView ();
+            return false;
         }
 
         InsertText (new Key ((KeyCode)'\t'));
@@ -6369,13 +6337,6 @@ public class TextView : View
     private void TextView_Initialized (object sender, EventArgs e)
     {
         Autocomplete.HostControl = this;
-
-        if (Application.Top is { })
-        {
-            Application.Top.AlternateForwardKeyChanged += Top_AlternateForwardKeyChanged!;
-            Application.Top.AlternateBackwardKeyChanged += Top_AlternateBackwardKeyChanged!;
-        }
-
         OnContentsChanged ();
     }
 
@@ -6393,9 +6354,6 @@ public class TextView : View
         _selectionStartRow = CurrentRow;
     }
 
-    private void Top_AlternateBackwardKeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey, e.NewKey); }
-    private void Top_AlternateForwardKeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey, e.NewKey); }
-
     // Tries to snap the cursor to the tracking column
     private void TrackColumn ()
     {

+ 1 - 1
Terminal.Gui/Views/Tile.cs

@@ -62,7 +62,7 @@ public class Tile
     /// <param name="newTitle">The new <see cref="Title"/> to be replaced.</param>
     public virtual void OnTitleChanged (string oldTitle, string newTitle)
     {
-        var args = new EventArgs<string> (ref newTitle);
+        var args = new EventArgs<string> (in newTitle);
         TitleChanged?.Invoke (this, args);
     }
 

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

@@ -871,7 +871,7 @@ public class TileView : View
         public TileViewLineView (TileView parent, int idx)
         {
             CanFocus = false;
-            TabStop = true;
+            TabStop = TabBehavior.TabStop;
 
             Parent = parent;
             Idx = idx;
@@ -908,7 +908,7 @@ public class TileView : View
             {
                 // Start a Drag
                 SetFocus ();
-                Application.BringOverlappedTopToFront ();
+                ApplicationOverlapped.BringOverlappedTopToFront ();
 
                 if (mouseEvent.Flags == MouseFlags.Button1Pressed)
                 {

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

@@ -58,26 +58,26 @@ public class TimeField : TextField
         AddCommand (Command.RightEnd, () => MoveEnd ());
         AddCommand (Command.Right, () => MoveRight ());
 
-        // Default keybindings for this view
-        KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
-        KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
+        // Replace the key bindings defined in TextField
+        KeyBindings.ReplaceCommands (Key.Delete, Command.DeleteCharRight);
+        KeyBindings.ReplaceCommands (Key.D.WithCtrl, Command.DeleteCharRight);
 
-        KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
+        KeyBindings.ReplaceCommands (Key.Backspace, Command.DeleteCharLeft);
 
-        KeyBindings.Add (Key.Home, Command.LeftHome);
-        KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome);
+        KeyBindings.ReplaceCommands (Key.Home, Command.LeftHome);
+        KeyBindings.ReplaceCommands (Key.A.WithCtrl, Command.LeftHome);
 
-        KeyBindings.Add (Key.CursorLeft, Command.Left);
-        KeyBindings.Add (Key.B.WithCtrl, Command.Left);
+        KeyBindings.ReplaceCommands (Key.CursorLeft, Command.Left);
+        KeyBindings.ReplaceCommands (Key.B.WithCtrl, Command.Left);
 
-        KeyBindings.Add (Key.End, Command.RightEnd);
-        KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
+        KeyBindings.ReplaceCommands (Key.End, Command.RightEnd);
+        KeyBindings.ReplaceCommands (Key.E.WithCtrl, Command.RightEnd);
 
-        KeyBindings.Add (Key.CursorRight, Command.Right);
-        KeyBindings.Add (Key.F.WithCtrl, Command.Right);
+        KeyBindings.ReplaceCommands (Key.CursorRight, Command.Right);
+        KeyBindings.ReplaceCommands (Key.F.WithCtrl, Command.Right);
 
 #if UNIX_KEY_BINDINGS
-        KeyBindings.Add (Key.D.WithAlt, Command.DeleteCharLeft);
+        KeyBindings.ReplaceCommands (Key.D.WithAlt, Command.DeleteCharLeft);
 #endif
     }
 

File diff suppressed because it is too large
+ 264 - 517
Terminal.Gui/Views/Toplevel.cs


+ 0 - 212
Terminal.Gui/Views/ToplevelOverlapped.cs

@@ -2,219 +2,7 @@
 
 public partial class Toplevel
 {
-    /// <summary>Gets or sets if this Toplevel is in overlapped mode within a Toplevel container.</summary>
-    public bool IsOverlapped => Application.OverlappedTop is { } && Application.OverlappedTop != this && !Modal;
-
     /// <summary>Gets or sets if this Toplevel is a container for overlapped children.</summary>
     public bool IsOverlappedContainer { get; set; }
 }
 
-public static partial class Application
-{
-    /// <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 ();
-
-                foreach (Toplevel top in _topLevels)
-                {
-                    if (top != OverlappedTop && !top.Modal)
-                    {
-                        _overlappedChildren.Add (top);
-                    }
-                }
-
-                return _overlappedChildren;
-            }
-
-            return null;
-        }
-    }
-
-    #nullable enable
-    /// <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 (Top is { IsOverlappedContainer: true })
-            {
-                return Top;
-            }
-
-            return null;
-        }
-    }
-    #nullable restore
-
-    /// <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 (Top?.MostFocused);
-
-        if (top is Toplevel && Top.Subviews.Count > 1 && Top.Subviews [^1] != top)
-        {
-            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 (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>
-    ///     Move to the next Overlapped child from the <see cref="OverlappedTop"/> and set it as the <see cref="Top"/> if
-    ///     it is not already.
-    /// </summary>
-    /// <param name="top"></param>
-    /// <returns></returns>
-    public static bool MoveToOverlappedChild (Toplevel top)
-    {
-        if (top.Visible && OverlappedTop is { } && Current?.Modal == false)
-        {
-            lock (_topLevels)
-            {
-                _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
-                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 { } && !Current.Modal)
-        {
-            lock (_topLevels)
-            {
-                _topLevels.MoveNext ();
-                var isOverlapped = false;
-
-                while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible)
-                {
-                    if (!isOverlapped && _topLevels.Peek () == OverlappedTop)
-                    {
-                        isOverlapped = true;
-                    }
-                    else if (isOverlapped && _topLevels.Peek () == OverlappedTop)
-                    {
-                        MoveCurrent (Top);
-
-                        break;
-                    }
-
-                    _topLevels.MoveNext ();
-                }
-
-                Current = _topLevels.Peek ();
-            }
-        }
-    }
-
-    /// <summary>Move to the previous Overlapped child from the <see cref="OverlappedTop"/>.</summary>
-    public static void OverlappedMovePrevious ()
-    {
-        if (OverlappedTop is { } && !Current.Modal)
-        {
-            lock (_topLevels)
-            {
-                _topLevels.MovePrevious ();
-                var isOverlapped = false;
-
-                while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible)
-                {
-                    if (!isOverlapped && _topLevels.Peek () == OverlappedTop)
-                    {
-                        isOverlapped = true;
-                    }
-                    else if (isOverlapped && _topLevels.Peek () == OverlappedTop)
-                    {
-                        MoveCurrent (Top);
-
-                        break;
-                    }
-
-                    _topLevels.MovePrevious ();
-                }
-
-                Current = _topLevels.Peek ();
-            }
-        }
-    }
-
-    private static bool OverlappedChildNeedsDisplay ()
-    {
-        if (OverlappedTop is null)
-        {
-            return false;
-        }
-
-        foreach (Toplevel top in _topLevels)
-        {
-            if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded))
-            {
-                OverlappedTop.SetSubViewNeedsDisplay ();
-
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private static bool SetCurrentOverlappedAsTop ()
-    {
-        if (OverlappedTop is null && Current != Top && Current?.SuperView is null && Current?.Modal == false)
-        {
-            Top = Current;
-
-            return true;
-        }
-
-        return false;
-    }
-}

+ 1 - 1
Terminal.Gui/Views/TreeView/TreeView.cs

@@ -352,7 +352,7 @@ public class TreeView<T> : View, ITreeView where T : class
         {
             if (objectActivationKey != value)
             {
-                KeyBindings.Replace (ObjectActivationKey, value);
+                KeyBindings.ReplaceKey (ObjectActivationKey, value);
                 objectActivationKey = value;
             }
         }

+ 2 - 0
Terminal.Gui/Views/Window.cs

@@ -28,6 +28,8 @@ public class Window : Toplevel
     public Window ()
     {
         CanFocus = true;
+        TabStop = TabBehavior.TabGroup;
+        Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped;
         ColorScheme = Colors.ColorSchemes ["Base"]; // TODO: make this a theme property
         BorderStyle = DefaultBorderStyle;
         ShadowStyle = DefaultShadow;

+ 3 - 1
Terminal.sln.DotSettings

@@ -1,4 +1,4 @@
-<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
 	<s:String x:Key="/Default/CodeEditing/GenerateMemberBody/AccessorImplementationKind/@EntryValue">BackingField</s:String>
 	<s:String x:Key="/Default/CodeEditing/GenerateMemberBody/DocumentationGenerationKind/@EntryValue">Inherit</s:String>
 	<s:Boolean x:Key="/Default/CodeEditing/GenerateMemberBody/PlaceBackingFieldAboveProperty/@EntryValue">True</s:Boolean>
@@ -390,6 +390,7 @@
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=70345118_002D4b40_002D4ece_002D937c_002Dbbeb7a0b2e70/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"&gt;&lt;ElementKinds&gt;&lt;Kind Name="NAMESPACE" /&gt;&lt;Kind Name="CLASS" /&gt;&lt;Kind Name="STRUCT" /&gt;&lt;Kind Name="ENUM" /&gt;&lt;Kind Name="DELEGATE" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4f433b8_002Dabcd_002D4e55_002Da08f_002D82e78cef0f0c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"&gt;&lt;ElementKinds&gt;&lt;Kind Name="LOCAL_CONSTANT" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=c873eafb_002Dd57f_002D481d_002D8c93_002D77f6863c2f88/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
 	<s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=A92AB40A0394A342A0BE0D83FEEA5DBF/@KeyIndexDefined">True</s:Boolean>
 	<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=A92AB40A0394A342A0BE0D83FEEA5DBF/RelativePath/@EntryValue">..\Terminal.sln.ToDo.DotSettings</s:String>
@@ -404,6 +405,7 @@
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Justifier/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=langword/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Roslynator/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Toplevel/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=unsynchronized/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=BUGBUG/@EntryIndexedValue">True</s:Boolean>
 </wpf:ResourceDictionary>

+ 7 - 1
UICatalog/Scenarios/AdornmentEditor.cs

@@ -90,6 +90,8 @@ public class AdornmentEditor : View
 
         BorderStyle = LineStyle.Dashed;
         Initialized += AdornmentEditor_Initialized;
+
+        TabStop = TabBehavior.TabGroup;
     }
 
     private void AdornmentEditor_Initialized (object sender, EventArgs e)
@@ -100,6 +102,7 @@ public class AdornmentEditor : View
         _topEdit = new ()
         {
             X = Pos.Center (), Y = 0,
+            Format = "{0, 2}",
             Enabled = false
         };
 
@@ -108,7 +111,8 @@ public class AdornmentEditor : View
 
         _leftEdit = new ()
         {
-            X = Pos.Left (_topEdit) - Pos.Func (() => _topEdit.Digits) - 2, Y = Pos.Bottom (_topEdit),
+            X = Pos.Left (_topEdit) - Pos.Func (() => _topEdit.Text.Length) - 2, Y = Pos.Bottom (_topEdit),
+            Format = _topEdit.Format,
             Enabled = false
         };
 
@@ -118,6 +122,7 @@ public class AdornmentEditor : View
         _rightEdit = new ()
         {
             X = Pos.Right (_leftEdit) + 5, Y = Pos.Bottom (_topEdit),
+            Format = _topEdit.Format,
             Enabled = false
         };
 
@@ -127,6 +132,7 @@ public class AdornmentEditor : View
         _bottomEdit = new ()
         {
             X = Pos.Center (), Y = Pos.Bottom (_leftEdit),
+            Format = _topEdit.Format,
             Enabled = false
         };
 

+ 81 - 63
UICatalog/Scenarios/AdornmentsEditor.cs

@@ -9,6 +9,24 @@ namespace UICatalog.Scenarios;
 /// </summary>
 public class AdornmentsEditor : View
 {
+    public AdornmentsEditor ()
+    {
+        //ColorScheme = Colors.ColorSchemes ["Dialog"];
+        Title = "AdornmentsEditor";
+
+        Width = Dim.Auto (DimAutoStyle.Content);
+        Height = Dim.Auto (DimAutoStyle.Content);
+
+        //SuperViewRendersLineCanvas = true;
+
+        TabStop = TabBehavior.TabGroup;
+
+        //Application.MouseEvent += Application_MouseEvent;
+        Application.Navigation!.FocusedChanged += ApplicationNavigationOnFocusedChanged;
+        Initialized += AdornmentsEditor_Initialized;
+    }
+
+    private readonly ViewDiagnosticFlags _savedDiagnosticFlags = Diagnostics;
     private View _viewToEdit;
 
     private Label _lblView; // Text describing the vi
@@ -20,33 +38,45 @@ public class AdornmentsEditor : View
     // TODO: Move Diagnostics to a separate Editor class (DiagnosticsEditor?).
     private CheckBox _diagPaddingCheckBox;
     private CheckBox _diagRulerCheckBox;
-    private readonly ViewDiagnosticFlags _savedDiagnosticFlags = Diagnostics;
 
-    public AdornmentsEditor ()
+    /// <summary>
+    ///     Gets or sets whether the AdornmentsEditor should automatically select the View to edit when the mouse is clicked
+    ///     anywhere outside the editor.
+    /// </summary>
+    public bool AutoSelectViewToEdit { get; set; }
+
+    public View ViewToEdit
     {
-        //ColorScheme = Colors.ColorSchemes ["Dialog"];
-        Title = $"AdornmentsEditor";
+        get => _viewToEdit;
+        set
+        {
+            if (_viewToEdit == value)
+            {
+                return;
+            }
 
-        Width = Dim.Auto (DimAutoStyle.Content);
-        Height = Dim.Auto (DimAutoStyle.Content);
+            _viewToEdit = value;
 
-        //SuperViewRendersLineCanvas = true;
+            _marginEditor.AdornmentToEdit = _viewToEdit?.Margin ?? null;
+            _borderEditor.AdornmentToEdit = _viewToEdit?.Border ?? null;
+            _paddingEditor.AdornmentToEdit = _viewToEdit?.Padding ?? null;
 
-        Application.MouseEvent += Application_MouseEvent;
-        Initialized += AdornmentsEditor_Initialized;
+            _lblView.Text = $"{_viewToEdit?.GetType ().Name}: {_viewToEdit?.Id}" ?? string.Empty;
+        }
     }
 
-    /// <summary>
-    /// Gets or sets whether the AdornmentsEditor should automatically select the View to edit when the mouse is clicked
-    /// anywhere outside the editor.
-    /// </summary>
-    public bool AutoSelectViewToEdit { get; set; }
+    /// <inheritdoc/>
+    protected override void Dispose (bool disposing)
+    {
+        Diagnostics = _savedDiagnosticFlags;
+        base.Dispose (disposing);
+    }
 
     private void AdornmentsEditor_Initialized (object sender, EventArgs e)
     {
         BorderStyle = LineStyle.Dotted;
 
-        ExpanderButton expandButton = new ExpanderButton ()
+        var expandButton = new ExpanderButton
         {
             Orientation = Orientation.Horizontal
         };
@@ -56,7 +86,7 @@ public class AdornmentsEditor : View
         {
             X = 0,
             Y = 0,
-            Height = 2,
+            Height = 2
         };
         _lblView.TextFormatter.WordWrap = true;
         _lblView.TextFormatter.MultiLine = true;
@@ -93,34 +123,34 @@ public class AdornmentsEditor : View
         _diagPaddingCheckBox.State = Diagnostics.FastHasFlags (ViewDiagnosticFlags.Padding) ? CheckState.Checked : CheckState.UnChecked;
 
         _diagPaddingCheckBox.Toggle += (s, e) =>
-                                        {
-                                            if (e.NewValue == CheckState.Checked)
-                                            {
-                                                Diagnostics |= ViewDiagnosticFlags.Padding;
-                                            }
-                                            else
-                                            {
-                                                Diagnostics &= ~ViewDiagnosticFlags.Padding;
-                                            }
-                                        };
+                                       {
+                                           if (e.NewValue == CheckState.Checked)
+                                           {
+                                               Diagnostics |= ViewDiagnosticFlags.Padding;
+                                           }
+                                           else
+                                           {
+                                               Diagnostics &= ~ViewDiagnosticFlags.Padding;
+                                           }
+                                       };
 
         Add (_diagPaddingCheckBox);
         _diagPaddingCheckBox.Y = Pos.Bottom (_paddingEditor);
 
         _diagRulerCheckBox = new () { Text = "_Diagnostic Ruler" };
-        _diagRulerCheckBox.State = Diagnostics.FastHasFlags(ViewDiagnosticFlags.Ruler) ? CheckState.Checked : CheckState.UnChecked;
+        _diagRulerCheckBox.State = Diagnostics.FastHasFlags (ViewDiagnosticFlags.Ruler) ? CheckState.Checked : CheckState.UnChecked;
 
         _diagRulerCheckBox.Toggle += (s, e) =>
-                                      {
-                                          if (e.NewValue == CheckState.Checked)
-                                          {
-                                              Diagnostics |= ViewDiagnosticFlags.Ruler;
-                                          }
-                                          else
-                                          {
-                                              Diagnostics &= ~ViewDiagnosticFlags.Ruler;
-                                          }
-                                      };
+                                     {
+                                         if (e.NewValue == CheckState.Checked)
+                                         {
+                                             Diagnostics |= ViewDiagnosticFlags.Ruler;
+                                         }
+                                         else
+                                         {
+                                             Diagnostics &= ~ViewDiagnosticFlags.Ruler;
+                                         }
+                                     };
 
         Add (_diagRulerCheckBox);
         _diagRulerCheckBox.Y = Pos.Bottom (_diagPaddingCheckBox);
@@ -134,7 +164,8 @@ public class AdornmentsEditor : View
         }
 
         // TODO: Add a setting (property) so only subviews of a specified view are considered.
-        var view = e.View;
+        View view = e.View;
+
         if (view is { } && e.Flags == MouseFlags.Button1Clicked)
         {
             if (view is Adornment adornment)
@@ -148,33 +179,20 @@ public class AdornmentsEditor : View
         }
     }
 
-    /// <inheritdoc />
-    protected override void Dispose (bool disposing)
-    {
-        View.Diagnostics = _savedDiagnosticFlags;
-        base.Dispose (disposing);
-    }
-
-    public View ViewToEdit
+    private void ApplicationNavigationOnFocusedChanged (object sender, EventArgs e)
     {
-        get => _viewToEdit;
-        set
+        if (ApplicationNavigation.IsInHierarchy (this, Application.Navigation!.GetFocused ()))
         {
-            if (_viewToEdit == value)
-            {
-                return;
-            }
-
-            _viewToEdit = value;
-
-
-            _marginEditor.AdornmentToEdit = _viewToEdit.Margin ?? null;
-            _borderEditor.AdornmentToEdit = _viewToEdit.Border ?? null;
-            _paddingEditor.AdornmentToEdit = _viewToEdit.Padding ?? null;
-
-            _lblView.Text = _viewToEdit.ToString ();
-
             return;
         }
+
+        if (Application.Navigation!.GetFocused () is Adornment adornment)
+        {
+            ViewToEdit = adornment.Parent;
+        }
+        else
+        {
+            ViewToEdit = Application.Navigation.GetFocused ();
+        }
     }
-}
+}

+ 13 - 13
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -20,10 +20,10 @@ public class BackgroundWorkerCollection : Scenario
         Application.Run<OverlappedMain> ().Dispose ();
 
 #if DEBUG_IDISPOSABLE
-        if (Application.OverlappedChildren is { })
+        if (ApplicationOverlapped.OverlappedChildren is { })
         {
-            Debug.Assert (Application.OverlappedChildren?.Count == 0);
-            Debug.Assert (Application.Top == Application.OverlappedTop);
+            Debug.Assert (ApplicationOverlapped.OverlappedChildren?.Count == 0);
+            Debug.Assert (Application.Top == ApplicationOverlapped.OverlappedTop);
         }
 #endif
 
@@ -134,7 +134,7 @@ public class BackgroundWorkerCollection : Scenario
         {
             var index = 1;
             List<MenuItem> menuItems = new ();
-            List<Toplevel> sortedChildren = Application.OverlappedChildren;
+            List<Toplevel> sortedChildren = ApplicationOverlapped.OverlappedChildren;
             sortedChildren.Sort (new ToplevelComparer ());
 
             foreach (Toplevel top in sortedChildren)
@@ -151,7 +151,7 @@ public class BackgroundWorkerCollection : Scenario
                 string topTitle = top is Window ? ((Window)top).Title : top.Data.ToString ();
                 string itemTitle = item.Title.Substring (index.ToString ().Length + 1);
 
-                if (top == Application.GetTopOverlappedChild () && topTitle == itemTitle)
+                if (top == ApplicationOverlapped.GetTopOverlappedChild () && topTitle == itemTitle)
                 {
                     item.Checked = true;
                 }
@@ -160,7 +160,7 @@ public class BackgroundWorkerCollection : Scenario
                     item.Checked = false;
                 }
 
-                item.Action += () => { Application.MoveToOverlappedChild (top); };
+                item.Action += () => { ApplicationOverlapped.MoveToOverlappedChild (top); };
                 menuItems.Add (item);
             }
 
@@ -188,7 +188,7 @@ public class BackgroundWorkerCollection : Scenario
         {
             List<MenuItem> menuItems = new ();
             var item = new MenuItem { Title = "WorkerApp", CheckType = MenuItemCheckStyle.Checked };
-            Toplevel top = Application.OverlappedChildren?.Find (x => x.Data.ToString () == "WorkerApp");
+            Toplevel top = ApplicationOverlapped.OverlappedChildren?.Find (x => x.Data.ToString () == "WorkerApp");
 
             if (top != null)
             {
@@ -197,16 +197,16 @@ public class BackgroundWorkerCollection : Scenario
 
             item.Action += () =>
                            {
-                               Toplevel top = Application.OverlappedChildren.Find (x => x.Data.ToString () == "WorkerApp");
+                               Toplevel top = ApplicationOverlapped.OverlappedChildren.Find (x => x.Data.ToString () == "WorkerApp");
                                item.Checked = top.Visible = (bool)!item.Checked;
 
                                if (top.Visible)
                                {
-                                   Application.MoveToOverlappedChild (top);
+                                   ApplicationOverlapped.MoveToOverlappedChild (top);
                                }
                                else
                                {
-                                   Application.OverlappedTop.SetNeedsDisplay ();
+                                   ApplicationOverlapped.OverlappedTop!.SetNeedsDisplay ();
                                }
                            };
             menuItems.Add (item);
@@ -373,14 +373,14 @@ public class BackgroundWorkerCollection : Scenario
         }
         private void WorkerApp_Closing (object sender, ToplevelClosingEventArgs e)
         {
-            Toplevel top = Application.OverlappedChildren.Find (x => x.Data.ToString () == "WorkerApp");
+            Toplevel top = ApplicationOverlapped.OverlappedChildren!.Find (x => x.Data.ToString () == "WorkerApp");
 
             if (Visible && top == this)
             {
                 Visible = false;
                 e.Cancel = true;
 
-                Application.OverlappedMoveNext ();
+                ApplicationOverlapped.OverlappedMoveNext ();
             }
         }
 
@@ -481,7 +481,7 @@ public class BackgroundWorkerCollection : Scenario
                                                  _stagingsUi.Add (stagingUI);
                                                  _stagingWorkers.Remove (staging);
 #if DEBUG_IDISPOSABLE
-                                                 if (Application.OverlappedTop is null)
+                                                 if (ApplicationOverlapped.OverlappedTop is null)
                                                  {
                                                      stagingUI.Dispose ();
                                                      return;

+ 16 - 4
UICatalog/Scenarios/Buttons.cs

@@ -22,7 +22,7 @@ public class Buttons : Scenario
         };
 
         // Add a label & text field so we can demo IsDefault
-        var editLabel = new Label { X = 0, Y = 0, TabStop = true, Text = "TextField (to demo IsDefault):" };
+        var editLabel = new Label { X = 0, Y = 0, Text = "TextField (to demo IsDefault):" };
         main.Add (editLabel);
 
         // Add a TextField using Absolute layout. 
@@ -336,8 +336,6 @@ public class Buttons : Scenario
             Value = 69,
             X = Pos.Right (label) + 1,
             Y = Pos.Top (label),
-            Width = 5,
-            Height = 1
         };
         numericUpDown.ValueChanged += NumericUpDown_ValueChanged;
 
@@ -390,7 +388,21 @@ public class Buttons : Scenario
         enableCB.Toggle += (s, e) => { repeatButton.Enabled = !repeatButton.Enabled; };
         main.Add (label, repeatButton, enableCB);
 
-        main.Ready += (s, e) => radioGroup.Refresh ();
+        //var decNumericUpDown = new NumericUpDown<decimal>
+        //{
+        //    Value = 42.11m,
+        //    Increment = 11.31m,
+        //    Format = "{0:0%}",
+        //    X = 0,
+        //    Y = Pos.Bottom (enableCB) + 1,
+        //};
+
+        //main.Add (decNumericUpDown);
+
+        main.Ready += (s, e) =>
+                      {
+                          radioGroup.Refresh ();
+                      };
         Application.Run (main);
         main.Dispose ();
         Application.Shutdown ();

+ 14 - 14
UICatalog/Scenarios/CombiningMarks.cs

@@ -15,20 +15,20 @@ public class CombiningMarks : Scenario
 
         top.DrawContentComplete += (s, e) =>
                                    {
-                                       Application.Driver.Move (0, 0);
-                                       Application.Driver.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
-                                       Application.Driver.Move (0, 2);
-                                       Application.Driver.AddStr ("\u0301\u0301\u0328<- \"\\u301\\u301\\u328]\" using AddStr.");
-                                       Application.Driver.Move (0, 3);
-                                       Application.Driver.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u301\\u301\\u328]\" using AddStr.");
-                                       Application.Driver.Move (0, 4);
-                                       Application.Driver.AddRune ('[');
-                                       Application.Driver.AddRune ('a');
-                                       Application.Driver.AddRune ('\u0301');
-                                       Application.Driver.AddRune ('\u0301');
-                                       Application.Driver.AddRune ('\u0328');
-                                       Application.Driver.AddRune (']');
-                                       Application.Driver.AddStr ("<- \"[a\\u301\\u301\\u328]\" using AddRune for each.");
+                                       Application.Driver?.Move (0, 0);
+                                       Application.Driver?.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
+                                       Application.Driver?.Move (0, 2);
+                                       Application.Driver?.AddStr ("\u0301\u0301\u0328<- \"\\u301\\u301\\u328]\" using AddStr.");
+                                       Application.Driver?.Move (0, 3);
+                                       Application.Driver?.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u301\\u301\\u328]\" using AddStr.");
+                                       Application.Driver?.Move (0, 4);
+                                       Application.Driver?.AddRune ('[');
+                                       Application.Driver?.AddRune ('a');
+                                       Application.Driver?.AddRune ('\u0301');
+                                       Application.Driver?.AddRune ('\u0301');
+                                       Application.Driver?.AddRune ('\u0328');
+                                       Application.Driver?.AddRune (']');
+                                       Application.Driver?.AddStr ("<- \"[a\\u301\\u301\\u328]\" using AddRune for each.");
                                    };
 
         Application.Run (top);

+ 7 - 7
UICatalog/Scenarios/Editor.cs

@@ -238,7 +238,7 @@ public class Editor : Scenario
 
         _appWindow.Add (menu);
 
-        var siCursorPosition = new Shortcut(KeyCode.Null, "", null);
+        var siCursorPosition = new Shortcut (KeyCode.Null, "", null);
 
         var statusBar = new StatusBar (
                                        new []
@@ -722,7 +722,7 @@ public class Editor : Scenario
             }
             else
             {
-                FocusFirst();
+                FocusFirst (null);
             }
         }
 
@@ -737,9 +737,9 @@ public class Editor : Scenario
     {
         _findReplaceWindow.Visible = true;
         _findReplaceWindow.SuperView.BringSubviewToFront (_findReplaceWindow);
-        _tabView.SetFocus();
+        _tabView.SetFocus ();
         _tabView.SelectedTab = isFind ? _tabView.Tabs.ToArray () [0] : _tabView.Tabs.ToArray () [1];
-        _tabView.SelectedTab.View.FocusFirst ();
+        _tabView.SelectedTab.View.FocusFirst (null);
     }
 
     private void CreateFindReplace ()
@@ -753,10 +753,10 @@ public class Editor : Scenario
 
         _tabView.AddTab (new () { DisplayText = "Find", View = CreateFindTab () }, true);
         _tabView.AddTab (new () { DisplayText = "Replace", View = CreateReplaceTab () }, false);
-        _tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusFirst ();
+        _tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusFirst (null);
         _findReplaceWindow.Add (_tabView);
 
-        _tabView.SelectedTab.View.FocusLast (); // Hack to get the first tab to be focused
+        _tabView.SelectedTab.View.FocusLast (null); // Hack to get the first tab to be focused
         _findReplaceWindow.Visible = false;
         _appWindow.Add (_findReplaceWindow);
     }
@@ -828,7 +828,7 @@ public class Editor : Scenario
         }
     }
 
-    private void Find () { ShowFindReplace(true); }
+    private void Find () { ShowFindReplace (true); }
     private void FindNext () { ContinueFind (); }
     private void FindPrevious () { ContinueFind (false); }
 

+ 2 - 2
UICatalog/Scenarios/Images.cs

@@ -20,9 +20,9 @@ public class Images : Scenario
         Application.Init ();
         var win = new Window { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName()}" };
 
-        bool canTrueColor = Application.Driver.SupportsTrueColor;
+        bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false;
 
-        var lblDriverName = new Label { X = 0, Y = 0, Text = $"Driver is {Application.Driver.GetType ().Name}" };
+        var lblDriverName = new Label { X = 0, Y = 0, Text = $"Driver is {Application.Driver?.GetType ().Name}" };
         win.Add (lblDriverName);
 
         var cbSupportsTrueColor = new CheckBox

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