浏览代码

New branch to correct merge problem

Tig 1 年之前
父节点
当前提交
7213588973

+ 72 - 24
Terminal.Gui/Application.cs

@@ -99,7 +99,6 @@ public static partial class Application
         // Don't dispose the Top. It's up to caller dispose it
         if (Top is { })
         {
-
             Debug.Assert (Top.WasDisposed);
 
             // If End wasn't called _cachedRunStateToplevel may be null
@@ -539,6 +538,7 @@ public static partial class Application
             toplevel.SetNeedsDisplay ();
             toplevel.Draw ();
             Driver.UpdateScreen ();
+
             if (PositionCursor (toplevel))
             {
                 Driver.UpdateCursor ();
@@ -551,13 +551,14 @@ public static partial class Application
     }
 
     /// <summary>
-    /// Calls <see cref="View.PositionCursor"/> on the most focused view in the view starting with <paramref name="view"/>.
+    ///     Calls <see cref="View.PositionCursor"/> on the most focused view in the view starting with <paramref name="view"/>.
     /// </summary>
     /// <remarks>
-    /// Does nothing if <paramref name="view"/> is <see langword="null"/> or if the most focused view is not visible or enabled.
-    /// <para>
-    /// If the most focused view is not visible within it's superview, the cursor will be hidden.
-    /// </para>
+    ///     Does nothing if <paramref name="view"/> is <see langword="null"/> or if the most focused view is not visible or
+    ///     enabled.
+    ///     <para>
+    ///         If the most focused view is not visible within it's superview, the cursor will be hidden.
+    ///     </para>
     /// </remarks>
     /// <returns><see langword="true"/> if a view positioned the cursor and the position is visible.</returns>
     internal static bool PositionCursor (View view)
@@ -581,6 +582,7 @@ public static partial class Application
         if (!mostFocused.Visible || !mostFocused.Enabled)
         {
             Driver.GetCursorVisibility (out CursorVisibility current);
+
             if (current != CursorVisibility.Invisible)
             {
                 Driver.SetCursorVisibility (CursorVisibility.Invisible);
@@ -592,6 +594,7 @@ public static partial class Application
         // If the view is not visible within it's superview, don't position the cursor
         Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty });
         Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver.Screen;
+
         if (!superViewViewport.IntersectsWith (mostFocusedViewport))
         {
             return false;
@@ -673,7 +676,7 @@ public static partial class Application
     /// </param>
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     public static T Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null)
-        where T : Toplevel, new()
+        where T : Toplevel, new ()
     {
         var top = new T ();
 
@@ -960,6 +963,7 @@ public static partial class Application
         {
             state.Toplevel.Draw ();
             Driver.UpdateScreen ();
+
             //Driver.UpdateCursor ();
         }
 
@@ -1604,7 +1608,6 @@ public static partial class Application
             WantContinuousButtonPressedView = null;
         }
 
-
         if (view is not Adornment)
         {
             if ((view is null || view == OverlappedTop)
@@ -1854,25 +1857,17 @@ public static partial class Application
             }
         }
 
-        // Invoke any Global KeyBindings
-        foreach (Toplevel topLevel in _topLevels.ToList ())
+        // Invoke any global (Application-scoped) KeyBindings.
+        // The first view that handles the key will stop the loop.
+        foreach (KeyValuePair<Key, List<View>> binding in _keyBindings.Where (b => b.Key == keyEvent.KeyCode))
         {
-            foreach (View view in topLevel.Subviews.Where (
-                                                           v => v.KeyBindings.TryGet (
-                                                                                      keyEvent,
-                                                                                      KeyBindingScope.Application,
-                                                                                      out KeyBinding _
-                                                                                     )
-                                                          ))
+            foreach (View view in binding.Value)
             {
-                if (view.KeyBindings.TryGet (keyEvent.KeyCode, KeyBindingScope.Application, out KeyBinding _))
-                {
-                    bool? handled = view.OnInvokingKeyBindings (keyEvent);
+                bool? handled = view?.OnInvokingKeyBindings (keyEvent);
 
-                    if (handled is { } && (bool)handled)
-                    {
-                        return true;
-                    }
+                if (handled != null && (bool)handled)
+                {
+                    return true;
                 }
             }
         }
@@ -1931,5 +1926,58 @@ public static partial class Application
         return false;
     }
 
+    /// <summary>
+    ///     The <see cref="KeyBindingScope.Application"/> key bindings.
+    /// </summary>
+    private static readonly Dictionary<Key, List<View>> _keyBindings = new ();
+
+    /// <summary>
+    ///     Adds an  <see cref="KeyBindingScope.Application"/> scoped key binding.
+    /// </summary>
+    /// <remarks>
+    ///     This is an internal method used by the <see cref="View"/> class to add Application key bindings.
+    /// </remarks>
+    /// <param name="key">The key being bound.</param>
+    /// <param name="view">The view that is bound to the key.</param>
+    internal static void AddKeyBinding (Key key, View view)
+    {
+        if (!_keyBindings.ContainsKey (key))
+        {
+            _keyBindings [key] = [];
+        }
+        _keyBindings [key].Add (view);
+    }
+
+    /// <summary>
+    ///     Removes an <see cref="KeyBindingScope.Application"/> scoped key binding.
+    /// </summary>
+    /// <remarks>
+    ///     This is an internal method used by the <see cref="View"/> class to remove Application key bindings.
+    /// </remarks>
+    /// <param name="key">The key that was bound.</param>
+    /// <param name="view">The view that is bound to the key.</param>
+    internal static void RemoveKeyBinding (Key key, View view)
+    {
+        if (_keyBindings.TryGetValue (key, out List<View> binding))
+        {
+            binding.Remove (view);
+        }
+    }
+
+    /// <summary>
+    ///     Removes all <see cref="KeyBindingScope.Application"/> scoped key bindings for the specified view.
+    /// </summary>
+    /// <remarks>
+    ///     This is an internal method used by the <see cref="View"/> class to remove Application key bindings.
+    /// </remarks>
+    /// <param name="view">The view that is bound to the key.</param>
+    internal static void RemoveAllKeyBindings (View view)
+    {
+        foreach (Key key in _keyBindings.Keys)
+        {
+            _keyBindings [key].Remove (view);
+        }
+    }
+
     #endregion Keyboard handling
 }

