Browse Source

Merge remote-tracking branch 'refs/remotes/origin/v2_2491-Toplevel-Redesign' into v2_2491-Toplevel-Redesign

Tig 1 year ago
parent
commit
a935ef83df
100 changed files with 4816 additions and 4486 deletions
  1. 1 1
      CommunityToolkitExample/Program.cs
  2. 45 0
      Terminal.Gui/Application/Application .Screen.cs
  3. 29 0
      Terminal.Gui/Application/Application.Driver.cs
  4. 212 0
      Terminal.Gui/Application/Application.Initialization.cs
  5. 428 0
      Terminal.Gui/Application/Application.Keyboard.cs
  6. 28 28
      Terminal.Gui/Application/Application.Mouse.cs
  7. 194 0
      Terminal.Gui/Application/Application.Navigation.cs
  8. 384 0
      Terminal.Gui/Application/Application.Overlapped.cs
  9. 863 0
      Terminal.Gui/Application/Application.Run.cs
  10. 56 0
      Terminal.Gui/Application/Application.Toplevel.cs
  11. 14 1327
      Terminal.Gui/Application/Application.cs
  12. 0 303
      Terminal.Gui/Application/ApplicationKeyboard.cs
  13. 1 1
      Terminal.Gui/Application/MainLoopSyncContext.cs
  14. 11 22
      Terminal.Gui/Clipboard/Clipboard.cs
  15. 114 144
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  16. 44 0
      Terminal.Gui/ConsoleDrivers/CursorVisibility.cs
  17. 9 9
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  18. 3 3
      Terminal.Gui/Drawing/LineCanvas.cs
  19. 4 4
      Terminal.Gui/Drawing/Ruler.cs
  20. 9 5
      Terminal.Gui/Drawing/Thickness.cs
  21. 3 0
      Terminal.Gui/Input/Command.cs
  22. 16 0
      Terminal.Gui/Input/KeyBinding.cs
  23. 1 1
      Terminal.Gui/Input/KeyBindingScope.cs
  24. 152 22
      Terminal.Gui/Input/KeyBindings.cs
  25. 2 2
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs
  26. 3 3
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  27. 6 5
      Terminal.Gui/View/Adornment/Border.cs
  28. 1 1
      Terminal.Gui/View/Adornment/ShadowView.cs
  29. 29 0
      Terminal.Gui/View/DrawEventArgs.cs
  30. 1 1
      Terminal.Gui/View/EventArgs.cs
  31. 4 4
      Terminal.Gui/View/Layout/Dim.cs
  32. 5 5
      Terminal.Gui/View/Layout/DimView.cs
  33. 12 0
      Terminal.Gui/View/Layout/LayoutEventArgs.cs
  34. 8 8
      Terminal.Gui/View/Layout/Pos.cs
  35. 7 7
      Terminal.Gui/View/Layout/PosView.cs
  36. 27 0
      Terminal.Gui/View/Navigation/FocusEventArgs.cs
  37. 1 1
      Terminal.Gui/View/View.Adornments.cs
  38. 15 0
      Terminal.Gui/View/View.Arrangement.cs
  39. 0 0
      Terminal.Gui/View/View.Content.cs
  40. 35 0
      Terminal.Gui/View/View.Cursor.cs
  41. 0 0
      Terminal.Gui/View/View.Diagnostics.cs
  42. 5 5
      Terminal.Gui/View/View.Drawing.cs
  43. 320 0
      Terminal.Gui/View/View.Hierarchy.cs
  44. 12 120
      Terminal.Gui/View/View.Keyboard.cs
  45. 7 7
      Terminal.Gui/View/View.Layout.cs
  46. 1 1
      Terminal.Gui/View/View.Mouse.cs
  47. 854 0
      Terminal.Gui/View/View.Navigation.cs
  48. 1 1
      Terminal.Gui/View/View.Text.cs
  49. 2 2
      Terminal.Gui/View/View.cs
  50. 16 15
      Terminal.Gui/View/ViewArrangement.cs
  51. 1 66
      Terminal.Gui/View/ViewEventArgs.cs
  52. 0 900
      Terminal.Gui/View/ViewSubViews.cs
  53. 13 13
      Terminal.Gui/Views/DateField.cs
  54. 5 5
      Terminal.Gui/Views/FileDialog.cs
  55. 3 3
      Terminal.Gui/Views/GraphView/Annotations.cs
  56. 7 7
      Terminal.Gui/Views/GraphView/Axis.cs
  57. 2 2
      Terminal.Gui/Views/GraphView/Series.cs
  58. 3 0
      Terminal.Gui/Views/Menu/Menu.cs
  59. 1 1
      Terminal.Gui/Views/Menu/MenuBar.cs
  60. 1 0
      Terminal.Gui/Views/Menu/MenuBarItem.cs
  61. 0 2
      Terminal.Gui/Views/MenuBarv2.cs
  62. 6 6
      Terminal.Gui/Views/RadioGroup.cs
  63. 246 238
      Terminal.Gui/Views/Shortcut.cs
  64. 4 0
      Terminal.Gui/Views/Slider.cs
  65. 0 2
      Terminal.Gui/Views/StatusBar.cs
  66. 1 1
      Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs
  67. 1 1
      Terminal.Gui/Views/TableView/TableView.cs
  68. 4 1
      Terminal.Gui/Views/TextField.cs
  69. 4 5
      Terminal.Gui/Views/TextValidateField.cs
  70. 4 56
      Terminal.Gui/Views/TextView.cs
  71. 1 1
      Terminal.Gui/Views/Tile.cs
  72. 1 1
      Terminal.Gui/Views/TileView.cs
  73. 13 13
      Terminal.Gui/Views/TimeField.cs
  74. 263 517
      Terminal.Gui/Views/Toplevel.cs
  75. 0 212
      Terminal.Gui/Views/ToplevelOverlapped.cs
  76. 1 1
      Terminal.Gui/Views/TreeView/TreeView.cs
  77. 3 1
      Terminal.sln.DotSettings
  78. 13 13
      UICatalog/Scenarios/BackgroundWorkerCollection.cs
  79. 2 2
      UICatalog/Scenarios/Buttons.cs
  80. 14 14
      UICatalog/Scenarios/CombiningMarks.cs
  81. 2 2
      UICatalog/Scenarios/Images.cs
  82. 20 17
      UICatalog/Scenarios/KeyBindings.cs
  83. 1 1
      UICatalog/Scenarios/ListColumns.cs
  84. 1 1
      UICatalog/Scenarios/ListViewWithSelection.cs
  85. 6 2
      UICatalog/Scenarios/MenuBarScenario.cs
  86. 0 1
      UICatalog/Scenarios/Notepad.cs
  87. 1 1
      UICatalog/Scenarios/SendKeys.cs
  88. 1 1
      UICatalog/Scenarios/TableEditor.cs
  89. 1 1
      UICatalog/Scenarios/TextEffectsScenario.cs
  90. 2 2
      UICatalog/Scenarios/TrueColors.cs
  91. 46 187
      UICatalog/Scenarios/ViewExperiments.cs
  92. 1 1
      UICatalog/Scenarios/VkeyPacketSimulator.cs
  93. 14 12
      UICatalog/UICatalog.cs
  94. 31 31
      UnitTests/Application/ApplicationTests.cs
  95. 5 2
      UnitTests/Application/CursorTests.cs
  96. 62 72
      UnitTests/Application/KeyboardTests.cs
  97. 2 2
      UnitTests/Clipboard/ClipboardTests.cs
  98. 6 6
      UnitTests/ConsoleDrivers/ClipRegionTests.cs
  99. 1 1
      UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
  100. 1 1
      UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.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 ();
     }
 

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

@@ -0,0 +1,45 @@
+#nullable enable
+namespace Terminal.Gui;
+
+public static partial class Application // Screen related stuff
+{
+    /// <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;
+    }
+}

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

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

@@ -0,0 +1,212 @@
+#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 ();
+        }
+
+        // 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;
+}

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

