Browse Source

Merged fucked up3

Tig 1 year ago
parent
commit
ad81978208
59 changed files with 2555 additions and 3495 deletions
  1. 5 1
      Terminal.Gui/Application/ApplicationKeyboard.cs
  2. 22 3
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  3. 1 1
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs
  4. 48 41
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  5. 41 0
      Terminal.Gui/Input/CommandContext.cs
  6. 17 7
      Terminal.Gui/Input/KeyBinding.cs
  7. 0 3
      Terminal.Gui/Input/KeyBindingScope.cs
  8. 4 2
      Terminal.Gui/Input/KeyBindings.cs
  9. 35 101
      Terminal.Gui/View/Layout/DimAuto.cs
  10. 1 1
      Terminal.Gui/View/Layout/DimAutoStyle.cs
  11. 57 52
      Terminal.Gui/View/ViewKeyboard.cs
  12. 0 236
      Terminal.Gui/Views/Bar.cs
  13. 35 32
      Terminal.Gui/Views/ComboBox.cs
  14. 116 31
      Terminal.Gui/Views/ListView.cs
  15. 43 473
      Terminal.Gui/Views/Menu/Menu.cs
  16. 127 450
      Terminal.Gui/Views/Menu/MenuBar.cs
  17. 178 0
      Terminal.Gui/Views/Menu/MenuBarItem.cs
  18. 31 0
      Terminal.Gui/Views/Menu/MenuClosingEventArgs.cs
  19. 0 73
      Terminal.Gui/Views/Menu/MenuEventArgs.cs
  20. 273 0
      Terminal.Gui/Views/Menu/MenuItem.cs
  21. 15 0
      Terminal.Gui/Views/Menu/MenuItemCheckStyle.cs
  22. 20 0
      Terminal.Gui/Views/Menu/MenuOpenedEventArgs.cs
  23. 24 0
      Terminal.Gui/Views/Menu/MenuOpeningEventArgs.cs
  24. 189 197
      Terminal.Gui/Views/RadioGroup.cs
  25. 0 529
      Terminal.Gui/Views/Shortcut.cs
  26. 11 94
      Terminal.Gui/Views/StatusBar.cs
  27. 59 0
      Terminal.Gui/Views/StatusItem.cs
  28. 8 5
      Terminal.Gui/Views/Toplevel.cs
  29. 4 3
      UICatalog/KeyBindingsDialog.cs
  30. 21 18
      UICatalog/Scenario.cs
  31. 4 3
      UICatalog/Scenarios/AllViewsTester.cs
  32. 5 4
      UICatalog/Scenarios/BackgroundWorkerCollection.cs
  33. 0 489
      UICatalog/Scenarios/Bars.cs
  34. 4 3
      UICatalog/Scenarios/CollectionNavigatorTester.cs
  35. 5 4
      UICatalog/Scenarios/ComboBoxIteration.cs
  36. 173 157
      UICatalog/Scenarios/ContextMenus.cs
  37. 47 38
      UICatalog/Scenarios/DynamicMenuBar.cs
  38. 14 16
      UICatalog/Scenarios/DynamicStatusBar.cs
  39. 150 188
      UICatalog/Scenarios/Editor.cs
  40. 191 0
      UICatalog/Scenarios/KeyBindings.cs
  41. 7 7
      UICatalog/Scenarios/Keys.cs
  42. 76 54
      UICatalog/Scenarios/ListViewWithSelection.cs
  43. 10 10
      UICatalog/Scenarios/ListsAndCombos.cs
  44. 2 2
      UICatalog/Scenarios/Localization.cs
  45. 34 24
      UICatalog/Scenarios/MenuBarScenario.cs
  46. 5 5
      UICatalog/Scenarios/Mouse.cs
  47. 5 3
      UICatalog/Scenarios/ProgressBarStyles.cs
  48. 6 6
      UICatalog/Scenarios/SingleBackgroundWorker.cs
  49. 2 1
      UICatalog/Scenarios/SpinnerStyles.cs
  50. 11 11
      UICatalog/Scenarios/Threading.cs
  51. 5 5
      UICatalog/Scenarios/Unicode.cs
  52. 15 14
      UICatalog/UICatalog.cs
  53. 5 4
      UnitTests/UICatalog/ScenarioTests.cs
  54. 20 19
      UnitTests/Views/ComboBoxTests.cs
  55. 308 40
      UnitTests/Views/ListViewTests.cs
  56. 7 10
      UnitTests/Views/MenuBarTests.cs
  57. 52 19
      UnitTests/Views/RadioGroupTests.cs
  58. 6 5
      UnitTests/Views/ScrollBarViewTests.cs
  59. 1 1
      UnitTests/Views/StatusBarTests.cs

+ 5 - 1
Terminal.Gui/Application/ApplicationKeyboard.cs

@@ -209,6 +209,11 @@ partial class Application
     /// </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>
@@ -289,6 +294,5 @@ partial class Application
     /// <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 () { _keyBindings.Clear (); }
 }

+ 22 - 3
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -315,12 +315,31 @@ public abstract class ConsoleDriver
                 {
                     Contents [row, c] = new Cell
                     {
-                        Rune = (Rune)' ', 
-                        Attribute = new Attribute (Color.White, Color.Black), 
+                        Rune = (Rune)' ',
+                        Attribute = new Attribute (Color.White, Color.Black),
                         IsDirty = true
                     };
-                    _dirtyLines [row] = true;
                 }
+                _dirtyLines [row] = true;
+            }
+        }
+    }
+
+    /// <summary>
+    /// Sets <see cref="Contents"/> as dirty for situations where views
+    /// don't need layout and redrawing, but just refresh the screen.
+    /// </summary>
+    public void SetContentsAsDirty ()
+    {
+        lock (Contents)
+        {
+            for (var row = 0; row < Rows; row++)
+            {
+                for (var c = 0; c < Cols; c++)
+                {
+                    Contents [row, c].IsDirty = true;
+                }
+                _dirtyLines [row] = true;
             }
         }
     }

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

@@ -76,7 +76,7 @@ public class EscSeqRequests
                 return false;
             }
 
-            if (found is { } && found.NumOutstanding > 0)
+            if (found is { NumOutstanding: > 0 })
             {
                 return true;
             }

+ 48 - 41
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -208,7 +208,7 @@ internal class NetEvents : IDisposable
 
         while (!cancellationToken.IsCancellationRequested)
         {
-            Task.Delay (100);
+            Task.Delay (100, cancellationToken).Wait (cancellationToken);
 
             if (Console.KeyAvailable)
             {
@@ -223,7 +223,7 @@ internal class NetEvents : IDisposable
 
     private void ProcessInputQueue ()
     {
-        while (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
+        while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
         {
             try
             {
@@ -242,13 +242,8 @@ internal class NetEvents : IDisposable
                 ConsoleModifiers mod = 0;
                 ConsoleKeyInfo newConsoleKeyInfo = default;
 
-                while (true)
+                while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
                 {
-                    if (_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
-                    {
-                        return;
-                    }
-
                     ConsoleKeyInfo consoleKeyInfo;
 
                     try
@@ -338,7 +333,7 @@ internal class NetEvents : IDisposable
             while (!cancellationToken.IsCancellationRequested)
             {
                 // Wait for a while then check if screen has changed sizes
-                Task.Delay (500, cancellationToken);
+                Task.Delay (500, cancellationToken).Wait (cancellationToken);
 
                 int buffHeight, buffWidth;
 
@@ -367,13 +362,8 @@ internal class NetEvents : IDisposable
             cancellationToken.ThrowIfCancellationRequested ();
         }
 
-        while (true)
+        while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
         {
-            if (_inputReadyCancellationTokenSource.IsCancellationRequested)
-            {
-                return;
-            }
-
             try
             {
                 _winChange.Wait (_inputReadyCancellationTokenSource.Token);
@@ -852,11 +842,37 @@ internal class NetDriver : ConsoleDriver
         { }
     }
 
-    #region Not Implemented
+    public override void Suspend ()
+    {
+        if (Environment.OSVersion.Platform != PlatformID.Unix)
+        {
+            return;
+        }
+
+        StopReportingMouseMoves ();
 
-    public override void Suspend () { throw new NotImplementedException (); }
+        if (!RunningUnitTests)
+        {
+            Console.ResetColor ();
+            Console.Clear ();
 
-    #endregion
+            //Disable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+
+            //Set cursor key to cursor.
+            Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
+
+            Platform.Suspend ();
+
+            //Enable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+
+            SetContentsAsDirty ();
+            Refresh ();
+        }
+
+        StartReportingMouseMoves ();
+    }
 
     public override void UpdateScreen ()
     {
@@ -877,7 +893,7 @@ internal class NetDriver : ConsoleDriver
         Attribute? redrawAttr = null;
         int lastCol = -1;
 
-        CursorVisibility? savedVisibitity = _cachedCursorVisibility;
+        CursorVisibility? savedVisibility = _cachedCursorVisibility;
         SetCursorVisibility (CursorVisibility.Invisible);
 
         for (int row = top; row < rows; row++)
@@ -1006,7 +1022,7 @@ internal class NetDriver : ConsoleDriver
 
         SetCursorPosition (0, 0);
 
-        _cachedCursorVisibility = savedVisibitity;
+        _cachedCursorVisibility = savedVisibility;
 
         void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
         {
@@ -1333,12 +1349,9 @@ internal class NetDriver : ConsoleDriver
     {
         _cachedCursorVisibility = visibility;
 
-        bool isVisible = RunningUnitTests
-                             ? visibility == CursorVisibility.Default
-                             : Console.CursorVisible = visibility == CursorVisibility.Default;
-        Console.Out.Write (isVisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
+        Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
 
-        return isVisible;
+        return visibility == CursorVisibility.Default;
     }
 
     public override bool EnsureCursorVisibility ()
@@ -1667,7 +1680,7 @@ internal class NetMainLoop : IMainLoopDriver
     private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
     private readonly Queue<InputResult?> _resultQueue = new ();
     private readonly ManualResetEventSlim _waitForProbe = new (false);
-    private CancellationTokenSource _eventReadyTokenSource = new ();
+    private readonly CancellationTokenSource _eventReadyTokenSource = new ();
     private MainLoop _mainLoop;
 
     /// <summary>Initializes the class with the console driver.</summary>
@@ -1719,14 +1732,13 @@ internal class NetMainLoop : IMainLoopDriver
             _eventReady.Reset ();
         }
 
+        _eventReadyTokenSource.Token.ThrowIfCancellationRequested ();
+
         if (!_eventReadyTokenSource.IsCancellationRequested)
         {
             return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
         }
 
-        _eventReadyTokenSource.Dispose ();
-        _eventReadyTokenSource = new CancellationTokenSource ();
-
         return true;
     }
 
@@ -1783,26 +1795,21 @@ internal class NetMainLoop : IMainLoopDriver
                 return;
             }
 
+            _inputHandlerTokenSource.Token.ThrowIfCancellationRequested ();
+
             if (_resultQueue.Count == 0)
             {
                 _resultQueue.Enqueue (_netEvents.DequeueInput ());
             }
 
-            try
+            while (_resultQueue.Count > 0 && _resultQueue.Peek () is null)
             {
-                while (_resultQueue.Peek () is null)
-                {
-                    _resultQueue.Dequeue ();
-                }
-
-                if (_resultQueue.Count > 0)
-                {
-                    _eventReady.Set ();
-                }
+                _resultQueue.Dequeue ();
             }
-            catch (InvalidOperationException)
+
+            if (_resultQueue.Count > 0)
             {
-                // Ignore
+                _eventReady.Set ();
             }
         }
     }

+ 41 - 0
Terminal.Gui/Input/CommandContext.cs

@@ -0,0 +1,41 @@
+#nullable enable
+namespace Terminal.Gui;
+/// <summary>
+///     Provides context for a <see cref="Command"/> that is being invoked.
+/// </summary
+/// <remarks>
+///     <para>
+///         To define a <see cref="Command"/> that is invoked with context,
+///         use <see cref="View.AddCommand(Command,Func{CommandContext,Nullable{bool}})"/>
+///     </para>
+/// </remarks>
+public record struct CommandContext
+{
+    /// <summary>
+    ///     Initializes a new instance of <see cref="CommandContext"/> with the specified <see cref="Command"/>,
+    /// </summary>
+    /// <param name="command"></param>
+    /// <param name="key"></param>
+    /// <param name="keyBinding"></param>
+    public CommandContext (Command command, Key? key, KeyBinding? keyBinding = null)
+    {
+        Command = command;
+        Key = key;
+        KeyBinding = keyBinding;
+    }
+
+    /// <summary>
+    ///     The <see cref="Command"/> that is being invoked.
+    /// </summary>
+    public Command Command { get; set; }
+
+    /// <summary>
+    ///     The <see cref="Key"/> that is being invoked. This is the key that was pressed to invoke the <see cref="Command"/>.
+    /// </summary>
+    public Key? Key { get; set; }
+
+    /// <summary>
+    /// The KeyBinding that was used to invoke the <see cref="Command"/>, if any.
+    /// </summary>
+    public KeyBinding? KeyBinding { get; set; }
+}

+ 17 - 7
Terminal.Gui/Input/KeyBinding.cs

@@ -1,24 +1,34 @@
 #nullable enable
+
 // These classes use a key binding system based on the design implemented in Scintilla.Net which is an
 // MIT licensed open source project https://github.com/jacobslusser/ScintillaNET/blob/master/src/ScintillaNET/Command.cs
 
 namespace Terminal.Gui;
 
-/// <summary>Provides a collection of <see cref="Command"/> objects that are scoped to <see cref="KeyBindingScope"/>.</summary>
+/// <summary>
+/// Provides a collection of <see cref="Command"/> objects that are scoped to <see cref="KeyBindingScope"/>.
+/// </summary>
 public record struct KeyBinding
 {
     /// <summary>Initializes a new instance.</summary>
-    /// <param name="commands"></param>
-    /// <param name="scope"></param>
-    public KeyBinding (Command [] commands, KeyBindingScope scope)
+    /// <param name="commands">The commands this key binding will invoke.</param>
+    /// <param name="scope">The scope of the <see cref="Commands"/>.</param>
+    /// <param name="context">Arbitrary context that can be associated with this key binding.</param>
+    public KeyBinding (Command [] commands, KeyBindingScope scope, object? context = null)
     {
         Commands = commands;
         Scope = scope;
+        Context = context;
     }
 
-    /// <summary>The actions which can be performed by the application or bound to keys in a <see cref="View"/> control.</summary>
+    /// <summary>The commands this key binding will invoke.</summary>
     public Command [] Commands { get; set; }
 
-    /// <summary>The scope of the <see cref="Commands"/> bound to a key.</summary>
+    /// <summary>The scope of the <see cref="Commands"/>.</summary>
     public KeyBindingScope Scope { get; set; }
-}
+
+    /// <summary>
+    ///     Arbitrary context that can be associated with this key binding.
+    /// </summary>
+    public object? Context { get; set; }
+}

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

@@ -41,9 +41,6 @@ public enum KeyBindingScope
     ///         Application-scoped key bindings are only invoked if the key down event was not handled by the focused view or
     ///         any of its subviews, and if the key was not bound to a <see cref="View.HotKey"/>.
     ///     </para>
-    ///     <para>
-    ///         <see cref="Shortcut"/> makes it easy to add Application-scoped key bindings with a visual indicator. See also <see cref="Bar"/>.
-    ///     </para>
     /// </remarks>
     Application = 4
 }

+ 4 - 2
Terminal.Gui/Input/KeyBindings.cs

@@ -2,7 +2,9 @@
 
 namespace Terminal.Gui;
 
-/// <summary>Provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.</summary>
+/// <summary>
+/// Provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.
+/// </summary>
 public class KeyBindings
 {
     /// <summary>
@@ -17,7 +19,7 @@ public class KeyBindings
     /// <summary>
     ///     The view that the <see cref="KeyBindings"/> are bound to.
     /// </summary>
-    public View BoundView { get; }
+    public View? BoundView { get; }
 
     // TODO: Add a dictionary comparer that ignores Scope
     // TODO: This should not be public!

+ 35 - 101
Terminal.Gui/View/Layout/DimAuto.cs

@@ -1,6 +1,4 @@
 #nullable enable
-using System.Drawing;
-
 namespace Terminal.Gui;
 
 /// <summary>
@@ -22,7 +20,6 @@ public class DimAuto () : Dim
     /// <summary>
     ///     Gets the maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED.
     /// </summary>
-
     // ReSharper disable once ConvertToAutoProperty
     public required Dim? MaximumContentDim
     {
@@ -35,7 +32,6 @@ public class DimAuto () : Dim
     /// <summary>
     ///     Gets the minimum dimension the View's ContentSize will be constrained to.
     /// </summary>
-
     // ReSharper disable once ConvertToAutoProperty
     public required Dim? MinimumContentDim
     {
@@ -48,7 +44,6 @@ public class DimAuto () : Dim
     /// <summary>
     ///     Gets the style of the DimAuto.
     /// </summary>
-
     // ReSharper disable once ConvertToAutoProperty
     public required DimAutoStyle Style
     {
@@ -86,11 +81,10 @@ public class DimAuto () : Dim
                 // TODO: This whole body of code is a WIP (for https://github.com/gui-cs/Terminal.Gui/pull/3451).
                 subviewsSize = 0;
 
-                List<View> includedSubviews = us.Subviews.ToList (); //.Where (v => !v.ExcludeFromLayout).ToList ();
+                List<View> includedSubviews = us.Subviews.ToList();//.Where (v => !v.ExcludeFromLayout).ToList ();
                 List<View> subviews;
 
                 #region Not Anchored and Are Not Dependent
-
                 // Start with subviews that are not anchored to the end, aligned, or dependent on content size
                 // [x] PosAnchorEnd
                 // [x] PosAlign
@@ -104,25 +98,17 @@ public class DimAuto () : Dim
                 // [ ] DimView
                 if (dimension == Dimension.Width)
                 {
-                    subviews = includedSubviews.Where (
-                                                       v => v.X is not PosAnchorEnd
-                                                            && v.X is not PosAlign
-
-                                                            // && v.X is not PosCenter
-                                                            && v.Width is not DimAuto
-                                                            && v.Width is not DimFill)
-                                               .ToList ();
+                    subviews = includedSubviews.Where (v => v.X is not PosAnchorEnd
+                                                           && v.X is not PosAlign
+                                                           // && v.X is not PosCenter
+                                                           && v.Width is not DimFill).ToList ();
                 }
                 else
                 {
-                    subviews = includedSubviews.Where (
-                                                       v => v.Y is not PosAnchorEnd
-                                                            && v.Y is not PosAlign
-
-                                                            // && v.Y is not PosCenter
-                                                            && v.Height is not DimAuto
-                                                            && v.Height is not DimFill)
-                                               .ToList ();
+                    subviews = includedSubviews.Where (v => v.Y is not PosAnchorEnd
+                                                           && v.Y is not PosAlign
+                                                           // && v.Y is not PosCenter
+                                                           && v.Height is not DimFill).ToList ();
                 }
 
                 for (var i = 0; i < subviews.Count; i++)
@@ -137,11 +123,9 @@ public class DimAuto () : Dim
                         subviewsSize = size;
                     }
                 }
-
                 #endregion Not Anchored and Are Not Dependent
 
                 #region Anchored
-
                 // Now, handle subviews that are anchored to the end
                 // [x] PosAnchorEnd
                 if (dimension == Dimension.Width)
@@ -154,7 +138,6 @@ public class DimAuto () : Dim
                 }
 
                 int maxAnchorEnd = 0;
-
                 for (var i = 0; i < subviews.Count; i++)
                 {
                     View v = subviews [i];
@@ -162,84 +145,47 @@ public class DimAuto () : Dim
                 }
 
                 subviewsSize += maxAnchorEnd;
-
                 #endregion Anchored
 
-
-                #region Auto
-
-                if (dimension == Dimension.Width)
-                {
-                    subviews = includedSubviews.Where (v => v.Width is DimAuto).ToList ();
-                }
-                else
-                {
-                    subviews = includedSubviews.Where (v => v.Height is DimAuto).ToList ();
-                }
-
-                int maxAuto = 0;
-
-                for (var i = 0; i < subviews.Count; i++)
-                {
-                    View v = subviews [i];
-
-                    maxAuto = CalculateMinDimension (us, dimension);
-
-                    if (maxAuto > subviewsSize)
-                    {
-                        // BUGBUG: Should we break here? Or choose min/max?
-                        subviewsSize = maxAuto;
-                    }
-                }
-
-                //                subviewsSize += maxAuto;
-
-                #endregion Auto
-
-                #region Center
-
-                // Now, handle subviews that are Centered
-                if (dimension == Dimension.Width)
-                {
-                    subviews = us.Subviews.Where (v => v.X is PosCenter).ToList ();
-                }
-                else
-                {
-                    subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList ();
-                }
-
-                int maxCenter = 0;
-
-                for (var i = 0; i < subviews.Count; i++)
-                {
-                    View v = subviews [i];
-                    maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
-
-                    if (maxCenter > subviewsSize)
-                    {
-                        // BUGBUG: Should we break here? Or choose min/max?
-                        subviewsSize = maxCenter;
-                    }
-                }
-
-                #endregion Center
+                //#region Center
+                //// Now, handle subviews that are Centered
+                //if (dimension == Dimension.Width)
+                //{
+                //    subviews = us.Subviews.Where (v => v.X is PosCenter).ToList ();
+                //}
+                //else
+                //{
+                //    subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList ();
+                //}
+
+                //int maxCenter = 0;
+                //for (var i = 0; i < subviews.Count; i++)
+                //{
+                //    View v = subviews [i];
+                //    maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
+                //}
+
+                //subviewsSize += maxCenter;
+                //#endregion Center
 
                 #region Are Dependent
-
                 // Now, go back to those that are dependent on content size
                 // [x] DimFill
                 // [ ] DimPercent
                 if (dimension == Dimension.Width)
                 {
-                    subviews = includedSubviews.Where (v => v.Width is DimFill).ToList ();
+                    subviews = includedSubviews.Where (v => v.Width is DimFill
+                                                      // || v.X is PosCenter
+                                                     ).ToList ();
                 }
                 else
                 {
-                    subviews = includedSubviews.Where (v => v.Height is DimFill).ToList ();
+                    subviews = includedSubviews.Where (v => v.Height is DimFill
+                                                      //|| v.Y is PosCenter
+                                                     ).ToList ();
                 }
 
                 int maxFill = 0;
-
                 for (var i = 0; i < subviews.Count; i++)
                 {
                     View v = subviews [i];
@@ -252,12 +198,10 @@ public class DimAuto () : Dim
                     {
                         v.SetRelativeLayout (new Size (0, autoMax - subviewsSize));
                     }
-
                     maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
                 }
 
                 subviewsSize += maxFill;
-
                 #endregion Are Dependent
             }
         }
@@ -283,16 +227,6 @@ public class DimAuto () : Dim
         return int.Min (max, autoMax);
     }
 
-    internal int CalculateMinDimension (View us, Dimension dimension)
-    {
-
-
-        int min = dimension == Dimension.Width ? us.Frame.Width : us.Frame.Height;
-
-        return min;
-    }
-
-
     internal override bool ReferencesOtherViews ()
     {
         // BUGBUG: This is not correct. _contentSize may be null.

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

@@ -12,7 +12,7 @@ public enum DimAutoStyle
     /// <summary>
     ///     The dimensions will be computed based on the View's <see cref="View.GetContentSize ()"/> and/or <see cref="View.Subviews"/>.
     ///     <para>
-    ///         If <see cref="View.GetContentSize ()TracksViewport"/> is <see langword="true"/>, <see cref="View.GetContentSize ()"/> will be used to determine the dimension.
+    ///         If <see cref="View.ContentSizeTracksViewport"/> is <see langword="true"/>, <see cref="View.GetContentSize ()"/> will be used to determine the dimension.
     ///     </para>
     ///     <para>
     ///         Otherwise, the Subview in <see cref="View.Subviews"/> with the largest corresponding position plus dimension

+ 57 - 52
Terminal.Gui/View/ViewKeyboard.cs

@@ -129,9 +129,10 @@ public partial class View
     /// </remarks>
     /// <param name="prevHotKey">The HotKey <paramref name="hotKey"/> is replacing. Key bindings for this key will be removed.</param>
     /// <param name="hotKey">The new HotKey. If <see cref="Key.Empty"/> <paramref name="prevHotKey"/> bindings will be removed.</param>
+    /// <param name="context">Arbitrary context that can be associated with this key binding.</param>
     /// <returns><see langword="true"/> if the HotKey bindings were added.</returns>
     /// <exception cref="ArgumentException"></exception>
-    public virtual bool AddKeyBindingsForHotKey (Key prevHotKey, Key hotKey)
+    public virtual bool AddKeyBindingsForHotKey (Key prevHotKey, Key hotKey, [CanBeNull] object context = null)
     {
         if (_hotKey == hotKey)
         {
@@ -194,15 +195,16 @@ public partial class View
         // Add the new 
         if (newKey != Key.Empty)
         {
+            KeyBinding keyBinding = new ([Command.HotKey], KeyBindingScope.HotKey, context);
             // Add the base and Alt key
-            KeyBindings.Add (newKey, KeyBindingScope.HotKey, Command.HotKey);
-            KeyBindings.Add (newKey.WithAlt, KeyBindingScope.HotKey, Command.HotKey);
+            KeyBindings.Add (newKey, keyBinding);
+            KeyBindings.Add (newKey.WithAlt, keyBinding);
 
             // If the Key is A..Z, add ShiftMask and AltMask | ShiftMask
             if (newKey.IsKeyCodeAtoZ)
             {
-                KeyBindings.Add (newKey.WithShift, KeyBindingScope.HotKey, Command.HotKey);
-                KeyBindings.Add (newKey.WithShift.WithAlt, KeyBindingScope.HotKey, Command.HotKey);
+                KeyBindings.Add (newKey.WithShift, keyBinding);
+                KeyBindings.Add (newKey.WithShift.WithAlt, keyBinding);
             }
         }
 
@@ -619,7 +621,7 @@ public partial class View
     /// <summary>Gets the key bindings for this view.</summary>
     public KeyBindings KeyBindings { get; internal set; }
 
-    private Dictionary<Command, Func<bool?>> CommandImplementations { get; } = new ();
+    private Dictionary<Command, Func<CommandContext, bool?>> CommandImplementations { get; } = new ();
 
     /// <summary>
     ///     Low-level API called when a user presses a key; invokes any key bindings set on the view. This is called
@@ -662,17 +664,17 @@ public partial class View
             return true;
         }
 
-        if (Margin is {} && ProcessAdornmentKeyBindings (Margin, keyEvent, ref handled))
+        if (Margin is { } && ProcessAdornmentKeyBindings (Margin, keyEvent, ref handled))
         {
             return true;
         }
 
-        if (Padding is {} && ProcessAdornmentKeyBindings (Padding, keyEvent, ref handled))
+        if (Padding is { } && ProcessAdornmentKeyBindings (Padding, keyEvent, ref handled))
         {
             return true;
         }
 
-        if (Border is {} && ProcessAdornmentKeyBindings (Border, keyEvent, ref handled))
+        if (Border is { } && ProcessAdornmentKeyBindings (Border, keyEvent, ref handled))
         {
             return true;
         }
@@ -720,30 +722,6 @@ public partial class View
         return false;
     }
 
-    // Function to search the subview hierarchy for the first view that has a KeyBindingScope.Application binding for the key.
-    // Called from Application.OnKeyDown
-    // TODO: Unwind recursion
-    // QUESTION: Should this return a list of views? As is, it will only return the first view that has the binding.
-    internal static View FindViewWithApplicationKeyBinding (View start, Key keyEvent)
-    {
-        if (start.KeyBindings.TryGet (keyEvent, KeyBindingScope.Application, out KeyBinding binding))
-        {
-            return start;
-        }
-
-        foreach (View subview in start.Subviews)
-        {
-            View found = FindViewWithApplicationKeyBinding (subview, keyEvent);
-
-            if (found is { })
-            {
-                return found;
-            }
-        }
-
-        return null;
-    }
-
     /// <summary>
     ///     Invoked when a key is pressed that may be mapped to a key binding. Set <see cref="Key.Handled"/> to true to
     ///     stop the key from being processed by other views.
@@ -779,7 +757,7 @@ public partial class View
             }
 
             // each command has its own return value
-            bool? thisReturn = InvokeCommand (command);
+            bool? thisReturn = InvokeCommand (command, key, binding);
 
             // if we haven't got anything yet, the current command result should be used
             toReturn ??= thisReturn;
@@ -798,12 +776,13 @@ public partial class View
     ///     Invokes the specified commands.
     /// </summary>
     /// <param name="commands"></param>
+    /// <param name="key">The key that caused the commands to be invoked, if any.</param>
     /// <returns>
     ///     <see langword="null"/> if no command was found.
     ///     <see langword="true"/> if the command was invoked and it handled the command.
     ///     <see langword="false"/> if the command was invoked and it did not handle the command.
     /// </returns>
-    public bool? InvokeCommands (Command [] commands)
+    public bool? InvokeCommands (Command [] commands, [CanBeNull] Key key = null, [CanBeNull] KeyBinding? keyBinding = null)
     {
         bool? toReturn = null;
 
@@ -815,7 +794,7 @@ public partial class View
             }
 
             // each command has its own return value
-            bool? thisReturn = InvokeCommand (command);
+            bool? thisReturn = InvokeCommand (command, key, keyBinding);
 
             // if we haven't got anything yet, the current command result should be used
             toReturn ??= thisReturn;
@@ -831,42 +810,68 @@ public partial class View
     }
 
     /// <summary>Invokes the specified command.</summary>
-    /// <param name="command"></param>
+    /// <param name="command">The command to invoke.</param>
+    /// <param name="key">The key that caused the command to be invoked, if any.</param>
+    /// <param name="keyBinding"></param>
     /// <returns>
-    ///     <see langword="null"/> if no command was found. <see langword="true"/> if the command was invoked and it
-    ///     handled the command. <see langword="false"/> if the command was invoked and it did not handle the command.
+    ///     <see langword="null"/> if no command was found. <see langword="true"/> if the command was invoked, and it
+    ///     handled the command. <see langword="false"/> if the command was invoked, and it did not handle the command.
     /// </returns>
-    public bool? InvokeCommand (Command command)
+    public bool? InvokeCommand (Command command, [CanBeNull] Key key = null, [CanBeNull] KeyBinding? keyBinding = null)
     {
-        if (!CommandImplementations.ContainsKey (command))
+        if (CommandImplementations.TryGetValue (command, out Func<CommandContext, bool?> implementation))
         {
-            return null;
+            var context = new CommandContext (command, key, keyBinding); // Create the context here
+            return implementation (context);
         }
 
-        return CommandImplementations [command] ();
+        return null;
+    }
+
+    /// <summary>
+    ///     <para>
+    ///         Sets the function that will be invoked for a <see cref="Command"/>. Views should call
+    ///        AddCommand for each command they support.
+    ///     </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 require <see cref="CommandContext"/>. Use <see cref="AddCommand(Command,Func{System.Nullable{bool}})"/>
+    ///     in cases where the command does not require a <see cref="CommandContext"/>.
+    /// </para>
+    /// </remarks>
+    /// <param name="command">The command.</param>
+    /// <param name="f">The function.</param>
+    protected void AddCommand (Command command, Func<CommandContext, bool?> f)
+    {
+        CommandImplementations [command] = f;
     }
 
     /// <summary>
     ///     <para>
     ///         Sets the function that will be invoked for a <see cref="Command"/>. Views should call
-    ///         <see cref="AddCommand"/> for each command they support.
+    ///        AddCommand for each command they support.
     ///     </para>
     ///     <para>
-    ///         If <see cref="AddCommand"/> has already been called for <paramref name="command"/> <paramref name="f"/> will
+    ///         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"/>.
+    ///     If the command requires context, use <see cref="AddCommand(Command,Func{CommandContext,System.Nullable{bool}})"/>
+    /// </para>
+    /// </remarks>
     /// <param name="command">The command.</param>
     /// <param name="f">The function.</param>
     protected void AddCommand (Command command, Func<bool?> f)
     {
-        // if there is already an implementation of this command
-        // replace that implementation
-        // else record how to perform the action (this should be the normal case)
-        if (CommandImplementations is { })
-        {
-            CommandImplementations [command] = f;
-        }
+        CommandImplementations [command] = ctx => f (); ;
     }
 
     /// <summary>Returns all commands that are supported by this <see cref="View"/>.</summary>

+ 0 - 236
Terminal.Gui/Views/Bar.cs

@@ -1,236 +0,0 @@
-using System;
-using System.Linq;
-
-namespace Terminal.Gui;
-
-/// <summary>
-///     Provides a horizontally or vertically oriented container for other views to be used as a menu, toolbar, or status bar.
-/// </summary>
-/// <remarks>
-/// </remarks>
-public class Bar : View
-{
-    /// <inheritdoc/>
-    public Bar ()
-    {
-        SetInitialProperties ();
-    }
-
-    /// <summary>
-    ///     Gets or sets the <see cref="Orientation"/> for this <see cref="Bar"/>. The default is
-    ///     <see cref="Orientation.Horizontal"/>.
-    /// </summary>
-    public Orientation Orientation { get; set; } = Orientation.Horizontal;
-
-    public bool StatusBarStyle { get; set; } = true;
-
-    public override void Add (View view)
-    {
-        if (Orientation == Orientation.Horizontal)
-        {
-            //view.AutoSize = true;
-        }
-
-        //if (StatusBarStyle)
-        //{
-        //    // Light up right border
-        //    view.BorderStyle = LineStyle.Single;
-        //    view.Border.Thickness = new Thickness (0, 0, 1, 0);
-        //}
-
-        //if (view is not Shortcut)
-        //{
-        //    if (StatusBarStyle)
-        //    {
-        //        view.Padding.Thickness = new Thickness (0, 0, 1, 0);
-        //    }
-
-        //    view.Margin.Thickness = new Thickness (1, 0, 0, 0);
-        //}
-
-        //view.ColorScheme = ColorScheme;
-
-        // Add any HotKey keybindings to our bindings
-        IEnumerable<KeyValuePair<Key, KeyBinding>> bindings = view.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.HotKey);
-
-        foreach (KeyValuePair<Key, KeyBinding> binding in bindings)
-        {
-            AddCommand (
-                        binding.Value.Commands [0],
-                        () =>
-                        {
-                            if (view is Shortcut shortcut)
-                            {
-                                return shortcut.CommandView.InvokeCommands (binding.Value.Commands);
-                            }
-
-                            return false;
-                        });
-            KeyBindings.Add (binding.Key, binding.Value);
-        }
-
-        base.Add (view);
-    }
-
-    private void Bar_LayoutStarted (object sender, LayoutEventArgs e)
-    {
-        View prevBarItem = null;
-
-        switch (Orientation)
-        {
-            case Orientation.Horizontal:
-                for (var index = 0; index < Subviews.Count; index++)
-                {
-                    View barItem = Subviews [index];
-
-                    if (!barItem.Visible)
-                    {
-                        continue;
-                    }
-
-                    if (prevBarItem == null)
-                    {
-                        barItem.X = 0;
-                    }
-                    else
-                    {
-                        // Make view to right be autosize
-                        //Subviews [^1].AutoSize = true;
-
-                        // Align the view to the right of the previous view
-                        barItem.X = Pos.Right (prevBarItem);
-                    }
-
-                    barItem.Y = Pos.Center ();
-                    barItem.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue));
-                    prevBarItem = barItem;
-                }
-
-                break;
-
-            case Orientation.Vertical:
-                // CommandView is aligned left, HelpView is aligned right, KeyView is aligned right
-                // All CommandView's are the same width, all HelpView's are the same width,
-                // all KeyView's are the same width
-
-                int maxCommandWidth = 0;
-                int maxHelpWidth = 0;
-
-                List<Shortcut> shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast<Shortcut> ().ToList ();
-
-                foreach (Shortcut shortcut in shortcuts)
-                {
-                    // Let AutoSize do its thing to get the minimum width of each CommandView and HelpView
-                    shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue));
-                    shortcut.KeyView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue));
-                    shortcut.HelpView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue));
-                }
-
-                maxCommandWidth = shortcuts.Max (s => s.CommandView.Frame.Width);
-                maxHelpWidth = shortcuts.Max (s => s.HelpView.Frame.Width);
-
-                // Set the width of all CommandView's and HelpView's to the max width
-                foreach (Shortcut shortcut in shortcuts)
-                {
-                    shortcut.CommandView.Width = Dim.Auto (minimumContentDim: maxCommandWidth);
-                    shortcut.KeyView.Width = Dim.Auto ();
-                    shortcut.HelpView.Width = Dim.Auto (minimumContentDim: maxHelpWidth);
-
-                   // shortcut.LayoutSubviews ();
-                }
-
-                // Set the overall size of the Bar and arrange the views vertically
-
-                var maxBarItemWidth = 0;
-
-                for (var index = 0; index < Subviews.Count; index++)
-                {
-                    View barItem = Subviews [index];
-
-                    if (!barItem.Visible)
-                    {
-                        continue;
-                    }
-
-                    if (prevBarItem == null)
-                    {
-                        barItem.Y = 0;
-                    }
-                    else
-                    {
-                        // Align the view to the bottom of the previous view
-                        barItem.Y = index;
-                    }
-
-                    prevBarItem = barItem;
-
-                    if (barItem is Shortcut shortcut)
-                    {
-                        //shortcut.SetRelativeLayout (new (int.MaxValue, int.MaxValue));
-                        maxBarItemWidth = Math.Max (maxBarItemWidth, shortcut.Frame.Width);
-                    }
-                    else
-                    {
-                        maxBarItemWidth = Math.Max (maxBarItemWidth, barItem.Frame.Width);
-                    }
-
-                    barItem.X = 0;
-                }
-
-                foreach (Shortcut shortcut in shortcuts)
-                {
-                    if (Width is DimAuto)
-                    {
-                        shortcut.Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: maxBarItemWidth);
-                    }
-                    else
-                    {
-                        //shortcut._container.Width = Dim.Fill ();
-                       // shortcut.Width = Dim.Fill ();
-                    }
-
-                    shortcut.LayoutSubviews ();
-                }
-                
-
-                //for (var index = 0; index < Subviews.Count; index++)
-                //{
-                //    var shortcut = Subviews [index] as Shortcut;
-
-                //    if (shortcut is { Visible: false })
-                //    {
-                //        continue;
-                //    }
-
-                //    if (Width is DimAuto)
-                //    {
-                //        shortcut._container.Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: maxBarItemWidth);
-                //    }
-                //    else
-                //    {
-                //        shortcut._container.Width = Dim.Fill ();
-                //        shortcut.Width = Dim.Fill ();
-                //    }
-
-                //    //shortcut.SetContentSize (new (maxBarItemWidth, 1));
-                //    //shortcut.Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: int.Max(maxBarItemWidth, GetContentSize().Width));
-
-                //}
-
-
-
-                break;
-        }
-    }
-
-    private void SetInitialProperties ()
-    {
-        ColorScheme = Colors.ColorSchemes ["Menu"];
-        CanFocus = true;
-
-        Width = Dim.Auto ();
-        Height = Dim.Auto ();
-
-        LayoutStarted += Bar_LayoutStarted;
-    }
-}

+ 35 - 32
Terminal.Gui/Views/ComboBox.cs

@@ -5,7 +5,7 @@
 //   Ross Ferguson ([email protected])
 //
 
-using System.Collections;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 
 namespace Terminal.Gui;
@@ -16,7 +16,7 @@ public class ComboBox : View
     private readonly ComboListView _listview;
     private readonly int _minimumHeight = 2;
     private readonly TextField _search;
-    private readonly IList _searchset = new List<object> ();
+    private readonly ObservableCollection<object> _searchSet = [];
     private bool _autoHide = true;
     private bool _hideDropdownListOnClick;
     private int _lastSelectedItem = -1;
@@ -47,9 +47,9 @@ public class ComboBox : View
 
         _listview.SelectedItemChanged += (sender, e) =>
                                          {
-                                             if (!HideDropdownListOnClick && _searchset.Count > 0)
+                                             if (!HideDropdownListOnClick && _searchSet.Count > 0)
                                              {
-                                                 SetValue (_searchset [_listview.SelectedItem]);
+                                                 SetValue (_searchSet [_listview.SelectedItem]);
                                              }
                                          };
 
@@ -174,7 +174,7 @@ public class ComboBox : View
 
     /// <summary>Gets or sets the <see cref="IListDataSource"/> backing this <see cref="ComboBox"/>, enabling custom rendering.</summary>
     /// <value>The source.</value>
-    /// <remarks>Use <see cref="SetSource"/> to set a new <see cref="IList"/> source.</remarks>
+    /// <remarks>Use <see cref="SetSource{T}"/> to set a new <see cref="ObservableCollection{T}"/> source.</remarks>
     public IListDataSource Source
     {
         get => _source;
@@ -366,13 +366,13 @@ public class ComboBox : View
     /// <summary>This event is raised when the selected item in the <see cref="ComboBox"/> has changed.</summary>
     public event EventHandler<ListViewItemEventArgs> SelectedItemChanged;
 
-    /// <summary>Sets the source of the <see cref="ComboBox"/> to an <see cref="IList"/>.</summary>
-    /// <value>An object implementing the IList interface.</value>
+    /// <summary>Sets the source of the <see cref="ComboBox"/> to an <see cref="ObservableCollection{T}"/>.</summary>
+    /// <value>An object implementing the INotifyCollectionChanged and INotifyPropertyChanged interface.</value>
     /// <remarks>
-    ///     Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custome
+    ///     Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custom
     ///     rendering.
     /// </remarks>
-    public void SetSource (IList source)
+    public void SetSource<T> (ObservableCollection<T> source)
     {
         if (source is null)
         {
@@ -380,7 +380,7 @@ public class ComboBox : View
         }
         else
         {
-            _listview.SetSource (source);
+            _listview.SetSource<T> (source);
             Source = _listview.Source;
         }
     }
@@ -408,7 +408,7 @@ public class ComboBox : View
 
         return Math.Min (
                          Math.Max (Viewport.Height - 1, _minimumHeight - 1),
-                         _searchset?.Count > 0 ? _searchset.Count :
+                         _searchSet?.Count > 0 ? _searchSet.Count :
                          IsShow ? Math.Max (Viewport.Height - 1, _minimumHeight - 1) : 0
                         );
     }
@@ -468,9 +468,9 @@ public class ComboBox : View
             return -1;
         }
 
-        for (var i = 0; i < _searchset.Count; i++)
+        for (var i = 0; i < _searchSet.Count; i++)
         {
-            if (_searchset [i].ToString () == searchText)
+            if (_searchSet [i].ToString () == searchText)
             {
                 return i;
             }
@@ -504,14 +504,14 @@ public class ComboBox : View
         if (_search.HasFocus)
         {
             // jump to list
-            if (_searchset?.Count > 0)
+            if (_searchSet?.Count > 0)
             {
                 _listview.TabStop = true;
                 _listview.SetFocus ();
 
                 if (_listview.SelectedItem > -1)
                 {
-                    SetValue (_searchset [_listview.SelectedItem]);
+                    SetValue (_searchSet [_listview.SelectedItem]);
                 }
                 else
                 {
@@ -572,7 +572,7 @@ public class ComboBox : View
 
     private bool? MoveUpList ()
     {
-        if (_listview.HasFocus && _listview.SelectedItem == 0 && _searchset?.Count > 0) // jump back to search
+        if (_listview.HasFocus && _listview.SelectedItem == 0 && _searchSet?.Count > 0) // jump back to search
         {
             _search.CursorPosition = _search.Text.GetRuneCount ();
             _search.SetFocus ();
@@ -634,7 +634,7 @@ public class ComboBox : View
 
         ResetSearchSet ();
 
-        _listview.SetSource (_searchset);
+        _listview.SetSource (_searchSet);
         _listview.Height = CalculatetHeight ();
 
         if (Subviews.Count > 0 && HasFocus)
@@ -645,7 +645,7 @@ public class ComboBox : View
 
     private void ResetSearchSet (bool noCopy = false)
     {
-        _searchset.Clear ();
+        _searchSet.Clear ();
 
         if (_autoHide || noCopy)
         {
@@ -680,16 +680,19 @@ public class ComboBox : View
             IsShow = true;
             ResetSearchSet (true);
 
-            foreach (object item in _source.ToList ())
+            if (!string.IsNullOrEmpty (_search.Text))
             {
-                // Iterate to preserver object type and force deep copy
-                if (item.ToString ()
-                        .StartsWith (
-                                     _search.Text,
-                                     StringComparison.CurrentCultureIgnoreCase
-                                    ))
+                foreach (object item in _source.ToList ())
                 {
-                    _searchset.Add (item);
+                    // Iterate to preserver object type and force deep copy
+                    if (item.ToString ()
+                            .StartsWith (
+                                         _search.Text,
+                                         StringComparison.CurrentCultureIgnoreCase
+                                        ))
+                    {
+                        _searchSet.Add (item);
+                    }
                 }
             }
         }
@@ -710,7 +713,7 @@ public class ComboBox : View
         IsShow = false;
         _listview.TabStop = false;
 
-        if (_listview.Source.Count == 0 || (_searchset?.Count ?? 0) == 0)
+        if (_listview.Source.Count == 0 || (_searchSet?.Count ?? 0) == 0)
         {
             _text = "";
             HideList ();
@@ -719,7 +722,7 @@ public class ComboBox : View
             return;
         }
 
-        SetValue (_listview.SelectedItem > -1 ? _searchset [_listview.SelectedItem] : _text);
+        SetValue (_listview.SelectedItem > -1 ? _searchSet [_listview.SelectedItem] : _text);
         _search.CursorPosition = _search.Text.GetColumns ();
         Search_Changed (this, new StateEventArgs<string> (_search.Text, _search.Text));
         OnOpenSelectedItem ();
@@ -738,7 +741,7 @@ public class ComboBox : View
         // force deep copy
         foreach (object item in Source.ToList ())
         {
-            _searchset.Add (item);
+            _searchSet.Add (item);
         }
     }
 
@@ -762,7 +765,7 @@ public class ComboBox : View
     /// Consider making public
     private void ShowList ()
     {
-        _listview.SetSource (_searchset);
+        _listview.SetSource (_searchSet);
         _listview.Clear (); 
         _listview.Height = CalculatetHeight ();
         SuperView?.BringSubviewToFront (this);
@@ -784,9 +787,9 @@ public class ComboBox : View
         private bool _isFocusing;
         public ComboListView (ComboBox container, bool hideDropdownListOnClick) { SetInitialProperties (container, hideDropdownListOnClick); }
 
-        public ComboListView (ComboBox container, IList source, bool hideDropdownListOnClick)
+        public ComboListView (ComboBox container, ObservableCollection<string> source, bool hideDropdownListOnClick)
         {
-            Source = new ListWrapper (source);
+            Source = new ListWrapper<string> (source);
             SetInitialProperties (container, hideDropdownListOnClick);
         }
 

+ 116 - 31
Terminal.Gui/Views/ListView.cs

@@ -1,11 +1,18 @@
 using System.Collections;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
 using static Terminal.Gui.SpinnerStyle;
 
 namespace Terminal.Gui;
 
 /// <summary>Implement <see cref="IListDataSource"/> to provide custom rendering for a <see cref="ListView"/>.</summary>
-public interface IListDataSource
+public interface IListDataSource: IDisposable
 {
+    /// <summary>
+    /// Event to raise when an item is added, removed, or moved, or the entire list is refreshed.
+    /// </summary>
+    event NotifyCollectionChangedEventHandler CollectionChanged;
+
     /// <summary>Returns the number of elements to display</summary>
     int Count { get; }
 
@@ -64,7 +71,7 @@ public interface IListDataSource
 ///     </para>
 ///     <para>
 ///         By default <see cref="ListView"/> uses <see cref="object.ToString"/> to render the items of any
-///         <see cref="IList"/> object (e.g. arrays, <see cref="List{T}"/>, and other collections). Alternatively, an
+///         <see cref="ObservableCollection{T}"/> object (e.g. arrays, <see cref="List{T}"/>, and other collections). Alternatively, an
 ///         object that implements <see cref="IListDataSource"/> can be provided giving full control of what is rendered.
 ///     </para>
 ///     <para>
@@ -258,12 +265,18 @@ public class ListView : View
         set
         {
             if (_source == value)
-
             {
                 return;
             }
+
+            _source?.Dispose ();
             _source = value;
 
+            if (_source is { })
+            {
+                _source.CollectionChanged += Source_CollectionChanged;
+            }
+
             SetContentSize (new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width));
             if (IsInitialized)
             {
@@ -277,6 +290,20 @@ public class ListView : View
         }
     }
 
+    private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+    {
+        SetContentSize (new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width));
+
+        if (Source is { Count: > 0 } && _selected > Source.Count - 1)
+        {
+            SelectedItem = Source.Count - 1;
+        }
+
+        SetNeedsDisplay ();
+
+        OnCollectionChanged (e);
+    }
+
     /// <summary>Gets or sets the index of the item that will appear at the top of the <see cref="View.Viewport"/>.</summary>
     /// <remarks>
     /// This a helper property for accessing <c>listView.Viewport.Y</c>.
@@ -501,7 +528,12 @@ public class ListView : View
 
             if (Viewport.Y + _selected > Viewport.Height - 1)
             {
-                Viewport = Viewport with { Y = _selected };
+                Viewport = Viewport with
+                {
+                    Y = _selected < Viewport.Height - 1
+                            ? Math.Max (Viewport.Height - _selected + 1, 0)
+                            : Math.Max (_selected - Viewport.Height + 1, 0)
+                };
             }
 
             OnSelectedChanged ();
@@ -796,21 +828,26 @@ public class ListView : View
     /// <summary>This event is raised when the selected item in the <see cref="ListView"/> has changed.</summary>
     public event EventHandler<ListViewItemEventArgs> SelectedItemChanged;
 
+    /// <summary>
+    /// Event to raise when an item is added, removed, or moved, or the entire list is refreshed.
+    /// </summary>
+    public event NotifyCollectionChangedEventHandler CollectionChanged;
+
     /// <summary>Sets the source of the <see cref="ListView"/> to an <see cref="IList"/>.</summary>
     /// <value>An object implementing the IList interface.</value>
     /// <remarks>
-    ///     Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custome
+    ///     Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custom
     ///     rendering.
     /// </remarks>
-    public void SetSource (IList source)
+    public void SetSource<T> (ObservableCollection<T> source)
     {
-        if (source is null && (Source is null || !(Source is ListWrapper)))
+        if (source is null && Source is not ListWrapper<T>)
         {
             Source = null;
         }
         else
         {
-            Source = new ListWrapper (source);
+            Source = new ListWrapper<T> (source);
         }
     }
 
@@ -820,18 +857,18 @@ public class ListView : View
     ///     Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custom
     ///     rendering.
     /// </remarks>
-    public Task SetSourceAsync (IList source)
+    public Task SetSourceAsync<T> (ObservableCollection<T> source)
     {
         return Task.Factory.StartNew (
                                       () =>
                                       {
-                                          if (source is null && (Source is null || !(Source is ListWrapper)))
+                                          if (source is null && (Source is null || !(Source is ListWrapper<T>)))
                                           {
                                               Source = null;
                                           }
                                           else
                                           {
-                                              Source = new ListWrapper (source);
+                                              Source = new ListWrapper<T> (source);
                                           }
 
                                           return source;
@@ -843,35 +880,74 @@ public class ListView : View
     }
 
     private void ListView_LayoutStarted (object sender, LayoutEventArgs e) { EnsureSelectedItemVisible (); }
+    /// <summary>
+    /// Call the event to raises the <see cref="CollectionChanged"/>.
+    /// </summary>
+    /// <param name="e"></param>
+    protected virtual void OnCollectionChanged (NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke (this, e); }
+
+    /// <inheritdoc />
+    protected override void Dispose (bool disposing)
+    {
+        _source?.Dispose ();
+
+        base.Dispose (disposing);
+    }
 }
 
 /// <summary>
 ///     Provides a default implementation of <see cref="IListDataSource"/> that renders <see cref="ListView"/> items
 ///     using <see cref="object.ToString()"/>.
 /// </summary>
-public class ListWrapper : IListDataSource
+public class ListWrapper<T> : IListDataSource, IDisposable
 {
-    private readonly int _count;
-    private readonly BitArray _marks;
-    private readonly IList _source;
+    private int _count;
+    private BitArray _marks;
+    private readonly ObservableCollection<T> _source;
 
     /// <inheritdoc/>
-    public ListWrapper (IList source)
+    public ListWrapper (ObservableCollection<T> source)
     {
         if (source is { })
         {
             _count = source.Count;
             _marks = new BitArray (_count);
             _source = source;
+            _source.CollectionChanged += Source_CollectionChanged;
             Length = GetMaxLengthItem ();
         }
     }
 
+    private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+    {
+        CheckAndResizeMarksIfRequired ();
+        CollectionChanged?.Invoke (sender, e);
+    }
+
+    /// <inheritdoc />
+    public event NotifyCollectionChangedEventHandler CollectionChanged;
+
     /// <inheritdoc/>
-    public int Count => _source is { } ? _source.Count : 0;
+    public int Count => _source?.Count ?? 0;
 
     /// <inheritdoc/>
-    public int Length { get; }
+    public int Length { get; private set; }
+
+    void CheckAndResizeMarksIfRequired ()
+    {
+        if (_source != null && _count != _source.Count)
+        {
+            _count = _source.Count;
+            BitArray newMarks = new BitArray (_count);
+            for (var i = 0; i < Math.Min (_marks.Length, newMarks.Length); i++)
+            {
+                newMarks [i] = _marks [i];
+            }
+            _marks = newMarks;
+
+            Length = GetMaxLengthItem ();
+        }
+    }
 
     /// <inheritdoc/>
     public void Render (
@@ -886,25 +962,25 @@ public class ListWrapper : IListDataSource
     )
     {
         container.Move (Math.Max (col - start, 0), line);
-        object t = _source? [item];
 
-        if (t is null)
-        {
-            RenderUstr (driver, "", col, line, width);
-        }
-        else
+        if (_source is { })
         {
-            if (t is string u)
-            {
-                RenderUstr (driver, u, col, line, width, start);
-            }
-            else if (t is string s)
+            object t = _source [item];
+
+            if (t is null)
             {
-                RenderUstr (driver, s, col, line, width, start);
+                RenderUstr (driver, "", col, line, width);
             }
             else
             {
-                RenderUstr (driver, t.ToString (), col, line, width, start);
+                if (t is string s)
+                {
+                    RenderUstr (driver, s, col, line, width, start);
+                }
+                else
+                {
+                    RenderUstr (driver, t.ToString (), col, line, width, start);
+                }
             }
         }
     }
@@ -1011,4 +1087,13 @@ public class ListWrapper : IListDataSource
             driver.AddRune ((Rune)' ');
         }
     }
+
+    /// <inheritdoc />
+    public void Dispose ()
+    {
+        if (_source is { })
+        {
+            _source.CollectionChanged -= Source_CollectionChanged;
+        }
+    }
 }

+ 43 - 473
Terminal.Gui/Views/Menu/Menu.cs

@@ -1,292 +1,5 @@
 namespace Terminal.Gui;
 
-/// <summary>Specifies how a <see cref="MenuItem"/> shows selection state.</summary>
-[Flags]
-public enum MenuItemCheckStyle
-{
-    /// <summary>The menu item will be shown normally, with no check indicator. The default.</summary>
-    NoCheck = 0b_0000_0000,
-
-    /// <summary>The menu item will indicate checked/un-checked state (see <see cref="Checked"/>).</summary>
-    Checked = 0b_0000_0001,
-
-    /// <summary>The menu item is part of a menu radio group (see <see cref="Checked"/>) and will indicate selected state.</summary>
-    Radio = 0b_0000_0010
-}
-
-/// <summary>
-///     A <see cref="MenuItem"/> has title, an associated help text, and an action to execute on activation. MenuItems
-///     can also have a checked indicator (see <see cref="Checked"/>).
-/// </summary>
-public class MenuItem
-{
-    private readonly ShortcutHelper _shortcutHelper;
-    private bool _allowNullChecked;
-    private MenuItemCheckStyle _checkType;
-
-    private string _title;
-
-    // TODO: Update to use Key instead of KeyCode
-    /// <summary>Initializes a new instance of <see cref="MenuItem"/></summary>
-    public MenuItem (KeyCode shortcut = KeyCode.Null) : this ("", "", null, null, null, shortcut) { }
-
-    // TODO: Update to use Key instead of KeyCode
-    /// <summary>Initializes a new instance of <see cref="MenuItem"/>.</summary>
-    /// <param name="title">Title for the menu item.</param>
-    /// <param name="help">Help text to display.</param>
-    /// <param name="action">Action to invoke when the menu item is activated.</param>
-    /// <param name="canExecute">Function to determine if the action can currently be executed.</param>
-    /// <param name="parent">The <see cref="Parent"/> of this menu item.</param>
-    /// <param name="shortcut">The <see cref="Shortcut"/> keystroke combination.</param>
-    public MenuItem (
-        string title,
-        string help,
-        Action action,
-        Func<bool> canExecute = null,
-        MenuItem parent = null,
-        KeyCode shortcut = KeyCode.Null
-    )
-    {
-        Title = title ?? "";
-        Help = help ?? "";
-        Action = action;
-        CanExecute = canExecute;
-        Parent = parent;
-        _shortcutHelper = new ShortcutHelper ();
-
-        if (shortcut != KeyCode.Null)
-        {
-            Shortcut = shortcut;
-        }
-    }
-
-    /// <summary>Gets or sets the action to be invoked when the menu item is triggered.</summary>
-    /// <value>Method to invoke.</value>
-    public Action Action { get; set; }
-
-    /// <summary>
-    ///     Used only if <see cref="CheckType"/> is of <see cref="MenuItemCheckStyle.Checked"/> type. If
-    ///     <see langword="true"/> allows <see cref="Checked"/> to be null, true or false. If <see langword="false"/> only
-    ///     allows <see cref="Checked"/> to be true or false.
-    /// </summary>
-    public bool AllowNullChecked
-    {
-        get => _allowNullChecked;
-        set
-        {
-            _allowNullChecked = value;
-            Checked ??= false;
-        }
-    }
-
-    /// <summary>
-    ///     Gets or sets the action to be invoked to determine if the menu can be triggered. If <see cref="CanExecute"/>
-    ///     returns <see langword="true"/> the menu item will be enabled. Otherwise, it will be disabled.
-    /// </summary>
-    /// <value>Function to determine if the action is can be executed or not.</value>
-    public Func<bool> CanExecute { get; set; }
-
-    /// <summary>
-    ///     Sets or gets whether the <see cref="MenuItem"/> shows a check indicator or not. See
-    ///     <see cref="MenuItemCheckStyle"/>.
-    /// </summary>
-    public bool? Checked { set; get; }
-
-    /// <summary>
-    ///     Sets or gets the <see cref="MenuItemCheckStyle"/> of a menu item where <see cref="Checked"/> is set to
-    ///     <see langword="true"/>.
-    /// </summary>
-    public MenuItemCheckStyle CheckType
-    {
-        get => _checkType;
-        set
-        {
-            _checkType = value;
-
-            if (_checkType == MenuItemCheckStyle.Checked && !_allowNullChecked && Checked is null)
-            {
-                Checked = false;
-            }
-        }
-    }
-
-    /// <summary>Gets or sets arbitrary data for the menu item.</summary>
-    /// <remarks>This property is not used internally.</remarks>
-    public object Data { get; set; }
-
-    /// <summary>Gets or sets the help text for the menu item. The help text is drawn to the right of the <see cref="Title"/>.</summary>
-    /// <value>The help text.</value>
-    public string Help { get; set; }
-
-    /// <summary>Gets the parent for this <see cref="MenuItem"/>.</summary>
-    /// <value>The parent.</value>
-    public MenuItem Parent { get; set; }
-
-    /// <summary>Gets or sets the title of the menu item .</summary>
-    /// <value>The title.</value>
-    public string Title
-    {
-        get => _title;
-        set
-        {
-            if (_title == value)
-            {
-                return;
-            }
-
-            _title = value;
-            GetHotKey ();
-        }
-    }
-
-    /// <summary>Gets if this <see cref="MenuItem"/> is from a sub-menu.</summary>
-    internal bool IsFromSubMenu => Parent != null;
-
-    internal int TitleLength => GetMenuBarItemLength (Title);
-
-    // 
-    // ┌─────────────────────────────┐
-    // │ Quit  Quit UI Catalog  Ctrl+Q │
-    // └─────────────────────────────┘
-    // ┌─────────────────┐
-    // │ ◌ TopLevel Alt+T │
-    // └─────────────────┘
-    // TODO: Replace the `2` literals with named constants 
-    internal int Width => 1
-                          + // space before Title
-                          TitleLength
-                          + 2
-                          + // space after Title - BUGBUG: This should be 1 
-                          (Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio)
-                               ? 2
-                               : 0)
-                          + // check glyph + space 
-                          (Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0)
-                          + // Two spaces before Help
-                          (ShortcutTag.GetColumns () > 0
-                               ? 2 + ShortcutTag.GetColumns ()
-                               : 0); // Pad two spaces before shortcut tag (which are also aligned right)
-
-    /// <summary>Merely a debugging aid to see the interaction with main.</summary>
-    public bool GetMenuBarItem () { return IsFromSubMenu; }
-
-    /// <summary>Merely a debugging aid to see the interaction with main.</summary>
-    public MenuItem GetMenuItem () { return this; }
-
-    /// <summary>
-    ///     Returns <see langword="true"/> if the menu item is enabled. This method is a wrapper around
-    ///     <see cref="CanExecute"/>.
-    /// </summary>
-    public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
-
-    /// <summary>
-    ///     Toggle the <see cref="Checked"/> between three states if <see cref="AllowNullChecked"/> is
-    ///     <see langword="true"/> or between two states if <see cref="AllowNullChecked"/> is <see langword="false"/>.
-    /// </summary>
-    public void ToggleChecked ()
-    {
-        if (_checkType != MenuItemCheckStyle.Checked)
-        {
-            throw new InvalidOperationException ("This isn't a Checked MenuItemCheckStyle!");
-        }
-
-        bool? previousChecked = Checked;
-
-        if (AllowNullChecked)
-        {
-            Checked = previousChecked switch
-                      {
-                          null => true,
-                          true => false,
-                          false => null
-                      };
-        }
-        else
-        {
-            Checked = !Checked;
-        }
-    }
-
-    private static int GetMenuBarItemLength (string title)
-    {
-        return title.EnumerateRunes ()
-                    .Where (ch => ch != MenuBar.HotKeySpecifier)
-                    .Sum (ch => Math.Max (ch.GetColumns (), 1));
-    }
-
-    #region Keyboard Handling
-
-    // TODO: Update to use Key instead of Rune
-    /// <summary>
-    ///     The HotKey is used to activate a <see cref="MenuItem"/> with the keyboard. HotKeys are defined by prefixing the
-    ///     <see cref="Title"/> of a MenuItem with an underscore ('_').
-    ///     <para>
-    ///         Pressing Alt-Hotkey for a <see cref="MenuBarItem"/> (menu items on the menu bar) works even if the menu is
-    ///         not active). Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem.
-    ///     </para>
-    ///     <para>
-    ///         For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the
-    ///         File menu. Pressing the N key will then activate the New MenuItem.
-    ///     </para>
-    ///     <para>See also <see cref="Shortcut"/> which enable global key-bindings to menu items.</para>
-    /// </summary>
-    public Rune HotKey { get; set; }
-
-    private void GetHotKey ()
-    {
-        var nextIsHot = false;
-
-        foreach (char x in _title)
-        {
-            if (x == MenuBar.HotKeySpecifier.Value)
-            {
-                nextIsHot = true;
-            }
-            else
-            {
-                if (nextIsHot)
-                {
-                    HotKey = (Rune)char.ToUpper (x);
-
-                    break;
-                }
-
-                nextIsHot = false;
-                HotKey = default (Rune);
-            }
-        }
-    }
-
-    // TODO: Update to use Key instead of KeyCode
-    /// <summary>
-    ///     Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the
-    ///     <see cref="View"/> that is the parent of the <see cref="MenuBar"/> or <see cref="ContextMenu"/> this
-    ///     <see cref="MenuItem"/>.
-    ///     <para>
-    ///         The <see cref="KeyCode"/> will be drawn on the MenuItem to the right of the <see cref="Title"/> and
-    ///         <see cref="Help"/> text. See <see cref="ShortcutTag"/>.
-    ///     </para>
-    /// </summary>
-    public KeyCode Shortcut
-    {
-        get => _shortcutHelper.Shortcut;
-        set
-        {
-            if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == KeyCode.Null))
-            {
-                _shortcutHelper.Shortcut = value;
-            }
-        }
-    }
-
-    /// <summary>Gets the text describing the keystroke combination defined by <see cref="Shortcut"/>.</summary>
-    public string ShortcutTag => _shortcutHelper.Shortcut == KeyCode.Null
-                                     ? string.Empty
-                                     : Key.ToString (_shortcutHelper.Shortcut, MenuBar.ShortcutDelimiter);
-
-    #endregion Keyboard Handling
-}
-
 /// <summary>
 ///     An internal class used to represent a menu pop-up menu. Created and managed by <see cref="MenuBar"/> and
 ///     <see cref="ContextMenu"/>.
@@ -408,15 +121,6 @@ internal sealed class Menu : View
                    );
 
         AddKeyBindings (_barItems);
-#if SUPPORT_ALT_TO_ACTIVATE_MENU
-        Initialized += (s, e) =>
-                       {
-                           if (SuperView is { })
-                           {
-                               SuperView.KeyUp += SuperView_KeyUp;
-                           }
-                       };
-#endif
     }
 
     public Menu ()
@@ -462,9 +166,9 @@ internal sealed class Menu : View
                         return true;
                     }
                    );
-        AddCommand (Command.Select, () => _host?.SelectItem (_menuItemToSelect));
-        AddCommand (Command.ToggleExpandCollapse, () => SelectOrRun ());
-        AddCommand (Command.HotKey, () => _host?.SelectItem (_menuItemToSelect));
+        AddCommand (Command.Select, ctx => _host?.SelectItem (ctx.KeyBinding?.Context as MenuItem));
+        AddCommand (Command.ToggleExpandCollapse, ctx => ExpandCollapse (ctx.KeyBinding?.Context as MenuItem));
+        AddCommand (Command.HotKey, ctx => _host?.SelectItem (ctx.KeyBinding?.Context as MenuItem));
 
         // Default key bindings for this view
         KeyBindings.Add (Key.CursorUp, Command.LineUp);
@@ -473,26 +177,7 @@ internal sealed class Menu : View
         KeyBindings.Add (Key.CursorRight, Command.Right);
         KeyBindings.Add (Key.Esc, Command.Cancel);
         KeyBindings.Add (Key.Enter, Command.Accept);
-        KeyBindings.Add (Key.F9, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
-
-        KeyBindings.Add (
-                         KeyCode.CtrlMask | KeyCode.Space,
-                         KeyBindingScope.HotKey,
-                         Command.ToggleExpandCollapse
-                        );
-    }
-
-#if SUPPORT_ALT_TO_ACTIVATE_MENU
-    void SuperView_KeyUp (object sender, KeyEventArgs e)
-    {
-        if (SuperView is null || SuperView.CanFocus == false || SuperView.Visible == false)
-        {
-            return;
-        }
-
-        _host.AltKeyUpHandler (e);
     }
-#endif
 
     private void AddKeyBindings (MenuBarItem menuBarItem)
     {
@@ -503,16 +188,18 @@ internal sealed class Menu : View
 
         foreach (MenuItem menuItem in menuBarItem.Children.Where (m => m is { }))
         {
-            KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, Command.ToggleExpandCollapse);
+            KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, menuItem);
 
-            KeyBindings.Add (
-                             (KeyCode)menuItem.HotKey.Value | KeyCode.AltMask,
-                             Command.ToggleExpandCollapse
-                            );
+            if ((KeyCode)menuItem.HotKey.Value != KeyCode.Null)
+            {
+                KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, keyBinding);
+                KeyBindings.Add ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask, keyBinding);
+            }
 
             if (menuItem.Shortcut != KeyCode.Null)
             {
-                KeyBindings.Add (menuItem.Shortcut, KeyBindingScope.HotKey, Command.Select);
+                keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem);
+                KeyBindings.Add (menuItem.Shortcut, keyBinding);
             }
 
             MenuBarItem subMenu = menuBarItem.SubMenu (menuItem);
@@ -520,25 +207,29 @@ internal sealed class Menu : View
         }
     }
 
-    private int _menuBarItemToActivate = -1;
-    private MenuItem _menuItemToSelect;
-
-    /// <summary>Called when a key bound to Command.Select is pressed. This means a hot key was pressed.</summary>
+    /// <summary>Called when a key bound to Command.ToggleExpandCollapse is pressed. This means a hot key was pressed.</summary>
     /// <returns></returns>
-    private bool SelectOrRun ()
+    private bool ExpandCollapse (MenuItem menuItem)
     {
         if (!IsInitialized || !Visible)
         {
             return true;
         }
 
-        if (_menuBarItemToActivate != -1)
+
+        for (var c = 0; c < _barItems.Children.Length; c++)
         {
-            _host.Activate (1, _menuBarItemToActivate);
+            if (_barItems.Children [c] == menuItem)
+            {
+                _currentChild = c;
+
+                break;
+            }
         }
-        else if (_menuItemToSelect is { })
+
+        if (menuItem is { })
         {
-            var m = _menuItemToSelect as MenuBarItem;
+            var m = menuItem as MenuBarItem;
 
             if (m?.Children?.Length > 0)
             {
@@ -566,7 +257,7 @@ internal sealed class Menu : View
             }
             else
             {
-                _host.SelectItem (_menuItemToSelect);
+                _host.SelectItem (menuItem);
             }
         }
         else if (_host.IsMenuOpen)
@@ -578,82 +269,12 @@ internal sealed class Menu : View
             _host.OpenMenu ();
         }
 
-        //_openedByHotKey = true;
         return true;
     }
 
     /// <inheritdoc/>
     public override bool? OnInvokingKeyBindings (Key keyEvent)
     {
-        // This is a bit of a hack. We want to handle the key bindings for menu bar but
-        // InvokeKeyBindings doesn't pass any context so we can't tell which item it is for.
-        // So before we call the base class we set SelectedItem appropriately.
-
-        KeyCode key = keyEvent.KeyCode;
-
-        if (KeyBindings.TryGet (key, out _))
-        {
-            _menuBarItemToActivate = -1;
-            _menuItemToSelect = null;
-
-            MenuItem [] children = _barItems.Children;
-
-            if (children is null)
-            {
-                return base.OnInvokingKeyBindings (keyEvent);
-            }
-
-            // Search for shortcuts first. If there's a shortcut, we don't want to activate the menu item.
-            foreach (MenuItem c in children)
-            {
-                if (key == c?.Shortcut)
-                {
-                    _menuBarItemToActivate = -1;
-                    _menuItemToSelect = c;
-                    //keyEvent.Scope = KeyBindingScope.HotKey;
-
-                    return base.OnInvokingKeyBindings (keyEvent);
-                }
-
-                MenuBarItem subMenu = _barItems.SubMenu (c);
-
-                if (FindShortcutInChildMenu (key, subMenu))
-                {
-                    //keyEvent.Scope = KeyBindingScope.HotKey;
-
-                    return base.OnInvokingKeyBindings (keyEvent);
-                }
-            }
-
-            // Search for hot keys next.
-            for (var c = 0; c < children.Length; c++)
-            {
-                int hotKeyValue = children [c]?.HotKey.Value ?? default (int);
-                var hotKey = (KeyCode)hotKeyValue;
-
-                if (hotKey == KeyCode.Null)
-                {
-                    continue;
-                }
-
-                bool matches = key == hotKey || key == (hotKey | KeyCode.AltMask);
-
-                if (!_host.IsMenuOpen)
-                {
-                    // If the menu is open, only match if Alt is not pressed.
-                    matches = key == hotKey;
-                }
-
-                if (matches)
-                {
-                    _menuItemToSelect = children [c];
-                    _currentChild = c;
-
-                    return base.OnInvokingKeyBindings (keyEvent);
-                }
-            }
-        }
-
         bool? handled = base.OnInvokingKeyBindings (keyEvent);
 
         if (handled is { } && (bool)handled)
@@ -661,34 +282,11 @@ internal sealed class Menu : View
             return true;
         }
 
+        // TODO: Determine if there's a cleaner way to handle this
         // This supports the case where the menu bar is a context menu
         return _host.OnInvokingKeyBindings (keyEvent);
     }
 
-    private bool FindShortcutInChildMenu (KeyCode key, MenuBarItem menuBarItem)
-    {
-        if (menuBarItem?.Children is null)
-        {
-            return false;
-        }
-
-        foreach (MenuItem menuItem in menuBarItem.Children)
-        {
-            if (key == menuItem?.Shortcut)
-            {
-                _menuBarItemToActivate = -1;
-                _menuItemToSelect = menuItem;
-
-                return true;
-            }
-
-            MenuBarItem subMenu = menuBarItem.SubMenu (menuItem);
-            FindShortcutInChildMenu (key, subMenu);
-        }
-
-        return false;
-    }
-
     private void Current_TerminalResized (object sender, SizeChangedEventArgs e)
     {
         if (_host.IsMenuOpen)
@@ -727,6 +325,7 @@ internal sealed class Menu : View
         View view = a.View ?? this;
 
         Point boundsPoint = view.ScreenToViewport (new (a.Position.X, a.Position.Y));
+
         var me = new MouseEvent
         {
             Position = boundsPoint,
@@ -786,12 +385,12 @@ internal sealed class Menu : View
 
             Driver.SetAttribute (
                                  item is null ? GetNormalColor () :
-                                 i == _currentChild ? GetFocusColor() : GetNormalColor ()
+                                 i == _currentChild ? GetFocusColor () : GetNormalColor ()
                                 );
 
             if (item is null && BorderStyle != LineStyle.None)
             {
-                var s = ViewportToScreen (new Point (-1, i));
+                Point s = ViewportToScreen (new Point (-1, i));
                 Driver.Move (s.X, s.Y);
                 Driver.AddRune (Glyphs.LeftTee);
             }
@@ -839,7 +438,7 @@ internal sealed class Menu : View
             {
                 if (BorderStyle != LineStyle.None && SuperView?.Frame.Right - Frame.X > Frame.Width)
                 {
-                    var s = ViewportToScreen (new Point (Frame.Width - 2, i));
+                    Point s = ViewportToScreen (new Point (Frame.Width - 2, i));
                     Driver.Move (s.X, s.Y);
                     Driver.AddRune (Glyphs.RightTee);
                 }
@@ -876,7 +475,8 @@ internal sealed class Menu : View
                 textToDraw = item.Title;
             }
 
-            var screen = ViewportToScreen (new Point(0  , i));
+            Point screen = ViewportToScreen (new Point (0, i));
+
             if (screen.X < Driver.Cols)
             {
                 Driver.Move (screen.X + 1, screen.Y);
@@ -895,7 +495,7 @@ internal sealed class Menu : View
 
                     // The -3 is left/right border + one space (not sure what for)
                     tf.Draw (
-                             ViewportToScreen (new Rectangle(1, i, Frame.Width - 3, 1)),
+                             ViewportToScreen (new Rectangle (1, i, Frame.Width - 3, 1)),
                              i == _currentChild ? GetFocusColor () : GetNormalColor (),
                              i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal,
                              SuperView?.ViewportToScreen (SuperView.Viewport) ?? Rectangle.Empty
@@ -934,7 +534,7 @@ internal sealed class Menu : View
 
         Driver.Clip = savedClip;
 
-       // PositionCursor ();
+        // PositionCursor ();
     }
 
     private void Current_DrawContentComplete (object sender, DrawEventArgs e)
@@ -953,13 +553,10 @@ internal sealed class Menu : View
             {
                 return _host?.PositionCursor ();
             }
-            else
-            {
-                Move (2, 1 + _currentChild);
 
-                return null; // Don't show the cursor
+            Move (2, 1 + _currentChild);
 
-            }
+            return null; // Don't show the cursor
         }
 
         return _host?.PositionCursor ();
@@ -1031,11 +628,11 @@ internal sealed class Menu : View
                 _currentChild = 0;
             }
 
-            if (this != _host.openCurrentMenu && _barItems.Children [_currentChild]?.IsFromSubMenu == true && _host._selectedSub > -1)
+            if (this != _host.OpenCurrentMenu && _barItems.Children [_currentChild]?.IsFromSubMenu == true && _host._selectedSub > -1)
             {
                 _host.PreviousMenu (true);
                 _host.SelectEnabledItem (_barItems.Children, _currentChild, out _currentChild);
-                _host.openCurrentMenu = this;
+                _host.OpenCurrentMenu = this;
             }
 
             MenuItem item = _barItems.Children [_currentChild];
@@ -1096,7 +693,7 @@ internal sealed class Menu : View
 
             if (_host.UseKeysUpDownAsKeysLeftRight && !_host.UseSubMenusSingleFrame)
             {
-                if ((_currentChild == -1 || this != _host.openCurrentMenu)
+                if ((_currentChild == -1 || this != _host.OpenCurrentMenu)
                     && _barItems.Children [_currentChild + 1].IsFromSubMenu
                     && _host._selectedSub > -1)
                 {
@@ -1106,7 +703,7 @@ internal sealed class Menu : View
                     if (_currentChild > 0)
                     {
                         _currentChild--;
-                        _host.openCurrentMenu = this;
+                        _host.OpenCurrentMenu = this;
                     }
 
                     break;
@@ -1176,7 +773,7 @@ internal sealed class Menu : View
         _host?.SetNeedsDisplay ();
     }
 
-    protected internal override bool OnMouseEvent  (MouseEvent me)
+    protected internal override bool OnMouseEvent (MouseEvent me)
     {
         if (!_host._handled && !_host.HandleGrabView (me, this))
         {
@@ -1285,8 +882,8 @@ internal sealed class Menu : View
             }
 
             if (pos == -1
-                && this != _host.openCurrentMenu
-                && subMenu.Children != _host.openCurrentMenu._barItems.Children
+                && this != _host.OpenCurrentMenu
+                && subMenu.Children != _host.OpenCurrentMenu._barItems.Children
                 && !_host.CloseMenu (false, true))
             {
                 return false;
@@ -1307,33 +904,6 @@ internal sealed class Menu : View
         return true;
     }
 
-    private int GetSubMenuIndex (MenuBarItem subMenu)
-    {
-        int pos = -1;
-
-        if (Subviews.Count == 0)
-        {
-            return pos;
-        }
-
-        Menu v = null;
-
-        foreach (View menu in Subviews)
-        {
-            if (((Menu)menu)._barItems == subMenu)
-            {
-                v = (Menu)menu;
-            }
-        }
-
-        if (v is { })
-        {
-            pos = Subviews.IndexOf (v);
-        }
-
-        return pos;
-    }
-
     protected override void Dispose (bool disposing)
     {
         if (Application.Current is { })

+ 127 - 450
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -1,191 +1,5 @@
 namespace Terminal.Gui;
 
-/// <summary>
-///     <see cref="MenuBarItem"/> is a menu item on  <see cref="MenuBar"/>. MenuBarItems do not support
-///     <see cref="MenuItem.Shortcut"/>.
-/// </summary>
-public class MenuBarItem : MenuItem
-{
-    /// <summary>Initializes a new <see cref="MenuBarItem"/> as a <see cref="MenuItem"/>.</summary>
-    /// <param name="title">Title for the menu item.</param>
-    /// <param name="help">Help text to display. Will be displayed next to the Title surrounded by parentheses.</param>
-    /// <param name="action">Action to invoke when the menu item is activated.</param>
-    /// <param name="canExecute">Function to determine if the action can currently be executed.</param>
-    /// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
-    public MenuBarItem (
-        string title,
-        string help,
-        Action action,
-        Func<bool> canExecute = null,
-        MenuItem parent = null
-    ) : base (title, help, action, canExecute, parent)
-    {
-        SetInitialProperties (title, null, null, true);
-    }
-
-    /// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
-    /// <param name="title">Title for the menu item.</param>
-    /// <param name="children">The items in the current menu.</param>
-    /// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
-    public MenuBarItem (string title, MenuItem [] children, MenuItem parent = null) { SetInitialProperties (title, children, parent); }
-
-    /// <summary>Initializes a new <see cref="MenuBarItem"/> with separate list of items.</summary>
-    /// <param name="title">Title for the menu item.</param>
-    /// <param name="children">The list of items in the current menu.</param>
-    /// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
-    public MenuBarItem (string title, List<MenuItem []> children, MenuItem parent = null) { SetInitialProperties (title, children, parent); }
-
-    /// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
-    /// <param name="children">The items in the current menu.</param>
-    public MenuBarItem (MenuItem [] children) : this ("", children) { }
-
-    /// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
-    public MenuBarItem () : this (new MenuItem [] { }) { }
-
-    /// <summary>
-    ///     Gets or sets an array of <see cref="MenuItem"/> objects that are the children of this
-    ///     <see cref="MenuBarItem"/>
-    /// </summary>
-    /// <value>The children.</value>
-    public MenuItem [] Children { get; set; }
-
-    internal bool IsTopLevel => Parent is null && (Children is null || Children.Length == 0) && Action != null;
-
-    /// <summary>Get the index of a child <see cref="MenuItem"/>.</summary>
-    /// <param name="children"></param>
-    /// <returns>Returns a greater than -1 if the <see cref="MenuItem"/> is a child.</returns>
-    public int GetChildrenIndex (MenuItem children)
-    {
-        var i = 0;
-
-        if (Children is { })
-        {
-            foreach (MenuItem child in Children)
-            {
-                if (child == children)
-                {
-                    return i;
-                }
-
-                i++;
-            }
-        }
-
-        return -1;
-    }
-
-    /// <summary>Check if a <see cref="MenuItem"/> is a submenu of this MenuBar.</summary>
-    /// <param name="menuItem"></param>
-    /// <returns>Returns <c>true</c> if it is a submenu. <c>false</c> otherwise.</returns>
-    public bool IsSubMenuOf (MenuItem menuItem)
-    {
-        foreach (MenuItem child in Children)
-        {
-            if (child == menuItem && child.Parent == menuItem.Parent)
-            {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /// <summary>Check if a <see cref="MenuItem"/> is a <see cref="MenuBarItem"/>.</summary>
-    /// <param name="menuItem"></param>
-    /// <returns>Returns a <see cref="MenuBarItem"/> or null otherwise.</returns>
-    public MenuBarItem SubMenu (MenuItem menuItem) { return menuItem as MenuBarItem; }
-
-    internal void AddKeyBindings (MenuBar menuBar)
-    {
-        if (Children is null)
-        {
-            return;
-        }
-
-        foreach (MenuItem menuItem in Children.Where (m => m is { }))
-        {
-            if (menuItem.HotKey != default (Rune))
-            {
-                menuBar.KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, Command.ToggleExpandCollapse);
-
-                menuBar.KeyBindings.Add (
-                                         (KeyCode)menuItem.HotKey.Value | KeyCode.AltMask,
-                                         KeyBindingScope.HotKey,
-                                         Command.ToggleExpandCollapse
-                                        );
-            }
-
-            if (menuItem.Shortcut != KeyCode.Null)
-            {
-                menuBar.KeyBindings.Add (menuItem.Shortcut, KeyBindingScope.HotKey, Command.Select);
-            }
-
-            SubMenu (menuItem)?.AddKeyBindings (menuBar);
-        }
-    }
-
-    private void SetInitialProperties (string title, object children, MenuItem parent = null, bool isTopLevel = false)
-    {
-        if (!isTopLevel && children is null)
-        {
-            throw new ArgumentNullException (
-                                             nameof (children),
-                                             "The parameter cannot be null. Use an empty array instead."
-                                            );
-        }
-
-        SetTitle (title ?? "");
-
-        if (parent is { })
-        {
-            Parent = parent;
-        }
-
-        if (children is List<MenuItem []> childrenList)
-        {
-            MenuItem [] newChildren = { };
-
-            foreach (MenuItem [] grandChild in childrenList)
-            {
-                foreach (MenuItem child in grandChild)
-                {
-                    SetParent (grandChild);
-                    Array.Resize (ref newChildren, newChildren.Length + 1);
-                    newChildren [newChildren.Length - 1] = child;
-                }
-            }
-
-            Children = newChildren;
-        }
-        else if (children is MenuItem [] items)
-        {
-            SetParent (items);
-            Children = items;
-        }
-        else
-        {
-            Children = null;
-        }
-    }
-
-    private void SetParent (MenuItem [] children)
-    {
-        foreach (MenuItem child in children)
-        {
-            if (child is { Parent: null })
-            {
-                child.Parent = this;
-            }
-        }
-    }
-
-    private void SetTitle (string title)
-    {
-        title ??= string.Empty;
-        Title = title;
-    }
-}
-
 /// <summary>
 ///     <para>Provides a menu bar that spans the top of a <see cref="Toplevel"/> View with drop-down and cascading menus.</para>
 ///     <para>
@@ -236,7 +50,6 @@ public class MenuBar : View
     internal bool _isMenuClosing;
     internal bool _isMenuOpening;
 
-    // BUGBUG: Hack
     internal Menu _openMenu;
     internal List<Menu> _openSubMenu;
     internal int _selected;
@@ -308,9 +121,8 @@ public class MenuBar : View
                         return true;
                     }
                    );
-
-        AddCommand (Command.ToggleExpandCollapse, () => SelectOrRun ());
-        AddCommand (Command.Select, () => Run (_menuItemToSelect?.Action));
+        AddCommand (Command.ToggleExpandCollapse, ctx => Select ((int)ctx.KeyBinding?.Context!));
+        AddCommand (Command.Select, ctx => Run ((ctx.KeyBinding?.Context as MenuItem)?.Action));
 
         // Default key bindings for this view
         KeyBindings.Add (Key.CursorLeft, Command.Left);
@@ -318,13 +130,15 @@ public class MenuBar : View
         KeyBindings.Add (Key.Esc, Command.Cancel);
         KeyBindings.Add (Key.CursorDown, Command.Accept);
         KeyBindings.Add (Key.Enter, Command.Accept);
-        KeyBindings.Add (Key, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
 
-        KeyBindings.Add (
-                         KeyCode.CtrlMask | KeyCode.Space,
-                         KeyBindingScope.HotKey,
-                         Command.ToggleExpandCollapse
-                        );
+        KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
+        KeyBindings.Add (Key, keyBinding);
+
+        // TODO: Why do we have two keybindings for opening the menu? Ctrl-Space and Key?
+        KeyBindings.Add (Key.Space.WithCtrl, keyBinding);
+
+        // TODO: Figure out how to make Alt work (on Windows)
+        //KeyBindings.Add (Key.WithAlt, keyBinding);
     }
 
     /// <summary><see langword="true"/> if the menu is open; otherwise <see langword="true"/>.</summary>
@@ -350,43 +164,27 @@ public class MenuBar : View
                 return;
             }
 
-            // TODO: Bindings (esp for hotkey) should be added across and then down. This currently does down then across. 
-            // TODO: As a result, _File._Save will have precedence over in "_File _Edit _ScrollbarView"
-            // TODO: Also: Hotkeys should not work for sub-menus if they are not visible!
-            foreach (MenuBarItem menuBarItem in Menus?.Where (m => m is { })!)
+            // TODO: Hotkeys should not work for sub-menus if they are not visible!
+            for (var i = 0; i < Menus.Length; i++)
             {
-                if (menuBarItem.HotKey != default (Rune))
+                MenuBarItem menuBarItem = Menus [i];
+
+                if (menuBarItem?.HotKey != default (Rune))
                 {
-                    KeyBindings.Add (
-                                     (KeyCode)menuBarItem.HotKey.Value,
-                                     Command.ToggleExpandCollapse
-                                    );
-
-                    KeyBindings.Add (
-                                     (KeyCode)menuBarItem.HotKey.Value | KeyCode.AltMask,
-                                     KeyBindingScope.HotKey,
-                                     Command.ToggleExpandCollapse
-                                    );
+                    KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, i);
+                    KeyBindings.Add ((KeyCode)menuBarItem.HotKey.Value, keyBinding);
+                    KeyBindings.Add ((KeyCode)menuBarItem.HotKey.Value | KeyCode.AltMask, keyBinding);
                 }
 
-                if (menuBarItem.Shortcut != KeyCode.Null)
+                if (menuBarItem?.Shortcut != KeyCode.Null)
                 {
                     // Technically this will never run because MenuBarItems don't have shortcuts
-                    KeyBindings.Add (menuBarItem.Shortcut, KeyBindingScope.HotKey, Command.Select);
+                    KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, i);
+                    KeyBindings.Add (menuBarItem.Shortcut, keyBinding);
                 }
 
-                menuBarItem.AddKeyBindings (this);
+                menuBarItem?.AddShortcutKeyBindings (this);
             }
-#if SUPPORT_ALT_TO_ACTIVATE_MENU
-            // Enable the Alt key as a menu activator
-            Initialized += (s, e) =>
-                           {
-                               if (SuperView is { })
-                               {
-                                   SuperView.KeyUp += SuperView_KeyUp;
-                               }
-                           };
-#endif
         }
     }
 
@@ -399,7 +197,7 @@ public class MenuBar : View
     /// <summary>
     ///     Gets or sets if the sub-menus must be displayed in a single or multiple frames.
     ///     <para>
-    ///         By default any sub-sub-menus (sub-menus of the main <see cref="MenuItem"/>s) are displayed in a cascading
+    ///         By default, any sub-sub-menus (sub-menus of the main <see cref="MenuItem"/>s) are displayed in a cascading
     ///         manner, where each sub-sub-menu pops out of the sub-menu frame (either to the right or left, depending on where
     ///         the sub-menu is relative to the edge of the screen). By setting <see cref="UseSubMenusSingleFrame"/> to
     ///         <see langword="true"/>, this behavior can be changed such that all sub-sub-menus are drawn within a single
@@ -436,7 +234,7 @@ public class MenuBar : View
         }
     }
 
-    internal Menu openCurrentMenu
+    internal Menu OpenCurrentMenu
     {
         get => _ocm;
         set
@@ -486,7 +284,7 @@ public class MenuBar : View
             if (i == _selected && IsMenuOpen)
             {
                 hotColor = i == _selected ? ColorScheme.HotFocus : GetHotNormalColor ();
-                normalColor = i == _selected ? GetFocusColor() : GetNormalColor ();
+                normalColor = i == _selected ? GetFocusColor () : GetNormalColor ();
             }
             else
             {
@@ -533,17 +331,17 @@ public class MenuBar : View
         MenuItem mi = null;
         MenuBarItem parent;
 
-        if (openCurrentMenu.BarItems.Children != null
-            && openCurrentMenu.BarItems!.Children.Length > 0
-            && openCurrentMenu?._currentChild > -1)
+        if (OpenCurrentMenu.BarItems.Children != null
+            && OpenCurrentMenu.BarItems!.Children.Length > 0
+            && OpenCurrentMenu?._currentChild > -1)
         {
-            parent = openCurrentMenu.BarItems;
-            mi = parent.Children [openCurrentMenu._currentChild];
+            parent = OpenCurrentMenu.BarItems;
+            mi = parent.Children [OpenCurrentMenu._currentChild];
         }
-        else if (openCurrentMenu!.BarItems.IsTopLevel)
+        else if (OpenCurrentMenu!.BarItems.IsTopLevel)
         {
             parent = null;
-            mi = openCurrentMenu.BarItems;
+            mi = OpenCurrentMenu.BarItems;
         }
         else
         {
@@ -587,16 +385,16 @@ public class MenuBar : View
         OpenMenu (_selected);
 
         if (!SelectEnabledItem (
-                                openCurrentMenu.BarItems.Children,
-                                openCurrentMenu._currentChild,
-                                out openCurrentMenu._currentChild
+                                OpenCurrentMenu.BarItems.Children,
+                                OpenCurrentMenu._currentChild,
+                                out OpenCurrentMenu._currentChild
                                )
             && !CloseMenu (false))
         {
             return;
         }
 
-        if (!openCurrentMenu.CheckSubMenu ())
+        if (!OpenCurrentMenu.CheckSubMenu ())
         {
             return;
         }
@@ -631,6 +429,7 @@ public class MenuBar : View
                           : 0)
                    + _rightPadding;
         }
+
         return null; // Don't show the cursor
     }
 
@@ -660,7 +459,6 @@ public class MenuBar : View
         }
 
         _openedByAltKey = false;
-        _openedByHotKey = false;
         IsMenuOpen = false;
         _selected = -1;
         CanFocus = _initialCanFocus;
@@ -697,20 +495,19 @@ public class MenuBar : View
             Application.UngrabMouse ();
         }
 
-        if (openCurrentMenu is { })
+        if (OpenCurrentMenu is { })
         {
-            openCurrentMenu = null;
+            OpenCurrentMenu = null;
         }
 
         IsMenuOpen = false;
         _openedByAltKey = false;
-        _openedByHotKey = false;
         OnMenuAllClosed ();
     }
 
     internal bool CloseMenu (bool reopen = false, bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
     {
-        MenuBarItem mbi = isSubMenu ? openCurrentMenu.BarItems : _openMenu?.BarItems;
+        MenuBarItem mbi = isSubMenu ? OpenCurrentMenu.BarItems : _openMenu?.BarItems;
 
         if (UseSubMenusSingleFrame && mbi is { } && !ignoreUseSubMenusSingleFrame && mbi.Parent is { })
         {
@@ -725,7 +522,7 @@ public class MenuBar : View
         {
             _isMenuClosing = false;
 
-            if (args.CurrentMenu.Parent is { })
+            if (args.CurrentMenu.Parent is { } && _openMenu is { })
             {
                 _openMenu._currentChild =
                     ((MenuBarItem)args.CurrentMenu.Parent).Children.IndexOf (args.CurrentMenu);
@@ -744,7 +541,7 @@ public class MenuBar : View
 
                 SetNeedsDisplay ();
 
-                if (_previousFocused is { } && _previousFocused is Menu && _openMenu is { } && _previousFocused.ToString () != openCurrentMenu.ToString ())
+                if (_previousFocused is Menu && _openMenu is { } && _previousFocused.ToString () != OpenCurrentMenu.ToString ())
                 {
                     _previousFocused.SetFocus ();
                 }
@@ -752,7 +549,7 @@ public class MenuBar : View
                 _openMenu?.Dispose ();
                 _openMenu = null;
 
-                if (_lastFocused is Menu || _lastFocused is MenuBar)
+                if (_lastFocused is Menu or MenuBar)
                 {
                     _lastFocused = null;
                 }
@@ -760,7 +557,7 @@ public class MenuBar : View
                 LastFocused = _lastFocused;
                 _lastFocused = null;
 
-                if (LastFocused is { } && LastFocused.CanFocus)
+                if (LastFocused is { CanFocus: true })
                 {
                     if (!reopen)
                     {
@@ -772,11 +569,11 @@ public class MenuBar : View
                         _openSubMenu = null;
                     }
 
-                    if (openCurrentMenu is { })
+                    if (OpenCurrentMenu is { })
                     {
-                        Application.Current.Remove (openCurrentMenu);
-                        openCurrentMenu.Dispose ();
-                        openCurrentMenu = null;
+                        Application.Current?.Remove (OpenCurrentMenu);
+                        OpenCurrentMenu.Dispose ();
+                        OpenCurrentMenu = null;
                     }
 
                     LastFocused.SetFocus ();
@@ -788,7 +585,6 @@ public class MenuBar : View
                 else
                 {
                     SetFocus ();
-                    //PositionCursor ();
                 }
 
                 IsMenuOpen = false;
@@ -799,7 +595,7 @@ public class MenuBar : View
                 _selectedSub = -1;
                 SetNeedsDisplay ();
                 RemoveAllOpensSubMenus ();
-                openCurrentMenu._previousSubFocused.SetFocus ();
+                OpenCurrentMenu._previousSubFocused.SetFocus ();
                 _openSubMenu = null;
                 IsMenuOpen = true;
 
@@ -823,11 +619,13 @@ public class MenuBar : View
 
         Rectangle superViewFrame = SuperView is null ? Driver.Screen : SuperView.Frame;
         View sv = SuperView is null ? Application.Current : SuperView;
+
         if (sv is null)
         {
             // Support Unit Tests
             return Point.Empty;
         }
+
         Point viewportOffset = sv?.GetViewportOffsetFromFrame () ?? Point.Empty;
 
         return new (
@@ -836,20 +634,6 @@ public class MenuBar : View
                    );
     }
 
-    /// <summary>
-    ///     Gets the <see cref="Application.Current"/> location offset relative to the <see cref="ConsoleDriver"/>
-    ///     location.
-    /// </summary>
-    /// <returns>The location offset.</returns>
-    internal Point GetScreenOffsetFromCurrent ()
-    {
-        Rectangle screen = Driver.Screen;
-        Rectangle currentFrame = Application.Current.Frame;
-        Point viewportOffset = Application.Top.GetViewportOffsetFromFrame ();
-
-        return new (screen.X - currentFrame.X - viewportOffset.X, screen.Y - currentFrame.Y - viewportOffset.Y);
-    }
-
     internal void NextMenu (bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
     {
         switch (isSubMenu)
@@ -876,9 +660,9 @@ public class MenuBar : View
                 OpenMenu (_selected);
 
                 SelectEnabledItem (
-                                   openCurrentMenu.BarItems.Children,
-                                   openCurrentMenu._currentChild,
-                                   out openCurrentMenu._currentChild
+                                   OpenCurrentMenu.BarItems.Children,
+                                   OpenCurrentMenu._currentChild,
+                                   out OpenCurrentMenu._currentChild
                                   );
 
                 break;
@@ -892,9 +676,9 @@ public class MenuBar : View
                 }
                 else
                 {
-                    MenuBarItem subMenu = openCurrentMenu._currentChild > -1 && openCurrentMenu.BarItems.Children.Length > 0
-                                              ? openCurrentMenu.BarItems.SubMenu (
-                                                                                  openCurrentMenu.BarItems.Children [openCurrentMenu._currentChild]
+                    MenuBarItem subMenu = OpenCurrentMenu._currentChild > -1 && OpenCurrentMenu.BarItems.Children.Length > 0
+                                              ? OpenCurrentMenu.BarItems.SubMenu (
+                                                                                  OpenCurrentMenu.BarItems.Children [OpenCurrentMenu._currentChild]
                                                                                  )
                                               : null;
 
@@ -908,13 +692,13 @@ public class MenuBar : View
                         NextMenu (false, ignoreUseSubMenusSingleFrame);
                     }
                     else if (subMenu != null
-                             || (openCurrentMenu._currentChild > -1
-                                 && !openCurrentMenu.BarItems
-                                                    .Children [openCurrentMenu._currentChild]
+                             || (OpenCurrentMenu._currentChild > -1
+                                 && !OpenCurrentMenu.BarItems
+                                                    .Children [OpenCurrentMenu._currentChild]
                                                     .IsFromSubMenu))
                     {
                         _selectedSub++;
-                        openCurrentMenu.CheckSubMenu ();
+                        OpenCurrentMenu.CheckSubMenu ();
                     }
                     else
                     {
@@ -930,7 +714,7 @@ public class MenuBar : View
 
                     if (UseKeysUpDownAsKeysLeftRight)
                     {
-                        openCurrentMenu.CheckSubMenu ();
+                        OpenCurrentMenu.CheckSubMenu ();
                     }
                 }
 
@@ -996,7 +780,7 @@ public class MenuBar : View
                     locationOffset.Y += SuperView.Border.Thickness.Top;
                 }
 
-                _openMenu = new()
+                _openMenu = new ()
                 {
                     Host = this,
                     X = Frame.X + pos + locationOffset.X,
@@ -1004,8 +788,8 @@ public class MenuBar : View
                     BarItems = Menus [index],
                     Parent = null
                 };
-                openCurrentMenu = _openMenu;
-                openCurrentMenu._previousSubFocused = _openMenu;
+                OpenCurrentMenu = _openMenu;
+                OpenCurrentMenu._previousSubFocused = _openMenu;
 
                 if (Application.Current is { })
                 {
@@ -1013,9 +797,10 @@ public class MenuBar : View
                 }
                 else
                 {
-                    _openMenu.BeginInit();
-                    _openMenu.EndInit();
+                    _openMenu.BeginInit ();
+                    _openMenu.EndInit ();
                 }
+
                 _openMenu.SetFocus ();
 
                 break;
@@ -1038,7 +823,7 @@ public class MenuBar : View
                     {
                         locationOffset = GetLocationOffset ();
 
-                        openCurrentMenu = new()
+                        OpenCurrentMenu = new ()
                         {
                             Host = this,
                             X = last.Frame.Left + last.Frame.Width + locationOffset.X,
@@ -1053,7 +838,7 @@ public class MenuBar : View
 
                         // 2 is for the parent and the separator
                         MenuItem [] mbi = new MenuItem [2 + subMenu.Children.Length];
-                        mbi [0] = new() { Title = subMenu.Title, Parent = subMenu };
+                        mbi [0] = new () { Title = subMenu.Title, Parent = subMenu };
                         mbi [1] = null;
 
                         for (var j = 0; j < subMenu.Children.Length; j++)
@@ -1063,23 +848,23 @@ public class MenuBar : View
 
                         var newSubMenu = new MenuBarItem (mbi) { Parent = subMenu };
 
-                        openCurrentMenu = new()
+                        OpenCurrentMenu = new ()
                         {
                             Host = this, X = first.Frame.Left, Y = first.Frame.Top, BarItems = newSubMenu
                         };
                         last.Visible = false;
-                        Application.GrabMouse (openCurrentMenu);
+                        Application.GrabMouse (OpenCurrentMenu);
                     }
 
-                    openCurrentMenu._previousSubFocused = last._previousSubFocused;
-                    _openSubMenu.Add (openCurrentMenu);
-                    Application.Current?.Add (openCurrentMenu);
+                    OpenCurrentMenu._previousSubFocused = last._previousSubFocused;
+                    _openSubMenu.Add (OpenCurrentMenu);
+                    Application.Current?.Add (OpenCurrentMenu);
 
-                    if (!openCurrentMenu.IsInitialized)
+                    if (!OpenCurrentMenu.IsInitialized)
                     {
                         // Supports unit tests
-                        openCurrentMenu.BeginInit ();
-                        openCurrentMenu.EndInit ();
+                        OpenCurrentMenu.BeginInit ();
+                        OpenCurrentMenu.EndInit ();
                     }
                 }
 
@@ -1087,12 +872,12 @@ public class MenuBar : View
 
                 if (_selectedSub > -1
                     && SelectEnabledItem (
-                                          openCurrentMenu.BarItems.Children,
-                                          openCurrentMenu._currentChild,
-                                          out openCurrentMenu._currentChild
+                                          OpenCurrentMenu.BarItems.Children,
+                                          OpenCurrentMenu._currentChild,
+                                          out OpenCurrentMenu._currentChild
                                          ))
                 {
-                    openCurrentMenu.SetFocus ();
+                    OpenCurrentMenu.SetFocus ();
                 }
 
                 break;
@@ -1124,13 +909,13 @@ public class MenuBar : View
                 OpenMenu (_selected);
 
                 if (!SelectEnabledItem (
-                                        openCurrentMenu.BarItems.Children,
-                                        openCurrentMenu._currentChild,
-                                        out openCurrentMenu._currentChild,
+                                        OpenCurrentMenu.BarItems.Children,
+                                        OpenCurrentMenu._currentChild,
+                                        out OpenCurrentMenu._currentChild,
                                         false
                                        ))
                 {
-                    openCurrentMenu._currentChild = 0;
+                    OpenCurrentMenu._currentChild = 0;
                 }
 
                 break;
@@ -1182,42 +967,35 @@ public class MenuBar : View
     }
 
     internal bool SelectEnabledItem (
-        IEnumerable<MenuItem> chldren,
+        IEnumerable<MenuItem> children,
         int current,
         out int newCurrent,
         bool forward = true
     )
     {
-        if (chldren is null)
+        if (children is null)
         {
             newCurrent = -1;
 
             return true;
         }
 
-        IEnumerable<MenuItem> childrens;
-
-        if (forward)
-        {
-            childrens = chldren;
-        }
-        else
-        {
-            childrens = chldren.Reverse ();
-        }
+        IEnumerable<MenuItem> childMenuItems = forward ? children : children.Reverse ();
 
         int count;
 
+        IEnumerable<MenuItem> menuItems = childMenuItems as MenuItem [] ?? childMenuItems.ToArray ();
+
         if (forward)
         {
             count = -1;
         }
         else
         {
-            count = childrens.Count ();
+            count = menuItems.Count ();
         }
 
-        foreach (MenuItem child in childrens)
+        foreach (MenuItem child in menuItems)
         {
             if (forward)
             {
@@ -1336,7 +1114,7 @@ public class MenuBar : View
 
         if (mi.IsTopLevel)
         {
-            var screen = ViewportToScreen (new Point (0 , i));
+            Point screen = ViewportToScreen (new Point (0, i));
             var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = mi };
             menu.Run (mi.Action);
             menu.Dispose ();
@@ -1348,16 +1126,16 @@ public class MenuBar : View
             OpenMenu (i);
 
             if (!SelectEnabledItem (
-                                    openCurrentMenu.BarItems.Children,
-                                    openCurrentMenu._currentChild,
-                                    out openCurrentMenu._currentChild
+                                    OpenCurrentMenu.BarItems.Children,
+                                    OpenCurrentMenu._currentChild,
+                                    out OpenCurrentMenu._currentChild
                                    )
                 && !CloseMenu (false))
             {
                 return;
             }
 
-            if (!openCurrentMenu.CheckSubMenu ())
+            if (!OpenCurrentMenu.CheckSubMenu ())
             {
                 return;
             }
@@ -1395,8 +1173,8 @@ public class MenuBar : View
                 menu.Visible = true;
             }
 
-            openCurrentMenu = menu;
-            openCurrentMenu.SetFocus ();
+            OpenCurrentMenu = menu;
+            OpenCurrentMenu.SetFocus ();
 
             if (_openSubMenu is { })
             {
@@ -1417,7 +1195,7 @@ public class MenuBar : View
 
         if (_openSubMenu.Count > 0)
         {
-            openCurrentMenu = _openSubMenu.Last ();
+            OpenCurrentMenu = _openSubMenu.Last ();
         }
 
         _isMenuClosing = false;
@@ -1449,7 +1227,8 @@ public class MenuBar : View
             }
 
             KeyBindings.Remove (_key);
-            KeyBindings.Add (value, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
+            KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
+            KeyBindings.Add (value, keyBinding);
             _key = value;
         }
     }
@@ -1490,168 +1269,66 @@ public class MenuBar : View
     /// <summary>The specifier character for the hot keys.</summary>
     public new static Rune HotKeySpecifier => (Rune)'_';
 
-    // Set in OnInvokingKeyBindings. -1 means no menu item is selected for activation.
-    private int _menuBarItemToActivate;
-
-    // Set in OnInvokingKeyBindings. null means no sub-menu is selected for activation.
-    private MenuItem _menuItemToSelect;
+    // TODO: This doesn't actually work. Figure out why.
     private bool _openedByAltKey;
-    private bool _openedByHotKey;
 
     /// <summary>
     ///     Called when a key bound to Command.Select is pressed. Either activates the menu item or runs it, depending on
     ///     whether it has a sub-menu. If the menu is open, it will close the menu bar.
     /// </summary>
+    /// <param name="index">The index of the menu bar item to select. -1 if the selection was via <see cref="Key"/>.</param>
     /// <returns></returns>
-    private bool SelectOrRun ()
+    private bool Select (int index)
     {
         if (!IsInitialized || !Visible)
         {
             return true;
         }
 
-        _openedByHotKey = true;
-
-        if (_menuBarItemToActivate != -1)
+        // If the menubar is open and the menu that's open is 'index' then close it. Otherwise activate it.
+        if (IsMenuOpen)
         {
-            Activate (_menuBarItemToActivate);
-        }
-        else if (_menuItemToSelect is { })
-        {
-            Run (_menuItemToSelect.Action);
-        }
-        else
-        {
-            if (IsMenuOpen && _openMenu is { })
+            if (index == -1)
             {
                 CloseAllMenus ();
-            }
-            else
-            {
-                OpenMenu ();
-            }
-        }
-
-        return true;
-    }
-
-    /// <inheritdoc/>
-    public override bool? OnInvokingKeyBindings (Key key)
-    {
-        // This is a bit of a hack. We want to handle the key bindings for menu bar but
-        // InvokeKeyBindings doesn't pass any context so we can't tell which item it is for.
-        // So before we call the base class we set SelectedItem appropriately.
-        // TODO: Figure out if there's a way to have KeyBindings pass context instead. Maybe a KeyBindingContext property?
-
-        if (KeyBindings.TryGet (key, out _))
-        {
-            _menuBarItemToActivate = -1;
-            _menuItemToSelect = null;
-
-            // Search for shortcuts first. If there's a shortcut, we don't want to activate the menu item.
-            for (var i = 0; i < Menus.Length; i++)
-            {
-                // Recurse through the menu to find one with the shortcut.
-                if (FindShortcutInChildMenu (key.KeyCode, Menus [i], out _menuItemToSelect))
-                {
-                    _menuBarItemToActivate = i;
 
-                    //keyEvent.Scope = KeyBindingScope.HotKey;
-
-                    return base.OnInvokingKeyBindings (key);
-                }
-
-                // Now see if any of the menu bar items have a hot key that matches
-                // Technically this is not possible because menu bar items don't have 
-                // shortcuts or Actions. But it's here for completeness. 
-                KeyCode? shortcut = Menus [i]?.Shortcut;
-
-                if (key == shortcut)
-                {
-                    throw new InvalidOperationException ("Menu bar items cannot have shortcuts");
-                }
+                return true;
             }
 
-            // Search for hot keys next.
+            // Find the index of the open submenu and close the menu if it matches
             for (var i = 0; i < Menus.Length; i++)
             {
-                if (IsMenuOpen)
+                MenuBarItem open = Menus [i];
+
+                if (open is null)
                 {
-                    // We don't need to do anything because `Menu` will handle the key binding.
-                    //break;
+                    continue;
                 }
 
-                // No submenu item matched (or the menu is closed)
-
-                // Check if one of the menu bar item has a hot key that matches
-                var hotKey = new Key ((char)Menus [i]?.HotKey.Value);
-
-                if (hotKey != Key.Empty)
+                if (open == OpenCurrentMenu.BarItems && i == index)
                 {
-                    bool matches = key == hotKey || key == hotKey.WithAlt || key == hotKey.NoShift.WithAlt;
-
-                    if (IsMenuOpen)
-                    {
-                        // If the menu is open, only match if Alt is not pressed.
-                        matches = key == hotKey;
-                    }
-
-                    if (matches)
-                    {
-                        _menuBarItemToActivate = i;
-
-                        //keyEvent.Scope = KeyBindingScope.HotKey;
-
-                        break;
-                    }
+                    CloseAllMenus ();
+                    return true;
                 }
             }
         }
 
-        return base.OnInvokingKeyBindings (key);
-    }
-
-    // TODO: Update to use Key instead of KeyCode
-    // Recurse the child menus looking for a shortcut that matches the key
-    private bool FindShortcutInChildMenu (KeyCode key, MenuBarItem menuBarItem, out MenuItem menuItemToSelect)
-    {
-        menuItemToSelect = null;
-
-        if (key == KeyCode.Null || menuBarItem?.Children is null)
+        if (index == -1)
         {
-            return false;
+            OpenMenu ();
         }
-
-        for (var c = 0; c < menuBarItem.Children.Length; c++)
+        else
         {
-            MenuItem menuItem = menuBarItem.Children [c];
-
-            if (key == menuItem?.Shortcut)
-            {
-                menuItemToSelect = menuItem;
-
-                return true;
-            }
-
-            MenuBarItem subMenu = menuBarItem.SubMenu (menuItem);
-
-            if (subMenu is { })
-            {
-                if (FindShortcutInChildMenu (key, subMenu, out menuItemToSelect))
-                {
-                    return true;
-                }
-            }
+            Activate (index);
         }
 
-        return false;
+        return true;
     }
 
     #endregion Keyboard handling
 
     #region Mouse Handling
 
-
     /// <inheritdoc/>
     public override bool OnLeave (View view)
     {
@@ -1699,7 +1376,7 @@ public class MenuBar : View
                     {
                         if (Menus [i].IsTopLevel)
                         {
-                            var screen = ViewportToScreen (new Point(0 , i));
+                            Point screen = ViewportToScreen (new Point (0, i));
                             var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = Menus [i] };
                             menu.Run (Menus [i].Action);
                             menu.Dispose ();
@@ -1741,9 +1418,9 @@ public class MenuBar : View
                     {
                         if (!UseSubMenusSingleFrame
                             || (UseSubMenusSingleFrame
-                                && openCurrentMenu != null
-                                && openCurrentMenu.BarItems.Parent != null
-                                && openCurrentMenu.BarItems.Parent.Parent != Menus [i]))
+                                && OpenCurrentMenu != null
+                                && OpenCurrentMenu.BarItems.Parent != null
+                                && OpenCurrentMenu.BarItems.Parent.Parent != Menus [i]))
                         {
                             Activate (i);
                         }

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

@@ -0,0 +1,178 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     <see cref="MenuBarItem"/> is a menu item on  <see cref="MenuBar"/>. MenuBarItems do not support
+///     <see cref="MenuItem.Shortcut"/>.
+/// </summary>
+public class MenuBarItem : MenuItem
+{
+    /// <summary>Initializes a new <see cref="MenuBarItem"/> as a <see cref="MenuItem"/>.</summary>
+    /// <param name="title">Title for the menu item.</param>
+    /// <param name="help">Help text to display. Will be displayed next to the Title surrounded by parentheses.</param>
+    /// <param name="action">Action to invoke when the menu item is activated.</param>
+    /// <param name="canExecute">Function to determine if the action can currently be executed.</param>
+    /// <param name="parent">The parent <see cref="MenuItem"/> of this if any.</param>
+    public MenuBarItem (
+        string title,
+        string help,
+        Action action,
+        Func<bool> canExecute = null,
+        MenuItem parent = null
+    ) : base (title, help, action, canExecute, parent)
+    {
+        SetInitialProperties (title, null, null, true);
+    }
+
+    /// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
+    /// <param name="title">Title for the menu item.</param>
+    /// <param name="children">The items in the current menu.</param>
+    /// <param name="parent">The parent <see cref="MenuItem"/> of this if any.</param>
+    public MenuBarItem (string title, MenuItem [] children, MenuItem parent = null) { SetInitialProperties (title, children, parent); }
+
+    /// <summary>Initializes a new <see cref="MenuBarItem"/> with separate list of items.</summary>
+    /// <param name="title">Title for the menu item.</param>
+    /// <param name="children">The list of items in the current menu.</param>
+    /// <param name="parent">The parent <see cref="MenuItem"/> of this if any.</param>
+    public MenuBarItem (string title, List<MenuItem []> children, MenuItem parent = null) { SetInitialProperties (title, children, parent); }
+
+    /// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
+    /// <param name="children">The items in the current menu.</param>
+    public MenuBarItem (MenuItem [] children) : this ("", children) { }
+
+    /// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
+    public MenuBarItem () : this ([]) { }
+
+    /// <summary>
+    ///     Gets or sets an array of <see cref="MenuItem"/> objects that are the children of this
+    ///     <see cref="MenuBarItem"/>
+    /// </summary>
+    /// <value>The children.</value>
+    public MenuItem [] Children { get; set; }
+
+    internal bool IsTopLevel => Parent is null && (Children is null || Children.Length == 0) && Action != null;
+
+    /// <summary>Get the index of a child <see cref="MenuItem"/>.</summary>
+    /// <param name="children"></param>
+    /// <returns>Returns a greater than -1 if the <see cref="MenuItem"/> is a child.</returns>
+    public int GetChildrenIndex (MenuItem children)
+    {
+        var i = 0;
+
+        if (Children is null)
+        {
+            return -1;
+        }
+
+        foreach (MenuItem child in Children)
+        {
+            if (child == children)
+            {
+                return i;
+            }
+
+            i++;
+        }
+
+        return -1;
+    }
+
+    /// <summary>Check if a <see cref="MenuItem"/> is a submenu of this MenuBar.</summary>
+    /// <param name="menuItem"></param>
+    /// <returns>Returns <c>true</c> if it is a submenu. <c>false</c> otherwise.</returns>
+    public bool IsSubMenuOf (MenuItem menuItem)
+    {
+        return Children.Any (child => child == menuItem && child.Parent == menuItem.Parent);
+    }
+
+    /// <summary>Check if a <see cref="MenuItem"/> is a <see cref="MenuBarItem"/>.</summary>
+    /// <param name="menuItem"></param>
+    /// <returns>Returns a <see cref="MenuBarItem"/> or null otherwise.</returns>
+    public MenuBarItem SubMenu (MenuItem menuItem) { return menuItem as MenuBarItem; }
+
+    internal void AddShortcutKeyBindings (MenuBar menuBar)
+    {
+        if (Children is null)
+        {
+            return;
+        }
+
+        foreach (MenuItem menuItem in Children.Where (m => m is { }))
+        {
+            // For MenuBar only add shortcuts for submenus
+
+            if (menuItem.Shortcut != KeyCode.Null)
+            {
+                KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem);
+                menuBar.KeyBindings.Add (menuItem.Shortcut, keyBinding);
+            }
+
+            SubMenu (menuItem)?.AddShortcutKeyBindings (menuBar);
+        }
+    }
+
+    private void SetInitialProperties (string title, object children, MenuItem parent = null, bool isTopLevel = false)
+    {
+        if (!isTopLevel && children is null)
+        {
+            throw new ArgumentNullException (
+                                             nameof (children),
+                                             @"The parameter cannot be null. Use an empty array instead."
+                                            );
+        }
+
+        SetTitle (title ?? "");
+
+        if (parent is { })
+        {
+            Parent = parent;
+        }
+
+        switch (children)
+        {
+            case List<MenuItem []> childrenList:
+            {
+                MenuItem [] newChildren = [];
+
+                foreach (MenuItem [] grandChild in childrenList)
+                {
+                    foreach (MenuItem child in grandChild)
+                    {
+                        SetParent (grandChild);
+                        Array.Resize (ref newChildren, newChildren.Length + 1);
+                        newChildren [^1] = child;
+                    }
+                }
+
+                Children = newChildren;
+
+                break;
+            }
+            case MenuItem [] items:
+                SetParent (items);
+                Children = items;
+
+                break;
+            default:
+                Children = null;
+
+                break;
+        }
+    }
+
+    private void SetParent (MenuItem [] children)
+    {
+        foreach (MenuItem child in children)
+        {
+            if (child is { Parent: null })
+            {
+                child.Parent = this;
+            }
+        }
+    }
+
+    private void SetTitle (string title)
+    {
+        title ??= string.Empty;
+        Title = title;
+    }
+}

+ 31 - 0
Terminal.Gui/Views/Menu/MenuClosingEventArgs.cs

@@ -0,0 +1,31 @@
+namespace Terminal.Gui;
+
+/// <summary>An <see cref="EventArgs"/> which allows passing a cancelable menu closing event.</summary>
+public class MenuClosingEventArgs : EventArgs
+{
+    /// <summary>Initializes a new instance of <see cref="MenuClosingEventArgs"/>.</summary>
+    /// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
+    /// <param name="reopen">Whether the current menu will reopen.</param>
+    /// <param name="isSubMenu">Indicates whether it is a sub-menu.</param>
+    public MenuClosingEventArgs (MenuBarItem currentMenu, bool reopen, bool isSubMenu)
+    {
+        CurrentMenu = currentMenu;
+        Reopen = reopen;
+        IsSubMenu = isSubMenu;
+    }
+
+    /// <summary>
+    ///     Flag that allows the cancellation of the event. If set to <see langword="true"/> in the event handler, the
+    ///     event will be canceled.
+    /// </summary>
+    public bool Cancel { get; set; }
+
+    /// <summary>The current <see cref="MenuBarItem"/> parent.</summary>
+    public MenuBarItem CurrentMenu { get; }
+
+    /// <summary>Indicates whether the current menu is a sub-menu.</summary>
+    public bool IsSubMenu { get; }
+
+    /// <summary>Indicates whether the current menu will reopen.</summary>
+    public bool Reopen { get; }
+}

+ 0 - 73
Terminal.Gui/Views/Menu/MenuEventArgs.cs

@@ -1,73 +0,0 @@
-namespace Terminal.Gui;
-
-/// <summary>
-///     An <see cref="EventArgs"/> which allows passing a cancelable menu opening event or replacing with a new
-///     <see cref="MenuBarItem"/>.
-/// </summary>
-public class MenuOpeningEventArgs : EventArgs
-{
-    /// <summary>Initializes a new instance of <see cref="MenuOpeningEventArgs"/>.</summary>
-    /// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
-    public MenuOpeningEventArgs (MenuBarItem currentMenu) { CurrentMenu = currentMenu; }
-
-    /// <summary>
-    ///     Flag that allows the cancellation of the event. If set to <see langword="true"/> in the event handler, the
-    ///     event will be canceled.
-    /// </summary>
-    public bool Cancel { get; set; }
-
-    /// <summary>The current <see cref="MenuBarItem"/> parent.</summary>
-    public MenuBarItem CurrentMenu { get; }
-
-    /// <summary>The new <see cref="MenuBarItem"/> to be replaced.</summary>
-    public MenuBarItem NewMenuBarItem { get; set; }
-}
-
-/// <summary>Defines arguments for the <see cref="MenuBar.MenuOpened"/> event</summary>
-public class MenuOpenedEventArgs : EventArgs
-{
-    /// <summary>Creates a new instance of the <see cref="MenuOpenedEventArgs"/> class</summary>
-    /// <param name="parent"></param>
-    /// <param name="menuItem"></param>
-    public MenuOpenedEventArgs (MenuBarItem parent, MenuItem menuItem)
-    {
-        Parent = parent;
-        MenuItem = menuItem;
-    }
-
-    /// <summary>Gets the <see cref="MenuItem"/> being opened.</summary>
-    public MenuItem MenuItem { get; }
-
-    /// <summary>The parent of <see cref="MenuItem"/>. Will be null if menu opening is the root.</summary>
-    public MenuBarItem Parent { get; }
-}
-
-/// <summary>An <see cref="EventArgs"/> which allows passing a cancelable menu closing event.</summary>
-public class MenuClosingEventArgs : EventArgs
-{
-    /// <summary>Initializes a new instance of <see cref="MenuClosingEventArgs"/>.</summary>
-    /// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
-    /// <param name="reopen">Whether the current menu will reopen.</param>
-    /// <param name="isSubMenu">Indicates whether it is a sub-menu.</param>
-    public MenuClosingEventArgs (MenuBarItem currentMenu, bool reopen, bool isSubMenu)
-    {
-        CurrentMenu = currentMenu;
-        Reopen = reopen;
-        IsSubMenu = isSubMenu;
-    }
-
-    /// <summary>
-    ///     Flag that allows the cancellation of the event. If set to <see langword="true"/> in the event handler, the
-    ///     event will be canceled.
-    /// </summary>
-    public bool Cancel { get; set; }
-
-    /// <summary>The current <see cref="MenuBarItem"/> parent.</summary>
-    public MenuBarItem CurrentMenu { get; }
-
-    /// <summary>Indicates whether the current menu is a sub-menu.</summary>
-    public bool IsSubMenu { get; }
-
-    /// <summary>Indicates whether the current menu will reopen.</summary>
-    public bool Reopen { get; }
-}

+ 273 - 0
Terminal.Gui/Views/Menu/MenuItem.cs

@@ -0,0 +1,273 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     A <see cref="MenuItem"/> has title, an associated help text, and an action to execute on activation. MenuItems
+///     can also have a checked indicator (see <see cref="Checked"/>).
+/// </summary>
+public class MenuItem
+{
+    private readonly ShortcutHelper _shortcutHelper;
+    private bool _allowNullChecked;
+    private MenuItemCheckStyle _checkType;
+
+    private string _title;
+
+    // TODO: Update to use Key instead of KeyCode
+    /// <summary>Initializes a new instance of <see cref="MenuItem"/></summary>
+    public MenuItem (KeyCode shortcut = KeyCode.Null) : this ("", "", null, null, null, shortcut) { }
+
+    // TODO: Update to use Key instead of KeyCode
+    /// <summary>Initializes a new instance of <see cref="MenuItem"/>.</summary>
+    /// <param name="title">Title for the menu item.</param>
+    /// <param name="help">Help text to display.</param>
+    /// <param name="action">Action to invoke when the menu item is activated.</param>
+    /// <param name="canExecute">Function to determine if the action can currently be executed.</param>
+    /// <param name="parent">The <see cref="Parent"/> of this menu item.</param>
+    /// <param name="shortcut">The <see cref="Shortcut"/> keystroke combination.</param>
+    public MenuItem (
+        string title,
+        string help,
+        Action action,
+        Func<bool> canExecute = null,
+        MenuItem parent = null,
+        KeyCode shortcut = KeyCode.Null
+    )
+    {
+        Title = title ?? "";
+        Help = help ?? "";
+        Action = action;
+        CanExecute = canExecute;
+        Parent = parent;
+        _shortcutHelper = new ();
+
+        if (shortcut != KeyCode.Null)
+        {
+            Shortcut = shortcut;
+        }
+    }
+
+    /// <summary>Gets or sets the action to be invoked when the menu item is triggered.</summary>
+    /// <value>Method to invoke.</value>
+    public Action Action { get; set; }
+
+    /// <summary>
+    ///     Used only if <see cref="CheckType"/> is of <see cref="MenuItemCheckStyle.Checked"/> type. If
+    ///     <see langword="true"/> allows <see cref="Checked"/> to be null, true or false. If <see langword="false"/> only
+    ///     allows <see cref="Checked"/> to be true or false.
+    /// </summary>
+    public bool AllowNullChecked
+    {
+        get => _allowNullChecked;
+        set
+        {
+            _allowNullChecked = value;
+            Checked ??= false;
+        }
+    }
+
+    /// <summary>
+    ///     Gets or sets the action to be invoked to determine if the menu can be triggered. If <see cref="CanExecute"/>
+    ///     returns <see langword="true"/> the menu item will be enabled. Otherwise, it will be disabled.
+    /// </summary>
+    /// <value>Function to determine if the action is can be executed or not.</value>
+    public Func<bool> CanExecute { get; set; }
+
+    /// <summary>
+    ///     Sets or gets whether the <see cref="MenuItem"/> shows a check indicator or not. See
+    ///     <see cref="MenuItemCheckStyle"/>.
+    /// </summary>
+    public bool? Checked { set; get; }
+
+    /// <summary>
+    ///     Sets or gets the <see cref="MenuItemCheckStyle"/> of a menu item where <see cref="Checked"/> is set to
+    ///     <see langword="true"/>.
+    /// </summary>
+    public MenuItemCheckStyle CheckType
+    {
+        get => _checkType;
+        set
+        {
+            _checkType = value;
+
+            if (_checkType == MenuItemCheckStyle.Checked && !_allowNullChecked && Checked is null)
+            {
+                Checked = false;
+            }
+        }
+    }
+
+    /// <summary>Gets or sets arbitrary data for the menu item.</summary>
+    /// <remarks>This property is not used internally.</remarks>
+    public object Data { get; set; }
+
+    /// <summary>Gets or sets the help text for the menu item. The help text is drawn to the right of the <see cref="Title"/>.</summary>
+    /// <value>The help text.</value>
+    public string Help { get; set; }
+
+    /// <summary>Gets the parent for this <see cref="MenuItem"/>.</summary>
+    /// <value>The parent.</value>
+    public MenuItem Parent { get; set; }
+
+    /// <summary>Gets or sets the title of the menu item .</summary>
+    /// <value>The title.</value>
+    public string Title
+    {
+        get => _title;
+        set
+        {
+            if (_title == value)
+            {
+                return;
+            }
+
+            _title = value;
+            GetHotKey ();
+        }
+    }
+
+    /// <summary>Gets if this <see cref="MenuItem"/> is from a sub-menu.</summary>
+    internal bool IsFromSubMenu => Parent != null;
+
+    internal int TitleLength => GetMenuBarItemLength (Title);
+
+    // 
+    // ┌─────────────────────────────┐
+    // │ Quit  Quit UI Catalog  Ctrl+Q │
+    // └─────────────────────────────┘
+    // ┌─────────────────┐
+    // │ ◌ TopLevel Alt+T │
+    // └─────────────────┘
+    // TODO: Replace the `2` literals with named constants 
+    internal int Width => 1
+                          + // space before Title
+                          TitleLength
+                          + 2
+                          + // space after Title - BUGBUG: This should be 1 
+                          (Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio)
+                               ? 2
+                               : 0)
+                          + // check glyph + space 
+                          (Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0)
+                          + // Two spaces before Help
+                          (ShortcutTag.GetColumns () > 0
+                               ? 2 + ShortcutTag.GetColumns ()
+                               : 0); // Pad two spaces before shortcut tag (which are also aligned right)
+
+    /// <summary>Merely a debugging aid to see the interaction with main.</summary>
+    internal bool GetMenuBarItem () { return IsFromSubMenu; }
+
+    /// <summary>Merely a debugging aid to see the interaction with main.</summary>
+    internal MenuItem GetMenuItem () { return this; }
+
+    /// <summary>
+    ///     Returns <see langword="true"/> if the menu item is enabled. This method is a wrapper around
+    ///     <see cref="CanExecute"/>.
+    /// </summary>
+    public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
+
+    /// <summary>
+    ///     Toggle the <see cref="Checked"/> between three states if <see cref="AllowNullChecked"/> is
+    ///     <see langword="true"/> or between two states if <see cref="AllowNullChecked"/> is <see langword="false"/>.
+    /// </summary>
+    public void ToggleChecked ()
+    {
+        if (_checkType != MenuItemCheckStyle.Checked)
+        {
+            throw new InvalidOperationException ("This isn't a Checked MenuItemCheckStyle!");
+        }
+
+        bool? previousChecked = Checked;
+
+        if (AllowNullChecked)
+        {
+            Checked = previousChecked switch
+                      {
+                          null => true,
+                          true => false,
+                          false => null
+                      };
+        }
+        else
+        {
+            Checked = !Checked;
+        }
+    }
+
+    private static int GetMenuBarItemLength (string title)
+    {
+        return title.EnumerateRunes ()
+                    .Where (ch => ch != MenuBar.HotKeySpecifier)
+                    .Sum (ch => Math.Max (ch.GetColumns (), 1));
+    }
+
+    #region Keyboard Handling
+
+    // TODO: Update to use Key instead of Rune
+    /// <summary>
+    ///     The HotKey is used to activate a <see cref="MenuItem"/> with the keyboard. HotKeys are defined by prefixing the
+    ///     <see cref="Title"/> of a MenuItem with an underscore ('_').
+    ///     <para>
+    ///         Pressing Alt-Hotkey for a <see cref="MenuBarItem"/> (menu items on the menu bar) works even if the menu is
+    ///         not active). Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem.
+    ///     </para>
+    ///     <para>
+    ///         For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the
+    ///         File menu. Pressing the N key will then activate the New MenuItem.
+    ///     </para>
+    ///     <para>See also <see cref="Shortcut"/> which enable global key-bindings to menu items.</para>
+    /// </summary>
+    public Rune HotKey { get; set; }
+    private void GetHotKey ()
+    {
+        var nextIsHot = false;
+
+        foreach (char x in _title)
+        {
+            if (x == MenuBar.HotKeySpecifier.Value)
+            {
+                nextIsHot = true;
+            }
+            else
+            {
+                if (nextIsHot)
+                {
+                    HotKey = (Rune)char.ToUpper (x);
+
+                    break;
+                }
+
+                nextIsHot = false;
+                HotKey = default (Rune);
+            }
+        }
+    }
+
+    // TODO: Update to use Key instead of KeyCode
+    /// <summary>
+    ///     Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the
+    ///     <see cref="View"/> that is the parent of the <see cref="MenuBar"/> or <see cref="ContextMenu"/> this
+    ///     <see cref="MenuItem"/>.
+    ///     <para>
+    ///         The <see cref="KeyCode"/> will be drawn on the MenuItem to the right of the <see cref="Title"/> and
+    ///         <see cref="Help"/> text. See <see cref="ShortcutTag"/>.
+    ///     </para>
+    /// </summary>
+    public KeyCode Shortcut
+    {
+        get => _shortcutHelper.Shortcut;
+        set
+        {
+            if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == KeyCode.Null))
+            {
+                _shortcutHelper.Shortcut = value;
+            }
+        }
+    }
+
+    /// <summary>Gets the text describing the keystroke combination defined by <see cref="Shortcut"/>.</summary>
+    public string ShortcutTag => _shortcutHelper.Shortcut == KeyCode.Null
+                                     ? string.Empty
+                                     : Key.ToString (_shortcutHelper.Shortcut, MenuBar.ShortcutDelimiter);
+
+    #endregion Keyboard Handling
+}

+ 15 - 0
Terminal.Gui/Views/Menu/MenuItemCheckStyle.cs

@@ -0,0 +1,15 @@
+namespace Terminal.Gui;
+
+/// <summary>Specifies how a <see cref="MenuItem"/> shows selection state.</summary>
+[Flags]
+public enum MenuItemCheckStyle
+{
+    /// <summary>The menu item will be shown normally, with no check indicator. The default.</summary>
+    NoCheck = 0b_0000_0000,
+
+    /// <summary>The menu item will indicate checked/un-checked state (see <see cref="Checked"/>).</summary>
+    Checked = 0b_0000_0001,
+
+    /// <summary>The menu item is part of a menu radio group (see <see cref="Checked"/>) and will indicate selected state.</summary>
+    Radio = 0b_0000_0010
+}

+ 20 - 0
Terminal.Gui/Views/Menu/MenuOpenedEventArgs.cs

@@ -0,0 +1,20 @@
+namespace Terminal.Gui;
+
+/// <summary>Defines arguments for the <see cref="MenuBar.MenuOpened"/> event</summary>
+public class MenuOpenedEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="MenuOpenedEventArgs"/> class</summary>
+    /// <param name="parent"></param>
+    /// <param name="menuItem"></param>
+    public MenuOpenedEventArgs (MenuBarItem parent, MenuItem menuItem)
+    {
+        Parent = parent;
+        MenuItem = menuItem;
+    }
+
+    /// <summary>Gets the <see cref="MenuItem"/> being opened.</summary>
+    public MenuItem MenuItem { get; }
+
+    /// <summary>The parent of <see cref="MenuItem"/>. Will be null if menu opening is the root.</summary>
+    public MenuBarItem Parent { get; }
+}

+ 24 - 0
Terminal.Gui/Views/Menu/MenuOpeningEventArgs.cs

@@ -0,0 +1,24 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     An <see cref="EventArgs"/> which allows passing a cancelable menu opening event or replacing with a new
+///     <see cref="MenuBarItem"/>.
+/// </summary>
+public class MenuOpeningEventArgs : EventArgs
+{
+    /// <summary>Initializes a new instance of <see cref="MenuOpeningEventArgs"/>.</summary>
+    /// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
+    public MenuOpeningEventArgs (MenuBarItem currentMenu) { CurrentMenu = currentMenu; }
+
+    /// <summary>
+    ///     Flag that allows the cancellation of the event. If set to <see langword="true"/> in the event handler, the
+    ///     event will be canceled.
+    /// </summary>
+    public bool Cancel { get; set; }
+
+    /// <summary>The current <see cref="MenuBarItem"/> parent.</summary>
+    public MenuBarItem CurrentMenu { get; }
+
+    /// <summary>The new <see cref="MenuBarItem"/> to be replaced.</summary>
+    public MenuBarItem NewMenuBarItem { get; set; }
+}

+ 189 - 197
Terminal.Gui/Views/RadioGroup.cs

@@ -65,16 +65,32 @@ public class RadioGroup : View
                     Command.Accept,
                     () =>
                     {
-                        SelectItem ();
+                        SelectedItem = _cursor;
+
                         return !OnAccept ();
                     }
                    );
 
+        AddCommand (
+                    Command.HotKey,
+                    ctx =>
+                    {
+                        SetFocus ();
+                        if (ctx.KeyBinding?.Context is { } && (int)ctx.KeyBinding?.Context! < _radioLabels.Count)
+                        {
+                            SelectedItem = (int)ctx.KeyBinding?.Context!;
+
+                            return !OnAccept();
+                        }
+
+                        return true;
+                    });
+
         SetupKeyBindings ();
 
         LayoutStarted += RadioGroup_LayoutStarted;
 
-        HighlightStyle = Gui.HighlightStyle.PressedOutside | Gui.HighlightStyle.Pressed;
+        HighlightStyle = HighlightStyle.PressedOutside | HighlightStyle.Pressed;
 
         MouseClick += RadioGroup_MouseClick;
     }
@@ -84,6 +100,7 @@ public class RadioGroup : View
     private void SetupKeyBindings ()
     {
         KeyBindings.Clear ();
+
         // Default keybindings for this view
         if (Orientation == Orientation.Vertical)
         {
@@ -95,6 +112,7 @@ public class RadioGroup : View
             KeyBindings.Add (Key.CursorLeft, Command.LineUp);
             KeyBindings.Add (Key.CursorRight, Command.LineDown);
         }
+
         KeyBindings.Add (Key.Home, Command.TopHome);
         KeyBindings.Add (Key.End, Command.BottomEnd);
         KeyBindings.Add (Key.Space, Command.Accept);
@@ -179,11 +197,13 @@ public class RadioGroup : View
             int prevCount = _radioLabels.Count;
             _radioLabels = value.ToList ();
 
-            foreach (string label in _radioLabels)
+            for (var index = 0; index < _radioLabels.Count; index++)
             {
+                string label = _radioLabels [index];
+
                 if (TextFormatter.FindHotKey (label, HotKeySpecifier, out _, out Key hotKey))
                 {
-                    AddKeyBindingsForHotKey (Key.Empty, hotKey);
+                    AddKeyBindingsForHotKey (Key.Empty, hotKey, index);
                 }
             }
 
@@ -192,7 +212,7 @@ public class RadioGroup : View
         }
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override string Text
     {
         get
@@ -201,6 +221,7 @@ public class RadioGroup : View
             {
                 return string.Empty;
             }
+
             // Return labels as a CSV string
             return string.Join (",", _radioLabels);
         }
@@ -220,73 +241,51 @@ public class RadioGroup : View
     /// <summary>The currently selected item from the list of radio labels</summary>
     /// <value>The selected.</value>
     public int SelectedItem
-{
-    get => _selected;
-    set
     {
-        OnSelectedItemChanged (value, SelectedItem);
-        _cursor = Math.Max (_selected, 0);
-        SetNeedsDisplay ();
+        get => _selected;
+        set
+        {
+            OnSelectedItemChanged (value, SelectedItem);
+            _cursor = Math.Max (_selected, 0);
+            SetNeedsDisplay ();
+        }
     }
-}
 
-/// <inheritdoc/>
-public override void OnDrawContent (Rectangle viewport)
-{
-    base.OnDrawContent (viewport);
+    /// <inheritdoc/>
+    public override void OnDrawContent (Rectangle viewport)
+    {
+        base.OnDrawContent (viewport);
 
-    Driver.SetAttribute (GetNormalColor ());
+        Driver.SetAttribute (GetNormalColor ());
 
-    for (var i = 0; i < _radioLabels.Count; i++)
-    {
-        switch (Orientation)
+        for (var i = 0; i < _radioLabels.Count; i++)
         {
-            case Orientation.Vertical:
-                Move (0, i);
+            switch (Orientation)
+            {
+                case Orientation.Vertical:
+                    Move (0, i);
 
-                break;
-            case Orientation.Horizontal:
-                Move (_horizontal [i].pos, 0);
+                    break;
+                case Orientation.Horizontal:
+                    Move (_horizontal [i].pos, 0);
 
-                break;
-        }
-
-        string rl = _radioLabels [i];
-        Driver.SetAttribute (GetNormalColor ());
-        Driver.AddStr ($"{(i == _selected ? Glyphs.Selected : Glyphs.UnSelected)} ");
-        TextFormatter.FindHotKey (rl, HotKeySpecifier, out int hotPos, out Key hotKey);
+                    break;
+            }
 
-        if (hotPos != -1 && hotKey != Key.Empty)
-        {
-            Rune [] rlRunes = rl.ToRunes ();
+            string rl = _radioLabels [i];
+            Driver.SetAttribute (GetNormalColor ());
+            Driver.AddStr ($"{(i == _selected ? Glyphs.Selected : Glyphs.UnSelected)} ");
+            TextFormatter.FindHotKey (rl, HotKeySpecifier, out int hotPos, out Key hotKey);
 
-            for (var j = 0; j < rlRunes.Length; j++)
+            if (hotPos != -1 && hotKey != Key.Empty)
             {
-                Rune rune = rlRunes [j];
+                Rune [] rlRunes = rl.ToRunes ();
 
-                if (j == hotPos && i == _cursor)
-                {
-                    Application.Driver.SetAttribute (
-                                                     HasFocus
-                                                         ? ColorScheme.HotFocus
-                                                         : GetHotNormalColor ()
-                                                    );
-                }
-                else if (j == hotPos && i != _cursor)
+                for (var j = 0; j < rlRunes.Length; j++)
                 {
-                    Application.Driver.SetAttribute (GetHotNormalColor ());
-                }
-                else if (HasFocus && i == _cursor)
-                {
-                    Application.Driver.SetAttribute (ColorScheme.Focus);
-                }
+                    Rune rune = rlRunes [j];
 
-                if (rune == HotKeySpecifier && j + 1 < rlRunes.Length)
-                {
-                    j++;
-                    rune = rlRunes [j];
-
-                    if (i == _cursor)
+                    if (j == hotPos && i == _cursor)
                     {
                         Application.Driver.SetAttribute (
                                                          HasFocus
@@ -294,184 +293,177 @@ public override void OnDrawContent (Rectangle viewport)
                                                              : GetHotNormalColor ()
                                                         );
                     }
-                    else if (i != _cursor)
+                    else if (j == hotPos && i != _cursor)
                     {
                         Application.Driver.SetAttribute (GetHotNormalColor ());
                     }
-                }
+                    else if (HasFocus && i == _cursor)
+                    {
+                        Application.Driver.SetAttribute (ColorScheme.Focus);
+                    }
+
+                    if (rune == HotKeySpecifier && j + 1 < rlRunes.Length)
+                    {
+                        j++;
+                        rune = rlRunes [j];
+
+                        if (i == _cursor)
+                        {
+                            Application.Driver.SetAttribute (
+                                                             HasFocus
+                                                                 ? ColorScheme.HotFocus
+                                                                 : GetHotNormalColor ()
+                                                            );
+                        }
+                        else if (i != _cursor)
+                        {
+                            Application.Driver.SetAttribute (GetHotNormalColor ());
+                        }
+                    }
 
-                Application.Driver.AddRune (rune);
-                Driver.SetAttribute (GetNormalColor ());
+                    Application.Driver.AddRune (rune);
+                    Driver.SetAttribute (GetNormalColor ());
+                }
+            }
+            else
+            {
+                DrawHotString (rl, HasFocus && i == _cursor, ColorScheme);
             }
-        }
-        else
-        {
-            DrawHotString (rl, HasFocus && i == _cursor, ColorScheme);
         }
     }
-}
 
-/// <inheritdoc/>
-public override bool? OnInvokingKeyBindings (Key keyEvent)
-{
-    // This is a bit of a hack. We want to handle the key bindings for the radio group but
-    // InvokeKeyBindings doesn't pass any context so we can't tell if the key binding is for
-    // the radio group or for one of the radio buttons. So before we call the base class
-    // we set SelectedItem appropriately.
-
-    Key key = keyEvent;
-
-    if (KeyBindings.TryGet (key, out _))
+    /// <summary>Called when the view orientation has changed. Invokes the <see cref="OrientationChanged"/> event.</summary>
+    /// <param name="newOrientation"></param>
+    /// <returns>True of the event was cancelled.</returns>
+    public virtual bool OnOrientationChanged (Orientation newOrientation)
     {
-        // Search RadioLabels 
-        for (var i = 0; i < _radioLabels.Count; i++)
+        var args = new OrientationEventArgs (newOrientation);
+        OrientationChanged?.Invoke (this, args);
+
+        if (!args.Cancel)
         {
-            if (TextFormatter.FindHotKey (
-                                          _radioLabels [i],
-                                          HotKeySpecifier,
-                                          out _,
-                                          out Key hotKey,
-                                          true
-                                         )
-                && key.NoAlt.NoCtrl.NoShift == hotKey)
-            {
-                SelectedItem = i;
-                break;
-            }
+            _orientation = newOrientation;
+            SetupKeyBindings ();
+            SetContentSize ();
         }
-    }
-
-    return base.OnInvokingKeyBindings (keyEvent);
-}
 
-/// <summary>Called when the view orientation has changed. Invokes the <see cref="OrientationChanged"/> event.</summary>
-/// <param name="newOrientation"></param>
-/// <returns>True of the event was cancelled.</returns>
-public virtual bool OnOrientationChanged (Orientation newOrientation)
-{
-    var args = new OrientationEventArgs (newOrientation);
-    OrientationChanged?.Invoke (this, args);
+        return args.Cancel;
+    }
 
-    if (!args.Cancel)
+    // TODO: This should be cancelable
+    /// <summary>Called whenever the current selected item changes. Invokes the <see cref="SelectedItemChanged"/> event.</summary>
+    /// <param name="selectedItem"></param>
+    /// <param name="previousSelectedItem"></param>
+    public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem)
     {
-        _orientation = newOrientation;
-        SetupKeyBindings ();
-        SetContentSize ();
+        _selected = selectedItem;
+        SelectedItemChanged?.Invoke (this, new (selectedItem, previousSelectedItem));
     }
 
-    return args.Cancel;
-}
+    /// <summary>
+    ///     Fired when the view orientation has changed. Can be cancelled by setting
+    ///     <see cref="OrientationEventArgs.Cancel"/> to true.
+    /// </summary>
+    public event EventHandler<OrientationEventArgs> OrientationChanged;
 
-// TODO: This should be cancelable
-/// <summary>Called whenever the current selected item changes. Invokes the <see cref="SelectedItemChanged"/> event.</summary>
-/// <param name="selectedItem"></param>
-/// <param name="previousSelectedItem"></param>
-public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem)
-{
-    _selected = selectedItem;
-    SelectedItemChanged?.Invoke (this, new SelectedItemChangedArgs (selectedItem, previousSelectedItem));
-}
+    /// <inheritdoc/>
+    public override Point? PositionCursor ()
+    {
+        var x = 0;
+        var y = 0;
 
-/// <summary>
-///     Fired when the view orientation has changed. Can be cancelled by setting
-///     <see cref="OrientationEventArgs.Cancel"/> to true.
-/// </summary>
-public event EventHandler<OrientationEventArgs> OrientationChanged;
+        switch (Orientation)
+        {
+            case Orientation.Vertical:
+                y = _cursor;
 
-/// <inheritdoc/>
-public override Point? PositionCursor ()
-{
-    int x = 0;
-    int y = 0;
-    switch (Orientation)
-    {
-        case Orientation.Vertical:
-            y = _cursor;
+                break;
+            case Orientation.Horizontal:
+                x = _horizontal [_cursor].pos;
 
-            break;
-        case Orientation.Horizontal:
-            x = _horizontal [_cursor].pos;
+                break;
 
-            break;
+            default:
+                return null;
+        }
 
-        default:
-            return null;
-    }
+        Move (x, y);
 
-    Move (x, y);
-    return null; // Don't show the cursor
-}
+        return null; // Don't show the cursor
+    }
 
-/// <summary>Allow to invoke the <see cref="SelectedItemChanged"/> after their creation.</summary>
-public void Refresh () { OnSelectedItemChanged (_selected, -1); }
+    /// <summary>Allow to invoke the <see cref="SelectedItemChanged"/> after their creation.</summary>
+    public void Refresh () { OnSelectedItemChanged (_selected, -1); }
 
-// TODO: This should use StateEventArgs<int> and should be cancelable.
-/// <summary>Invoked when the selected radio label has changed.</summary>
-public event EventHandler<SelectedItemChangedArgs> SelectedItemChanged;
+    // TODO: This should use StateEventArgs<int> and should be cancelable.
+    /// <summary>Invoked when the selected radio label has changed.</summary>
+    public event EventHandler<SelectedItemChangedArgs> SelectedItemChanged;
 
-private void MoveDownRight ()
-{
-    if (_cursor + 1 < _radioLabels.Count)
+    private void MoveDownRight ()
     {
-        _cursor++;
-        SetNeedsDisplay ();
-    }
-    else if (_cursor > 0)
-    {
-        _cursor = 0;
-        SetNeedsDisplay ();
+        if (_cursor + 1 < _radioLabels.Count)
+        {
+            _cursor++;
+            SetNeedsDisplay ();
+        }
+        else if (_cursor > 0)
+        {
+            _cursor = 0;
+            SetNeedsDisplay ();
+        }
     }
-}
 
-private void MoveEnd () { _cursor = Math.Max (_radioLabels.Count - 1, 0); }
-private void MoveHome () { _cursor = 0; }
+    private void MoveEnd () { _cursor = Math.Max (_radioLabels.Count - 1, 0); }
+    private void MoveHome () { _cursor = 0; }
 
-private void MoveUpLeft ()
-{
-    if (_cursor > 0)
+    private void MoveUpLeft ()
     {
-        _cursor--;
-        SetNeedsDisplay ();
-    }
-    else if (_radioLabels.Count - 1 > 0)
-    {
-        _cursor = _radioLabels.Count - 1;
-        SetNeedsDisplay ();
+        if (_cursor > 0)
+        {
+            _cursor--;
+            SetNeedsDisplay ();
+        }
+        else if (_radioLabels.Count - 1 > 0)
+        {
+            _cursor = _radioLabels.Count - 1;
+            SetNeedsDisplay ();
+        }
     }
-}
 
-private void RadioGroup_LayoutStarted (object sender, EventArgs e) { SetContentSize (); }
-private void SelectItem () { SelectedItem = _cursor; }
+    private void RadioGroup_LayoutStarted (object sender, EventArgs e) { SetContentSize (); }
 
-private void SetContentSize ()
-{
-    switch (_orientation)
+    private void SetContentSize ()
     {
-        case Orientation.Vertical:
-            var width = 0;
+        switch (_orientation)
+        {
+            case Orientation.Vertical:
+                var width = 0;
 
-            foreach (string s in _radioLabels)
-            {
-                width = Math.Max (s.GetColumns () + 2, width);
-            }
+                foreach (string s in _radioLabels)
+                {
+                    width = Math.Max (s.GetColumns () + 2, width);
+                }
 
-            SetContentSize (new (width, _radioLabels.Count));
-            break;
+                SetContentSize (new (width, _radioLabels.Count));
 
-        case Orientation.Horizontal:
-            _horizontal = new List<(int pos, int length)> ();
-            var start = 0;
-            var length = 0;
+                break;
 
-            for (var i = 0; i < _radioLabels.Count; i++)
-            {
-                start += length;
+            case Orientation.Horizontal:
+                _horizontal = new ();
+                var start = 0;
+                var length = 0;
 
-                length = _radioLabels [i].GetColumns () + 2 + (i < _radioLabels.Count - 1 ? _horizontalSpace : 0);
-                _horizontal.Add ((start, length));
-            }
-            SetContentSize (new (_horizontal.Sum (item => item.length), 1));
-            break;
+                for (var i = 0; i < _radioLabels.Count; i++)
+                {
+                    start += length;
+
+                    length = _radioLabels [i].GetColumns () + 2 + (i < _radioLabels.Count - 1 ? _horizontalSpace : 0);
+                    _horizontal.Add ((start, length));
+                }
+
+                SetContentSize (new (_horizontal.Sum (item => item.length), 1));
+
+                break;
+        }
     }
 }
-}

+ 0 - 529
Terminal.Gui/Views/Shortcut.cs

@@ -1,529 +0,0 @@
-using System.ComponentModel;
-
-namespace Terminal.Gui;
-
-// TODO: I don't love the name Shortcut, but I can't think of a better one right now. Shortcut is a bit overloaded.
-// TODO: It can mean "Application-scoped key binding" or "A key binding that is displayed in a visual way".
-// TODO: I tried `BarItem` but that's not great either as it implies it can only be used in `Bar`s.
-
-/// <summary>
-///     Displays a command, help text, and a key binding. Useful for displaying a command in <see cref="Bar"/> such as a
-///     menu, toolbar, or status bar.
-/// </summary>
-/// <remarks>
-///     <para>
-///         When the user clicks on the <see cref="Shortcut"/> or presses the key
-///         specified by <see cref="Key"/> the <see cref="Command.Accept"/> command is invoked, causing the
-///         <see cref="Accept"/> event to be fired
-///     </para>
-///     <para>
-///         If <see cref="KeyBindingScope"/> is <see cref="KeyBindingScope.Application"/>, the <see cref="Command"/>
-///         be invoked regardless of what View has focus, enabling an application-wide keyboard shortcut.
-///     </para>
-///     <para>
-///         Set <see cref="View.Title"/> to change the Command text displayed in the <see cref="Shortcut"/>.
-///         By default, the <see cref="Command"/> text is the <see cref="View.Title"/> of <see cref="CommandView"/>.
-///     </para>
-///     <para>
-///         Set <see cref="View.Text"/> to change the Help text displayed in the <see cref="Shortcut"/>.
-///     </para>
-///     <para>
-///         The text displayed for the <see cref="Key"/> is the string representation of the <see cref="Key"/>.
-///         If the <see cref="Key"/> is <see cref="Key.Empty"/>, the <see cref="Key"/> text is not displayed.
-///     </para>
-/// </remarks>
-public class Shortcut : View
-{
-    // Hosts the Command, Help, and Key Views. Needed (IIRC - wrote a long time ago) to allow mouse clicks to be handled by the Shortcut.
-    internal readonly View _container;
-
-    /// <summary>
-    ///     Creates a new instance of <see cref="Shortcut"/>.
-    /// </summary>
-    public Shortcut ()
-    {
-        CanFocus = true;
-        Width = Dim.Auto (DimAutoStyle.Content);
-        Height = Dim.Auto (DimAutoStyle.Content);
-
-        //Height = Dim.Auto (minimumContentDim: 1, maximumContentDim: 1);
-
-        AddCommand (Gui.Command.HotKey, () => true);
-        AddCommand (Gui.Command.Accept, OnAccept);
-        KeyBindings.Add (KeyCode.Space, Gui.Command.Accept);
-        KeyBindings.Add (KeyCode.Enter, Gui.Command.Accept);
-
-        _container = new ()
-        {
-            Id = "_container",
-            // Only the Shortcut (_container) should be able to have focus, not any subviews.
-            CanFocus = true,
-            Width = Dim.Auto (DimAutoStyle.Content, 1),
-            Height = Dim.Auto (DimAutoStyle.Content, 1),
-            BorderStyle = LineStyle.Dashed
-        };
-
-        CommandView = new ();
-
-        HelpView = new ()
-        {
-            Id = "_helpView",
-            // Only the Shortcut should be able to have focus, not any subviews
-            CanFocus = false,
-            X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast | AlignmentModes.AddSpaceBetweenItems),
-            Y = Pos.Center (),
-
-            // Helpview is the only subview that doesn't have a min width
-            Width = Dim.Auto (DimAutoStyle.Text),
-            Height = Dim.Auto (DimAutoStyle.Text),
-            ColorScheme = Colors.ColorSchemes ["Error"]
-        };
-        _container.Add (HelpView);
-
-        //        HelpView.TextAlignment = Alignment.End;
-        HelpView.MouseClick += Shortcut_MouseClick;
-
-        KeyView = new ()
-        {
-            Id = "_keyView",
-            // Only the Shortcut should be able to have focus, not any subviews
-            CanFocus = false,
-            X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast | AlignmentModes.AddSpaceBetweenItems),
-            Y = Pos.Center (),
-
-            // Bar will set the width of all KeyViews to the width of the widest KeyView.
-            Width = Dim.Auto (DimAutoStyle.Text),
-            Height = Dim.Auto (DimAutoStyle.Text),
-        };
-        _container.Add (KeyView);
-
-        KeyView.MouseClick += Shortcut_MouseClick;
-
-        CommandView.Margin.Thickness = new Thickness (1, 0, 1, 0);
-        HelpView.Margin.Thickness = new Thickness (1, 0, 1, 0);
-        KeyView.Margin.Thickness = new Thickness (1, 0, 1, 0);
-
-        MouseClick += Shortcut_MouseClick;
-
-        TitleChanged += Shortcut_TitleChanged;
-        Initialized += OnInitialized;
-
-        Add (_container);
-
-        return;
-
-        void OnInitialized (object sender, EventArgs e)
-        {
-            if (ColorScheme != null)
-            {
-                var cs = new ColorScheme (ColorScheme)
-                {
-                    Normal = ColorScheme.HotNormal,
-                    HotNormal = ColorScheme.Normal
-                };
-                KeyView.ColorScheme = cs;
-            }
-        }
-    }
-
-    private void Shortcut_MouseClick (object sender, MouseEventEventArgs e)
-    {
-        // When the Shortcut is clicked, we want to invoke the Command and Set focus
-        View view = sender as View;
-        if (!e.Handled && Command.HasValue)
-        {
-            // If the subview (likely CommandView) didn't handle the mouse click, invoke the command.
-            bool? handled = false;
-            handled = InvokeCommand (Command.Value);
-            if (handled.HasValue)
-            {
-                e.Handled = handled.Value;
-            }
-        }
-        if (CanFocus)
-        {
-            SetFocus ();
-        }
-        e.Handled = true;
-    }
-
-    /// <inheritdoc/>
-    public override ColorScheme ColorScheme
-    {
-        get
-        {
-            if (base.ColorScheme == null)
-            {
-                return SuperView?.ColorScheme ?? base.ColorScheme;
-            }
-
-            return base.ColorScheme;
-        }
-        set
-        {
-            base.ColorScheme = value;
-
-            if (ColorScheme != null)
-            {
-                var cs = new ColorScheme (ColorScheme)
-                {
-                    Normal = ColorScheme.HotNormal,
-                    HotNormal = ColorScheme.Normal
-                };
-                KeyView.ColorScheme = cs;
-            }
-        }
-    }
-
-    #region Command
-
-    private Command? _command;
-
-    /// <summary>
-    ///     Gets or sets the <see cref="Command"/> that will be invoked when the user clicks on the <see cref="Shortcut"/> or
-    ///     presses <see cref="Key"/>.
-    /// </summary>
-    public Command? Command
-    {
-        get => _command;
-        set
-        {
-            if (value != null)
-            {
-                _command = value.Value;
-                UpdateKeyBinding ();
-            }
-        }
-    }
-
-    private View _commandView;
-
-    /// <summary>
-    ///     Gets or sets the View that displays the command text and hotkey.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         By default, the <see cref="View.Title"/> of the <see cref="CommandView"/> is displayed as the Shortcut's
-    ///         command text.
-    ///     </para>
-    ///     <para>
-    ///         By default, the CommandView is a <see cref="View"/> with <see cref="View.CanFocus"/> set to
-    ///         <see langword="false"/>.
-    ///     </para>
-    ///     <para>
-    ///         Setting the <see cref="CommandView"/> will add it to the <see cref="Shortcut"/> and remove any existing
-    ///         <see cref="CommandView"/>.
-    ///     </para>
-    /// </remarks>
-    /// <example>
-    ///     <para>
-    ///         This example illustrates how to add a <see cref="Shortcut"/> to a <see cref="StatusBar"/> that toggles the
-    ///         <see cref="Application.Force16Colors"/> property.
-    ///     </para>
-    ///     <code>
-    ///     var force16ColorsShortcut = new Shortcut
-    ///     {
-    ///         Key = Key.F6,
-    ///         KeyBindingScope = KeyBindingScope.HotKey,
-    ///         Command = Command.Accept,
-    ///         CommandView = new CheckBox { Text = "Force 16 Colors" }
-    ///     };
-    ///     var cb = force16ColorsShortcut.CommandView as CheckBox;
-    ///     cb.Checked = Application.Force16Colors;
-    /// 
-    ///     cb.Toggled += (s, e) =>
-    ///     {
-    ///         var cb = s as CheckBox;
-    ///         Application.Force16Colors = cb!.Checked == true;
-    ///         Application.Refresh();
-    ///     };
-    ///     StatusBar.Add(force16ColorsShortcut);
-    /// </code>
-    /// </example>
-
-    public View CommandView
-    {
-        get => _commandView;
-        set
-        {
-            if (value == null)
-            {
-                throw new ArgumentNullException ();
-            }
-
-            if (_commandView is { })
-            {
-                _container.Remove (_commandView);
-                _commandView?.Dispose ();
-            }
-
-
-            _commandView = value;
-            _commandView.Id = "_commandView";
-
-            // TODO: Determine if it makes sense to allow the CommandView to be focusable.
-            // Right now, we don't set CanFocus to false here.
-            _commandView.CanFocus = false;
-
-            // Bar will set the width of all CommandViews to the width of the widest CommandViews.
-            _commandView.Width = Dim.Auto (DimAutoStyle.Text);
-            _commandView.Height = Dim.Auto (DimAutoStyle.Text);
-            _commandView.X = X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast | AlignmentModes.AddSpaceBetweenItems);
-            _commandView.Y = Pos.Center ();
-
-            _commandView.MouseClick += Shortcut_MouseClick;
-            _commandView.Accept += CommandView_Accept;
-
-            _commandView.Margin.Thickness = new (1, 0, 1, 0);
-
-            _commandView.HotKeyChanged += (s, e) =>
-                                          {
-                                              if (e.NewKey != Key.Empty)
-                                              {
-                                                  // Add it 
-                                                  AddKeyBindingsForHotKey (e.OldKey, e.NewKey);
-                                              }
-                                          };
-
-            _commandView.HotKeySpecifier = new ('_');
-
-            _container.Remove (HelpView);
-            _container.Remove (KeyView);
-            _container.Add (_commandView, HelpView, KeyView);
-
-            UpdateKeyBinding();
-
-        }
-    }
-
-    private void _commandView_MouseEvent (object sender, MouseEventEventArgs e)
-    {
-        e.Handled = true;
-    }
-
-    private void Shortcut_TitleChanged (object sender, StateEventArgs<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 CommandView_Accept (object sender, CancelEventArgs e)
-    {
-        // When the CommandView fires its Accept event, we want to act as though the
-        // Shortcut was clicked.
-        var args = new HandledEventArgs ();
-        Accept?.Invoke (this, args);
-
-        if (args.Handled)
-        {
-            e.Cancel = args.Handled;
-        }
-    }
-
-    #endregion Command
-
-    #region Help
-
-    /// <summary>
-    ///     The subview that displays the help text for the command. Internal for unit testing.
-    /// </summary>
-    internal View HelpView { get; set; }
-
-    /// <summary>
-    ///     Gets or sets the help text displayed in the middle of the Shortcut.
-    /// </summary>
-    public override string Text
-    {
-        get => base.Text;
-        set
-        {
-            //base.Text = value;
-            if (HelpView != null)
-            {
-                HelpView.Text = value;
-            }
-        }
-    }
-
-    #endregion Help
-
-    #region Key
-
-    private Key _key;
-
-    /// <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
-        {
-            if (value == null)
-            {
-                throw new ArgumentNullException ();
-            }
-
-            _key = value;
-
-            if (Command != null)
-            {
-                UpdateKeyBinding ();
-            }
-
-            KeyView.Text = $"{Key}";
-            KeyView.Visible = Key != Key.Empty;
-        }
-    }
-
-    private KeyBindingScope _keyBindingScope;
-
-    /// <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
-        {
-            _keyBindingScope = value;
-
-            if (Command != null)
-            {
-                UpdateKeyBinding ();
-            }
-        }
-    }
-
-    /// <summary>
-    ///     Gets the subview that displays the key. Internal for unit testing.
-    /// </summary>
-
-    internal View KeyView { get; }
-
-    private void UpdateKeyBinding ()
-    {
-        if (KeyBindingScope == KeyBindingScope.Application)
-        {
-          //  return;
-        }
-
-        if (Command != null && Key != null && Key != Key.Empty)
-        {
-            // CommandView holds our command/keybinding
-            // Add a key binding for this command to this Shortcut
-            if (CommandView.GetSupportedCommands ().Contains (Command.Value))
-            {
-                CommandView.KeyBindings.Remove (Key);
-                CommandView.KeyBindings.Add (Key, KeyBindingScope, Command.Value);
-            }
-            else
-            {
-               // throw new InvalidOperationException ($"CommandView does not support the command {Command.Value}");
-            }
-        }
-    }
-
-    #endregion Key
-
-    /// <summary>
-    ///     The event fired when the <see cref="Command.Accept"/> command is received. This
-    ///     occurs if the user clicks on the Shortcut or presses <see cref="Key"/>.
-    /// </summary>
-    public new event EventHandler<HandledEventArgs> Accept;
-
-    /// <summary>
-    ///     Called when the <see cref="Command.Accept"/> command is received. This
-    ///     occurs if the user clicks on the Bar with the mouse or presses the key bound to
-    ///     Command.Accept (Space by default).
-    /// </summary>
-    protected new bool? OnAccept ()
-    {
-        // TODO: This is not completely thought through.
-
-
-
-        if (Key == null || Key == Key.Empty)
-        {
-            return false;
-        }
-
-        var handled = false;
-        var keyCopy = new Key (Key);
-
-        switch (KeyBindingScope)
-        {
-            case KeyBindingScope.Application:
-                // Simulate a key down to invoke the Application scoped key binding
-                handled = Application.OnKeyDown (keyCopy);
-
-                break;
-            case KeyBindingScope.Focused:
-                handled = InvokeCommand (Command.Value) == true;
-                handled = false;
-
-                break;
-            case KeyBindingScope.HotKey:
-                if (Command.HasValue)
-                {
-                    //handled = _commandView.InvokeCommand (Gui.Command.HotKey) == true;
-                    //handled = false;
-                }
-                break;
-        }
-
-        //if (handled == false)
-        {
-            var args = new HandledEventArgs ();
-            Accept?.Invoke (this, args);
-            handled = args.Handled;
-        }
-
-        return handled;
-    }
-
-    /// <inheritdoc/>
-    public override bool OnEnter (View view)
-    {
-        // TODO: This is a hack. Need to refine this.
-        var cs = new ColorScheme (ColorScheme)
-        {
-            Normal = ColorScheme.Focus,
-            HotNormal = ColorScheme.HotFocus
-        };
-
-        _container.ColorScheme = cs;
-
-        cs = new (ColorScheme)
-        {
-            Normal = ColorScheme.HotFocus,
-            HotNormal = ColorScheme.Focus
-        };
-        KeyView.ColorScheme = cs;
-
-        return base.OnEnter (view);
-    }
-
-    /// <inheritdoc/>
-    public override bool OnLeave (View view)
-    {
-        // TODO: This is a hack. Need to refine this.
-        var cs = new ColorScheme (ColorScheme)
-        {
-            Normal = ColorScheme.Normal,
-            HotNormal = ColorScheme.HotNormal
-        };
-
-        _container.ColorScheme = cs;
-
-        cs = new (ColorScheme)
-        {
-            Normal = ColorScheme.HotNormal,
-            HotNormal = ColorScheme.Normal
-        };
-        KeyView.ColorScheme = cs;
-
-        return base.OnLeave (view);
-    }
-}

+ 11 - 94
Terminal.Gui/Views/StatusBar.cs

@@ -1,63 +1,5 @@
 namespace Terminal.Gui;
 
-/// <summary>
-///     <see cref="StatusItem"/> objects are contained by <see cref="StatusBar"/> <see cref="View"/>s. Each
-///     <see cref="StatusItem"/> has a title, a shortcut (hotkey), and an <see cref="Command"/> that will be invoked when
-///     the <see cref="StatusItem.Shortcut"/> is pressed. The <see cref="StatusItem.Shortcut"/> will be a global hotkey for
-///     the application in the current context of the screen. The color of the <see cref="StatusItem.Title"/> will be
-///     changed after each ~. A <see cref="StatusItem.Title"/> set to `~F1~ Help` will render as *F1* using
-///     <see cref="ColorScheme.HotNormal"/> and *Help* as <see cref="ColorScheme.HotNormal"/>.
-/// </summary>
-public class StatusItem
-{
-    /// <summary>Initializes a new <see cref="StatusItem"/>.</summary>
-    /// <param name="shortcut">Shortcut to activate the <see cref="StatusItem"/>.</param>
-    /// <param name="title">Title for the <see cref="StatusItem"/>.</param>
-    /// <param name="action">Action to invoke when the <see cref="StatusItem"/> is activated.</param>
-    /// <param name="canExecute">Function to determine if the action can currently be executed.</param>
-    public StatusItem (Key shortcut, string title, Action action, Func<bool> canExecute = null)
-    {
-        Title = title ?? "";
-        Shortcut = shortcut;
-        Action = action;
-        CanExecute = canExecute;
-    }
-
-    /// <summary>Gets or sets the action to be invoked when the statusbar item is triggered</summary>
-    /// <value>Action to invoke.</value>
-    public Action Action { get; set; }
-
-    /// <summary>
-    ///     Gets or sets the action to be invoked to determine if the <see cref="StatusItem"/> can be triggered. If
-    ///     <see cref="CanExecute"/> returns <see langword="true"/> the status item will be enabled. Otherwise, it will be
-    ///     disabled.
-    /// </summary>
-    /// <value>Function to determine if the action is can be executed or not.</value>
-    public Func<bool> CanExecute { get; set; }
-
-    /// <summary>Gets or sets arbitrary data for the status item.</summary>
-    /// <remarks>This property is not used internally.</remarks>
-    public object Data { get; set; }
-
-    /// <summary>Gets the global shortcut to invoke the action on the menu.</summary>
-    public Key Shortcut { get; set; }
-
-    /// <summary>Gets or sets the title.</summary>
-    /// <value>The title.</value>
-    /// <remarks>
-    ///     The colour of the <see cref="StatusItem.Title"/> will be changed after each ~. A
-    ///     <see cref="StatusItem.Title"/> set to `~F1~ Help` will render as *F1* using <see cref="ColorScheme.HotNormal"/> and
-    ///     *Help* as <see cref="ColorScheme.HotNormal"/>.
-    /// </remarks>
-    public string Title { get; set; }
-
-    /// <summary>
-    ///     Returns <see langword="true"/> if the status item is enabled. This method is a wrapper around
-    ///     <see cref="CanExecute"/>.
-    /// </summary>
-    public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
-}
-
 /// <summary>
 ///     A status bar is a <see cref="View"/> that snaps to the bottom of a <see cref="Toplevel"/> displaying set of
 ///     <see cref="StatusItem"/>s. The <see cref="StatusBar"/> should be context sensitive. This means, if the main menu
@@ -69,8 +11,7 @@ public class StatusBar : View
 {
     private static Rune _shortcutDelimiter = (Rune)'=';
 
-    private StatusItem [] _items = { };
-    private StatusItem _itemToInvoke;
+    private StatusItem [] _items = [];
 
     /// <summary>Initializes a new instance of the <see cref="StatusBar"/> class.</summary>
     public StatusBar () : this (new StatusItem [] { }) { }
@@ -91,10 +32,11 @@ public class StatusBar : View
         CanFocus = false;
         ColorScheme = Colors.ColorSchemes ["Menu"];
         X = 0;
-        Y = Pos.AnchorEnd (1);
+        Y = Pos.AnchorEnd ();
         Width = Dim.Fill ();
         Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize ().
-        AddCommand (Command.Accept, InvokeItem);
+
+        AddCommand (Command.Accept, ctx => InvokeItem ((StatusItem)ctx.KeyBinding?.Context));
     }
 
     /// <summary>The items that compose the <see cref="StatusBar"/></summary>
@@ -110,9 +52,10 @@ public class StatusBar : View
 
             _items = value;
 
-            foreach (StatusItem item in _items)
+            foreach (StatusItem item in _items.Where (i => i.Shortcut != Key.Empty))
             {
-                KeyBindings.Add (item.Shortcut, KeyBindingScope.HotKey, Command.Accept);
+                KeyBinding keyBinding = new (new [] { Command.Accept }, KeyBindingScope.HotKey, item);
+                KeyBindings.Add (item.Shortcut, keyBinding);
             }
         }
     }
@@ -142,7 +85,7 @@ public class StatusBar : View
     }
 
     ///<inheritdoc/>
-    protected internal override bool OnMouseEvent  (MouseEvent me)
+    protected internal override bool OnMouseEvent (MouseEvent me)
     {
         if (me.Flags != MouseFlags.Button1Clicked)
         {
@@ -215,32 +158,6 @@ public class StatusBar : View
         }
     }
 
-    /// <inheritdoc/>
-    public override bool? OnInvokingKeyBindings (Key keyEvent)
-    {
-        // This is a bit of a hack. We want to handle the key bindings for status bar but
-        // InvokeKeyBindings doesn't pass any context so we can't tell which item it is for.
-        // So before we call the base class we set SelectedItem appropriately.
-        Key key = new (keyEvent);
-
-        if (KeyBindings.TryGet (key, out _))
-        {
-            // Search RadioLabels 
-            foreach (StatusItem item in Items)
-            {
-                if (item.Shortcut == key)
-                {
-                    _itemToInvoke = item;
-                    //keyEvent.Scope = KeyBindingScope.HotKey;
-
-                    break;
-                }
-            }
-        }
-
-        return base.OnInvokingKeyBindings (keyEvent);
-    }
-
     /// <summary>Removes a <see cref="StatusItem"/> at specified index of <see cref="Items"/>.</summary>
     /// <param name="index">The zero-based index of the item to remove.</param>
     /// <returns>The <see cref="StatusItem"/> removed.</returns>
@@ -287,11 +204,11 @@ public class StatusBar : View
         return len;
     }
 
-    private bool? InvokeItem ()
+    private bool? InvokeItem (StatusItem itemToInvoke)
     {
-        if (_itemToInvoke is { Action: { } })
+        if (itemToInvoke is { Action: { } })
         {
-            _itemToInvoke.Action.Invoke ();
+            itemToInvoke.Action.Invoke ();
 
             return true;
         }

+ 59 - 0
Terminal.Gui/Views/StatusItem.cs

@@ -0,0 +1,59 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     <see cref="StatusItem"/> objects are contained by <see cref="StatusBar"/> <see cref="View"/>s. Each
+///     <see cref="StatusItem"/> has a title, a shortcut (hotkey), and an <see cref="Command"/> that will be invoked when
+///     the <see cref="StatusItem.Shortcut"/> is pressed. The <see cref="StatusItem.Shortcut"/> will be a global hotkey for
+///     the application in the current context of the screen. The color of the <see cref="StatusItem.Title"/> will be
+///     changed after each ~. A <see cref="StatusItem.Title"/> set to `~F1~ Help` will render as *F1* using
+///     <see cref="ColorScheme.HotNormal"/> and *Help* as <see cref="ColorScheme.HotNormal"/>.
+/// </summary>
+public class StatusItem
+{
+    /// <summary>Initializes a new <see cref="StatusItem"/>.</summary>
+    /// <param name="shortcut">Shortcut to activate the <see cref="StatusItem"/>.</param>
+    /// <param name="title">Title for the <see cref="StatusItem"/>.</param>
+    /// <param name="action">Action to invoke when the <see cref="StatusItem"/> is activated.</param>
+    /// <param name="canExecute">Function to determine if the action can currently be executed.</param>
+    public StatusItem (Key shortcut, string title, Action action, Func<bool> canExecute = null)
+    {
+        Title = title ?? "";
+        Shortcut = shortcut;
+        Action = action;
+        CanExecute = canExecute;
+    }
+
+    /// <summary>Gets or sets the action to be invoked when the <see cref="StatusItem"/> is triggered</summary>
+    /// <value>Action to invoke.</value>
+    public Action Action { get; set; }
+
+    /// <summary>
+    ///     Gets or sets the action to be invoked to determine if the <see cref="StatusItem"/> can be triggered. If
+    ///     <see cref="CanExecute"/> returns <see langword="true"/> the status item will be enabled. Otherwise, it will be
+    ///     disabled.
+    /// </summary>
+    /// <value>Function to determine if the action is can be executed or not.</value>
+    public Func<bool> CanExecute { get; set; }
+
+    /// <summary>Gets or sets arbitrary data for the status item.</summary>
+    /// <remarks>This property is not used internally.</remarks>
+    public object Data { get; set; }
+
+    /// <summary>Gets the global shortcut to invoke the action on the menu.</summary>
+    public Key Shortcut { get; set; }
+
+    /// <summary>Gets or sets the title.</summary>
+    /// <value>The title.</value>
+    /// <remarks>
+    ///     The colour of the <see cref="StatusItem.Title"/> will be changed after each ~. A
+    ///     <see cref="StatusItem.Title"/> set to `~F1~ Help` will render as *F1* using <see cref="ColorScheme.HotNormal"/> and
+    ///     *Help* as <see cref="ColorScheme.HotNormal"/>.
+    /// </remarks>
+    public string Title { get; set; }
+
+    /// <summary>
+    ///     Returns <see langword="true"/> if the status item is enabled. This method is a wrapper around
+    ///     <see cref="CanExecute"/>.
+    /// </summary>
+    public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
+}

+ 8 - 5
Terminal.Gui/Views/Toplevel.cs

@@ -1,5 +1,3 @@
-using System.Net.Mime;
-
 namespace Terminal.Gui;
 
 /// <summary>
@@ -108,7 +106,7 @@ public partial class Toplevel : View
                    );
 
         // Default keybindings for this view
-        KeyBindings.Add (Application.QuitKey, Command.QuitToplevel);
+        KeyBindings.Add (Application.QuitKey, KeyBindingScope.Application, Command.QuitToplevel);
 
         KeyBindings.Add (Key.CursorRight, Command.NextView);
         KeyBindings.Add (Key.CursorDown, Command.NextView);
@@ -120,12 +118,17 @@ public partial class Toplevel : View
         KeyBindings.Add (Key.Tab.WithCtrl, Command.NextViewOrTop);
         KeyBindings.Add (Key.Tab.WithShift.WithCtrl, Command.PreviousViewOrTop);
 
-        KeyBindings.Add (Key.F5, Command.Refresh);
+        // TODO: Refresh Key should be configurable
+        KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
         KeyBindings.Add (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix
         KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
 
+        if (Environment.OSVersion.Platform == PlatformID.Unix)
+        {
+            KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
+        }
+
 #if UNIX_KEY_BINDINGS
-        KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
         KeyBindings.Add (Key.L.WithCtrl, Command.Refresh); // Unix
         KeyBindings.Add (Key.F.WithCtrl, Command.NextView); // Unix
         KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix

+ 4 - 3
UICatalog/KeyBindingsDialog.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using Terminal.Gui;
 
@@ -10,7 +11,7 @@ internal class KeyBindingsDialog : Dialog
     // TODO: Update to use Key instead of KeyCode
     private static readonly Dictionary<Command, KeyCode> CurrentBindings = new ();
 
-    private readonly Command [] _commands;
+    private readonly ObservableCollection<Command> _commands;
     private readonly ListView _commandsListView;
     private readonly Label _keyLabel;
 
@@ -26,13 +27,13 @@ internal class KeyBindingsDialog : Dialog
         }
 
         // known commands that views can support
-        _commands = Enum.GetValues (typeof (Command)).Cast<Command> ().ToArray ();
+        _commands = new (Enum.GetValues (typeof (Command)).Cast<Command> ().ToArray ());
 
         _commandsListView = new ListView
         {
             Width = Dim.Percent (50),
             Height = Dim.Percent (100) - 1,
-            Source = new ListWrapper (_commands),
+            Source = new ListWrapper<Command> (_commands),
             SelectedItem = 0
         };
 

+ 21 - 18
UICatalog/Scenario.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using Terminal.Gui;
 
@@ -97,7 +98,7 @@ public class Scenario : IDisposable
     ///     <see cref="ScenarioMetadata.Name"/>.
     ///     https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
     /// </summary>
-    public static List<Scenario> GetScenarios ()
+    public static ObservableCollection<Scenario> GetScenarios ()
     {
         List<Scenario> objects = new ();
 
@@ -113,7 +114,7 @@ public class Scenario : IDisposable
             _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
         }
 
-        return objects.OrderBy (s => s.GetName ()).ToList ();
+        return new (objects.OrderBy (s => s.GetName ()).ToList ());
     }
 
     /// <summary>
@@ -239,24 +240,26 @@ public class Scenario : IDisposable
     #endregion IDispose
 
     /// <summary>Returns a list of all Categories set by all of the <see cref="Scenario"/>s defined in the project.</summary>
-    internal static List<string> GetAllCategories ()
+    internal static ObservableCollection<string> GetAllCategories ()
     {
-        List<string> categories = new ();
-
-        categories = typeof (Scenario).Assembly.GetTypes ()
-                                      .Where (
-                                              myType => myType.IsClass
-                                                        && !myType.IsAbstract
-                                                        && myType.IsSubclassOf (typeof (Scenario)))
-                                      .Select (type => System.Attribute.GetCustomAttributes (type).ToList ())
-                                      .Aggregate (
-                                                  categories,
-                                                  (current, attrs) => current
-                                                                      .Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name))
-                                                                      .ToList ());
+        List<string> aCategories = [];
+
+        aCategories = typeof (Scenario).Assembly.GetTypes ()
+                                       .Where (
+                                               myType => myType.IsClass
+                                                         && !myType.IsAbstract
+                                                         && myType.IsSubclassOf (typeof (Scenario)))
+                                       .Select (type => System.Attribute.GetCustomAttributes (type).ToList ())
+                                       .Aggregate (
+                                                   aCategories,
+                                                   (current, attrs) => current
+                                                                       .Union (
+                                                                               attrs.Where (a => a is ScenarioCategory)
+                                                                                    .Select (a => ((ScenarioCategory)a).Name))
+                                                                       .ToList ());
 
         // Sort
-        categories = categories.OrderBy (c => c).ToList ();
+        ObservableCollection<string> categories = new (aCategories.OrderBy (c => c).ToList ());
 
         // Put "All" at the top
         categories.Insert (0, "All Scenarios");
@@ -264,7 +267,7 @@ public class Scenario : IDisposable
         return categories;
     }
 
-    /// <summary>Defines the category names used to catagorize a <see cref="Scenario"/></summary>
+    /// <summary>Defines the category names used to categorize a <see cref="Scenario"/></summary>
     [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)]
     public class ScenarioCategory (string Name) : System.Attribute
     {

+ 4 - 3
UICatalog/Scenarios/AllViewsTester.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Diagnostics;
 using System.Linq;
 using System.Reflection;
@@ -81,7 +82,7 @@ public class AllViewsTester : Scenario
             AllowsMarking = false,
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
             SelectedItem = 0,
-            Source = new ListWrapper (_viewClasses.Keys.ToList ())
+            Source = new ListWrapper<string> (new (_viewClasses.Keys.ToList ()))
         };
         _classListView.OpenSelectedItem += (s, a) => { _settingsPane.SetFocus (); };
 
@@ -386,8 +387,8 @@ public class AllViewsTester : Scenario
         // If the view supports a Source property, set it so we have something to look at
         if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource))
         {
-            var source = new ListWrapper (new List<string> { "Test Text #1", "Test Text #2", "Test Text #3" });
-            view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
+            var source = new ListWrapper<string> (["Test Text #1", "Test Text #2", "Test Text #3"]);
+            view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, [source]);
         }
 
         // If the view supports a Title property, set it so we have something to look at

+ 5 - 4
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Diagnostics;
 using System.Threading;
@@ -247,7 +248,7 @@ public class BackgroundWorkerCollection : Scenario
         private readonly ListView _listView;
         private readonly Button _start;
 
-        public StagingUIController (Staging staging, List<string> list) : this ()
+        public StagingUIController (Staging staging, ObservableCollection<string> list) : this ()
         {
             Staging = staging;
             _label.Text = "Work list:";
@@ -335,7 +336,7 @@ public class BackgroundWorkerCollection : Scenario
     private class WorkerApp : Toplevel
     {
         private readonly ListView _listLog;
-        private readonly List<string> _log = [];
+        private readonly ObservableCollection<string> _log = [];
         private List<StagingUIController> _stagingsUi;
         private Dictionary<Staging, BackgroundWorker> _stagingWorkers;
 
@@ -357,7 +358,7 @@ public class BackgroundWorkerCollection : Scenario
                 Y = 0,
                 Width = Dim.Fill (),
                 Height = Dim.Fill (),
-                Source = new ListWrapper (_log)
+                Source = new ListWrapper<string> (_log)
             };
             Add (_listLog);
 
@@ -464,7 +465,7 @@ public class BackgroundWorkerCollection : Scenario
                                                           );
                                                  Application.Refresh ();
 
-                                                 var stagingUI = new StagingUIController (staging, e.Result as List<string>)
+                                                 var stagingUI = new StagingUIController (staging, e.Result as ObservableCollection<string>)
                                                  {
                                                      Modal = false,
                                                      Title =

+ 0 - 489
UICatalog/Scenarios/Bars.cs

@@ -1,489 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Terminal.Gui;
-
-namespace UICatalog.Scenarios;
-
-[ScenarioMetadata ("Bars", "Illustrates Bar views (e.g. StatusBar)")]
-[ScenarioCategory ("Controls")]
-public class Bars : Scenario
-{
-    public override void Main ()
-    {
-        Application.Init ();
-        Window app = new ();
-
-        app.Loaded += App_Loaded;
-
-        Application.Run (app);
-        app.Dispose ();
-        Application.Shutdown ();
-    }
-
-
-    // Setting everything up in Loaded handler because we change the
-    // QuitKey and it only sticks if changed after init
-    private void App_Loaded (object sender, EventArgs e)
-    {
-        Application.QuitKey = Key.Z.WithCtrl;
-        Application.Top.Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}";
-
-        List<string> eventSource = new ();
-        ListView eventLog = new ListView ()
-        {
-            X = Pos.AnchorEnd (),
-            Width = 50,
-            Height = Dim.Fill (),
-            ColorScheme = Colors.ColorSchemes ["Toplevel"],
-            Source = new ListWrapper (eventSource)
-        };
-        Application.Top.Add (eventLog);
-
-        var shortcut1 = new Shortcut
-        {
-            Title = "_Zigzag",
-            Key = Key.Z.WithAlt,
-            Text = "Gonna zig zag",
-            KeyBindingScope = KeyBindingScope.HotKey,
-            Command = Command.Accept,
-        };
-        shortcut1.Accept += (s, e) =>
-                            {
-                                eventSource.Add ($"Accept: {s}");
-                                eventLog.MoveDown ();
-                            };
-        Application.Top.Add (shortcut1);
-        shortcut1.SetFocus ();
-
-        //var shortcut2 = new Shortcut
-        //{
-        //    Title = "Za_G",
-        //    Text = "Gonna zag",
-        //    Key = Key.G.WithAlt,
-        //    KeyBindingScope = KeyBindingScope.HotKey,
-        //    Command = Command.Accept,
-        //    X = Pos.Left (shortcut1),
-        //    Y = Pos.Bottom (shortcut1),
-        //    //Width = 50,
-        //};
-
-
-        //var shortcut3 = new Shortcut
-        //{
-        //    Title = "Shortcut3",
-        //    Key = Key.D3.WithCtrl,
-        //    Text = "Number Three",
-        //    KeyBindingScope = KeyBindingScope.Application,
-        //    Command = Command.Accept,
-        //};
-
-        //shortcut3.Accept += (s, e) =>
-        //                    {
-        //                        eventSource.Add ($"Accept: {s}");
-        //                        eventLog.MoveDown ();
-        //                    };
-
-        //var shortcut4 = new Shortcut
-        //{
-        //    Title = "Shortcut4",
-        //    Text = "Number 4",
-        //    Key = Key.F4,
-        //    KeyBindingScope = KeyBindingScope.Application,
-        //    Command = Command.Accept,
-        //};
-
-        //var cb = new CheckBox ()
-        //{
-        //    Title = "Hello",// shortcut4.Text
-        //};
-
-        //cb.Toggled += (s, e) =>
-        //             {
-        //                 eventSource.Add ($"Toggled: {s}");
-        //                 eventLog.MoveDown ();
-        //             };
-
-        //shortcut4.CommandView = cb;
-
-        //shortcut4.Accept += (s, e) =>
-        //                    {
-        //                        eventSource.Add ($"Accept: {s}");
-        //                        eventLog.MoveDown ();
-        //                    };
-
-        //var bar = new Bar
-        //{
-        //    X = 2,
-        //    Y = Pos.Bottom(shortcut1),
-        //    Orientation = Orientation.Vertical,
-        //    StatusBarStyle = false,
-        //    Width = Dim.Percent(40)
-        //};
-        //bar.Add (shortcut3, shortcut4);
-
-        ////CheckBox hello = new ()
-        ////{
-        ////    Title = "Hello",
-        ////    X = 0,
-        ////    Y = 1,
-        ////};
-        ////Application.Top.Add (hello);
-        ////hello.Toggled += (s, e) =>
-        ////                 {
-        ////                     eventSource.Add ($"Toggled: {s}");
-        ////                     eventLog.MoveDown ();
-        ////                 };
-
-        //Application.Top.Add (bar);
-
-        // BUGBUG: This should not be needed
-        //Application.Top.LayoutSubviews ();
-
-        //SetupMenuBar ();
-        //SetupContentMenu ();
-       // SetupStatusBar ();
-    }
-
-    private void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); }
-
-    //private void SetupContentMenu ()
-    //{
-    //    Application.Top.Add (new Label { Text = "Right Click for Context Menu", X = Pos.Center (), Y = 4 });
-    //    Application.Top.MouseClick += ShowContextMenu;
-    //}
-
-    //private void ShowContextMenu (object s, MouseEventEventArgs e)
-    //{
-    //    if (e.MouseEvent.Flags != MouseFlags.Button3Clicked)
-    //    {
-    //        return;
-    //    }
-
-    //    var contextMenu = new Bar
-    //    {
-    //        Id = "contextMenu",
-    //        X = e.MouseEvent.Position.X,
-    //        Y = e.MouseEvent.Position.Y,
-    //        Width = Dim.Auto (DimAutoStyle.Content),
-    //        Height = Dim.Auto (DimAutoStyle.Content),
-    //        Orientation = Orientation.Vertical,
-    //        StatusBarStyle = false,
-    //        BorderStyle = LineStyle.Rounded,
-    //        Modal = true,
-    //    };
-
-    //    var newMenu = new Shortcut
-    //    {
-    //        Title = "_New...",
-    //        Text = "Create a new file",
-    //        Key = Key.N.WithCtrl,
-    //        CanFocus = true
-    //    };
-
-    //    newMenu.Accept += (s, e) =>
-    //                      {
-    //                          contextMenu.RequestStop ();
-
-    //                          Application.AddTimeout (
-    //                                                  new TimeSpan (0),
-    //                                                  () =>
-    //                                                  {
-    //                                                      MessageBox.Query ("File", "New");
-
-    //                                                      return false;
-    //                                                  });
-    //                      };
-
-    //    var open = new Shortcut
-    //    {
-    //        Title = "_Open...",
-    //        Text = "Show the File Open Dialog",
-    //        Key = Key.O.WithCtrl,
-    //        CanFocus = true
-    //    };
-
-    //    open.Accept += (s, e) =>
-    //                   {
-    //                       contextMenu.RequestStop ();
-
-    //                       Application.AddTimeout (
-    //                                               new TimeSpan (0),
-    //                                               () =>
-    //                                               {
-    //                                                   MessageBox.Query ("File", "Open");
-
-    //                                                   return false;
-    //                                               });
-    //                   };
-
-    //    var save = new Shortcut
-    //    {
-    //        Title = "_Save...",
-    //        Text = "Save",
-    //        Key = Key.S.WithCtrl,
-    //        CanFocus = true
-    //    };
-
-    //    save.Accept += (s, e) =>
-    //                   {
-    //                       contextMenu.RequestStop ();
-
-    //                       Application.AddTimeout (
-    //                                               new TimeSpan (0),
-    //                                               () =>
-    //                                               {
-    //                                                   MessageBox.Query ("File", "Save");
-
-    //                                                   return false;
-    //                                               });
-    //                   };
-
-    //    var saveAs = new Shortcut
-    //    {
-    //        Title = "Save _As...",
-    //        Text = "Save As",
-    //        Key = Key.A.WithCtrl,
-    //        CanFocus = true
-    //    };
-
-    //    saveAs.Accept += (s, e) =>
-    //                     {
-    //                         contextMenu.RequestStop ();
-
-    //                         Application.AddTimeout (
-    //                                                 new TimeSpan (0),
-    //                                                 () =>
-    //                                                 {
-    //                                                     MessageBox.Query ("File", "Save As");
-
-    //                                                     return false;
-    //                                                 });
-    //                     };
-
-    //    contextMenu.Add (newMenu, open, save, saveAs);
-
-    //    contextMenu.KeyBindings.Add (Key.Esc, Command.QuitToplevel);
-
-    //    contextMenu.Initialized += Menu_Initialized;
-
-    //    void Application_MouseEvent (object sender, MouseEvent e)
-    //    {
-    //        // If user clicks outside of the menuWindow, close it
-    //        if (!contextMenu.Frame.Contains (e.Position.X, e.Position.Y))
-    //        {
-    //            if (e.Flags is (MouseFlags.Button1Clicked or MouseFlags.Button3Clicked))
-    //            {
-    //                contextMenu.RequestStop ();
-    //            }
-    //        }
-    //    }
-
-    //    Application.MouseEvent += Application_MouseEvent;
-
-    //    Application.Run (contextMenu);
-    //    contextMenu.Dispose ();
-
-    //    Application.MouseEvent -= Application_MouseEvent;
-    //}
-
-    private void Menu_Initialized (object sender, EventArgs e)
-    {
-        // BUGBUG: this should not be needed    
-
-        ((View)(sender)).LayoutSubviews ();
-    }
-
-    //private void SetupMenuBar ()
-    //{
-    //    var menuBar = new Bar
-    //    {
-    //        Id = "menuBar",
-
-    //        X = 0,
-    //        Y = 0,
-    //        Width = Dim.Fill (),
-    //        Height = Dim.Auto (DimAutoStyle.Content),
-    //        StatusBarStyle = true
-    //    };
-
-    //    var fileMenu = new Shortcut
-    //    {
-    //        Title = "_File",
-    //        Key = Key.F.WithAlt,
-    //        KeyBindingScope = KeyBindingScope.HotKey,
-    //        Command = Command.Accept,
-    //    };
-    //    fileMenu.HelpView.Visible = false;
-    //    fileMenu.KeyView.Visible = false;
-
-    //    fileMenu.Accept += (s, e) =>
-    //                       {
-    //                           fileMenu.SetFocus ();
-
-    //                           if (s is View view)
-    //                           {
-    //                               var menu = new Bar
-    //                               {
-    //                                   X = view.Frame.X + 1,
-    //                                   Y = view.Frame.Y + 1,
-    //                                   ColorScheme = view.ColorScheme,
-    //                                   Orientation = Orientation.Vertical,
-    //                                   StatusBarStyle = false,
-    //                                   BorderStyle = LineStyle.Dotted,
-    //                                   Width = Dim.Auto (DimAutoStyle.Content),
-    //                                   Height = Dim.Auto (DimAutoStyle.Content),
-    //                               };
-
-    //                               menu.KeyBindings.Add (Key.Esc, Command.QuitToplevel);
-
-    //                               var newMenu = new Shortcut
-    //                               {
-    //                                   Title = "_New...",
-    //                                   Text = "Create a new file",
-    //                                   Key = Key.N.WithCtrl
-    //                               };
-
-    //                               var open = new Shortcut
-    //                               {
-    //                                   Title = "_Open...",
-    //                                   Text = "Show the File Open Dialog",
-    //                                   Key = Key.O.WithCtrl
-    //                               };
-
-    //                               var save = new Shortcut
-    //                               {
-    //                                   Title = "_Save...",
-    //                                   Text = "Save",
-    //                                   Key = Key.S.WithCtrl
-    //                               };
-
-    //                               menu.Add (newMenu, open, save);
-
-    //                               // BUGBUG: this is all bad
-    //                               menu.Initialized += Menu_Initialized;
-    //                               open.Initialized += Menu_Initialized;
-    //                               save.Initialized += Menu_Initialized;
-    //                               newMenu.Initialized += Menu_Initialized;
-
-    //                               Application.Run (menu);
-    //                               menu.Dispose ();
-    //                               Application.Refresh ();
-    //                           }
-    //                       };
-
-    //    var editMenu = new Shortcut
-    //    {
-    //        Title = "_Edit",
-
-    //        //Key = Key.E.WithAlt,
-    //        KeyBindingScope = KeyBindingScope.HotKey,
-    //        Command = Command.Accept
-    //    };
-
-    //    editMenu.Accept += (s, e) => { };
-    //    editMenu.HelpView.Visible = false;
-    //    editMenu.KeyView.Visible = false;
-
-    //    menuBar.Add (fileMenu, editMenu);
-
-    //    menuBar.Initialized += Menu_Initialized;
-
-    //    Application.Top.Add (menuBar);
-    //}
-
-    private void SetupStatusBar ()
-    {
-        var statusBar = new Bar
-        {
-            Id = "statusBar",
-            X = 0,
-            Y = Pos.AnchorEnd (),
-            Width = Dim.Fill (),
-        };
-
-        var shortcut = new Shortcut
-        {
-            Text = "Quit Application",
-            Title = "Q_uit",
-            Key = Application.QuitKey,
-            KeyBindingScope = KeyBindingScope.Application,
-            Command = Command.QuitToplevel,
-            CanFocus = false
-        };
-
-        statusBar.Add (shortcut);
-
-        shortcut = new Shortcut
-        {
-            Text = "Help Text",
-            Title = "Help",
-            Key = Key.F1,
-            KeyBindingScope = KeyBindingScope.HotKey,
-            Command = Command.Accept,
-            CanFocus = false
-        };
-
-        var labelHelp = new Label
-        {
-            X = Pos.Center (),
-            Y = Pos.Top (statusBar) - 1,
-            Text = "Help"
-        };
-        Application.Top.Add (labelHelp);
-
-        shortcut.Accept += (s, e) =>
-                           {
-                               labelHelp.Text = labelHelp.Text + "!";
-                               e.Handled = true;
-                           };
-
-        statusBar.Add (shortcut);
-
-        shortcut = new Shortcut
-        {
-            Title = "_Show/Hide",
-            Key = Key.F10,
-            KeyBindingScope = KeyBindingScope.HotKey,
-            Command = Command.ToggleExpandCollapse,
-            CommandView = new CheckBox
-            {
-                Text = "_Show/Hide"
-            },
-            CanFocus = false
-        };
-
-        statusBar.Add (shortcut);
-
-        var button1 = new Button
-        {
-            Text = "I'll Hide",
-            Visible = false
-        };
-        button1.Accept += Button_Clicked;
-        statusBar.Add (button1);
-
-        ((CheckBox)shortcut.CommandView).Toggled += (s, e) =>
-                                                    {
-                                                        button1.Visible = !button1.Visible;
-                                                        button1.Enabled = button1.Visible;
-                                                    };
-
-        statusBar.Add (new Label { HotKeySpecifier = new Rune ('_'), Text = "Fo_cusLabel", CanFocus = true });
-
-        var button2 = new Button
-        {
-            Text = "Or me!",
-        };
-        button2.Accept += (s, e) => Application.RequestStop ();
-
-        statusBar.Add (button2);
-
-        statusBar.Initialized += Menu_Initialized;
-
-        Application.Top.Add (statusBar);
-
-
-    }
-
-}

+ 4 - 3
UICatalog/Scenarios/CollectionNavigatorTester.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using Terminal.Gui;
 
@@ -16,7 +17,7 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Mouse and Keyboard")]
 public class CollectionNavigatorTester : Scenario
 {
-    private readonly List<string> _items = new []
+    private ObservableCollection<string> _items = new ObservableCollection<string> (new ObservableCollection<string> ()
     {
         "a",
         "b",
@@ -71,7 +72,7 @@ public class CollectionNavigatorTester : Scenario
         "q",
         "quit",
         "quitter"
-    }.ToList ();
+    }.ToList ());
 
     private ListView _listView;
     private TreeView _treeView;
@@ -129,7 +130,7 @@ public class CollectionNavigatorTester : Scenario
 
         Top.Add (menu);
 
-        _items.Sort (StringComparer.OrdinalIgnoreCase);
+        _items = new (_items.OrderBy (i => i, StringComparer.OrdinalIgnoreCase));
 
         CreateListView ();
         var vsep = new LineView (Orientation.Vertical) { X = Pos.Right (_listView), Y = 1, Height = Dim.Fill () };

+ 5 - 4
UICatalog/Scenarios/ComboBoxIteration.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
@@ -10,14 +11,14 @@ public class ComboBoxIteration : Scenario
 {
     public override void Setup ()
     {
-        List<string> items = new () { "one", "two", "three" };
+        ObservableCollection<string> items = ["one", "two", "three"];
 
         var lbListView = new Label { Width = 10, Height = 1 };
         Win.Add (lbListView);
 
         var listview = new ListView
         {
-            Y = Pos.Bottom (lbListView) + 1, Width = 10, Height = Dim.Fill (2), Source = new ListWrapper (items)
+            Y = Pos.Bottom (lbListView) + 1, Width = 10, Height = Dim.Fill (2), Source = new ListWrapper<string> (items)
         };
         Win.Add (listview);
 
@@ -59,7 +60,7 @@ public class ComboBoxIteration : Scenario
 
         btnTwo.Accept += (s, e) =>
                           {
-                              items = new List<string> { "one", "two" };
+                              items = ["one", "two"];
                               comboBox.SetSource (items);
                               listview.SetSource (items);
                               listview.SelectedItem = 0;
@@ -70,7 +71,7 @@ public class ComboBoxIteration : Scenario
 
         btnThree.Accept += (s, e) =>
                             {
-                                items = new List<string> { "one", "two", "three" };
+                                items =["one", "two", "three"];
                                 comboBox.SetSource (items);
                                 listview.SetSource (items);
                                 listview.SelectedItem = 0;

+ 173 - 157
UICatalog/Scenarios/ContextMenus.cs

@@ -17,72 +17,88 @@ public class ContextMenus : Scenario
     private TextField _tfTopLeft, _tfTopRight, _tfMiddle, _tfBottomLeft, _tfBottomRight;
     private bool _useSubMenusSingleFrame;
 
-    public override void Setup ()
+    public override void Main ()
     {
+        // Init
+        Application.Init ();
+
+        // Setup - Create a top-level application window and configure it.
+        Window appWindow = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"
+        };
+
         var text = "Context Menu";
         var width = 20;
-        KeyCode winContextMenuKey = (KeyCode)Key.Space.WithCtrl;
+        var winContextMenuKey = (KeyCode)Key.Space.WithCtrl;
 
         var label = new Label
         {
             X = Pos.Center (), Y = 1, Text = $"Press '{winContextMenuKey}' to open the Window context menu."
         };
-        Win.Add (label);
+        appWindow.Add (label);
 
-        label = new Label
+        label = new()
         {
             X = Pos.Center (),
             Y = Pos.Bottom (label),
             Text = $"Press '{ContextMenu.DefaultKey}' to open the TextField context menu."
         };
-        Win.Add (label);
+        appWindow.Add (label);
 
-        _tfTopLeft = new TextField { Width = width, Text = text };
-        Win.Add (_tfTopLeft);
+        _tfTopLeft = new() { Width = width, Text = text };
+        appWindow.Add (_tfTopLeft);
 
-        _tfTopRight = new TextField { X = Pos.AnchorEnd (width), Width = width, Text = text };
-        Win.Add (_tfTopRight);
+        _tfTopRight = new() { X = Pos.AnchorEnd (width), Width = width, Text = text };
+        appWindow.Add (_tfTopRight);
 
-        _tfMiddle = new TextField { X = Pos.Center (), Y = Pos.Center (), Width = width, Text = text };
-        Win.Add (_tfMiddle);
+        _tfMiddle = new() { X = Pos.Center (), Y = Pos.Center (), Width = width, Text = text };
+        appWindow.Add (_tfMiddle);
 
-        _tfBottomLeft = new TextField { Y = Pos.AnchorEnd (1), Width = width, Text = text };
-        Win.Add (_tfBottomLeft);
+        _tfBottomLeft = new() { Y = Pos.AnchorEnd (1), Width = width, Text = text };
+        appWindow.Add (_tfBottomLeft);
 
-        _tfBottomRight = new TextField { X = Pos.AnchorEnd (width), Y = Pos.AnchorEnd (1), Width = width, Text = text };
-        Win.Add (_tfBottomRight);
+        _tfBottomRight = new() { X = Pos.AnchorEnd (width), Y = Pos.AnchorEnd (1), Width = width, Text = text };
+        appWindow.Add (_tfBottomRight);
 
         Point mousePos = default;
 
-        Win.KeyDown += (s, e) =>
-                       {
-                           if (e.KeyCode == winContextMenuKey)
-                           {
-                               ShowContextMenu (mousePos.X, mousePos.Y);
-                               e.Handled = true;
-                           }
-                       };
-
-        Win.MouseClick += (s, e) =>
-                          {
-                              if (e.MouseEvent.Flags == _contextMenu.MouseFlags)
-                              {
-                                  ShowContextMenu (e.MouseEvent.Position.X, e.MouseEvent.Position.Y);
-                                  e.Handled = true;
-                              }
-                          };
+        appWindow.KeyDown += (s, e) =>
+                             {
+                                 if (e.KeyCode == winContextMenuKey)
+                                 {
+                                     ShowContextMenu (mousePos.X, mousePos.Y);
+                                     e.Handled = true;
+                                 }
+                             };
+
+        appWindow.MouseClick += (s, e) =>
+                                {
+                                    if (e.MouseEvent.Flags == _contextMenu.MouseFlags)
+                                    {
+                                        ShowContextMenu (e.MouseEvent.Position.X, e.MouseEvent.Position.Y);
+                                        e.Handled = true;
+                                    }
+                                };
 
         Application.MouseEvent += ApplicationMouseEvent;
 
         void ApplicationMouseEvent (object sender, MouseEvent a) { mousePos = a.Position; }
 
-        Win.WantMousePositionReports = true;
+        appWindow.WantMousePositionReports = true;
 
-        Top.Closed += (s, e) =>
-                                  {
-                                      Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US");
-                                      Application.MouseEvent -= ApplicationMouseEvent;
-                                  };
+        appWindow.Closed += (s, e) =>
+                            {
+                                Thread.CurrentThread.CurrentUICulture = new ("en-US");
+                                Application.MouseEvent -= ApplicationMouseEvent;
+                            };
+
+        // Run - Start the application.
+        Application.Run (appWindow);
+        appWindow.Dispose ();
+
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
     }
 
     private MenuItem [] GetSupportedCultures ()
@@ -102,7 +118,7 @@ public class ContextMenus : Scenario
                 CreateAction (supportedCultures, culture);
                 supportedCultures.Add (culture);
                 index++;
-                culture = new MenuItem { CheckType = MenuItemCheckStyle.Checked };
+                culture = new() { CheckType = MenuItemCheckStyle.Checked };
             }
 
             culture.Title = $"_{c.Parent.EnglishName}";
@@ -118,7 +134,7 @@ public class ContextMenus : Scenario
         {
             culture.Action += () =>
                               {
-                                  Thread.CurrentThread.CurrentUICulture = new CultureInfo (culture.Help);
+                                  Thread.CurrentThread.CurrentUICulture = new (culture.Help);
                                   culture.Checked = true;
 
                                   foreach (MenuItem item in supportedCultures)
@@ -131,126 +147,126 @@ public class ContextMenus : Scenario
 
     private void ShowContextMenu (int x, int y)
     {
-        _contextMenu = new ContextMenu
+        _contextMenu = new()
         {
-            Position = new Point (x, y),
-            MenuItems = new MenuBarItem (
-                                         new []
-                                         {
-                                             new (
-                                                  "_Configuration",
-                                                  "Show configuration",
-                                                  () => MessageBox.Query (
-                                                                          50,
-                                                                          5,
-                                                                          "Info",
-                                                                          "This would open settings dialog",
-                                                                          "Ok"
-                                                                         )
-                                                 ),
-                                             new MenuBarItem (
-                                                              "More options",
-                                                              new MenuItem []
-                                                              {
-                                                                  new (
-                                                                       "_Setup",
-                                                                       "Change settings",
-                                                                       () => MessageBox
-                                                                           .Query (
-                                                                                   50,
-                                                                                   5,
-                                                                                   "Info",
-                                                                                   "This would open setup dialog",
-                                                                                   "Ok"
-                                                                                  ),
-                                                                       shortcut: KeyCode.T
-                                                                                 | KeyCode
-                                                                                     .CtrlMask
+            Position = new (x, y),
+            MenuItems = new (
+                             new []
+                             {
+                                 new (
+                                      "_Configuration",
+                                      "Show configuration",
+                                      () => MessageBox.Query (
+                                                              50,
+                                                              5,
+                                                              "Info",
+                                                              "This would open settings dialog",
+                                                              "Ok"
+                                                             )
+                                     ),
+                                 new MenuBarItem (
+                                                  "More options",
+                                                  new MenuItem []
+                                                  {
+                                                      new (
+                                                           "_Setup",
+                                                           "Change settings",
+                                                           () => MessageBox
+                                                               .Query (
+                                                                       50,
+                                                                       5,
+                                                                       "Info",
+                                                                       "This would open setup dialog",
+                                                                       "Ok"
                                                                       ),
-                                                                  new (
-                                                                       "_Maintenance",
-                                                                       "Maintenance mode",
-                                                                       () => MessageBox
-                                                                           .Query (
-                                                                                   50,
-                                                                                   5,
-                                                                                   "Info",
-                                                                                   "This would open maintenance dialog",
-                                                                                   "Ok"
-                                                                                  )
+                                                           shortcut: KeyCode.T
+                                                                     | KeyCode
+                                                                         .CtrlMask
+                                                          ),
+                                                      new (
+                                                           "_Maintenance",
+                                                           "Maintenance mode",
+                                                           () => MessageBox
+                                                               .Query (
+                                                                       50,
+                                                                       5,
+                                                                       "Info",
+                                                                       "This would open maintenance dialog",
+                                                                       "Ok"
                                                                       )
-                                                              }
-                                                             ),
-                                             new MenuBarItem (
-                                                              "_Languages",
-                                                              GetSupportedCultures ()
-                                                             ),
-                                             _miForceMinimumPosToZero =
-                                                 new MenuItem (
-                                                               "ForceMinimumPosToZero",
-                                                               "",
-                                                               () =>
-                                                               {
-                                                                   _miForceMinimumPosToZero
-                                                                           .Checked =
-                                                                       _forceMinimumPosToZero =
-                                                                           !_forceMinimumPosToZero;
-
-                                                                   _tfTopLeft.ContextMenu
-                                                                             .ForceMinimumPosToZero =
-                                                                       _forceMinimumPosToZero;
-
-                                                                   _tfTopRight.ContextMenu
-                                                                              .ForceMinimumPosToZero =
-                                                                       _forceMinimumPosToZero;
-
-                                                                   _tfMiddle.ContextMenu
-                                                                            .ForceMinimumPosToZero =
-                                                                       _forceMinimumPosToZero;
-
-                                                                   _tfBottomLeft.ContextMenu
-                                                                                .ForceMinimumPosToZero =
-                                                                       _forceMinimumPosToZero;
-
-                                                                   _tfBottomRight
-                                                                           .ContextMenu
-                                                                           .ForceMinimumPosToZero =
-                                                                       _forceMinimumPosToZero;
-                                                               }
-                                                              )
-                                                 {
-                                                     CheckType =
-                                                         MenuItemCheckStyle
-                                                             .Checked,
-                                                     Checked =
-                                                         _forceMinimumPosToZero
-                                                 },
-                                             _miUseSubMenusSingleFrame =
-                                                 new MenuItem (
-                                                               "Use_SubMenusSingleFrame",
-                                                               "",
-                                                               () => _contextMenu
-                                                                             .UseSubMenusSingleFrame =
-                                                                         (bool)
-                                                                         (_miUseSubMenusSingleFrame
-                                                                                  .Checked =
-                                                                              _useSubMenusSingleFrame =
-                                                                                  !_useSubMenusSingleFrame)
-                                                              )
-                                                 {
-                                                     CheckType = MenuItemCheckStyle
-                                                         .Checked,
-                                                     Checked =
-                                                         _useSubMenusSingleFrame
-                                                 },
-                                             null,
-                                             new (
-                                                  "_Quit",
-                                                  "",
-                                                  () => Application.RequestStop ()
-                                                 )
-                                         }
-                                        ),
+                                                          )
+                                                  }
+                                                 ),
+                                 new MenuBarItem (
+                                                  "_Languages",
+                                                  GetSupportedCultures ()
+                                                 ),
+                                 _miForceMinimumPosToZero =
+                                     new (
+                                          "ForceMinimumPosToZero",
+                                          "",
+                                          () =>
+                                          {
+                                              _miForceMinimumPosToZero
+                                                      .Checked =
+                                                  _forceMinimumPosToZero =
+                                                      !_forceMinimumPosToZero;
+
+                                              _tfTopLeft.ContextMenu
+                                                        .ForceMinimumPosToZero =
+                                                  _forceMinimumPosToZero;
+
+                                              _tfTopRight.ContextMenu
+                                                         .ForceMinimumPosToZero =
+                                                  _forceMinimumPosToZero;
+
+                                              _tfMiddle.ContextMenu
+                                                       .ForceMinimumPosToZero =
+                                                  _forceMinimumPosToZero;
+
+                                              _tfBottomLeft.ContextMenu
+                                                           .ForceMinimumPosToZero =
+                                                  _forceMinimumPosToZero;
+
+                                              _tfBottomRight
+                                                      .ContextMenu
+                                                      .ForceMinimumPosToZero =
+                                                  _forceMinimumPosToZero;
+                                          }
+                                         )
+                                     {
+                                         CheckType =
+                                             MenuItemCheckStyle
+                                                 .Checked,
+                                         Checked =
+                                             _forceMinimumPosToZero
+                                     },
+                                 _miUseSubMenusSingleFrame =
+                                     new (
+                                          "Use_SubMenusSingleFrame",
+                                          "",
+                                          () => _contextMenu
+                                                        .UseSubMenusSingleFrame =
+                                                    (bool)
+                                                    (_miUseSubMenusSingleFrame
+                                                             .Checked =
+                                                         _useSubMenusSingleFrame =
+                                                             !_useSubMenusSingleFrame)
+                                         )
+                                     {
+                                         CheckType = MenuItemCheckStyle
+                                             .Checked,
+                                         Checked =
+                                             _useSubMenusSingleFrame
+                                     },
+                                 null,
+                                 new (
+                                      "_Quit",
+                                      "",
+                                      () => Application.RequestStop ()
+                                     )
+                             }
+                            ),
             ForceMinimumPosToZero = _forceMinimumPosToZero,
             UseSubMenusSingleFrame = _useSubMenusSingleFrame
         };

+ 47 - 38
UICatalog/Scenarios/DynamicMenuBar.cs

@@ -1,6 +1,5 @@
 using System;
-using System.Collections;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Reflection;
 using System.Runtime.CompilerServices;
@@ -14,15 +13,23 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Menus")]
 public class DynamicMenuBar : Scenario
 {
-    public override void Init ()
+    public override void Main ()
     {
+        // Init
         Application.Init ();
 
-        Top = new ();
+        // Setup - Create a top-level application window and configure it.
+        DynamicMenuBarSample appWindow = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"
+        };
+
+        // Run - Start the application.
+        Application.Run (appWindow);
+        appWindow.Dispose ();
 
-        Top.Add (
-                 new DynamicMenuBarSample { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" }
-                );
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
     }
 
     public class Binding
@@ -107,31 +114,31 @@ public class DynamicMenuBar : Scenario
             var _lblTitle = new Label { Y = 1, Text = "Title:" };
             Add (_lblTitle);
 
-            TextTitle = new() { X = Pos.Right (_lblTitle) + 2, Y = Pos.Top (_lblTitle), Width = Dim.Fill () };
+            TextTitle = new () { X = Pos.Right (_lblTitle) + 2, Y = Pos.Top (_lblTitle), Width = Dim.Fill () };
             Add (TextTitle);
 
             var _lblHelp = new Label { X = Pos.Left (_lblTitle), Y = Pos.Bottom (_lblTitle) + 1, Text = "Help:" };
             Add (_lblHelp);
 
-            TextHelp = new() { X = Pos.Left (TextTitle), Y = Pos.Top (_lblHelp), Width = Dim.Fill () };
+            TextHelp = new () { X = Pos.Left (TextTitle), Y = Pos.Top (_lblHelp), Width = Dim.Fill () };
             Add (TextHelp);
 
             var _lblAction = new Label { X = Pos.Left (_lblTitle), Y = Pos.Bottom (_lblHelp) + 1, Text = "Action:" };
             Add (_lblAction);
 
-            TextAction = new()
+            TextAction = new ()
             {
                 X = Pos.Left (TextTitle), Y = Pos.Top (_lblAction), Width = Dim.Fill (), Height = 5
             };
             Add (TextAction);
 
-            CkbIsTopLevel = new()
+            CkbIsTopLevel = new ()
             {
                 X = Pos.Left (_lblTitle), Y = Pos.Bottom (_lblAction) + 5, Text = "IsTopLevel"
             };
             Add (CkbIsTopLevel);
 
-            CkbSubMenu = new()
+            CkbSubMenu = new ()
             {
                 X = Pos.Left (_lblTitle),
                 Y = Pos.Bottom (CkbIsTopLevel),
@@ -140,7 +147,7 @@ public class DynamicMenuBar : Scenario
             };
             Add (CkbSubMenu);
 
-            CkbNullCheck = new()
+            CkbNullCheck = new ()
             {
                 X = Pos.Left (_lblTitle), Y = Pos.Bottom (CkbSubMenu), Text = "Allow null checked"
             };
@@ -148,7 +155,7 @@ public class DynamicMenuBar : Scenario
 
             var _rChkLabels = new [] { "NoCheck", "Checked", "Radio" };
 
-            RbChkStyle = new()
+            RbChkStyle = new ()
             {
                 X = Pos.Left (_lblTitle), Y = Pos.Bottom (CkbSubMenu) + 1, RadioLabels = _rChkLabels
             };
@@ -160,7 +167,7 @@ public class DynamicMenuBar : Scenario
             };
             Add (_lblShortcut);
 
-            TextShortcut = new()
+            TextShortcut = new ()
             {
                 X = Pos.X (_lblShortcut), Y = Pos.Bottom (_lblShortcut), Width = Dim.Fill (), ReadOnly = true
             };
@@ -440,7 +447,9 @@ public class DynamicMenuBar : Scenario
                                     TextTitle.Text = string.Empty;
                                     Application.RequestStop ();
                                 };
-            var dialog = new Dialog { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel] };
+
+            var dialog = new Dialog
+                { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel], Height = Dim.Auto (DimAutoStyle.Content, 22, Driver.Rows) };
 
             Width = Dim.Fill ();
             Height = Dim.Fill () - 1;
@@ -452,7 +461,7 @@ public class DynamicMenuBar : Scenario
 
             if (valid)
             {
-                return new()
+                return new ()
                 {
                     Title = TextTitle.Text,
                     Help = TextHelp.Text,
@@ -596,10 +605,10 @@ public class DynamicMenuBar : Scenario
             var _btnAddMenuBar = new Button { Y = 1, Text = "Add a MenuBar" };
             _frmMenu.Add (_btnAddMenuBar);
 
-            var _btnMenuBarUp = new Button { X = Pos.Center (), Text = "^" };
+            var _btnMenuBarUp = new Button { X = Pos.Center (), Text = CM.Glyphs.UpArrow.ToString () };
             _frmMenu.Add (_btnMenuBarUp);
 
-            var _btnMenuBarDown = new Button { X = Pos.Center (), Y = Pos.Bottom (_btnMenuBarUp), Text = "v" };
+            var _btnMenuBarDown = new Button { X = Pos.Center (), Y = Pos.Bottom (_btnMenuBarUp), Text = CM.Glyphs.DownArrow.ToString () };
             _frmMenu.Add (_btnMenuBarDown);
 
             var _btnRemoveMenuBar = new Button { Y = 1, Text = "Remove a MenuBar" };
@@ -609,7 +618,7 @@ public class DynamicMenuBar : Scenario
 
             var _btnPrevious = new Button
             {
-                X = Pos.Left (_btnAddMenuBar), Y = Pos.Top (_btnAddMenuBar) + 2, Text = "<"
+                X = Pos.Left (_btnAddMenuBar), Y = Pos.Top (_btnAddMenuBar) + 2, Text = CM.Glyphs.LeftArrow.ToString ()
             };
             _frmMenu.Add (_btnPrevious);
 
@@ -617,7 +626,7 @@ public class DynamicMenuBar : Scenario
             _btnAdd.X = Pos.AnchorEnd ();
             _frmMenu.Add (_btnAdd);
 
-            var _btnNext = new Button { X = Pos.X (_btnAdd), Y = Pos.Top (_btnPrevious), Text = ">" };
+            var _btnNext = new Button { X = Pos.X (_btnAdd), Y = Pos.Top (_btnPrevious), Text = CM.Glyphs.RightArrow.ToString () };
             _frmMenu.Add (_btnNext);
 
             var _lblMenuBar = new Label
@@ -650,14 +659,14 @@ public class DynamicMenuBar : Scenario
             };
             _frmMenu.Add (_btnPreviowsParent);
 
-            _lstMenus = new()
+            _lstMenus = new ()
             {
                 ColorScheme = Colors.ColorSchemes ["Dialog"],
                 X = Pos.Right (_btnPrevious) + 1,
                 Y = Pos.Top (_btnPrevious) + 2,
                 Width = _lblMenuBar.Width,
                 Height = Dim.Fill (),
-                Source = new ListWrapper (new List<DynamicMenuItemList> ())
+                Source = new ListWrapper<DynamicMenuItemList> ([])
             };
             _frmMenu.Add (_lstMenus);
 
@@ -669,10 +678,10 @@ public class DynamicMenuBar : Scenario
             var _btnRemove = new Button { X = Pos.Left (_btnAdd), Y = Pos.Top (_btnAdd) + 1, Text = "Remove" };
             _frmMenu.Add (_btnRemove);
 
-            var _btnUp = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (_btnRemove) + 2, Text = "^" };
+            var _btnUp = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (_btnRemove) + 2, Text = CM.Glyphs.UpArrow.ToString () };
             _frmMenu.Add (_btnUp);
 
-            var _btnDown = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (_btnUp) + 1, Text = "v" };
+            var _btnDown = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (_btnUp) + 1, Text = CM.Glyphs.DownArrow.ToString () };
             _frmMenu.Add (_btnDown);
 
             Add (_frmMenu);
@@ -747,7 +756,7 @@ public class DynamicMenuBar : Scenario
                                          DataContext.Menus [i] = DataContext.Menus [i - 1];
 
                                          DataContext.Menus [i - 1] =
-                                             new() { Title = menuItem.Title, MenuItem = menuItem };
+                                             new () { Title = menuItem.Title, MenuItem = menuItem };
                                          _lstMenus.SelectedItem = i - 1;
                                      }
                                  }
@@ -769,7 +778,7 @@ public class DynamicMenuBar : Scenario
                                            DataContext.Menus [i] = DataContext.Menus [i + 1];
 
                                            DataContext.Menus [i + 1] =
-                                               new() { Title = menuItem.Title, MenuItem = menuItem };
+                                               new () { Title = menuItem.Title, MenuItem = menuItem };
                                            _lstMenus.SelectedItem = i + 1;
                                        }
                                    }
@@ -857,7 +866,7 @@ public class DynamicMenuBar : Scenario
                                       return;
                                   }
 
-                                  if (!(_currentMenuBarItem is MenuBarItem))
+                                  if (_currentMenuBarItem is not MenuBarItem)
                                   {
                                       var parent = _currentMenuBarItem.Parent as MenuBarItem;
                                       int idx = parent.GetChildrenIndex (_currentMenuBarItem);
@@ -895,7 +904,7 @@ public class DynamicMenuBar : Scenario
                                           menuBarItem.Children = childrens;
                                       }
 
-                                      DataContext.Menus.Add (new() { Title = newMenu.Title, MenuItem = newMenu });
+                                      DataContext.Menus.Add (new () { Title = newMenu.Title, MenuItem = newMenu });
                                       _lstMenus.MoveDown ();
                                   }
                               };
@@ -938,7 +947,7 @@ public class DynamicMenuBar : Scenario
                                                                             _currentMenuBarItem.Help,
                                                                             _frmMenuDetails.CreateAction (
                                                                                                           _currentEditMenuBarItem,
-                                                                                                          new()
+                                                                                                          new ()
                                                                                                           {
                                                                                                               Title = _currentEditMenuBarItem
                                                                                                                   .Title
@@ -1109,7 +1118,7 @@ public class DynamicMenuBar : Scenario
             SetFrameDetails ();
 
             var ustringConverter = new UStringValueConverter ();
-            var listWrapperConverter = new ListWrapperConverter ();
+            ListWrapperConverter<DynamicMenuItemList> listWrapperConverter = new ListWrapperConverter<DynamicMenuItemList> ();
 
             var lblMenuBar = new Binding (this, "MenuBar", _lblMenuBar, "Text", ustringConverter);
             var lblParent = new Binding (this, "Parent", _lblParent, "Text", ustringConverter);
@@ -1160,7 +1169,7 @@ public class DynamicMenuBar : Scenario
 
             void SetListViewSource (MenuItem _currentMenuBarItem, bool fill = false)
             {
-                DataContext.Menus = new ();
+                DataContext.Menus = [];
                 var menuBarItem = _currentMenuBarItem as MenuBarItem;
 
                 if (menuBarItem != null && menuBarItem?.Children == null)
@@ -1298,7 +1307,7 @@ public class DynamicMenuBar : Scenario
                     if (DataContext.Menus.Count == 0)
                     {
                         DataContext.Menus.Add (
-                                               new()
+                                               new ()
                                                {
                                                    Title = _currentEditMenuBarItem.Title, MenuItem = _currentEditMenuBarItem
                                                }
@@ -1306,7 +1315,7 @@ public class DynamicMenuBar : Scenario
                     }
 
                     DataContext.Menus [index] =
-                        new()
+                        new ()
                         {
                             Title = _currentEditMenuBarItem.Title, MenuItem = _currentEditMenuBarItem
                         };
@@ -1344,7 +1353,7 @@ public class DynamicMenuBar : Scenario
     public class DynamicMenuItemModel : INotifyPropertyChanged
     {
         private string _menuBar;
-        private List<DynamicMenuItemList> _menus;
+        private ObservableCollection<DynamicMenuItemList> _menus;
         private string _parent;
         public DynamicMenuItemModel () { Menus = []; }
 
@@ -1367,7 +1376,7 @@ public class DynamicMenuBar : Scenario
             }
         }
 
-        public List<DynamicMenuItemList> Menus
+        public ObservableCollection<DynamicMenuItemList> Menus
         {
             get => _menus;
             set
@@ -1414,9 +1423,9 @@ public class DynamicMenuBar : Scenario
         object Convert (object value, object parameter = null);
     }
 
-    public class ListWrapperConverter : IValueConverter
+    public class ListWrapperConverter<T> : IValueConverter
     {
-        public object Convert (object value, object parameter = null) { return new ListWrapper ((IList)value); }
+        public object Convert (object value, object parameter = null) { return new ListWrapper<T> ((ObservableCollection<T>)value); }
     }
 
     public class UStringValueConverter : IValueConverter

+ 14 - 16
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -1,6 +1,5 @@
 using System;
-using System.Collections;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Reflection;
 using System.Runtime.CompilerServices;
@@ -284,7 +283,7 @@ public class DynamicStatusBar : Scenario
                                       TextTitle.Text = string.Empty;
                                       Application.RequestStop ();
                                   };
-            var dialog = new Dialog { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel] };
+            var dialog = new Dialog { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel], Height = Dim.Auto (DimAutoStyle.Content, 17, Driver.Rows) };
 
             Width = Dim.Fill ();
             Height = Dim.Fill () - 1;
@@ -375,7 +374,7 @@ public class DynamicStatusBar : Scenario
             _frmStatusBar.Add (_btnRemoveStatusBar);
 
             var _btnAdd = new Button { Y = Pos.Top (_btnRemoveStatusBar) + 2, Text = " Add  " };
-            _btnAdd.X = Pos.AnchorEnd (0);
+            _btnAdd.X = Pos.AnchorEnd ();
             _frmStatusBar.Add (_btnAdd);
 
             _lstItems = new ListView
@@ -384,17 +383,17 @@ public class DynamicStatusBar : Scenario
                 Y = Pos.Top (_btnAddStatusBar) + 2,
                 Width = Dim.Fill () - Dim.Width (_btnAdd) - 1,
                 Height = Dim.Fill (),
-                Source = new ListWrapper (new List<DynamicStatusItemList> ())
+                Source = new ListWrapper<DynamicStatusItemList> ([])
             };
             _frmStatusBar.Add (_lstItems);
 
             var _btnRemove = new Button { X = Pos.Left (_btnAdd), Y = Pos.Top (_btnAdd) + 1, Text = "Remove" };
             _frmStatusBar.Add (_btnRemove);
 
-            var _btnUp = new Button { X = Pos.Right (_lstItems) + 2, Y = Pos.Top (_btnRemove) + 2, Text = "^" };
+            var _btnUp = new Button { X = Pos.Right (_lstItems) + 2, Y = Pos.Top (_btnRemove) + 2, Text = CM.Glyphs.UpArrow.ToString () };
             _frmStatusBar.Add (_btnUp);
 
-            var _btnDown = new Button { X = Pos.Right (_lstItems) + 2, Y = Pos.Top (_btnUp) + 1, Text = "v" };
+            var _btnDown = new Button { X = Pos.Right (_lstItems) + 2, Y = Pos.Top (_btnUp) + 1, Text = CM.Glyphs.DownArrow.ToString () };
             _frmStatusBar.Add (_btnDown);
 
             Add (_frmStatusBar);
@@ -569,7 +568,7 @@ public class DynamicStatusBar : Scenario
 
                                                Remove (_statusBar);
                                                _statusBar = null;
-                                               DataContext.Items = new List<DynamicStatusItemList> ();
+                                               DataContext.Items = [];
                                                _currentStatusItem = null;
                                                _currentSelectedStatusBar = -1;
                                                SetListViewSource (_currentStatusItem, true);
@@ -579,7 +578,7 @@ public class DynamicStatusBar : Scenario
             SetFrameDetails ();
 
             var ustringConverter = new UStringValueConverter ();
-            var listWrapperConverter = new ListWrapperConverter ();
+            var listWrapperConverter = new ListWrapperConverter<DynamicStatusItemList> ();
 
             var lstItems = new Binding (this, "Items", _lstItems, "Source", listWrapperConverter);
 
@@ -611,7 +610,7 @@ public class DynamicStatusBar : Scenario
 
             void SetListViewSource (StatusItem _currentStatusItem, bool fill = false)
             {
-                DataContext.Items = new List<DynamicStatusItemList> ();
+                DataContext.Items = [];
                 StatusItem statusItem = _currentStatusItem;
 
                 if (!fill)
@@ -681,10 +680,9 @@ public class DynamicStatusBar : Scenario
             if (split.Length > 1)
             {
                 txt = split [2].Trim ();
-                ;
             }
 
-            if (string.IsNullOrEmpty (shortcut))
+            if (string.IsNullOrEmpty (shortcut) || shortcut == "Null")
             {
                 return txt;
             }
@@ -717,11 +715,11 @@ public class DynamicStatusBar : Scenario
 
     public class DynamicStatusItemModel : INotifyPropertyChanged
     {
-        private List<DynamicStatusItemList> _items;
+        private ObservableCollection<DynamicStatusItemList> _items;
         private string _statusBar;
         public DynamicStatusItemModel () { Items = []; }
 
-        public List<DynamicStatusItemList> Items
+        public ObservableCollection<DynamicStatusItemList> Items
         {
             get => _items;
             set
@@ -768,9 +766,9 @@ public class DynamicStatusBar : Scenario
         object Convert (object value, object parameter = null);
     }
 
-    public class ListWrapperConverter : IValueConverter
+    public class ListWrapperConverter<T> : IValueConverter
     {
-        public object Convert (object value, object parameter = null) { return new ListWrapper ((IList)value); }
+        public object Convert (object value, object parameter = null) { return new ListWrapper<T> ((ObservableCollection<T>)value); }
     }
 
     public class UStringValueConverter : IValueConverter

+ 150 - 188
UICatalog/Scenarios/Editor.cs

@@ -8,6 +8,7 @@ using System.Text;
 using System.Text.RegularExpressions;
 using System.Threading;
 using Terminal.Gui;
+using static UICatalog.Scenarios.DynamicMenuBar;
 
 namespace UICatalog.Scenarios;
 
@@ -18,8 +19,10 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Top Level Windows")]
 [ScenarioCategory ("Files and IO")]
 [ScenarioCategory ("TextView")]
+[ScenarioCategory ("Menus")]
 public class Editor : Scenario
 {
+    private Window _appWindow;
     private List<CultureInfo> _cultureInfos;
     private string _fileName = "demo.txt";
     private bool _forceMinimumPosToZero = true;
@@ -33,41 +36,36 @@ public class Editor : Scenario
     private string _textToFind;
     private string _textToReplace;
     private TextView _textView;
-    private Window _winDialog;
+    private FindReplaceWindow _findReplaceWindow;
 
-    public override void Init ()
+    public override void Main ()
     {
+        // Init
         Application.Init ();
-        _cultureInfos = Application.SupportedCultures;
-        ConfigurationManager.Themes.Theme = Theme;
-        ConfigurationManager.Apply ();
-
-        Top = new ();
 
-        Win = new()
+        // Setup - Create a top-level application window and configure it.
+        _appWindow = new ()
         {
+            //Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
             Title = _fileName ?? "Untitled",
-            X = 0,
-            Y = 1,
-            Width = Dim.Fill (),
-            Height = Dim.Fill (),
-            ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]
+            BorderStyle = LineStyle.None
         };
-        Top.Add (Win);
 
-        _textView = new()
+        _cultureInfos = Application.SupportedCultures;
+
+        _textView = new ()
         {
             X = 0,
-            Y = 0,
+            Y = 1,
             Width = Dim.Fill (),
-            Height = Dim.Fill ()
+            Height = Dim.Fill (1),
         };
 
         CreateDemoFile (_fileName);
 
         LoadFile ();
 
-        Win.Add (_textView);
+        _appWindow.Add (_textView);
 
         var menu = new MenuBar
         {
@@ -238,7 +236,7 @@ public class Editor : Scenario
             ]
         };
 
-        Top.Add (menu);
+        _appWindow.Add (menu);
 
         var siCursorPosition = new StatusItem (KeyCode.Null, "", null);
 
@@ -267,7 +265,7 @@ public class Editor : Scenario
                                                  siCursorPosition.Title = $"Ln {e.Point.Y + 1}, Col {e.Point.X + 1}";
                                              };
 
-        Top.Add (statusBar);
+        _appWindow.Add (statusBar);
 
         _scrollBar = new (_textView, true);
 
@@ -310,49 +308,19 @@ public class Editor : Scenario
                                      _scrollBar.Refresh ();
                                  };
 
-        Win.KeyDown += (s, e) =>
-                       {
-                           if (_winDialog != null && (e.KeyCode == KeyCode.Esc || e == Application.QuitKey))
-                           {
-                               DisposeWinDialog ();
-                           }
-                           else if (e == Application.QuitKey)
-                           {
-                               Quit ();
-                               e.Handled = true;
-                           }
-                           else if (_winDialog != null && e.KeyCode == (KeyCode.Tab | KeyCode.CtrlMask))
-                           {
-                               if (_tabView.SelectedTab == _tabView.Tabs.ElementAt (_tabView.Tabs.Count - 1))
-                               {
-                                   _tabView.SelectedTab = _tabView.Tabs.ElementAt (0);
-                               }
-                               else
-                               {
-                                   _tabView.SwitchTabBy (1);
-                               }
-
-                               e.Handled = true;
-                           }
-                           else if (_winDialog != null && e.KeyCode == (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask))
-                           {
-                               if (_tabView.SelectedTab == _tabView.Tabs.ElementAt (0))
-                               {
-                                   _tabView.SelectedTab = _tabView.Tabs.ElementAt (_tabView.Tabs.Count - 1);
-                               }
-                               else
-                               {
-                                   _tabView.SwitchTabBy (-1);
-                               }
-
-                               e.Handled = true;
-                           }
-                       };
 
-        Top.Closed += (s, e) => Thread.CurrentThread.CurrentUICulture = new ("en-US");
-    }
+        _appWindow.Closed += (s, e) => Thread.CurrentThread.CurrentUICulture = new ("en-US");
+
+        CreateFindReplace ();
 
-    public override void Setup () { }
+        // Run - Start the application.
+        Application.Run (_appWindow);
+        _appWindow.Dispose ();
+
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
+
+    }
 
     private bool CanCloseFile ()
     {
@@ -366,7 +334,7 @@ public class Editor : Scenario
 
         int r = MessageBox.ErrorQuery (
                                        "Save File",
-                                       $"Do you want save changes in {Win.Title}?",
+                                       $"Do you want save changes in {_appWindow.Title}?",
                                        "Yes",
                                        "No",
                                        "Cancel"
@@ -414,7 +382,7 @@ public class Editor : Scenario
 
         if (replace
             && (string.IsNullOrEmpty (_textToFind)
-                || (_winDialog == null && string.IsNullOrEmpty (_textToReplace))))
+                || (_findReplaceWindow == null && string.IsNullOrEmpty (_textToReplace))))
         {
             Replace ();
 
@@ -689,11 +657,7 @@ public class Editor : Scenario
         for (var i = 0; i < 30; i++)
         {
             sb.Append (
-                       $"{
-                           i
-                       } - This is a test with a very long line and many lines to test the ScrollViewBar against the TextView. - {
-                           i
-                       }\n"
+                       $"{i} - This is a test with a very long line and many lines to test the ScrollViewBar against the TextView. - {i}\n"
                       );
         }
 
@@ -721,38 +685,85 @@ public class Editor : Scenario
         return item;
     }
 
-    private void CreateFindReplace (bool isFind = true)
+    private class FindReplaceWindow : Window
     {
-        if (_winDialog != null)
+        private TextView _textView;
+        public FindReplaceWindow (TextView textView)
         {
-            _winDialog.SetFocus ();
-
-            return;
+            Title = "Find and Replace";
+
+            _textView = textView;
+            X = Pos.AnchorEnd () - 1;
+            Y = 2;
+            Width = 57;
+            Height = 11;
+            Arrangement = ViewArrangement.Movable;
+
+            KeyBindings.Add (Key.Esc, KeyBindingScope.Focused, Command.Cancel);
+            AddCommand (Command.Cancel, () =>
+                                        {
+                                            Visible = false;
+
+                                            return true;
+                                        });
+            VisibleChanged += FindReplaceWindow_VisibleChanged;
+            Initialized += FindReplaceWindow_Initialized;
+
+            //var btnCancel = new Button
+            //{
+            //    X = Pos.AnchorEnd (),
+            //    Y = Pos.AnchorEnd (),
+            //    Text = "Cancel"
+            //};
+            //btnCancel.Accept += (s, e) => { Visible = false; };
+            //Add (btnCancel);
         }
 
-        _winDialog = new()
+        private void FindReplaceWindow_VisibleChanged (object sender, EventArgs e)
         {
-            Title = isFind ? "Find" : "Replace",
-            X = Win.Viewport.Width / 2 - 30,
-            Y = Win.Viewport.Height / 2 - 10,
-            ColorScheme = Colors.ColorSchemes ["TopLevel"]
-        };
+            if (Visible == false)
+            {
+                _textView.SetFocus ();
+            }
+            else
+            {
+                FocusFirst();
+            }
+        }
 
-        _tabView = new() { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
+        private void FindReplaceWindow_Initialized (object sender, EventArgs e)
+        {
+            Border.LineStyle = LineStyle.Dashed;
+            Border.Thickness = new (0, 1, 0, 0);
+        }
+    }
 
-        _tabView.AddTab (new() { DisplayText = "Find", View = FindTab () }, isFind);
-        View replace = ReplaceTab ();
-        _tabView.AddTab (new() { DisplayText = "Replace", View = replace }, !isFind);
-        _tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusFirst ();
-        _winDialog.Add (_tabView);
+    private void ShowFindReplace (bool isFind = true)
+    {
+        _findReplaceWindow.Visible = true;
+        _findReplaceWindow.SuperView.BringSubviewToFront (_findReplaceWindow);
+        _tabView.SetFocus();
+        _tabView.SelectedTab = isFind ? _tabView.Tabs.ToArray () [0] : _tabView.Tabs.ToArray () [1];
+        _tabView.SelectedTab.View.FocusFirst ();
+    }
 
-        Win.Add (_winDialog);
+    private void CreateFindReplace ()
+    {
+        _findReplaceWindow = new (_textView);
+        _tabView = new ()
+        {
+            X = 0, Y = 0,
+            Width = Dim.Fill (), Height = Dim.Fill (0)
+        };
 
-        _winDialog.Width = replace.Width + 4;
-        _winDialog.Height = replace.Height + 4;
+        _tabView.AddTab (new () { DisplayText = "Find", View = CreateFindTab () }, true);
+        _tabView.AddTab (new () { DisplayText = "Replace", View = CreateReplaceTab () }, false);
+        _tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusFirst ();
+        _findReplaceWindow.Add (_tabView);
 
-        _winDialog.SuperView.BringSubviewToFront (_winDialog);
-        _winDialog.SetFocus ();
+        _tabView.SelectedTab.View.FocusLast (); // Hack to get the first tab to be focused
+        _findReplaceWindow.Visible = false;
+        _appWindow.Add (_findReplaceWindow);
     }
 
     private MenuItem [] CreateKeepChecked ()
@@ -822,34 +833,22 @@ public class Editor : Scenario
         }
     }
 
-    private void DisposeWinDialog ()
-    {
-        _winDialog.Dispose ();
-        Win.Remove (_winDialog);
-        _winDialog = null;
-    }
-
-    private void Find () { CreateFindReplace (); }
+    private void Find () { ShowFindReplace(true); }
     private void FindNext () { ContinueFind (); }
     private void FindPrevious () { ContinueFind (false); }
 
-    private View FindTab ()
+    private View CreateFindTab ()
     {
-        var d = new View ();
-
-        d.DrawContent += (s, e) =>
-                         {
-                             foreach (View v in d.Subviews)
-                             {
-                                 v.SetNeedsDisplay ();
-                             }
-                         };
+        var d = new View ()
+        {
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+        };
 
         int lblWidth = "Replace:".Length;
 
         var label = new Label
         {
-            Y = 1,
             Width = lblWidth,
             TextAlignment = Alignment.End,
 
@@ -861,18 +860,19 @@ public class Editor : Scenario
 
         var txtToFind = new TextField
         {
-            X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Text = _textToFind
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Width = Dim.Fill (1),
+            Text = _textToFind
         };
         txtToFind.Enter += (s, e) => txtToFind.Text = _textToFind;
         d.Add (txtToFind);
 
         var btnFindNext = new Button
         {
-            X = Pos.Right (txtToFind) + 1,
-            Y = Pos.Top (label),
-            Width = 20,
+            X = Pos.Align (Alignment.Center),
+            Y = Pos.AnchorEnd (),
             Enabled = !string.IsNullOrEmpty (txtToFind.Text),
-            TextAlignment = Alignment.Center,
             IsDefault = true,
 
             Text = "Find _Next"
@@ -882,12 +882,9 @@ public class Editor : Scenario
 
         var btnFindPrevious = new Button
         {
-            X = Pos.Right (txtToFind) + 1,
-            Y = Pos.Top (btnFindNext) + 1,
-            Width = 20,
+            X = Pos.Align (Alignment.Center),
+            Y = Pos.AnchorEnd (),
             Enabled = !string.IsNullOrEmpty (txtToFind.Text),
-            TextAlignment = Alignment.Center,
-
             Text = "Find _Previous"
         };
         btnFindPrevious.Accept += (s, e) => FindPrevious ();
@@ -901,18 +898,6 @@ public class Editor : Scenario
                                      btnFindPrevious.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
                                  };
 
-        var btnCancel = new Button
-        {
-            X = Pos.Right (txtToFind) + 1,
-            Y = Pos.Top (btnFindPrevious) + 2,
-            Width = 20,
-            TextAlignment = Alignment.Center,
-
-            Text = "Cancel"
-        };
-        btnCancel.Accept += (s, e) => { DisposeWinDialog (); };
-        d.Add (btnCancel);
-
         var ckbMatchCase = new CheckBox
         {
             X = 0, Y = Pos.Top (txtToFind) + 2, Checked = _matchCase, Text = "Match c_ase"
@@ -926,10 +911,6 @@ public class Editor : Scenario
         };
         ckbMatchWholeWord.Toggled += (s, e) => _matchWholeWord = (bool)ckbMatchWholeWord.Checked;
         d.Add (ckbMatchWholeWord);
-
-        d.Width = label.Width + txtToFind.Width + btnFindNext.Width + 2;
-        d.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4;
-
         return d;
     }
 
@@ -950,7 +931,7 @@ public class Editor : Scenario
                 CreateAction (supportedCultures, culture);
                 supportedCultures.Add (culture);
                 index++;
-                culture = new() { CheckType = MenuItemCheckStyle.Checked };
+                culture = new () { CheckType = MenuItemCheckStyle.Checked };
             }
 
             culture.Title = $"_{c.Parent.EnglishName}";
@@ -986,7 +967,7 @@ public class Editor : Scenario
 
             //_textView.Text = System.IO.File.ReadAllText (_fileName);
             _originalText = Encoding.Unicode.GetBytes (_textView.Text);
-            Win.Title = _fileName;
+            _appWindow.Title = _fileName;
             _saved = true;
         }
     }
@@ -998,7 +979,7 @@ public class Editor : Scenario
             return;
         }
 
-        Win.Title = "Untitled.txt";
+        _appWindow.Title = "Untitled.txt";
         _fileName = null;
         _originalText = new MemoryStream ().ToArray ();
         _textView.Text = Encoding.Unicode.GetString (_originalText);
@@ -1053,11 +1034,11 @@ public class Editor : Scenario
         Application.RequestStop ();
     }
 
-    private void Replace () { CreateFindReplace (false); }
+    private void Replace () { ShowFindReplace (false); }
 
     private void ReplaceAll ()
     {
-        if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && _winDialog == null))
+        if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && _findReplaceWindow == null))
         {
             Replace ();
 
@@ -1085,26 +1066,20 @@ public class Editor : Scenario
     private void ReplaceNext () { ContinueFind (true, true); }
     private void ReplacePrevious () { ContinueFind (false, true); }
 
-    private View ReplaceTab ()
+    private View CreateReplaceTab ()
     {
-        var d = new View ();
-
-        d.DrawContent += (s, e) =>
-                         {
-                             foreach (View v in d.Subviews)
-                             {
-                                 v.SetNeedsDisplay ();
-                             }
-                         };
+        var d = new View ()
+        {
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+        };
 
         int lblWidth = "Replace:".Length;
 
         var label = new Label
         {
-            Y = 1,
             Width = lblWidth,
             TextAlignment = Alignment.End,
-
             Text = "Find:"
         };
         d.Add (label);
@@ -1113,45 +1088,50 @@ public class Editor : Scenario
 
         var txtToFind = new TextField
         {
-            X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Text = _textToFind
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Width = Dim.Fill (1),
+            Text = _textToFind
         };
         txtToFind.Enter += (s, e) => txtToFind.Text = _textToFind;
         d.Add (txtToFind);
 
         var btnFindNext = new Button
         {
-            X = Pos.Right (txtToFind) + 1,
-            Y = Pos.Top (label),
-            Width = 20,
+            X = Pos.Align (Alignment.Center),
+            Y = Pos.AnchorEnd (),
             Enabled = !string.IsNullOrEmpty (txtToFind.Text),
-            TextAlignment = Alignment.Center,
             IsDefault = true,
-
             Text = "Replace _Next"
         };
         btnFindNext.Accept += (s, e) => ReplaceNext ();
         d.Add (btnFindNext);
 
-        label = new() { X = Pos.Left (label), Y = Pos.Top (label) + 1, Text = "Replace:" };
+        label = new ()
+        {
+            X = Pos.Left (label),
+            Y = Pos.Top (label) + 1,
+            Text = "Replace:"
+        };
         d.Add (label);
 
         SetFindText ();
 
         var txtToReplace = new TextField
         {
-            X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Text = _textToReplace
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Width = Dim.Fill (1),
+            Text = _textToReplace
         };
         txtToReplace.TextChanged += (s, e) => _textToReplace = txtToReplace.Text;
         d.Add (txtToReplace);
 
         var btnFindPrevious = new Button
         {
-            X = Pos.Right (txtToFind) + 1,
-            Y = Pos.Top (btnFindNext) + 1,
-            Width = 20,
+            X = Pos.Align (Alignment.Center),
+            Y = Pos.AnchorEnd (),
             Enabled = !string.IsNullOrEmpty (txtToFind.Text),
-            TextAlignment = Alignment.Center,
-
             Text = "Replace _Previous"
         };
         btnFindPrevious.Accept += (s, e) => ReplacePrevious ();
@@ -1159,12 +1139,9 @@ public class Editor : Scenario
 
         var btnReplaceAll = new Button
         {
-            X = Pos.Right (txtToFind) + 1,
-            Y = Pos.Top (btnFindPrevious) + 1,
-            Width = 20,
+            X = Pos.Align (Alignment.Center),
+            Y = Pos.AnchorEnd (),
             Enabled = !string.IsNullOrEmpty (txtToFind.Text),
-            TextAlignment = Alignment.Center,
-
             Text = "Replace _All"
         };
         btnReplaceAll.Accept += (s, e) => ReplaceAll ();
@@ -1179,18 +1156,6 @@ public class Editor : Scenario
                                      btnReplaceAll.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
                                  };
 
-        var btnCancel = new Button
-        {
-            X = Pos.Right (txtToFind) + 1,
-            Y = Pos.Top (btnReplaceAll) + 1,
-            Width = 20,
-            TextAlignment = Alignment.Center,
-
-            Text = "Cancel"
-        };
-        btnCancel.Accept += (s, e) => { DisposeWinDialog (); };
-        d.Add (btnCancel);
-
         var ckbMatchCase = new CheckBox
         {
             X = 0, Y = Pos.Top (txtToFind) + 2, Checked = _matchCase, Text = "Match c_ase"
@@ -1205,9 +1170,6 @@ public class Editor : Scenario
         ckbMatchWholeWord.Toggled += (s, e) => _matchWholeWord = (bool)ckbMatchWholeWord.Checked;
         d.Add (ckbMatchWholeWord);
 
-        d.Width = lblWidth + txtToFind.Width + btnFindNext.Width + 2;
-        d.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4;
-
         return d;
     }
 
@@ -1217,7 +1179,7 @@ public class Editor : Scenario
         {
             // FIXED: BUGBUG: #279 TextView does not know how to deal with \r\n, only \r 
             // As a result files saved on Windows and then read back will show invalid chars.
-            return SaveFile (Win.Title, _fileName);
+            return SaveFile (_appWindow.Title, _fileName);
         }
 
         return SaveAs ();
@@ -1231,7 +1193,7 @@ public class Editor : Scenario
         };
         var sd = new SaveDialog { Title = "Save file", AllowedTypes = aTypes };
 
-        sd.Path = Win.Title;
+        sd.Path = _appWindow.Title;
         Application.Run (sd);
         bool canceled = sd.Canceled;
         string path = sd.Path;
@@ -1270,7 +1232,7 @@ public class Editor : Scenario
     {
         try
         {
-            Win.Title = title;
+            _appWindow.Title = title;
             _fileName = file;
             File.WriteAllText (_fileName, _textView.Text);
             _originalText = Encoding.Unicode.GetBytes (_textView.Text);

+ 191 - 0
UICatalog/Scenarios/KeyBindings.cs

@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("KeyBindings", "Illustrates the KeyBindings API.")]
+[ScenarioCategory ("Mouse and Keyboard")]
+public sealed class KeyBindings : Scenario
+{
+    private readonly ObservableCollection<string> _focusedBindings = [];
+    private ListView _focusedBindingsListView;
+
+    public override void Main ()
+    {
+        // Init
+        Application.Init ();
+
+        // Setup - Create a top-level application window and configure it.
+        Window appWindow = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+            SuperViewRendersLineCanvas = true,
+        };
+
+        Label label = new ()
+        {
+            Title = "_Label:",
+        };
+        TextField textField = new ()
+        {
+            X = Pos.Right (label),
+            Y = Pos.Top (label),
+            Width = 20,
+        };
+
+        appWindow.Add (label, textField);
+
+        Button button = new ()
+        {
+            X = Pos.Right (textField) + 1,
+            Y = Pos.Top (label),
+            Text = "_Button",
+        };
+        appWindow.Add (button);
+
+        KeyBindingsDemo keyBindingsDemo = new ()
+        {
+            X = Pos.Right (button) + 1,
+            Width = Dim.Auto (DimAutoStyle.Text),
+            Height = Dim.Auto (DimAutoStyle.Text),
+            HotKeySpecifier = (Rune)'_',
+            Title = "_KeyBindingsDemo",
+            Text = @"These keys will cause this view to show a message box:
+- Hotkey: k, K, Alt-K, Alt-Shift-K
+- Focused: F3
+- Application: F4
+Pressing Ctrl-Q will cause it to quit the app.",
+            BorderStyle = LineStyle.Dashed
+        };
+        appWindow.Add (keyBindingsDemo);
+
+        ObservableCollection<string> appBindings = new ();
+        ListView appBindingsListView = new ()
+        {
+            Title = "_Application Bindings",
+            BorderStyle = LineStyle.Single,
+            X = -1,
+            Y = Pos.Bottom (keyBindingsDemo) + 1,
+            Width = Dim.Auto (),
+            Height = Dim.Fill () + 1,
+            CanFocus = true,
+            Source = new ListWrapper<string> (appBindings),
+            SuperViewRendersLineCanvas = true
+        };
+        appWindow.Add (appBindingsListView);
+
+        foreach (var appBinding in Application.GetKeyBindings ())
+        {
+            foreach (var view in appBinding.Value)
+            {
+                var commands = view.KeyBindings.GetCommands (appBinding.Key);
+                appBindings.Add ($"{appBinding.Key} -> {view.GetType ().Name} - {commands [0]}");
+            }
+        }
+
+        ObservableCollection<string> hotkeyBindings = new ();
+        ListView hotkeyBindingsListView = new ()
+        {
+            Title = "_Hotkey Bindings",
+            BorderStyle = LineStyle.Single,
+            X = Pos.Right (appBindingsListView) - 1,
+            Y = Pos.Bottom (keyBindingsDemo) + 1,
+            Width = Dim.Auto (),
+            Height = Dim.Fill () + 1,
+            CanFocus = true,
+            Source = new ListWrapper<string> (hotkeyBindings),
+            SuperViewRendersLineCanvas = true
+
+        };
+        appWindow.Add (hotkeyBindingsListView);
+
+        foreach (var subview in appWindow.Subviews)
+        {
+            foreach (var binding in subview.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.HotKey))
+            {
+                hotkeyBindings.Add ($"{binding.Key} -> {subview.GetType ().Name} - {binding.Value.Commands [0]}");
+            }
+        }
+
+        _focusedBindingsListView = new ()
+        {
+            Title = "_Focused Bindings",
+            BorderStyle = LineStyle.Single,
+            X = Pos.Right (hotkeyBindingsListView) - 1,
+            Y = Pos.Bottom (keyBindingsDemo) + 1,
+            Width = Dim.Auto (),
+            Height = Dim.Fill () + 1,
+            CanFocus = true,
+            Source = new ListWrapper<string> (_focusedBindings),
+            SuperViewRendersLineCanvas = true
+
+        };
+        appWindow.Add (_focusedBindingsListView);
+
+        appWindow.Leave += AppWindow_Leave;
+        appWindow.Enter += AppWindow_Leave;
+        appWindow.DrawContent += AppWindow_DrawContent;
+
+        // Run - Start the application.
+        Application.Run (appWindow);
+        appWindow.Dispose ();
+
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
+    }
+
+    private void AppWindow_DrawContent (object sender, DrawEventArgs e)
+    {
+        _focusedBindingsListView.Title = $"_Focused ({Application.Top.MostFocused.GetType ().Name}) Bindings";
+
+        _focusedBindings.Clear ();
+        foreach (var binding in Application.Top.MostFocused.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused))
+        {
+            _focusedBindings.Add ($"{binding.Key} -> {binding.Value.Commands [0]}");
+        }
+    }
+
+    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]}");
+        //}
+    }
+}
+
+public class KeyBindingsDemo : View
+{
+    public KeyBindingsDemo ()
+    {
+        CanFocus = true;
+
+        AddCommand (Command.New, ctx =>
+                                {
+                                    MessageBox.Query ("Hi", $"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");
+            SetFocus ();
+            return true;
+        });
+
+        KeyBindings.Add (Key.F3, KeyBindingScope.Focused, Command.New);
+        KeyBindings.Add (Key.F4, KeyBindingScope.Application, Command.New);
+
+
+        AddCommand (Command.QuitToplevel, ctx =>
+                                         {
+                                             Application.RequestStop ();
+                                             return true;
+                                         });
+        KeyBindings.Add (Key.Q.WithCtrl, KeyBindingScope.Application, Command.QuitToplevel);
+    }
+}

+ 7 - 7
UICatalog/Scenarios/Keys.cs

@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
@@ -9,8 +9,8 @@ public class Keys : Scenario
 {
     public override void Setup ()
     {
-        List<string> keyPressedList = new ();
-        List<string> invokingKeyBindingsList = new ();
+        ObservableCollection<string> keyPressedList = [];
+        ObservableCollection<string> invokingKeyBindingsList = new ();
 
         var editLabel = new Label { X = 0, Y = 0, Text = "Type text here:" };
         Win.Add (editLabel);
@@ -57,7 +57,7 @@ public class Keys : Scenario
         Win.Add (keyLogLabel);
         int maxKeyString = Key.CursorRight.WithAlt.WithCtrl.WithShift.ToString ().Length;
         var yOffset = 1;
-        List<string> keyEventlist = new ();
+        ObservableCollection<string> keyEventlist = new ();
 
         var keyEventListView = new ListView
         {
@@ -65,7 +65,7 @@ public class Keys : Scenario
             Y = Pos.Top (keyLogLabel) + yOffset,
             Width = "Key Down:".Length + maxKeyString,
             Height = Dim.Fill (),
-            Source = new ListWrapper (keyEventlist)
+            Source = new ListWrapper<string> (keyEventlist)
         };
         keyEventListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
         Win.Add (keyEventListView);
@@ -85,7 +85,7 @@ public class Keys : Scenario
             Y = Pos.Top (onKeyPressedLabel) + yOffset,
             Width = maxKeyString,
             Height = Dim.Fill (),
-            Source = new ListWrapper (keyPressedList)
+            Source = new ListWrapper<string> (keyPressedList)
         };
         onKeyPressedListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
         Win.Add (onKeyPressedListView);
@@ -105,7 +105,7 @@ public class Keys : Scenario
             Y = Pos.Top (onInvokingKeyBindingsLabel) + yOffset,
             Width = Dim.Fill (1),
             Height = Dim.Fill (),
-            Source = new ListWrapper (invokingKeyBindingsList)
+            Source = new ListWrapper<string> (invokingKeyBindingsList)
         };
         onInvokingKeyBindingsListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
         Win.Add (onInvokingKeyBindingsListView);

+ 76 - 54
UICatalog/Scenarios/ListViewWithSelection.cs

@@ -1,5 +1,7 @@
 using System.Collections;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
 using System.Text;
 using JetBrains.Annotations;
 using Terminal.Gui;
@@ -11,25 +13,34 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("ListView")]
 public class ListViewWithSelection : Scenario
 {
-    public CheckBox _allowMarkingCB;
-    public CheckBox _allowMultipleCB;
-    public CheckBox _customRenderCB;
-    public ListView _listView;
-    public List<Scenario> _scenarios;
-
-    public override void Setup ()
+    private CheckBox _allowMarkingCB;
+    private CheckBox _allowMultipleCB;
+    private CheckBox _customRenderCB;
+    private ListView _listView;
+    private ObservableCollection<Scenario> _scenarios;
+    private Window _appWindow;
+
+    /// <inheritdoc />
+    public override void Main ()
     {
+        Application.Init ();
+
+        _appWindow = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+        };
+
         _scenarios = GetScenarios ();
 
         _customRenderCB = new CheckBox { X = 0, Y = 0, Text = "Use custom rendering" };
-        Win.Add (_customRenderCB);
+        _appWindow.Add (_customRenderCB);
         _customRenderCB.Toggled += _customRenderCB_Toggled;
 
         _allowMarkingCB = new CheckBox
         {
             X = Pos.Right (_customRenderCB) + 1, Y = 0, Text = "Allow Marking", AllowNullChecked = false
         };
-        Win.Add (_allowMarkingCB);
+        _appWindow.Add (_allowMarkingCB);
         _allowMarkingCB.Toggled += AllowMarkingCB_Toggled;
 
         _allowMultipleCB = new CheckBox
@@ -39,7 +50,7 @@ public class ListViewWithSelection : Scenario
             Visible = (bool)_allowMarkingCB.Checked,
             Text = "Allow Multi-Select"
         };
-        Win.Add (_allowMultipleCB);
+        _appWindow.Add (_allowMultipleCB);
         _allowMultipleCB.Toggled += AllowMultipleCB_Toggled;
 
         _listView = new ListView
@@ -54,42 +65,42 @@ public class ListViewWithSelection : Scenario
             AllowsMultipleSelection = false
         };
         _listView.RowRender += ListView_RowRender;
-        Win.Add (_listView);
+        _appWindow.Add (_listView);
 
         var scrollBar = new ScrollBarView (_listView, true);
 
         scrollBar.ChangedPosition += (s, e) =>
-                                     {
-                                         _listView.TopItem = scrollBar.Position;
+        {
+            _listView.TopItem = scrollBar.Position;
 
-                                         if (_listView.TopItem != scrollBar.Position)
-                                         {
-                                             scrollBar.Position = _listView.TopItem;
-                                         }
+            if (_listView.TopItem != scrollBar.Position)
+            {
+                scrollBar.Position = _listView.TopItem;
+            }
 
-                                         _listView.SetNeedsDisplay ();
-                                     };
+            _listView.SetNeedsDisplay ();
+        };
 
         scrollBar.OtherScrollBarView.ChangedPosition += (s, e) =>
-                                                        {
-                                                            _listView.LeftItem = scrollBar.OtherScrollBarView.Position;
+        {
+            _listView.LeftItem = scrollBar.OtherScrollBarView.Position;
 
-                                                            if (_listView.LeftItem != scrollBar.OtherScrollBarView.Position)
-                                                            {
-                                                                scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
-                                                            }
+            if (_listView.LeftItem != scrollBar.OtherScrollBarView.Position)
+            {
+                scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
+            }
 
-                                                            _listView.SetNeedsDisplay ();
-                                                        };
+            _listView.SetNeedsDisplay ();
+        };
 
         _listView.DrawContent += (s, e) =>
-                                 {
-                                     scrollBar.Size = _listView.Source.Count;
-                                     scrollBar.Position = _listView.TopItem;
-                                     scrollBar.OtherScrollBarView.Size = _listView.MaxLength;
-                                     scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
-                                     scrollBar.Refresh ();
-                                 };
+        {
+            scrollBar.Size = _listView.Source.Count;
+            scrollBar.Position = _listView.TopItem;
+            scrollBar.OtherScrollBarView.Size = _listView.MaxLength;
+            scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
+            scrollBar.Refresh ();
+        };
 
         _listView.SetSource (_scenarios);
 
@@ -100,7 +111,11 @@ public class ListViewWithSelection : Scenario
             X = Pos.AnchorEnd (k.Length + 3), Y = 0, Text = k, Checked = scrollBar.AutoHideScrollBars
         };
         keepCheckBox.Toggled += (s, e) => scrollBar.KeepContentAlwaysInViewport = (bool)keepCheckBox.Checked;
-        Win.Add (keepCheckBox);
+        _appWindow.Add (keepCheckBox);
+
+        Application.Run (_appWindow);
+        _appWindow.Dispose ();
+        Application.Shutdown ();
     }
 
     private void _customRenderCB_Toggled (object sender, StateEventArgs<bool?> stateEventArgs)
@@ -114,20 +129,20 @@ public class ListViewWithSelection : Scenario
             _listView.Source = new ScenarioListDataSource (_scenarios);
         }
 
-        Win.SetNeedsDisplay ();
+        _appWindow.SetNeedsDisplay ();
     }
 
     private void AllowMarkingCB_Toggled (object sender, [NotNull] StateEventArgs<bool?> stateEventArgs)
     {
         _listView.AllowsMarking = (bool)!stateEventArgs.OldValue;
         _allowMultipleCB.Visible = _listView.AllowsMarking;
-        Win.SetNeedsDisplay ();
+        _appWindow.SetNeedsDisplay ();
     }
 
     private void AllowMultipleCB_Toggled (object sender, [NotNull] StateEventArgs<bool?> stateEventArgs)
     {
         _listView.AllowsMultipleSelection = (bool)!stateEventArgs.OldValue;
-        Win.SetNeedsDisplay ();
+        _appWindow.SetNeedsDisplay ();
     }
 
     private void ListView_RowRender (object sender, ListViewRowEventArgs obj)
@@ -158,21 +173,21 @@ public class ListViewWithSelection : Scenario
     internal class ScenarioListDataSource : IListDataSource
     {
         private readonly int _nameColumnWidth = 30;
-        private int count;
-        private BitArray marks;
-        private List<Scenario> scenarios;
-        public ScenarioListDataSource (List<Scenario> itemList) { Scenarios = itemList; }
+        private int _count;
+        private BitArray _marks;
+        private ObservableCollection<Scenario> _scenarios;
+        public ScenarioListDataSource (ObservableCollection<Scenario> itemList) { Scenarios = itemList; }
 
-        public List<Scenario> Scenarios
+        public ObservableCollection<Scenario> Scenarios
         {
-            get => scenarios;
+            get => _scenarios;
             set
             {
                 if (value != null)
                 {
-                    count = value.Count;
-                    marks = new BitArray (count);
-                    scenarios = value;
+                    _count = value.Count;
+                    _marks = new BitArray (_count);
+                    _scenarios = value;
                     Length = GetMaxLengthItem ();
                 }
             }
@@ -180,14 +195,16 @@ public class ListViewWithSelection : Scenario
 
         public bool IsMarked (int item)
         {
-            if (item >= 0 && item < count)
+            if (item >= 0 && item < _count)
             {
-                return marks [item];
+                return _marks [item];
             }
 
             return false;
         }
 
+        /// <inheritdoc />
+        public event NotifyCollectionChangedEventHandler CollectionChanged;
         public int Count => Scenarios != null ? Scenarios.Count : 0;
         public int Length { get; private set; }
 
@@ -214,9 +231,9 @@ public class ListViewWithSelection : Scenario
 
         public void SetMark (int item, bool value)
         {
-            if (item >= 0 && item < count)
+            if (item >= 0 && item < _count)
             {
-                marks [item] = value;
+                _marks [item] = value;
             }
         }
 
@@ -224,17 +241,17 @@ public class ListViewWithSelection : Scenario
 
         private int GetMaxLengthItem ()
         {
-            if (scenarios?.Count == 0)
+            if (_scenarios?.Count == 0)
             {
                 return 0;
             }
 
             var maxLength = 0;
 
-            for (var i = 0; i < scenarios.Count; i++)
+            for (var i = 0; i < _scenarios.Count; i++)
             {
                 string s = string.Format (
-                                          string.Format ("{{0,{0}}}", -_nameColumnWidth),
+                                          $"{{0,{-_nameColumnWidth}}}",
                                           Scenarios [i].GetName ()
                                          );
                 var sc = $"{s}  {Scenarios [i].GetDescription ()}";
@@ -276,5 +293,10 @@ public class ListViewWithSelection : Scenario
                 used++;
             }
         }
+
+        public void Dispose ()
+        {
+            _scenarios = null;
+        }
     }
 }

+ 10 - 10
UICatalog/Scenarios/ListsAndCombos.cs

@@ -1,5 +1,5 @@
 using System;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.IO;
 using System.Linq;
 using Terminal.Gui;
@@ -15,19 +15,19 @@ public class ListsAndCombos : Scenario
     public override void Setup ()
     {
         //TODO: Duplicated code in Demo.cs Consider moving to shared assembly
-        List<string> items = new ();
+        ObservableCollection<string> items = [];
 
         foreach (string dir in new [] { "/etc", @$"{Environment.GetEnvironmentVariable ("SystemRoot")}\System32" })
         {
             if (Directory.Exists (dir))
             {
-                items = Directory.GetFiles (dir)
-                                 .Union (Directory.GetDirectories (dir))
-                                 .Select (Path.GetFileName)
-                                 .Where (x => char.IsLetterOrDigit (x [0]))
-                                 .OrderBy (x => x)
-                                 .Select (x => x)
-                                 .ToList ();
+                items = new (Directory.GetFiles (dir)
+                                      .Union (Directory.GetDirectories (dir))
+                                      .Select (Path.GetFileName)
+                                      .Where (x => char.IsLetterOrDigit (x [0]))
+                                      .OrderBy (x => x)
+                                      .Select (x => x)
+                                      .ToList ());
             }
         }
 
@@ -47,7 +47,7 @@ public class ListsAndCombos : Scenario
             Y = Pos.Bottom (lbListView) + 1,
             Height = Dim.Fill (2),
             Width = Dim.Percent (40),
-            Source = new ListWrapper (items)
+            Source = new ListWrapper<string> (items)
         };
         listview.SelectedItemChanged += (s, e) => lbListView.Text = items [listview.SelectedItem];
         Win.Add (lbListView, listview);

+ 2 - 2
UICatalog/Scenarios/Localization.cs

@@ -112,10 +112,10 @@ public class Localization : Scenario
             Width = _cultureInfoNameSource.Select (cn => cn.Length + 3).Max (),
             Height = _cultureInfoNameSource.Length + 1,
             HideDropdownListOnClick = true,
-            Source = new ListWrapper (_cultureInfoNameSource),
+            Source = new ListWrapper<string> (new (_cultureInfoNameSource)),
             SelectedItem = _cultureInfoNameSource.Length - 1
         };
-        _languageComboBox.SetSource (_cultureInfoNameSource);
+        _languageComboBox.SetSource<string> (new (_cultureInfoNameSource));
         _languageComboBox.SelectedItemChanged += LanguageComboBox_SelectChanged;
         Win.Add (_languageComboBox);
 

+ 34 - 24
UICatalog/Scenarios/MenuBarScenario.cs

@@ -5,7 +5,7 @@ namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("MenuBar", "Demonstrates the MenuBar using the same menu used in unit tests.")]
 [ScenarioCategory ("Controls")]
-[ScenarioCategory ("Menu")]
+[ScenarioCategory ("Menus")]
 public class MenuBarScenario : Scenario
 {
     private Label _currentMenuBarItem;
@@ -15,13 +15,14 @@ public class MenuBarScenario : Scenario
     private Label _lastKey;
 
     /// <summary>
-    ///     This method creates at test menu bar. It is called by the MenuBar unit tests so it's possible to do both unit
+    ///     This method creates at test menu bar. It is called by the MenuBar unit tests, so it's possible to do both unit
     ///     testing and user-experience testing with the same setup.
     /// </summary>
     /// <param name="actionFn"></param>
     /// <returns></returns>
     public static MenuBar CreateTestMenu (Func<string, bool> actionFn)
     {
+        // TODO: add a disabled menu item to this
         var mb = new MenuBar
         {
             Menus =
@@ -195,48 +196,50 @@ public class MenuBarScenario : Scenario
         return mb;
     }
 
-    // Don't create a Window, just return the top-level view
-    public override void Init ()
+    public override void Main ()
     {
+        // Init
         Application.Init ();
-        Top = new ();
-        Top.ColorScheme = Colors.ColorSchemes ["Base"];
-    }
 
-    public override void Setup ()
-    {
+        // Setup - Create a top-level application window and configure it.
+        Window appWindow = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+            BorderStyle = LineStyle.None
+        };
+
         MenuItem mbiCurrent = null;
         MenuItem miCurrent = null;
 
         var label = new Label { X = 0, Y = 10, Text = "Last Key: " };
-        Top.Add (label);
+        appWindow.Add (label);
 
         _lastKey = new Label { X = Pos.Right (label), Y = Pos.Top (label), Text = "" };
 
-        Top.Add (_lastKey);
+        appWindow.Add (_lastKey);
         label = new Label { X = 0, Y = Pos.Bottom (label), Text = "Current MenuBarItem: " };
-        Top.Add (label);
+        appWindow.Add (label);
 
         _currentMenuBarItem = new Label { X = Pos.Right (label), Y = Pos.Top (label), Text = "" };
-        Top.Add (_currentMenuBarItem);
+        appWindow.Add (_currentMenuBarItem);
 
         label = new Label { X = 0, Y = Pos.Bottom (label), Text = "Current MenuItem: " };
-        Top.Add (label);
+        appWindow.Add (label);
 
         _currentMenuItem = new Label { X = Pos.Right (label), Y = Pos.Top (label), Text = "" };
-        Top.Add (_currentMenuItem);
+        appWindow.Add (_currentMenuItem);
 
         label = new Label { X = 0, Y = Pos.Bottom (label), Text = "Last Action: " };
-        Top.Add (label);
+        appWindow.Add (label);
 
         _lastAction = new Label { X = Pos.Right (label), Y = Pos.Top (label), Text = "" };
-        Top.Add (_lastAction);
+        appWindow.Add (_lastAction);
 
         label = new Label { X = 0, Y = Pos.Bottom (label), Text = "Focused View: " };
-        Top.Add (label);
+        appWindow.Add (label);
 
         _focusedView = new Label { X = Pos.Right (label), Y = Pos.Top (label), Text = "" };
-        Top.Add (_focusedView);
+        appWindow.Add (_focusedView);
 
         MenuBar menuBar = CreateTestMenu (
                                           s =>
@@ -277,21 +280,28 @@ public class MenuBarScenario : Scenario
                                };
 
         // There's no focus change event, so this is a bit of a hack.
-        menuBar.LayoutComplete += (s, e) => { _focusedView.Text = Top.MostFocused?.ToString () ?? "None"; };
+        menuBar.LayoutComplete += (s, e) => { _focusedView.Text = appWindow.MostFocused?.ToString () ?? "None"; };
 
         var openBtn = new Button { X = Pos.Center (), Y = 4, Text = "_Open Menu", IsDefault = true };
         openBtn.Accept += (s, e) => { menuBar.OpenMenu (); };
-        Top.Add (openBtn);
+        appWindow.Add (openBtn);
 
         var hideBtn = new Button { X = Pos.Center (), Y = Pos.Bottom (openBtn), Text = "Toggle Menu._Visible" };
         hideBtn.Accept += (s, e) => { menuBar.Visible = !menuBar.Visible; };
-        Top.Add (hideBtn);
+        appWindow.Add (hideBtn);
 
         var enableBtn = new Button { X = Pos.Center (), Y = Pos.Bottom (hideBtn), Text = "_Toggle Menu.Enable" };
         enableBtn.Accept += (s, e) => { menuBar.Enabled = !menuBar.Enabled; };
-        Top.Add (enableBtn);
+        appWindow.Add (enableBtn);
+
+        appWindow.Add (menuBar);
+
+        // Run - Start the application.
+        Application.Run (appWindow);
+        appWindow.Dispose ();
 
-        Top.Add (menuBar);
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
     }
 
     private void SetCurrentMenuBarItem (MenuItem mbi) { _currentMenuBarItem.Text = mbi != null ? mbi.Title : "Closed"; }

+ 5 - 5
UICatalog/Scenarios/Mouse.cs

@@ -1,5 +1,5 @@
 using System;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using Terminal.Gui;
 
@@ -113,7 +113,7 @@ public class Mouse : Scenario
             Y = Pos.Bottom (demo)
         };
 
-        List<string> appLogList = new ();
+        ObservableCollection<string> appLogList = new ();
 
         var appLog = new ListView
         {
@@ -122,7 +122,7 @@ public class Mouse : Scenario
             Width = 50,
             Height = Dim.Fill (),
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
-            Source = new ListWrapper (appLogList)
+            Source = new ListWrapper<string> (appLogList)
         };
         win.Add (label, appLog);
 
@@ -144,7 +144,7 @@ public class Mouse : Scenario
             X = Pos.Right (appLog) + 1,
             Y = Pos.Top (label)
         };
-        List<string> winLogList = new ();
+        ObservableCollection<string> winLogList = new ();
 
         var winLog = new ListView
         {
@@ -153,7 +153,7 @@ public class Mouse : Scenario
             Width = Dim.Percent (50),
             Height = Dim.Fill (),
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
-            Source = new ListWrapper (winLogList)
+            Source = new ListWrapper<string> (winLogList)
         };
         win.Add (label, winLog);
 

+ 5 - 3
UICatalog/Scenarios/ProgressBarStyles.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Threading;
 using Terminal.Gui;
@@ -261,9 +262,10 @@ public class ProgressBarStyles : Scenario
         container.Add (marqueesContinuousPB);
 
         _pbList.SetSource (
-                          container.Subviews.Where (v => v.GetType () == typeof (ProgressBar))
-                                   .Select (v => v.Title)
-                                   .ToList ()
+                          new ObservableCollection<string> (
+                                                            container.Subviews.Where (v => v.GetType () == typeof (ProgressBar))
+                                                                     .Select (v => v.Title)
+                                                                     .ToList ())
                          );
 
         _pbList.SelectedItemChanged += (sender, e) =>

+ 6 - 6
UICatalog/Scenarios/SingleBackgroundWorker.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
-using System.Diagnostics;
 using System.Threading;
 using Terminal.Gui;
 
@@ -24,7 +24,7 @@ public class SingleBackgroundWorker : Scenario
     public class MainApp : Toplevel
     {
         private readonly ListView _listLog;
-        private readonly List<string> _log = new ();
+        private readonly ObservableCollection<string> _log = [];
         private DateTime? _startStaging;
         private BackgroundWorker _worker;
 
@@ -90,7 +90,7 @@ public class SingleBackgroundWorker : Scenario
                 Y = 2,
                 Width = Dim.Fill (),
                 Height = Dim.Fill (),
-                Source = new ListWrapper (_log)
+                Source = new ListWrapper<string> (_log)
             };
             workerLogTop.Add (_listLog);
             Add (workerLogTop);
@@ -194,7 +194,7 @@ public class SingleBackgroundWorker : Scenario
                                                   Application.Refresh ();
 
                                                   var builderUI =
-                                                      new StagingUIController (_startStaging, e.Result as List<string>);
+                                                      new StagingUIController (_startStaging, e.Result as ObservableCollection<string>);
                                                   var top = Application.Top;
                                                   top.Visible = false;
                                                   Application.Current.Visible = false;
@@ -215,7 +215,7 @@ public class SingleBackgroundWorker : Scenario
     {
         private Toplevel _top;
 
-        public StagingUIController (DateTime? start, List<string> list)
+        public StagingUIController (DateTime? start, ObservableCollection<string> list)
         {
             _top = new Toplevel
             {
@@ -302,7 +302,7 @@ public class SingleBackgroundWorker : Scenario
                      Y = 0,
                      Width = Dim.Fill (),
                      Height = Dim.Fill (),
-                     Source = new ListWrapper (list)
+                     Source = new ListWrapper<string> (list)
                  }
                 );
 

+ 2 - 1
UICatalog/Scenarios/SpinnerStyles.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using Terminal.Gui;
 
@@ -116,7 +117,7 @@ public class SpinnerViewStyles : Scenario
         {
             X = Pos.Center (), Y = Pos.Bottom (preview) + 2, Height = Dim.Fill (), Width = Dim.Fill (1)
         };
-        styles.SetSource (styleArray);
+        styles.SetSource (new ObservableCollection<string> (styleArray));
         styles.SelectedItem = 0; // SpinnerStyle.Custom;
         app.Add (styles);
         SetCustom ();

+ 11 - 11
UICatalog/Scenarios/Threading.cs

@@ -1,5 +1,5 @@
 using System;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Threading;
 using System.Threading.Tasks;
 using Terminal.Gui;
@@ -10,7 +10,7 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Threading")]
 public class Threading : Scenario
 {
-    private readonly List<string> _log = [];
+    private readonly ObservableCollection<string> _log = [];
     private Action _action;
     private Button _btnActionCancel;
     private CancellationTokenSource _cancellationTokenSource;
@@ -28,7 +28,7 @@ public class Threading : Scenario
                   {
                       _itemsList.Source = null;
                       LogJob ("Loading task lambda");
-                      List<string> items = await LoadDataAsync ();
+                      ObservableCollection<string> items = await LoadDataAsync ();
                       LogJob ("Returning from task lambda");
                       await _itemsList.SetSourceAsync (items);
                   };
@@ -37,7 +37,7 @@ public class Threading : Scenario
                    {
                        _itemsList.Source = null;
                        LogJob ("Loading task handler");
-                       List<string> items = await LoadDataAsync ();
+                       ObservableCollection<string> items = await LoadDataAsync ();
                        LogJob ("Returning from task handler");
                        await _itemsList.SetSourceAsync (items);
                    };
@@ -47,7 +47,7 @@ public class Threading : Scenario
                     _itemsList.Source = null;
                     LogJob ("Loading task synchronous");
 
-                    List<string> items =
+                    ObservableCollection<string> items =
                         ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
                     LogJob ("Returning from task synchronous");
                     _itemsList.SetSource (items);
@@ -76,7 +76,7 @@ public class Threading : Scenario
             Width = 50,
             Height = Dim.Fill (),
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
-            Source = new ListWrapper (_log)
+            Source = new ListWrapper<string> (_log)
         };
 
         var text = new TextField { X = 1, Y = 3, Width = 100, Text = "Type anything after press the button" };
@@ -148,7 +148,7 @@ public class Threading : Scenario
             }
 
             LogJob ($"Calling task Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
-            List<string> items = await Task.Run (LoadItemsAsync, _cancellationTokenSource.Token);
+            ObservableCollection<string> items = await Task.Run (LoadItemsAsync, _cancellationTokenSource.Token);
 
             if (!_cancellationTokenSource.IsCancellationRequested)
             {
@@ -177,12 +177,12 @@ public class Threading : Scenario
     {
         _itemsList.Source = null;
         LogJob ("Loading task");
-        List<string> items = await LoadDataAsync ();
+        ObservableCollection<string> items = await LoadDataAsync ();
         LogJob ("Returning from task");
         await _itemsList.SetSourceAsync (items);
     }
 
-    private async Task<List<string>> LoadDataAsync ()
+    private async Task<ObservableCollection<string>> LoadDataAsync ()
     {
         _itemsList.Source = null;
         LogJob ("Starting delay");
@@ -211,7 +211,7 @@ public class Threading : Scenario
         ];
     }
 
-    private async Task<List<string>> LoadItemsAsync ()
+    private async Task<ObservableCollection<string>> LoadItemsAsync ()
     {
         // Do something that takes lot of times.
         LogJob ($"Starting delay Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
@@ -231,7 +231,7 @@ public class Threading : Scenario
     {
         _itemsList.Source = null;
         LogJob ("Loading task method");
-        List<string> items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
+        ObservableCollection<string> items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
         await Task.Delay (3000);
         LogJob ("Returning from task method");
         await _itemsList.SetSourceAsync (items);

+ 5 - 5
UICatalog/Scenarios/Unicode.cs

@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.IO;
 using System.Text;
 using Terminal.Gui;
@@ -140,7 +140,7 @@ public class UnicodeInMenu : Scenario
         label = new() { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1, Text = "ComboBox:" };
         Win.Add (label);
         var comboBox = new ComboBox { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) };
-        comboBox.SetSource (new List<string> { gitString, "Со_хранить" });
+        comboBox.SetSource (new ObservableCollection<string> { gitString, "Со_хранить" });
 
         Win.Add (comboBox);
         comboBox.Text = gitString;
@@ -163,9 +163,9 @@ public class UnicodeInMenu : Scenario
             Y = Pos.Y (label),
             Width = Dim.Percent (60),
             Height = 3,
-            Source = new ListWrapper (
-                                      new List<string> { "item #1", gitString, "Со_хранить", unicode }
-                                     )
+            Source = new ListWrapper<string> (
+                                              ["item #1", gitString, "Со_хранить", unicode]
+                                             )
         };
         Win.Add (listView);
 

+ 15 - 14
UICatalog/UICatalog.cs

@@ -2,6 +2,7 @@ global using Attribute = Terminal.Gui.Attribute;
 global using CM = Terminal.Gui.ConfigurationManager;
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.CommandLine;
 using System.Diagnostics;
 using System.Globalization;
@@ -54,14 +55,14 @@ internal class UICatalogApp
     // main app UI can be restored to previous state
     private static int _cachedScenarioIndex;
     private static string? _cachedTheme = string.Empty;
-    private static List<string>? _categories;
+    private static ObservableCollection<string>? _categories;
     private static readonly FileSystemWatcher _currentDirWatcher = new ();
     private static ViewDiagnosticFlags _diagnosticFlags;
     private static string _forceDriver = string.Empty;
     private static readonly FileSystemWatcher _homeDirWatcher = new ();
     private static bool _isFirstRunning = true;
     private static Options _options;
-    private static List<Scenario>? _scenarios;
+    private static ObservableCollection<Scenario>? _scenarios;
 
     // If set, holds the scenario the user selected
     private static Scenario? _selectedScenario;
@@ -128,7 +129,7 @@ internal class UICatalogApp
                                 {
                                     var options = new Options
                                     {
-                                        Driver = context.ParseResult.GetValueForOption (driverOption),
+                                        Driver = context.ParseResult.GetValueForOption (driverOption) ?? string.Empty,
                                         Scenario = context.ParseResult.GetValueForArgument (scenarioArgument)
                                         /* etc. */
                                     };
@@ -280,11 +281,12 @@ internal class UICatalogApp
         {
             _topLevelColorScheme = "Base";
 
-            int item = _scenarios!.FindIndex (
-                                              s =>
-                                                  s.GetName ()
-                                                   .Equals (options.Scenario, StringComparison.OrdinalIgnoreCase)
-                                             );
+            int item = _scenarios!.IndexOf (
+                                            _scenarios!.FirstOrDefault (
+                                                                        s =>
+                                                                            s.GetName ()
+                                                                             .Equals (options.Scenario, StringComparison.OrdinalIgnoreCase)
+                                                                       )!);
             _selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ())!;
 
             Application.Init (driverName: _forceDriver);
@@ -303,7 +305,7 @@ internal class UICatalogApp
         _aboutMessage = new ();
         _aboutMessage.AppendLine (@"A comprehensive sample library for");
         _aboutMessage.AppendLine (@"");
-        _aboutMessage.AppendLine (@" _______                  _             _   _____       _   ");
+        _aboutMessage.AppendLine (@"  _______                  _             _   _____       _  ");
         _aboutMessage.AppendLine (@" |__   __|                (_)           | | / ____|     (_) ");
         _aboutMessage.AppendLine (@"    | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _  ");
         _aboutMessage.AppendLine (@"    | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | ");
@@ -492,7 +494,7 @@ internal class UICatalogApp
                 Title = "_Categories",
                 BorderStyle = LineStyle.Single,
                 SuperViewRendersLineCanvas = true,
-                Source = new ListWrapper (_categories)
+                Source = new ListWrapper<string> (_categories)
             };
             CategoryList.OpenSelectedItem += (s, a) => { ScenarioList!.SetFocus (); };
             CategoryList.SelectedItemChanged += CategoryView_SelectedChanged;
@@ -660,7 +662,7 @@ internal class UICatalogApp
 
             List<MenuItem> schemeMenuItems = new ();
 
-            foreach (KeyValuePair<string, ColorScheme> sc in Colors.ColorSchemes)
+            foreach (KeyValuePair<string, ColorScheme?> sc in Colors.ColorSchemes)
             {
                 var item = new MenuItem { Title = $"_{sc.Key}", Data = sc.Key };
                 item.CheckType |= MenuItemCheckStyle.Radio;
@@ -691,17 +693,16 @@ internal class UICatalogApp
         private void CategoryView_SelectedChanged (object? sender, ListViewItemEventArgs? e)
         {
             string item = _categories! [e!.Item];
-            List<Scenario> newlist;
+            ObservableCollection<Scenario> newlist;
 
             if (e.Item == 0)
             {
                 // First category is "All"
                 newlist = _scenarios!;
-                newlist = _scenarios!;
             }
             else
             {
-                newlist = _scenarios!.Where (s => s.GetCategories ().Contains (item)).ToList ();
+                newlist = new (_scenarios!.Where (s => s.GetCategories ().Contains (item)).ToList ());
             }
 
             ScenarioList.Table = new EnumerableTableSource<Scenario> (

+ 5 - 4
UnitTests/UICatalog/ScenarioTests.cs

@@ -1,3 +1,4 @@
+using System.Collections.ObjectModel;
 using System.Diagnostics;
 using System.Reflection;
 using Xunit.Abstractions;
@@ -140,7 +141,7 @@ public class ScenarioTests : TestsAllViews
             Height = Dim.Fill (),
             AllowsMarking = false,
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
-            Source = new ListWrapper (_viewClasses.Keys.ToList ())
+            Source = new ListWrapper<string> (new (_viewClasses.Keys.ToList ()))
         };
         _leftPane.Add (_classListView);
 
@@ -514,7 +515,7 @@ public class ScenarioTests : TestsAllViews
                 && view.GetType ().GetProperty ("Source") != null
                 && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource))
             {
-                var source = new ListWrapper (new List<string> { "Test Text #1", "Test Text #2", "Test Text #3" });
+                var source = new ListWrapper<string> (["Test Text #1", "Test Text #2", "Test Text #3"]);
                 view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
             }
 
@@ -539,10 +540,10 @@ public class ScenarioTests : TestsAllViews
     [Fact]
     public void Run_Generic ()
     {
-        List<Scenario> scenarios = Scenario.GetScenarios ();
+        ObservableCollection<Scenario> scenarios = Scenario.GetScenarios ();
         Assert.NotEmpty (scenarios);
 
-        int item = scenarios.FindIndex (s => s.GetName ().Equals ("Generic", StringComparison.OrdinalIgnoreCase));
+        int item = scenarios.IndexOf (s => s.GetName ().Equals ("Generic", StringComparison.OrdinalIgnoreCase));
         Scenario generic = scenarios [item];
 
         Application.Init (new FakeDriver ());

+ 20 - 19
UnitTests/Views/ComboBoxTests.cs

@@ -1,4 +1,5 @@
-using Xunit.Abstractions;
+using System.Collections.ObjectModel;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewsTests;
 
@@ -9,7 +10,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var cb = new ComboBox
         {
-            Source = new ListWrapper (new List<string> { "One", "Two", "Three" }), SelectedItem = 1
+            Source = new ListWrapper<string> (["One", "Two", "Three"]), SelectedItem = 1
         };
         cb.BeginInit ();
         cb.EndInit ();
@@ -48,7 +49,7 @@ public class ComboBoxTests (ITestOutputHelper output)
             Y = 2,
             Width = 10,
             Height = 20,
-            Source = new ListWrapper (new List<string> { "One", "Two", "Three" })
+            Source = new ListWrapper<string> (["One", "Two", "Three"])
         };
         cb.BeginInit ();
         cb.EndInit ();
@@ -58,7 +59,7 @@ public class ComboBoxTests (ITestOutputHelper output)
         Assert.Equal (new Rectangle (1, 2, 10, 20), cb.Frame);
         Assert.Equal (-1, cb.SelectedItem);
 
-        cb = new ComboBox { Source = new ListWrapper (new List<string> { "One", "Two", "Three" }) };
+        cb = new ComboBox { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
         cb.BeginInit ();
         cb.EndInit ();
         cb.LayoutSubviews ();
@@ -74,7 +75,7 @@ public class ComboBoxTests (ITestOutputHelper output)
         var comboBox = new ComboBox { Text = "0" };
 
         string [] source = Enumerable.Range (0, 15).Select (x => x.ToString ()).ToArray ();
-        comboBox.SetSource (source);
+        comboBox.SetSource (new ObservableCollection<string> (source.ToList ()));
 
         var top = new Toplevel ();
         top.Add (comboBox);
@@ -90,7 +91,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     public void Expanded_Collapsed_Events ()
     {
         var cb = new ComboBox { Height = 4, Width = 5 };
-        List<string> list = new () { "One", "Two", "Three" };
+        ObservableCollection<string> list = ["One", "Two", "Three"];
 
         cb.Expanded += (s, e) => cb.SetSource (list);
         cb.Collapsed += (s, e) => cb.Source = null;
@@ -124,7 +125,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = false };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -179,7 +180,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = false };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -216,7 +217,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = false, ReadOnly = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -275,7 +276,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5 };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -377,7 +378,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -496,7 +497,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Width = 6, Height = 4, HideDropdownListOnClick = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -654,7 +655,7 @@ Three ",
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -717,7 +718,7 @@ Three ",
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -775,7 +776,7 @@ Three ",
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -809,7 +810,7 @@ Three ",
     [AutoInitShutdown]
     public void KeyBindings_Command ()
     {
-        List<string> source = new () { "One", "Two", "Three" };
+        ObservableCollection<string> source = ["One", "Two", "Three"];
         var cb = new ComboBox { Width = 10 };
         cb.SetSource (source);
         var top = new Toplevel ();
@@ -826,7 +827,7 @@ Three ",
         Assert.True (opened);
         Assert.Equal ("Tw", cb.Text);
         Assert.False (cb.IsShow);
-        cb.SetSource (null);
+        cb.SetSource<string> (null);
         Assert.False (cb.NewKeyDownEvent (Key.Enter));
         Assert.True (cb.NewKeyDownEvent (Key.F4)); // with no source also expand empty
         Assert.True (cb.IsShow);
@@ -975,7 +976,7 @@ Three
         top.FocusFirst ();
         Assert.Null (cb.Source);
         Assert.Equal (-1, cb.SelectedItem);
-        List<string> source = new ();
+        ObservableCollection<string> source = [];
         cb.SetSource (source);
         Assert.NotNull (cb.Source);
         Assert.Equal (0, cb.Source.Count);
@@ -1003,7 +1004,7 @@ Three
         Assert.False (cb.IsShow);
         Assert.Equal (-1, cb.SelectedItem); // retains last accept selected item
         Assert.Equal ("", cb.Text); // clear text
-        cb.SetSource (new List<string> ());
+        cb.SetSource (new ObservableCollection<string> ());
         Assert.Equal (0, cb.Source.Count);
         Assert.Equal (-1, cb.SelectedItem);
         Assert.Equal ("", cb.Text);

+ 308 - 40
UnitTests/Views/ListViewTests.cs

@@ -1,4 +1,6 @@
 using System.Collections;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
 using System.ComponentModel;
 using Xunit.Abstractions;
 
@@ -14,7 +16,7 @@ public class ListViewTests (ITestOutputHelper output)
         Assert.True (lv.CanFocus);
         Assert.Equal (-1, lv.SelectedItem);
 
-        lv = new() { Source = new ListWrapper (new List<string> { "One", "Two", "Three" }) };
+        lv = new () { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
         Assert.NotNull (lv.Source);
         Assert.Equal (-1, lv.SelectedItem);
 
@@ -24,7 +26,7 @@ public class ListViewTests (ITestOutputHelper output)
 
         lv = new()
         {
-            Y = 1, Width = 10, Height = 20, Source = new ListWrapper (new List<string> { "One", "Two", "Three" })
+            Y = 1, Width = 10, Height = 20, Source = new ListWrapper<string> (["One", "Two", "Three"])
         };
         Assert.NotNull (lv.Source);
         Assert.Equal (-1, lv.SelectedItem);
@@ -40,14 +42,14 @@ public class ListViewTests (ITestOutputHelper output)
     [AutoInitShutdown]
     public void Ensures_Visibility_SelectedItem_On_MoveDown_And_MoveUp ()
     {
-        List<string> source = new ();
+        ObservableCollection<string> source = [];
 
         for (var i = 0; i < 20; i++)
         {
             source.Add ($"Line{i}");
         }
 
-        var lv = new ListView { Width = Dim.Fill (), Height = Dim.Fill (), Source = new ListWrapper (source) };
+        var lv = new ListView { Width = Dim.Fill (), Height = Dim.Fill (), Source = new ListWrapper<string> (source) };
         var win = new Window ();
         win.Add (lv);
         var top = new Toplevel ();
@@ -124,16 +126,16 @@ public class ListViewTests (ITestOutputHelper output)
         TestHelpers.AssertDriverContentsWithFrameAre (
                                                       @"
 ┌──────────┐
+│Line10    │
+│Line11    │
+│Line12    │
+│Line13    │
+│Line14    │
+│Line15    │
+│Line16    │
+│Line17    │
+│Line18    │
 │Line19    │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
 └──────────┘",
                                                       output
                                                      );
@@ -291,14 +293,14 @@ public class ListViewTests (ITestOutputHelper output)
     [AutoInitShutdown]
     public void EnsureSelectedItemVisible_SelectedItem ()
     {
-        List<string> source = new ();
+        ObservableCollection<string> source = [];
 
         for (var i = 0; i < 10; i++)
         {
             source.Add ($"Item {i}");
         }
 
-        var lv = new ListView { Width = 10, Height = 5, Source = new ListWrapper (source) };
+        var lv = new ListView { Width = 10, Height = 5, Source = new ListWrapper<string> (source) };
         var top = new Toplevel ();
         top.Add (lv);
         Application.Begin (top);
@@ -333,8 +335,8 @@ Item 6",
     [AutoInitShutdown]
     public void EnsureSelectedItemVisible_Top ()
     {
-        List<string> source = new () { "First", "Second" };
-        var lv = new ListView { Width = Dim.Fill (), Height = 1, Source = new ListWrapper (source) };
+        ObservableCollection<string> source = ["First", "Second"];
+        var lv = new ListView { Width = Dim.Fill (), Height = 1, Source = new ListWrapper<string> (source) };
         lv.SelectedItem = 1;
         var top = new Toplevel ();
         top.Add (lv);
@@ -366,8 +368,8 @@ Item 6",
     [Fact]
     public void KeyBindings_Command ()
     {
-        List<string> source = new () { "One", "Two", "Three" };
-        var lv = new ListView { Height = 2, AllowsMarking = true, Source = new ListWrapper (source) };
+        ObservableCollection<string> source = ["One", "Two", "Three"];
+        var lv = new ListView { Height = 2, AllowsMarking = true, Source = new ListWrapper<string> (source) };
         lv.BeginInit ();
         lv.EndInit ();
         Assert.Equal (-1, lv.SelectedItem);
@@ -424,8 +426,8 @@ Item 6",
     [Fact]
     public void Accept_Command_Accepts_and_Opens_Selected_Item ()
     {
-        List<string> source = ["One", "Two", "Three"];
-        var listView = new ListView { Source = new ListWrapper (source) };
+        ObservableCollection<string> source = ["One", "Two", "Three"];
+        var listView = new ListView { Source = new ListWrapper<string> (source) };
         listView.SelectedItem = 0;
 
         var accepted = false;
@@ -455,8 +457,8 @@ Item 6",
     [Fact]
     public void Accept_Cancel_Event_Prevents_OpenSelectedItem ()
     {
-        List<string> source = ["One", "Two", "Three"];
-        var listView = new ListView { Source = new ListWrapper (source) };
+        ObservableCollection<string> source = ["One", "Two", "Three"];
+        var listView = new ListView { Source = new ListWrapper<string> (source) };
         listView.SelectedItem = 0;
 
         var accepted = false;
@@ -494,7 +496,7 @@ Item 6",
     [Fact]
     public void ListViewProcessKeyReturnValue_WithMultipleCommands ()
     {
-        var lv = new ListView { Source = new ListWrapper (new List<string> { "One", "Two", "Three", "Four" }) };
+        var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three", "Four"]) };
 
         Assert.NotNull (lv.Source);
 
@@ -512,7 +514,7 @@ Item 6",
         Assert.Equal (1, lv.SelectedItem);
 
         // clear the items
-        lv.SetSource (null);
+        lv.SetSource<string> (null);
 
         // Press key combo again - return should be false this time as none of the Commands are allowable
         Assert.False (lv.NewKeyDownEvent (ev), "We cannot move down so will not respond to this");
@@ -521,7 +523,7 @@ Item 6",
     [Fact]
     public void ListViewSelectThenDown ()
     {
-        var lv = new ListView { Source = new ListWrapper (new List<string> { "One", "Two", "Three" }) };
+        var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
         lv.AllowsMarking = true;
 
         Assert.NotNull (lv.Source);
@@ -585,7 +587,7 @@ Item 6",
     [Fact]
     public void ListWrapper_StartsWith ()
     {
-        var lw = new ListWrapper (new List<string> { "One", "Two", "Three" });
+        var lw = new ListWrapper<string> (["One", "Two", "Three"]);
 
         Assert.Equal (1, lw.StartsWith ("t"));
         Assert.Equal (1, lw.StartsWith ("tw"));
@@ -594,7 +596,7 @@ Item 6",
         Assert.Equal (1, lw.StartsWith ("TW"));
         Assert.Equal (2, lw.StartsWith ("TH"));
 
-        lw = new (new List<string> { "One", "Two", "Three" });
+        lw = new (["One", "Two", "Three"]);
 
         Assert.Equal (1, lw.StartsWith ("t"));
         Assert.Equal (1, lw.StartsWith ("tw"));
@@ -619,7 +621,7 @@ Item 6",
     public void RowRender_Event ()
     {
         var rendered = false;
-        List<string> source = new () { "one", "two", "three" };
+        ObservableCollection<string> source = ["one", "two", "three"];
         var lv = new ListView { Width = Dim.Fill (), Height = Dim.Fill () };
         lv.RowRender += (s, _) => rendered = true;
         var top = new Toplevel ();
@@ -636,7 +638,7 @@ Item 6",
     [Fact]
     public void SelectedItem_Get_Set ()
     {
-        var lv = new ListView { Source = new ListWrapper (new List<string> { "One", "Two", "Three" }) };
+        var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
         Assert.Equal (-1, lv.SelectedItem);
         Assert.Throws<ArgumentException> (() => lv.SelectedItem = 3);
         Exception exception = Record.Exception (() => lv.SelectedItem = -1);
@@ -646,32 +648,35 @@ Item 6",
     [Fact]
     public void SetSource_Preserves_ListWrapper_Instance_If_Not_Null ()
     {
-        var lv = new ListView { Source = new ListWrapper (new List<string> { "One", "Two" }) };
+        var lv = new ListView { Source = new ListWrapper<string> (["One", "Two"]) };
 
         Assert.NotNull (lv.Source);
 
-        lv.SetSource (null);
+        lv.SetSource<string> (null);
         Assert.NotNull (lv.Source);
 
         lv.Source = null;
         Assert.Null (lv.Source);
 
-        lv = new() { Source = new ListWrapper (new List<string> { "One", "Two" }) };
+        lv = new () { Source = new ListWrapper<string> (["One", "Two"]) };
         Assert.NotNull (lv.Source);
 
-        lv.SetSourceAsync (null);
+        lv.SetSourceAsync<string> (null);
         Assert.NotNull (lv.Source);
     }
 
     [Fact]
     public void SettingEmptyKeybindingThrows ()
     {
-        var lv = new ListView { Source = new ListWrapper (new List<string> { "One", "Two", "Three" }) };
+        var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
         Assert.Throws<ArgumentException> (() => lv.KeyBindings.Add (Key.Space));
     }
 
     private class NewListDataSource : IListDataSource
     {
+        /// <inheritdoc />
+        public event NotifyCollectionChangedEventHandler CollectionChanged;
+
         public int Count => 0;
         public int Length => 0;
         public bool IsMarked (int item) { throw new NotImplementedException (); }
@@ -692,6 +697,11 @@ Item 6",
 
         public void SetMark (int item, bool value) { throw new NotImplementedException (); }
         public IList ToList () { return new List<string> { "One", "Two", "Three" }; }
+
+        public void Dispose ()
+        {
+            throw new NotImplementedException ();
+        }
     }
 
     [Fact]
@@ -706,7 +716,7 @@ Item 6",
             Width = 7,
             BorderStyle = LineStyle.Single
         };
-        lv.SetSource (new List<string> { "One", "Two", "Three", "Four" });
+        lv.SetSource (["One", "Two", "Three", "Four"]);
         lv.SelectedItemChanged += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (lv);
@@ -767,9 +777,9 @@ Item 6",
     [AutoInitShutdown]
     public void LeftItem_TopItem_Tests ()
     {
-        List<string> source = new List<string> ();
+        ObservableCollection<string> source = [];
 
-        for (var i = 0; i < 5; i++)
+        for (int i = 0; i < 5; i++)
         {
             source.Add ($"Item {i}");
         }
@@ -779,7 +789,7 @@ Item 6",
             X = 1,
             Width = 10,
             Height = 5,
-            Source = new ListWrapper (source)
+            Source = new ListWrapper<string> (source)
         };
         var top = new Toplevel ();
         top.Add (lv);
@@ -807,4 +817,262 @@ Item 6",
                                                       output);
         top.Dispose ();
     }
-}
+
+    [Fact]
+    public void CollectionChanged_Event ()
+    {
+        var added = 0;
+        var removed = 0;
+        ObservableCollection<string> source = [];
+        var lv = new ListView { Source = new ListWrapper<string> (source) };
+
+        lv.CollectionChanged += (sender, args) =>
+                                {
+                                    if (args.Action == NotifyCollectionChangedAction.Add)
+                                    {
+                                        added++;
+                                    }
+                                    else if (args.Action == NotifyCollectionChangedAction.Remove)
+                                    {
+                                        removed++;
+                                    }
+                                };
+
+        for (int i = 0; i < 3; i++)
+        {
+            source.Add ($"Item{i}");
+        }
+        Assert.Equal (3, added);
+        Assert.Equal (0, removed);
+
+        added = 0;
+
+        for (int i = 0; i < 3; i++)
+        {
+            source.Remove (source [0]);
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (3, removed);
+        Assert.Empty (source);
+    }
+
+    [Fact]
+    public void CollectionChanged_Event_Is_Only_Subscribed_Once ()
+    {
+        var added = 0;
+        var removed = 0;
+        var otherActions = 0;
+        IList<string> source1 = [];
+        var lv = new ListView { Source = new ListWrapper<string> (new ( source1)) };
+
+        lv.CollectionChanged += (sender, args) =>
+                                {
+                                    if (args.Action == NotifyCollectionChangedAction.Add)
+                                    {
+                                        added++;
+                                    }
+                                    else if (args.Action == NotifyCollectionChangedAction.Remove)
+                                    {
+                                        removed++;
+                                    }
+                                    else
+                                    {
+                                        otherActions++;
+                                    }
+                                };
+
+        ObservableCollection<string> source2 = [];
+        lv.Source = new ListWrapper<string> (source2);
+        ObservableCollection<string> source3 = [];
+        lv.Source = new ListWrapper<string> (source3);
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Add ($"Item{i}");
+            source2.Add ($"Item{i}");
+            source3.Add ($"Item{i}");
+        }
+        Assert.Equal (3, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        added = 0;
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Remove (source1 [0]);
+            source2.Remove (source2 [0]);
+            source3.Remove (source3 [0]);
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (3, removed);
+        Assert.Equal (0, otherActions);
+        Assert.Empty (source1);
+        Assert.Empty (source2);
+        Assert.Empty (source3);
+    }
+
+    [Fact]
+    public void CollectionChanged_Event_UnSubscribe_Previous_If_New_Is_Null ()
+    {
+        var added = 0;
+        var removed = 0;
+        var otherActions = 0;
+        ObservableCollection<string> source1 = [];
+        var lv = new ListView { Source = new ListWrapper<string> (source1) };
+
+        lv.CollectionChanged += (sender, args) =>
+        {
+            if (args.Action == NotifyCollectionChangedAction.Add)
+            {
+                added++;
+            }
+            else if (args.Action == NotifyCollectionChangedAction.Remove)
+            {
+                removed++;
+            }
+            else
+            {
+                otherActions++;
+            }
+        };
+
+        lv.Source = new ListWrapper<string> (null);
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Add ($"Item{i}");
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Remove (source1 [0]);
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+        Assert.Empty (source1);
+    }
+
+    [Fact]
+    public void ListWrapper_CollectionChanged_Event_Is_Only_Subscribed_Once ()
+    {
+        var added = 0;
+        var removed = 0;
+        var otherActions = 0;
+        ObservableCollection<string> source1 = [];
+        ListWrapper<string> lw = new (source1);
+
+        lw.CollectionChanged += (sender, args) =>
+        {
+            if (args.Action == NotifyCollectionChangedAction.Add)
+            {
+                added++;
+            }
+            else if (args.Action == NotifyCollectionChangedAction.Remove)
+            {
+                removed++;
+            }
+            else
+            {
+                otherActions++;
+            }
+        };
+
+        ObservableCollection<string> source2 = [];
+        lw = new (source2);
+        ObservableCollection<string> source3 = [];
+        lw = new (source3);
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Add ($"Item{i}");
+            source2.Add ($"Item{i}");
+            source3.Add ($"Item{i}");
+        }
+
+        Assert.Equal (3, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        added = 0;
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Remove (source1 [0]);
+            source2.Remove (source2 [0]);
+            source3.Remove (source3 [0]);
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (3, removed);
+        Assert.Equal (0, otherActions);
+        Assert.Empty (source1);
+        Assert.Empty (source2);
+        Assert.Empty (source3);
+    }
+
+    [Fact]
+    public void ListWrapper_CollectionChanged_Event_UnSubscribe_Previous_Is_Disposed ()
+    {
+        var added = 0;
+        var removed = 0;
+        var otherActions = 0;
+        ObservableCollection<string> source1 = [];
+        ListWrapper<string> lw = new (source1);
+
+        lw.CollectionChanged += Lw_CollectionChanged;
+
+        lw.Dispose ();
+        lw = new (null);
+        Assert.Equal (0, lw.Count);
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Add ($"Item{i}");
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Remove (source1 [0]);
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+        Assert.Empty (source1);
+
+
+        void Lw_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+        {
+            if (e.Action == NotifyCollectionChangedAction.Add)
+            {
+                added++;
+            }
+            else if (e.Action == NotifyCollectionChangedAction.Remove)
+            {
+                removed++;
+            }
+            else
+            {
+                otherActions++;
+            }
+        }
+    }
+}

+ 7 - 10
UnitTests/Views/MenuBarTests.cs

@@ -1235,11 +1235,11 @@ wo
     [InlineData ("_Edit", "F_ind", "", KeyCode.AltMask | KeyCode.E, KeyCode.F)]
     [InlineData ("_Edit", "F_ind", "", KeyCode.AltMask | KeyCode.E, KeyCode.AltMask | KeyCode.F)]
     [InlineData ("Closed", "None", "Replace", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.R)]
-    [InlineData ("Closed", "None", "", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.C)]
+    [InlineData ("Closed", "None", "Copy", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.C)]
     [InlineData ("_Edit", "_1st", "", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3)]
     [InlineData ("Closed", "None", "1", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D1)]
     [InlineData ("Closed", "None", "1", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.Enter)]
-    [InlineData ("Closed", "None", "", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D4)]
+    [InlineData ("_Edit", "_3rd Level", "", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D4)]
     [InlineData ("Closed", "None", "5", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D4, KeyCode.D5)]
     [InlineData ("_About", "_About", "", KeyCode.AltMask | KeyCode.A)]
     public void KeyBindings_Navigation_Commands (
@@ -2246,7 +2246,6 @@ wo
         Assert.True (menu.NewKeyDownEvent (menu.Key));
         Assert.True (menu.IsMenuOpen);
 
-        // BUGBUG: This is wrong -> The _New doc isn't enabled because it can't execute and so can't be selected
         // The _New doc is enabled but the sub-menu isn't enabled. Is show but can't be selected and executed
         Assert.Equal ("_New", miCurrent.Parent.Title);
         Assert.Equal ("_New doc", miCurrent.Title);
@@ -2399,7 +2398,7 @@ Edit
         menu.MenuOpened += (s, e) =>
                            {
                                miCurrent = e.MenuItem;
-                               mCurrent = menu.openCurrentMenu;
+                               mCurrent = menu.OpenCurrentMenu;
                            };
         var top = new Toplevel ();
         top.Add (menu);
@@ -2770,7 +2769,7 @@ Edit
     }
 
     [Fact]
-    public void Separators_Does_Not_Throws_Pressing_Menu_Shortcut ()
+    public void Separator_Does_Not_Throws_Pressing_Menu_Hotkey ()
     {
         var menu = new MenuBar
         {
@@ -2782,9 +2781,7 @@ Edit
                     )
             ]
         };
-
-        Exception exception = Record.Exception (() => Assert.True (menu.NewKeyDownEvent (Key.Q.WithAlt)));
-        Assert.Null (exception);
+        Assert.False (menu.NewKeyDownEvent (Key.Q.WithAlt));
     }
 
     [Fact]
@@ -3126,7 +3123,7 @@ Edit
         Assert.True (menu._openMenu.NewKeyDownEvent (Key.CursorDown));
         menu.Draw ();
         menu._openMenu.Draw ();
-        menu.openCurrentMenu.Draw ();
+        menu.OpenCurrentMenu.Draw ();
 
         expected = @"
  Numbers           
@@ -3419,7 +3416,7 @@ Edit
         Assert.True (menu._openMenu.NewKeyDownEvent (Key.Enter));
         menu.Draw ();
         menu._openMenu.Draw ();
-        menu.openCurrentMenu.Draw ();
+        menu.OpenCurrentMenu.Draw ();
 
         expected = @"
  Numbers     

+ 52 - 19
UnitTests/Views/RadioGroupTests.cs

@@ -14,12 +14,12 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.Equal (Rectangle.Empty, rg.Frame);
         Assert.Equal (0, rg.SelectedItem);
 
-        rg = new() { RadioLabels = new [] { "Test" } };
+        rg = new () { RadioLabels = new [] { "Test" } };
         Assert.True (rg.CanFocus);
         Assert.Single (rg.RadioLabels);
         Assert.Equal (0, rg.SelectedItem);
 
-        rg = new()
+        rg = new ()
         {
             X = 1,
             Y = 2,
@@ -32,7 +32,7 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.Equal (new (1, 2, 20, 5), rg.Frame);
         Assert.Equal (0, rg.SelectedItem);
 
-        rg = new() { X = 1, Y = 2, RadioLabels = new [] { "Test" } };
+        rg = new () { X = 1, Y = 2, RadioLabels = new [] { "Test" } };
 
         var view = new View { Width = 30, Height = 40 };
         view.Add (rg);
@@ -130,6 +130,51 @@ public class RadioGroupTests (ITestOutputHelper output)
         Assert.Equal (1, rg.SelectedItem);
     }
 
+    [Fact]
+    public void HotKey_For_View_SetsFocus ()
+    {
+        var superView = new View
+        {
+            CanFocus = true
+        };
+        superView.Add (new View { CanFocus = true });
+
+        var group = new RadioGroup
+        {
+            Title = "Radio_Group",
+            RadioLabels = new [] { "_Left", "_Right", "Cen_tered", "_Justified" }
+        };
+        superView.Add (group);
+
+        Assert.False (group.HasFocus);
+        Assert.Equal (0, group.SelectedItem);
+
+        group.NewKeyDownEvent (Key.G.WithAlt);
+
+        Assert.Equal (0, group.SelectedItem);
+        Assert.True (group.HasFocus);
+    }
+
+    [Fact]
+    public void HotKey_For_Item_SetsFocus ()
+    {
+        var superView = new View
+        {
+            CanFocus = true
+        };
+        superView.Add (new View { CanFocus = true });
+        var group = new RadioGroup { RadioLabels = new [] { "_Left", "_Right", "Cen_tered", "_Justified" } };
+        superView.Add (group);
+
+        Assert.False (group.HasFocus);
+        Assert.Equal (0, group.SelectedItem);
+
+        group.NewKeyDownEvent (Key.R);
+
+        Assert.Equal (1, group.SelectedItem);
+        Assert.True (group.HasFocus);
+    }
+
     [Fact]
     public void HotKey_Command_Does_Not_Accept ()
     {
@@ -184,12 +229,8 @@ public class RadioGroupTests (ITestOutputHelper output)
 
         var expected = @$"
 ┌────────────────────────────┐
-│{
-    CM.Glyphs.Selected
-} Test                      │
-│{
-    CM.Glyphs.UnSelected
-} New Test 你               │
+│{CM.Glyphs.Selected} Test                      │
+│{CM.Glyphs.UnSelected} New Test 你               │
 │                            │
 └────────────────────────────┘
 ";
@@ -209,11 +250,7 @@ public class RadioGroupTests (ITestOutputHelper output)
 
         expected = @$"
 ┌────────────────────────────┐
-│{
-    CM.Glyphs.Selected
-} Test  {
-    CM.Glyphs.UnSelected
-} New Test 你       │
+│{CM.Glyphs.Selected} Test  {CM.Glyphs.UnSelected} New Test 你       │
 │                            │
 │                            │
 └────────────────────────────┘
@@ -234,11 +271,7 @@ public class RadioGroupTests (ITestOutputHelper output)
 
         expected = @$"
 ┌────────────────────────────┐
-│{
-    CM.Glyphs.Selected
-} Test    {
-    CM.Glyphs.UnSelected
-} New Test 你     │
+│{CM.Glyphs.Selected} Test    {CM.Glyphs.UnSelected} New Test 你     │
 │                            │
 │                            │
 └────────────────────────────┘

+ 6 - 5
UnitTests/Views/ScrollBarViewTests.cs

@@ -1,4 +1,5 @@
-using System.Reflection;
+using System.Collections.ObjectModel;
+using System.Reflection;
 using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewsTests;
@@ -381,7 +382,7 @@ This is a tes
 
                                               var win = new Window { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
 
-                                              List<string> source = new ();
+                                              ObservableCollection<string> source = [];
 
                                               for (var i = 0; i < 50; i++)
                                               {
@@ -402,7 +403,7 @@ This is a tes
                                                   Y = 0,
                                                   Width = Dim.Fill (),
                                                   Height = Dim.Fill (),
-                                                  Source = new ListWrapper (source)
+                                                  Source = new ListWrapper<string> (source)
                                               };
                                               win.Add (listView);
 
@@ -469,7 +470,7 @@ This is a tes
                                                     Application.Init (new FakeDriver ());
                                                     Toplevel top = new ();
                                                     var win = new Window { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
-                                                    List<string> source = new ();
+                                                    ObservableCollection<string> source = [];
 
                                                     for (var i = 0; i < 50; i++)
                                                     {
@@ -482,7 +483,7 @@ This is a tes
                                                         Y = 0,
                                                         Width = Dim.Fill (),
                                                         Height = Dim.Fill (),
-                                                        Source = new ListWrapper (source)
+                                                        Source = new ListWrapper<string> (source)
                                                     };
                                                     win.Add (listView);
                                                     var newScrollBarView = new ScrollBarView (listView, true, false) { KeepContentAlwaysInViewport = true };

+ 1 - 1
UnitTests/Views/StatusBarTests.cs

@@ -187,7 +187,7 @@ CTRL-O Open {
         Assert.False (sb.CanFocus);
         Assert.Equal (Colors.ColorSchemes ["Menu"], sb.ColorScheme);
         Assert.Equal (0, sb.X);
-        Assert.Equal ("AnchorEnd(1)", sb.Y.ToString ());
+        Assert.Equal ("AnchorEnd()", sb.Y.ToString ());
         Assert.Equal (Dim.Fill (), sb.Width);
         Assert.Equal (1, sb.Height);
     }