+ 1 - 238
Terminal.Gui/Input/KeyBinding.cs

@@ -3,46 +3,6 @@
 
 namespace Terminal.Gui;
 
-/// <summary>
-///     Defines the scope of a <see cref="Command"/> that has been bound to a key with
-///     <see cref="KeyBindings.Add(Key, Terminal.Gui.Command[])"/>.
-/// </summary>
-/// <remarks>
-///     <para>Key bindings are scoped to the most-focused view (<see cref="Focused"/>) by default.</para>
-/// </remarks>
-[Flags]
-public enum KeyBindingScope
-{
-    /// <summary>The key binding is scoped to just the view that has focus.</summary>
-    Focused = 1,
-
-    /// <summary>
-    ///     The key binding is scoped to the View's SuperView and will be triggered even when the View does not have focus, as
-    ///     long as the SuperView does have focus. This is typically used for <see cref="View.HotKey"/>s.
-    ///     <remarks>
-    ///         <para>
-    ///             Use for Views such as MenuBar and StatusBar which provide commands (shortcuts etc...) that trigger even
-    ///             when not focused.
-    ///         </para>
-    ///         <para>
-    ///             HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or
-    ///             any of its subviews.
-    ///         </para>
-    ///     </remarks>
-    /// </summary>
-    HotKey = 2,
-
-    /// <summary>
-    ///     The key binding will be triggered regardless of which view has focus. This is typically used for global
-    ///     commands.
-    /// </summary>
-    /// <remarks>
-    ///     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 down event was not bound to a <see cref="View.HotKey"/>.
-    /// </remarks>
-    Application = 4
-}
-
 /// <summary>Provides a collection of <see cref="Command"/> objects that are scoped to <see cref="KeyBindingScope"/>.</summary>
 public class KeyBinding
 {
@@ -60,201 +20,4 @@ public class KeyBinding
 
     /// <summary>The scope of the <see cref="Commands"/> bound to a key.</summary>
     public KeyBindingScope Scope { get; set; }
-}
-
-/// <summary>A class that provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.</summary>
-public class KeyBindings
-{
-    // TODO: Add a dictionary comparer that ignores Scope
-    /// <summary>The collection of <see cref="KeyBinding"/> objects.</summary>
-    public Dictionary<Key, KeyBinding> Bindings { get; } = new ();
-
-    /// <summary>Adds a <see cref="KeyBinding"/> to the collection.</summary>
-    /// <param name="key"></param>
-    /// <param name="binding"></param>
-    public void Add (Key key, KeyBinding binding) { Bindings.Add (key, binding); }
-
-    /// <summary>
-    ///     <para>Adds a new key combination that will trigger the commands in <paramref name="commands"/>.</para>
-    ///     <para>
-    ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
-    ///         <paramref name="commands"/>.
-    ///     </para>
-    /// </summary>
-    /// <remarks>
-    ///     Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
-    ///     focus to another view and perform multiple commands there).
-    /// </remarks>
-    /// <param name="key">The key to check.</param>
-    /// <param name="scope">The scope for the command.</param>
-    /// <param name="commands">
-    ///     The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
-    ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
-    ///     consumed if any took effect.
-    /// </param>
-    public void Add (Key key, KeyBindingScope scope, params Command [] commands)
-    {
-        if (key is null || !key.IsValid)
-        {
-            //throw new ArgumentException ("Invalid Key", nameof (commands));
-            return;
-        }
-        
-        if (commands.Length == 0)
-        {
-            throw new ArgumentException (@"At least one command must be specified", nameof (commands));
-        }
-
-        if (TryGet (key, out KeyBinding _))
-        {
-            Bindings [key] = new KeyBinding (commands, scope);
-        }
-        else
-        {
-            Bindings.Add (key, new KeyBinding (commands, scope));
-        }
-    }
-
-    /// <summary>
-    ///     <para>
-    ///         Adds a new key combination that will trigger the commands in <paramref name="commands"/> (if supported by the
-    ///         View - see <see cref="View.GetSupportedCommands"/>).
-    ///     </para>
-    ///     <para>
-    ///         This is a helper function for <see cref="Add(Key,KeyBindingScope,Terminal.Gui.Command[])"/> for
-    ///         <see cref="KeyBindingScope.Focused"/> scoped commands.
-    ///     </para>
-    ///     <para>
-    ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
-    ///         <paramref name="commands"/>.
-    ///     </para>
-    /// </summary>
-    /// <remarks>
-    ///     Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
-    ///     focus to another view and perform multiple commands there).
-    /// </remarks>
-    /// <param name="key">The key to check.</param>
-    /// <param name="commands">
-    ///     The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
-    ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
-    ///     consumed if any took effect.
-    /// </param>
-    public void Add (Key key, params Command [] commands) { Add (key, KeyBindingScope.Focused, commands); }
-
-    /// <summary>Removes all <see cref="KeyBinding"/> objects from the collection.</summary>
-    public void Clear () { Bindings.Clear (); }
-
-    /// <summary>
-    ///     Removes all key bindings that trigger the given command set. Views can have multiple different keys bound to
-    ///     the same command sets and this method will clear all of them.
-    /// </summary>
-    /// <param name="command"></param>
-    public void Clear (params Command [] command)
-    {
-        var kvps = Bindings
-                   .Where (kvp => kvp.Value.Commands.SequenceEqual (command))
-                   .ToArray ();
-        foreach (KeyValuePair<Key, KeyBinding> kvp in kvps)
-        {
-            Bindings.Remove (kvp.Key);
-        }
-    }
-
-    /// <summary>Gets the <see cref="KeyBinding"/> for the specified <see cref="Key"/>.</summary>
-    /// <param name="key"></param>
-    /// <returns></returns>
-    public KeyBinding Get (Key key) { return TryGet (key, out KeyBinding binding) ? binding : null; }
-
-    /// <summary>Gets the <see cref="KeyBinding"/> for the specified <see cref="Key"/>.</summary>
-    /// <param name="key"></param>
-    /// <param name="scope"></param>
-    /// <returns></returns>
-    public KeyBinding Get (Key key, KeyBindingScope scope) { return TryGet (key, scope, out KeyBinding binding) ? binding : null; }
-
-    /// <summary>Gets the array of <see cref="Command"/>s bound to <paramref name="key"/> if it exists.</summary>
-    /// <param name="key">The key to check.</param>
-    /// <returns>
-    ///     The array of <see cref="Command"/>s if <paramref name="key"/> is bound. An empty <see cref="Command"/> array
-    ///     if not.
-    /// </returns>
-    public Command [] GetCommands (Key key)
-    {
-        if (TryGet (key, out KeyBinding bindings))
-        {
-            return bindings.Commands;
-        }
-
-        return Array.Empty<Command> ();
-    }
-
-    /// <summary>Gets the Key used by a set of commands.</summary>
-    /// <remarks></remarks>
-    /// <param name="commands">The set of commands to search.</param>
-    /// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
-    /// <exception cref="InvalidOperationException">If no matching set of commands was found.</exception>
-    public Key GetKeyFromCommands (params Command [] commands) { return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key; }
-
-    /// <summary>Removes a <see cref="KeyBinding"/> from the collection.</summary>
-    /// <param name="key"></param>
-    public void Remove (Key key) { Bindings.Remove (key); }
-
-    /// <summary>Replaces a key combination already bound to a set of <see cref="Command"/>s.</summary>
-    /// <remarks></remarks>
-    /// <param name="fromKey">The key to be replaced.</param>
-    /// <param name="toKey">The new key to be used.</param>
-    public void Replace (Key fromKey, Key toKey)
-    {
-        if (!TryGet (fromKey, out KeyBinding _))
-        {
-            return;
-        }
-
-        KeyBinding value = Bindings [fromKey];
-        Bindings.Remove (fromKey);
-        Bindings [toKey] = value;
-    }
-
-    /// <summary>Gets the commands bound with the specified Key.</summary>
-    /// <remarks></remarks>
-    /// <param name="key">The key to check.</param>
-    /// <param name="binding">
-    ///     When this method returns, contains the commands bound with the specified Key, if the Key is
-    ///     found; otherwise, null. This parameter is passed uninitialized.
-    /// </param>
-    /// <returns><see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.</returns>
-    public bool TryGet (Key key, out KeyBinding binding)
-    {
-        if (key.IsValid)
-        {
-            return Bindings.TryGetValue (key, out binding);
-        }
-
-        binding = new KeyBinding (Array.Empty<Command> (), KeyBindingScope.Focused);
-
-        return false;
-    }
-
-    /// <summary>Gets the commands bound with the specified Key that are scoped to a particular scope.</summary>
-    /// <remarks></remarks>
-    /// <param name="key">The key to check.</param>
-    /// <param name="scope">the scope to filter on</param>
-    /// <param name="binding">
-    ///     When this method returns, contains the commands bound with the specified Key, if the Key is
-    ///     found; otherwise, null. This parameter is passed uninitialized.
-    /// </param>
-    /// <returns><see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.</returns>
-    public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding)
-    {
-        if (key.IsValid && Bindings.TryGetValue (key, out binding))
-        {
-            if (scope.HasFlag (binding.Scope))
-            {
-                return true;
-            }
-        }
-
-        binding = new KeyBinding (Array.Empty<Command> (), KeyBindingScope.Focused);
-
-        return false;
-    }
-}
+}

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