@@ -0,0 +1,428 @@
+#nullable enable
+using System.Text.Json.Serialization;
+using static System.Formats.Asn1.AsnWriter;
+
+namespace Terminal.Gui;
+
+public static partial class Application // Keyboard handling
+{
+    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;
+
+                if (_alternateForwardKey == Key.Empty)
+                {
+                    KeyBindings.Remove (_alternateForwardKey);
+                }
+                else
+                {
+                    KeyBindings.ReplaceKey (oldKey, _alternateForwardKey);
+                }
+            }
+        }
+    }
+
+    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;
+
+                if (_alternateBackwardKey == Key.Empty)
+                {
+                    KeyBindings.Remove (_alternateBackwardKey);
+                }
+                else
+                {
+                    KeyBindings.ReplaceKey (oldKey, _alternateBackwardKey);
+                }
+            }
+        }
+    }
+
+    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;
+                if (_quitKey == Key.Empty)
+                {
+                    KeyBindings.Remove (_quitKey);
+                }
+                else
+                {
+                    KeyBindings.ReplaceKey (oldKey, _quitKey);
+                }
+            }
+        }
+    }
+
+    /// <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;
+        }
+
+        foreach (Toplevel topLevel in TopLevels.ToList ())
+        {
+            if (topLevel.NewKeyDownEvent (keyEvent))
+            {
+                return true;
+            }
+
+            if (topLevel.Modal)
+            {
+                break;
+            }
+        }
+
+        // Invoke any Application-scoped KeyBindings.
+        // The first view that handles the key will stop the loop.
+        foreach (var 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; } = new ();
+
+    /// <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 ();
+    }
+
+    internal static void AddApplicationKeyBindings ()
+    {
+        // 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
+                        {
+                            Application.RequestStop ();
+                        }
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Suspend,
+                    () =>
+                    {
+                        Driver?.Suspend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.NextView,
+                    () =>
+                    {
+                        // TODO: Move this method to Application.Navigation.cs
+                        ApplicationNavigation.MoveNextView ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.PreviousView,
+                    () =>
+                    {
+                        // TODO: Move this method to Application.Navigation.cs
+                        ApplicationNavigation.MovePreviousView ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.NextViewOrTop,
+                    () =>
+                    {
+                        // TODO: Move this method to Application.Navigation.cs
+                        ApplicationNavigation.MoveNextViewOrTop ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.PreviousViewOrTop,
+                    () =>
+                    {
+                        // TODO: Move this method to Application.Navigation.cs
+                        ApplicationNavigation.MovePreviousViewOrTop ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Refresh,
+                    () =>
+                    {
+                        Refresh ();
+
+                        return true;
+                    }
+                   );
+
+
+        KeyBindings.Add (Application.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 (Key.Tab, KeyBindingScope.Application, Command.NextView);
+        KeyBindings.Add (Key.Tab.WithShift, KeyBindingScope.Application, Command.PreviousView);
+        KeyBindings.Add (Key.Tab.WithCtrl, KeyBindingScope.Application, Command.NextViewOrTop);
+        KeyBindings.Add (Key.Tab.WithShift.WithCtrl, KeyBindingScope.Application, Command.PreviousViewOrTop);
+
+        // TODO: Refresh Key should be configurable
+        KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
+        KeyBindings.Add (Application.AlternateForwardKey, KeyBindingScope.Application, Command.NextViewOrTop); // Needed on Unix
+        KeyBindings.Add (Application.AlternateBackwardKey, KeyBindingScope.Application, Command.PreviousViewOrTop); // Needed on Unix
+
+        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)
+    {
+        var 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

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

@@ -0,0 +1,194 @@
+#nullable enable
+using System.Security.Cryptography;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Helper class for <see cref="Application"/> navigation.
+/// </summary>
+internal static class ApplicationNavigation
+{
+    /// <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>
+    ///    Sets the focus to the next view in the <see cref="View.TabIndexes"/> list. If the last view is focused, the first view is focused.
+    /// </summary>
+    /// <param name="viewsInTabIndexes"></param>
+    /// <param name="direction"></param>
+    internal static void FocusNearestView (IEnumerable<View>? viewsInTabIndexes, View.NavigationDirection direction)
+    {
+        if (viewsInTabIndexes is null)
+        {
+            return;
+        }
+
+        var found = false;
+        var focusProcessed = false;
+        var idx = 0;
+
+        foreach (View v in viewsInTabIndexes)
+        {
+            if (v == Application.Current)
+            {
+                found = true;
+            }
+
+            if (found && v != Application.Current)
+            {
+                if (direction == View.NavigationDirection.Forward)
+                {
+                    Application.Current!.SuperView?.FocusNext ();
+                }
+                else
+                {
+                    Application.Current!.SuperView?.FocusPrev ();
+                }
+
+                focusProcessed = true;
+
+                if (Application.Current.SuperView?.Focused is { } && Application.Current.SuperView.Focused != Application.Current)
+                {
+                    return;
+                }
+            }
+            else if (found && !focusProcessed && idx == viewsInTabIndexes.Count () - 1)
+            {
+                viewsInTabIndexes.ToList () [0].SetFocus ();
+            }
+
+            idx++;
+        }
+    }
+
+    /// <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 MoveNextView ()
+    {
+        View? old = GetDeepestFocusedSubview (Application.Current!.Focused);
+
+        if (!Application.Current.FocusNext ())
+        {
+            Application.Current.FocusNext ();
+        }
+
+        if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
+        {
+            old?.SetNeedsDisplay ();
+            Application.Current.Focused?.SetNeedsDisplay ();
+        }
+        else
+        {
+            FocusNearestView (Application.Current.SuperView?.TabIndexes, View.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.FocusNext ())
+            {
+                Application.Current.FocusNext ();
+            }
+
+            if (top != Application.Current.Focused && top != Application.Current.Focused?.Focused)
+            {
+                top?.SetNeedsDisplay ();
+                Application.Current.Focused?.SetNeedsDisplay ();
+            }
+            else
+            {
+                FocusNearestView (Application.Current.SuperView?.TabIndexes, View.NavigationDirection.Forward);
+            }
+
+
+
+            //top!.FocusNext ();
+
+            //if (top.Focused is null)
+            //{
+            //    top.FocusNext ();
+            //}
+
+            //top.SetNeedsDisplay ();
+            ApplicationOverlapped.BringOverlappedTopToFront ();
+        }
+        else
+        {
+            ApplicationOverlapped.OverlappedMoveNext ();
+        }
+    }
+
+    /// <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.FocusPrev ())
+        {
+            Application.Current.FocusPrev ();
+        }
+
+        if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
+        {
+            old?.SetNeedsDisplay ();
+            Application.Current.Focused?.SetNeedsDisplay ();
+        }
+        else
+        {
+            FocusNearestView (Application.Current.SuperView?.TabIndexes?.Reverse (), View.NavigationDirection.Backward);
+        }
+    }
+
+    internal static void MovePreviousViewOrTop ()
+    {
+        if (ApplicationOverlapped.OverlappedTop is null)
+        {
+            Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top;
+            top!.FocusPrev ();
+
+            if (top.Focused is null)
+            {
+                top.FocusPrev ();
+            }
+
+            top.SetNeedsDisplay ();
+            ApplicationOverlapped.BringOverlappedTopToFront ();
+        }
+        else
+        {
+            ApplicationOverlapped.OverlappedMovePrevious ();
+        }
+    }
+}

+ 384 - 0
Terminal.Gui/Application/Application.Overlapped.cs

@@ -0,0 +1,384 @@
+#nullable enable
+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>
+    ///     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;
+    }
+}

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

@@ -0,0 +1,863 @@
+#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!))
+            {
+                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)
+            {
+                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 ();
+        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)
+        {
+            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 ();
+                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 (ApplicationOverlapped.OverlappedTop is { } && !TopLevels.Contains (ApplicationOverlapped.OverlappedTop))
+        {
+            _cachedRunStateToplevel = ApplicationOverlapped.OverlappedTop;
+        }
+        else
+        {
+            _cachedRunStateToplevel = runState.Toplevel;
+        }
+
+        runState.Toplevel = null;
+        runState.Dispose ();
+    }
+}

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

File diff suppressed because it is too large
+ 14 - 1327
Terminal.Gui/Application/Application.cs


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

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

+ 152 - 22
Terminal.Gui/Input/KeyBindings.cs

@@ -1,6 +1,7 @@
 #nullable enable
 
 using System.Diagnostics;
+using Microsoft.CodeAnalysis;
 
 namespace Terminal.Gui;
 
@@ -11,7 +12,7 @@ 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 () { }
 
@@ -21,6 +22,9 @@ public class KeyBindings
     /// <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; }
 
     // TODO: Add a dictionary comparer that ignores Scope
@@ -31,19 +35,30 @@ public class KeyBindings
     /// <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;
         }
         else
         {
-            Bindings.Add (key, binding);
-            if (binding.Scope.HasFlag (KeyBindingScope.Application))
+            if (BoundView is { })
+            {
+                binding.BoundView = BoundView;
+            }
+            else
             {
-                Application.AddKeyBinding (key, BoundView);
+                binding.BoundView = boundViewForAppScope;
             }
+            Bindings.Add (key, binding);
         }
     }
 
@@ -60,13 +75,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,13 +99,62 @@ 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))
+        {
+            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))
+        {
+            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));
+            return;
+        }
+
+        if (commands.Length == 0)
+        {
+            throw new ArgumentException (@"At least one command must be specified", nameof (commands));
+        }
+
+        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));
+            Add (key, new KeyBinding (commands, scope, BoundView), null);
         }
     }
 
@@ -94,8 +164,42 @@ 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
+    ///         <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="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, View? boundViewForAppScope = null, params Command [] 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>
+    ///     <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
@@ -114,14 +218,16 @@ public class KeyBindings
     /// </param>
     public void Add (Key key, params Command [] commands)
     {
-        Add (key, KeyBindingScope.Focused, commands);
+        if (BoundView is null)
+        {
+            throw new ArgumentException (@"Application scoped KeyBindings must provide a boundViewForAppScope to Add.");
+        }
+        Add (key, BoundView is { } ? KeyBindingScope.Focused : KeyBindingScope.Application, null, commands);
     }
 
     /// <summary>Removes all <see cref="KeyBinding"/> objects from the collection.</summary>
     public void Clear ()
     {
-        Application.ClearKeyBindings (BoundView);
-
         Bindings.Clear ();
     }
 
@@ -192,17 +298,23 @@ 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 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)
+    public void ReplaceKey (Key oldKey, Key newKey)
     {
         if (!TryGet (oldKey, out KeyBinding _))
         {
@@ -214,6 +326,26 @@ public class KeyBindings
         Add (newKey, value);
     }
 
+    /// <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>Gets the commands bound with the specified Key.</summary>
     /// <remarks></remarks>
     /// <param name="key">The key to check.</param>
@@ -224,13 +356,12 @@ 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,7 @@ 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 +385,6 @@ public class KeyBindings
             }
         }
 
-        binding = new (Array.Empty<Command> (), KeyBindingScope.Focused);
-
         return false;
     }
 }

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

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

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

+ 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


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

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

@@ -0,0 +1,320 @@
+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 ();
+        }
+
+        _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 ();
+    }
+
+}

+ 12 - 120
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);
@@ -800,11 +691,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;

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