@@ -0,0 +1,49 @@
+using Terminal.Gui.Analyzers.Internal.Attributes;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Defines the scope of a <see cref="Command"/> that has been bound to a key with
+///     <see cref="KeyBindings.Add(Key, Terminal.Gui.Command[])"/>.
+/// </summary>
+/// <remarks>
+///     <para>Key bindings are scoped to the most-focused view (<see cref="Focused"/>) by default.</para>
+/// </remarks>
+[Flags]
+[GenerateEnumExtensionMethods (FastHasFlags = true)]
+public enum KeyBindingScope
+{
+    /// <summary>The key binding is scoped to just the view that has focus.</summary>
+    Focused = 1,
+
+    /// <summary>
+    ///     The key binding is scoped to the View's Superview hierarchy and will be triggered even when the View does not have
+    ///     focus, as
+    ///     long as the SuperView does have focus. This is typically used for <see cref="View.HotKey"/>s.
+    ///     <remarks>
+    ///         <para>
+    ///             The View must be visible.
+    ///         </para>
+    ///         <para>
+    ///             HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or
+    ///             any of its subviews.
+    ///         </para>
+    ///     </remarks>
+    /// </summary>
+    HotKey = 2,
+
+    /// <summary>
+    ///     The key binding will be triggered regardless of which view has focus. This is typically used for global
+    ///     commands, which are called Shortcuts.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         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
+}

+ 230 - 0
Terminal.Gui/Input/KeyBindings.cs

@@ -0,0 +1,230 @@
+namespace Terminal.Gui;
+
+/// <summary>Provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.</summary>
+public class KeyBindings
+{
+    /// <summary>
+    ///     Initializes a new instance. This constructor is used when the <see cref="KeyBindings"/> are not bound to a
+    ///     <see cref="View"/>, such as in unit tests.
+    /// </summary>
+    public KeyBindings () { }
+
+    /// <summary>Initializes a new instance bound to <paramref name="boundView"/>.</summary>
+    public KeyBindings (View boundView) { BoundView = boundView; }
+
+    /// <summary>
+    ///     The view that the <see cref="KeyBindings"/> are bound to.
+    /// </summary>
+    public View BoundView { get; }
+
+    // TODO: Add a dictionary comparer that ignores Scope
+    // TODO: This should not be public!
+    /// <summary>The collection of <see cref="KeyBinding"/> objects.</summary>
+    public Dictionary<Key, KeyBinding> Bindings { get; } = new ();
+
+    /// <summary>Adds a <see cref="KeyBinding"/> to the collection.</summary>
+    /// <param name="key"></param>
+    /// <param name="binding"></param>
+    public void Add (Key key, KeyBinding binding)
+    {
+        Bindings.Add (key, binding);
+    }
+
+    /// <summary>
+    ///     <para>Adds a new key combination that will trigger the commands in <paramref name="commands"/>.</para>
+    ///     <para>
+    ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
+    ///         <paramref name="commands"/>.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
+    ///     focus to another view and perform multiple commands there).
+    /// </remarks>
+    /// <param name="key">The key to check.</param>
+    /// <param name="scope">The scope for the command.</param>
+    /// <param name="commands">
+    ///     The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
+    ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
+    ///     consumed if any took effect.
+    /// </param>
+    public void Add (Key key, KeyBindingScope scope, params Command [] commands)
+    {
+        if (key is null || !key.IsValid)
+        {
+            //throw new ArgumentException ("Invalid Key", nameof (commands));
+            return;
+        }
+
+        if (commands.Length == 0)
+        {
+            throw new ArgumentException (@"At least one command must be specified", nameof (commands));
+        }
+
+        if (TryGet (key, out KeyBinding _))
+        {
+            Bindings [key] = new (commands, scope);
+        }
+        else
+        {
+            Add (key, new KeyBinding (commands, scope));
+            if (scope.HasFlag (KeyBindingScope.Application))
+            {
+                Application.AddKeyBinding (key, BoundView);
+            }
+        }
+    }
+
+    /// <summary>
+    ///     <para>
+    ///         Adds a new key combination that will trigger the commands in <paramref name="commands"/> (if supported by the
+    ///         View - see <see cref="View.GetSupportedCommands"/>).
+    ///     </para>
+    ///     <para>
+    ///         This is a helper function for <see cref="Add(Key,KeyBindingScope,Terminal.Gui.Command[])"/> for
+    ///         <see cref="KeyBindingScope.Focused"/> scoped commands.
+    ///     </para>
+    ///     <para>
+    ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
+    ///         <paramref name="commands"/>.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
+    ///     focus to another view and perform multiple commands there).
+    /// </remarks>
+    /// <param name="key">The key to check.</param>
+    /// <param name="commands">
+    ///     The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
+    ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
+    ///     consumed if any took effect.
+    /// </param>
+    public void Add (Key key, params Command [] commands) { Add (key, KeyBindingScope.Focused, commands); }
+
+    /// <summary>Removes all <see cref="KeyBinding"/> objects from the collection.</summary>
+    public void Clear ()
+    {
+        Application.RemoveAllKeyBindings (BoundView);
+
+        Bindings.Clear ();
+    }
+
+    /// <summary>
+    ///     Removes all key bindings that trigger the given command set. Views can have multiple different keys bound to
+    ///     the same command sets and this method will clear all of them.
+    /// </summary>
+    /// <param name="command"></param>
+    public void Clear (params Command [] command)
+    {
+        KeyValuePair<Key, KeyBinding> [] kvps = Bindings
+                                                .Where (kvp => kvp.Value.Commands.SequenceEqual (command))
+                                                .ToArray ();
+
+        foreach (KeyValuePair<Key, KeyBinding> kvp in kvps)
+        {
+            Remove (kvp.Key);
+        }
+    }
+
+    /// <summary>Gets the <see cref="KeyBinding"/> for the specified <see cref="Key"/>.</summary>
+    /// <param name="key"></param>
+    /// <returns></returns>
+    public KeyBinding Get (Key key) { return TryGet (key, out KeyBinding binding) ? binding : null; }
+
+    /// <summary>Gets the <see cref="KeyBinding"/> for the specified <see cref="Key"/>.</summary>
+    /// <param name="key"></param>
+    /// <param name="scope"></param>
+    /// <returns></returns>
+    public KeyBinding Get (Key key, KeyBindingScope scope) { return TryGet (key, scope, out KeyBinding binding) ? binding : null; }
+
+    /// <summary>Gets the array of <see cref="Command"/>s bound to <paramref name="key"/> if it exists.</summary>
+    /// <param name="key">The key to check.</param>
+    /// <returns>
+    ///     The array of <see cref="Command"/>s if <paramref name="key"/> is bound. An empty <see cref="Command"/> array
+    ///     if not.
+    /// </returns>
+    public Command [] GetCommands (Key key)
+    {
+        if (TryGet (key, out KeyBinding bindings))
+        {
+            return bindings.Commands;
+        }
+
+        return Array.Empty<Command> ();
+    }
+
+    /// <summary>Gets the Key used by a set of commands.</summary>
+    /// <remarks></remarks>
+    /// <param name="commands">The set of commands to search.</param>
+    /// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
+    /// <exception cref="InvalidOperationException">If no matching set of commands was found.</exception>
+    public Key GetKeyFromCommands (params Command [] commands) { return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key; }
+
+    /// <summary>Removes a <see cref="KeyBinding"/> from the collection.</summary>
+    /// <param name="key"></param>
+    public void Remove (Key key)
+    {
+        Bindings.Remove (key);
+        Application.RemoveKeyBinding (key, BoundView);
+    }
+
+    /// <summary>Replaces a key combination already bound to a set of <see cref="Command"/>s.</summary>
+    /// <remarks></remarks>
+    /// <param name="fromKey">The key to be replaced.</param>
+    /// <param name="toKey">The new key to be used.</param>
+    public void Replace (Key fromKey, Key toKey)
+    {
+        if (!TryGet (fromKey, out KeyBinding _))
+        {
+            return;
+        }
+
+        KeyBinding value = Bindings [fromKey];
+        Remove (fromKey);
+        Add (toKey, value);
+    }
+
+    /// <summary>Gets the commands bound with the specified Key.</summary>
+    /// <remarks></remarks>
+    /// <param name="key">The key to check.</param>
+    /// <param name="binding">
+    ///     When this method returns, contains the commands bound with the specified Key, if the Key is
+    ///     found; otherwise, null. This parameter is passed uninitialized.
+    /// </param>
+    /// <returns><see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.</returns>
+    public bool TryGet (Key key, out KeyBinding binding)
+    {
+        if (key.IsValid)
+        {
+            return Bindings.TryGetValue (key, out binding);
+        }
+
+        binding = new (Array.Empty<Command> (), KeyBindingScope.Focused);
+
+        return false;
+    }
+
+    /// <summary>Gets the commands bound with the specified Key that are scoped to a particular scope.</summary>
+    /// <remarks></remarks>
+    /// <param name="key">The key to check.</param>
+    /// <param name="scope">the scope to filter on</param>
+    /// <param name="binding">
+    ///     When this method returns, contains the commands bound with the specified Key, if the Key is
+    ///     found; otherwise, null. This parameter is passed uninitialized.
+    /// </param>
+    /// <returns><see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.</returns>
+    public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding)
+    {
+        if (key.IsValid && Bindings.TryGetValue (key, out binding))
+        {
+            if (scope.HasFlag (binding.Scope))
+            {
+                return true;
+            }
+        }
+
+        binding = new (Array.Empty<Command> (), KeyBindingScope.Focused);
+
+        return false;
+    }
+}