@@ -0,0 +1,854 @@
+using System.Diagnostics;
+
+namespace Terminal.Gui;
+
+public partial class View // Focus and cross-view navigation management (TabStop, TabIndex, etc...)
+{
+    /// <summary>Returns a value indicating if this View is currently on Top (Active)</summary>
+    public bool IsCurrentTop => Application.Current == this;
+
+    // BUGBUG: This API is poorly defined and implemented. It deeply intertwines the view hierarchy with the tab order.
+    /// <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>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)
+    {
+        var args = new FocusEventArgs (this, enteringView);
+        Leave?.Invoke (this, args);
+
+        if (args.Handled)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <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>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;
+
+    private NavigationDirection _focusDirection;
+
+    /// <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;
+            }
+        }
+    }
+
+    private bool _hasFocus;
+
+    /// <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>
+    ///     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;
+        }
+    }
+
+    // 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;
+
+    // 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>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 this view is 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>
+    /// </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:
+                    TabIndex = -1;
+
+                    break;
+
+                case true when SuperView?.CanFocus == false && _addingViewSoCanFocusAlsoUpdatesSuperView:
+                    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?.FocusFirstOrLast ();
+
+                // If EnsureFocus () didn't set focus to a view, focus the next focusable view in the application
+                if (SuperView is { Focused: null })
+                {
+                    SuperView.FocusNext ();
+
+                    if (SuperView.Focused is null && Application.Current is { })
+                    {
+                        Application.Current.FocusNext ();
+                    }
+
+                    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>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); }
+
+    /// <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>
+    ///     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>Causes subview specified by <paramref name="view"/> to enter 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.FocusFirstOrLast ();
+
+        // Send focus upwards
+        if (SuperView is { })
+        {
+            SuperView.SetFocus (this);
+        }
+        else
+        {
+            SetFocus (this);
+        }
+    }
+
+    /// <summary>Causes this view to be focused and entire Superview 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>
+    ///     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 FocusFirstOrLast ()
+    {
+        if (Focused is null && _subviews?.Count > 0)
+        {
+            if (FocusDirection == NavigationDirection.Forward)
+            {
+                FocusFirst ();
+            }
+            else
+            {
+                FocusLast ();
+            }
+        }
+    }
+
+    /// <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="overlappedOnly">
+    ///     If <see langword="true"/>, only subviews where <see cref="Arrangement"/> has <see cref="ViewArrangement.Overlapped"/> set
+    ///     will be considered.
+    /// </param>
+    public void FocusFirst (bool overlappedOnly = false)
+    {
+        if (!CanBeVisible (this))
+        {
+            return;
+        }
+
+        if (_tabIndexes is null)
+        {
+            SuperView?.SetFocus (this);
+
+            return;
+        }
+
+        foreach (View view in _tabIndexes.Where (v => !overlappedOnly || v.Arrangement.HasFlag (ViewArrangement.Overlapped)))
+        {
+            if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
+            {
+                SetFocus (view);
+
+                return;
+            }
+        }
+    }
+
+    /// <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="overlappedOnly">
+    ///     If <see langword="true"/>, only subviews where <see cref="Arrangement"/> has <see cref="ViewArrangement.Overlapped"/> set
+    ///     will be considered.
+    /// </param>
+    public void FocusLast (bool overlappedOnly = false)
+    {
+        if (!CanBeVisible (this))
+        {
+            return;
+        }
+
+        if (_tabIndexes is null)
+        {
+            SuperView?.SetFocus (this);
+
+            return;
+        }
+
+        foreach (View view in _tabIndexes.Where (v => !overlappedOnly || v.Arrangement.HasFlag (ViewArrangement.Overlapped)).Reverse ())
+        {
+            if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
+            {
+                SetFocus (view);
+
+                return;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Focuses the previous view in <see cref="View.TabIndexes"/>. If there is no previous view, the focus is set to the
+    ///     view itself.
+    /// </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 the focused view is overlapped don't focus on the next if it's not overlapped.
+                if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped))
+                {
+                    FocusLast (true);
+
+                    return true;
+                }
+
+                // If the focused view is not overlapped and the next is, skip it
+                if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped))
+                {
+                    continue;
+                }
+
+                if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
+                {
+                    w.FocusLast ();
+                }
+
+                SetFocus (w);
+
+                return true;
+            }
+        }
+
+        // There's no prev view in tab indexes.
+        if (Focused is { })
+        {
+            // Leave Focused
+            Focused.SetHasFocus (false, this);
+
+            if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped))
+            {
+                FocusLast (true);
+
+                return true;
+            }
+
+            // Signal to caller no next view was found
+            Focused = null;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Focuses the next view in <see cref="View.TabIndexes"/>. If there is no next view, the focus is set to the view
+    ///     itself.
+    /// </summary>
+    /// <returns><see langword="true"/> if focus was changed to another subview (or stayed on this one), <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 is { };
+        }
+
+        int focusedIdx = -1;
+
+        for (var i = 0; i < TabIndexes.Count; i++)
+        {
+            View w = TabIndexes [i];
+
+            if (w.HasFocus)
+            {
+                // A subview has focus, tell *it* to FocusNext
+                if (w.FocusNext ())
+                {
+                    // The subview changed which of it's subviews had focus
+                    return true;
+                }
+
+                Debug.Assert (w.HasFocus);
+
+                // The subview has no subviews that can be next. Cache that we found a focused subview.
+                focusedIdx = i;
+
+                continue;
+            }
+
+            // The subview does not have focus, but at least one other that can. Can this one be focused?
+            if (focusedIdx != -1 && w.CanFocus && w._tabStop && w.Visible && w.Enabled)
+            {
+                // Make Focused Leave
+                Focused.SetHasFocus (false, w);
+
+                //// If the focused view is overlapped don't focus on the next if it's not overlapped.
+                //if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)/* && !w.Arrangement.HasFlag (ViewArrangement.Overlapped)*/)
+                //{
+                //    return false;
+                //}
+
+                //// If the focused view is not overlapped and the next is, skip it
+                //if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped))
+                //{
+                //    continue;
+                //}
+
+                // QUESTION: Why do we check these again here?
+                if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
+                {
+                    w.FocusFirst ();
+                }
+
+                SetFocus (w);
+
+                return true;
+            }
+        }
+
+        // There's no next view in tab indexes.
+        if (Focused is { })
+        {
+            // Leave
+            Focused.SetHasFocus (false, this);
+
+            //if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped))
+            //{
+            //    FocusFirst (true);
+            //    return true;
+            //}
+
+            // Signal to caller no next view was found; this will enable it to make a peer
+            // or view up the superview hierarchy have focus.
+            Focused = null;
+        }
+
+        return false;
+    }
+
+    private View GetMostFocused (View view)
+    {
+        if (view is null)
+        {
+            return null;
+        }
+
+        return view.Focused is { } ? GetMostFocused (view.Focused) : view;
+    }
+
+    #region Tab/Focus Handling
+
+    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;
+
+    // TODO: Change this to int? and use null to indicate the view is not in the tab order.
+    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>
+    /// <remarks>
+    ///     <para>
+    ///         If the value is -1, the view is not part of the tab order.
+    ///     </para>
+    ///     <para>
+    ///         On set, if <see cref="CanFocus"/> is <see langword="false"/>, <see cref="TabIndex"/> will be set to -1.
+    ///     </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>
+    /// </remarks>
+    public int TabIndex
+    {
+        get => _tabIndex;
+        set
+        {
+            if (!CanFocus)
+            {
+                // BUGBUG: Property setters should set the property to the value passed in and not have side effects.
+                _tabIndex = -1;
+
+                return;
+            }
+
+            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 (_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 (_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)
+    {
+        var i = 0;
+
+        foreach (View superViewTabStop in SuperView._tabIndexes)
+        {
+            if (superViewTabStop._tabIndex == -1 || 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 ()
+    {
+        var i = 0;
+
+        foreach (View superViewTabStop in SuperView._tabIndexes)
+        {
+            if (superViewTabStop._tabIndex == -1)
+            {
+                continue;
+            }
+
+            superViewTabStop._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 <see cref="CanFocus"/> is <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
+}

+ 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 - 2
Terminal.Gui/View/View.cs

@@ -332,7 +332,7 @@ public partial class View : Responder, ISupportInitializeNotification
                 else
                 {
                     view.Enabled = view._oldEnabled;
-                    view._addingView = _enabled;
+                    view._addingViewSoCanFocusAlsoUpdatesSuperView = _enabled;
                 }
             }
         }
@@ -490,7 +490,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
-}

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

+ 5 - 5
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) =>
                              {

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

+ 1 - 1
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -1594,7 +1594,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;

+ 6 - 6
Terminal.Gui/Views/RadioGroup.cs

@@ -278,7 +278,7 @@ public class RadioGroup : View, IDesignable
 
                     if (j == hotPos && i == _cursor)
                     {
-                        Application.Driver.SetAttribute (
+                        Application.Driver?.SetAttribute (
                                                          HasFocus
                                                              ? ColorScheme.HotFocus
                                                              : GetHotNormalColor ()
@@ -286,11 +286,11 @@ public class RadioGroup : View, IDesignable
                     }
                     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)
@@ -300,7 +300,7 @@ public class RadioGroup : View, IDesignable
 
                         if (i == _cursor)
                         {
-                            Application.Driver.SetAttribute (
+                            Application.Driver?.SetAttribute (
                                                              HasFocus
                                                                  ? ColorScheme.HotFocus
                                                                  : GetHotNormalColor ()
@@ -308,11 +308,11 @@ public class RadioGroup : View, IDesignable
                         }
                         else if (i != _cursor)
                         {
-                            Application.Driver.SetAttribute (GetHotNormalColor ());
+                            Application.Driver?.SetAttribute (GetHotNormalColor ());
                         }
                     }
 
-                    Application.Driver.AddRune (rune);
+                    Application.Driver?.AddRune (rune);
                     Driver.SetAttribute (GetNormalColor ());
                 }
             }

+ 246 - 238
Terminal.Gui/Views/Shortcut.cs

@@ -440,40 +440,40 @@ public class Shortcut : View
     }
 
     private void SetCommandViewDefaultLayout ()
-{
-    CommandView.Margin.Thickness = GetMarginThickness ();
-    CommandView.X = Pos.Align (Alignment.End, AlignmentModes);
-    CommandView.Y = 0; //Pos.Center ();
-}
+    {
+        CommandView.Margin.Thickness = GetMarginThickness ();
+        CommandView.X = Pos.Align (Alignment.End, AlignmentModes);
+        CommandView.Y = 0; //Pos.Center ();
+    }
 
-private void Shortcut_TitleChanged (object sender, EventArgs<string> e)
-{
-    // If the Title changes, update the CommandView text.
-    // This is a helper to make it easier to set the CommandView text.
-    // CommandView is public and replaceable, but this is a convenience.
-    _commandView.Text = Title;
-}
+    private void Shortcut_TitleChanged (object sender, EventArgs<string> e)
+    {
+        // If the Title changes, update the CommandView text.
+        // This is a helper to make it easier to set the CommandView text.
+        // CommandView is public and replaceable, but this is a convenience.
+        _commandView.Text = Title;
+    }
 
-#endregion Command
+    #endregion Command
 
-#region Help
+    #region Help
 
-/// <summary>
-///     The subview that displays the help text for the command. Internal for unit testing.
-/// </summary>
-internal View HelpView { get; } = new ();
+    /// <summary>
+    ///     The subview that displays the help text for the command. Internal for unit testing.
+    /// </summary>
+    internal View HelpView { get; } = new ();
 
-private void SetHelpViewDefaultLayout ()
-{
-    HelpView.Margin.Thickness = GetMarginThickness ();
-    HelpView.X = Pos.Align (Alignment.End, AlignmentModes);
-    HelpView.Y = 0; //Pos.Center ();
-    HelpView.Width = Dim.Auto (DimAutoStyle.Text);
-    HelpView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
-
-    HelpView.Visible = true;
-    HelpView.VerticalTextAlignment = Alignment.Center;
-}
+    private void SetHelpViewDefaultLayout ()
+    {
+        HelpView.Margin.Thickness = GetMarginThickness ();
+        HelpView.X = Pos.Align (Alignment.End, AlignmentModes);
+        HelpView.Y = 0; //Pos.Center ();
+        HelpView.Width = Dim.Auto (DimAutoStyle.Text);
+        HelpView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
+
+        HelpView.Visible = true;
+        HelpView.VerticalTextAlignment = Alignment.Center;
+    }
 
 /// <summary>
 ///     Gets or sets the help text displayed in the middle of the Shortcut. Identical in function to <see cref="HelpText"/>
@@ -508,285 +508,293 @@ public string HelpText
     }
 }
 
-#endregion Help
+    #endregion Help
 
-#region Key
+    #region Key
 
-private Key _key = Key.Empty;
+    private Key _key = Key.Empty;
 
-/// <summary>
-///     Gets or sets the <see cref="Key"/> that will be bound to the <see cref="Command.Accept"/> command.
-/// </summary>
-public Key Key
-{
-    get => _key;
-    set
+    /// <summary>
+    ///     Gets or sets the <see cref="Key"/> that will be bound to the <see cref="Command.Accept"/> command.
+    /// </summary>
+    public Key Key
     {
-        if (value == null)
+        get => _key;
+        set
         {
-            throw new ArgumentNullException ();
-        }
+            if (value == null)
+            {
+                throw new ArgumentNullException ();
+            }
 
-        _key = value;
+            _key = value;
 
-        UpdateKeyBinding ();
+            UpdateKeyBinding ();
 
-        KeyView.Text = Key == Key.Empty ? string.Empty : $"{Key}";
-        ShowHide ();
+            KeyView.Text = Key == Key.Empty ? string.Empty : $"{Key}";
+            ShowHide ();
+        }
     }
-}
 
-private KeyBindingScope _keyBindingScope = KeyBindingScope.HotKey;
+    private KeyBindingScope _keyBindingScope = KeyBindingScope.HotKey;
 
-/// <summary>
-///     Gets or sets the scope for the key binding for how <see cref="Key"/> is bound to <see cref="Command"/>.
-/// </summary>
-public KeyBindingScope KeyBindingScope
-{
-    get => _keyBindingScope;
-    set
+    /// <summary>
+    ///     Gets or sets the scope for the key binding for how <see cref="Key"/> is bound to <see cref="Command"/>.
+    /// </summary>
+    public KeyBindingScope KeyBindingScope
     {
-        _keyBindingScope = value;
+        get => _keyBindingScope;
+        set
+        {
+            _keyBindingScope = value;
 
-        UpdateKeyBinding ();
+            UpdateKeyBinding ();
+        }
     }
-}
 
-/// <summary>
-///     Gets the subview that displays the key. Internal for unit testing.
-/// </summary>
+    /// <summary>
+    ///     Gets the subview that displays the key. Internal for unit testing.
+    /// </summary>
 
-internal View KeyView { get; } = new ();
+    internal View KeyView { get; } = new ();
 
-private int _minimumKeyTextSize;
+    private int _minimumKeyTextSize;
 
-/// <summary>
-/// Gets or sets the minimum size of the key text. Useful for aligning the key text with other <see cref="Shortcut"/>s.
-/// </summary>
-public int MinimumKeyTextSize
-{
-    get => _minimumKeyTextSize;
-    set
+    /// <summary>
+    /// Gets or sets the minimum size of the key text. Useful for aligning the key text with other <see cref="Shortcut"/>s.
+    /// </summary>
+    public int MinimumKeyTextSize
     {
-        if (value == _minimumKeyTextSize)
+        get => _minimumKeyTextSize;
+        set
         {
-            //return;
-        }
+            if (value == _minimumKeyTextSize)
+            {
+                //return;
+            }
 
-        _minimumKeyTextSize = value;
-        SetKeyViewDefaultLayout ();
-        CommandView.SetNeedsLayout ();
-        HelpView.SetNeedsLayout ();
-        KeyView.SetNeedsLayout ();
-        SetSubViewNeedsDisplay ();
+            _minimumKeyTextSize = value;
+            SetKeyViewDefaultLayout ();
+            CommandView.SetNeedsLayout ();
+            HelpView.SetNeedsLayout ();
+            KeyView.SetNeedsLayout ();
+            SetSubViewNeedsDisplay ();
+        }
     }
-}
-
-private int GetMinimumKeyViewSize () { return MinimumKeyTextSize; }
 
-private void SetKeyViewDefaultLayout ()
-{
-    KeyView.Margin.Thickness = GetMarginThickness ();
-    KeyView.X = Pos.Align (Alignment.End, AlignmentModes);
-    KeyView.Y = 0; //Pos.Center ();
-    KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize));
-    KeyView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
-
-    KeyView.Visible = true;
-
-    // Right align the text in the keyview
-    KeyView.TextAlignment = Alignment.End;
-    KeyView.VerticalTextAlignment = Alignment.Center;
-    KeyView.KeyBindings.Clear ();
-}
+    private int GetMinimumKeyViewSize () { return MinimumKeyTextSize; }
 
-private void UpdateKeyBinding ()
-{
-    if (Key != null)
+    private void SetKeyViewDefaultLayout ()
     {
-        // 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);
+        KeyView.Margin.Thickness = GetMarginThickness ();
+        KeyView.X = Pos.Align (Alignment.End, AlignmentModes);
+        KeyView.Y = 0; //Pos.Center ();
+        KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize));
+        KeyView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
+
+        KeyView.Visible = true;
+
+        // Right align the text in the keyview
+        KeyView.TextAlignment = Alignment.End;
+        KeyView.VerticalTextAlignment = Alignment.Center;
+        KeyView.KeyBindings.Clear ();
     }
-}
-
-#endregion Key
 
-#region Accept Handling
+    private void UpdateKeyBinding ()
+    {
+        if (Key != null)
+        {
+            // Disable the command view key bindings
+            CommandView.KeyBindings.Remove (Key);
+            CommandView.KeyBindings.Remove (CommandView.HotKey);
 
-/// <summary>
-///     Called when the <see cref="Command.Accept"/> command is received. This
-///     occurs
-///     - if the user clicks anywhere on the shortcut with the mouse
-///     - if the user presses Key
-///     - if the user presses the HotKey specified by CommandView
-///     - if HasFocus and the user presses Space or Enter (or any other key bound to Command.Accept).
-/// </summary>
-protected bool? OnAccept (CommandContext ctx)
-{
-    var cancel = false;
+            if (KeyBindingScope.FastHasFlags (KeyBindingScope.Application))
+            {
+                Application.KeyBindings.Remove (Key);
+                Application.KeyBindings.Add (Key, this, Command.Accept);
+            }
+            else
+            {
+                KeyBindings.Remove (Key);
+                KeyBindings.Add (Key, KeyBindingScope | KeyBindingScope.HotKey, Command.Accept);
+            }
+        }
+    }
 
-    switch (ctx.KeyBinding?.Scope)
-    {
-        case KeyBindingScope.Application:
-            cancel = base.OnAccept () == true;
+    #endregion Key
 
-            break;
+    #region Accept Handling
 
-        case KeyBindingScope.Focused:
-            base.OnAccept ();
+    /// <summary>
+    ///     Called when the <see cref="Command.Accept"/> command is received. This
+    ///     occurs
+    ///     - if the user clicks anywhere on the shortcut with the mouse
+    ///     - if the user presses Key
+    ///     - if the user presses the HotKey specified by CommandView
+    ///     - if HasFocus and the user presses Space or Enter (or any other key bound to Command.Accept).
+    /// </summary>
+    protected bool? OnAccept (CommandContext ctx)
+    {
+        var cancel = false;
 
-            // cancel if we're focused
-            cancel = true;
+        switch (ctx.KeyBinding?.Scope)
+        {
+            case KeyBindingScope.Application:
+                cancel = base.OnAccept () == true;
 
-            break;
+                break;
 
-        case KeyBindingScope.HotKey:
-            cancel = base.OnAccept () == true;
+            case KeyBindingScope.Focused:
+                base.OnAccept ();
 
-            if (CanFocus)
-            {
-                SetFocus ();
+                // cancel if we're focused
                 cancel = true;
-            }
 
-            break;
+                break;
 
-        default:
-            // Mouse
-            cancel = base.OnAccept () == true;
+            case KeyBindingScope.HotKey:
+                cancel = base.OnAccept () == true;
 
-            break;
-    }
+                if (CanFocus)
+                {
+                    SetFocus ();
+                    cancel = true;
+                }
 
-    CommandView.InvokeCommand (Command.Accept, ctx.Key, ctx.KeyBinding);
+                break;
 
-    if (Action is { })
-    {
-        Action.Invoke ();
-        // Assume if there's a subscriber to Action, it's handled.
-        cancel = true;
-    }
+            default:
+                // Mouse
+                cancel = base.OnAccept () == true;
 
-    return cancel;
-}
+                break;
+        }
 
-/// <summary>
-///     Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the
-///     mouse.
-/// </summary>
-/// <remarks>
-///     Note, the <see cref="View.Accept"/> event is fired first, and if cancelled, the event will not be invoked.
-/// </remarks>
-[CanBeNull]
-public Action Action { get; set; }
+        CommandView.InvokeCommand (Command.Accept, ctx.Key, ctx.KeyBinding);
 
-#endregion Accept Handling
+        if (Action is { })
+        {
+            Action.Invoke ();
+            // Assume if there's a subscriber to Action, it's handled.
+            cancel = true;
+        }
 
-private bool? OnSelect (CommandContext ctx)
-{
-    if (CommandView.GetSupportedCommands ().Contains (Command.Select))
-    {
-        return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding);
+        return cancel;
     }
-    return false;
-
-}
 
+    /// <summary>
+    ///     Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the
+    ///     mouse.
+    /// </summary>
+    /// <remarks>
+    ///     Note, the <see cref="View.Accept"/> event is fired first, and if cancelled, the event will not be invoked.
+    /// </remarks>
+    [CanBeNull]
+    public Action Action { get; set; }
 
-#region Focus
+    #endregion Accept Handling
 
-/// <inheritdoc/>
-public override ColorScheme ColorScheme
-{
-    get => base.ColorScheme;
-    set
+    private bool? OnSelect (CommandContext ctx)
     {
-        base.ColorScheme = value;
-        SetColors ();
+        if (CommandView.GetSupportedCommands ().Contains (Command.Select))
+        {
+            return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding);
+        }
+        return false;
+
     }
-}
 
-/// <summary>
-/// </summary>
-internal void SetColors ()
-{
-    // Border should match superview.
-    Border.ColorScheme = SuperView?.ColorScheme;
 
-    if (HasFocus)
+    #region Focus
+
+    /// <inheritdoc/>
+    public override ColorScheme ColorScheme
     {
-        // When we have focus, we invert the colors
-        base.ColorScheme = new (base.ColorScheme)
+        get => base.ColorScheme;
+        set
         {
-            Normal = base.ColorScheme.Focus,
-            HotNormal = base.ColorScheme.HotFocus,
-            HotFocus = base.ColorScheme.HotNormal,
-            Focus = base.ColorScheme.Normal
-        };
-    }
-    else
-    {
-        base.ColorScheme = SuperView?.ColorScheme ?? base.ColorScheme;
+            base.ColorScheme = value;
+            SetColors ();
+        }
     }
 
-    // Set KeyView's colors to show "hot"
-    if (IsInitialized && base.ColorScheme is { })
+    /// <summary>
+    /// </summary>
+    internal void SetColors ()
     {
-        var cs = new ColorScheme (base.ColorScheme)
+        // Border should match superview.
+        Border.ColorScheme = SuperView?.ColorScheme;
+
+        if (HasFocus)
         {
-            Normal = base.ColorScheme.HotNormal,
-            HotNormal = base.ColorScheme.Normal
-        };
-        KeyView.ColorScheme = cs;
+            // When we have focus, we invert the colors
+            base.ColorScheme = new (base.ColorScheme)
+            {
+                Normal = base.ColorScheme.Focus,
+                HotNormal = base.ColorScheme.HotFocus,
+                HotFocus = base.ColorScheme.HotNormal,
+                Focus = base.ColorScheme.Normal
+            };
+        }
+        else
+        {
+            base.ColorScheme = SuperView?.ColorScheme ?? base.ColorScheme;
+        }
+
+        // Set KeyView's colors to show "hot"
+        if (IsInitialized && base.ColorScheme is { })
+        {
+            var cs = new ColorScheme (base.ColorScheme)
+            {
+                Normal = base.ColorScheme.HotNormal,
+                HotNormal = base.ColorScheme.Normal
+            };
+            KeyView.ColorScheme = cs;
+        }
     }
-}
 
-View _lastFocusedView;
-/// <inheritdoc/>
-public override bool OnEnter (View view)
-{
-    SetColors ();
-    _lastFocusedView = view;
+    View _lastFocusedView;
+    /// <inheritdoc/>
+    public override bool OnEnter (View view)
+    {
+        SetColors ();
+        _lastFocusedView = view;
 
-    return base.OnEnter (view);
-}
+        return base.OnEnter (view);
+    }
 
-/// <inheritdoc/>
-public override bool OnLeave (View view)
-{
-    SetColors ();
-    _lastFocusedView = this;
+    /// <inheritdoc/>
+    public override bool OnLeave (View view)
+    {
+        SetColors ();
+        _lastFocusedView = this;
 
-    return base.OnLeave (view);
-}
+        return base.OnLeave (view);
+    }
 
-#endregion Focus
+    #endregion Focus
 
-/// <inheritdoc/>
-protected override void Dispose (bool disposing)
-{
-    if (disposing)
+    /// <inheritdoc/>
+    protected override void Dispose (bool disposing)
     {
-        if (CommandView?.IsAdded == false)
+        if (disposing)
         {
-            CommandView.Dispose ();
-        }
+            if (CommandView?.IsAdded == false)
+            {
+                CommandView.Dispose ();
+            }
 
-        if (HelpView?.IsAdded == false)
-        {
-            HelpView.Dispose ();
-        }
+            if (HelpView?.IsAdded == false)
+            {
+                HelpView.Dispose ();
+            }
 
-        if (KeyView?.IsAdded == false)
-        {
-            KeyView.Dispose ();
+            if (KeyView?.IsAdded == false)
+            {
+                KeyView.Dispose ();
+            }
         }
-    }
 
-    base.Dispose (disposing);
-}
+        base.Dispose (disposing);
+    }
 }

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

@@ -1454,9 +1454,13 @@ public class Slider<T> : View
             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);
     }
 

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

@@ -65,8 +65,6 @@ public class StatusBar : 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;

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

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

@@ -319,7 +319,7 @@ public class TableView : View
         {
             if (cellActivationKey != value)
             {
-                KeyBindings.Replace (cellActivationKey, value);
+                KeyBindings.ReplaceKey (cellActivationKey, value);
 
                 // 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

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

@@ -1332,7 +1332,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 ()
     {

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

+ 4 - 56
Terminal.Gui/Views/TextView.cs

@@ -2369,8 +2369,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 +2501,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 +4310,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 ()
     {
@@ -5365,16 +5357,6 @@ public class TextView : View
         DoNeededAction ();
     }
 
-    private bool MoveNextView ()
-    {
-        if (Application.OverlappedTop is { })
-        {
-            return SuperView?.FocusNext () == true;
-        }
-
-        return false;
-    }
-
     private void MovePageDown ()
     {
         int nPageDnShift = Viewport.Height - 1;
@@ -5431,16 +5413,6 @@ public class TextView : View
         DoNeededAction ();
     }
 
-    private bool MovePreviousView ()
-    {
-        if (Application.OverlappedTop is { })
-        {
-            return SuperView?.FocusPrev () == true;
-        }
-
-        return false;
-    }
-
     private void MoveRight ()
     {
         List<RuneCell> currentLine = GetCurrentLine ();
@@ -5617,7 +5589,7 @@ public class TextView : View
 
         if (!AllowsTab || _isReadOnly)
         {
-            return ProcessMovePreviousView ();
+            return false;
         }
 
         if (CurrentColumn > 0)
@@ -5889,21 +5861,7 @@ public class TextView : View
         StartSelecting ();
         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)
@@ -6163,7 +6121,7 @@ public class TextView : View
 
         if (!AllowsTab || _isReadOnly)
         {
-            return ProcessMoveNextView ();
+            return false;
         }
 
         InsertText (new Key ((KeyCode)'\t'));
@@ -6369,13 +6327,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 +6344,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);
     }
 

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

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

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

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

+ 2 - 2
UICatalog/Scenarios/Buttons.cs

@@ -527,8 +527,8 @@ public class Buttons : Scenario
                 }
 
                 _value = value;