+ 91 - 9
Terminal.Gui/View/Layout/DimAuto.cs

@@ -1,4 +1,6 @@
 #nullable enable
+using System.Drawing;
+
 namespace Terminal.Gui;
 
 /// <summary>
@@ -81,7 +83,7 @@ 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
@@ -100,14 +102,16 @@ public class DimAuto () : Dim
                 {
                     subviews = includedSubviews.Where (v => v.X is not PosAnchorEnd
                                                            && v.X is not PosAlign
-                                                           // && v.X is not PosCenter
+                                                            // && v.X is not PosCenter
+                                                            && v.Width is not DimAuto
                                                            && 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.Y is not PosCenter
+                                                            && v.Height is not DimAuto
                                                            && v.Height is not DimFill).ToList ();
                 }
 
@@ -147,6 +151,88 @@ public class DimAuto () : Dim
                 subviewsSize += maxAnchorEnd;
                 #endregion Anchored
 
+                #region Aligned
+
+                // Now, handle subviews that are anchored to the end
+                // [x] PosAnchorEnd
+                int maxAlign = 0;
+                if (dimension == Dimension.Width)
+                {
+                    // Use Linq to get a list of distinct GroupIds from the subviews
+                    List<int> groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList ();
+
+                    foreach (var groupId in groupIds)
+                    {
+                        List<int> dimensionsList = new ();
+
+                        // PERF: If this proves a perf issue, consider caching a ref to this list in each item
+                        List<PosAlign?> posAlignsInGroup = includedSubviews.Where (
+                            v =>
+                            {
+                                return dimension switch
+                                {
+                                    Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId,
+                                    Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId,
+                                    _ => false
+                                };
+                            })
+                            .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign)
+                            .ToList ();
+
+                        if (posAlignsInGroup.Count == 0)
+                        {
+                            continue;
+                        }
+
+                        maxAlign = posAlignsInGroup [0].CalculateMinDimension (groupId, includedSubviews, dimension);
+                    }
+                }
+                else
+                {
+                    subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList ();
+                }
+
+                subviewsSize = int.Max (subviewsSize, maxAlign);
+                #endregion Aligned
+
+
+                #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];
+
+                    //if (dimension == Dimension.Width)
+                    //{
+                    //    v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0));
+                    //}
+                    //else
+                    //{
+                    //    v.SetRelativeLayout (new Size (0, autoMax - subviewsSize));
+                    //}
+                    maxAuto = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
+
+                    if (maxAuto > subviewsSize)
+                    {
+                        // 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)
@@ -174,15 +260,11 @@ public class DimAuto () : Dim
                 // [ ] DimPercent
                 if (dimension == Dimension.Width)
                 {
-                    subviews = includedSubviews.Where (v => v.Width is DimFill
-                                                      // || v.X is PosCenter
-                                                     ).ToList ();
+                    subviews = includedSubviews.Where (v => v.Width is DimFill).ToList ();
                 }
                 else
                 {
-                    subviews = includedSubviews.Where (v => v.Height is DimFill
-                                                      //|| v.Y is PosCenter
-                                                     ).ToList ();
+                    subviews = includedSubviews.Where (v => v.Height is DimFill).ToList ();
                 }
 
                 int maxFill = 0;

+ 52 - 0
Terminal.Gui/View/Layout/PosAlign.cs

@@ -1,6 +1,7 @@
 #nullable enable
 
 using System.ComponentModel;
+using System.Drawing;
 
 namespace Terminal.Gui;
 
@@ -166,4 +167,55 @@ public class PosAlign : Pos
 
         return 0;
     }