-                _number.Text = _value.ToString ();
-                ValueChanged?.Invoke (this, new (ref _value));
+                _number.Text = _value.ToString ()!;
+                ValueChanged?.Invoke (this, new (in _value));
             }
         }
 

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

+ 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

+ 20 - 17
UICatalog/Scenarios/KeyBindings.cs

@@ -80,13 +80,10 @@ public sealed class KeyBindings : Scenario
         };
         appWindow.Add (appBindingsListView);
 
-        foreach (var appBinding in Application.GetKeyBindings ())
+        foreach (var appBinding in Application.KeyBindings.Bindings)
         {
-            foreach (var view in appBinding.Value)
-            {
-                var commands = view.KeyBindings.GetCommands (appBinding.Key);
-                appBindings.Add ($"{appBinding.Key} -> {view.GetType ().Name} - {commands [0]}");
-            }
+                var commands = Application.KeyBindings.GetCommands (appBinding.Key);
+                appBindings.Add ($"{appBinding.Key} -> {appBinding.Value.BoundView?.GetType ().Name} - {commands [0]}");
         }
 
         ObservableCollection<string> hotkeyBindings = new ();
@@ -153,10 +150,10 @@ public sealed class KeyBindings : Scenario
 
     private void AppWindow_Leave (object sender, FocusEventArgs e)
     {
-        //foreach (var binding in Application.Top.MostFocused.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused))
-        //{
-        //    _focusedBindings.Add ($"{binding.Key} -> {binding.Value.Commands [0]}");
-        //}
+        foreach (var binding in Application.Top.MostFocused.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused))
+        {
+            _focusedBindings.Add ($"{binding.Key} -> {binding.Value.Commands [0]}");
+        }
     }
 }
 
@@ -166,28 +163,34 @@ public class KeyBindingsDemo : View
     {
         CanFocus = true;
 
+
+        AddCommand (Command.Save, ctx =>
+                                 {
+                                     MessageBox.Query ($"{ctx.KeyBinding?.Scope}", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok");
+                                     return true;
+                                 });
         AddCommand (Command.New, ctx =>
                                 {
-                                    MessageBox.Query ("Hi", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok");
-
+                                    MessageBox.Query ($"{ctx.KeyBinding?.Scope}", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok");
                                     return true;
                                 });
         AddCommand (Command.HotKey, ctx =>
         {
-            MessageBox.Query ("Hi", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok");
+            MessageBox.Query ($"{ctx.KeyBinding?.Scope}", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok");
             SetFocus ();
             return true;
         });
 
-        KeyBindings.Add (Key.F3, KeyBindingScope.Focused, Command.New);
-        KeyBindings.Add (Key.F4, KeyBindingScope.Application, Command.New);
-
+        KeyBindings.Add (Key.F2, KeyBindingScope.Focused, Command.Save);
+        KeyBindings.Add (Key.F3, Command.New); // same as specifying KeyBindingScope.Focused
+        Application.KeyBindings.Add (Key.F4, this, Command.New);
 
         AddCommand (Command.QuitToplevel, ctx =>
                                          {
+                                             MessageBox.Query ($"{ctx.KeyBinding?.Scope}", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok");
                                              Application.RequestStop ();
                                              return true;
                                          });
-        KeyBindings.Add (Key.Q.WithCtrl, KeyBindingScope.Application, Command.QuitToplevel);
+        Application.KeyBindings.Add (Key.Q.WithAlt, this, Command.QuitToplevel);
     }
 }

+ 1 - 1
UICatalog/Scenarios/ListColumns.cs

@@ -254,7 +254,7 @@ public class ListColumns : Scenario
         // if user clicks the mouse in TableView
         _listColView.MouseClick += (s, e) => { _listColView.ScreenToCell (e.MouseEvent.Position, out int? clickedCol); };
 
-        _listColView.KeyBindings.Add (Key.Space, Command.Accept);
+        _listColView.KeyBindings.ReplaceCommands (Key.Space, Command.Accept);
 
         top.Add (appWindow);
 

+ 1 - 1
UICatalog/Scenarios/ListViewWithSelection.cs

@@ -205,7 +205,7 @@ public class ListViewWithSelection : Scenario
 
         /// <inheritdoc />
         public event NotifyCollectionChangedEventHandler CollectionChanged;
-        public int Count => Scenarios != null ? Scenarios.Count : 0;
+        public int Count => Scenarios?.Count ?? 0;
         public int Length { get; private set; }
         public bool SuspendCollectionChangedEvent { get => throw new System.NotImplementedException (); set => throw new System.NotImplementedException (); }
 

+ 6 - 2
UICatalog/Scenarios/MenuBarScenario.cs

@@ -1,5 +1,6 @@
 using System;
 using Terminal.Gui;
+using static System.Runtime.InteropServices.JavaScript.JSType;
 
 namespace UICatalog.Scenarios;
 
@@ -64,14 +65,17 @@ public class MenuBarScenario : Scenario
         menuBar.Key = KeyCode.F9;
         menuBar.Title = "TestMenuBar";
 
-        bool fnAction (string s)
+        bool FnAction (string s)
         {
             _lastAction.Text = s;
 
             return true;
         }
+        
+        // Declare a variable for the function
+        Func<string, bool> fnActionVariable = FnAction;
 
-        menuBar.EnableForDesign ((Func<string, bool>)fnAction);
+        menuBar.EnableForDesign (ref fnActionVariable);
 
         menuBar.MenuOpening += (s, e) =>
                                {

+ 0 - 1
UICatalog/Scenarios/Notepad.cs

@@ -309,7 +309,6 @@ public class Notepad : Scenario
         tab.CloneTo (newTabView);
         newTile.ContentView.Add (newTabView);
 
-        newTabView.EnsureFocus ();
         newTabView.FocusFirst ();
         newTabView.FocusNext ();
     }

+ 1 - 1
UICatalog/Scenarios/SendKeys.cs

@@ -86,7 +86,7 @@ public class SendKeys : Scenario
                                     ? (ConsoleKey)char.ToUpper (r)
                                     : (ConsoleKey)r;
 
-                Application.Driver.SendKeys (
+                Application.Driver?.SendKeys (
                                              r,
                                              ck,
                                              ckbShift.State == CheckState.Checked,

+ 1 - 1
UICatalog/Scenarios/TableEditor.cs

@@ -769,7 +769,7 @@ public class TableEditor : Scenario
                                      }
                                  };
 
-        _tableView.KeyBindings.Add (Key.Space, Command.Accept);
+        _tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Accept);
 
         // Run - Start the application.
         Application.Run (appWindow);

+ 1 - 1
UICatalog/Scenarios/TextEffectsScenario.cs

@@ -260,5 +260,5 @@ internal class GradientsView : View
         }
     }
 
-    private static void SetColor (Color color) { Application.Driver.SetAttribute (new (color, color)); }
+    private static void SetColor (Color color) { Application.Driver?.SetAttribute (new (color, color)); }
 }

+ 2 - 2
UICatalog/Scenarios/TrueColors.cs

@@ -19,11 +19,11 @@ public class TrueColors : Scenario
         var x = 2;
         var y = 1;
 
-        bool canTrueColor = Application.Driver.SupportsTrueColor;
+        bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false;
 
         var lblDriverName = new Label
         {
-            X = x, Y = y++, Text = $"Current driver is {Application.Driver.GetType ().Name}"
+            X = x, Y = y++, Text = $"Current driver is {Application.Driver?.GetType ().Name}"
         };
         app.Add (lblDriverName);
         y++;

+ 46 - 187
UICatalog/Scenarios/ViewExperiments.cs

@@ -18,230 +18,89 @@ public class ViewExperiments : Scenario
             Title = GetQuitKeyAndName ()
         };
 
-        var containerLabel = new Label
-        {
-            X = 0,
-            Y = 0,
-
-            Width = Dim.Fill (),
-            Height = 3
-        };
-        app.Add (containerLabel);
-
         var view = new View
         {
             X = 2,
-            Y = Pos.Bottom (containerLabel),
-            Height = Dim.Fill (2),
-            Width = Dim.Fill (2),
-            Title = "View with 2xMargin, 2xBorder, & 2xPadding",
+            Y = 2,
+            Height = Dim.Auto (),
+            Width = Dim.Auto (),
+            Title = "View1",
             ColorScheme = Colors.ColorSchemes ["Base"],
-            Id = "DaView"
+            Id = "View1",
+            ShadowStyle = ShadowStyle.Transparent,
+            BorderStyle = LineStyle.Double,
+            CanFocus = true, // Can't drag without this? BUGBUG
+            Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
         };
 
-        //app.Add (view);
-
-        view.Margin.Thickness = new (2, 2, 2, 2);
-        view.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
-        view.Margin.Data = "Margin";
-        view.Border.Thickness = new (3);
-        view.Border.LineStyle = LineStyle.Single;
-        view.Border.ColorScheme = view.ColorScheme;
-        view.Border.Data = "Border";
-        view.Padding.Thickness = new (2);
-        view.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
-        view.Padding.Data = "Padding";
-
-        var window1 = new Window
+        Button button = new ()
         {
-            X = 2,
-            Y = 3,
-            Height = 7,
-            Width = 17,
-            Title = "Window 1",
-            Text = "Window #2",
-            TextAlignment = Alignment.Center
+            Title = "Button_1",
         };
+        view.Add (button);
 
-        window1.Margin.Thickness = new (0);
-        window1.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
-        window1.Margin.Data = "Margin";
-        window1.Border.Thickness = new (1);
-        window1.Border.LineStyle = LineStyle.Single;
-        window1.Border.ColorScheme = view.ColorScheme;
-        window1.Border.Data = "Border";
-        window1.Padding.Thickness = new (0);
-        window1.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
-        window1.Padding.Data = "Padding";
-
-        view.Add (window1);
-
-        var window2 = new Window
+        button = new ()
         {
-            X = Pos.Right (window1) + 1,
-            Y = 3,
-            Height = 5,
-            Width = 37,
-            Title = "Window2",
-            Text = "Window #2 (Right(window1)+1",
-            TextAlignment = Alignment.Center
+            Y = Pos.Bottom (button),
+            Title = "Button_2",
         };
+        view.Add (button);
 
-        //view3.InitializeFrames ();
-        window2.Margin.Thickness = new (1, 1, 0, 0);
-        window2.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
-        window2.Margin.Data = "Margin";
-        window2.Border.Thickness = new (1, 1, 1, 1);
-        window2.Border.LineStyle = LineStyle.Single;
-        window2.Border.ColorScheme = view.ColorScheme;
-        window2.Border.Data = "Border";
-        window2.Padding.Thickness = new (1, 1, 0, 0);
-        window2.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
-        window2.Padding.Data = "Padding";
-
-        view.Add (window2);
-
-        var view4 = new View
-        {
-            X = Pos.Right (window2) + 1,
-            Y = 3,
-            Height = 5,
-            Width = 37,
-            Title = "View4",
-            Text = "View #4 (Right(window2)+1",
-            TextAlignment = Alignment.Center
-        };
+        //app.Add (view);
 
-        //view4.InitializeFrames ();
-        view4.Margin.Thickness = new (0, 0, 1, 1);
-        view4.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
-        view4.Margin.Data = "Margin";
-        view4.Border.Thickness = new (1, 1, 1, 1);
-        view4.Border.LineStyle = LineStyle.Single;
-        view4.Border.ColorScheme = view.ColorScheme;
-        view4.Border.Data = "Border";
-        view4.Padding.Thickness = new (0, 0, 1, 1);
-        view4.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
-        view4.Padding.Data = "Padding";
-
-        view.Add (view4);
-
-        var view5 = new View
-        {
-            X = Pos.Right (view4) + 1,
-            Y = 3,
-            Height = Dim.Fill (2),
-            Width = Dim.Fill (),
-            Title = "View5",
-            Text = "View #5 (Right(view4)+1 Fill",
-            TextAlignment = Alignment.Center
-        };
+        view.BorderStyle = LineStyle.Double;
 
-        //view5.InitializeFrames ();
-        view5.Margin.Thickness = new (0, 0, 0, 0);
-        view5.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
-        view5.Margin.Data = "Margin";
-        view5.Border.Thickness = new (1, 1, 1, 1);
-        view5.Border.LineStyle = LineStyle.Single;
-        view5.Border.ColorScheme = view.ColorScheme;
-        view5.Border.Data = "Border";
-        view5.Padding.Thickness = new (0, 0, 0, 0);
-        view5.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
-        view5.Padding.Data = "Padding";
-
-        view.Add (view5);
-
-        var edit = new TextField
+        var view2 = new View
         {
-            Text = "Right (view5)",
-            X = Pos.Right (view5),
-            Y = 1,
-            Width = 15,
-            Height = 1
+            X = Pos.Right (view),
+            Y = Pos.Bottom (view),
+            Height = Dim.Auto (),
+            Width = Dim.Auto (),
+            Title = "View2",
+            ColorScheme = Colors.ColorSchemes ["Base"],
+            Id = "View2",
+            ShadowStyle = ShadowStyle.Transparent,
+            BorderStyle = LineStyle.Double,
+            CanFocus = true, // Can't drag without this? BUGBUG
+            Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
         };
-        view.Add (edit);
 
-        edit = new()
-        {
-            Text = "Right (edit) + 1",
-            X = Pos.Right (edit) + 1,
-            Y = 1,
-            Width = 20,
-            Height = 1
-        };
-        view.Add (edit);
 
-        var label50 = new View
+        button = new ()
         {
-            Title = "Border Inherit Demo",
-            Text = "Center();50%",
-            X = Pos.Center (),
-            Y = Pos.Percent (50),
-            Width = 30,
-            TextAlignment = Alignment.Center
+            Title = "Button_3",
         };
-        label50.Border.Thickness = new (1, 3, 1, 1);
-        label50.Height = 5;
-        view.Add (label50);
+        view2.Add (button);
 
-        edit = new()
+        button = new ()
         {
-            Text = "0 + Percent(50);70%",
-            X = 0 + Pos.Percent (50),
-            Y = Pos.Percent (70),
-            Width = 30,
-            Height = 1
+            Y = Pos.Bottom (button),
+            Title = "Button_4",
         };
-        view.Add (edit);
+        view2.Add (button);
 
-        edit = new() { Text = "AnchorEnd ();AnchorEnd ()", X = Pos.AnchorEnd (), Y = Pos.AnchorEnd (), Width = 30, Height = 1 };
-        view.Add (edit);
+        view2.Add (button);
 
-        edit = new()
+        button = new ()
         {
-            Text = "Left;AnchorEnd (2)",
-            X = 0,
-            Y = Pos.AnchorEnd (2),
-            Width = 30,
-            Height = 1
+            X = Pos.AnchorEnd (),
+            Y = Pos.AnchorEnd (),
+            Title = "Button_5",
         };
-        view.Add (edit);
-
-        view.LayoutComplete += (s, e) =>
-                               {
-                                   containerLabel.Text =
-                                       $"Container.Frame: {
-                                           app.Frame
-                                       } .Bounds: {
-                                           app.Viewport
-                                       }\nView.Frame: {
-                                           view.Frame
-                                       } .Viewport: {
-                                           view.Viewport
-                                       } .viewportOffset: {
-                                           view.GetViewportOffsetFromFrame ()
-                                       }\n .Padding.Frame: {
-                                           view.Padding.Frame
-                                       } .Padding.Viewport: {
-                                           view.Padding.Viewport
-                                       }";
-                               };
-
-        view.X = Pos.Center ();
 
         var editor = new AdornmentsEditor
         {
             X = 0,
-            Y = Pos.Bottom (containerLabel),
+            Y = 0,
             AutoSelectViewToEdit = true
         };
 
         app.Add (editor);
-        view.X = 36;
+        view.X = 34;
         view.Y = 4;
-        view.Width = Dim.Fill ();
-        view.Height = Dim.Fill ();
         app.Add (view);
+        app.Add (view2);
+        app.Add (button);
 
         Application.Run (app);
         app.Dispose ();

+ 1 - 1
UICatalog/Scenarios/VkeyPacketSimulator.cs

@@ -198,7 +198,7 @@ public class VkeyPacketSimulator : Scenario
                                                            char keyChar =
                                                                ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo);
 
-                                                           Application.Driver.SendKeys (
+                                                           Application.Driver?.SendKeys (
                                                                                         keyChar,
                                                                                         ConsoleKey.Packet,
                                                                                         consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift),

+ 14 - 12
UICatalog/UICatalog.cs

@@ -131,7 +131,7 @@ public class UICatalogApp
         // If no driver is provided, the default driver is used.
         Option<string> driverOption = new Option<string> ("--driver", "The ConsoleDriver to use.").FromAmong (
              Application.GetDriverTypes ()
-                        .Select (d => d.Name)
+                        .Select (d => d!.Name)
                         .ToArray ()
             );
 
@@ -383,7 +383,7 @@ public class UICatalogApp
     /// </summary>
     public class UICatalogTopLevel : Toplevel
     {
-        public ListView CategoryList;
+        public ListView? CategoryList;
         public MenuItem? MiForce16Colors;
         public MenuItem? MiIsMenuBorderDisabled;
         public MenuItem? MiIsMouseDisabled;
@@ -619,7 +619,9 @@ public class UICatalogApp
             ScenarioList.CellActivated += ScenarioView_OpenSelectedItem;
 
             // TableView typically is a grid where nav keys are biased for moving left/right.
+            ScenarioList.KeyBindings.Remove (Key.Home);
             ScenarioList.KeyBindings.Add (Key.Home, Command.TopHome);
+            ScenarioList.KeyBindings.Remove (Key.End);
             ScenarioList.KeyBindings.Add (Key.End, Command.BottomEnd);
 
             // Ideally, TableView.MultiSelect = false would turn off any keybindings for
@@ -670,7 +672,7 @@ public class UICatalogApp
 
             ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
 
-            MenuBar.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey;
+            MenuBar!.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey;
 
             if (StatusBar is { })
             {
@@ -680,7 +682,7 @@ public class UICatalogApp
 
             MiIsMouseDisabled!.Checked = Application.IsMouseDisabled;
 
-            Application.Top.SetNeedsDisplay ();
+            Application.Top!.SetNeedsDisplay ();
         }
 
         public MenuItem []? CreateThemeMenuItems ()
@@ -727,7 +729,7 @@ public class UICatalogApp
                                    }
 
                                    ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
-                                   Application.Top.SetNeedsDisplay ();
+                                   Application.Top!.SetNeedsDisplay ();
                                };
                 schemeMenuItems.Add (item);
             }
@@ -849,7 +851,7 @@ public class UICatalogApp
                                    }
 
                                    Diagnostics = _diagnosticFlags;
-                                   Application.Top.SetNeedsDisplay ();
+                                   Application.Top!.SetNeedsDisplay ();
                                };
                 menuItems.Add (item);
             }
@@ -954,7 +956,7 @@ public class UICatalogApp
                                              {
                                                  MiIsMenuBorderDisabled.Checked = (bool)!MiIsMenuBorderDisabled.Checked!;
 
-                                                 MenuBar.MenusBorderStyle = !(bool)MiIsMenuBorderDisabled.Checked
+                                                 MenuBar!.MenusBorderStyle = !(bool)MiIsMenuBorderDisabled.Checked
                                                                                 ? LineStyle.Single
                                                                                 : LineStyle.None;
                                              };
@@ -997,7 +999,7 @@ public class UICatalogApp
             MiUseSubMenusSingleFrame.Action += () =>
                                                {
                                                    MiUseSubMenusSingleFrame.Checked = (bool)!MiUseSubMenusSingleFrame.Checked!;
-                                                   MenuBar.UseSubMenusSingleFrame = (bool)MiUseSubMenusSingleFrame.Checked;
+                                                   MenuBar!.UseSubMenusSingleFrame = (bool)MiUseSubMenusSingleFrame.Checked;
                                                };
             menuItems.Add (MiUseSubMenusSingleFrame);
 
@@ -1013,7 +1015,7 @@ public class UICatalogApp
                 Title = "Force _16 Colors",
                 Shortcut = (KeyCode)Key.F6,
                 Checked = Application.Force16Colors,
-                CanExecute = () => Application.Driver.SupportsTrueColor
+                CanExecute = () => Application.Driver?.SupportsTrueColor ?? false
             };
             MiForce16Colors.CheckType |= MenuItemCheckStyle.Checked;
 
@@ -1077,7 +1079,7 @@ public class UICatalogApp
                                                 ShowStatusBar = StatusBar.Visible;
 
                                                 int height = StatusBar.Visible ? 1 : 0;
-                                                CategoryList.Height = Dim.Fill (height);
+                                                CategoryList!.Height = Dim.Fill (height);
                                                 ScenarioList.Height = Dim.Fill (height);
 
                                                 // ContentPane.Height = Dim.Fill (height);
@@ -1087,7 +1089,7 @@ public class UICatalogApp
             }
 
             Loaded -= LoadedHandler;
-            CategoryList.EnsureSelectedItemVisible ();
+            CategoryList!.EnsureSelectedItemVisible ();
             ScenarioList.EnsureSelectedCellIsVisible ();
         }
 
@@ -1098,7 +1100,7 @@ public class UICatalogApp
             if (_selectedScenario is null)
             {
                 // Save selected item state
-                _cachedCategoryIndex = CategoryList.SelectedItem;
+                _cachedCategoryIndex = CategoryList!.SelectedItem;
                 _cachedScenarioIndex = ScenarioList.SelectedRow;
 
                 // Create new instance of scenario (even though Scenarios contains instances)

+ 31 - 31
UnitTests/Application/ApplicationTests.cs

@@ -44,7 +44,7 @@ public class ApplicationTests
         Toplevel top = new ();
         Application.Begin (top);
         Assert.Equal (new (0, 0, 80, 25), Application.Top.Frame);
-        ((FakeDriver)Application.Driver).SetBufferSize (5, 5);
+        ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
         Assert.Equal (new (0, 0, 5, 5), Application.Top.Frame);
         top.Dispose ();
     }
@@ -89,12 +89,12 @@ public class ApplicationTests
 
         RunState runstate = null;
 
-        EventHandler<RunStateEventArgs> NewRunStateFn = (s, e) =>
+        EventHandler<RunStateEventArgs> newRunStateFn = (s, e) =>
                                                         {
                                                             Assert.NotNull (e.State);
                                                             runstate = e.State;
                                                         };