+
+    internal int CalculateMinDimension (int groupId, IList<View> views, Dimension dimension)
+    {
+        List<int> dimensionsList = new ();
+
+        // PERF: If this proves a perf issue, consider caching a ref to this list in each item
+        List<View> viewsInGroup = views.Where (
+                                               v =>
+                                               {
+                                                   return dimension switch
+                                                          {
+                                                              Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId,
+                                                              Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId,
+                                                              _ => false
+                                                          };
+                                               })
+                                       .ToList ();
+
+        if (viewsInGroup.Count == 0)
+        {
+            return 0;
+        }
+
+        // PERF: We iterate over viewsInGroup multiple times here.
+
+        Aligner? firstInGroup = null;
+
+        // Update the dimensionList with the sizes of the views
+        for (var index = 0; index < viewsInGroup.Count; index++)
+        {
+            View view = viewsInGroup [index];
+            PosAlign? posAlign = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign;
+
+            if (posAlign is { })
+            {
+                if (index == 0)
+                {
+                    firstInGroup = posAlign.Aligner;
+                }
+
+                dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height);
+            }
+        }
+
+        // Align
+        var aligner = firstInGroup;
+        aligner.ContainerSize = dimensionsList.Sum();
+        int [] locations = aligner.Align (dimensionsList.ToArray ());
+
+        return locations.Sum ();
+    }
 }

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

@@ -123,25 +123,20 @@ public partial class View : Responder, ISupportInitializeNotification
     /// </remarks>
     public View ()
     {
-        CreateAdornments ();
-
-        HotKeySpecifier = (Rune)'_';
-        TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged;
-
-        TextDirection = TextDirection.LeftRight_TopBottom;
-        Text = string.Empty;
+        SetupAdornments ();
+        SetupKeyboard ();
+        //SetupMouse ();
+        SetupText ();
 
         CanFocus = false;
         TabIndex = -1;
         TabStop = false;
-
-        AddCommands ();
     }
 
     /// <summary>
     ///     Event called only once when the <see cref="View"/> is being initialized for the first time. Allows