-        Application.NotifyNewRunState += NewRunStateFn;
+        Application.NotifyNewRunState += newRunStateFn;
 
         var topLevel = new Toplevel ();
         RunState rs = Application.Begin (topLevel);
@@ -105,7 +105,7 @@ public class ApplicationTests
         Assert.Equal (topLevel, Application.Top);
         Assert.Equal (topLevel, Application.Current);
 
-        Application.NotifyNewRunState -= NewRunStateFn;
+        Application.NotifyNewRunState -= newRunStateFn;
         Application.End (runstate);
 
         Assert.Null (Application.Current);
@@ -134,7 +134,7 @@ public class ApplicationTests
         Application.Init (driverName: driverType.Name);
         Assert.NotNull (Application.Driver);
         Assert.NotEqual (driver, Application.Driver);
-        Assert.Equal (driverType, Application.Driver.GetType ());
+        Assert.Equal (driverType, Application.Driver?.GetType ());
         Shutdown ();
     }
 
@@ -162,7 +162,7 @@ public class ApplicationTests
         // Set some values
 
         Application.Init (driverName: driverType.Name);
-        Application._initialized = true;
+        Application.IsInitialized = true;
 
         // Reset
         Application.ResetState ();
@@ -187,19 +187,19 @@ public class ApplicationTests
             Assert.Equal (Key.Empty, Application.AlternateBackwardKey);
             Assert.Equal (Key.Empty, Application.AlternateForwardKey);
             Assert.Equal (Key.Empty, Application.QuitKey);
-            Assert.Null (Application.OverlappedChildren);
-            Assert.Null (Application.OverlappedTop);
+            Assert.Null (ApplicationOverlapped.OverlappedChildren);
+            Assert.Null (ApplicationOverlapped.OverlappedTop);
 
             // Internal properties
-            Assert.False (Application._initialized);
+            Assert.False (Application.IsInitialized);
             Assert.Equal (Application.GetSupportedCultures (), Application.SupportedCultures);
             Assert.False (Application._forceFakeConsole);
-            Assert.Equal (-1, Application._mainThreadId);
-            Assert.Empty (Application._topLevels);
-            Assert.Null (Application._mouseEnteredView);
+            Assert.Equal (-1, Application.MainThreadId);
+            Assert.Empty (Application.TopLevels);
+            Assert.Null (Application.MouseEnteredView);
 
             // Keyboard
-            Assert.Empty (Application.GetViewsWithKeyBindings ());
+            Assert.Empty (Application.GetViewKeyBindings ());
 
             // Events - Can't check
             //Assert.Null (Application.NotifyNewRunState);
@@ -218,12 +218,12 @@ public class ApplicationTests
         CheckReset ();
 
         // Set the values that can be set
-        Application._initialized = true;
+        Application.IsInitialized = true;
         Application._forceFakeConsole = true;
-        Application._mainThreadId = 1;
+        Application.MainThreadId = 1;
 
         //Application._topLevels = new List<Toplevel> ();
-        Application._mouseEnteredView = new ();
+        Application.MouseEnteredView = new ();
 
         //Application.SupportedCultures = new List<CultureInfo> ();
         Application.Force16Colors = true;
@@ -233,11 +233,11 @@ public class ApplicationTests
         Application.AlternateBackwardKey = Key.A;
         Application.AlternateForwardKey = Key.B;
         Application.QuitKey = Key.C;
-        Application.AddKeyBinding (Key.A, new View ());
+        Application.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Cancel);
 
-        //Application.OverlappedChildren = new List<View> ();
-        //Application.OverlappedTop = 
-        Application._mouseEnteredView = new ();
+        //ApplicationOverlapped.OverlappedChildren = new List<View> ();
+        //ApplicationOverlapped.OverlappedTop = 
+        Application.MouseEnteredView = new ();
 
         //Application.WantContinuousButtonPressedView = new View ();
 
@@ -378,12 +378,12 @@ public class ApplicationTests
 
         RunState runstate = null;
 
-        EventHandler<RunStateEventArgs> NewRunStateFn = (s, e) =>
+        EventHandler<RunStateEventArgs> newRunStateFn = (s, e) =>
                                                         {
                                                             Assert.NotNull (e.State);
                                                             runstate = e.State;
                                                         };
-        Application.NotifyNewRunState += NewRunStateFn;
+        Application.NotifyNewRunState += newRunStateFn;
 
         RunState rs = Application.Begin (topLevel);
         Assert.NotNull (rs);
@@ -393,7 +393,7 @@ public class ApplicationTests
         Assert.Equal (topLevel, Application.Top);
         Assert.Equal (topLevel, Application.Current);
 
-        Application.NotifyNewRunState -= NewRunStateFn;
+        Application.NotifyNewRunState -= newRunStateFn;
         Application.End (runstate);
 
         Assert.Null (Application.Current);
@@ -413,13 +413,13 @@ public class ApplicationTests
     [AutoInitShutdown]
     public void Internal_Properties_Correct ()
     {
-        Assert.True (Application._initialized);
+        Assert.True (Application.IsInitialized);
         Assert.Null (Application.Top);
         RunState rs = Application.Begin (new ());
         Assert.Equal (Application.Top, rs.Toplevel);
         Assert.Null (Application.MouseGrabView); // public
         Assert.Null (Application.WantContinuousButtonPressedView); // public
-        Assert.False (Application.MoveToOverlappedChild (Application.Top));
+        Assert.False (ApplicationOverlapped.MoveToOverlappedChild (Application.Top!));
         Application.Top.Dispose ();
     }
 
@@ -565,8 +565,8 @@ public class ApplicationTests
         Assert.NotNull (Application.MainLoop);
 
         // FakeDriver is always 80x25
-        Assert.Equal (80, Application.Driver.Cols);
-        Assert.Equal (25, Application.Driver.Rows);
+        Assert.Equal (80, Application.Driver!.Cols);
+        Assert.Equal (25, Application.Driver!.Rows);
     }
 
     private void Pre_Init_State ()
@@ -695,7 +695,7 @@ public class ApplicationTests
         Application.ForceDriver = "FakeDriver";
 
         Application.Init ();
-        Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ());
+        Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ());
 
         Application.Iteration += (s, a) => { Application.RequestStop (); };
 
@@ -737,7 +737,7 @@ public class ApplicationTests
         Application.Iteration += (s, a) => { Application.RequestStop (); };
 
         Application.Run<TestToplevel> ();
-        Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ());
+        Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ());
 
         Application.Top.Dispose ();
         Shutdown ();
@@ -888,7 +888,7 @@ public class ApplicationTests
             Width = 5, Height = 5,
             Arrangement = ViewArrangement.Movable
         };
-        ((FakeDriver)Application.Driver).SetBufferSize (10, 10);
+        ((FakeDriver)Application.Driver!).SetBufferSize (10, 10);
         RunState rs = Application.Begin (w);
 
         // Don't use visuals to test as style of border can change over time.
@@ -1206,7 +1206,7 @@ public class ApplicationTests
             Thread.Sleep ((int)timeoutTime / 10);
 
             // Worst case scenario - something went wrong
-            if (Application._initialized && iteration > 25)
+            if (Application.IsInitialized && iteration > 25)
             {
                 _output.WriteLine ($"Too many iterations ({iteration}): Calling Application.RequestStop.");
                 Application.RequestStop ();

+ 5 - 2
UnitTests/Application/CursorTests.cs

@@ -141,7 +141,10 @@ public class CursorTests
 
         Assert.True (view.HasFocus);
         Assert.False (Application.PositionCursor (view));
-        Application.Driver.GetCursorVisibility (out CursorVisibility cursor);
-        Assert.Equal (CursorVisibility.Invisible, cursor);
+
+        if (Application.Driver?.GetCursorVisibility (out CursorVisibility cursor) ?? false)
+        {
+            Assert.Equal (CursorVisibility.Invisible, cursor);
+        }
     }
 }

+ 62 - 72
UnitTests/Application/KeyboardTests.cs

@@ -71,7 +71,7 @@ public class KeyboardTests
         // After Init
         Assert.Equal (Key.Esc, Application.QuitKey);
 
-        Application.Shutdown();
+        Application.Shutdown ();
     }
 
     private object _timeoutLock;
@@ -169,7 +169,7 @@ public class KeyboardTests
             _output.WriteLine ("Iteration: {0}", iteration);
             iteration++;
             Assert.True (iteration < 2, "Too many iterations, something is wrong.");
-            if (Application._initialized)
+            if (Application.IsInitialized)
             {
                 _output.WriteLine ("  Pressing QuitKey");
                 Application.OnKeyDown (Application.QuitKey);
@@ -200,62 +200,62 @@ public class KeyboardTests
                                      Assert.True (v1.HasFocus);
 
                                      // Using default keys.
-                                     top.NewKeyDownEvent (Key.Tab.WithCtrl);
+                                     Application.OnKeyDown (Key.Tab.WithCtrl);
                                      Assert.True (v2.HasFocus);
-                                     top.NewKeyDownEvent (Key.Tab.WithCtrl);
+                                     Application.OnKeyDown (Key.Tab.WithCtrl);
                                      Assert.True (v3.HasFocus);
-                                     top.NewKeyDownEvent (Key.Tab.WithCtrl);
+                                     Application.OnKeyDown (Key.Tab.WithCtrl);
                                      Assert.True (v4.HasFocus);
-                                     top.NewKeyDownEvent (Key.Tab.WithCtrl);
+                                     Application.OnKeyDown (Key.Tab.WithCtrl);
                                      Assert.True (v1.HasFocus);
 
-                                     top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl);
+                                     Application.OnKeyDown (Key.Tab.WithShift.WithCtrl);
                                      Assert.True (v4.HasFocus);
-                                     top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl);
+                                     Application.OnKeyDown (Key.Tab.WithShift.WithCtrl);
                                      Assert.True (v3.HasFocus);
-                                     top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl);
+                                     Application.OnKeyDown (Key.Tab.WithShift.WithCtrl);
                                      Assert.True (v2.HasFocus);
-                                     top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl);
+                                     Application.OnKeyDown (Key.Tab.WithShift.WithCtrl);
                                      Assert.True (v1.HasFocus);
 
-                                     top.NewKeyDownEvent (Key.PageDown.WithCtrl);
+                                     Application.OnKeyDown (Key.PageDown.WithCtrl);
                                      Assert.True (v2.HasFocus);
-                                     top.NewKeyDownEvent (Key.PageDown.WithCtrl);
+                                     Application.OnKeyDown (Key.PageDown.WithCtrl);
                                      Assert.True (v3.HasFocus);
-                                     top.NewKeyDownEvent (Key.PageDown.WithCtrl);
+                                     Application.OnKeyDown (Key.PageDown.WithCtrl);
                                      Assert.True (v4.HasFocus);
-                                     top.NewKeyDownEvent (Key.PageDown.WithCtrl);
+                                     Application.OnKeyDown (Key.PageDown.WithCtrl);
                                      Assert.True (v1.HasFocus);
 
-                                     top.NewKeyDownEvent (Key.PageUp.WithCtrl);
+                                     Application.OnKeyDown (Key.PageUp.WithCtrl);
                                      Assert.True (v4.HasFocus);
-                                     top.NewKeyDownEvent (Key.PageUp.WithCtrl);
+                                     Application.OnKeyDown (Key.PageUp.WithCtrl);
                                      Assert.True (v3.HasFocus);
-                                     top.NewKeyDownEvent (Key.PageUp.WithCtrl);
+                                     Application.OnKeyDown (Key.PageUp.WithCtrl);
                                      Assert.True (v2.HasFocus);
-                                     top.NewKeyDownEvent (Key.PageUp.WithCtrl);
+                                     Application.OnKeyDown (Key.PageUp.WithCtrl);
                                      Assert.True (v1.HasFocus);
 
                                      // Using another's alternate keys.
                                      Application.AlternateForwardKey = Key.F7;
                                      Application.AlternateBackwardKey = Key.F6;
 
-                                     top.NewKeyDownEvent (Key.F7);
+                                     Application.OnKeyDown (Key.F7);
                                      Assert.True (v2.HasFocus);
-                                     top.NewKeyDownEvent (Key.F7);
+                                     Application.OnKeyDown (Key.F7);
                                      Assert.True (v3.HasFocus);
-                                     top.NewKeyDownEvent (Key.F7);
+                                     Application.OnKeyDown (Key.F7);
                                      Assert.True (v4.HasFocus);
-                                     top.NewKeyDownEvent (Key.F7);
+                                     Application.OnKeyDown (Key.F7);
                                      Assert.True (v1.HasFocus);
 
-                                     top.NewKeyDownEvent (Key.F6);
+                                     Application.OnKeyDown (Key.F6);
                                      Assert.True (v4.HasFocus);
-                                     top.NewKeyDownEvent (Key.F6);
+                                     Application.OnKeyDown (Key.F6);
                                      Assert.True (v3.HasFocus);
-                                     top.NewKeyDownEvent (Key.F6);
+                                     Application.OnKeyDown (Key.F6);
                                      Assert.True (v2.HasFocus);
-                                     top.NewKeyDownEvent (Key.F6);
+                                     Application.OnKeyDown (Key.F6);
                                      Assert.True (v1.HasFocus);
 
                                      Application.RequestStop ();
@@ -321,14 +321,14 @@ public class KeyboardTests
         Assert.True (win2.HasFocus);
         Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
 
-        top.NewKeyDownEvent (Key.Tab.WithCtrl);
+        Application.OnKeyDown (Key.Tab.WithCtrl);
         Assert.True (win2.CanFocus);
         Assert.False (win.HasFocus);
         Assert.True (win2.CanFocus);
         Assert.True (win2.HasFocus);
         Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
 
-        top.NewKeyDownEvent (Key.Tab.WithCtrl);
+        Application.OnKeyDown (Key.Tab.WithCtrl);
         Assert.False (win.CanFocus);
         Assert.False (win.HasFocus);
         Assert.True (win2.CanFocus);
@@ -374,14 +374,14 @@ public class KeyboardTests
         Assert.False (win2.HasFocus);
         Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
 
-        top.NewKeyDownEvent (Key.Tab.WithCtrl);
+        Application.OnKeyDown (Key.Tab.WithCtrl);
         Assert.True (win.CanFocus);
         Assert.False (win.HasFocus);
         Assert.True (win2.CanFocus);
         Assert.True (win2.HasFocus);
         Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
 
-        top.NewKeyDownEvent (Key.Tab.WithCtrl);
+        Application.OnKeyDown (Key.Tab.WithCtrl);
         Assert.True (win.CanFocus);
         Assert.True (win.HasFocus);
         Assert.True (win2.CanFocus);
@@ -496,21 +496,21 @@ public class KeyboardTests
         Application.Begin (top);
 
         Application.OnKeyDown (Key.A);
-        Assert.True (invoked);
+        Assert.False (invoked);
         Assert.True (view.ApplicationCommand);
 
         invoked = false;
         view.ApplicationCommand = false;
-        view.KeyBindings.Remove (KeyCode.A);
+        Application.KeyBindings.Remove (KeyCode.A); 
         Application.OnKeyDown (Key.A); // old
         Assert.False (invoked);
         Assert.False (view.ApplicationCommand);
-        view.KeyBindings.Add (Key.A.WithCtrl, KeyBindingScope.Application, Command.Save);
+        Application.KeyBindings.Add (Key.A.WithCtrl, view, Command.Save);
         Application.OnKeyDown (Key.A); // old
         Assert.False (invoked);
         Assert.False (view.ApplicationCommand);
         Application.OnKeyDown (Key.A.WithCtrl); // new
-        Assert.True (invoked);
+        Assert.False (invoked);
         Assert.True (view.ApplicationCommand);
 
         invoked = false;
@@ -556,70 +556,60 @@ public class KeyboardTests
         top.Dispose ();
     }
 
-
     [Fact]
     [AutoInitShutdown]
-    public void KeyBinding_AddKeyBinding_Adds ()
+    public void KeyBinding_Application_KeyBindings_Add_Adds ()
     {
-        View view1 = new ();
-        Application.AddKeyBinding (Key.A, view1);
+        Application.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Accept);
+        Application.KeyBindings.Add (Key.B, KeyBindingScope.Application, Command.Accept);
 
-        View view2 = new ();
-        Application.AddKeyBinding (Key.A, view2);
-
-        Assert.True (Application.TryGetKeyBindings (Key.A, out List<View> views));
-        Assert.Contains (view1, views);
-        Assert.Contains (view2, views);
-
-        Assert.False (Application.TryGetKeyBindings (Key.B, out List<View> _));
+        Assert.True (Application.KeyBindings.TryGet (Key.A, out var binding));
+        Assert.Null (binding.BoundView);
+        Assert.True (Application.KeyBindings.TryGet (Key.B, out binding));
+        Assert.Null (binding.BoundView);
     }
 
     [Fact]
     [AutoInitShutdown]
-    public void KeyBinding_ViewKeyBindings_Add_Adds ()
+    public void KeyBinding_View_KeyBindings_Add_Adds ()
     {
         View view1 = new ();
-        view1.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save);
-        view1.KeyBindings.Add (Key.B, KeyBindingScope.HotKey, Command.Left);
-        Assert.Single (Application.GetViewsWithKeyBindings ());
+        Application.KeyBindings.Add (Key.A, view1, Command.Accept);
 
         View view2 = new ();
-        view2.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save);
-        view2.KeyBindings.Add (Key.B, KeyBindingScope.HotKey, Command.Left);
-
-        Assert.True (Application.TryGetKeyBindings (Key.A, out List<View> views));
-        Assert.Contains (view1, views);
-        Assert.Contains (view2, views);
+        Application.KeyBindings.Add (Key.B, view2, Command.Accept);
 
-        Assert.False (Application.TryGetKeyBindings (Key.B, out List<View> _));
+        Assert.True (Application.KeyBindings.TryGet (Key.A, out var binding));
+        Assert.Equal (view1, binding.BoundView);
+        Assert.True (Application.KeyBindings.TryGet (Key.B, out binding));
+        Assert.Equal (view2, binding.BoundView);
     }
 
     [Fact]
     [AutoInitShutdown]
-    public void KeyBinding_RemoveKeyBinding_Removes ()
+    public void KeyBinding_Application_RemoveKeyBinding_Removes ()
     {
-        View view1 = new ();
-        Application.AddKeyBinding (Key.A, view1);
+        Application.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Accept);
 
-        Assert.True (Application.TryGetKeyBindings (Key.A, out List<View> views));
-        Assert.Contains (view1, views);
+        Assert.True (Application.KeyBindings.TryGet (Key.A, out _));
 
-        Application.RemoveKeyBinding (Key.A, view1);
-        Assert.False (Application.TryGetKeyBindings (Key.A, out List<View> _));
+        Application.KeyBindings.Remove (Key.A);
+        Assert.False (Application.KeyBindings.TryGet (Key.A, out _));
     }
 
     [Fact]
     [AutoInitShutdown]
-    public void KeyBinding_ViewKeyBindings_RemoveKeyBinding_Removes ()
+    public void KeyBinding_View_KeyBindings_RemoveKeyBinding_Removes ()
     {
+
         View view1 = new ();
-        view1.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save);
+        Application.KeyBindings.Add (Key.A, view1, Command.Accept);
 
-        Assert.True (Application.TryGetKeyBindings (Key.A, out List<View> views));
-        Assert.Contains (view1, views);
+        View view2 = new ();
+        Application.KeyBindings.Add (Key.B, view1, Command.Accept);
 
-        view1.KeyBindings.Remove (Key.A);
-        Assert.False (Application.TryGetKeyBindings (Key.A, out List<View> _));
+        Application.KeyBindings.Remove (Key.A, view1);
+        Assert.False (Application.KeyBindings.TryGet (Key.A, out _));
     }
 
     // Test View for testing Application key Bindings
@@ -631,9 +621,9 @@ public class KeyboardTests
             AddCommand (Command.HotKey, () => HotKeyCommand = true);
             AddCommand (Command.Left, () => FocusedCommand = true);
 
-            KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save);
+            Application.KeyBindings.Add (Key.A, this, Command.Save);
             HotKey = KeyCode.H;
-            KeyBindings.Add (Key.F, KeyBindingScope.Focused, Command.Left);
+            KeyBindings.Add (Key.F, Command.Left);
         }
 
         public bool ApplicationCommand { get; set; }

+ 2 - 2
UnitTests/Clipboard/ClipboardTests.cs

@@ -9,14 +9,14 @@ public class ClipboardTests
     [Fact, AutoInitShutdown (useFakeClipboard: true, fakeClipboardAlwaysThrowsNotSupportedException: true)]
     public void IClipboard_GetClipBoardData_Throws_NotSupportedException ()
     {
-        var iclip = Application.Driver.Clipboard;
+        var iclip = Application.Driver?.Clipboard;
         Assert.Throws<NotSupportedException> (() => iclip.GetClipboardData ());
     }
 
     [Fact, AutoInitShutdown (useFakeClipboard: true, fakeClipboardAlwaysThrowsNotSupportedException: true)]
     public void IClipboard_SetClipBoardData_Throws_NotSupportedException ()
     {
-        var iclip = Application.Driver.Clipboard;
+        var iclip = Application.Driver?.Clipboard;
         Assert.Throws<NotSupportedException> (() => iclip.SetClipboardData ("foo"));
     }
 

+ 6 - 6
UnitTests/ConsoleDrivers/ClipRegionTests.cs

@@ -7,12 +7,12 @@ namespace Terminal.Gui.DriverTests;
 
 public class ClipRegionTests
 {
-    private readonly ITestOutputHelper output;
+    private readonly ITestOutputHelper _output;
 
     public ClipRegionTests (ITestOutputHelper output)
     {
         ConsoleDriver.RunningUnitTests = true;
-        this.output = output;
+        this._output = output;
     }
 
     [Theory]
@@ -26,8 +26,8 @@ public class ClipRegionTests
     {
         var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
         Application.Init (driver);
-        Application.Driver.Rows = 25;
-        Application.Driver.Cols = 80;
+        Application.Driver!.Rows = 25;
+        Application.Driver!.Cols = 80;
 
         driver.Move (0, 0);
         driver.AddRune ('x');
@@ -94,8 +94,8 @@ public class ClipRegionTests
     {
         var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
         Application.Init (driver);
-        Application.Driver.Rows = 10;
-        Application.Driver.Cols = 10;
+        Application.Driver!.Rows = 10;
+        Application.Driver!.Cols = 10;
 
         // positive
         Assert.True (driver.IsValidLocation (0, 0));

+ 1 - 1
UnitTests/ConsoleDrivers/ConsoleDriverTests.cs

@@ -234,7 +234,7 @@ public class ConsoleDriverTests
     //		{
     //			var win = new Window ();
     //			Application.Begin (win);
-    //			((FakeDriver)Application.Driver).SetBufferSize (20, 8);
+    //			((FakeDriver)Application.Driver!).SetBufferSize (20, 8);
 
     //			System.Threading.Tasks.Task.Run (() => {
     //				System.Threading.Tasks.Task.Delay (500).Wait ();

+ 1 - 1
UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs

@@ -123,7 +123,7 @@ public class ConsoleKeyMappingTests
                                          if (iterations == 0)
                                          {
                                              var keyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo);
-                                             Application.Driver.SendKeys (keyChar, ConsoleKey.Packet, shift, alt, control);
+                                             Application.Driver?.SendKeys (keyChar, ConsoleKey.Packet, shift, alt, control);
                                          }
                                      };
             Application.Run ();

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