-    ///     configurations and assignments to be performed before the <see cref="View"/> being shown. This derived from
-    ///     <see cref="ISupportInitializeNotification"/> to allow notify all the views that are being initialized.
+    ///     configurations and assignments to be performed before the <see cref="View"/> being shown.
+    ///     View implements <see cref="ISupportInitializeNotification"/> to allow for more sophisticated initialization.
     /// </summary>
     public event EventHandler Initialized;
 
@@ -503,25 +498,12 @@ public partial class View : Responder, ISupportInitializeNotification
     /// <returns></returns>
     public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; }
 
-    /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
-    /// <remarks>
-    /// <para>
-    ///     Subviews added to this view via <see cref="Add(View)"/> have their lifetime owned by this view and <see cref="Dispose"/> will
-    ///     dispose them. To prevent this, and have the creator of the Subview instance handle disposal, use <see cref="Remove"/> to remove
-    ///     the subview first.
-    /// </para>
-    /// <para>
-    ///     If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and
-    ///     unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from
-    ///     inside the finalizer, and you should not reference other objects. Only unmanaged resources can be disposed.
-    /// </para>
-    /// </remarks>
-    /// <param name="disposing"></param>
+    /// <inheritdoc/>
     protected override void Dispose (bool disposing)
     {
-        // BUGBUG: We should only dispose these objects if disposing == true
         LineCanvas.Dispose ();
 
+        DisposeKeyboard ();
         DisposeAdornments ();
 
         for (int i = InternalSubviews.Count - 1; i >= 0; i--)

+ 4 - 1
Terminal.Gui/View/ViewAdornments.cs

@@ -2,7 +2,10 @@
 
 public partial class View
 {
-    private void CreateAdornments ()
+    /// <summary>
+    ///    Initializes the Adornments of the View. Called by the constructor.
+    /// </summary>
+    private void SetupAdornments ()
     {
         //// TODO: Move this to Adornment as a static factory method
         if (this is not Adornment)

+ 42 - 2
Terminal.Gui/View/ViewKeyboard.cs

@@ -4,8 +4,15 @@ namespace Terminal.Gui;
 
 public partial class View
 {
-    private void AddCommands ()
+    /// <summary>
+    ///  Helper to configure all things keyboard related for a View. Called from the View constructor.
+    /// </summary>
+    private void SetupKeyboard ()
     {
+        KeyBindings = new (this);
+        HotKeySpecifier = (Rune)'_';
+        TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged;
+
         // By default, the HotKey command sets the focus
         AddCommand (Command.HotKey, OnHotKey);
 
@@ -13,6 +20,15 @@ public partial class View
         AddCommand (Command.Accept, OnAccept);
     }
 
+    /// <summary>
+    ///    Helper to dispose all things keyboard related for a View. Called from the View Dispose method.
+    /// </summary>
+    private void DisposeKeyboard ()
+    {
+        TitleTextFormatter.HotKeyChanged -= TitleTextFormatter_HotKeyChanged;
+        KeyBindings.Clear ();
+    }
+
     #region HotKey Support
 
     /// <summary>
@@ -601,7 +617,7 @@ public partial class View
     #region Key Bindings
 
     /// <summary>Gets the key bindings for this view.</summary>
-    public KeyBindings KeyBindings { get; } = new ();
+    public KeyBindings KeyBindings { get; internal set; }
 
     private Dictionary<Command, Func<bool?>> CommandImplementations { get; } = new ();
 
@@ -704,6 +720,30 @@ 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.

+ 4 - 0
Terminal.Gui/View/ViewMouse.cs

@@ -447,6 +447,10 @@ public partial class View
     internal bool SetHighlight (HighlightStyle style)
     {
         // TODO: Make the highlight colors configurable
+        if (!CanFocus)
+        {
+            return false;
+        }
 
         // Enable override via virtual method and/or event
         if (OnHighlight (style) == true)

+ 9 - 1
Terminal.Gui/View/ViewText.cs

@@ -4,7 +4,15 @@ namespace Terminal.Gui;
 
 public partial class View
 {
-    private string _text;
+    /// <summary>
+    ///    Initializes the Text of the View. Called by the constructor.
+    /// </summary>
+    private void SetupText ()
+    {
+        TextDirection = TextDirection.LeftRight_TopBottom;
+    }
+
+    private string _text = string.Empty;
 
     /// <summary>
     ///     Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved

+ 177 - 69
Terminal.Gui/Views/Bar.cs

@@ -1,73 +1,181 @@
-using System;
-using System.Linq;
-
-namespace Terminal.Gui;
-
-
-public class BarItem : View {
-	public BarItem ()
-	{
-		Height = 1;
-		AddCommand (Command.ToggleExpandCollapse, () => { Visible = !Visible; return true; });
-	}
-	public override string Text {
-		set {
-			base.Text = $"{KeyBindings.Bindings.FirstOrDefault (b => b.Value.Scope != KeyBindingScope.Focused).Key} {value}";
-		}
-		get {
-			return $"{KeyBindings.Bindings.FirstOrDefault(b => b.Value.Scope != KeyBindingScope.Focused).Key} {base.Text}";
-		}
-	}
-}
+namespace Terminal.Gui;
+
 /// <summary>
-/// The Bar <see cref="View"/> provides a container for other views to be used as a toolbar or status bar.
+///     Provides a horizontally or vertically oriented container for other views to be used as a menu, toolbar, or status bar.
 /// </summary>
 /// <remarks>
-/// Views added to a Bar will be positioned horizontally from left to right.
 /// </remarks>
-public class Bar : View {
-	/// <inheritdoc/>
-	public Bar () => SetInitialProperties ();
-
-	void SetInitialProperties ()
-	{
-		X = 0;
-		Y = Pos.AnchorEnd (1);
-		Width = Dim.Fill ();
-		Height = 1;
-		AutoSize = false;
-		ColorScheme = Colors.Menu;
-		CanFocus = true;
-	}
-
-	public override void Add (View view)
-	{
-		// Align the views horizontally from left to right. Use Border to separate them.
-
-		// until we know this view is not the rightmost, make it fill the bar
-		//view.Width = Dim.Fill ();
-
-		view.Margin.Thickness = new Thickness (1, 0, 0, 0);
-		view.Margin.ColorScheme = Colors.Menu;
-
-		// Light up right border
-		view.BorderStyle = LineStyle.Single;
-		view.Border.Thickness = new Thickness (0, 0, 1, 0);
-		view.Padding.Thickness = new Thickness (0, 0, 1, 0);
-		view.Padding.ColorScheme = Colors.Menu;
-
-		// leftmost view is at X=0
-		if (Subviews.Count == 0) {
-			view.X = 0;
-		} else {
-			// Make view to right be autosize
-			//Subviews [^1].AutoSize = true;
-
-			// Align the view to the right of the previous view
-			view.X = Pos.Right (Subviews [^1]);
-
-		}
-
-		base.Add (view);
-	}
-}
+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:
+                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;
+                }
+
+                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;
+    }
+}

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

@@ -0,0 +1,522 @@
+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)
+        };
+
+        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 (),
+            Width = Dim.Auto (DimAutoStyle.Text),
+            Height = Dim.Auto (DimAutoStyle.Text)
+        };
+        _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 (),
+            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;
+
+            _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);
+    }
+}

+ 480 - 47
UICatalog/Scenarios/Bars.cs

@@ -1,52 +1,485 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
-[ScenarioMetadata (Name: "Bars", Description: "Illustrates Bar views (e.g. StatusBar)")]
+
+[ScenarioMetadata ("Bars", "Illustrates Bar views (e.g. StatusBar)")]
 [ScenarioCategory ("Controls")]
-public class bars : Scenario {
-	public override void Init ()
-	{
-		Application.Init ();
-		ConfigurationManager.Themes.Theme = Theme;
-		ConfigurationManager.Apply ();
-		Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
-		Application.Top.Loaded += Top_Initialized;
-	}
-
-	// Setting everything up in Initialized handler because we change the
-	// QuitKey and it only sticks if changed after init
-	void Top_Initialized (object sender, System.EventArgs e)
-	{
-		Application.QuitKey = Key.Z.WithCtrl;
-
-		var bar = new Bar () {
-		};
-		var barITem = new BarItem () { Text = $"Quit - {Application.QuitKey}", AutoSize = true };
-		barITem.KeyBindings.Add (Application.QuitKey, KeyBindingScope.Application, Command.QuitToplevel);
-		bar.Add (barITem);
-
-		barITem = new BarItem () { Text = $"Show/Hide - {Key.F10}", AutoSize = true };
-		barITem.KeyBindings.Add (Key.F10, KeyBindingScope.Application, Command.ToggleExpandCollapse);
-		bar.Add (barITem);
-
-		bar.Add (new Label () { Text = "FocusLabel", CanFocus = true });
-
-		var button = new Button ("Press me!") {
-			AutoSize = true
-		};
-		button.Clicked += Button_Clicked;
-
-		bar.Add (button);
-
-		button = new Button ("Or me!") {
-			AutoSize = true,
-		};
-		button.Clicked += Button_Clicked;
-
-		bar.Add (button);
-
-		Application.Top.Add (bar);
-	}
-
-	void Button_Clicked (object sender, System.EventArgs e) => MessageBox.Query("Hi", $"You clicked {sender}");
+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,
+        //    X = 10,// Pos.Center (),
+        //    Y = 10,//Pos.Center ()
+        //    //Width = Dim.Auto(DimAutoStyle.Content, minimumContentDim: 50),
+        //};
+
+        //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,
+        //};
+
+        //Application.Top.Add (shortcut1, shortcut2);
+        //shortcut1.SetFocus ();
+
+        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 = 2,
+            Orientation = Orientation.Vertical,
+            StatusBarStyle = false,
+            Width = Dim.Percent(40)
+        };
+        bar.Add (shortcut3, shortcut4);
+
+        CheckBox hello = new ()
+        {
+            Title = "Hello",
+        };
+        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);
+
+
+    }
+
 }