瀏覽代碼

Fixes #3691 - Adds `ViewArrangement.Popover` (#3852)

* Added Applicaton.Popover.
Refactored FindDeepestView

* Popover prototype

* Testing highlight

* Fixed click outside issue

* Fixed DialogTests

* Fixed click outside issue (agbain)

* Enabled mouse wheel in Bar

* Enabled mouse wheel in Bar

* Progress. Broke arrangement

* Added popover tests.
Fixed a bunch more CM issues related ot unreliable unit tests.
Updated config.json to include Glyphs.

* Can't set ForceDriver to empty in Resources/config.json.

* added BUGBUG

* Made Position/ScreenPosition clear

* Added View.IsInHierarchy tests

* Added Contextmenuv2 scenario.

* Implemented CM2 in TextView

* Removed unneeded CM stuff from testhelpers

* Shortcut API docs

* Fixed keybinding unit tests

* Fixed mouse handling

* Fighting with CM related unit test failures

* Unit tests pass. I think.

* Shortcut code cleanup

* TextView uses new CM2

* Starting on OnSelect etc...

* Starting on OnSelect etc...

* Fixed ContextMenuv2

* ContextMenu is working again.

* Ugh. ANd fixed button api docs

* Fixed DrawHorizontalShadowTransparent (vertical was already fixed).

* Made Scenarios compatible with #nullable enable

* Undid some keybinding stuff

* Fixed stuff

* Sped up unit tests

* Sped up unit tests 2

* Sped up unit tests 3

* Messing with menus

* merged latest v2_develop

* Added more Popover unit tests

* Added more Popover unit tests2

* Fixed positioning bug

* Fixed mouse bug

* Fixed Bar draw issue

* WIP

* merge v2_develop

* CM2 sorta works

* Enabled Bar subclasses to have IDesignable

* Added ViewportSettings.Transparent

* Region -> nullable enable

* Added ViewportSettigs Editor

* merged v2_develop part 2

* merged v2_develop part 3

* WIP: GetViewsUnderMouse

* WIP: More GetViewsUnderMouse work

* Bars works again

* Added unit tests

* CM now works

* MenuItemv2 POC

* SubMenu POC

* CommandNotBound

* More POC

* Optimize Margin to not defer draw if there's no shadow

* Logger cleanup

* Reverted Generic

* Cascading mostly working

* fixed layout bug

* API docs

* API docs

* Fixed cascade

* Events basically work

* code cleanup

* Fixed IsDefault bug;

* Enabled hotkey support

* Made context-menu-like

* Improved usability

* Refactored ApplicationPopover again

* Cleanup

* Menuv2 POC basically complete

* Code Cleanup

* Made menu API simpler

* Fixed Strings bugs

* Got old ContextMenu scenario mostly working

* ContextMenu scenario now works

* ContextMenu fixes

* ContextMenu fixes

* Tons of menu cleanup

* ContextMenu works in TextView

* Fixed unit tes

* Added unit tests

* Fixed tests

* code cleanup

* More code cleanup

* Deep dive

* scenario

* typos

* Demo colorpicker in a Menu

* Added Region tests proving Region is broken in some Union cases

* fixed v2win/net
Tig 5 月之前
父節點
當前提交
39d4c7dd3d
共有 89 個文件被更改,包括 4438 次插入911 次删除
  1. 8 1
      Terminal.Gui/Application/Application.Initialization.cs
  2. 61 27
      Terminal.Gui/Application/Application.Keyboard.cs
  3. 16 0
      Terminal.Gui/Application/Application.Mouse.cs
  4. 9 0
      Terminal.Gui/Application/Application.Popover.cs
  5. 14 3
      Terminal.Gui/Application/Application.Run.cs
  6. 10 1
      Terminal.Gui/Application/Application.cs
  7. 4 0
      Terminal.Gui/Application/ApplicationNavigation.cs
  8. 160 0
      Terminal.Gui/Application/ApplicationPopover.cs
  9. 10 0
      Terminal.Gui/Application/IPopover.cs
  10. 78 0
      Terminal.Gui/Application/PopoverBaseImpl.cs
  11. 1 1
      Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
  12. 1 1
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  13. 1 0
      Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
  14. 1 1
      Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
  15. 8 2
      Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
  16. 2 9
      Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs
  17. 8 0
      Terminal.Gui/Input/Command.cs
  18. 8 3
      Terminal.Gui/Input/CommandContext.cs
  19. 6 0
      Terminal.Gui/Input/ICommandContext.cs
  20. 6 0
      Terminal.Gui/Input/IInputBinding.cs
  21. 1 1
      Terminal.Gui/Input/InputBindings.cs
  22. 3 5
      Terminal.Gui/Input/Keyboard/KeyBinding.cs
  23. 3 0
      Terminal.Gui/Input/Mouse/MouseBinding.cs
  24. 1 1
      Terminal.Gui/Resources/GlobalResources.cs
  25. 3 3
      Terminal.Gui/Resources/ResourceManagerWrapper.cs
  26. 162 0
      Terminal.Gui/Resources/Strings.Designer.cs
  27. 54 0
      Terminal.Gui/Resources/Strings.fr-FR.resx
  28. 54 0
      Terminal.Gui/Resources/Strings.ja-JP.resx
  29. 54 0
      Terminal.Gui/Resources/Strings.pt-PT.resx
  30. 54 0
      Terminal.Gui/Resources/Strings.resx
  31. 54 0
      Terminal.Gui/Resources/Strings.zh-Hans.resx
  32. 2 3
      Terminal.Gui/View/SuperViewChangedEventArgs.cs
  33. 4 4
      Terminal.Gui/View/View.Adornments.cs
  34. 78 15
      Terminal.Gui/View/View.Command.cs
  35. 0 29
      Terminal.Gui/View/View.Diagnostics.cs
  36. 23 17
      Terminal.Gui/View/View.Drawing.cs
  37. 14 30
      Terminal.Gui/View/View.Keyboard.cs
  38. 17 12
      Terminal.Gui/View/View.Layout.cs
  39. 17 4
      Terminal.Gui/View/View.Mouse.cs
  40. 33 0
      Terminal.Gui/View/View.Navigation.cs
  41. 2 0
      Terminal.Gui/View/View.cs
  42. 1 1
      Terminal.Gui/View/ViewArrangement.cs
  43. 31 0
      Terminal.Gui/View/ViewDiagnosticFlags.cs
  44. 14 6
      Terminal.Gui/Views/Bar.cs
  45. 1 1
      Terminal.Gui/Views/Button.cs
  46. 5 9
      Terminal.Gui/Views/ComboBox.cs
  47. 104 0
      Terminal.Gui/Views/Menu/ContextMenuv2.cs
  48. 1 2
      Terminal.Gui/Views/Menu/Menu.cs
  49. 98 0
      Terminal.Gui/Views/Menu/MenuBarItemv2.cs
  50. 342 0
      Terminal.Gui/Views/Menu/MenuBarv2.cs
  51. 200 0
      Terminal.Gui/Views/Menu/MenuItemv2.cs
  52. 172 0
      Terminal.Gui/Views/Menu/Menuv2.cs
  53. 532 0
      Terminal.Gui/Views/Menu/PopoverMenu.cs
  54. 0 51
      Terminal.Gui/Views/MenuBarv2.cs
  55. 0 98
      Terminal.Gui/Views/Menuv2.cs
  56. 6 12
      Terminal.Gui/Views/MessageBox.cs
  57. 1 1
      Terminal.Gui/Views/ScrollBar/ScrollSlider.cs
  58. 73 90
      Terminal.Gui/Views/Shortcut.cs
  59. 1 1
      Terminal.Gui/Views/Slider.cs
  60. 68 75
      Terminal.Gui/Views/TextField.cs
  61. 39 84
      Terminal.Gui/Views/TextView.cs
  62. 444 0
      Tests/UnitTests/Application/ApplicationPopoverTests.cs
  63. 2 1
      Tests/UnitTests/Application/ApplicationTests.cs
  64. 1 1
      Tests/UnitTests/Configuration/ConfigurationMangerTests.cs
  65. 4 1
      Tests/UnitTests/Resources/ResourceManagerTests.cs
  66. 53 0
      Tests/UnitTests/View/Mouse/GetViewsUnderMouseTests.cs
  67. 48 48
      Tests/UnitTests/Views/ContextMenuTests.cs
  68. 1 1
      Tests/UnitTests/Views/TextFieldTests.cs
  69. 1 1
      Tests/UnitTests/Views/TextViewTests.cs
  70. 163 0
      Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs
  71. 20 0
      Tests/UnitTestsParallelizable/Drawing/DrawContextTests.cs
  72. 40 0
      Tests/UnitTestsParallelizable/Drawing/Region/RegionTests.cs
  73. 62 1
      Tests/UnitTestsParallelizable/View/ViewCommandTests.cs
  74. 28 0
      UICatalog/Scenarios/Arrangement.cs
  75. 9 9
      UICatalog/Scenarios/Bars.cs
  76. 1 1
      UICatalog/Scenarios/ColorPicker.cs
  77. 152 208
      UICatalog/Scenarios/ContextMenus.cs
  78. 5 5
      UICatalog/Scenarios/Editor.cs
  79. 4 12
      UICatalog/Scenarios/Generic.cs
  80. 556 0
      UICatalog/Scenarios/MenusV2.cs
  81. 1 1
      UICatalog/Scenarios/Snake.cs
  82. 19 3
      UICatalog/Scenarios/Transparent.cs
  83. 46 0
      UICatalog/Scenarios/ViewExperiments.cs
  84. 12 9
      UICatalog/UICatalog.cs
  85. 18 0
      docfx/docs/Popovers.md
  86. 1 0
      docfx/docs/index.md
  87. 11 5
      docfx/docs/logging.md
  88. 2 0
      docfx/docs/toc.yml
  89. 二進制
      docfx/images/UICatalog_Logging.png

+ 8 - 1
Terminal.Gui/Application/Application.Initialization.cs

@@ -83,6 +83,7 @@ public static partial class Application // Initialization (Init/Shutdown)
         }
         }
 
 
         Navigation = new ();
         Navigation = new ();
+        Popover = new ();
 
 
         // For UnitTests
         // For UnitTests
         if (driver is { })
         if (driver is { })
@@ -162,6 +163,12 @@ public static partial class Application // Initialization (Init/Shutdown)
 
 
         SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
         SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
 
 
+        // TODO: This is probably not needed
+        if (Popover.GetActivePopover () is View popover)
+        {
+            popover.Visible = false;
+        }
+
         MainThreadId = Thread.CurrentThread.ManagedThreadId;
         MainThreadId = Thread.CurrentThread.ManagedThreadId;
         bool init = Initialized = true;
         bool init = Initialized = true;
         InitializedChanged?.Invoke (null, new (init));
         InitializedChanged?.Invoke (null, new (init));
@@ -265,6 +272,6 @@ public static partial class Application // Initialization (Init/Shutdown)
     /// </summary>
     /// </summary>
     internal static void OnInitializedChanged (object sender, EventArgs<bool> e)
     internal static void OnInitializedChanged (object sender, EventArgs<bool> e)
     {
     {
-        Application.InitializedChanged?.Invoke (sender,e);
+        Application.InitializedChanged?.Invoke (sender, e);
     }
     }
 }
 }

+ 61 - 27
Terminal.Gui/Application/Application.Keyboard.cs

@@ -13,6 +13,7 @@ public static partial class Application // Keyboard handling
     /// <returns><see langword="true"/> if the key was handled.</returns>
     /// <returns><see langword="true"/> if the key was handled.</returns>
     public static bool RaiseKeyDownEvent (Key key)
     public static bool RaiseKeyDownEvent (Key key)
     {
     {
+        // TODO: This should match standard event patterns
         KeyDown?.Invoke (null, key);
         KeyDown?.Invoke (null, key);
 
 
         if (key.Handled)
         if (key.Handled)
@@ -20,6 +21,11 @@ public static partial class Application // Keyboard handling
             return true;
             return true;
         }
         }
 
 
+        if (Popover?.DispatchKeyDown (key) is true)
+        {
+            return true;
+        }
+
         if (Top is null)
         if (Top is null)
         {
         {
             foreach (Toplevel topLevel in TopLevels.ToList ())
             foreach (Toplevel topLevel in TopLevels.ToList ())
@@ -43,6 +49,27 @@ public static partial class Application // Keyboard handling
             }
             }
         }
         }
 
 
+        bool? commandHandled = InvokeCommandsBoundToKey (key);
+        if(commandHandled is true)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Invokes any commands bound at the Application-level to <paramref name="key"/>.
+    /// </summary>
+    /// <param name="key"></param>
+    /// <returns>
+    ///     <see langword="null"/> if no command was found; input processing should continue.
+    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input processing should continue.
+    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
+    /// </returns>
+    public static bool? InvokeCommandsBoundToKey (Key key)
+    {
+        bool? handled = null;
         // Invoke any Application-scoped KeyBindings.
         // Invoke any Application-scoped KeyBindings.
         // The first view that handles the key will stop the loop.
         // The first view that handles the key will stop the loop.
         // foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
         // foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
@@ -52,22 +79,17 @@ public static partial class Application // Keyboard handling
             {
             {
                 if (!binding.Target.Enabled)
                 if (!binding.Target.Enabled)
                 {
                 {
-                    return false;
+                    return null;
                 }
                 }
 
 
-                bool? handled = binding.Target?.InvokeCommands (binding.Commands, binding);
-
-                if (handled != null && (bool)handled)
-                {
-                    return true;
-                }
+                handled = binding.Target?.InvokeCommands (binding.Commands, binding);
             }
             }
             else
             else
             {
             {
                 // BUGBUG: this seems unneeded.
                 // BUGBUG: this seems unneeded.
                 if (!KeyBindings.TryGet (key, out KeyBinding keybinding))
                 if (!KeyBindings.TryGet (key, out KeyBinding keybinding))
                 {
                 {
-                    return false;
+                    return null;
                 }
                 }
 
 
                 bool? toReturn = null;
                 bool? toReturn = null;
@@ -77,30 +99,42 @@ public static partial class Application // Keyboard handling
                     toReturn = InvokeCommand (command, key, keybinding);
                     toReturn = InvokeCommand (command, key, keybinding);
                 }
                 }
 
 
-                return toReturn ?? true;
+                handled = toReturn ?? true;
             }
             }
         }
         }
 
 
-        return false;
+        return handled;
+    }
 
 
-        static bool? InvokeCommand (Command command, Key key, KeyBinding binding)
+    /// <summary>
+    ///     Invokes an Application-bound commmand.
+    /// </summary>
+    /// <param name="command">The Command to invoke</param>
+    /// <param name="key">The Application-bound Key that was pressed.</param>
+    /// <param name="binding">Describes the binding.</param>
+    /// <returns>
+    ///     <see langword="null"/> if no command was found; input processing should continue.
+    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input processing should continue.
+    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
+    /// </returns>
+    /// <exception cref="NotSupportedException"></exception>
+    public static bool? InvokeCommand (Command command, Key key, KeyBinding binding)
+    {
+        if (!_commandImplementations!.ContainsKey (command))
         {
         {
-            if (!_commandImplementations!.ContainsKey (command))
-            {
-                throw new NotSupportedException (
-                                                 @$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by Application."
-                                                );
-            }
-
-            if (_commandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
-            {
-                CommandContext<KeyBinding> context = new (command, binding); // Create the context here
+            throw new NotSupportedException (
+                                             @$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by Application."
+                                            );
+        }
 
 
-                return implementation (context);
-            }
+        if (_commandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
+        {
+            CommandContext<KeyBinding> context = new (command, null, binding); // Create the context here
 
 
-            return false;
+            return implementation (context);
         }
         }
+
+        return null;
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -167,7 +201,7 @@ public static partial class Application // Keyboard handling
     {
     {
         _commandImplementations.Clear ();
         _commandImplementations.Clear ();
 
 
-        // Things this view knows how to do
+        // Things Application knows how to do
         AddCommand (
         AddCommand (
                     Command.Quit,
                     Command.Quit,
                     static () =>
                     static () =>
@@ -213,7 +247,7 @@ public static partial class Application // Keyboard handling
                    );
                    );
 
 
         AddCommand (
         AddCommand (
-                    Command.Edit,
+                    Command.Arrange,
                     static () =>
                     static () =>
                     {
                     {
                         View? viewToArrange = Navigation?.GetFocused ();
                         View? viewToArrange = Navigation?.GetFocused ();
@@ -249,7 +283,7 @@ public static partial class Application // Keyboard handling
         KeyBindings.Add (PrevTabKey, Command.PreviousTabStop);
         KeyBindings.Add (PrevTabKey, Command.PreviousTabStop);
         KeyBindings.Add (NextTabGroupKey, Command.NextTabGroup);
         KeyBindings.Add (NextTabGroupKey, Command.NextTabGroup);
         KeyBindings.Add (PrevTabGroupKey, Command.PreviousTabGroup);
         KeyBindings.Add (PrevTabGroupKey, Command.PreviousTabGroup);
-        KeyBindings.Add (ArrangeKey, Command.Edit);
+        KeyBindings.Add (ArrangeKey, Command.Arrange);
 
 
         KeyBindings.Add (Key.CursorRight, Command.NextTabStop);
         KeyBindings.Add (Key.CursorRight, Command.NextTabStop);
         KeyBindings.Add (Key.CursorDown, Command.NextTabStop);
         KeyBindings.Add (Key.CursorDown, Command.NextTabStop);

+ 16 - 0
Terminal.Gui/Application/Application.Mouse.cs

@@ -1,5 +1,6 @@
 #nullable enable
 #nullable enable
 using System.ComponentModel;
 using System.ComponentModel;
+using System.Diagnostics;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
@@ -168,6 +169,20 @@ public static partial class Application // Mouse handling
             return;
             return;
         }
         }
 
 
+        // Dismiss the Popover if the user presses mouse outside of it
+        if (mouseEvent.IsPressed
+            && Popover?.GetActivePopover () as View is { Visible: true } visiblePopover
+            && View.IsInHierarchy (visiblePopover, deepestViewUnderMouse, includeAdornments: true) is false)
+        {
+
+            visiblePopover.Visible = false;
+
+            // Recurse once so the event can be handled below the popover
+            RaiseMouseEvent (mouseEvent);
+
+            return;
+        }
+
         if (HandleMouseGrab (deepestViewUnderMouse, mouseEvent))
         if (HandleMouseGrab (deepestViewUnderMouse, mouseEvent))
         {
         {
             return;
             return;
@@ -216,6 +231,7 @@ public static partial class Application // Mouse handling
         else
         else
         {
         {
             // The mouse was outside any View's Viewport.
             // The mouse was outside any View's Viewport.
+            //Debug.Fail ("this should not happen.");
 
 
             // Debug.Fail ("This should never happen. If it does please file an Issue!!");
             // Debug.Fail ("This should never happen. If it does please file an Issue!!");
 
 

+ 9 - 0
Terminal.Gui/Application/Application.Popover.cs

@@ -0,0 +1,9 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+public static partial class Application // Popover handling
+{
+    /// <summary>Gets the Application <see cref="Popover"/> manager.</summary>
+    public static ApplicationPopover? Popover { get; internal set; }
+}

+ 14 - 3
Terminal.Gui/Application/Application.Run.cs

@@ -337,7 +337,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     [RequiresUnreferencedCode ("AOT")]
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
     public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-        where T : Toplevel, new ()
+        where T : Toplevel, new()
     {
     {
         return ApplicationImpl.Instance.Run<T> (errorHandler, driver);
         return ApplicationImpl.Instance.Run<T> (errorHandler, driver);
     }
     }
@@ -426,7 +426,16 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
 
     internal static void LayoutAndDrawImpl (bool forceDraw = false)
     internal static void LayoutAndDrawImpl (bool forceDraw = false)
     {
     {
-        bool neededLayout = View.Layout (TopLevels.Reverse (), Screen.Size);
+        List<View> tops = [..TopLevels];
+
+        if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
+        {
+            visiblePopover.SetNeedsDraw ();
+            visiblePopover.SetNeedsLayout ();
+            tops.Insert (0, visiblePopover);
+        }
+
+        bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Screen.Size);
 
 
         if (ClearScreenNextIteration)
         if (ClearScreenNextIteration)
         {
         {
@@ -440,7 +449,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
         }
         }
 
 
         View.SetClipToScreen ();
         View.SetClipToScreen ();
-        View.Draw (TopLevels, neededLayout || forceDraw);
+        View.Draw (tops, neededLayout || forceDraw);
         View.SetClipToScreen ();
         View.SetClipToScreen ();
         Driver?.Refresh ();
         Driver?.Refresh ();
     }
     }
@@ -555,6 +564,8 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     {
     {
         ArgumentNullException.ThrowIfNull (runState);
         ArgumentNullException.ThrowIfNull (runState);
 
 
+        Popover?.HidePopover (Popover?.GetActivePopover ());
+
         runState.Toplevel.OnUnloaded ();
         runState.Toplevel.OnUnloaded ();
 
 
         // End the RunState.Toplevel
         // End the RunState.Toplevel

+ 10 - 1
Terminal.Gui/Application/Application.cs

@@ -103,6 +103,7 @@ public static partial class Application
                        .ToList ();
                        .ToList ();
     }
     }
 
 
+    // BUGBUG: This does not return en-US even though it's supported by default
     internal static List<CultureInfo> GetSupportedCultures ()
     internal static List<CultureInfo> GetSupportedCultures ()
     {
     {
         CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
         CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
@@ -148,6 +149,12 @@ public static partial class Application
             t!.Running = false;
             t!.Running = false;
         }
         }
 
 
+        if (Popover?.GetActivePopover () is View popover)
+        {
+            popover.Visible = false;
+        }
+        Popover = null;
+
         TopLevels.Clear ();
         TopLevels.Clear ();
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
 
 
@@ -197,7 +204,9 @@ public static partial class Application
         Initialized = false;
         Initialized = false;
 
 
         // Mouse
         // Mouse
-        _lastMousePosition = null;
+        // Do not clear _lastMousePosition; Popover's require it to stay set with
+        // last mouse pos.
+        //_lastMousePosition = null;
         _cachedViewsUnderMouse.Clear ();
         _cachedViewsUnderMouse.Clear ();
         WantContinuousButtonPressedView = null;
         WantContinuousButtonPressedView = null;
         MouseEvent = null;
         MouseEvent = null;

+ 4 - 0
Terminal.Gui/Application/ApplicationNavigation.cs

@@ -104,6 +104,10 @@ public class ApplicationNavigation
     /// </returns>
     /// </returns>
     public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
     public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
     {
     {
+        if (Application.Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
+        {
+            return visiblePopover.AdvanceFocus (direction, behavior);
+        }
         return Application.Top is { } && Application.Top.AdvanceFocus (direction, behavior);
         return Application.Top is { } && Application.Top.AdvanceFocus (direction, behavior);
     }
     }
 }
 }

+ 160 - 0
Terminal.Gui/Application/ApplicationPopover.cs

@@ -0,0 +1,160 @@
+#nullable enable
+
+using System.Diagnostics;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Helper class for support of <see cref="IPopover"/> views for <see cref="Application"/>. Held by <see cref="Application.Popover"/>
+/// </summary>
+public class ApplicationPopover
+{
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="ApplicationPopover"/> class.
+    /// </summary>
+    public ApplicationPopover () { }
+
+    private readonly List<IPopover> _popovers = [];
+
+    /// <summary></summary>
+    public IReadOnlyCollection<IPopover> Popovers => _popovers.AsReadOnly ();
+
+    /// <summary>
+    ///     Registers <paramref name="popover"/> with the application.
+    ///     This enables the popover to receive keyboard events even when when it is not active.
+    /// </summary>
+    /// <param name="popover"></param>
+    public void Register (IPopover? popover)
+    {
+        if (popover is { } && !_popovers.Contains (popover))
+        {
+            _popovers.Add (popover);
+
+        }
+    }
+
+    /// <summary>
+    ///     De-registers <paramref name="popover"/> with the application. Use this to remove the popover and it's
+    ///     keyboard bindings from the application.
+    /// </summary>
+    /// <param name="popover"></param>
+    /// <returns></returns>
+    public bool DeRegister (IPopover? popover)
+    {
+        if (popover is { } && _popovers.Contains (popover))
+        {
+            if (GetActivePopover () == popover)
+            {
+                _activePopover = null;
+            }
+
+            _popovers.Remove (popover);
+
+            return true;
+        }
+
+        return false;
+    }
+
+    private IPopover? _activePopover;
+
+    /// <summary>
+    ///     Gets the active popover, if any.
+    /// </summary>
+    /// <returns></returns>
+    public IPopover? GetActivePopover () { return _activePopover; }
+
+    /// <summary>
+    ///     Shows <paramref name="popover"/>. IPopover implementations should use OnVisibleChnaged/VisibleChanged to be
+    ///     notified when the user has done something to cause the popover to be hidden.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Note, this API calls <see cref="Register"/>. To disable the popover from processing keyboard events,
+    ///         either call <see cref="DeRegister"/> to
+    ///         remove the popover from the application or set <see cref="View.Enabled"/> to <see langword="false"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="popover"></param>
+    public void ShowPopover (IPopover? popover)
+    {
+        // If there's an existing popover, hide it.
+        if (_activePopover is View popoverView)
+        {
+            popoverView.Visible = false;
+            _activePopover = null;
+        }
+
+        if (popover is View newPopover)
+        {
+            Register (popover);
+
+            if (!newPopover.IsInitialized)
+            {
+                newPopover.BeginInit ();
+                newPopover.EndInit ();
+            }
+
+            _activePopover = newPopover as IPopover;
+            newPopover.Enabled = true;
+            newPopover.Visible = true;
+        }
+    }
+
+    /// <summary>
+    ///     Causes the specified popover to be hidden.
+    ///     If the popover is dervied from <see cref="PopoverBaseImpl"/>, this is the same as setting <see cref="View.Visible"/> to <see langword="false"/>.
+    /// </summary>
+    /// <param name="popover"></param>
+    public void HidePopover (IPopover? popover)
+    {
+        // If there's an existing popover, hide it.
+        if (_activePopover is View popoverView && popoverView == popover)
+        {
+            popoverView.Visible = false;
+            _activePopover = null;
+            Application.Top?.SetNeedsDraw ();
+        }
+    }
+
+
+    /// <summary>
+    ///     Called when the user presses a key. Dispatches the key to the active popover, if any,
+    ///     otherwise to the popovers in the order they were registered. Inactive popovers only get hotkeys.
+    /// </summary>
+    /// <param name="key"></param>
+    /// <returns></returns>
+    internal bool DispatchKeyDown (Key key)
+    {
+        // Do active first - Active gets all key down events.
+        if (GetActivePopover () as View is { Visible: true } visiblePopover)
+        {
+            if (visiblePopover.NewKeyDownEvent (key))
+            {
+                return true;
+            }
+        }
+
+        // If the active popover didn't handle the key, try the inactive ones.
+        // Inactive only get hotkeys
+        bool? hotKeyHandled = null;
+
+        foreach (IPopover popover in _popovers)
+        {
+            if (GetActivePopover () == popover || popover is not View popoverView)
+            {
+                continue;
+            }
+
+            // hotKeyHandled = popoverView.InvokeCommandsBoundToHotKey (key);
+            hotKeyHandled = popoverView.NewKeyDownEvent (key);
+
+            if (hotKeyHandled is true)
+            {
+                return true;
+            }
+        }
+
+        return hotKeyHandled is true;
+    }
+}

+ 10 - 0
Terminal.Gui/Application/IPopover.cs

@@ -0,0 +1,10 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+///     Interface identifying a View as being capable of being a Popover.
+/// </summary>
+public interface IPopover
+{
+
+}

+ 78 - 0
Terminal.Gui/Application/PopoverBaseImpl.cs

@@ -0,0 +1,78 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+///     Abstract base class for Popover Views.
+/// </summary>
+/// <remarks>
+///     <para>
+///         To show a Popover, use <see cref="ApplicationPopover.ShowPopover"/>. To hide a popover,
+///         call <see cref="ApplicationPopover.ShowPopover"/> with <see langword="null"/> set <see cref="View.Visible"/> to <see langword="false"/>.
+///     </para>
+///     <para>
+///         If the user clicks anywhere not occulded by a SubView of the Popover, presses <see cref="Application.QuitKey"/>,
+///         or causes another popover to show, the Popover will be hidden.
+///     </para>
+/// </remarks>
+
+public abstract class PopoverBaseImpl : View, IPopover
+{
+    /// <summary>
+    /// 
+    /// </summary>
+    protected PopoverBaseImpl ()
+    {
+        Id = "popoverBaseImpl";
+        CanFocus = true;
+        Width = Dim.Fill ();
+        Height = Dim.Fill ();
+        ViewportSettings = ViewportSettings.Transparent | ViewportSettings.TransparentMouse;
+
+        //// TODO: Add a diagnostic setting for this?
+        TextFormatter.VerticalAlignment = Alignment.End;
+        TextFormatter.Alignment = Alignment.End;
+        base.Text = "popover";
+
+        AddCommand (Command.Quit, Quit);
+        KeyBindings.Add (Application.QuitKey, Command.Quit);
+
+        return;
+
+        bool? Quit (ICommandContext? ctx)
+        {
+            if (!Visible)
+            {
+                return null;
+            }
+
+            Visible = false;
+
+            return true;
+        }
+    }
+
+    /// <inheritdoc />
+    protected override bool OnVisibleChanging ()
+    {
+        bool ret = base.OnVisibleChanging ();
+        if (!ret & !Visible)
+        {
+            // Whenvver visible is changing to true, we need to resize;
+            // it's our only chance because we don't get laid out until we're visible
+            Layout (Application.Screen.Size);
+        }
+
+        return ret;
+    }
+
+    // TODO: Pretty sure this is not needed. set_Visible SetFocus already
+    ///// <inheritdoc />
+    //protected override void OnVisibleChanged ()
+    //{
+    //    base.OnVisibleChanged ();
+    //    if (Visible)
+    //    {
+    //        //SetFocus ();
+    //    }
+    //}
+}

+ 1 - 1
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs

@@ -52,7 +52,7 @@ public class AnsiMouseParser
                 Flags = GetFlags (buttonCode, terminator)
                 Flags = GetFlags (buttonCode, terminator)
             };
             };
 
 
-            Logging.Trace ($"{nameof (AnsiMouseParser)} handled as {input} mouse {m.Flags} at {m.Position}");
+            //Logging.Trace ($"{nameof (AnsiMouseParser)} handled as {input} mouse {m.Flags} at {m.Position}");
 
 
             return m;
             return m;
         }
         }

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

@@ -682,7 +682,7 @@ public abstract class ConsoleDriver : IConsoleDriver
     public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
     public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
 
 
     // TODO: Remove this API - it was needed when we didn't have a reliable way to simulate key presses.
     // TODO: Remove this API - it was needed when we didn't have a reliable way to simulate key presses.
-    // TODO: We now do: Applicaiton.RaiseKeyDown and Application.RaiseKeyUp
+    // TODO: We now do: Application.RaiseKeyDown and Application.RaiseKeyUp
     /// <summary>Simulates a key press.</summary>
     /// <summary>Simulates a key press.</summary>
     /// <param name="keyChar">The key character.</param>
     /// <param name="keyChar">The key character.</param>
     /// <param name="key">The key.</param>
     /// <param name="key">The key.</param>

+ 1 - 0
Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs

@@ -64,6 +64,7 @@ public class ApplicationV2 : ApplicationImpl
         }
         }
 
 
         Application.Navigation = new ();
         Application.Navigation = new ();
+        Application.Popover = new ();
 
 
         Application.AddKeyBindings ();
         Application.AddKeyBindings ();
 
 

+ 1 - 1
Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs

@@ -79,7 +79,7 @@ public abstract class InputProcessor<T> : IInputProcessor
 
 
         foreach (MouseEventArgs e in _mouseInterpreter.Process (a))
         foreach (MouseEventArgs e in _mouseInterpreter.Process (a))
         {
         {
-            Logging.Trace ($"Mouse Interpreter raising {e.Flags}");
+           // Logging.Trace ($"Mouse Interpreter raising {e.Flags}");
 
 
             // Pass on
             // Pass on
             MouseEvent?.Invoke (this, e);
             MouseEvent?.Invoke (this, e);

+ 8 - 2
Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs

@@ -122,7 +122,8 @@ public class MainLoop<T> : IMainLoop<T>
 
 
         if (Application.Top != null)
         if (Application.Top != null)
         {
         {
-            bool needsDrawOrLayout = AnySubViewsNeedDrawn (Application.Top);
+            bool needsDrawOrLayout = AnySubViewsNeedDrawn (Application.Popover?.GetActivePopover () as View)
+                                     || AnySubViewsNeedDrawn (Application.Top);
 
 
             bool sizeChanged = WindowSizeMonitor.Poll ();
             bool sizeChanged = WindowSizeMonitor.Poll ();
 
 
@@ -174,8 +175,13 @@ public class MainLoop<T> : IMainLoop<T>
         }
         }
     }
     }
 
 
-    private bool AnySubViewsNeedDrawn (View v)
+    private bool AnySubViewsNeedDrawn (View? v)
     {
     {
+        if (v is null)
+        {
+            return false;
+        }
+
         if (v.NeedsDraw || v.NeedsLayout)
         if (v.NeedsDraw || v.NeedsLayout)
         {
         {
             Logging.Trace ($"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) ");
             Logging.Trace ($"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) ");

+ 2 - 9
Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs

@@ -185,11 +185,7 @@ public sealed class Colors : INotifyCollectionChanged, IDictionary<string, Color
         }
         }
     }
     }
 
 
-    /// <summary>
-    ///     Copies the elements of the <see cref="ColorSchemes"/> to an array, starting at a particular array index.
-    /// </summary>
-    /// <param name="array">The one-dimensional array that is the destination of the elements copied from <see cref="ColorSchemes"/>.</param>
-    /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
+    /// <inheritdoc />
     public void CopyTo (KeyValuePair<string, ColorScheme?> [] array, int arrayIndex)
     public void CopyTo (KeyValuePair<string, ColorScheme?> [] array, int arrayIndex)
     {
     {
         lock (_lock)
         lock (_lock)
@@ -198,10 +194,7 @@ public sealed class Colors : INotifyCollectionChanged, IDictionary<string, Color
         }
         }
     }
     }
 
 
-    /// <summary>
-    ///     Returns an enumerator that iterates through the <see cref="ColorSchemes"/>.
-    /// </summary>
-    /// <returns>An enumerator for the <see cref="ColorSchemes"/>.</returns>
+    /// <inheritdoc />
     public IEnumerator<KeyValuePair<string, ColorScheme?>> GetEnumerator ()
     public IEnumerator<KeyValuePair<string, ColorScheme?>> GetEnumerator ()
     {
     {
         lock (_lock)
         lock (_lock)

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

@@ -14,6 +14,11 @@ namespace Terminal.Gui;
 /// </remarks>
 /// </remarks>
 public enum Command
 public enum Command
 {
 {
+    /// <summary>
+    ///     Indicates the command is not bound or invalid. Will call <see cref="View.RaiseCommandNotBound"/>.
+    /// </summary>
+    NotBound = 0,
+
     #region Base View Commands
     #region Base View Commands
 
 
     /// <summary>
     /// <summary>
@@ -270,6 +275,9 @@ public enum Command
     /// <summary>Tabs back to the previous item.</summary>
     /// <summary>Tabs back to the previous item.</summary>
     BackTab,
     BackTab,
 
 
+    /// <summary>Enables arrange mode.</summary>
+    Arrange,
+
     #endregion
     #endregion
 
 
     #region Action Commands
     #region Action Commands

+ 8 - 3
Terminal.Gui/Input/CommandContext.cs

@@ -1,28 +1,33 @@
 #nullable enable
 #nullable enable
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
-#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
+#pragma warning disable CS1574, CS0419 // XML comment has cref attribute that could not be resolved
 /// <summary>
 /// <summary>
 ///     Provides context for a <see cref="Command"/> invocation.
 ///     Provides context for a <see cref="Command"/> invocation.
 /// </summary>
 /// </summary>
 /// <seealso cref="View.InvokeCommand"/>.
 /// <seealso cref="View.InvokeCommand"/>.
-#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
+#pragma warning restore CS1574, CS0419 // XML comment has cref attribute that could not be resolved
 public record struct CommandContext<TBinding> : ICommandContext
 public record struct CommandContext<TBinding> : ICommandContext
 {
 {
     /// <summary>
     /// <summary>
     ///     Initializes a new instance with the specified <see cref="Command"/>,
     ///     Initializes a new instance with the specified <see cref="Command"/>,
     /// </summary>
     /// </summary>
     /// <param name="command"></param>
     /// <param name="command"></param>
+    /// <param name="source"></param>
     /// <param name="binding"></param>
     /// <param name="binding"></param>
-    public CommandContext (Command command, TBinding? binding)
+    public CommandContext (Command command, View? source, TBinding? binding)
     {
     {
         Command = command;
         Command = command;
         Binding = binding;
         Binding = binding;
+        Source = source;
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public Command Command { get; set; }
     public Command Command { get; set; }
 
 
+    /// <inheritdoc />
+    public View? Source { get; set; }
+
     /// <summary>
     /// <summary>
     /// The keyboard or mouse minding that was used to invoke the <see cref="Command"/>, if any.
     /// The keyboard or mouse minding that was used to invoke the <see cref="Command"/>, if any.
     /// </summary>
     /// </summary>

+ 6 - 0
Terminal.Gui/Input/ICommandContext.cs

@@ -15,4 +15,10 @@ public interface ICommandContext
     ///     The <see cref="Command"/> that is being invoked.
     ///     The <see cref="Command"/> that is being invoked.
     /// </summary>
     /// </summary>
     public Command Command { get; set; }
     public Command Command { get; set; }
+
+    /// <summary>
+    ///     The View that was the source of the command invocation, if any.
+    ///     (e.g. the view the user clicked on or the view that had focus when a key was pressed).
+    /// </summary>
+    public View? Source { get; set; }
 }
 }

+ 6 - 0
Terminal.Gui/Input/IInputBinding.cs

@@ -10,4 +10,10 @@ public interface IInputBinding
     ///     Gets or sets the commands this input binding will invoke.
     ///     Gets or sets the commands this input binding will invoke.
     /// </summary>
     /// </summary>
     Command [] Commands { get; set; }
     Command [] Commands { get; set; }
+
+    /// <summary>
+    ///     Arbitrary context that can be associated with this input binding.
+    /// </summary>
+    public object? Data { get; set; }
+
 }
 }

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

@@ -162,7 +162,7 @@ public abstract class InputBindings<TEvent, TBinding> where TBinding : IInputBin
     ///     The first matching <typeparamref name="TEvent"/> bound to the set of commands specified by
     ///     The first matching <typeparamref name="TEvent"/> bound to the set of commands specified by
     ///     <paramref name="commands"/>. <see langword="null"/> if the set of caommands was not found.
     ///     <paramref name="commands"/>. <see langword="null"/> if the set of caommands was not found.
     /// </returns>
     /// </returns>
-    public TEvent GetFirstFromCommands (params Command [] commands) { return _bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key; }
+    public TEvent? GetFirstFromCommands (params Command [] commands) { return _bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key; }
 
 
     /// <summary>Gets all <typeparamref name="TEvent"/> bound to the set of commands specified by <paramref name="commands"/>.</summary>
     /// <summary>Gets all <typeparamref name="TEvent"/> bound to the set of commands specified by <paramref name="commands"/>.</summary>
     /// <param name="commands">The set of commands to search.</param>
     /// <param name="commands">The set of commands to search.</param>

+ 3 - 5
Terminal.Gui/Input/Keyboard/KeyBinding.cs

@@ -36,6 +36,9 @@ public record struct KeyBinding : IInputBinding
     /// <summary>The commands this key binding will invoke.</summary>
     /// <summary>The commands this key binding will invoke.</summary>
     public Command [] Commands { get; set; }
     public Command [] Commands { get; set; }
 
 
+    /// <inheritdoc />
+    public object? Data { get; set; }
+
     /// <summary>
     /// <summary>
     ///     The Key that is bound to the <see cref="Commands"/>.
     ///     The Key that is bound to the <see cref="Commands"/>.
     /// </summary>
     /// </summary>
@@ -43,9 +46,4 @@ public record struct KeyBinding : IInputBinding
 
 
     /// <summary>The view the key binding is bound to.</summary>
     /// <summary>The view the key binding is bound to.</summary>
     public View? Target { get; set; }
     public View? Target { get; set; }
-
-    /// <summary>
-    ///     Arbitrary context that can be associated with this key binding.
-    /// </summary>
-    public object? Data { get; set; }
 }
 }

+ 3 - 0
Terminal.Gui/Input/Mouse/MouseBinding.cs

@@ -25,6 +25,9 @@ public record struct MouseBinding : IInputBinding
     /// <summary>The commands this binding will invoke.</summary>
     /// <summary>The commands this binding will invoke.</summary>
     public Command [] Commands { get; set; }
     public Command [] Commands { get; set; }
 
 
+    /// <inheritdoc />
+    public object? Data { get; set; }
+
     /// <summary>
     /// <summary>
     ///     The mouse event arguments.
     ///     The mouse event arguments.
     /// </summary>
     /// </summary>

+ 1 - 1
Terminal.Gui/Resources/GlobalResources.cs

@@ -66,5 +66,5 @@ public static class GlobalResources
     /// <param name="name"></param>
     /// <param name="name"></param>
     /// <param name="culture"></param>
     /// <param name="culture"></param>
     /// <returns>Null if the resource was not found in the current culture or the invariant culture.</returns>
     /// <returns>Null if the resource was not found in the current culture or the invariant culture.</returns>
-    public static string GetString (string name, CultureInfo? culture = null!) { return _resourceManagerWrapper.GetString (name, culture); }
+    public static string? GetString (string name, CultureInfo? culture = null!) { return _resourceManagerWrapper.GetString (name, culture); }
 }
 }

+ 3 - 3
Terminal.Gui/Resources/ResourceManagerWrapper.cs

@@ -66,10 +66,10 @@ internal class ResourceManagerWrapper (ResourceManager resourceManager)
         return filteredValue;
         return filteredValue;
     }
     }
 
 
-    public string GetString (string name, CultureInfo? culture = null!)
+    public string? GetString (string name, CultureInfo? culture = null!)
     {
     {
         // Attempt to get the string for the specified culture
         // Attempt to get the string for the specified culture
-        string value = _resourceManager.GetString (name, culture)!;
+        string? value = _resourceManager.GetString (name, culture)!;
 
 
         // If it's already using the invariant culture return
         // If it's already using the invariant culture return
         if (Equals (culture, CultureInfo.InvariantCulture))
         if (Equals (culture, CultureInfo.InvariantCulture))
@@ -80,7 +80,7 @@ internal class ResourceManagerWrapper (ResourceManager resourceManager)
         // If the string is empty or null, fall back to the invariant culture
         // If the string is empty or null, fall back to the invariant culture
         if (string.IsNullOrEmpty (value))
         if (string.IsNullOrEmpty (value))
         {
         {
-            value = _resourceManager.GetString (name, CultureInfo.InvariantCulture)!;
+            value = _resourceManager.GetString (name, CultureInfo.InvariantCulture);
         }
         }
 
 
         return value;
         return value;

+ 162 - 0
Terminal.Gui/Resources/Strings.Designer.cs

@@ -159,6 +159,168 @@ namespace Terminal.Gui.Resources {
             }
             }
         }
         }
         
         
+        /// <summary>
+        ///   Looks up a localized string similar to _Copy.
+        /// </summary>
+        internal static string cmd_Copy {
+            get {
+                return ResourceManager.GetString("cmd.Copy", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Copy to clipboard.
+        /// </summary>
+        internal static string cmd_Copy_Help {
+            get {
+                return ResourceManager.GetString("cmd.Copy.Help", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Cu_t.
+        /// </summary>
+        internal static string cmd_Cut {
+            get {
+                return ResourceManager.GetString("cmd.Cut", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Cut to clipboard.
+        /// </summary>
+        internal static string cmd_Cut_Help {
+            get {
+                return ResourceManager.GetString("cmd.Cut.Help", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to _New file.
+        /// </summary>
+        internal static string cmd_New {
+            get {
+                return ResourceManager.GetString("cmd.New", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to New file.
+        /// </summary>
+        internal static string cmd_New_Help {
+            get {
+                return ResourceManager.GetString("cmd.New.Help", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to _Open....
+        /// </summary>
+        internal static string cmd_Open {
+            get {
+                return ResourceManager.GetString("cmd.Open", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Open a file.
+        /// </summary>
+        internal static string cmd_Open_Help {
+            get {
+                return ResourceManager.GetString("cmd.Open.Help", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to _Paste.
+        /// </summary>
+        internal static string cmd_Paste {
+            get {
+                return ResourceManager.GetString("cmd.Paste", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Paste from clipboard.
+        /// </summary>
+        internal static string cmd_Paste_Help {
+            get {
+                return ResourceManager.GetString("cmd.Paste.Help", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to E_xit.
+        /// </summary>
+        internal static string cmd_Quit {
+            get {
+                return ResourceManager.GetString("cmd.Quit", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to .
+        /// </summary>
+        internal static string cmd_Quit_Help {
+            get {
+                return ResourceManager.GetString("cmd.Quit.Help", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to _Save.
+        /// </summary>
+        internal static string cmd_Save {
+            get {
+                return ResourceManager.GetString("cmd.Save", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Save.
+        /// </summary>
+        internal static string cmd_Save_Help {
+            get {
+                return ResourceManager.GetString("cmd.Save.Help", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Save _As....
+        /// </summary>
+        internal static string cmd_SaveAs {
+            get {
+                return ResourceManager.GetString("cmd.SaveAs", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Save file as.
+        /// </summary>
+        internal static string cmd_SaveAs_Help {
+            get {
+                return ResourceManager.GetString("cmd.SaveAs.Help", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to _Select all.
+        /// </summary>
+        internal static string cmd_SelectAll {
+            get {
+                return ResourceManager.GetString("cmd.SelectAll", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Select all.
+        /// </summary>
+        internal static string cmd_SelectAll_Help {
+            get {
+                return ResourceManager.GetString("cmd.SelectAll.Help", resourceCulture);
+            }
+        }
+        
         /// <summary>
         /// <summary>
         ///   Looks up a localized string similar to Co_lors.
         ///   Looks up a localized string similar to Co_lors.
         /// </summary>
         /// </summary>

+ 54 - 0
Terminal.Gui/Resources/Strings.fr-FR.resx

@@ -183,4 +183,58 @@
   <data name="ctxColors" xml:space="preserve">
   <data name="ctxColors" xml:space="preserve">
     <value>Cou_leurs</value>
     <value>Cou_leurs</value>
   </data>
   </data>
+  <data name="cmd.Open" xml:space="preserve">
+    <value>_Ouvrir</value>
+  </data>
+  <data name="cmd.Quit" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Save" xml:space="preserve">
+    <value>_Enregistrer</value>
+  </data>
+  <data name="cmd.SaveAs" xml:space="preserve">
+    <value>E_nregistrer sous</value>
+  </data>
+  <data name="cmd.Cut" xml:space="preserve">
+    <value>Co_uper</value>
+  </data>
+  <data name="cmd.Copy" xml:space="preserve">
+    <value>_Copier</value>
+  </data>
+  <data name="cmd.Paste" xml:space="preserve">
+    <value>C_oller</value>
+  </data>
+  <data name="cmd.SelectAll" xml:space="preserve">
+    <value>Tout _sélectionner</value>
+  </data>
+  <data name="cmd.New" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Open.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Quit.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Save.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.SaveAs.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Cut.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Copy.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Paste.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.SelectAll.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.New.Help" xml:space="preserve">
+    <value />
+  </data>
 </root>
 </root>

+ 54 - 0
Terminal.Gui/Resources/Strings.ja-JP.resx

@@ -279,4 +279,58 @@
   <data name="ctxColors" xml:space="preserve">
   <data name="ctxColors" xml:space="preserve">
     <value>絵の具 (_L)</value>
     <value>絵の具 (_L)</value>
   </data>
   </data>
+  <data name="cmd.Open" xml:space="preserve">
+    <value>開く (_O)</value>
+  </data>
+  <data name="cmd.Quit" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Save" xml:space="preserve">
+    <value>保存 (_S)</value>
+  </data>
+  <data name="cmd.SaveAs" xml:space="preserve">
+    <value>名前を付けて保存(_A)</value>
+  </data>
+  <data name="cmd.Cut" xml:space="preserve">
+    <value>切り取り (_T)</value>
+  </data>
+  <data name="cmd.Copy" xml:space="preserve">
+    <value>コピー (_C)</value>
+  </data>
+  <data name="cmd.Paste" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.SelectAll" xml:space="preserve">
+    <value>全て選択 (_S)</value>
+  </data>
+  <data name="cmd.New" xml:space="preserve">
+    <value>新規 (_N)</value>
+  </data>
+  <data name="cmd.Open.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Quit.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Save.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.SaveAs.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Cut.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Copy.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Paste.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.SelectAll.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.New.Help" xml:space="preserve">
+    <value />
+  </data>
 </root>
 </root>

+ 54 - 0
Terminal.Gui/Resources/Strings.pt-PT.resx

@@ -183,4 +183,58 @@
   <data name="ctxColors" xml:space="preserve">
   <data name="ctxColors" xml:space="preserve">
     <value>Co_res</value>
     <value>Co_res</value>
   </data>
   </data>
+  <data name="cmd.Open" xml:space="preserve">
+    <value>_Abrir</value>
+  </data>
+  <data name="cmd.Quit" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Save" xml:space="preserve">
+    <value>_Guardar</value>
+  </data>
+  <data name="cmd.SaveAs" xml:space="preserve">
+    <value>Guardar _como</value>
+  </data>
+  <data name="cmd.Cut" xml:space="preserve">
+    <value>Cor_tar</value>
+  </data>
+  <data name="cmd.Copy" xml:space="preserve">
+    <value>_Copiar</value>
+  </data>
+  <data name="cmd.Paste" xml:space="preserve">
+    <value>Co_lar</value>
+  </data>
+  <data name="cmd.SelectAll" xml:space="preserve">
+    <value>_Selecionar Tudo</value>
+  </data>
+  <data name="cmd.New" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Open.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Quit.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Save.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.SaveAs.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Cut.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Copy.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Paste.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.SelectAll.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.New.Help" xml:space="preserve">
+    <value />
+  </data>
 </root>
 </root>

+ 54 - 0
Terminal.Gui/Resources/Strings.resx

@@ -301,4 +301,58 @@
   <data name="failedGetting" xml:space="preserve">
   <data name="failedGetting" xml:space="preserve">
     <value>failed getting</value>
     <value>failed getting</value>
   </data>
   </data>
+  <data name="cmd.Open" xml:space="preserve">
+    <value>_Open...</value>
+  </data>
+  <data name="cmd.Quit" xml:space="preserve">
+    <value>E_xit</value>
+  </data>
+  <data name="cmd.Save" xml:space="preserve">
+    <value>_Save</value>
+  </data>
+  <data name="cmd.SaveAs" xml:space="preserve">
+    <value>Save _As...</value>
+  </data>
+  <data name="cmd.Cut" xml:space="preserve">
+    <value>Cu_t</value>
+  </data>
+  <data name="cmd.Copy" xml:space="preserve">
+    <value>_Copy</value>
+  </data>
+  <data name="cmd.Paste" xml:space="preserve">
+    <value>_Paste</value>
+  </data>
+  <data name="cmd.SelectAll" xml:space="preserve">
+    <value>_Select all</value>
+  </data>
+  <data name="cmd.New" xml:space="preserve">
+    <value>_New file</value>
+  </data>
+  <data name="cmd.Open.Help" xml:space="preserve">
+    <value>Open a file</value>
+  </data>
+  <data name="cmd.Quit.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Save.Help" xml:space="preserve">
+    <value>Save</value>
+  </data>
+  <data name="cmd.SaveAs.Help" xml:space="preserve">
+    <value>Save file as</value>
+  </data>
+  <data name="cmd.Cut.Help" xml:space="preserve">
+    <value>Cut to clipboard</value>
+  </data>
+  <data name="cmd.Copy.Help" xml:space="preserve">
+    <value>Copy to clipboard</value>
+  </data>
+  <data name="cmd.Paste.Help" xml:space="preserve">
+    <value>Paste from clipboard</value>
+  </data>
+  <data name="cmd.SelectAll.Help" xml:space="preserve">
+    <value>Select all</value>
+  </data>
+  <data name="cmd.New.Help" xml:space="preserve">
+    <value>New file</value>
+  </data>
 </root>
 </root>

+ 54 - 0
Terminal.Gui/Resources/Strings.zh-Hans.resx

@@ -279,4 +279,58 @@
   <data name="ctxColors" xml:space="preserve">
   <data name="ctxColors" xml:space="preserve">
     <value>旗帜 (_L)</value>
     <value>旗帜 (_L)</value>
   </data>
   </data>
+  <data name="cmd.Open" xml:space="preserve">
+    <value>打开 (_O)</value>
+  </data>
+  <data name="cmd.Quit" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Save" xml:space="preserve">
+    <value>保存 (_S)</value>
+  </data>
+  <data name="cmd.SaveAs" xml:space="preserve">
+    <value>另存为 (_A)</value>
+  </data>
+  <data name="cmd.Cut" xml:space="preserve">
+    <value>剪切 (_T)</value>
+  </data>
+  <data name="cmd.Copy" xml:space="preserve">
+    <value>复制 (_C)</value>
+  </data>
+  <data name="cmd.Paste" xml:space="preserve">
+    <value>粘贴 (_P)</value>
+  </data>
+  <data name="cmd.SelectAll" xml:space="preserve">
+    <value>全选 (_S)</value>
+  </data>
+  <data name="cmd.New" xml:space="preserve">
+    <value>新建 (_N)</value>
+  </data>
+  <data name="cmd.Open.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Quit.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Save.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.SaveAs.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Cut.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Copy.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.Paste.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.SelectAll.Help" xml:space="preserve">
+    <value />
+  </data>
+  <data name="cmd.New.Help" xml:space="preserve">
+    <value />
+  </data>
 </root>
 </root>

+ 2 - 3
Terminal.Gui/View/SuperViewChangedEventArgs.cs

@@ -2,7 +2,7 @@
 
 
 /// <summary>
 /// <summary>
 ///     Args for events where the <see cref="View.SuperView"/> of a <see cref="View"/> is changed (e.g.
 ///     Args for events where the <see cref="View.SuperView"/> of a <see cref="View"/> is changed (e.g.
-///     <see cref="View.Removed"/> / <see cref="View.IsAddedChanged"/> events).
+///     <see cref="View.Removed"/>).
 /// </summary>
 /// </summary>
 public class SuperViewChangedEventArgs : EventArgs
 public class SuperViewChangedEventArgs : EventArgs
 {
 {
@@ -19,8 +19,7 @@ public class SuperViewChangedEventArgs : EventArgs
     public View SubView { get; }
     public View SubView { get; }
 
 
     /// <summary>
     /// <summary>
-    ///     The parent.  For <see cref="View.Removed"/> this is the old parent (new parent now being null).  For
-    ///     <see cref="View.IsAddedChanged"/> it is the new parent to whom view now belongs.
+    ///     The parent.  For <see cref="View.Removed"/> this is the old parent (new parent now being null).
     /// </summary>
     /// </summary>
     public View SuperView { get; }
     public View SuperView { get; }
 }
 }

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

@@ -203,10 +203,10 @@ public partial class View // Adornments
     ///     </para>
     ///     </para>
     ///     <para>For more advanced customization of the view's border, manipulate see <see cref="Border"/> directly.</para>
     ///     <para>For more advanced customization of the view's border, manipulate see <see cref="Border"/> directly.</para>
     /// </remarks>
     /// </remarks>
-    /// <param name="value"></param>
-    public virtual void SetBorderStyle (LineStyle value)
+    /// <param name="style"></param>
+    public virtual void SetBorderStyle (LineStyle style)
     {
     {
-        if (value != LineStyle.None)
+        if (style != LineStyle.None)
         {
         {
             if (Border!.Thickness == Thickness.Empty)
             if (Border!.Thickness == Thickness.Empty)
             {
             {
@@ -218,7 +218,7 @@ public partial class View // Adornments
             Border!.Thickness = new (0);
             Border!.Thickness = new (0);
         }
         }
 
 
-        Border.LineStyle = value;
+        Border.LineStyle = style;
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 78 - 15
Terminal.Gui/View/View.Command.cs

@@ -14,6 +14,9 @@ public partial class View // Command APIs
     /// </summary>
     /// </summary>
     private void SetupCommands ()
     private void SetupCommands ()
     {
     {
+        // NotBound - Invoked if no handler is bound
+        AddCommand (Command.NotBound, RaiseCommandNotBound);
+
         // Enter - Raise Accepted
         // Enter - Raise Accepted
         AddCommand (Command.Accept, RaiseAccepting);
         AddCommand (Command.Accept, RaiseAccepting);
 
 
@@ -50,6 +53,45 @@ public partial class View // Command APIs
                                     });
                                     });
     }
     }
 
 
+    /// <summary>
+    ///     Called when a command that has not been bound is invoked.
+    /// </summary>
+    /// <returns>
+    ///     <see langword="null"/> if no event was raised; input processing should continue.
+    ///     <see langword="false"/> if the event was raised and was not handled (or cancelled); input processing should continue.
+    ///     <see langword="true"/> if the event was raised and handled (or cancelled); input processing should stop.
+    /// </returns>
+    protected bool? RaiseCommandNotBound (ICommandContext? ctx)
+    {
+        CommandEventArgs args = new () { Context = ctx };
+
+        // Best practice is to invoke the virtual method first.
+        // This allows derived classes to handle the event and potentially cancel it.
+        if (OnCommandNotBound (args) || args.Cancel)
+        {
+            return true;
+        }
+
+        // If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
+        CommandNotBound?.Invoke (this, args);
+
+        return CommandNotBound is null ? null : args.Cancel;
+    }
+
+    /// <summary>
+    ///      Called when a command that has not been bound is invoked.
+    ///     Set CommandEventArgs.Cancel to
+    ///     <see langword="true"/> and return <see langword="true"/> to cancel the event. The default implementation does nothing.
+    /// </summary>
+    /// <param name="args">The event arguments.</param>
+    /// <returns><see langword="true"/> to stop processing.</returns>
+    protected virtual bool OnCommandNotBound (CommandEventArgs args) { return false; }
+
+    /// <summary>
+    ///     Cancelable event raised when a command that has not been bound is invoked.
+    /// </summary>
+    public event EventHandler<CommandEventArgs>? CommandNotBound;
+
     /// <summary>
     /// <summary>
     ///     Called when the user is accepting the state of the View and the <see cref="Command.Accept"/> has been invoked. Calls <see cref="OnAccepting"/> which can be cancelled; if not cancelled raises <see cref="Accepting"/>.
     ///     Called when the user is accepting the state of the View and the <see cref="Command.Accept"/> has been invoked. Calls <see cref="OnAccepting"/> which can be cancelled; if not cancelled raises <see cref="Accepting"/>.
     ///     event. The default <see cref="Command.Accept"/> handler calls this method.
     ///     event. The default <see cref="Command.Accept"/> handler calls this method.
@@ -95,7 +137,9 @@ public partial class View // Command APIs
 
 
             if (isDefaultView != this && isDefaultView is Button { IsDefault: true } button)
             if (isDefaultView != this && isDefaultView is Button { IsDefault: true } button)
             {
             {
-                bool? handled = isDefaultView.InvokeCommand<KeyBinding> (Command.Accept, new ([Command.Accept], null, this));
+                // TODO: It's a bit of a hack that this uses KeyBinding. There should be an InvokeCommmand that 
+                // TODO: is generic?
+                bool? handled = isDefaultView.InvokeCommand (Command.Accept, ctx);
                 if (handled == true)
                 if (handled == true)
                 {
                 {
                     return true;
                     return true;
@@ -104,7 +148,7 @@ public partial class View // Command APIs
 
 
             if (SuperView is { })
             if (SuperView is { })
             {
             {
-                return SuperView?.InvokeCommand<KeyBinding> (Command.Accept, new ([Command.Accept], null, this)) is true;
+                return SuperView?.InvokeCommand (Command.Accept, ctx) is true;
             }
             }
         }
         }
 
 
@@ -294,9 +338,7 @@ public partial class View // Command APIs
         {
         {
             if (!_commandImplementations.ContainsKey (command))
             if (!_commandImplementations.ContainsKey (command))
             {
             {
-                throw new NotSupportedException (
-                                                 @$"A Binding was set up for the command {command} ({binding}) but that command is not supported by this View ({GetType ().Name})"
-                                                );
+                Logging.Warning (@$"{command} is not supported by this View ({GetType ().Name}). Binding: {binding}.");
             }
             }
 
 
             // each command has its own return value
             // each command has its own return value
@@ -327,16 +369,36 @@ public partial class View // Command APIs
     /// </returns>
     /// </returns>
     public bool? InvokeCommand<TBindingType> (Command command, TBindingType binding)
     public bool? InvokeCommand<TBindingType> (Command command, TBindingType binding)
     {
     {
-        if (_commandImplementations.TryGetValue (command, out CommandImplementation? implementation))
+        if (!_commandImplementations.TryGetValue (command, out CommandImplementation? implementation))
         {
         {
-            return implementation (new CommandContext<TBindingType> ()
-            {
-                Command = command,
-                Binding = binding,
-            });
+            _commandImplementations.TryGetValue (Command.NotBound, out implementation);
         }
         }
+        return implementation! (new CommandContext<TBindingType> ()
+        {
+            Command = command,
+            Source = this,
+            Binding = binding,
+        });
+    }
 
 
-        return null;
+
+    /// <summary>
+    /// Invokes the specified command.
+    /// </summary>
+    /// <param name="command">The command to invoke.</param>
+    /// <param name="ctx">The context to pass with the command.</param>
+    /// <returns>
+    ///     <see langword="null"/> if no command was found; input processing should continue.
+    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input processing should continue.
+    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
+    /// </returns>
+    public bool? InvokeCommand (Command command, ICommandContext? ctx)
+    {
+        if (!_commandImplementations.TryGetValue (command, out CommandImplementation? implementation))
+        {
+            _commandImplementations.TryGetValue (Command.NotBound, out implementation);
+        }
+        return implementation! (ctx);
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -350,11 +412,12 @@ public partial class View // Command APIs
     /// </returns>
     /// </returns>
     public bool? InvokeCommand (Command command)
     public bool? InvokeCommand (Command command)
     {
     {
-        if (_commandImplementations.TryGetValue (command, out CommandImplementation? implementation))
+        if (!_commandImplementations.TryGetValue (command, out CommandImplementation? implementation))
         {
         {
-            return implementation (null);
+            _commandImplementations.TryGetValue (Command.NotBound, out implementation);
         }
         }
 
 
-        return null;
+        return implementation! (null);
+
     }
     }
 }
 }

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

@@ -1,35 +1,6 @@
 #nullable enable
 #nullable enable
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
-/// <summary>Enables diagnostic functions for <see cref="View"/>.</summary>
-[Flags]
-public enum ViewDiagnosticFlags : uint
-{
-    /// <summary>All diagnostics off</summary>
-    Off = 0b_0000_0000,
-
-    /// <summary>
-    ///     When enabled, <see cref="Adornment"/> will draw a ruler in the Thickness. See <see cref="Adornment.Diagnostics"/>.
-    /// </summary>
-    Ruler = 0b_0000_0001,
-
-    /// <summary>
-    ///     When enabled, <see cref="Adornment"/> will draw the first letter of the Adornment name ('M', 'B', or 'P')
-    ///     in the Thickness. See <see cref="Adornment.Diagnostics"/>.
-    /// </summary>
-    Thickness = 0b_0000_0010,
-
-    /// <summary>
-    ///     When enabled the View's colors will be darker when the mouse is hovering over the View (See <see cref="View.MouseEnter"/> and <see cref="View.MouseLeave"/>.
-    /// </summary>
-    Hover = 0b_0000_00100,
-
-    /// <summary>
-    ///     When enabled a draw indicator will be shown; the indicator will change each time the View's Draw method is called with NeedsDraw set to true.
-    /// </summary>
-    DrawIndicator = 0b_0000_01000,
-}
-
 public partial class View
 public partial class View
 {
 {
     /// <summary>Gets or sets whether diagnostic information will be drawn. This is a bit-field of <see cref="ViewDiagnosticFlags"/>.e <see cref="View"/> diagnostics.</summary>
     /// <summary>Gets or sets whether diagnostic information will be drawn. This is a bit-field of <see cref="ViewDiagnosticFlags"/>.e <see cref="View"/> diagnostics.</summary>

+ 23 - 17
Terminal.Gui/View/View.Drawing.cs

@@ -1,5 +1,6 @@
 #nullable enable
 #nullable enable
 using System.ComponentModel;
 using System.ComponentModel;
+using static Unix.Terminal.Curses;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
@@ -76,7 +77,7 @@ public partial class View // Drawing APIs
 
 
             // TODO: Simplify/optimize SetAttribute system.
             // TODO: Simplify/optimize SetAttribute system.
             DoSetAttribute ();
             DoSetAttribute ();
-            DoClearViewport ();
+            DoClearViewport (context);
 
 
             // ------------------------------------
             // ------------------------------------
             // Draw the subviews first (order matters: SubViews, Text, Content)
             // Draw the subviews first (order matters: SubViews, Text, Content)
@@ -134,7 +135,6 @@ public partial class View // Drawing APIs
 
 
     private void DoDrawAdornmentsSubViews ()
     private void DoDrawAdornmentsSubViews ()
     {
     {
-
         // NOTE: We do not support subviews of Margin?
         // NOTE: We do not support subviews of Margin?
 
 
         if (Border?.SubViews is { } && Border.Thickness != Thickness.Empty)
         if (Border?.SubViews is { } && Border.Thickness != Thickness.Empty)
@@ -188,8 +188,7 @@ public partial class View // Drawing APIs
         if (Margin?.NeedsLayout == true)
         if (Margin?.NeedsLayout == true)
         {
         {
             Margin.NeedsLayout = false;
             Margin.NeedsLayout = false;
-            // BUGBUG: This should not use ClearFrame as that clears the insides too
-            Margin?.ClearFrame ();
+            Margin?.Thickness.Draw (FrameToScreen ());
             Margin?.Parent?.SetSubViewNeedsDraw ();
             Margin?.Parent?.SetSubViewNeedsDraw ();
         }
         }
 
 
@@ -316,31 +315,29 @@ public partial class View // Drawing APIs
 
 
     #region ClearViewport
     #region ClearViewport
 
 
-    internal void DoClearViewport ()
+    internal void DoClearViewport (DrawContext? context = null)
     {
     {
-        if (ViewportSettings.HasFlag (ViewportSettings.Transparent))
-        {
-            return;
-        }
-
-        if (OnClearingViewport ())
+        if (ViewportSettings.HasFlag (ViewportSettings.Transparent) || OnClearingViewport ())
         {
         {
             return;
             return;
         }
         }
 
 
-        var dev = new DrawEventArgs (Viewport, Rectangle.Empty, null);
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty, context);
         ClearingViewport?.Invoke (this, dev);
         ClearingViewport?.Invoke (this, dev);
 
 
         if (dev.Cancel)
         if (dev.Cancel)
         {
         {
+            // BUGBUG: We should add the Viewport to context.DrawRegion here?
             SetNeedsDraw ();
             SetNeedsDraw ();
             return;
             return;
         }
         }
 
 
-        ClearViewport ();
-
-        OnClearedViewport ();
-        ClearedViewport?.Invoke (this, new (Viewport, Viewport, null));
+        if (!ViewportSettings.HasFlag (ViewportSettings.Transparent))
+        {
+            ClearViewport (context);
+            OnClearedViewport ();
+            ClearedViewport?.Invoke (this, new (Viewport, Viewport, null));
+        }
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -379,7 +376,7 @@ public partial class View // Drawing APIs
     ///         the area outside the content to be visually distinct.
     ///         the area outside the content to be visually distinct.
     ///     </para>
     ///     </para>
     /// </remarks>
     /// </remarks>
-    public void ClearViewport ()
+    public void ClearViewport (DrawContext? context = null)
     {
     {
         if (Driver is null)
         if (Driver is null)
         {
         {
@@ -397,6 +394,9 @@ public partial class View // Drawing APIs
 
 
         Attribute prev = SetAttribute (GetNormalColor ());
         Attribute prev = SetAttribute (GetNormalColor ());
         Driver.FillRect (toClear);
         Driver.FillRect (toClear);
+
+        // context.AddDrawnRectangle (toClear);
+
         SetAttribute (prev);
         SetAttribute (prev);
         SetNeedsDraw ();
         SetNeedsDraw ();
     }
     }
@@ -412,6 +412,7 @@ public partial class View // Drawing APIs
             return;
             return;
         }
         }
 
 
+        // TODO: Get rid of this vf in lieu of the one above
         if (OnDrawingText ())
         if (OnDrawingText ())
         {
         {
             return;
             return;
@@ -544,6 +545,7 @@ public partial class View // Drawing APIs
             return;
             return;
         }
         }
 
 
+        // TODO: Get rid of this vf in lieu of the one above
         if (OnDrawingSubViews ())
         if (OnDrawingSubViews ())
         {
         {
             return;
             return;
@@ -707,6 +709,9 @@ public partial class View // Drawing APIs
                 // Exclude the Border and Padding from the clip
                 // Exclude the Border and Padding from the clip
                 ExcludeFromClip (Border?.Thickness.AsRegion (FrameToScreen ()));
                 ExcludeFromClip (Border?.Thickness.AsRegion (FrameToScreen ()));
                 ExcludeFromClip (Padding?.Thickness.AsRegion (FrameToScreen ()));
                 ExcludeFromClip (Padding?.Thickness.AsRegion (FrameToScreen ()));
+
+                // QUESTION: This makes it so that no nesting of transparent views is possible, but is more correct?
+                //context = new DrawContext ();
             }
             }
             else
             else
             {
             {
@@ -721,6 +726,7 @@ public partial class View // Drawing APIs
                 // In the non-transparent (typical case), we want to exclude the entire view area (borderFrame) from the clip
                 // In the non-transparent (typical case), we want to exclude the entire view area (borderFrame) from the clip
                 ExcludeFromClip (borderFrame);
                 ExcludeFromClip (borderFrame);
 
 
+                // BUGBUG: There looks like a bug in Region where this Union call is not adding the rectangle right
                 // Update context.DrawnRegion to include the entire view (borderFrame), but clipped to our SuperView's viewport
                 // Update context.DrawnRegion to include the entire view (borderFrame), but clipped to our SuperView's viewport
                 // This enables the SuperView to know what was drawn by this view.
                 // This enables the SuperView to know what was drawn by this view.
                 context?.AddDrawnRectangle (borderFrame);
                 context?.AddDrawnRectangle (borderFrame);

+ 14 - 30
Terminal.Gui/View/View.Keyboard.cs

@@ -302,9 +302,9 @@ public partial class View // Keyboard APIs
             return true;
             return true;
         }
         }
 
 
-        bool? handled = false;
+        bool? handled = InvokeCommandsBoundToHotKey (key);
 
 
-        if (InvokeCommandsBoundToHotKey (key, ref handled))
+        if (handled is true)
         {
         {
             return true;
             return true;
         }
         }
@@ -590,10 +590,16 @@ public partial class View // Keyboard APIs
     ///     Invokes any commands bound to <paramref name="hotKey"/> on this view and subviews.
     ///     Invokes any commands bound to <paramref name="hotKey"/> on this view and subviews.
     /// </summary>
     /// </summary>
     /// <param name="hotKey"></param>
     /// <param name="hotKey"></param>
-    /// <param name="handled"></param>
-    /// <returns></returns>
-    internal bool InvokeCommandsBoundToHotKey (Key hotKey, ref bool? handled)
+    /// <returns>
+    ///     <see langword="null"/> if no command was invoked; input processing should continue.
+    ///     <see langword="false"/> if at least one command was invoked and was not handled (or cancelled); input processing
+    ///     should continue.
+    ///     <see langword="true"/> if at least one command was invoked and handled (or cancelled); input processing should
+    ///     stop.
+    /// </returns>
+    internal bool? InvokeCommandsBoundToHotKey (Key hotKey)
     {
     {
+        bool? handled = null;
         // Process this View
         // Process this View
         if (HotKeyBindings.TryGet (hotKey, out KeyBinding binding))
         if (HotKeyBindings.TryGet (hotKey, out KeyBinding binding))
         {
         {
@@ -604,16 +610,16 @@ public partial class View // Keyboard APIs
         }
         }
 
 
         // Now, process any HotKey bindings in the subviews
         // Now, process any HotKey bindings in the subviews
-        foreach (View subview in InternalSubViews)
+        foreach (View subview in InternalSubViews.ToList())
         {
         {
             if (subview == Focused)
             if (subview == Focused)
             {
             {
                 continue;
                 continue;
             }
             }
 
 
-            bool recurse = subview.InvokeCommandsBoundToHotKey (hotKey, ref handled);
+            bool? recurse = subview.InvokeCommandsBoundToHotKey (hotKey);
 
 
-            if (recurse || (handled is { } && (bool)handled))
+            if (recurse is true || (handled is { } && (bool)handled))
             {
             {
                 return true;
                 return true;
             }
             }
@@ -644,27 +650,5 @@ public partial class View // Keyboard APIs
         return InvokeCommands (binding.Commands, binding);
         return InvokeCommands (binding.Commands, binding);
     }
     }
 
 
-    /// <summary>
-    ///     Invokes the Commands bound to <paramref name="hotKey"/>.
-    ///     <para>See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see></para>
-    /// </summary>
-    /// <param name="hotKey">The hot key event passed.</param>
-    /// <returns>
-    ///     <see langword="null"/> if no command was invoked; input processing should continue.
-    ///     <see langword="false"/> if at least one command was invoked and was not handled (or cancelled); input processing
-    ///     should continue.
-    ///     <see langword="true"/> if at least one command was invoked and handled (or cancelled); input processing should
-    ///     stop.
-    /// </returns>
-    protected bool? InvokeCommandsBoundToHotKey (Key hotKey)
-    {
-        if (!HotKeyBindings.TryGet (hotKey, out KeyBinding binding))
-        {
-            return null;
-        }
-
-        return InvokeCommands (binding.Commands, binding);
-    }
-
     #endregion Key Bindings
     #endregion Key Bindings
 }
 }

+ 17 - 12
Terminal.Gui/View/View.Layout.cs

@@ -1020,6 +1020,7 @@ public partial class View // Layout APIs
 
 
     // BUGBUG: This method interferes with Dialog/MessageBox default min/max size.
     // BUGBUG: This method interferes with Dialog/MessageBox default min/max size.
     // TODO: Get rid of MenuBar coupling as part of https://github.com/gui-cs/Terminal.Gui/issues/2975
     // TODO: Get rid of MenuBar coupling as part of https://github.com/gui-cs/Terminal.Gui/issues/2975
+    // TODO: Refactor / rewrite this - It's a mess
     /// <summary>
     /// <summary>
     ///     Gets a new location of the <see cref="View"/> that is within the Viewport of the <paramref name="viewToMove"/>'s
     ///     Gets a new location of the <see cref="View"/> that is within the Viewport of the <paramref name="viewToMove"/>'s
     ///     <see cref="View.SuperView"/> (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates.
     ///     <see cref="View.SuperView"/> (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates.
@@ -1048,7 +1049,7 @@ public partial class View // Layout APIs
         int maxDimension;
         int maxDimension;
         View? superView;
         View? superView;
 
 
-        if (viewToMove is not Toplevel || viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
+        if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
         {
         {
             maxDimension = Application.Screen.Width;
             maxDimension = Application.Screen.Width;
             superView = Application.Top;
             superView = Application.Top;
@@ -1070,14 +1071,14 @@ public partial class View // Layout APIs
             nx = Math.Max (targetX, 0);
             nx = Math.Max (targetX, 0);
             nx = nx + viewToMove.Frame.Width > maxDimension ? Math.Max (maxDimension - viewToMove.Frame.Width, 0) : nx;
             nx = nx + viewToMove.Frame.Width > maxDimension ? Math.Max (maxDimension - viewToMove.Frame.Width, 0) : nx;
 
 
-            if (nx > viewToMove.Frame.X + viewToMove.Frame.Width)
-            {
-                nx = Math.Max (viewToMove.Frame.Right, 0);
-            }
+            //if (nx > viewToMove.Frame.X + viewToMove.Frame.Width)
+            //{
+            //    nx = Math.Max (viewToMove.Frame.Right, 0);
+            //}
         }
         }
         else
         else
         {
         {
-            nx = targetX;
+            nx = 0;//targetX;
         }
         }
 
 
         //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
         //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
@@ -1136,15 +1137,19 @@ public partial class View // Layout APIs
                      ? Math.Max (maxDimension - viewToMove.Frame.Height, menuVisible ? 1 : 0)
                      ? Math.Max (maxDimension - viewToMove.Frame.Height, menuVisible ? 1 : 0)
                      : ny;
                      : ny;
 
 
-            if (ny > viewToMove.Frame.Y + viewToMove.Frame.Height)
-            {
-                ny = Math.Max (viewToMove.Frame.Bottom, 0);
-            }
+            //if (ny > viewToMove.Frame.Y + viewToMove.Frame.Height)
+            //{
+            //    ny = Math.Max (viewToMove.Frame.Bottom, 0);
+            //}
+        }
+        else
+        {
+            ny = 0;
         }
         }
 
 
-        //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
+            //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
 
 
-        return superView!;
+            return superView!;
     }
     }
 
 
     #endregion Utilities
     #endregion Utilities

+ 17 - 4
Terminal.Gui/View/View.Mouse.cs

@@ -560,7 +560,7 @@ public partial class View // Mouse APIs
             if (!WantMousePositionReports && Viewport.Contains (mouseEvent.Position))
             if (!WantMousePositionReports && Viewport.Contains (mouseEvent.Position))
             {
             {
 
 
-               return RaiseMouseClickEvent (mouseEvent);
+                return RaiseMouseClickEvent (mouseEvent);
             }
             }
 
 
             return mouseEvent.Handled = true;
             return mouseEvent.Handled = true;
@@ -770,11 +770,23 @@ public partial class View // Mouse APIs
 
 
         View? start = Application.Top;
         View? start = Application.Top;
 
 
+        // PopoverHost - If visible, start with it instead of Top
+        if (Application.Popover?.GetActivePopover () is View {Visible: true } visiblePopover && !ignoreTransparent)
+        {
+            start = visiblePopover;
+
+            // Put Top on stack next
+            viewsUnderMouse.Add (Application.Top);
+        }
+
         Point currentLocation = location;
         Point currentLocation = location;
 
 
         while (start is { Visible: true } && start.Contains (currentLocation))
         while (start is { Visible: true } && start.Contains (currentLocation))
         {
         {
-            viewsUnderMouse.Add (start);
+            if (!start.ViewportSettings.HasFlag(ViewportSettings.TransparentMouse))
+            {
+                viewsUnderMouse.Add (start);
+            }
 
 
             Adornment? found = null;
             Adornment? found = null;
 
 
@@ -825,13 +837,14 @@ public partial class View // Mouse APIs
 
 
             if (subview is null)
             if (subview is null)
             {
             {
+                // In the case start is transparent, recursively add all it's subviews etc...
                 if (start.ViewportSettings.HasFlag (ViewportSettings.TransparentMouse))
                 if (start.ViewportSettings.HasFlag (ViewportSettings.TransparentMouse))
                 {
                 {
                     viewsUnderMouse.AddRange (View.GetViewsUnderMouse (location, true));
                     viewsUnderMouse.AddRange (View.GetViewsUnderMouse (location, true));
 
 
                     // De-dupe viewsUnderMouse
                     // De-dupe viewsUnderMouse
-                    HashSet<View?> dedupe = [..viewsUnderMouse];
-                    viewsUnderMouse = [..dedupe];
+                    HashSet<View?> hashSet = [.. viewsUnderMouse];
+                    viewsUnderMouse = [.. hashSet];
                 }
                 }
 
 
                 // No subview was found that's under the mouse, so we're done
                 // No subview was found that's under the mouse, so we're done

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

@@ -315,6 +315,25 @@ public partial class View // Focus and cross-view navigation management (TabStop
         }
         }
     }
     }
 
 
+    internal void RaiseFocusedChanged (View? previousFocused, View? focused)
+    {
+        //Logging.Trace($"RaiseFocusedChanged: {focused.Title}");
+        OnFocusedChanged (previousFocused, focused);
+        FocusedChanged?.Invoke (this, new HasFocusEventArgs (true, true, previousFocused, focused));
+    }
+
+    /// <summary>
+    ///     Called when the focused view has changed.
+    /// </summary>
+    /// <param name="previousFocused"></param>
+    /// <param name="focused"></param>
+    protected virtual void OnFocusedChanged (View? previousFocused, View? focused) { }
+
+    /// <summary>
+    ///     Raised when the focused view has changed.
+    /// </summary>
+    public event EventHandler<HasFocusEventArgs>? FocusedChanged;
+
     /// <summary>Returns a value indicating if this View is currently on Top (Active)</summary>
     /// <summary>Returns a value indicating if this View is currently on Top (Active)</summary>
     public bool IsCurrentTop => Application.Top == this;
     public bool IsCurrentTop => Application.Top == this;
 
 
@@ -373,6 +392,14 @@ public partial class View // Focus and cross-view navigation management (TabStop
         return false;
         return false;
     }
     }
 
 
+    /// <summary>
+    ///     Clears any focus state (e.g. the previously focused subview) from this view.
+    /// </summary>
+    public void ClearFocus ()
+    {
+        _previouslyFocused = null;
+    }
+
     private View? FindDeepestFocusableView (NavigationDirection direction, TabBehavior? behavior)
     private View? FindDeepestFocusableView (NavigationDirection direction, TabBehavior? behavior)
     {
     {
         View [] indicies = GetFocusChain (direction, behavior);
         View [] indicies = GetFocusChain (direction, behavior);
@@ -853,6 +880,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
 
     private void RaiseFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
     private void RaiseFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
     {
     {
+        // If we are the most focused view, we need to set the focused view in Application.Navigation
         if (newHasFocus && focusedView?.Focused is null)
         if (newHasFocus && focusedView?.Focused is null)
         {
         {
             Application.Navigation?.SetFocused (focusedView);
             Application.Navigation?.SetFocused (focusedView);
@@ -864,6 +892,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
         // Raise the event
         // Raise the event
         var args = new HasFocusEventArgs (newHasFocus, newHasFocus, previousFocusedView, focusedView);
         var args = new HasFocusEventArgs (newHasFocus, newHasFocus, previousFocusedView, focusedView);
         HasFocusChanged?.Invoke (this, args);
         HasFocusChanged?.Invoke (this, args);
+
+        if (newHasFocus || focusedView is null)
+        {
+            SuperView?.RaiseFocusedChanged (previousFocusedView, focusedView);
+        }
     }
     }
 
 
     /// <summary>
     /// <summary>

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

@@ -337,6 +337,8 @@ public partial class View : IDisposable, ISupportInitializeNotification
 
 
             if (!_visible)
             if (!_visible)
             {
             {
+                // BUGBUG: Ideally we'd reset _previouslyFocused to the first focusable subview
+                _previouslyFocused = SubViews.FirstOrDefault(v => v.CanFocus);
                 if (HasFocus)
                 if (HasFocus)
                 {
                 {
                     HasFocus = false;
                     HasFocus = false;

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

@@ -70,5 +70,5 @@ public enum ViewArrangement
     ///         Use Ctrl-Tab (Ctrl-PageDown) / Ctrl-Shift-Tab (Ctrl-PageUp) to move between overlapped views.
     ///         Use Ctrl-Tab (Ctrl-PageDown) / Ctrl-Shift-Tab (Ctrl-PageUp) to move between overlapped views.
     ///     </para>
     ///     </para>
     /// </summary>
     /// </summary>
-    Overlapped = 32
+    Overlapped = 32,
 }
 }

+ 31 - 0
Terminal.Gui/View/ViewDiagnosticFlags.cs

@@ -0,0 +1,31 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>Enables diagnostic functions for <see cref="View"/>.</summary>
+[Flags]
+public enum ViewDiagnosticFlags : uint
+{
+    /// <summary>All diagnostics off</summary>
+    Off = 0b_0000_0000,
+
+    /// <summary>
+    ///     When enabled, <see cref="Adornment"/> will draw a ruler in the Thickness. See <see cref="Adornment.Diagnostics"/>.
+    /// </summary>
+    Ruler = 0b_0000_0001,
+
+    /// <summary>
+    ///     When enabled, <see cref="Adornment"/> will draw the first letter of the Adornment name ('M', 'B', or 'P')
+    ///     in the Thickness. See <see cref="Adornment.Diagnostics"/>.
+    /// </summary>
+    Thickness = 0b_0000_0010,
+
+    /// <summary>
+    ///     When enabled the View's colors will be darker when the mouse is hovering over the View (See <see cref="View.MouseEnter"/> and <see cref="View.MouseLeave"/>.
+    /// </summary>
+    Hover = 0b_0000_00100,
+
+    /// <summary>
+    ///     When enabled a draw indicator will be shown; the indicator will change each time the View's Draw method is called with NeedsDraw set to true.
+    /// </summary>
+    DrawIndicator = 0b_0000_01000,
+}

+ 14 - 6
Terminal.Gui/Views/Bar.cs

@@ -20,7 +20,7 @@ public class Bar : View, IOrientation, IDesignable
     public Bar () : this ([]) { }
     public Bar () : this ([]) { }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public Bar (IEnumerable<Shortcut>? shortcuts)
+    public Bar (IEnumerable<View>? shortcuts)
     {
     {
         CanFocus = true;
         CanFocus = true;
 
 
@@ -32,9 +32,10 @@ public class Bar : View, IOrientation, IDesignable
         // Initialized += Bar_Initialized;
         // Initialized += Bar_Initialized;
         MouseEvent += OnMouseEvent;
         MouseEvent += OnMouseEvent;
 
 
+
         if (shortcuts is { })
         if (shortcuts is { })
         {
         {
-            foreach (Shortcut shortcut in shortcuts)
+            foreach (View shortcut in shortcuts)
             {
             {
                 Add (shortcut);
                 Add (shortcut);
             }
             }
@@ -81,13 +82,14 @@ public class Bar : View, IOrientation, IDesignable
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override void SetBorderStyle (LineStyle value)
+    public override void SetBorderStyle (LineStyle lineStyle)
     {
     {
         if (Border is { })
         if (Border is { })
         {
         {
             // The default changes the thickness. We don't want that. We just set the style.
             // The default changes the thickness. We don't want that. We just set the style.
-            Border.LineStyle = value;
+           Border.LineStyle = lineStyle;
         }
         }
+        //base.SetBorderStyle(lineStyle);
     }
     }
 
 
     #region IOrientation members
     #region IOrientation members
@@ -217,7 +219,13 @@ public class Bar : View, IOrientation, IDesignable
                     barItem.ColorScheme = ColorScheme;
                     barItem.ColorScheme = ColorScheme;
                     barItem.X = Pos.Align (Alignment.Start, AlignmentModes);
                     barItem.X = Pos.Align (Alignment.Start, AlignmentModes);
                     barItem.Y = 0; //Pos.Center ();
                     barItem.Y = 0; //Pos.Center ();
+
+                    if (barItem is Shortcut sc)
+                    {
+                        sc.Width = sc.GetWidthDimAuto ();
+                    }
                 }
                 }
+
                 break;
                 break;
 
 
             case Orientation.Vertical:
             case Orientation.Vertical:
@@ -278,7 +286,7 @@ public class Bar : View, IOrientation, IDesignable
                     {
                     {
                         if (subView is not Line)
                         if (subView is not Line)
                         {
                         {
-                            subView.Width = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: maxBarItemWidth);
+                            subView.Width = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: maxBarItemWidth, maximumContentDim: maxBarItemWidth);
                         }
                         }
                     }
                     }
                 }
                 }
@@ -298,7 +306,7 @@ public class Bar : View, IOrientation, IDesignable
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
-    public bool EnableForDesign ()
+    public virtual bool EnableForDesign ()
     {
     {
         var shortcut = new Shortcut
         var shortcut = new Shortcut
         {
         {

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

@@ -1,7 +1,7 @@
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
 /// <summary>
 /// <summary>
-///     A button View that can be pressed with the mouse or keybaord.
+///     A button View that can be pressed with the mouse or keyboard.
 /// </summary>
 /// </summary>
 /// <remarks>
 /// <remarks>
 ///     <para>
 ///     <para>

+ 5 - 9
Terminal.Gui/Views/ComboBox.cs

@@ -80,11 +80,7 @@ public class ComboBox : View, IDesignable
         // Things this view knows how to do
         // Things this view knows how to do
         AddCommand (Command.Accept, (ctx) =>
         AddCommand (Command.Accept, (ctx) =>
                                     {
                                     {
-                                        if (ctx is not CommandContext<KeyBinding> keyCommandContext)
-                                        {
-                                            return false;
-                                        }
-                                        if (keyCommandContext.Binding.Data == _search)
+                                        if (ctx?.Source == _search)
                                         {
                                         {
                                             return null;
                                             return null;
                                         }
                                         }
@@ -93,8 +89,8 @@ public class ComboBox : View, IDesignable
         AddCommand (Command.Toggle, () => ExpandCollapse ());
         AddCommand (Command.Toggle, () => ExpandCollapse ());
         AddCommand (Command.Expand, () => Expand ());
         AddCommand (Command.Expand, () => Expand ());
         AddCommand (Command.Collapse, () => Collapse ());
         AddCommand (Command.Collapse, () => Collapse ());
-        AddCommand (Command.Down, () => MoveDown ());
-        AddCommand (Command.Up, () => MoveUp ());
+        AddCommand (Command.Down, MoveDown);
+        AddCommand (Command.Up, MoveUp);
         AddCommand (Command.PageDown, () => PageDown ());
         AddCommand (Command.PageDown, () => PageDown ());
         AddCommand (Command.PageUp, () => PageUp ());
         AddCommand (Command.PageUp, () => PageUp ());
         AddCommand (Command.Start, () => MoveHome ());
         AddCommand (Command.Start, () => MoveHome ());
@@ -511,7 +507,7 @@ public class ComboBox : View, IDesignable
         }
         }
 
 
         Reset (true);
         Reset (true);
-        _listview.ClearViewport ();
+        _listview.ClearViewport (null);
         _listview.TabStop = TabBehavior.NoStop;
         _listview.TabStop = TabBehavior.NoStop;
         SuperView?.MoveSubViewToStart (this);
         SuperView?.MoveSubViewToStart (this);
 
 
@@ -812,7 +808,7 @@ public class ComboBox : View, IDesignable
         _listview.SetSource (_searchSet);
         _listview.SetSource (_searchSet);
         _listview.ResumeSuspendCollectionChangedEvent ();
         _listview.ResumeSuspendCollectionChangedEvent ();
 
 
-        _listview.ClearViewport ();
+        _listview.ClearViewport (null);
         _listview.Height = CalculateHeight ();
         _listview.Height = CalculateHeight ();
         SuperView?.MoveSubViewToStart (this);
         SuperView?.MoveSubViewToStart (this);
     }
     }

+ 104 - 0
Terminal.Gui/Views/Menu/ContextMenuv2.cs

@@ -0,0 +1,104 @@
+#nullable enable
+
+using System.Diagnostics;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     ContextMenuv2 provides a Popover menu that can be positioned anywhere within a <see cref="View"/>.
+///     <para>
+///         To show the ContextMenu, set <see cref="Application.Popover"/> to the ContextMenu object and set
+///         <see cref="View.Visible"/> property to <see langword="true"/>.
+///     </para>
+///     <para>
+///         The menu will be hidden when the user clicks outside the menu or when the user presses <see cref="Application.QuitKey"/>.
+///     </para>
+///     <para>
+///         To explicitly hide the menu, set <see cref="View.Visible"/> property to <see langword="false"/>.
+///     </para>
+///     <para>
+///         <see cref="Key"/> is the key used to activate the ContextMenus (<c>Shift+F10</c> by default). Callers can use this in
+///         their keyboard handling code.
+///     </para>
+///     <para>The menu will be displayed at the current mouse coordinates.</para>
+/// </summary>
+public class ContextMenuv2 : PopoverMenu, IDesignable
+{
+
+    /// <summary>
+    ///     The mouse flags that will trigger the context menu. The default is <see cref="MouseFlags.Button3Clicked"/> which is typically the right mouse button.
+    /// </summary>
+    public MouseFlags MouseFlags { get; set; } = MouseFlags.Button3Clicked;
+
+    /// <summary>Initializes a context menu with no menu items.</summary>
+    public ContextMenuv2 () : this ([]) { }
+
+    /// <inheritdoc/>
+    public ContextMenuv2 (Menuv2? menu) : base (menu)
+    {
+        Key = DefaultKey;
+    }
+
+    /// <inheritdoc/>
+    public ContextMenuv2 (IEnumerable<View>? menuItems) : this (new Menuv2 (menuItems))
+    {
+    }
+
+    private Key _key = DefaultKey;
+
+    /// <summary>Specifies the key that will activate the context menu.</summary>
+    public Key Key
+    {
+        get => _key;
+        set
+        {
+            Key oldKey = _key;
+            _key = value;
+            KeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, _key));
+        }
+    }
+
+    /// <summary>Event raised when the <see cref="ContextMenu.Key"/> is changed.</summary>
+    public event EventHandler<KeyChangedEventArgs>? KeyChanged;
+
+    /// <inheritdoc />
+    public bool EnableForDesign ()
+    {
+        var shortcut = new Shortcut
+        {
+            Text = "Quit",
+            Title = "Q_uit",
+            Key = Key.Z.WithCtrl,
+        };
+
+        Add (shortcut);
+
+        shortcut = new Shortcut
+        {
+            Text = "Help Text",
+            Title = "Help",
+            Key = Key.F1,
+        };
+
+        Add (shortcut);
+
+        shortcut = new Shortcut
+        {
+            Text = "Czech",
+            CommandView = new CheckBox ()
+            {
+                Title = "_Check"
+            },
+            Key = Key.F9,
+            CanFocus = false
+        };
+
+        Add (shortcut);
+
+        // HACK: This enables All Views Tester to show the CM if DefaultKey is pressed
+        AddCommand (Command.Context, () => Visible = true);
+        HotKeyBindings.Add (DefaultKey, Command.Context);
+
+        return true;
+    }
+}

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

@@ -252,8 +252,7 @@ internal sealed class Menu : View
     protected override bool OnKeyDownNotHandled (Key keyEvent)
     protected override bool OnKeyDownNotHandled (Key keyEvent)
     {
     {
         // We didn't handle the key, pass it on to host
         // We didn't handle the key, pass it on to host
-        bool? handled = null;
-        return _host.InvokeCommandsBoundToHotKey (keyEvent, ref handled) == true;
+        return _host.InvokeCommandsBoundToHotKey (keyEvent) is true;
     }
     }
 
 
     protected override bool OnMouseEvent (MouseEventArgs me)
     protected override bool OnMouseEvent (MouseEventArgs me)

+ 98 - 0
Terminal.Gui/Views/Menu/MenuBarItemv2.cs

@@ -0,0 +1,98 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     A <see cref="Shortcut"/>-derived object to be used as items in a <see cref="MenuBarv2"/>.
+///     MenuBarItems have a title, a hotkey, and an action to execute on activation.
+/// </summary>
+public class MenuBarItemv2 : MenuItemv2
+{
+    /// <summary>
+    ///     Creates a new instance of <see cref="MenuBarItemv2"/>.
+    /// </summary>
+    public MenuBarItemv2 () : base (null, Command.NotBound) { }
+
+    /// <summary>
+    ///     Creates a new instance of <see cref="MenuBarItemv2"/>. Each MenuBarItem typically has a <see cref="PopoverMenu"/>
+    ///     that is
+    ///     shown when the item is selected.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    /// <param name="targetView">
+    ///     The View that <paramref name="command"/> will be invoked on when user does something that causes the MenuBarItems's
+    ///     Accept event to be raised.
+    /// </param>
+    /// <param name="command">
+    ///     The Command to invoke on <paramref name="targetView"/>. The Key <paramref name="targetView"/>
+    ///     has bound to <paramref name="command"/> will be used as <see cref="Key"/>
+    /// </param>
+    /// <param name="commandText">The text to display for the command.</param>
+    /// <param name="popoverMenu">The Popover Menu that will be displayed when this item is selected.</param>
+    public MenuBarItemv2 (View? targetView, Command command, string? commandText, PopoverMenu? popoverMenu = null)
+        : base (
+                targetView,
+                command,
+                commandText)
+    {
+        TargetView = targetView;
+        Command = command;
+        PopoverMenu = popoverMenu;
+    }
+
+    /// <summary>
+    ///     Creates a new instance of <see cref="MenuBarItemv2"/> with the specified <paramref name="popoverMenu"/>. This is a
+    ///     helper for the most common MenuBar use-cases.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    /// <param name="commandText">The text to display for the command.</param>
+    /// <param name="popoverMenu">The Popover Menu that will be displayed when this item is selected.</param>
+    public MenuBarItemv2 (string commandText, PopoverMenu? popoverMenu = null)
+        : this (
+                null,
+                Command.NotBound,
+                commandText,
+                popoverMenu)
+    { }
+
+    /// <summary>
+    ///     Creates a new instance of <see cref="MenuBarItemv2"/> with the <paramref name="menuItems"/> automatcialy added to a
+    ///     <see cref="PopoverMenu"/>.
+    ///     This is a helper for the most common MenuBar use-cases.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    /// <param name="commandText">The text to display for the command.</param>
+    /// <param name="menuItems">
+    ///     The menu items that will be added to the Popover Menu that will be displayed when this item is
+    ///     selected.
+    /// </param>
+    public MenuBarItemv2 (string commandText, IEnumerable<View> menuItems)
+        : this (
+                null,
+                Command.NotBound,
+                commandText,
+                new (new (menuItems)))
+    { }
+
+    // TODO: Hide base.SubMenu?
+
+    /// <summary>
+    ///     The Popover Menu that will be displayed when this item is selected.
+    /// </summary>
+    public PopoverMenu? PopoverMenu { get; set; }
+
+    /// <inheritdoc/>
+    protected override void Dispose (bool disposing)
+    {
+        if (disposing)
+        {
+            PopoverMenu?.Dispose ();
+            PopoverMenu = null;
+        }
+
+        base.Dispose (disposing);
+    }
+}

+ 342 - 0
Terminal.Gui/Views/Menu/MenuBarv2.cs

@@ -0,0 +1,342 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+///     A horizontal list of <see cref="MenuBarItemv2"/>s. Each <see cref="MenuBarItemv2"/> can have a
+///     <see cref="PopoverMenu"/> that is shown when the <see cref="MenuBarItemv2"/> is selected.
+/// </summary>
+/// <remarks>
+///     MenuBars may be hosted by any View and will, by default, be positioned the full width across the top of the View's
+///     Viewport.
+/// </remarks>
+public class MenuBarv2 : Menuv2, IDesignable
+{
+    /// <inheritdoc/>
+    public MenuBarv2 () : this ([]) { }
+
+    /// <inheritdoc/>
+    public MenuBarv2 (IEnumerable<MenuBarItemv2> menuBarItems) : base (menuBarItems)
+    {
+        TabStop = TabBehavior.TabGroup;
+        Y = 0;
+        Width = Dim.Fill ();
+        Orientation = Orientation.Horizontal;
+
+        AddCommand (Command.Right, MoveRight);
+        KeyBindings.Add (Key.CursorRight, Command.Right);
+
+        AddCommand (Command.Left, MoveLeft);
+        KeyBindings.Add (Key.CursorLeft, Command.Left);
+
+        return;
+
+        bool? MoveLeft (ICommandContext? ctx) { return AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop); }
+
+        bool? MoveRight (ICommandContext? ctx) { return AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); }
+    }
+
+    /// <inheritdoc/>
+    protected override void OnSelectedMenuItemChanged (MenuItemv2? selected)
+    {
+        if (selected is MenuBarItemv2 { } selectedMenuBarItem)
+        {
+            ShowPopover (selectedMenuBarItem);
+        }
+    }
+
+    /// <inheritdoc/>
+    public override void EndInit ()
+    {
+        base.EndInit ();
+
+        if (Border is { })
+        {
+            Border.Thickness = new (0);
+            Border.LineStyle = LineStyle.None;
+        }
+
+        // TODO: This needs to be done whenever a menuitem in any memubaritem changes
+        foreach (MenuBarItemv2? mbi in SubViews.Select(s => s as MenuBarItemv2))
+        {
+            Application.Popover?.Register (mbi?.PopoverMenu);
+        }
+    }
+
+    /// <inheritdoc/>
+    protected override bool OnAccepting (CommandEventArgs args)
+    {
+        if (args.Context?.Source is MenuBarItemv2 { PopoverMenu: { } } menuBarItem)
+        {
+            ShowPopover (menuBarItem);
+        }
+
+        return base.OnAccepting (args);
+    }
+
+    private void ShowPopover (MenuBarItemv2? menuBarItem)
+    {
+        if (menuBarItem?.PopoverMenu is { IsInitialized: false })
+        {
+            menuBarItem.PopoverMenu.BeginInit ();
+            menuBarItem.PopoverMenu.EndInit ();
+        }
+
+        // If the active popover is a PopoverMenu and part of this MenuBar...
+        if (menuBarItem?.PopoverMenu is null
+            && Application.Popover?.GetActivePopover () is PopoverMenu popoverMenu
+            && popoverMenu?.Root?.SuperMenuItem?.SuperView == this)
+        {
+            Application.Popover?.HidePopover (popoverMenu);
+        }
+
+        menuBarItem?.PopoverMenu?.MakeVisible (new Point (menuBarItem.FrameToScreen ().X, menuBarItem.FrameToScreen ().Bottom));
+
+        if (menuBarItem?.PopoverMenu?.Root is { })
+        {
+            menuBarItem.PopoverMenu.Root.SuperMenuItem = menuBarItem;
+        }
+    }
+
+    /// <inheritdoc/>
+    public bool EnableForDesign<TContext> (ref readonly TContext context) where TContext : notnull
+    {
+        Add (
+             new MenuBarItemv2 (
+                                "_File",
+                                [
+                                    new MenuItemv2 (this, Command.New),
+                                    new MenuItemv2 (this, Command.Open),
+                                    new MenuItemv2 (this, Command.Save),
+                                    new MenuItemv2 (this, Command.SaveAs),
+                                    new Line (),
+                                    new MenuItemv2
+                                    {
+                                        Title = "_Preferences",
+                                        SubMenu = new (
+                                                       [
+                                                           new MenuItemv2
+                                                           {
+                                                               CommandView = new CheckBox ()
+                                                               {
+                                                                   Title = "O_ption",
+                                                               },
+                                                               HelpText = "Toggle option"
+                                                           },
+                                                           new MenuItemv2
+                                                           {
+                                                               Title = "_Settings...",
+                                                               HelpText = "More settings",
+                                                               Action = () =>  MessageBox.Query ("Settings", "This is the Settings Dialog\n", ["_Ok", "_Cancel"])
+                                                           }
+                                                       ]
+                                                      )
+                                    },
+                                    new Line (),
+                                    new MenuItemv2 (this, Command.Quit)
+                                ]
+                               )
+            );
+
+        Add (
+             new MenuBarItemv2 (
+                                "_Edit",
+                                [
+                                    new MenuItemv2 (this, Command.Cut),
+                                    new MenuItemv2 (this, Command.Copy),
+                                    new MenuItemv2 (this, Command.Paste),
+                                    new Line (),
+                                    new MenuItemv2 (this, Command.SelectAll)
+                                ]
+                               )
+            );
+
+        Add (
+             new MenuBarItemv2 (
+                                "_Help",
+                                [
+                                    new MenuItemv2
+                                    {
+                                        Title = "_Online Help...",
+                                        Action = () => MessageBox.Query ("Online Help", "https://gui-cs.github.io/Terminal.GuiV2Docs", "Ok")
+                                    },
+                                    new MenuItemv2
+                                    {
+                                        Title = "About...",
+                                        Action = () => MessageBox.Query ("About", "Something About Mary.", "Ok")
+                                    }
+                                ]
+                               )
+            );
+
+        //        if (context is not Func<string, bool> actionFn)
+        //        {
+        //            actionFn = (_) => true;
+        //        }
+
+        //        View? targetView = context as View;
+
+        //        Add (new MenuItemv2 (targetView,
+        //                             Command.NotBound,
+        //                             "_File",
+        //                             new MenuItem []
+        //                             {
+        //                                 new (
+        //                                      "_New",
+        //                                      "",
+        //                                      () => actionFn ("New"),
+        //                                      null,
+        //                                      null,
+        //                                      KeyCode.CtrlMask | KeyCode.N
+        //                                     ),
+        //                                 new (
+        //                                      "_Open",
+        //                                      "",
+        //                                      () => actionFn ("Open"),
+        //                                      null,
+        //                                      null,
+        //                                      KeyCode.CtrlMask | KeyCode.O
+        //                                     ),
+        //                                 new (
+        //                                      "_Save",
+        //                                      "",
+        //                                      () => actionFn ("Save"),
+        //                                      null,
+        //                                      null,
+        //                                      KeyCode.CtrlMask | KeyCode.S
+        //                                     ),
+        //#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
+        //                                 null,
+        //#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
+
+        //                                 // Don't use Application.Quit so we can disambiguate between quitting and closing the toplevel
+        //                                 new (
+        //                                      "_Quit",
+        //                                      "",
+        //                                      () => actionFn ("Quit"),
+        //                                      null,
+        //                                      null,
+        //                                      KeyCode.CtrlMask | KeyCode.Q
+        //                                     )
+        //                             }
+        //                            ),
+        //            new MenuBarItem (
+        //                             "_Edit",
+        //                             new MenuItem []
+        //                             {
+        //                                 new (
+        //                                      "_Copy",
+        //                                      "",
+        //                                      () => actionFn ("Copy"),
+        //                                      null,
+        //                                      null,
+        //                                      KeyCode.CtrlMask | KeyCode.C
+        //                                     ),
+        //                                 new (
+        //                                      "C_ut",
+        //                                      "",
+        //                                      () => actionFn ("Cut"),
+        //                                      null,
+        //                                      null,
+        //                                      KeyCode.CtrlMask | KeyCode.X
+        //                                     ),
+        //                                 new (
+        //                                      "_Paste",
+        //                                      "",
+        //                                      () => actionFn ("Paste"),
+        //                                      null,
+        //                                      null,
+        //                                      KeyCode.CtrlMask | KeyCode.V
+        //                                     ),
+        //                                 new MenuBarItem (
+        //                                                  "_Find and Replace",
+        //                                                  new MenuItem []
+        //                                                  {
+        //                                                      new (
+        //                                                           "F_ind",
+        //                                                           "",
+        //                                                           () => actionFn ("Find"),
+        //                                                           null,
+        //                                                           null,
+        //                                                           KeyCode.CtrlMask | KeyCode.F
+        //                                                          ),
+        //                                                      new (
+        //                                                           "_Replace",
+        //                                                           "",
+        //                                                           () => actionFn ("Replace"),
+        //                                                           null,
+        //                                                           null,
+        //                                                           KeyCode.CtrlMask | KeyCode.H
+        //                                                          ),
+        //                                                      new MenuBarItem (
+        //                                                                       "_3rd Level",
+        //                                                                       new MenuItem []
+        //                                                                       {
+        //                                                                           new (
+        //                                                                                "_1st",
+        //                                                                                "",
+        //                                                                                () => actionFn (
+        //                                                                                                "1"
+        //                                                                                               ),
+        //                                                                                null,
+        //                                                                                null,
+        //                                                                                KeyCode.F1
+        //                                                                               ),
+        //                                                                           new (
+        //                                                                                "_2nd",
+        //                                                                                "",
+        //                                                                                () => actionFn (
+        //                                                                                                "2"
+        //                                                                                               ),
+        //                                                                                null,
+        //                                                                                null,
+        //                                                                                KeyCode.F2
+        //                                                                               )
+        //                                                                       }
+        //                                                                      ),
+        //                                                      new MenuBarItem (
+        //                                                                       "_4th Level",
+        //                                                                       new MenuItem []
+        //                                                                       {
+        //                                                                           new (
+        //                                                                                "_5th",
+        //                                                                                "",
+        //                                                                                () => actionFn (
+        //                                                                                                "5"
+        //                                                                                               ),
+        //                                                                                null,
+        //                                                                                null,
+        //                                                                                KeyCode.CtrlMask
+        //                                                                                | KeyCode.D5
+        //                                                                               ),
+        //                                                                           new (
+        //                                                                                "_6th",
+        //                                                                                "",
+        //                                                                                () => actionFn (
+        //                                                                                                "6"
+        //                                                                                               ),
+        //                                                                                null,
+        //                                                                                null,
+        //                                                                                KeyCode.CtrlMask
+        //                                                                                | KeyCode.D6
+        //                                                                               )
+        //                                                                       }
+        //                                                                      )
+        //                                                  }
+        //                                                 ),
+        //                                 new (
+        //                                      "_Select All",
+        //                                      "",
+        //                                      () => actionFn ("Select All"),
+        //                                      null,
+        //                                      null,
+        //                                      KeyCode.CtrlMask
+        //                                      | KeyCode.ShiftMask
+        //                                      | KeyCode.S
+        //                                     )
+        //                             }
+        //                            ),
+        //            new MenuBarItem ("_About", "Top-Level", () => actionFn ("About"))
+        //        ];
+        return true;
+    }
+}

+ 200 - 0
Terminal.Gui/Views/Menu/MenuItemv2.cs

@@ -0,0 +1,200 @@
+#nullable enable
+
+using System.ComponentModel;
+using Terminal.Gui.Resources;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     A <see cref="Shortcut"/>-dervied object to be used as a menu item in a <see cref="Menuv2"/>. Has title, an
+///     associated help text, and an action to execute on activation.
+/// </summary>
+public class MenuItemv2 : Shortcut
+{
+    /// <summary>
+    ///     Creates a new instance of <see cref="MenuItemv2"/>.
+    /// </summary>
+    public MenuItemv2 () : base (Key.Empty, null, null) { }
+
+    /// <summary>
+    ///     Creates a new instance of <see cref="MenuItemv2"/>, binding it to <paramref name="targetView"/> and
+    ///     <paramref name="command"/>. The Key <paramref name="targetView"/>
+    ///     has bound to <paramref name="command"/> will be used as <see cref="Key"/>.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    /// <param name="targetView">
+    ///     The View that <paramref name="command"/> will be invoked on when user does something that causes the Shortcut's
+    ///     Accept
+    ///     event to be raised.
+    /// </param>
+    /// <param name="command">
+    ///     The Command to invoke on <paramref name="targetView"/>. The Key <paramref name="targetView"/>
+    ///     has bound to <paramref name="command"/> will be used as <see cref="Key"/>
+    /// </param>
+    /// <param name="commandText">The text to display for the command.</param>
+    /// <param name="helpText">The help text to display.</param>
+    /// <param name="subMenu">The submenu to display when the user selects this menu item.</param>
+    public MenuItemv2 (View? targetView, Command command, string? commandText = null, string? helpText = null, Menuv2? subMenu = null)
+        : base (
+                targetView?.HotKeyBindings.GetFirstFromCommands (command)!,
+                string.IsNullOrEmpty (commandText) ? GlobalResources.GetString ($"cmd.{command}") : commandText,
+                null,
+                string.IsNullOrEmpty (helpText) ? GlobalResources.GetString ($"cmd.{command}.Help") : helpText
+               )
+    {
+        TargetView = targetView;
+        Command = command;
+
+        SubMenu = subMenu;
+    }
+
+    // TODO: Consider moving TargetView and Command to Shortcut?
+
+    /// <summary>
+    ///     Gets the target <see cref="View"/> that the <see cref="Command"/> will be invoked on.
+    /// </summary>
+    public View? TargetView { get; set; }
+
+    private Command _command;
+
+    /// <summary>
+    ///     Gets the <see cref="Command"/> that will be invoked on <see cref="TargetView"/> when the MenuItem is selected.
+    /// </summary>
+    public Command Command
+    {
+        get => _command;
+        set
+        {
+            if (_command == value)
+            {
+                return;
+            }
+
+            _command = value;
+
+            if (string.IsNullOrEmpty (Title))
+            {
+                Title = GlobalResources.GetString ($"cmd.{_command}") ?? string.Empty;
+            }
+
+            if (string.IsNullOrEmpty (HelpText))
+            {
+                HelpText = GlobalResources.GetString ($"cmd.{_command}.Help") ?? string.Empty;
+            }
+        }
+    }
+
+    internal override bool? DispatchCommand (ICommandContext? commandContext)
+    {
+        bool? ret = null;
+
+        if (commandContext is { Command: not Command.HotKey })
+        {
+            if (TargetView is { })
+            {
+                commandContext.Command = Command;
+                ret = TargetView.InvokeCommand (Command, commandContext);
+            }
+            else
+            {
+                // Is this an Application-bound command?
+                ret = Application.InvokeCommandsBoundToKey (Key);
+            }
+        }
+
+        if (ret is not true)
+        {
+            ret = base.DispatchCommand (commandContext);
+        }
+
+        Logging.Trace ($"{commandContext?.Source?.Title}");
+
+        RaiseAccepted (commandContext);
+
+        return ret;
+    }
+
+    private Menuv2? _subMenu;
+
+    /// <summary>
+    ///     The submenu to display when the user selects this menu item.
+    /// </summary>
+    public Menuv2? SubMenu
+    {
+        get => _subMenu;
+        set
+        {
+            _subMenu = value;
+
+            if (_subMenu is { })
+            {
+                // TODO: This is a temporary hack - add a flag or something instead
+                KeyView.Text = $"{Glyphs.RightArrow}";
+                _subMenu.SuperMenuItem = this;
+            }
+        }
+    }
+
+    /// <inheritdoc/>
+    protected override bool OnMouseEnter (CancelEventArgs eventArgs)
+    {
+        // When the mouse enters a menuitem, we set focus to it automatically.
+
+        // Logging.Trace($"OnEnter {Title}");
+        SetFocus ();
+
+        return base.OnMouseEnter (eventArgs);
+    }
+
+    // TODO: Consider moving Accepted to Shortcut?
+
+    /// <summary>
+    ///     Riases the <see cref="OnAccepted"/>/<see cref="Accepted"/> event indicating this item (or submenu)
+    ///     was accepted. This is used to determine when to hide the menu.
+    /// </summary>
+    /// <param name="ctx"></param>
+    /// <returns></returns>
+    protected bool? RaiseAccepted (ICommandContext? ctx)
+    {
+        Logging.Trace ($"RaiseAccepted: {ctx}");
+        CommandEventArgs args = new () { Context = ctx };
+
+        OnAccepted (args);
+        Accepted?.Invoke (this, args);
+
+        return true;
+    }
+
+    /// <summary>
+    ///     Called when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the
+    ///     menu.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    /// <param name="args"></param>
+    protected virtual void OnAccepted (CommandEventArgs args) { }
+
+    /// <summary>
+    ///     Raised when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the
+    ///     menu.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         See <see cref="RaiseAccepted"/> for more information.
+    ///     </para>
+    /// </remarks>
+    public event EventHandler<CommandEventArgs>? Accepted;
+
+    /// <inheritdoc/>
+    protected override void Dispose (bool disposing)
+    {
+        if (disposing)
+        {
+            SubMenu?.Dispose ();
+            SubMenu = null;
+        }
+
+        base.Dispose (disposing);
+    }
+}

+ 172 - 0
Terminal.Gui/Views/Menu/Menuv2.cs

@@ -0,0 +1,172 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+///     A <see cref="Bar"/>-derived object to be used as a verticaly-oriented menu. Each subview is a <see cref="MenuItemv2"/>.
+/// </summary>
+public class Menuv2 : Bar
+{
+    /// <inheritdoc/>
+    public Menuv2 () : this ([]) { }
+
+    /// <inheritdoc/>
+    public Menuv2 (IEnumerable<View>? shortcuts) : base (shortcuts)
+    {
+        Orientation = Orientation.Vertical;
+        Width = Dim.Auto ();
+        Height = Dim.Auto (DimAutoStyle.Content, 1);
+
+        Border!.Thickness = new Thickness (1, 1, 1, 1);
+        Border.LineStyle = LineStyle.Single;
+
+    }
+
+    /// <summary>
+    ///     Gets or sets the menu item that opened this menu as a sub-menu.
+    /// </summary>
+    public MenuItemv2? SuperMenuItem { get; set; }
+
+    /// <inheritdoc />
+    protected override void OnVisibleChanged ()
+    {
+        if (Visible)
+        {
+            SelectedMenuItem = SubViews.Where (mi => mi is MenuItemv2).ElementAtOrDefault (0) as MenuItemv2;
+        }
+    }
+
+    /// <inheritdoc />
+    public override void EndInit ()
+    {
+        base.EndInit ();
+
+        if (Border is { })
+        {
+        }
+    }
+
+    /// <inheritdoc />
+    protected override void OnSubViewAdded (View view)
+    {
+        base.OnSubViewAdded (view);
+
+        if (view is MenuItemv2 menuItem)
+        {
+            menuItem.CanFocus = true;
+
+            AddCommand (menuItem.Command, RaiseAccepted);
+
+            menuItem.Selecting += MenuItemOnSelecting;
+            menuItem.Accepting += MenuItemOnAccepting;
+            menuItem.Accepted += MenuItemOnAccepted;
+
+            void MenuItemOnSelecting (object? sender, CommandEventArgs e)
+            {
+                Logging.Trace ($"Selecting: {e.Context?.Source?.Title}");
+            }
+
+            void MenuItemOnAccepting (object? sender, CommandEventArgs e)
+            {
+                Logging.Trace ($"Accepting: {e.Context?.Source?.Title}");
+            }
+
+            void MenuItemOnAccepted (object? sender, CommandEventArgs e)
+            {
+                Logging.Trace ($"Accepted: {e.Context?.Source?.Title}");
+                RaiseAccepted (e.Context);
+            }
+        }
+
+        if (view is Line line)
+        {
+            // Grow line so we get autojoin line
+            line.X = Pos.Func (() => -Border!.Thickness.Left);
+            line.Width = Dim.Fill ()! + Dim.Func (() => Border!.Thickness.Right);
+        }
+    }
+
+    // TODO: Consider moving Accepted to Bar?
+
+    /// <summary>
+    ///     Riases the <see cref="OnAccepted"/>/<see cref="Accepted"/> event indicating an item in this menu (or submenu)
+    ///     was accepted. This is used to determine when to hide the menu.
+    /// </summary>
+    /// <param name="ctx"></param>
+    /// <returns></returns>
+    protected bool? RaiseAccepted (ICommandContext? ctx)
+    {
+        Logging.Trace ($"RaiseAccepted: {ctx}");
+        CommandEventArgs args = new () { Context = ctx };
+
+        OnAccepted (args);
+        Accepted?.Invoke (this, args);
+
+        return true;
+    }
+
+    /// <summary>
+    ///     Called when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the menu.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    /// <param name="args"></param>
+    protected virtual void OnAccepted (CommandEventArgs args) { }
+
+    /// <summary>
+    ///     Raised when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the menu.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    ///    See <see cref="RaiseAccepted"/> for more information.
+    /// </para>
+    /// </remarks>
+    public event EventHandler<CommandEventArgs>? Accepted;
+
+    /// <inheritdoc />
+    protected override void OnFocusedChanged (View? previousFocused, View? focused)
+    {
+        base.OnFocusedChanged (previousFocused, focused);
+        SelectedMenuItem = focused as MenuItemv2;
+        RaiseSelectedMenuItemChanged (SelectedMenuItem);
+    }
+
+    /// <summary>
+    ///     Gets or set the currently selected menu item. This is a helper that
+    ///     tracks <see cref="View.Focused"/>.
+    /// </summary>
+    public MenuItemv2? SelectedMenuItem
+    {
+        get => Focused as MenuItemv2;
+        set
+        {
+            if (value == Focused)
+            {
+                return;
+            }
+
+            // Note we DO NOT set focus here; This property tracks Focused
+        }
+    }
+
+    internal void RaiseSelectedMenuItemChanged (MenuItemv2? selected)
+    {
+        //Logging.Trace ($"RaiseSelectedMenuItemChanged: {selected?.Title}");
+
+        OnSelectedMenuItemChanged (selected);
+        SelectedMenuItemChanged?.Invoke (this, selected);
+    }
+
+    /// <summary>
+    ///     Called when the the selected menu item has changed.
+    /// </summary>
+    /// <param name="selected"></param>
+    protected virtual void OnSelectedMenuItemChanged (MenuItemv2? selected)
+    {
+    }
+
+    /// <summary>
+    ///     Raised when the selected menu item has changed.
+    /// </summary>
+    public event EventHandler<MenuItemv2?>? SelectedMenuItemChanged;
+
+}

+ 532 - 0
Terminal.Gui/Views/Menu/PopoverMenu.cs

@@ -0,0 +1,532 @@
+#nullable enable
+using System.Diagnostics;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Provides a cascading popover menu.
+/// </summary>
+public class PopoverMenu : PopoverBaseImpl
+{
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="PopoverMenu"/> class.
+    /// </summary>
+    public PopoverMenu () : this (null) { }
+
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="PopoverMenu"/> class with the specified root <see cref="Menuv2"/>.
+    /// </summary>
+    public PopoverMenu (Menuv2? root)
+    {
+        base.Visible = false;
+        //base.ColorScheme = Colors.ColorSchemes ["Menu"];
+
+        Root = root;
+
+        AddCommand (Command.Right, MoveRight);
+        KeyBindings.Add (Key.CursorRight, Command.Right);
+
+        AddCommand (Command.Left, MoveLeft);
+        KeyBindings.Add (Key.CursorLeft, Command.Left);
+
+        // TODO: Remove; for debugging for now
+        AddCommand (
+                    Command.NotBound,
+                    ctx =>
+                    {
+                        Logging.Trace ($"popoverMenu NotBound: {ctx}");
+
+                        return false;
+                    });
+
+        KeyBindings.Add (DefaultKey, Command.Quit);
+        KeyBindings.ReplaceCommands (Application.QuitKey, Command.Quit);
+
+        AddCommand (
+                    Command.Quit,
+                    ctx =>
+                    {
+                        if (!Visible)
+                        {
+                            return false;
+                        }
+
+                        Visible = false;
+
+                        return RaiseAccepted (ctx);
+                    });
+
+        return;
+
+        bool? MoveLeft (ICommandContext? ctx)
+        {
+            if (Focused == Root)
+            {
+                return false;
+            }
+
+            if (MostFocused is MenuItemv2 { SuperView: Menuv2 focusedMenu })
+            {
+                focusedMenu.SuperMenuItem?.SetFocus ();
+
+                return true;
+            }
+
+            return AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop);
+        }
+
+        bool? MoveRight (ICommandContext? ctx)
+        {
+            if (Focused == Root)
+            {
+                return false;
+            }
+
+            if (MostFocused is MenuItemv2 { SubMenu.Visible: true } focused)
+            {
+                focused.SubMenu.SetFocus ();
+
+                return true;
+            }
+
+            return AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
+        }
+    }
+
+    /// <summary>
+    ///     The mouse flags that will cause the popover menu to be visible. The default is
+    ///     <see cref="MouseFlags.Button3Clicked"/> which is typically the right mouse button.
+    /// </summary>
+    public static MouseFlags MouseFlags { get; set; } = MouseFlags.Button3Clicked;
+
+    /// <summary>The default key for activating popover menus.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static Key DefaultKey { get; set; } = Key.F10.WithShift;
+
+    /// <summary>
+    ///     Makes the popover menu visible and locates it at <paramref name="idealScreenPosition"/>. The actual position of the menu
+    ///     will be adjusted to
+    ///     ensure the menu fully fits on the screen, and the mouse cursor is over the first cell of the
+    ///     first MenuItem.
+    /// </summary>
+    /// <param name="idealScreenPosition">If <see langword="null"/>, the current mouse position will be used.</param>
+    public void MakeVisible (Point? idealScreenPosition = null)
+    {
+        UpdateKeyBindings ();
+        SetPosition (idealScreenPosition);
+        Application.Popover?.ShowPopover (this);
+    }
+
+    /// <summary>
+    ///     Locates the popover menu at <paramref name="idealScreenPosition"/>. The actual position of the menu will be adjusted to
+    ///     ensure the menu fully fits on the screen, and the mouse cursor is over the first cell of the
+    ///     first MenuItem (if possible).
+    /// </summary>
+    /// <param name="idealScreenPosition">If <see langword="null"/>, the current mouse position will be used.</param>
+    public void SetPosition (Point? idealScreenPosition = null)
+    {
+        idealScreenPosition ??= Application.GetLastMousePosition ();
+
+        if (idealScreenPosition is { } && Root is { })
+        {
+            Point pos = idealScreenPosition.Value;
+
+            if (!Root.IsInitialized)
+            {
+                Root.BeginInit();
+                Root.EndInit ();
+                Root.Layout ();
+            }
+            pos = GetMostVisibleLocationForSubMenu (Root, pos);
+
+            Root.X = pos.X;
+            Root.Y = pos.Y;
+        }
+    }
+
+    /// <inheritdoc/>
+    protected override void OnVisibleChanged ()
+    {
+        base.OnVisibleChanged ();
+
+        if (Visible)
+        {
+            AddAndShowSubMenu (_root);
+        }
+        else
+        {
+            HideAndRemoveSubMenu (_root);
+            Application.Popover?.HidePopover (this);
+        }
+    }
+
+    private Menuv2? _root;
+
+    /// <summary>
+    ///     Gets or sets the <see cref="Menuv2"/> that is the root of the Popover Menu.
+    /// </summary>
+    public Menuv2? Root
+    {
+        get => _root;
+        set
+        {
+            if (_root == value)
+            {
+                return;
+            }
+
+            if (_root is { })
+            {
+                _root.Accepting -= MenuOnAccepting;
+            }
+
+            HideAndRemoveSubMenu (_root);
+
+            _root = value;
+
+            if (_root is { })
+            {
+                _root.Accepting += MenuOnAccepting;
+            }
+
+            UpdateKeyBindings ();
+
+            IEnumerable<Menuv2> allMenus = GetAllSubMenus ();
+
+            foreach (Menuv2 menu in allMenus)
+            {
+                menu.Accepting += MenuOnAccepting;
+                menu.Accepted += MenuAccepted;
+                menu.SelectedMenuItemChanged += MenuOnSelectedMenuItemChanged;
+            }
+        }
+    }
+
+    private void UpdateKeyBindings ()
+    {
+        // TODO: This needs to be done whenever any MenuItem in the menu tree changes to support dynamic menus
+        // TODO: And it needs to clear them first
+        IEnumerable<MenuItemv2> all = GetMenuItemsOfAllSubMenus ();
+
+        foreach (MenuItemv2 menuItem in all.Where(mi => mi.Command != Command.NotBound))
+        {
+            if (menuItem.TargetView is { })
+            {
+                // A TargetView implies HotKey
+                // Automatically set MenuItem.Key
+                Key? key = menuItem.TargetView.HotKeyBindings.GetFirstFromCommands (menuItem.Command);
+
+                if (key is { IsValid: true })
+                {
+                    if (menuItem.Key.IsValid)
+                    {
+                        //Logging.Warning ("Do not specify a Key for MenuItems where a Command is specified. Key will be determined automatically.");
+                    }
+
+                    menuItem.Key = key;
+                    Logging.Trace ($"HotKey: {menuItem.Key}->{menuItem.Command}");
+                }
+            }
+            else
+            {
+                // No TargetView implies Application HotKey
+                Key? key = Application.KeyBindings.GetFirstFromCommands (menuItem.Command);
+
+                if (key is { IsValid: true })
+                {
+                    if (menuItem.Key.IsValid)
+                    {
+                        // Logging.Warning ("App HotKey: Do not specify a Key for MenuItems where a Command is specified. Key will be determined automatically.");
+                    }
+
+                    menuItem.Key = key;
+                    Logging.Trace ($"App HotKey: {menuItem.Key}->{menuItem.Command}");
+                }
+            }
+        }
+
+        foreach (MenuItemv2 menuItem in all.Where (mi => mi is { Command: Command.NotBound, Key.IsValid: true }))
+        {
+
+        }
+
+    }
+
+    /// <inheritdoc/>
+    protected override bool OnKeyDownNotHandled (Key key)
+    {
+        // See if any of our MenuItems have this key as Key
+        IEnumerable<MenuItemv2> all = GetMenuItemsOfAllSubMenus ();
+
+        foreach (MenuItemv2 menuItem in all)
+        {
+            if (menuItem.Key == key)
+            {
+                return menuItem.NewKeyDownEvent (key);
+            }
+        }
+
+        return base.OnKeyDownNotHandled (key);
+    }
+
+    /// <summary>
+    ///     Gets all the submenus in the PopoverMenu.
+    /// </summary>
+    /// <returns></returns>
+    internal IEnumerable<Menuv2> GetAllSubMenus ()
+    {
+        List<Menuv2> result = [];
+
+        if (Root == null)
+        {
+            return result;
+        }
+
+        Stack<Menuv2> stack = new ();
+        stack.Push (Root);
+
+        while (stack.Count > 0)
+        {
+            Menuv2 currentMenu = stack.Pop ();
+            result.Add (currentMenu);
+
+            foreach (View subView in currentMenu.SubViews)
+            {
+                if (subView is MenuItemv2 menuItem && menuItem.SubMenu != null)
+                {
+                    stack.Push (menuItem.SubMenu);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /// <summary>
+    ///     Gets all the MenuItems in the PopoverMenu.
+    /// </summary>
+    /// <returns></returns>
+    internal IEnumerable<MenuItemv2> GetMenuItemsOfAllSubMenus ()
+    {
+        List<MenuItemv2> result = [];
+
+        foreach (Menuv2 menu in GetAllSubMenus ())
+        {
+            foreach (View subView in menu.SubViews)
+            {
+                if (subView is MenuItemv2 menuItem)
+                {
+                    result.Add (menuItem);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /// <summary>
+    ///     Pops up the submenu of the specified MenuItem, if there is one.
+    /// </summary>
+    /// <param name="menuItem"></param>
+    internal void ShowSubMenu (MenuItemv2? menuItem)
+    {
+        var menu = menuItem?.SuperView as Menuv2;
+
+        if (menu is { })
+        {
+            menu.Layout ();
+        }
+        // If there's a visible peer, remove / hide it
+
+        // Debug.Assert (menu is null || menu?.SubViews.Count (v => v is MenuItemv2 { SubMenu.Visible: true }) < 2);
+
+        if (menu?.SubViews.FirstOrDefault (v => v is MenuItemv2 { SubMenu.Visible: true }) is MenuItemv2 visiblePeer)
+        {
+            HideAndRemoveSubMenu (visiblePeer.SubMenu);
+            visiblePeer.ForceFocusColors = false;
+        }
+
+        if (menuItem is { SubMenu: { Visible: false } })
+        {
+            AddAndShowSubMenu (menuItem.SubMenu);
+
+            Point idealLocation = ScreenToViewport (
+                                                    new (
+                                                         menuItem.FrameToScreen ().Right - menuItem.SubMenu.GetAdornmentsThickness ().Left,
+                                                         menuItem.FrameToScreen ().Top - menuItem.SubMenu.GetAdornmentsThickness ().Top));
+
+            Point pos = GetMostVisibleLocationForSubMenu (menuItem.SubMenu, idealLocation);
+            menuItem.SubMenu.X = pos.X;
+            menuItem.SubMenu.Y = pos.Y;
+
+            menuItem.ForceFocusColors = true;
+        }
+    }
+
+    /// <summary>
+    ///     Gets the most visible screen-relative location for <paramref name="menu"/>.
+    /// </summary>
+    /// <param name="menu">The menu to locate.</param>
+    /// <param name="idealLocation">Ideal screen-relative location.</param>
+    /// <returns></returns>
+    internal Point GetMostVisibleLocationForSubMenu (Menuv2 menu, Point idealLocation)
+    {
+        var pos = Point.Empty;
+
+        // Calculate the initial position to the right of the menu item
+        GetLocationEnsuringFullVisibility (
+                                           menu,
+                                           idealLocation.X,
+                                           idealLocation.Y,
+                                           out int nx,
+                                           out int ny);
+
+        return new (nx, ny);
+    }
+
+    private void AddAndShowSubMenu (Menuv2? menu)
+    {
+        if (menu is { SuperView: null })
+        {
+            // TODO: Find the menu item below the mouse, if any, and select it
+
+            // TODO: Enable No Border menu style
+            menu.Border.LineStyle = LineStyle.Single;
+            menu.Border.Thickness = new (1);
+
+            if (!menu.IsInitialized)
+            {
+                menu.BeginInit ();
+                menu.EndInit ();
+            }
+
+            menu.ClearFocus ();
+            base.Add (menu);
+
+
+            // IMPORTANT: This must be done after adding the menu to the super view or Add will try
+            // to set focus to it.
+            menu.Visible = true;
+
+            menu.Layout ();
+        }
+    }
+
+    private void HideAndRemoveSubMenu (Menuv2? menu)
+    {
+        if (menu is { Visible: true })
+        {
+            // If there's a visible submenu, remove / hide it
+            // Debug.Assert (menu.SubViews.Count (v => v is MenuItemv2 { SubMenu.Visible: true }) <= 1);
+
+            if (menu.SubViews.FirstOrDefault (v => v is MenuItemv2 { SubMenu.Visible: true }) is MenuItemv2 visiblePeer)
+            {
+                HideAndRemoveSubMenu (visiblePeer.SubMenu);
+                visiblePeer.ForceFocusColors = false;
+            }
+
+            menu.Visible = false;
+            menu.ClearFocus ();
+            base.Remove (menu);
+
+            if (menu == Root)
+            {
+                Visible = false;
+            }
+        }
+    }
+
+    private void MenuOnAccepting (object? sender, CommandEventArgs e)
+    {
+        if (e.Context?.Command != Command.HotKey)
+        {
+            Visible = false;
+        }
+        else
+        {
+            // This supports the case when a hotkey of a menuitem with a submenu is pressed
+            e.Cancel = true;
+        }
+
+        Logging.Trace ($"{e.Context?.Source?.Title}");
+    }
+
+    private void MenuAccepted (object? sender, CommandEventArgs e)
+    {
+        Logging.Trace ($"{e.Context?.Source?.Title}");
+
+        if (e.Context?.Source is MenuItemv2 { SubMenu: null })
+        {
+            HideAndRemoveSubMenu (_root);
+            RaiseAccepted (e.Context);
+        }
+        else if (e.Context?.Source is MenuItemv2 { SubMenu: { } } menuItemWithSubMenu)
+        {
+            ShowSubMenu (menuItemWithSubMenu);
+        }
+    }
+
+    /// <summary>
+    ///     Riases the <see cref="OnAccepted"/>/<see cref="Accepted"/> event indicating a menu (or submenu)
+    ///     was accepted and the Menus in the PopoverMenu were hidden. Use this to determine when to hide the PopoverMenu.
+    /// </summary>
+    /// <param name="ctx"></param>
+    /// <returns></returns>
+    protected bool? RaiseAccepted (ICommandContext? ctx)
+    {
+        Logging.Trace ($"RaiseAccepted: {ctx}");
+        CommandEventArgs args = new () { Context = ctx };
+
+        OnAccepted (args);
+        Accepted?.Invoke (this, args);
+
+        return true;
+    }
+
+    /// <summary>
+    ///     Called when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the
+    ///     menu.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    /// <param name="args"></param>
+    protected virtual void OnAccepted (CommandEventArgs args) { }
+
+    /// <summary>
+    ///     Raised when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the
+    ///     menu.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         See <see cref="RaiseAccepted"/> for more information.
+    ///     </para>
+    /// </remarks>
+    public event EventHandler<CommandEventArgs>? Accepted;
+
+    private void MenuOnSelectedMenuItemChanged (object? sender, MenuItemv2? e)
+    {
+        //Logging.Trace ($"{e}");
+        ShowSubMenu (e);
+    }
+
+    /// <inheritdoc/>
+    protected override void Dispose (bool disposing)
+    {
+        if (disposing)
+        {
+            IEnumerable<Menuv2> allMenus = GetAllSubMenus ();
+
+            foreach (Menuv2 menu in allMenus)
+            {
+                menu.Accepting -= MenuOnAccepting;
+                menu.Accepted -= MenuAccepted;
+                menu.SelectedMenuItemChanged -= MenuOnSelectedMenuItemChanged;
+            }
+
+            _root?.Dispose ();
+            _root = null;
+        }
+
+        base.Dispose (disposing);
+    }
+}

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

@@ -1,51 +0,0 @@
-using System;
-using System.Reflection;
-
-namespace Terminal.Gui;
-
-/// <summary>
-///     A menu bar is a <see cref="View"/> that snaps to the top of a <see cref="Toplevel"/> displaying set of
-///     <see cref="Shortcut"/>s.
-/// </summary>
-public class MenuBarv2 : Bar
-{
-    /// <inheritdoc/>
-    public MenuBarv2 () : this ([]) { }
-
-    /// <inheritdoc/>
-    public MenuBarv2 (IEnumerable<Shortcut> shortcuts) : base (shortcuts)
-    {
-        Y = 0;
-        Width = Dim.Fill ();
-        Height = Dim.Auto (DimAutoStyle.Content, 1);
-        BorderStyle = LineStyle.Dashed;
-        ColorScheme = Colors.ColorSchemes ["Menu"];
-        Orientation = Orientation.Horizontal;
-
-        SubViewLayout += MenuBarv2_LayoutStarted;
-    }
-
-    // MenuBarv2 arranges the items horizontally.
-    // The first item has no left border, the last item has no right border.
-    // The Shortcuts are configured with the command, help, and key views aligned in reverse order (EndToStart).
-    private void MenuBarv2_LayoutStarted (object sender, LayoutEventArgs e)
-    {
-       
-    }
-
-    /// <inheritdoc/>
-    protected override void OnSubViewAdded (View subView)
-    {
-        subView.CanFocus = false;
-
-        if (subView is Shortcut shortcut)
-        {
-            // TODO: not happy about using AlignmentModes for this. Too implied.
-            // TODO: instead, add a property (a style enum?) to Shortcut to control this
-            //shortcut.AlignmentModes = AlignmentModes.EndToStart;
-
-            shortcut.KeyView.Visible = false;
-            shortcut.HelpView.Visible = false;
-        }
-    }
-}

+ 0 - 98
Terminal.Gui/Views/Menuv2.cs

@@ -1,98 +0,0 @@
-using System;
-using System.ComponentModel;
-using System.Reflection;
-
-namespace Terminal.Gui;
-
-/// <summary>
-/// </summary>
-public class Menuv2 : Bar
-{
-    /// <inheritdoc/>
-    public Menuv2 () : this ([]) { }
-
-    /// <inheritdoc/>
-    public Menuv2 (IEnumerable<Shortcut> shortcuts) : base (shortcuts)
-    {
-        Orientation = Orientation.Vertical;
-        Width = Dim.Auto ();
-        Height = Dim.Auto (DimAutoStyle.Content, 1);
-        Initialized += Menuv2_Initialized;
-        VisibleChanged += OnVisibleChanged;
-    }
-
-    private void OnVisibleChanged (object sender, EventArgs e)
-    {
-        if (Visible)
-        {
-            //Application.GrabMouse(this);
-        }
-        else
-        {
-            if (Application.MouseGrabView == this)
-            {
-                //Application.UngrabMouse ();
-            }
-        }
-    }
-
-    private void Menuv2_Initialized (object sender, EventArgs e)
-    {
-        Border.Thickness = new Thickness (1, 1, 1, 1);
-        Border.LineStyle = LineStyle.Single;
-        ColorScheme = Colors.ColorSchemes ["Menu"];
-    }
-
-    // Menuv2 arranges the items horizontally.
-    // The first item has no left border, the last item has no right border.
-    // The Shortcuts are configured with the command, help, and key views aligned in reverse order (EndToStart).
-    /// <inheritdoc />
-    protected override void OnSubViewLayout (LayoutEventArgs args)
-    {
-        for (int index = 0; index < SubViews.Count; index++)
-        {
-            View barItem = SubViews.ElementAt (index);
-
-            if (!barItem.Visible)
-            {
-                continue;
-            }
-
-        }
-        base.OnSubViewLayout (args);
-    }
-
-    /// <inheritdoc/>
-    /// 
-    protected override void OnSubViewAdded (View subView)
-    {
-        if (subView is Shortcut shortcut)
-        {
-            shortcut.CanFocus = true;
-            shortcut.Orientation = Orientation.Vertical;
-            shortcut.HighlightStyle |= HighlightStyle.Hover;
-
-            // TODO: not happy about using AlignmentModes for this. Too implied.
-            // TODO: instead, add a property (a style enum?) to Shortcut to control this
-            //shortcut.AlignmentModes = AlignmentModes.EndToStart;
-
-            shortcut.Accepting += ShortcutOnAccept;
-
-            void ShortcutOnAccept (object sender, CommandEventArgs e)
-            {
-                if (Arrangement.HasFlag (ViewArrangement.Overlapped) && Visible)
-                {
-                    Visible = false;
-                    e.Cancel = true;
-
-                    return;
-                }
-
-                //if (!e.Handled)
-                //{
-                //    RaiseAcceptEvent ();
-                //}
-            }
-        }
-    }
-}

+ 6 - 12
Terminal.Gui/Views/MessageBox.cs

@@ -360,26 +360,20 @@ public static class MessageBox
                     b.IsDefault = true;
                     b.IsDefault = true;
                     b.Accepting += (_, e) =>
                     b.Accepting += (_, e) =>
                                    {
                                    {
-                                       if (e.Context is not CommandContext<KeyBinding> keyCommandContext)
-                                       {
-                                           return;
-                                       }
-
-                                       // TODO: With https://github.com/gui-cs/Terminal.Gui/issues/3778 we can simplify this
-                                       if (keyCommandContext.Binding.Data is Button button)
+                                       if (e?.Context?.Source is Button button)
                                        {
                                        {
                                            Clicked = (int)button.Data!;
                                            Clicked = (int)button.Data!;
                                        }
                                        }
-                                       else if (keyCommandContext.Binding.Target is Button btn)
-                                       {
-                                           Clicked = (int)btn.Data!;
-                                       }
                                        else
                                        else
                                        {
                                        {
                                            Clicked = defaultButton;
                                            Clicked = defaultButton;
                                        }
                                        }
 
 
-                                       e.Cancel = true;
+                                       if (e is { })
+                                       {
+                                           e.Cancel = true;
+                                       }
+
                                        Application.RequestStop ();
                                        Application.RequestStop ();
                                    };
                                    };
                 }
                 }

+ 1 - 1
Terminal.Gui/Views/ScrollBar/ScrollSlider.cs

@@ -241,7 +241,7 @@ public class ScrollSlider : View, IOrientation, IDesignable
         OnScrolled (distance);
         OnScrolled (distance);
         Scrolled?.Invoke (this, new (in distance));
         Scrolled?.Invoke (this, new (in distance));
 
 
-        RaiseSelecting (new CommandContext<KeyBinding> (Command.Select, new KeyBinding ([Command.Select], null, distance)));
+        RaiseSelecting (new CommandContext<KeyBinding> (Command.Select, this, new KeyBinding ([Command.Select], null, distance)));
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 73 - 90
Terminal.Gui/Views/Shortcut.cs

@@ -46,38 +46,6 @@ public class Shortcut : View, IOrientation, IDesignable
     /// </summary>
     /// </summary>
     public Shortcut () : this (Key.Empty, null, null, null) { }
     public Shortcut () : this (Key.Empty, null, null, null) { }
 
 
-    /// <summary>
-    ///     Creates a new instance of <see cref="Shortcut"/>, binding it to <paramref name="targetView"/> and
-    ///     <paramref name="command"/>. The Key <paramref name="targetView"/>
-    ///     has bound to <paramref name="command"/> will be used as <see cref="Key"/>.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         This is a helper API that simplifies creation of multiple Shortcuts when adding them to <see cref="Bar"/>-based
-    ///         objects, like <see cref="MenuBarv2"/>.
-    ///     </para>
-    /// </remarks>
-    /// <param name="targetView">
-    ///     The View that <paramref name="command"/> will be invoked on when user does something that causes the Shortcut's Accept
-    ///     event to be raised.
-    /// </param>
-    /// <param name="command">
-    ///     The Command to invoke on <paramref name="targetView"/>. The Key <paramref name="targetView"/>
-    ///     has bound to <paramref name="command"/> will be used as <see cref="Key"/>
-    /// </param>
-    /// <param name="commandText">The text to display for the command.</param>
-    /// <param name="helpText">The help text to display.</param>
-    public Shortcut (View targetView, Command command, string commandText, string? helpText = null)
-        : this (
-                targetView?.HotKeyBindings.GetFirstFromCommands (command)!,
-                commandText,
-                null,
-                helpText)
-    {
-        _targetView = targetView;
-        Command = command;
-    }
-
     /// <summary>
     /// <summary>
     ///     Creates a new instance of <see cref="Shortcut"/>.
     ///     Creates a new instance of <see cref="Shortcut"/>.
     /// </summary>
     /// </summary>
@@ -132,11 +100,12 @@ public class Shortcut : View, IOrientation, IDesignable
 
 
         Action = action;
         Action = action;
 
 
-        SubViewLayout += OnLayoutStarted;
-
         ShowHide ();
         ShowHide ();
     }
     }
 
 
+    /// <inheritdoc />
+    protected override bool OnClearingViewport () { return base.OnClearingViewport (); }
+
     // Helper to set Width consistently
     // Helper to set Width consistently
     internal Dim GetWidthDimAuto ()
     internal Dim GetWidthDimAuto ()
     {
     {
@@ -158,10 +127,11 @@ public class Shortcut : View, IOrientation, IDesignable
     {
     {
         if (args.NewValue.HasFlag (HighlightStyle.Hover))
         if (args.NewValue.HasFlag (HighlightStyle.Hover))
         {
         {
-            HasFocus = true;
+            SetFocus ();
+            return true;
         }
         }
 
 
-        return true;
+        return false;
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -204,13 +174,14 @@ public class Shortcut : View, IOrientation, IDesignable
             SetHelpViewDefaultLayout ();
             SetHelpViewDefaultLayout ();
         }
         }
 
 
-        if (KeyView.Visible && Key != Key.Empty)
+        if (KeyView.Visible && (Key != Key.Empty || KeyView.Text != string.Empty))
         {
         {
             Add (KeyView);
             Add (KeyView);
             SetKeyViewDefaultLayout ();
             SetKeyViewDefaultLayout ();
         }
         }
 
 
-        SetColors ();
+        // BUGBUG: Causes ever other layout to lose focus colors
+        //SetColors ();
     }
     }
 
 
     // Force Width to DimAuto to calculate natural width and then set it back
     // Force Width to DimAuto to calculate natural width and then set it back
@@ -234,8 +205,11 @@ public class Shortcut : View, IOrientation, IDesignable
     }
     }
 
 
     // When layout starts, we need to adjust the layout of the HelpView and KeyView
     // When layout starts, we need to adjust the layout of the HelpView and KeyView
-    private void OnLayoutStarted (object? sender, LayoutEventArgs e)
+    /// <inheritdoc />
+    protected override void OnSubViewLayout (LayoutEventArgs e)
     {
     {
+        base.OnSubViewLayout (e);
+
         ShowHide ();
         ShowHide ();
         ForceCalculateNaturalWidth ();
         ForceCalculateNaturalWidth ();
 
 
@@ -278,18 +252,6 @@ public class Shortcut : View, IOrientation, IDesignable
 
 
     #region Accept/Select/HotKey Command Handling
     #region Accept/Select/HotKey Command Handling
 
 
-    private readonly View? _targetView; // If set, _command will be invoked
-
-    /// <summary>
-    ///     Gets the target <see cref="View"/> that the <see cref="Command"/> will be invoked on.
-    /// </summary>
-    public View? TargetView => _targetView;
-
-    /// <summary>
-    ///     Gets the <see cref="Command"/> that will be invoked on <see cref="TargetView"/> when the Shortcut is activated.
-    /// </summary>
-    public Command Command { get; }
-
     private void AddCommands ()
     private void AddCommands ()
     {
     {
         // Accept (Enter key) -
         // Accept (Enter key) -
@@ -300,18 +262,24 @@ public class Shortcut : View, IOrientation, IDesignable
         AddCommand (Command.Select, DispatchCommand);
         AddCommand (Command.Select, DispatchCommand);
     }
     }
 
 
-    private bool? DispatchCommand (ICommandContext? commandContext)
+    /// <summary>
+    ///     Called when a Command has been invoked on this Shortcut.
+    /// </summary>
+    /// <param name="commandContext"></param>
+    /// <returns></returns>
+    internal virtual bool? DispatchCommand (ICommandContext? commandContext)
     {
     {
-        CommandContext<KeyBinding>? keyCommandContext = commandContext is CommandContext<KeyBinding> ? (CommandContext<KeyBinding>)commandContext : default;
+        CommandContext<KeyBinding>? keyCommandContext = commandContext as CommandContext<KeyBinding>? ?? default (CommandContext<KeyBinding>);
 
 
         if (keyCommandContext?.Binding.Data != this)
         if (keyCommandContext?.Binding.Data != this)
         {
         {
-            // Invoke Select on the command view to cause it to change state if it wants to
+            // Invoke Select on the CommandView to cause it to change state if it wants to
             // If this causes CommandView to raise Accept, we eat it
             // If this causes CommandView to raise Accept, we eat it
             keyCommandContext = keyCommandContext!.Value with { Binding = keyCommandContext.Value.Binding with { Data = this } };
             keyCommandContext = keyCommandContext!.Value with { Binding = keyCommandContext.Value.Binding with { Data = this } };
             CommandView.InvokeCommand (Command.Select, keyCommandContext);
             CommandView.InvokeCommand (Command.Select, keyCommandContext);
         }
         }
 
 
+        // BUGBUG: Why does this use keyCommandContext and not commandContext?
         if (RaiseSelecting (keyCommandContext) is true)
         if (RaiseSelecting (keyCommandContext) is true)
         {
         {
             return true;
             return true;
@@ -322,6 +290,10 @@ public class Shortcut : View, IOrientation, IDesignable
 
 
         var cancel = false;
         var cancel = false;
 
 
+        if (commandContext is { })
+        {
+            commandContext.Source = this;
+        }
         cancel = RaiseAccepting (commandContext) is true;
         cancel = RaiseAccepting (commandContext) is true;
 
 
         if (cancel)
         if (cancel)
@@ -342,10 +314,6 @@ public class Shortcut : View, IOrientation, IDesignable
             cancel = true;
             cancel = true;
         }
         }
 
 
-        if (_targetView is { })
-        {
-            _targetView.InvokeCommand (Command, commandContext);
-        }
 
 
         return cancel;
         return cancel;
     }
     }
@@ -502,7 +470,6 @@ public class Shortcut : View, IOrientation, IDesignable
                     InvokeCommand<KeyBinding> (Command.Select, new ([Command.Select], null, this));
                     InvokeCommand<KeyBinding> (Command.Select, new ([Command.Select], null, this));
                 }
                 }
 
 
-                // BUGBUG: This prevents NumericUpDown on statusbar in HexEditor from working
                 e.Cancel = true;
                 e.Cancel = true;
             }
             }
         }
         }
@@ -668,12 +635,6 @@ public class Shortcut : View, IOrientation, IDesignable
 
 
             _minimumKeyTextSize = value;
             _minimumKeyTextSize = value;
             SetKeyViewDefaultLayout ();
             SetKeyViewDefaultLayout ();
-
-            //// TODO: Prob not needed
-            //CommandView.SetNeedsLayout ();
-            //HelpView.SetNeedsLayout ();
-            //KeyView.SetNeedsLayout ();
-            //SetSubViewNeedsDraw ();
         }
         }
     }
     }
 
 
@@ -700,28 +661,30 @@ public class Shortcut : View, IOrientation, IDesignable
 
 
     private void UpdateKeyBindings (Key oldKey)
     private void UpdateKeyBindings (Key oldKey)
     {
     {
-        if (Key.IsValid)
+        if (!Key.IsValid)
         {
         {
-            if (BindKeyToApplication)
-            {
-                if (oldKey != Key.Empty)
-                {
-                    Application.KeyBindings.Remove (oldKey);
-                }
+            return;
+        }
 
 
-                Application.KeyBindings.Remove (Key);
-                Application.KeyBindings.Add (Key, this, Command.HotKey);
-            }
-            else
+        if (BindKeyToApplication)
+        {
+            if (oldKey != Key.Empty)
             {
             {
-                if (oldKey != Key.Empty)
-                {
-                    HotKeyBindings.Remove (oldKey);
-                }
+                Application.KeyBindings.Remove (oldKey);
+            }
 
 
-                HotKeyBindings.Remove (Key);
-                HotKeyBindings.Add (Key,  Command.HotKey);
+            Application.KeyBindings.Remove (Key);
+            Application.KeyBindings.Add (Key, this, Command.HotKey);
+        }
+        else
+        {
+            if (oldKey != Key.Empty)
+            {
+                HotKeyBindings.Remove (oldKey);
             }
             }
+
+            HotKeyBindings.Remove (Key);
+            HotKeyBindings.Add (Key, Command.HotKey);
         }
         }
     }
     }
 
 
@@ -740,12 +703,29 @@ public class Shortcut : View, IOrientation, IDesignable
         }
         }
     }
     }
 
 
+    private bool _forceFocusColors;
+
+    /// <summary>
+    ///     TODO: IS this needed?
+    /// </summary>
+    public bool ForceFocusColors
+    {
+        get => _forceFocusColors;
+        set
+        {
+            _forceFocusColors = value;
+            SetColors (value);
+            //SetNeedsDraw();
+        }
+    }
+
     private ColorScheme? _nonFocusColorScheme;
     private ColorScheme? _nonFocusColorScheme;
+
     /// <summary>
     /// <summary>
     /// </summary>
     /// </summary>
     internal void SetColors (bool highlight = false)
     internal void SetColors (bool highlight = false)
     {
     {
-        if (HasFocus || highlight)
+        if (HasFocus || highlight || ForceFocusColors)
         {
         {
             if (_nonFocusColorScheme is null)
             if (_nonFocusColorScheme is null)
             {
             {
@@ -757,10 +737,10 @@ public class Shortcut : View, IOrientation, IDesignable
             // When we have focus, we invert the colors
             // When we have focus, we invert the colors
             base.ColorScheme = new (base.ColorScheme)
             base.ColorScheme = new (base.ColorScheme)
             {
             {
-                Normal = base.ColorScheme.Focus,
-                HotNormal = base.ColorScheme.HotFocus,
-                HotFocus = base.ColorScheme.HotNormal,
-                Focus = base.ColorScheme.Normal
+                Normal = GetFocusColor (),
+                HotNormal = GetHotFocusColor (),
+                HotFocus = GetHotNormalColor (),
+                Focus = GetNormalColor (),
             };
             };
         }
         }
         else
         else
@@ -781,8 +761,8 @@ public class Shortcut : View, IOrientation, IDesignable
         {
         {
             var cs = new ColorScheme (base.ColorScheme)
             var cs = new ColorScheme (base.ColorScheme)
             {
             {
-                Normal = base.ColorScheme.HotNormal,
-                HotNormal = base.ColorScheme.Normal
+                Normal = GetHotNormalColor (),
+                HotNormal = GetNormalColor ()
             };
             };
             KeyView.ColorScheme = cs;
             KeyView.ColorScheme = cs;
         }
         }
@@ -803,7 +783,10 @@ public class Shortcut : View, IOrientation, IDesignable
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? view) { SetColors (); }
+    protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? view)
+    {
+        SetColors ();
+    }
 
 
     #endregion Focus
     #endregion Focus
 
 

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

@@ -841,7 +841,7 @@ public class Slider<T> : View, IOrientation
     private void DrawSlider ()
     private void DrawSlider ()
     {
     {
         // TODO: be more surgical on clear
         // TODO: be more surgical on clear
-        ClearViewport ();
+        ClearViewport (null);
 
 
         // Attributes
         // Attributes
 
 

+ 68 - 75
Terminal.Gui/Views/TextField.cs

@@ -316,7 +316,7 @@ public class TextField : View
                     Command.Context,
                     Command.Context,
                     () =>
                     () =>
                     {
                     {
-                        ShowContextMenu ();
+                        ShowContextMenu (keyboard: true);
 
 
                         return true;
                         return true;
                     }
                     }
@@ -395,14 +395,12 @@ public class TextField : View
         KeyBindings.Add (Key.R.WithCtrl, Command.DeleteAll);
         KeyBindings.Add (Key.R.WithCtrl, Command.DeleteAll);
         KeyBindings.Add (Key.D.WithCtrl.WithShift, Command.DeleteAll);
         KeyBindings.Add (Key.D.WithCtrl.WithShift, Command.DeleteAll);
 
 
-        _currentCulture = Thread.CurrentThread.CurrentUICulture;
+        KeyBindings.Remove (Key.Space);
 
 
-        ContextMenu = new () { Host = this };
-        ContextMenu.KeyChanged += ContextMenu_KeyChanged;
+        _currentCulture = Thread.CurrentThread.CurrentUICulture;
 
 
+        CreateContextMenu ();
         KeyBindings.Add (ContextMenu.Key, Command.Context);
         KeyBindings.Add (ContextMenu.Key, Command.Context);
-
-        KeyBindings.Remove (Key.Space);
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -421,7 +419,8 @@ public class TextField : View
     public Color CaptionColor { get; set; }
     public Color CaptionColor { get; set; }
 
 
     /// <summary>Get the <see cref="ContextMenu"/> for this view.</summary>
     /// <summary>Get the <see cref="ContextMenu"/> for this view.</summary>
-    public ContextMenu ContextMenu { get; }
+    [CanBeNull]
+    public ContextMenuv2 ContextMenu { get; private set; }
 
 
     /// <summary>Sets or gets the current cursor position.</summary>
     /// <summary>Sets or gets the current cursor position.</summary>
     public virtual int CursorPosition
     public virtual int CursorPosition
@@ -801,7 +800,7 @@ public class TextField : View
             && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition)
             && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition)
             && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
             && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
             && !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)
             && !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)
-            && !ev.Flags.HasFlag (ContextMenu.MouseFlags))
+            && !ev.Flags.HasFlag (PopoverMenu.MouseFlags))
         {
         {
             return false;
             return false;
         }
         }
@@ -901,9 +900,10 @@ public class TextField : View
             ClearAllSelection ();
             ClearAllSelection ();
             PrepareSelection (0, _text.Count);
             PrepareSelection (0, _text.Count);
         }
         }
-        else if (ev.Flags == ContextMenu.MouseFlags)
+        else if (ev.Flags == PopoverMenu.MouseFlags)
         {
         {
-            ShowContextMenu ();
+            PositionCursor (ev);
+            ShowContextMenu (false);
         }
         }
 
 
         //SetNeedsDraw ();
         //SetNeedsDraw ();
@@ -1223,72 +1223,31 @@ public class TextField : View
         }
         }
     }
     }
 
 
-    private MenuBarItem BuildContextMenuBarItem ()
+    private void CreateContextMenu ()
     {
     {
-        return new (
-                    new MenuItem []
-                    {
-                        new (
-                             Strings.ctxSelectAll,
-                             "",
-                             () => SelectAll (),
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.SelectAll)
-                            ),
-                        new (
-                             Strings.ctxDeleteAll,
-                             "",
-                             () => DeleteAll (),
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.DeleteAll)
-                            ),
-                        new (
-                             Strings.ctxCopy,
-                             "",
-                             () => Copy (),
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Copy)
-                            ),
-                        new (
-                             Strings.ctxCut,
-                             "",
-                             () => Cut (),
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Cut)
-                            ),
-                        new (
-                             Strings.ctxPaste,
-                             "",
-                             () => Paste (),
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Paste)
-                            ),
-                        new (
-                             Strings.ctxUndo,
-                             "",
-                             () => Undo (),
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Undo)
-                            ),
-                        new (
-                             Strings.ctxRedo,
-                             "",
-                             () => Redo (),
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Redo)
-                            )
-                    }
-                   );
+        DisposeContextMenu ();
+        ContextMenuv2 menu = new (new List<MenuItemv2> ()
+        {
+            new (this, Command.SelectAll, Strings.ctxSelectAll),
+            new (this, Command.DeleteAll, Strings.ctxDeleteAll),
+            new (this, Command.Copy, Strings.ctxCopy),
+            new (this, Command.Cut, Strings.ctxCut),
+            new (this, Command.Paste, Strings.ctxPaste),
+            new (this, Command.Undo, Strings.ctxUndo),
+            new (this, Command.Redo, Strings.ctxRedo),
+        });
+
+        HotKeyBindings.Remove (menu.Key);
+        HotKeyBindings.Add (menu.Key, Command.Context);
+        menu.KeyChanged += ContextMenu_KeyChanged;
+
+        ContextMenu = menu;
     }
     }
 
 
-    private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode); }
+    private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e)
+    {
+        KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode);
+    }
 
 
     private List<Rune> DeleteSelectedText ()
     private List<Rune> DeleteSelectedText ()
     {
     {
@@ -1808,14 +1767,27 @@ public class TextField : View
     private void SetText (List<Rune> newText) { Text = StringExtensions.ToString (newText); }
     private void SetText (List<Rune> newText) { Text = StringExtensions.ToString (newText); }
     private void SetText (IEnumerable<Rune> newText) { SetText (newText.ToList ()); }
     private void SetText (IEnumerable<Rune> newText) { SetText (newText.ToList ()); }
 
 
-    private void ShowContextMenu ()
+    private void ShowContextMenu (bool keyboard)
     {
     {
+
         if (!Equals (_currentCulture, Thread.CurrentThread.CurrentUICulture))
         if (!Equals (_currentCulture, Thread.CurrentThread.CurrentUICulture))
         {
         {
             _currentCulture = Thread.CurrentThread.CurrentUICulture;
             _currentCulture = Thread.CurrentThread.CurrentUICulture;
+
+            if (ContextMenu is { })
+            {
+                CreateContextMenu ();
+            }
         }
         }
 
 
-        ContextMenu.Show (BuildContextMenuBarItem ());
+        if (keyboard)
+        {
+            ContextMenu?.MakeVisible(ViewportToScreen (new Point (_cursorPosition - ScrollOffset, 1)));
+        }
+        else
+        {
+            ContextMenu?.MakeVisible ();
+        }
     }
     }
 
 
     private void TextField_SuperViewChanged (object sender, SuperViewChangedEventArgs e)
     private void TextField_SuperViewChanged (object sender, SuperViewChangedEventArgs e)
@@ -1849,6 +1821,27 @@ public class TextField : View
             Autocomplete.PopupInsideContainer = false;
             Autocomplete.PopupInsideContainer = false;
         }
         }
     }
     }
+
+    private void DisposeContextMenu ()
+    {
+        if (ContextMenu is { })
+        {
+            ContextMenu.Visible = false;
+            ContextMenu.KeyChanged -= ContextMenu_KeyChanged;
+            ContextMenu.Dispose ();
+            ContextMenu = null;
+        }
+    }
+
+    /// <inheritdoc />
+    protected override void Dispose (bool disposing)
+    {
+        if (disposing)
+        {
+            DisposeContextMenu ();
+        }
+        base.Dispose (disposing);
+    }
 }
 }
 
 
 /// <summary>
 /// <summary>

+ 39 - 84
Terminal.Gui/Views/TextView.cs

@@ -2290,11 +2290,7 @@ public class TextView : View
                     Command.Context,
                     Command.Context,
                     () =>
                     () =>
                     {
                     {
-                        ContextMenu!.Position = new (
-                                                     CursorPosition.X - _leftColumn + 2,
-                                                     CursorPosition.Y - _topRow + 2
-                                                    );
-                        ShowContextMenu ();
+                        ShowContextMenu (true);
 
 
                         return true;
                         return true;
                     }
                     }
@@ -2410,9 +2406,7 @@ public class TextView : View
 
 
         _currentCulture = Thread.CurrentThread.CurrentUICulture;
         _currentCulture = Thread.CurrentThread.CurrentUICulture;
 
 
-        ContextMenu = new ();
-        ContextMenu.KeyChanged += ContextMenu_KeyChanged!;
-
+        ContextMenu = CreateContextMenu ();
         KeyBindings.Add (ContextMenu.Key, Command.Context);
         KeyBindings.Add (ContextMenu.Key, Command.Context);
     }
     }
 
 
@@ -2496,8 +2490,8 @@ public class TextView : View
     /// </summary>
     /// </summary>
     public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete ();
     public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete ();
 
 
-    /// <summary>Get the <see cref="ContextMenu"/> for this view.</summary>
-    public ContextMenu? ContextMenu { get; }
+    /// <summary>Get the <see cref="ContextMenuv2"/> for this view.</summary>
+    public ContextMenuv2? ContextMenu { get; private set; }
 
 
     /// <summary>Gets the cursor column.</summary>
     /// <summary>Gets the cursor column.</summary>
     /// <value>The cursor column.</value>
     /// <value>The cursor column.</value>
@@ -3505,8 +3499,12 @@ public class TextView : View
         }
         }
         else if (ev.Flags == ContextMenu!.MouseFlags)
         else if (ev.Flags == ContextMenu!.MouseFlags)
         {
         {
-            ContextMenu.Position = ViewportToScreen ((Viewport with { X = ev.Position.X, Y = ev.Position.Y }).Location);
-            ShowContextMenu ();
+            ContextMenu!.X = ev.ScreenPosition.X;
+            ContextMenu!.Y = ev.ScreenPosition.Y;
+
+            ShowContextMenu (false);
+            //ContextMenu.Position = ViewportToScreen ((Viewport with { X = ev.Position.X, Y = ev.Position.Y }).Location);
+            //ShowContextMenu ();
         }
         }
 
 
         return true;
         return true;
@@ -4150,77 +4148,22 @@ public class TextView : View
 
 
     private void AppendClipboard (string text) { Clipboard.Contents += text; }
     private void AppendClipboard (string text) { Clipboard.Contents += text; }
 
 
-    private MenuBarItem? BuildContextMenuBarItem ()
+    private ContextMenuv2 CreateContextMenu ()
     {
     {
-        return new (
-                    new MenuItem []
+        ContextMenuv2 menu = new (new List<MenuItemv2> ()
                     {
                     {
-                        new (
-                             Strings.ctxSelectAll,
-                             "",
-                             SelectAll,
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.SelectAll)
-                            ),
-                        new (
-                             Strings.ctxDeleteAll,
-                             "",
-                             DeleteAll,
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.DeleteAll)
-                            ),
-                        new (
-                             Strings.ctxCopy,
-                             "",
-                             Copy,
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Copy)
-                            ),
-                        new (
-                             Strings.ctxCut,
-                             "",
-                             Cut,
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Cut)
-                            ),
-                        new (
-                             Strings.ctxPaste,
-                             "",
-                             Paste,
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Paste)
-                            ),
-                        new (
-                             Strings.ctxUndo,
-                             "",
-                             Undo,
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Undo)
-                            ),
-                        new (
-                             Strings.ctxRedo,
-                             "",
-                             Redo,
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Redo)
-                            ),
-                        new (
-                             Strings.ctxColors,
-                             "",
-                             () => PromptForColors (),
-                             null,
-                             null,
-                             (KeyCode)KeyBindings.GetFirstFromCommands (Command.Open)
-                            )
-                    }
-                   );
+            new (this, Command.SelectAll, Strings.ctxSelectAll),
+            new (this, Command.DeleteAll, Strings.ctxDeleteAll),
+            new (this, Command.Copy, Strings.ctxCopy),
+            new (this, Command.Cut, Strings.ctxCut),
+            new (this, Command.Paste, Strings.ctxPaste),
+            new (this, Command.Undo, Strings.ctxUndo),
+            new (this, Command.Redo, Strings.ctxRedo),
+        });
+
+        menu.KeyChanged += ContextMenu_KeyChanged;
+
+        return menu;
     }
     }
 
 
     private void ClearRegion (int left, int top, int right, int bottom)
     private void ClearRegion (int left, int top, int right, int bottom)
@@ -4331,7 +4274,7 @@ public class TextView : View
         DoNeededAction ();
         DoNeededAction ();
     }
     }
 
 
-    private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey, e.NewKey); }
+    private void ContextMenu_KeyChanged (object? sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey, e.NewKey); }
 
 
     private bool DeleteTextBackwards ()
     private bool DeleteTextBackwards ()
     {
     {
@@ -6387,14 +6330,14 @@ public class TextView : View
         }
         }
     }
     }
 
 
-    private void ShowContextMenu ()
+    private void ShowContextMenu (bool keyboard)
     {
     {
         if (!Equals (_currentCulture, Thread.CurrentThread.CurrentUICulture))
         if (!Equals (_currentCulture, Thread.CurrentThread.CurrentUICulture))
         {
         {
             _currentCulture = Thread.CurrentThread.CurrentUICulture;
             _currentCulture = Thread.CurrentThread.CurrentUICulture;
         }
         }
 
 
-        ContextMenu!.Show (BuildContextMenuBarItem ());
+        ContextMenu?.MakeVisible(ViewportToScreen(new Point (CursorPosition.X, CursorPosition.Y)));
     }
     }
 
 
     private void StartSelecting ()
     private void StartSelecting ()
@@ -6567,6 +6510,18 @@ public class TextView : View
             SetNeedsDraw ();
             SetNeedsDraw ();
         }
         }
     }
     }
+
+    /// <inheritdoc />
+    protected override void Dispose (bool disposing)
+    {
+        if (disposing && ContextMenu is { })
+        {
+            ContextMenu.Visible = false;
+            ContextMenu.Dispose ();
+            ContextMenu = null;
+        }
+        base.Dispose (disposing);
+    }
 }
 }
 
 
 /// <summary>
 /// <summary>

+ 444 - 0
Tests/UnitTests/Application/ApplicationPopoverTests.cs

@@ -0,0 +1,444 @@
+using static System.Net.Mime.MediaTypeNames;
+
+namespace Terminal.Gui.ApplicationTests;
+
+public class ApplicationPopoverTests
+{
+    [Fact]
+    public void Popover_ApplicationInit_Inits ()
+    {
+        // Arrange
+        Assert.Null (Application.Popover);
+        Application.Init (new FakeDriver ());
+
+        // Act
+        Assert.NotNull (Application.Popover);
+
+        Application.ResetState (true);
+    }
+
+    [Fact]
+    public void Popover_ApplicationShutdown_CleansUp ()
+    {
+        // Arrange
+        Assert.Null (Application.Popover);
+        Application.Init (new FakeDriver ());
+
+        // Act
+        Assert.NotNull (Application.Popover);
+
+        Application.Shutdown ();
+
+        // Test
+        Assert.Null (Application.Popover);
+    }
+
+    [Fact]
+    public void Popover_NotCleanedUp_On_End ()
+    {
+        // Arrange
+        Assert.Null (Application.Popover);
+        Application.Init (new FakeDriver ());
+        Assert.NotNull (Application.Popover);
+        Application.Iteration += (s, a) => Application.RequestStop ();
+
+        var top = new Toplevel ();
+        RunState rs = Application.Begin (top);
+
+        // Act
+        Application.End (rs);
+
+        // Test
+        Assert.NotNull (Application.Popover);
+
+        top.Dispose ();
+        Application.Shutdown ();
+    }
+
+    [Fact]
+    public void Popover_Active_Hidden_On_End ()
+    {
+        // Arrange
+        Assert.Null (Application.Popover);
+        Application.Init (new FakeDriver ());
+        Application.Iteration += (s, a) => Application.RequestStop ();
+
+        var top = new Toplevel ();
+        RunState rs = Application.Begin (top);
+
+        IPopoverTestClass popover = new ();
+
+        Application.Popover?.ShowPopover (popover);
+        Assert.True (popover.Visible);
+
+        // Act
+        Application.End (rs);
+        top.Dispose ();
+
+        // Test
+        Assert.False (popover.Visible);
+        Assert.NotNull (Application.Popover);
+
+        popover.Dispose ();
+        Application.Shutdown ();
+    }
+
+    public class IPopoverTestClass : View, IPopover
+    {
+        public List<Key> HandledKeys { get; } = new List<Key> ();
+        public int NewCommandInvokeCount { get; private set; }
+
+        public IPopoverTestClass ()
+        {
+            CanFocus = true;
+            AddCommand (Command.New, NewCommandHandler);
+            HotKeyBindings.Add (Key.N.WithCtrl, Command.New);
+
+            bool? NewCommandHandler (ICommandContext ctx)
+            {
+                NewCommandInvokeCount++;
+
+                return false;
+            }
+        }
+
+        protected override bool OnKeyDown (Key key)
+        {
+            HandledKeys.Add (key);
+            return false;
+        }
+    }
+    //[Fact]
+    //public void Popover_SetToNull ()
+    //{
+    //    // Arrange
+    //    var popover = new View ();
+    //    Application.Popover = popover;
+
+    //    // Act
+    //    Application.Popover = null;
+
+    //    // Assert
+    //    Assert.Null (Application.Popover);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_VisibleChangedEvent ()
+    //{
+    //    // Arrange
+    //    var popover = new View ()
+    //    {
+    //        Visible = false
+    //    };
+    //    Application.Popover = popover;
+    //    bool eventTriggered = false;
+
+    //    popover.VisibleChanged += (sender, e) => eventTriggered = true;
+
+    //    // Act
+    //    popover.Visible = true;
+
+    //    // Assert
+    //    Assert.True (eventTriggered);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_InitializesCorrectly ()
+    //{
+    //    // Arrange
+    //    var popover = new View ();
+
+    //    // Act
+    //    Application.Popover = popover;
+
+    //    // Assert
+    //    Assert.True (popover.IsInitialized);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_SetsColorScheme ()
+    //{
+    //    // Arrange
+    //    var popover = new View ();
+    //    var topColorScheme = new ColorScheme ();
+    //    Application.Top = new Toplevel { ColorScheme = topColorScheme };
+
+    //    // Act
+    //    Application.Popover = popover;
+
+    //    // Assert
+    //    Assert.Equal (topColorScheme, popover.ColorScheme);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_VisibleChangedToTrue_SetsFocus ()
+    //{
+    //    // Arrange
+    //    var popover = new View ()
+    //    {
+    //        Visible = false,
+    //        CanFocus = true
+    //    };
+    //    Application.Popover = popover;
+
+    //    // Act
+    //    popover.Visible = true;
+
+    //    // Assert
+    //    Assert.True (popover.Visible);
+    //    Assert.True (popover.HasFocus);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Theory]
+    //[InlineData(-1, -1)]
+    //[InlineData (0, 0)]
+    //[InlineData (2048, 2048)]
+    //[InlineData (2049, 2049)]
+    //public void Popover_VisibleChangedToTrue_Locates_In_Visible_Position (int x, int y)
+    //{
+    //    // Arrange
+    //    var popover = new View ()
+    //    {
+    //        X = x,
+    //        Y = y,
+    //        Visible = false,
+    //        CanFocus = true,
+    //        Width = 1,
+    //        Height = 1
+    //    };
+    //    Application.Popover = popover;
+
+    //    // Act
+    //    popover.Visible = true;
+    //    Application.LayoutAndDraw();
+
+    //    // Assert
+    //    Assert.True (Application.Screen.Contains (popover.Frame));
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_VisibleChangedToFalse_Hides_And_Removes_Focus ()
+    //{
+    //    // Arrange
+    //    var popover = new View ()
+    //    {
+    //        Visible = false,
+    //        CanFocus = true
+    //    };
+    //    Application.Popover = popover;
+    //    popover.Visible = true;
+
+    //    // Act
+    //    popover.Visible = false;
+
+    //    // Assert
+    //    Assert.False (popover.Visible);
+    //    Assert.False (popover.HasFocus);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_Quit_Command_Hides ()
+    //{
+    //    // Arrange
+    //    var popover = new View ()
+    //    {
+    //        Visible = false,
+    //        CanFocus = true
+    //    };
+    //    Application.Popover = popover;
+    //    popover.Visible = true;
+    //    Assert.True (popover.Visible);
+    //    Assert.True (popover.HasFocus);
+
+    //    // Act
+    //    Application.RaiseKeyDownEvent (Application.QuitKey);
+
+    //    // Assert
+    //    Assert.False (popover.Visible);
+    //    Assert.False (popover.HasFocus);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_MouseClick_Outside_Hides_Passes_Event_On ()
+    //{
+    //    // Arrange
+    //    Application.Top = new Toplevel ()
+    //    {
+    //        Id = "top",
+    //        Height = 10,
+    //        Width = 10,
+    //    };
+
+    //    View otherView = new ()
+    //    {
+    //        X = 1,
+    //        Y = 1,
+    //        Height = 1,
+    //        Width = 1,
+    //        Id = "otherView",
+    //    };
+
+    //    bool otherViewPressed = false;
+    //    otherView.MouseEvent += (sender, e) =>
+    //                            {
+    //                                otherViewPressed = e.Flags.HasFlag(MouseFlags.Button1Pressed);
+    //                            };
+
+    //    Application.Top.Add (otherView);
+
+    //    var popover = new View ()
+    //    {
+    //        Id = "popover",
+    //        X = 5,
+    //        Y = 5,
+    //        Width = 1,
+    //        Height = 1,
+    //        Visible = false,
+    //        CanFocus = true
+    //    };
+
+    //    Application.Popover = popover;
+    //    popover.Visible = true;
+    //    Assert.True (popover.Visible);
+    //    Assert.True (popover.HasFocus);
+
+    //    // Act
+    //    // Click on popover
+    //    Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed, ScreenPosition = new (5, 5) });
+    //    Assert.True (popover.Visible);
+
+    //    // Click outside popover (on button)
+    //    Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed, ScreenPosition = new (1, 1) });
+
+    //    // Assert
+    //    Assert.True (otherViewPressed);
+    //    Assert.False (popover.Visible);
+
+    //    Application.Top.Dispose ();
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Theory]
+    //[InlineData (0, 0, false)]
+    //[InlineData (5, 5, true)]
+    //[InlineData (10, 10, false)]
+    //[InlineData (5, 10, false)]
+    //[InlineData (9, 9, false)]
+    //public void Popover_MouseClick_Outside_Hides (int mouseX, int mouseY, bool expectedVisible)
+    //{
+    //    // Arrange
+    //    Application.Top = new Toplevel ()
+    //    {
+    //        Id = "top",
+    //        Height = 10,
+    //        Width = 10,
+    //    };
+    //    var popover = new View ()
+    //    {
+    //        Id = "popover",
+    //        X = 5,
+    //        Y = 5,
+    //        Width = 1,
+    //        Height = 1,
+    //        Visible = false,
+    //        CanFocus = true
+    //    };
+
+    //    Application.Popover = popover;
+    //    popover.Visible = true;
+    //    Assert.True (popover.Visible);
+    //    Assert.True (popover.HasFocus);
+
+    //    // Act
+    //    Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed, ScreenPosition = new (mouseX, mouseY) });
+
+    //    // Assert
+    //    Assert.Equal (expectedVisible, popover.Visible);
+
+    //    Application.Top.Dispose ();
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_SetAndGet_ReturnsCorrectValue ()
+    //{
+    //    // Arrange
+    //    var view = new View ();
+
+    //    // Act
+    //    Application.Popover = view;
+
+    //    // Assert
+    //    Assert.Equal (view, Application.Popover);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_SetToNull_HidesPreviousPopover ()
+    //{
+    //    // Arrange
+    //    var view = new View { Visible = true };
+    //    Application.Popover = view;
+
+    //    // Act
+    //    Application.Popover = null;
+
+    //    // Assert
+    //    Assert.False (view.Visible);
+    //    Assert.Null (Application.Popover);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_SetNewPopover_HidesPreviousPopover ()
+    //{
+    //    // Arrange
+    //    var oldView = new View { Visible = true };
+    //    var newView = new View ();
+    //    Application.Popover = oldView;
+
+    //    // Act
+    //    Application.Popover = newView;
+
+    //    // Assert
+    //    Assert.False (oldView.Visible);
+    //    Assert.Equal (newView, Application.Popover);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+
+    //[Fact]
+    //public void Popover_SetNewPopover_InitializesAndSetsProperties ()
+    //{
+    //    // Arrange
+    //    var view = new View ();
+
+    //    // Act
+    //    Application.Popover = view;
+
+    //    // Assert
+    //    Assert.True (view.IsInitialized);
+    //    Assert.True (view.Arrangement.HasFlag (ViewArrangement.Overlapped));
+    //    Assert.Equal (Application.Top?.ColorScheme, view.ColorScheme);
+
+    //    Application.ResetState (ignoreDisposed: true);
+    //}
+}

+ 2 - 1
Tests/UnitTests/Application/ApplicationTests.cs

@@ -331,7 +331,8 @@ public class ApplicationTests
             Assert.Empty (Application._cachedViewsUnderMouse);
             Assert.Empty (Application._cachedViewsUnderMouse);
 
 
             // Mouse
             // Mouse
-            Assert.Null (Application._lastMousePosition);
+            // Do not reset _lastMousePosition
+            //Assert.Null (Application._lastMousePosition);
 
 
             // Navigation
             // Navigation
             Assert.Null (Application.Navigation);
             Assert.Null (Application.Navigation);

+ 1 - 1
Tests/UnitTests/Configuration/ConfigurationMangerTests.cs

@@ -235,7 +235,7 @@ public class ConfigurationManagerTests
     public void Load_Loads_Custom_Json ()
     public void Load_Loads_Custom_Json ()
     {
     {
         // arrange
         // arrange
-        Locations = ConfigLocations.All;
+        Locations = ConfigLocations.Runtime | ConfigLocations.Default;
         Reset ();
         Reset ();
         ThrowOnJsonErrors = true;
         ThrowOnJsonErrors = true;
 
 

+ 4 - 1
Tests/UnitTests/Resources/ResourceManagerTests.cs

@@ -63,7 +63,10 @@ public class ResourceManagerTests
     }
     }
 
 
     [Fact]
     [Fact]
-    public void GetString_Does_Not_Overflows_If_Key_Does_Not_Exist () { Assert.Null (GlobalResources.GetString (NO_EXISTENT_KEY, CultureInfo.CurrentCulture)); }
+    public void GetString_Does_Not_Overflows_If_Key_Does_Not_Exist ()
+    {
+        Assert.Null (GlobalResources.GetString (NO_EXISTENT_KEY, CultureInfo.CurrentCulture));
+    }
 
 
     [Fact]
     [Fact]
     public void GetString_FallBack_To_Default_For_No_Existent_Culture_File ()
     public void GetString_FallBack_To_Default_For_No_Existent_Culture_File ()

+ 53 - 0
Tests/UnitTests/View/Mouse/GetViewsUnderMouseTests.cs

@@ -661,4 +661,57 @@ public class GetViewsUnderMouseTests
         Application.Top.Dispose ();
         Application.Top.Dispose ();
         Application.ResetState (true);
         Application.ResetState (true);
     }
     }
+
+    [Theory]
+    [InlineData (0, 0, new [] { "top" })]
+    [InlineData (9, 9, new [] { "top" })]
+    [InlineData (10, 10, new string [] { })]
+    [InlineData (-1, -1, new string [] { })]
+    [InlineData (1, 1, new [] { "top", "view" })]
+    [InlineData (1, 2, new [] { "top", "view" })]
+    [InlineData (2, 1, new [] { "top", "view" })]
+    [InlineData (2, 2, new [] { "top", "view", "popover" })]
+    [InlineData (3, 3, new [] { "top" })] // clipped
+    [InlineData (2, 3, new [] { "top" })] // clipped
+    public void GetViewsUnderMouse_Popover (int mouseX, int mouseY, string [] viewIdStrings)
+    {
+        // Arrange
+        Application.Top = new ()
+        {
+            Frame = new (0, 0, 10, 10),
+            Id = "top"
+        };
+
+        var view = new View
+        {
+            Id = "view",
+            X = 1,
+            Y = 1,
+            Width = 2,
+            Height = 2,
+            Arrangement = ViewArrangement.Overlapped
+        }; // at 1,1 to 3,2 (screen)
+
+        var popOver = new View
+        {
+            Id = "popover",
+            X = 1,
+            Y = 1,
+            Width = 2,
+            Height = 2,
+            Arrangement = ViewArrangement.Overlapped
+        }; // at 2,2 to 4,3 (screen)
+
+        view.Add (popOver);
+        Application.Top.Add (view);
+
+        List<View?> found = View.GetViewsUnderMouse (new (mouseX, mouseY));
+
+        string [] foundIds = found.Select (v => v!.Id).ToArray ();
+
+        Assert.Equal (viewIdStrings, foundIds);
+
+        Application.Top.Dispose ();
+        Application.ResetState (true);
+    }
 }
 }

+ 48 - 48
Tests/UnitTests/Views/ContextMenuTests.cs

@@ -5,7 +5,7 @@ namespace Terminal.Gui.ViewsTests;
 
 
 public class ContextMenuTests (ITestOutputHelper output)
 public class ContextMenuTests (ITestOutputHelper output)
 {
 {
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void ContextMenu_Constructors ()
     public void ContextMenu_Constructors ()
     {
     {
@@ -60,7 +60,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void ContextMenu_Is_Closed_If_Another_MenuBar_Is_Open_Or_Vice_Versa ()
     public void ContextMenu_Is_Closed_If_Another_MenuBar_Is_Open_Or_Vice_Versa ()
     {
     {
@@ -316,7 +316,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         dialog.Dispose ();
         dialog.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void ForceMinimumPosToZero_True_False ()
     public void ForceMinimumPosToZero_True_False ()
     {
     {
@@ -366,7 +366,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Hide_Is_Invoke_At_Container_Closing ()
     public void Hide_Is_Invoke_At_Container_Closing ()
     {
     {
@@ -395,25 +395,25 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
-    [AutoInitShutdown]
-    public void Key_Open_And_Close_The_ContextMenu ()
-    {
-        var tf = new TextField ();
-        var top = new Toplevel ();
-        top.Add (tf);
-        Application.Begin (top);
-
-        Assert.True (Application.RaiseKeyDownEvent (ContextMenu.DefaultKey));
-        Assert.True (tf.ContextMenu.MenuBar!.IsMenuOpen);
-        Assert.True (Application.RaiseKeyDownEvent (ContextMenu.DefaultKey));
-
-        // The last context menu bar opened is always preserved
-        Assert.NotNull (tf.ContextMenu.MenuBar);
-        top.Dispose ();
-    }
-
-    [Fact]
+    //[Fact (Skip = "Redo for CMv2")]
+    //[AutoInitShutdown]
+    //public void Key_Open_And_Close_The_ContextMenu ()
+    //{
+    //    var tf = new TextField ();
+    //    var top = new Toplevel ();
+    //    top.Add (tf);
+    //    Application.Begin (top);
+
+    //    Assert.True (Application.RaiseKeyDownEvent (ContextMenu.DefaultKey));
+    //    Assert.True (tf.ContextMenu.MenuBar!.IsMenuOpen);
+    //    Assert.True (Application.RaiseKeyDownEvent (ContextMenu.DefaultKey));
+
+    //    // The last context menu bar opened is always preserved
+    //    Assert.False (tf.ContextMenu.Visible);
+    //    top.Dispose ();
+    //}
+
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void KeyChanged_Event ()
     public void KeyChanged_Event ()
     {
     {
@@ -427,7 +427,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Assert.Equal (ContextMenu.DefaultKey, oldKey);
         Assert.Equal (ContextMenu.DefaultKey, oldKey);
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void MenuItens_Changing ()
     public void MenuItens_Changing ()
     {
     {
@@ -479,7 +479,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Menus_And_SubMenus_Always_Try_To_Be_On_Screen ()
     public void Menus_And_SubMenus_Always_Try_To_Be_On_Screen ()
     {
     {
@@ -747,7 +747,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void MouseFlags_Changing ()
     public void MouseFlags_Changing ()
     {
     {
@@ -778,7 +778,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     public void MouseFlagsChanged_Event ()
     public void MouseFlagsChanged_Event ()
     {
     {
         var oldMouseFlags = new MouseFlags ();
         var oldMouseFlags = new MouseFlags ();
@@ -791,7 +791,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Assert.Equal (MouseFlags.Button3Clicked, oldMouseFlags);
         Assert.Equal (MouseFlags.Button3Clicked, oldMouseFlags);
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Position_Changing ()
     public void Position_Changing ()
     {
     {
@@ -836,7 +836,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void RequestStop_While_ContextMenu_Is_Open_Does_Not_Throws ()
     public void RequestStop_While_ContextMenu_Is_Open_Does_Not_Throws ()
     {
     {
@@ -921,7 +921,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Show_Display_At_Zero_If_The_Toplevel_Height_Is_Less_Than_The_Menu_Height ()
     public void Show_Display_At_Zero_If_The_Toplevel_Height_Is_Less_Than_The_Menu_Height ()
     {
     {
@@ -959,7 +959,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Show_Display_At_Zero_If_The_Toplevel_Width_Is_Less_Than_The_Menu_Width ()
     public void Show_Display_At_Zero_If_The_Toplevel_Width_Is_Less_Than_The_Menu_Width ()
     {
     {
@@ -998,7 +998,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Show_Display_Below_The_Bottom_Host_If_Has_Enough_Space ()
     public void Show_Display_Below_The_Bottom_Host_If_Has_Enough_Space ()
     {
     {
@@ -1073,7 +1073,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Show_Ensures_Display_Inside_The_Container_But_Preserves_Position ()
     public void Show_Ensures_Display_Inside_The_Container_But_Preserves_Position ()
     {
     {
@@ -1111,7 +1111,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Show_Ensures_Display_Inside_The_Container_Without_Overlap_The_Host ()
     public void Show_Ensures_Display_Inside_The_Container_Without_Overlap_The_Host ()
     {
     {
@@ -1162,7 +1162,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Show_Hide_IsShow ()
     public void Show_Hide_IsShow ()
     {
     {
@@ -1201,7 +1201,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void UseSubMenusSingleFrame_True_By_Mouse ()
     public void UseSubMenusSingleFrame_True_By_Mouse ()
     {
     {
@@ -1288,7 +1288,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void UseSubMenusSingleFrame_False_By_Mouse ()
     public void UseSubMenusSingleFrame_False_By_Mouse ()
     {
     {
@@ -1404,7 +1404,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Handling_TextField_With_Opened_ContextMenu_By_Mouse_HasFocus ()
     public void Handling_TextField_With_Opened_ContextMenu_By_Mouse_HasFocus ()
     {
     {
@@ -1424,7 +1424,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Assert.False (tf1.HasFocus);
         Assert.False (tf1.HasFocus);
         Assert.False (tf2.HasFocus);
         Assert.False (tf2.HasFocus);
         Assert.Equal (6, win.SubViews.Count);
         Assert.Equal (6, win.SubViews.Count);
-        Assert.True (tf2.ContextMenu.MenuBar.IsMenuOpen);
+        //Assert.True (tf2.ContextMenu.IsMenuOpen);
         Assert.True (win.Focused is Menu);
         Assert.True (win.Focused is Menu);
         Assert.True (Application.MouseGrabView is Menu);
         Assert.True (Application.MouseGrabView is Menu);
         Assert.Equal (tf2, Application._cachedViewsUnderMouse.LastOrDefault ());
         Assert.Equal (tf2, Application._cachedViewsUnderMouse.LastOrDefault ());
@@ -1436,7 +1436,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Assert.Equal (5, win.SubViews.Count);
         Assert.Equal (5, win.SubViews.Count);
 
 
         // The last context menu bar opened is always preserved
         // The last context menu bar opened is always preserved
-        Assert.NotNull (tf2.ContextMenu.MenuBar);
+        Assert.NotNull (tf2.ContextMenu);
         Assert.Equal (win.Focused, tf1);
         Assert.Equal (win.Focused, tf1);
         Assert.Null (Application.MouseGrabView);
         Assert.Null (Application.MouseGrabView);
         Assert.Equal (tf1, Application._cachedViewsUnderMouse.LastOrDefault ());
         Assert.Equal (tf1, Application._cachedViewsUnderMouse.LastOrDefault ());
@@ -1448,7 +1448,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Assert.Equal (5, win.SubViews.Count);
         Assert.Equal (5, win.SubViews.Count);
 
 
         // The last context menu bar opened is always preserved
         // The last context menu bar opened is always preserved
-        Assert.NotNull (tf2.ContextMenu.MenuBar);
+        Assert.NotNull (tf2.ContextMenu);
         Assert.Equal (win.Focused, tf2);
         Assert.Equal (win.Focused, tf2);
         Assert.Null (Application.MouseGrabView);
         Assert.Null (Application.MouseGrabView);
         Assert.Equal (tf2, Application._cachedViewsUnderMouse.LastOrDefault ());
         Assert.Equal (tf2, Application._cachedViewsUnderMouse.LastOrDefault ());
@@ -1457,7 +1457,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         win.Dispose ();
         win.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Empty_Menus_Items_Children_Does_Not_Open_The_Menu ()
     public void Empty_Menus_Items_Children_Does_Not_Open_The_Menu ()
     {
     {
@@ -1473,7 +1473,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void KeyBindings_Removed_On_Close_ContextMenu ()
     public void KeyBindings_Removed_On_Close_ContextMenu ()
     {
     {
@@ -1544,7 +1544,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         void Delete () { deleteFile = true; }
         void Delete () { deleteFile = true; }
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void KeyBindings_With_ContextMenu_And_MenuBar ()
     public void KeyBindings_With_ContextMenu_And_MenuBar ()
     {
     {
@@ -1623,7 +1623,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         void Rename () { renameFile = true; }
         void Rename () { renameFile = true; }
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void KeyBindings_With_Same_Shortcut_ContextMenu_And_MenuBar ()
     public void KeyBindings_With_Same_Shortcut_ContextMenu_And_MenuBar ()
     {
     {
@@ -1693,7 +1693,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         void NewContextMenu () { newContextMenu = true; }
         void NewContextMenu () { newContextMenu = true; }
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void HotKeys_Removed_On_Close_ContextMenu ()
     public void HotKeys_Removed_On_Close_ContextMenu ()
     {
     {
@@ -1779,7 +1779,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         void Delete () { deleteFile = true; }
         void Delete () { deleteFile = true; }
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void HotKeys_With_ContextMenu_And_MenuBar ()
     public void HotKeys_With_ContextMenu_And_MenuBar ()
     {
     {
@@ -1911,7 +1911,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         void Rename () { renameFile = true; }
         void Rename () { renameFile = true; }
     }
     }
 
 
-    [Fact]
+    [Fact (Skip = "Redo for CMv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     public void Opened_MenuBar_Is_Closed_When_Another_MenuBar_Is_Opening_Also_By_HotKey ()
     public void Opened_MenuBar_Is_Closed_When_Another_MenuBar_Is_Opening_Also_By_HotKey ()
     {
     {

+ 1 - 1
Tests/UnitTests/Views/TextFieldTests.cs

@@ -195,7 +195,7 @@ public class TextFieldTests (ITestOutputHelper output)
         Application.Top.Dispose ();
         Application.Top.Dispose ();
     }
     }
 
 
-    [Theory]
+    [Theory (Skip = "Broke with ContextMenuv2")]
     [AutoInitShutdown]
     [AutoInitShutdown]
     [InlineData ("blah")]
     [InlineData ("blah")]
     [InlineData (" ")]
     [InlineData (" ")]

+ 1 - 1
Tests/UnitTests/Views/TextViewTests.cs

@@ -5534,7 +5534,7 @@ This is the second line.
         Assert.False (tv.NewKeyDownEvent (Application.PrevTabGroupKey));
         Assert.False (tv.NewKeyDownEvent (Application.PrevTabGroupKey));
 
 
         Assert.True (tv.NewKeyDownEvent (ContextMenu.DefaultKey));
         Assert.True (tv.NewKeyDownEvent (ContextMenu.DefaultKey));
-        Assert.True (tv.ContextMenu != null && tv.ContextMenu.MenuBar.Visible);
+        Assert.True (tv.ContextMenu != null && tv.ContextMenu.Visible);
         top.Dispose ();
         top.Dispose ();
     }
     }
 
 

+ 163 - 0
Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs

@@ -0,0 +1,163 @@
+using Moq;
+
+namespace Terminal.Gui.ApplicationTests;
+
+public class ApplicationPopoverTests
+{
+    [Fact]
+    public void Register_AddsPopover ()
+    {
+        // Arrange
+        var popover = new Mock<IPopover> ().Object;
+        var popoverManager = new ApplicationPopover ();
+
+        // Act
+        popoverManager.Register (popover);
+
+        // Assert
+        Assert.Contains (popover, popoverManager.Popovers);
+    }
+
+    [Fact]
+    public void DeRegister_RemovesPopover ()
+    {
+        // Arrange
+        var popover = new Mock<IPopover> ().Object;
+        var popoverManager = new ApplicationPopover ();
+        popoverManager.Register (popover);
+
+        // Act
+        var result = popoverManager.DeRegister (popover);
+
+        // Assert
+        Assert.True (result);
+        Assert.DoesNotContain (popover, popoverManager.Popovers);
+    }
+
+    [Fact]
+    public void ShowPopover_SetsActivePopover ()
+    {
+        // Arrange
+        var popover = new Mock<IPopoverTestClass> ().Object;
+        var popoverManager = new ApplicationPopover ();
+
+        // Act
+        popoverManager.ShowPopover (popover);
+
+        // Assert
+        Assert.Equal (popover, popoverManager.GetActivePopover ());
+    }
+
+    [Fact]
+    public void HidePopover_ClearsActivePopover ()
+    {
+        // Arrange
+        var popover = new Mock<IPopover> ().Object;
+        var popoverManager = new ApplicationPopover ();
+        popoverManager.ShowPopover (popover);
+
+        // Act
+        popoverManager.HidePopover (popover);
+
+        // Assert
+        Assert.Null (popoverManager.GetActivePopover ());
+    }
+
+
+    [Fact]
+    public void DispatchKeyDown_ActivePopoverGetsKey ()
+    {
+        // Arrange
+        var popover = new IPopoverTestClass ();
+        var popoverManager = new ApplicationPopover ();
+        popoverManager.ShowPopover (popover);
+
+        // Act
+        popoverManager.DispatchKeyDown (Key.A);
+
+        // Assert
+        Assert.Contains (KeyCode.A, popover.HandledKeys);
+    }
+
+
+    [Fact]
+    public void DispatchKeyDown_ActivePopoverGetsHotKey ()
+    {
+        // Arrange
+        var popover = new IPopoverTestClass ();
+        var popoverManager = new ApplicationPopover ();
+        popoverManager.ShowPopover (popover);
+
+        // Act
+        popoverManager.DispatchKeyDown (Key.N.WithCtrl);
+
+        // Assert
+        Assert.Equal(1, popover.NewCommandInvokeCount);
+        Assert.Contains (Key.N.WithCtrl, popover.HandledKeys);
+    }
+
+
+    [Fact]
+    public void DispatchKeyDown_InactivePopoverGetsHotKey ()
+    {
+        // Arrange
+        var activePopover = new IPopoverTestClass () { Id = "activePopover" };
+        var inactivePopover = new IPopoverTestClass () { Id = "inactivePopover" }; ;
+        var popoverManager = new ApplicationPopover ();
+        popoverManager.ShowPopover (activePopover);
+        popoverManager.Register (inactivePopover);
+
+        // Act
+        popoverManager.DispatchKeyDown (Key.N.WithCtrl);
+
+        // Assert
+        Assert.Equal (1, activePopover.NewCommandInvokeCount);
+        Assert.Equal (1, inactivePopover.NewCommandInvokeCount);
+        Assert.Contains (Key.N.WithCtrl, activePopover.HandledKeys);
+        Assert.NotEmpty (inactivePopover.HandledKeys);
+    }
+
+    [Fact]
+    public void DispatchKeyDown_InactivePopoverDoesGetKey ()
+    {
+        // Arrange
+        var activePopover = new IPopoverTestClass ();
+        var inactivePopover = new IPopoverTestClass ();
+        var popoverManager = new ApplicationPopover ();
+        popoverManager.ShowPopover (activePopover);
+        popoverManager.Register (inactivePopover);
+
+        // Act
+        popoverManager.DispatchKeyDown (Key.A);
+
+        // Assert
+        Assert.Contains (Key.A, activePopover.HandledKeys);
+        Assert.NotEmpty (inactivePopover.HandledKeys);
+    }
+    
+    public class IPopoverTestClass : View, IPopover
+    {
+        public List<Key> HandledKeys { get; } = new List<Key> ();
+        public int NewCommandInvokeCount { get; private set; }
+
+        public IPopoverTestClass ()
+        {
+            CanFocus = true;
+            AddCommand(Command.New, NewCommandHandler );
+            HotKeyBindings.Add (Key.N.WithCtrl, Command.New);
+
+            bool? NewCommandHandler (ICommandContext ctx)
+            {
+                NewCommandInvokeCount++;
+
+                return false;
+            }
+        }
+
+        protected override bool OnKeyDown (Key key)
+        {
+            HandledKeys.Add (key);
+            return false;
+        }
+    }
+}

+ 20 - 0
Tests/UnitTestsParallelizable/Drawing/DrawContextTests.cs

@@ -0,0 +1,20 @@
+namespace Terminal.Gui.DrawingTests;
+
+public class DrawContextTests
+{
+    [Fact (Skip = "Region Union is broken")]
+    public void AddDrawnRectangle_Unions ()
+    {
+        DrawContext drawContext = new DrawContext ();
+
+        drawContext.AddDrawnRectangle (new (0, 0, 1, 1));
+        drawContext.AddDrawnRectangle (new (1, 0, 1, 1));
+
+        Assert.Equal (new Rectangle (0, 0, 2, 1), drawContext.GetDrawnRegion ().GetBounds ());
+        Assert.Equal (2, drawContext.GetDrawnRegion ().GetRectangles ().Length);
+
+        drawContext.AddDrawnRectangle (new (0, 0, 4, 1));
+        Assert.Equal (new Rectangle (0, 1, 4, 1), drawContext.GetDrawnRegion ().GetBounds ());
+        Assert.Single (drawContext.GetDrawnRegion ().GetRectangles ());
+    }
+}

+ 40 - 0
Tests/UnitTestsParallelizable/Drawing/Region/RegionTests.cs

@@ -783,6 +783,46 @@ public class RegionTests
         Assert.True (region1.Contains (40, 40));
         Assert.True (region1.Contains (40, 40));
     }
     }
 
 
+    [Fact (Skip = "Union is broken")]
+    public void Union_Third_Rect_Covering_Two_Disjoint_Merges ()
+    {
+        var origRegion = new Region ();
+
+        var region1 = new Region (new (0, 0, 1, 1));
+        var region2 = new Region (new (1, 0, 1, 1));
+
+        origRegion.Union(region1);
+        origRegion.Union(region2);
+
+        Assert.Equal (new Rectangle (0, 0, 2, 1), origRegion.GetBounds ());
+        Assert.Equal (2, origRegion.GetRectangles ().Length);
+
+        origRegion.Union(new Region(new (0, 0, 4, 1)));
+
+        Assert.Equal (new Rectangle (0, 1, 4, 1), origRegion.GetBounds ());
+        Assert.Single (origRegion.GetRectangles ());
+    }
+
+    [Fact (Skip = "MinimalUnion is broken")]
+    public void MinimalUnion_Third_Rect_Covering_Two_Disjoint_Merges ()
+    {
+        var origRegion = new Region ();
+
+        var region1 = new Region (new (0, 0, 1, 1));
+        var region2 = new Region (new (1, 0, 1, 1));
+
+        origRegion.Union (region1);
+        origRegion.Union (region2);
+
+        Assert.Equal (new Rectangle (0, 0, 2, 1), origRegion.GetBounds ());
+        Assert.Equal (2, origRegion.GetRectangles ().Length);
+
+        origRegion.MinimalUnion (new Region (new (0, 0, 4, 1)));
+
+        Assert.Equal (new Rectangle (0, 1, 4, 1), origRegion.GetBounds ());
+        Assert.Single (origRegion.GetRectangles ());
+    }
+
     /// <summary>
     /// <summary>
     ///     Proves MergeRegion does not overly combine regions.
     ///     Proves MergeRegion does not overly combine regions.
     /// </summary>
     /// </summary>

+ 62 - 1
Tests/UnitTestsParallelizable/View/ViewCommandTests.cs

@@ -226,10 +226,52 @@ public class ViewCommandTests
 
 
     #endregion OnHotKey/HotKey tests
     #endregion OnHotKey/HotKey tests
 
 
+    #region InvokeCommand Tests
+
+
+    [Fact]
+    public void InvokeCommand_NotBound_Invokes_CommandNotBound ()
+    {
+        ViewEventTester view = new ();
+
+        view.InvokeCommand (Command.NotBound);
+
+        Assert.False (view.HasFocus);
+        Assert.Equal (1, view.OnCommandNotBoundCount);
+        Assert.Equal (1, view.CommandNotBoundCount);
+    }
+
+    [Fact]
+    public void InvokeCommand_Command_Not_Bound_Invokes_CommandNotBound ()
+    {
+        ViewEventTester view = new ();
+
+        view.InvokeCommand (Command.New);
+
+        Assert.False (view.HasFocus);
+        Assert.Equal (1, view.OnCommandNotBoundCount);
+        Assert.Equal (1, view.CommandNotBoundCount);
+    }
+
+    [Fact]
+    public void InvokeCommand_Command_Bound_Does_Not_Invoke_CommandNotBound ()
+    {
+        ViewEventTester view = new ();
+
+        view.InvokeCommand (Command.Accept);
+
+        Assert.False (view.HasFocus);
+        Assert.Equal (0, view.OnCommandNotBoundCount);
+        Assert.Equal (0, view.CommandNotBoundCount);
+    }
+
+    #endregion
+
     public class ViewEventTester : View
     public class ViewEventTester : View
     {
     {
         public ViewEventTester ()
         public ViewEventTester ()
         {
         {
+            Id = "viewEventTester";
             CanFocus = true;
             CanFocus = true;
 
 
             Accepting += (s, a) =>
             Accepting += (s, a) =>
@@ -249,6 +291,12 @@ public class ViewCommandTests
                              a.Cancel = HandleSelecting;
                              a.Cancel = HandleSelecting;
                              SelectingCount++;
                              SelectingCount++;
                          };
                          };
+
+            CommandNotBound += (s, a) =>
+                               {
+                                   a.Cancel = HandleCommandNotBound;
+                                   CommandNotBoundCount++;
+                               };
         }
         }
 
 
         public int OnAcceptedCount { get; set; }
         public int OnAcceptedCount { get; set; }
@@ -282,6 +330,8 @@ public class ViewCommandTests
         public int OnSelectingCount { get; set; }
         public int OnSelectingCount { get; set; }
         public int SelectingCount { get; set; }
         public int SelectingCount { get; set; }
         public bool HandleOnSelecting { get; set; }
         public bool HandleOnSelecting { get; set; }
+        public bool HandleSelecting { get; set; }
+
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected override bool OnSelecting (CommandEventArgs args)
         protected override bool OnSelecting (CommandEventArgs args)
@@ -291,6 +341,17 @@ public class ViewCommandTests
             return HandleOnSelecting;
             return HandleOnSelecting;
         }
         }
 
 
-        public bool HandleSelecting { get; set; }
+        public int OnCommandNotBoundCount { get; set; }
+        public int CommandNotBoundCount { get; set; }
+
+        public bool HandleOnCommandNotBound { get; set; }
+
+        public bool HandleCommandNotBound { get; set; }
+
+        protected override bool OnCommandNotBound (CommandEventArgs args)
+        {
+            OnCommandNotBoundCount++;
+            return HandleOnCommandNotBound;
+        }
     }
     }
 }
 }

+ 28 - 0
UICatalog/Scenarios/Arrangement.cs

@@ -198,6 +198,9 @@ public class Arrangement : Scenario
         testFrame.Add (movableSizeableWithProgress);
         testFrame.Add (movableSizeableWithProgress);
         testFrame.Add (transparentView);
         testFrame.Add (transparentView);
 
 
+
+        testFrame.Add (new TransparentView ());
+
         adornmentsEditor.AutoSelectSuperView = testFrame;
         adornmentsEditor.AutoSelectSuperView = testFrame;
         arrangementEditor.AutoSelectSuperView = testFrame;
         arrangementEditor.AutoSelectSuperView = testFrame;
 
 
@@ -312,6 +315,31 @@ public class Arrangement : Scenario
 
 
         return keys;
         return keys;
     }
     }
+
+    public class TransparentView : FrameView
+    {
+        public TransparentView()
+        {
+            Title = "Transparent";
+            Text = "Text";
+            X = 0;
+            Y = 0;
+            Width = 30;
+            Height = 10;
+            Arrangement = ViewArrangement.Overlapped | ViewArrangement.Resizable | ViewArrangement.Movable;
+            ViewportSettings |= Terminal.Gui.ViewportSettings.Transparent;
+
+            Padding!.Thickness = new Thickness (1);
+
+            Add (
+                 new Button ()
+                 {
+                     Title = "_Hi",
+                     X = Pos.Center (),
+                     Y = Pos.Center ()
+                 });
+        }
+    }
 }
 }
 
 
 public class TransparentView : FrameView
 public class TransparentView : FrameView

+ 9 - 9
UICatalog/Scenarios/Bars.cs

@@ -81,15 +81,15 @@ public class Bars : Scenario
         };
         };
         menuBarLikeExamples.Add (label);
         menuBarLikeExamples.Add (label);
 
 
-        bar = new MenuBarv2
-        {
-            Id = "menuBar",
-            X = Pos.Right (label),
-            Y = Pos.Top (label),
-        };
-
-        ConfigMenuBar (bar);
-        menuBarLikeExamples.Add (bar);
+        //bar = new MenuBarv2
+        //{
+        //    Id = "menuBar",
+        //    X = Pos.Right (label),
+        //    Y = Pos.Top (label),
+        //};
+
+        //ConfigMenuBar (bar);
+        //menuBarLikeExamples.Add (bar);
 
 
         FrameView menuLikeExamples = new ()
         FrameView menuLikeExamples = new ()
         {
         {

+ 1 - 1
UICatalog/Scenarios/ColorPicker.cs

@@ -250,7 +250,7 @@ public class ColorPickers : Scenario
     /// <summary>Update a color label from his ColorPicker.</summary>
     /// <summary>Update a color label from his ColorPicker.</summary>
     private void UpdateColorLabel (Label label, Color color)
     private void UpdateColorLabel (Label label, Color color)
     {
     {
-        label.ClearViewport ();
+        label.ClearViewport (null);
 
 
         label.Text =
         label.Text =
             $"{color} ({(int)color}) #{color.R:X2}{color.G:X2}{color.B:X2}";
             $"{color} ({(int)color}) #{color.R:X2}{color.G:X2}{color.B:X2}";

+ 152 - 208
UICatalog/Scenarios/ContextMenus.cs

@@ -1,6 +1,5 @@
-using System.Collections.Generic;
-using System.Globalization;
-using System.Threading;
+using System.Globalization;
+using JetBrains.Annotations;
 using Terminal.Gui;
 using Terminal.Gui;
 
 
 namespace UICatalog.Scenarios;
 namespace UICatalog.Scenarios;
@@ -9,38 +8,37 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Menus")]
 [ScenarioCategory ("Menus")]
 public class ContextMenus : Scenario
 public class ContextMenus : Scenario
 {
 {
-    private List<CultureInfo> _cultureInfos = null;
-    private ContextMenu _contextMenu = new ();
-    private bool _forceMinimumPosToZero = true;
-    private MenuItem _miForceMinimumPosToZero;
-    private MenuItem _miUseSubMenusSingleFrame;
+    [CanBeNull]
+    private ContextMenuv2 _winContextMenu;
     private TextField _tfTopLeft, _tfTopRight, _tfMiddle, _tfBottomLeft, _tfBottomRight;
     private TextField _tfTopLeft, _tfTopRight, _tfMiddle, _tfBottomLeft, _tfBottomRight;
-    private bool _useSubMenusSingleFrame;
+    private readonly List<CultureInfo> _cultureInfos = Application.SupportedCultures;
+    private readonly Key _winContextMenuKey = Key.Space.WithCtrl;
 
 
     public override void Main ()
     public override void Main ()
     {
     {
         // Init
         // Init
         Application.Init ();
         Application.Init ();
 
 
-        _cultureInfos = Application.SupportedCultures;
         // Setup - Create a top-level application window and configure it.
         // Setup - Create a top-level application window and configure it.
         Window appWindow = new ()
         Window appWindow = new ()
         {
         {
             Title = GetQuitKeyAndName (),
             Title = GetQuitKeyAndName (),
-            Arrangement = ViewArrangement.Fixed
+            Arrangement = ViewArrangement.Fixed,
+            ColorScheme = Colors.ColorSchemes ["Toplevel"]
         };
         };
 
 
         var text = "Context Menu";
         var text = "Context Menu";
         var width = 20;
         var width = 20;
-        var winContextMenuKey = (KeyCode)Key.Space.WithCtrl;
+
+        CreateWinContextMenu ();
 
 
         var label = new Label
         var label = new Label
         {
         {
-            X = Pos.Center (), Y = 1, Text = $"Press '{winContextMenuKey}' to open the Window context menu."
+            X = Pos.Center (), Y = 1, Text = $"Press '{_winContextMenuKey}' to open the Window context menu."
         };
         };
         appWindow.Add (label);
         appWindow.Add (label);
 
 
-        label = new()
+        label = new ()
         {
         {
             X = Pos.Center (),
             X = Pos.Center (),
             Y = Pos.Bottom (label),
             Y = Pos.Bottom (label),
@@ -48,252 +46,198 @@ public class ContextMenus : Scenario
         };
         };
         appWindow.Add (label);
         appWindow.Add (label);
 
 
-        _tfTopLeft = new() { Width = width, Text = text };
+        _tfTopLeft = new () { Id = "_tfTopLeft", Width = width, Text = text };
         appWindow.Add (_tfTopLeft);
         appWindow.Add (_tfTopLeft);
 
 
-        _tfTopRight = new() { X = Pos.AnchorEnd (width), Width = width, Text = text };
+        _tfTopRight = new () { Id = "_tfTopRight", X = Pos.AnchorEnd (width), Width = width, Text = text };
         appWindow.Add (_tfTopRight);
         appWindow.Add (_tfTopRight);
 
 
-        _tfMiddle = new() { X = Pos.Center (), Y = Pos.Center (), Width = width, Text = text };
+        _tfMiddle = new () { Id = "_tfMiddle", X = Pos.Center (), Y = Pos.Center (), Width = width, Text = text };
         appWindow.Add (_tfMiddle);
         appWindow.Add (_tfMiddle);
 
 
-        _tfBottomLeft = new() { Y = Pos.AnchorEnd (1), Width = width, Text = text };
+        _tfBottomLeft = new () { Id = "_tfBottomLeft", Y = Pos.AnchorEnd (1), Width = width, Text = text };
         appWindow.Add (_tfBottomLeft);
         appWindow.Add (_tfBottomLeft);
 
 
-        _tfBottomRight = new() { X = Pos.AnchorEnd (width), Y = Pos.AnchorEnd (1), Width = width, Text = text };
+        _tfBottomRight = new () { Id = "_tfBottomRight", X = Pos.AnchorEnd (width), Y = Pos.AnchorEnd (1), Width = width, Text = text };
         appWindow.Add (_tfBottomRight);
         appWindow.Add (_tfBottomRight);
 
 
-        Point mousePos = default;
-
-        appWindow.KeyDown += (s, e) =>
-                             {
-                                 if (e.KeyCode == winContextMenuKey)
-                                 {
-                                     ShowContextMenu (mousePos.X, mousePos.Y);
-                                     e.Handled = true;
-                                 }
-                             };
+        appWindow.KeyDown += OnAppWindowOnKeyDown;
+        appWindow.MouseClick += OnAppWindowOnMouseClick;
 
 
-        appWindow.MouseClick += (s, e) =>
-                                {
-                                    if (e.Flags == _contextMenu.MouseFlags)
-                                    {
-                                        ShowContextMenu (e.Position.X, e.Position.Y);
-                                        e.Handled = true;
-                                    }
-                                };
+        CultureInfo originalCulture = Thread.CurrentThread.CurrentUICulture;
+        appWindow.Closed += (s, e) => { Thread.CurrentThread.CurrentUICulture = originalCulture; };
 
 
-        Application.MouseEvent += ApplicationMouseEvent;
+        // Run - Start the application.
+        Application.Run (appWindow);
+        appWindow.Dispose ();
+        appWindow.KeyDown -= OnAppWindowOnKeyDown;
+        appWindow.MouseClick -= OnAppWindowOnMouseClick;
+        _winContextMenu?.Dispose ();
 
 
-        void ApplicationMouseEvent (object sender, MouseEventArgs a) { mousePos = a.Position; }
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
 
 
-        appWindow.WantMousePositionReports = true;
+        return;
 
 
-        appWindow.Closed += (s, e) =>
-                            {
-                                Thread.CurrentThread.CurrentUICulture = new ("en-US");
-                                Application.MouseEvent -= ApplicationMouseEvent;
-                            };
+        void OnAppWindowOnMouseClick (object s, MouseEventArgs e)
+        {
+            if (e.Flags == MouseFlags.Button3Clicked)
+            {
+                // ReSharper disable once AccessToDisposedClosure
+                _winContextMenu?.MakeVisible (e.ScreenPosition);
+                e.Handled = true;
+            }
+        }
 
 
-        var top = new Toplevel ();
-        top.Add (appWindow);
+        void OnAppWindowOnKeyDown (object s, Key e)
+        {
+            if (e == _winContextMenuKey)
+            {
+                // ReSharper disable once AccessToDisposedClosure
+                _winContextMenu?.MakeVisible ();
+                e.Handled = true;
+            }
+        }
+    }
 
 
-        // Run - Start the application.
-        Application.Run (top);
-        top.Dispose ();
+    private void CreateWinContextMenu ()
+    {
+        if (_winContextMenu is { })
+        {
+            _winContextMenu.Dispose ();
+            _winContextMenu = null;
+        }
 
 
-        // Shutdown - Calling Application.Shutdown is required.
-        Application.Shutdown ();
+        _winContextMenu = new (
+                               [
+                                   new MenuItemv2
+                                   {
+                                       Title = "C_ultures",
+                                       SubMenu = GetSupportedCultureMenu (),
+                                   },
+                                   new Line (),
+                                   new MenuItemv2
+                                   {
+                                       Title = "_Configuration...",
+                                       HelpText = "Show configuration",
+                                       Action = () => MessageBox.Query (
+                                                                        50,
+                                                                        10,
+                                                                        "Configuration",
+                                                                        "This would be a configuration dialog",
+                                                                        "Ok"
+                                                                       )
+                                   },
+                                   new MenuItemv2
+                                   {
+                                       Title = "M_ore options",
+                                       SubMenu = new (
+                                                      [
+                                                          new MenuItemv2
+                                                          {
+                                                              Title = "_Setup...",
+                                                              HelpText = "Perform setup",
+                                                              Action = () => MessageBox
+                                                                           .Query (
+                                                                                   50,
+                                                                                   10,
+                                                                                   "Setup",
+                                                                                   "This would be a setup dialog",
+                                                                                   "Ok"
+                                                                                  ),
+                                                              Key = Key.T.WithCtrl
+                                                          },
+                                                          new MenuItemv2
+                                                          {
+                                                              Title = "_Maintenance...",
+                                                              HelpText = "Maintenance mode",
+                                                              Action = () => MessageBox
+                                                                           .Query (
+                                                                                   50,
+                                                                                   10,
+                                                                                   "Maintenance",
+                                                                                   "This would be a maintenance dialog",
+                                                                                   "Ok"
+                                                                                  )
+                                                          }
+                                                      ])
+                                   },
+                                   new Line (),
+                                   new MenuItemv2
+                                   {
+                                       Title = "_Quit",
+                                       Action = () => Application.RequestStop ()
+                                   }
+                               ])
+        {
+            Key = _winContextMenuKey
+        };
     }
     }
 
 
-    private MenuItem [] GetSupportedCultures ()
+    private Menuv2 GetSupportedCultureMenu ()
     {
     {
-        List<MenuItem> supportedCultures = new ();
+        List<MenuItemv2> supportedCultures = [];
         int index = -1;
         int index = -1;
 
 
-        if (_cultureInfos == null)
-        {
-            return supportedCultures.ToArray ();
-        }
-
         foreach (CultureInfo c in _cultureInfos)
         foreach (CultureInfo c in _cultureInfos)
         {
         {
-            var culture = new MenuItem { CheckType = MenuItemCheckStyle.Checked };
+            MenuItemv2 culture = new ();
+
+            culture.CommandView = new CheckBox { CanFocus = false, HighlightStyle = HighlightStyle.None };
 
 
             if (index == -1)
             if (index == -1)
             {
             {
+                // Create English because GetSupportedCutures doesn't include it
+                culture.Id = "_English";
                 culture.Title = "_English";
                 culture.Title = "_English";
-                culture.Help = "en-US";
-                culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == "en-US";
+                culture.HelpText = "en-US";
+
+                ((CheckBox)culture.CommandView).CheckedState =
+                    Thread.CurrentThread.CurrentUICulture.Name == "en-US" ? CheckState.Checked : CheckState.UnChecked;
                 CreateAction (supportedCultures, culture);
                 CreateAction (supportedCultures, culture);
                 supportedCultures.Add (culture);
                 supportedCultures.Add (culture);
+
                 index++;
                 index++;
-                culture = new() { CheckType = MenuItemCheckStyle.Checked };
+                culture = new ();
+                culture.CommandView = new CheckBox { CanFocus = false, HighlightStyle = HighlightStyle.None };
             }
             }
 
 
+            culture.Id = $"_{c.Parent.EnglishName}";
             culture.Title = $"_{c.Parent.EnglishName}";
             culture.Title = $"_{c.Parent.EnglishName}";
-            culture.Help = c.Name;
-            culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == c.Name;
+            culture.HelpText = c.Name;
+
+            ((CheckBox)culture.CommandView).CheckedState =
+                Thread.CurrentThread.CurrentUICulture.Name == culture.HelpText ? CheckState.Checked : CheckState.UnChecked;
             CreateAction (supportedCultures, culture);
             CreateAction (supportedCultures, culture);
             supportedCultures.Add (culture);
             supportedCultures.Add (culture);
         }
         }
 
 
-        return supportedCultures.ToArray ();
+        Menuv2 menu = new (supportedCultures.ToArray ());
+        menu.Border.LineStyle = LineStyle.None;
+        menu.Border.Thickness = new (0,0,0,0);
+
+       // menu.Padding.Thickness = new (1);
 
 
-        void CreateAction (List<MenuItem> supportedCultures, MenuItem culture)
+        return menu;
+
+        void CreateAction (List<MenuItemv2> cultures, MenuItemv2 culture)
         {
         {
             culture.Action += () =>
             culture.Action += () =>
                               {
                               {
-                                  Thread.CurrentThread.CurrentUICulture = new (culture.Help);
-                                  culture.Checked = true;
+                                  Thread.CurrentThread.CurrentUICulture = new (culture.HelpText);
 
 
-                                  foreach (MenuItem item in supportedCultures)
+                                  foreach (MenuItemv2 item in cultures)
                                   {
                                   {
-                                      item.Checked = item.Help == Thread.CurrentThread.CurrentUICulture.Name;
+                                      ((CheckBox)item.CommandView).CheckedState =
+                                          Thread.CurrentThread.CurrentUICulture.Name == item.HelpText ? CheckState.Checked : CheckState.UnChecked;
                                   }
                                   }
                               };
                               };
         }
         }
     }
     }
 
 
-    private void ShowContextMenu (int x, int y)
-    {
-        _contextMenu = new()
-        {
-            Position = new (x, y),
-            ForceMinimumPosToZero = _forceMinimumPosToZero,
-            UseSubMenusSingleFrame = _useSubMenusSingleFrame
-        };
-
-        MenuBarItem menuItems = new (
-                                     new []
-                                     {
-                                         new MenuBarItem (
-                                                          "_Languages",
-                                                          GetSupportedCultures ()
-                                                         ),
-                                         new (
-                                              "_Configuration",
-                                              "Show configuration",
-                                              () => MessageBox.Query (
-                                                                      50,
-                                                                      5,
-                                                                      "Info",
-                                                                      "This would open settings dialog",
-                                                                      "Ok"
-                                                                     )
-                                             ),
-                                         new MenuBarItem (
-                                                          "M_ore options",
-                                                          new MenuItem []
-                                                          {
-                                                              new (
-                                                                   "_Setup",
-                                                                   "Change settings",
-                                                                   () => MessageBox
-                                                                       .Query (
-                                                                               50,
-                                                                               5,
-                                                                               "Info",
-                                                                               "This would open setup dialog",
-                                                                               "Ok"
-                                                                              ),
-                                                                   shortcutKey: KeyCode.T
-                                                                                | KeyCode
-                                                                                    .CtrlMask
-                                                                  ),
-                                                              new (
-                                                                   "_Maintenance",
-                                                                   "Maintenance mode",
-                                                                   () => MessageBox
-                                                                       .Query (
-                                                                               50,
-                                                                               5,
-                                                                               "Info",
-                                                                               "This would open maintenance dialog",
-                                                                               "Ok"
-                                                                              )
-                                                                  )
-                                                          }
-                                                         ),
-                                         _miForceMinimumPosToZero =
-                                             new (
-                                                  "Fo_rceMinimumPosToZero",
-                                                  "",
-                                                  () =>
-                                                  {
-                                                      _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 ()
-                                             )
-                                     }
-                                    );
-        _tfTopLeft.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
-        _tfTopRight.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
-        _tfMiddle.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
-        _tfBottomLeft.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
-        _tfBottomRight.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
-
-        _contextMenu.Show (menuItems);
-    }
-
-
     public override List<Key> GetDemoKeyStrokes ()
     public override List<Key> GetDemoKeyStrokes ()
     {
     {
-        var keys = new List<Key> ();
+        List<Key> keys = new ();
 
 
         keys.Add (Key.F10.WithShift);
         keys.Add (Key.F10.WithShift);
         keys.Add (Key.Esc);
         keys.Add (Key.Esc);

+ 5 - 5
UICatalog/Scenarios/Editor.cs

@@ -225,12 +225,12 @@ public class Editor : Scenario
                                                          "",
                                                          "",
                                                          () =>
                                                          () =>
                                                          {
                                                          {
-                                                             _miForceMinimumPosToZero.Checked =
-                                                                 _forceMinimumPosToZero =
-                                                                     !_forceMinimumPosToZero;
+                                                             //_miForceMinimumPosToZero.Checked =
+                                                             //    _forceMinimumPosToZero =
+                                                             //        !_forceMinimumPosToZero;
 
 
-                                                             _textView.ContextMenu.ForceMinimumPosToZero =
-                                                                 _forceMinimumPosToZero;
+                                                             //_textView.ContextMenu.ForceMinimumPosToZero =
+                                                             //    _forceMinimumPosToZero;
                                                          }
                                                          }
                                                         )
                                                         )
                          {
                          {

+ 4 - 12
UICatalog/Scenarios/Generic.cs

@@ -18,19 +18,11 @@ public sealed class Generic : Scenario
             Title = GetQuitKeyAndName (),
             Title = GetQuitKeyAndName (),
         };
         };
 
 
-        FrameView frame = new ()
-        {
-            Height = Dim.Fill (),
-            Width = Dim.Fill (),
-            Title = "Frame"
-        };
-        appWindow.Add (frame);
-
         var button = new Shortcut ()
         var button = new Shortcut ()
         {
         {
-            Id = "button", 
-            X = Pos.Center (), 
-            Y = 1, 
+            Id = "button",
+            X = Pos.Center (),
+            Y = 1,
             Text = "_Press me!"
             Text = "_Press me!"
         };
         };
 
 
@@ -41,7 +33,7 @@ public sealed class Generic : Scenario
                                 MessageBox.ErrorQuery ("Error", "You pressed the button!", "_Ok");
                                 MessageBox.ErrorQuery ("Error", "You pressed the button!", "_Ok");
                             };
                             };
 
 
-        frame.Add (button);
+        appWindow.Add (button);
 
 
         // Run - Start the application.
         // Run - Start the application.
         Application.Run (appWindow);
         Application.Run (appWindow);

+ 556 - 0
UICatalog/Scenarios/MenusV2.cs

@@ -0,0 +1,556 @@
+#nullable enable
+
+using System.Collections.ObjectModel;
+using Microsoft.Extensions.Logging;
+using Serilog;
+using Serilog.Core;
+using Serilog.Events;
+using Terminal.Gui;
+using ILogger = Microsoft.Extensions.Logging.ILogger;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("MenusV2", "Illustrates MenuV2")]
+[ScenarioCategory ("Controls")]
+[ScenarioCategory ("Shortcuts")]
+public class MenusV2 : Scenario
+{
+    public override void Main ()
+    {
+        Logging.Logger = CreateLogger ();
+
+        Application.Init ();
+        Toplevel app = new ();
+        app.Title = GetQuitKeyAndName ();
+
+        ObservableCollection<string> eventSource = new ();
+
+        var eventLog = new ListView
+        {
+            Title = "Event Log",
+            X = Pos.AnchorEnd (),
+            Width = Dim.Auto (),
+            Height = Dim.Fill (), // Make room for some wide things
+            ColorScheme = Colors.ColorSchemes ["Toplevel"],
+            Source = new ListWrapper<string> (eventSource)
+        };
+        eventLog.Border!.Thickness = new (0, 1, 0, 0);
+
+        TargetView targetView = new ()
+        {
+            Id = "targetView",
+            Title = "Target View",
+
+            X = 5,
+            Y = 5,
+            Width = Dim.Fill (2)! - Dim.Width (eventLog),
+            Height = Dim.Fill (2),
+            BorderStyle = LineStyle.Dotted
+        };
+        app.Add (targetView);
+
+        targetView.CommandNotBound += (o, args) =>
+                                      {
+                                          if (args.Cancel)
+                                          {
+                                              return;
+                                          }
+
+                                          Logging.Trace ($"targetView CommandNotBound: {args?.Context?.Command}");
+                                          eventSource.Add ($"targetView CommandNotBound: {args?.Context?.Command}");
+                                          eventLog.MoveDown ();
+                                      };
+
+        targetView.Accepting += (o, args) =>
+                                {
+                                    if (args.Cancel)
+                                    {
+                                        return;
+                                    }
+
+                                    Logging.Trace ($"targetView Accepting: {args?.Context?.Source?.Title}");
+                                    eventSource.Add ($"targetView Accepting: {args?.Context?.Source?.Title}: ");
+                                    eventLog.MoveDown ();
+                                };
+
+        targetView.FilePopoverMenu!.Accepted += (o, args) =>
+                                                {
+                                                    if (args.Cancel)
+                                                    {
+                                                        return;
+                                                    }
+
+                                                    Logging.Trace ($"FilePopoverMenu Accepted: {args?.Context?.Source?.Text}");
+                                                    eventSource.Add ($"FilePopoverMenu Accepted: {args?.Context?.Source?.Text}: ");
+                                                    eventLog.MoveDown ();
+                                                };
+
+        app.Add (eventLog);
+
+        Application.Run (app);
+        app.Dispose ();
+        Application.Shutdown ();
+    }
+
+    public class TargetView : View
+    {
+        internal PopoverMenu? FilePopoverMenu { get; }
+
+        private CheckBox? _enableOverwriteCb;
+        private CheckBox? _autoSaveCb;
+        private CheckBox? _editModeCb;
+
+        private RadioGroup? _mutuallyExclusiveOptionsRg;
+
+        private ColorPicker? _menuBgColorCp;
+
+        public TargetView ()
+        {
+            CanFocus = true;
+            Text = "TargetView";
+            BorderStyle = LineStyle.Dashed;
+
+            AddCommand (
+                        Command.Context,
+                        ctx =>
+                        {
+                            FilePopoverMenu?.MakeVisible ();
+
+                            return true;
+                        });
+
+            KeyBindings.Add (PopoverMenu.DefaultKey, Command.Context);
+
+            MouseBindings.ReplaceCommands (PopoverMenu.MouseFlags, Command.Context);
+
+            AddCommand (
+                        Command.Cancel,
+                        ctx =>
+                        {
+                            if (Application.Popover?.GetActivePopover () as PopoverMenu is { Visible: true } visiblePopover)
+                            {
+                                visiblePopover.Visible = false;
+                            }
+
+                            return true;
+                        });
+
+            MouseBindings.ReplaceCommands (MouseFlags.Button1Clicked, Command.Cancel);
+
+            Label lastCommandLabel = new ()
+            {
+                Title = "_Last Command:",
+                X = 15,
+                Y = 10,
+            };
+
+            View lastCommandText = new ()
+            {
+                X = Pos.Right (lastCommandLabel) + 1,
+                Y = Pos.Top (lastCommandLabel),
+                Height = Dim.Auto (),
+                Width = Dim.Auto ()
+            };
+
+            Add (lastCommandLabel, lastCommandText);
+
+            AddCommand (Command.New, HandleCommand);
+            HotKeyBindings.Add (Key.F2, Command.New);
+
+            AddCommand (Command.Open, HandleCommand);
+            HotKeyBindings.Add (Key.F3, Command.Open);
+
+            AddCommand (Command.Save, HandleCommand);
+            HotKeyBindings.Add (Key.F4, Command.Save);
+
+            AddCommand (Command.SaveAs, HandleCommand);
+            HotKeyBindings.Add (Key.A.WithCtrl, Command.SaveAs);
+
+            HotKeyBindings.Add (Key.W.WithCtrl, Command.EnableOverwrite);
+
+            var fileMenu = new Menuv2
+            {
+                Id = "fileMenu"
+            };
+            ConfigureFileMenu (fileMenu);
+
+            var optionsSubMenu = new Menuv2
+            {
+                Id = "optionsSubMenu",
+                Visible = false
+            };
+            ConfigureOptionsSubMenu (optionsSubMenu);
+
+            var optionsSubMenuItem = new MenuItemv2 (this, Command.NotBound, "O_ptions", "File options", optionsSubMenu);
+            fileMenu.Add (optionsSubMenuItem);
+
+            var detailsSubMenu = new Menuv2
+            {
+                Id = "detailsSubMenu",
+                Visible = false
+            };
+            ConfigureDetialsSubMenu (detailsSubMenu);
+
+            var detailsSubMenuItem = new MenuItemv2 (this, Command.NotBound, "_Details", "File details", detailsSubMenu);
+            fileMenu.Add (detailsSubMenuItem);
+
+            var moreDetailsSubMenu = new Menuv2
+            {
+                Id = "moreDetailsSubMenu",
+                Visible = false
+            };
+            ConfigureMoreDetailsSubMenu (moreDetailsSubMenu);
+
+            var moreDetailsSubMenuItem = new MenuItemv2 (this, Command.NotBound, "_More Details", "More details", moreDetailsSubMenu);
+            detailsSubMenu.Add (moreDetailsSubMenuItem);
+
+            FilePopoverMenu = new (fileMenu)
+            {
+                Id = "FilePopoverMenu"
+            };
+
+            MenuBarItemv2 fileMenuRootItem = new ("_File", FilePopoverMenu);
+
+            AddCommand (Command.Cut, HandleCommand);
+            HotKeyBindings.Add (Key.X.WithCtrl, Command.Cut);
+
+            AddCommand (Command.Copy, HandleCommand);
+            HotKeyBindings.Add (Key.C.WithCtrl, Command.Copy);
+
+            AddCommand (Command.Paste, HandleCommand);
+            HotKeyBindings.Add (Key.V.WithCtrl, Command.Paste);
+
+            AddCommand (Command.SelectAll, HandleCommand);
+            HotKeyBindings.Add (Key.T.WithCtrl, Command.SelectAll);
+
+            Add (new MenuBarv2 (
+                                [
+                                    fileMenuRootItem,
+                                    new MenuBarItemv2 (
+                                                       "_Edit",
+                                                       [
+                                                           new MenuItemv2 (this, Command.Cut),
+                                                           new MenuItemv2 (this, Command.Copy),
+                                                           new MenuItemv2 (this, Command.Paste),
+                                                           new Line (),
+                                                           new MenuItemv2 (this, Command.SelectAll)
+                                                       ]
+                                                      ),
+                                    new MenuBarItemv2 (this, Command.NotBound, "_Help")
+                                    {
+                                        Key = Key.F1,
+                                        Action = () => { MessageBox.Query ("Help", "This is the help...", "_Ok"); }
+                                    }
+                                ]
+                               )
+                 );
+
+            Label lastAcceptedLabel = new ()
+            {
+                Title = "Last Accepted:",
+                X = Pos.Left (lastCommandLabel),
+                Y = Pos.Bottom (lastCommandLabel)
+            };
+
+            View lastAcceptedText = new ()
+            {
+                X = Pos.Right (lastAcceptedLabel) + 1,
+                Y = Pos.Top (lastAcceptedLabel),
+                Height = Dim.Auto (),
+                Width = Dim.Auto ()
+            };
+
+            Add (lastAcceptedLabel, lastAcceptedText);
+
+            CheckBox autoSaveStatusCb = new ()
+            {
+                Title = "AutoSave",
+                X = Pos.Left (lastAcceptedLabel),
+                Y = Pos.Bottom (lastAcceptedLabel)
+            };
+
+            autoSaveStatusCb.CheckedStateChanged += (sender, args) => { _autoSaveCb!.CheckedState = autoSaveStatusCb.CheckedState; };
+
+            Add (autoSaveStatusCb);
+
+            CheckBox enableOverwriteStatusCb = new ()
+            {
+                Title = "Enable Overwrite",
+                X = Pos.Left (autoSaveStatusCb),
+                Y = Pos.Bottom (autoSaveStatusCb)
+            };
+            enableOverwriteStatusCb.CheckedStateChanged += (sender, args) => { _enableOverwriteCb!.CheckedState = enableOverwriteStatusCb.CheckedState; };
+            base.Add (enableOverwriteStatusCb);
+
+            AddCommand (
+                        Command.EnableOverwrite,
+                        ctx =>
+                        {
+                            enableOverwriteStatusCb.CheckedState =
+                                enableOverwriteStatusCb.CheckedState == CheckState.UnChecked ? CheckState.Checked : CheckState.UnChecked;
+
+                            return HandleCommand (ctx);
+                        });
+
+            CheckBox editModeStatusCb = new ()
+            {
+                Title = "EditMode (App binding)",
+                X = Pos.Left (enableOverwriteStatusCb),
+                Y = Pos.Bottom (enableOverwriteStatusCb)
+            };
+            editModeStatusCb.CheckedStateChanged += (sender, args) => { _editModeCb!.CheckedState = editModeStatusCb.CheckedState; };
+            base.Add (editModeStatusCb);
+
+            AddCommand (Command.Edit, ctx =>
+                                      {
+                                          editModeStatusCb.CheckedState =
+                                              editModeStatusCb.CheckedState == CheckState.UnChecked ? CheckState.Checked : CheckState.UnChecked;
+
+                                          return HandleCommand (ctx);
+                                      });
+
+            Application.KeyBindings.Add (Key.F9, this, Command.Edit);
+
+
+            FilePopoverMenu!.Accepted += (o, args) =>
+                                         {
+                                             lastAcceptedText.Text = args?.Context?.Source?.Title!;
+
+                                             if (args?.Context?.Source is MenuItemv2 mi && mi.CommandView == _autoSaveCb)
+                                             {
+                                                 autoSaveStatusCb.CheckedState = _autoSaveCb.CheckedState;
+                                             }
+                                         };
+
+            FilePopoverMenu!.VisibleChanged += (sender, args) =>
+                                               {
+                                                   if (FilePopoverMenu!.Visible)
+                                                   {
+                                                       lastCommandText.Text = string.Empty;
+                                                   }
+                                               };
+
+            Add (
+                 new Button
+                 {
+                     Title = "_Button",
+                     X = Pos.Center (),
+                     Y = Pos.Center ()
+                 });
+
+            autoSaveStatusCb.SetFocus ();
+
+            return;
+
+            // Add the commands supported by this View
+            bool? HandleCommand (ICommandContext? ctx)
+            {
+                lastCommandText.Text = ctx?.Command!.ToString ()!;
+
+                return true;
+            }
+        }
+
+        private void ConfigureFileMenu (Menuv2 menu)
+        {
+            var newFile = new MenuItemv2
+            {
+                Command = Command.New,
+                TargetView = this
+            };
+
+            var openFile = new MenuItemv2
+            {
+                Command = Command.Open,
+                TargetView = this
+            };
+
+            var saveFile = new MenuItemv2
+            {
+                Command = Command.Save,
+                TargetView = this
+            };
+
+            var saveFileAs = new MenuItemv2 (this, Command.SaveAs);
+
+            menu.Add (newFile, openFile, saveFile, saveFileAs, new Line ());
+        }
+
+        private void ConfigureOptionsSubMenu (Menuv2 menu)
+        {
+            // This is an example of a menu item with a checkbox that is NOT
+            // bound to a Command. The PopoverMenu will raise Accepted when Alt-U is pressed.
+            // The checkbox state will automatically toggle each time Alt-U is pressed beacuse
+            // the MenuItem actaully gets the key events.
+            var autoSave = new MenuItemv2
+            {
+                Title = "_Auto Save",
+                Text = "(no Command)",
+                Key = Key.F10
+            };
+
+            autoSave.CommandView = _autoSaveCb = new ()
+            {
+                Title = autoSave.Title,
+                HighlightStyle = HighlightStyle.None,
+                CanFocus = false
+            };
+
+            // This is an example of a MenuItem with a checkbox that is bound to a command.
+            // When the key bound to Command.EntableOverwrite is pressed, InvokeCommand will invoke it 
+            // on targetview, and thus the MenuItem will never see the key event. 
+            // Because of this, the check box will not automatically track the state.
+            var enableOverwrite = new MenuItemv2
+            {
+                Title = "Enable _Overwrite",
+                Text = "Overwrite",
+                Command = Command.EnableOverwrite,
+                TargetView = this
+            };
+
+            enableOverwrite.CommandView = _enableOverwriteCb = new ()
+            {
+                Title = enableOverwrite.Title,
+                HighlightStyle = HighlightStyle.None,
+                CanFocus = false
+            };
+
+            _enableOverwriteCb.Accepting += (sender, args) => args.Cancel = true;
+
+            var mutuallyExclusiveOptions = new MenuItemv2
+            {
+                HelpText = "3 Mutually Exclusive Options",
+                Key = Key.F7
+            };
+
+            mutuallyExclusiveOptions.CommandView = _mutuallyExclusiveOptionsRg = new RadioGroup ()
+            {
+                RadioLabels = [ "G_ood", "_Bad", "U_gly" ]
+            };
+
+            var menuBGColor = new MenuItemv2
+            {
+                HelpText = "Menu BG Color",
+                Key = Key.F8,
+            };
+
+            menuBGColor.CommandView = _menuBgColorCp = new ColorPicker() 
+            {
+                Width = 30
+            };
+
+            _menuBgColorCp.ColorChanged += (sender, args) =>
+                                           {
+                                               menu.ColorScheme = menu.ColorScheme with
+                                               {
+                                                   Normal = new (menu.ColorScheme.Normal.Foreground, args.CurrentValue)
+                                               };
+                                           };
+
+            menu.Add (autoSave, enableOverwrite, new Line (), mutuallyExclusiveOptions, new Line (), menuBGColor);
+        }
+
+        private void ConfigureDetialsSubMenu (Menuv2 menu)
+        {
+            var shortcut2 = new MenuItemv2
+            {
+                Title = "_Detail 1",
+                Text = "Some detail #1"
+            };
+
+            var shortcut3 = new MenuItemv2
+            {
+                Title = "_Three",
+                Text = "The 3rd item"
+            };
+
+            var editMode = new MenuItemv2
+            {
+                Title = "E_dit Mode",
+                Text = "App binding to Command.Edit",
+                Command = Command.Edit,
+            };
+
+            editMode.CommandView = _editModeCb = new CheckBox
+            {
+                Title = editMode.Title,
+                HighlightStyle = HighlightStyle.None,
+                CanFocus = false
+            };
+
+            // This ensures the checkbox state toggles when the hotkey of Title is pressed.
+            //shortcut4.Accepting += (sender, args) => args.Cancel = true;
+
+            menu.Add (shortcut2, shortcut3, new Line (), editMode);
+        }
+
+        private void ConfigureMoreDetailsSubMenu (Menuv2 menu)
+        {
+            var deeperDetail = new MenuItemv2
+            {
+                Title = "_Deeper Detail",
+                Text = "Deeper Detail",
+                Action = () => { MessageBox.Query ("Deeper Detail", "Lots of details", "_Ok"); }
+            };
+
+            var shortcut4 = new MenuItemv2
+            {
+                Title = "_Third",
+                Text = "Below the line"
+            };
+
+            // This ensures the checkbox state toggles when the hotkey of Title is pressed.
+            //shortcut4.Accepting += (sender, args) => args.Cancel = true;
+
+            menu.Add (deeperDetail, new Line (), shortcut4);
+        }
+
+        /// <inheritdoc/>
+        protected override void Dispose (bool disposing)
+        {
+            if (disposing)
+            {
+                //    if (FilePopoverMenu is { })
+                //    {
+                //        FilePopoverMenu.Visible = false;
+                //        FilePopoverMenu?.Dispose ();
+                //        FilePopoverMenu = null;
+                //    }
+            }
+
+            base.Dispose (disposing);
+        }
+    }
+
+    private const string LOGFILE_LOCATION = "./logs";
+    private static readonly string _logFilePath = string.Empty;
+    private static readonly LoggingLevelSwitch _logLevelSwitch = new ();
+
+    private static ILogger CreateLogger ()
+    {
+        // Configure Serilog to write logs to a file
+        _logLevelSwitch.MinimumLevel = LogEventLevel.Verbose;
+
+        Log.Logger = new LoggerConfiguration ()
+                     .MinimumLevel.ControlledBy (_logLevelSwitch)
+                     .Enrich.FromLogContext () // Enables dynamic enrichment
+                     .WriteTo.Debug ()
+                     .WriteTo.File (
+                                    _logFilePath,
+                                    rollingInterval: RollingInterval.Day,
+                                    outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
+                     .CreateLogger ();
+
+        // Create a logger factory compatible with Microsoft.Extensions.Logging
+        using ILoggerFactory loggerFactory = LoggerFactory.Create (
+                                                                   builder =>
+                                                                   {
+                                                                       builder
+                                                                           .AddSerilog (dispose: true) // Integrate Serilog with ILogger
+                                                                           .SetMinimumLevel (LogLevel.Trace); // Set minimum log level
+                                                                   });
+
+        // Get an ILogger instance
+        return loggerFactory.CreateLogger ("Global Logger");
+    }
+}

+ 1 - 1
UICatalog/Scenarios/Snake.cs

@@ -317,7 +317,7 @@ public class Snake : Scenario
         protected override bool OnDrawingContent ()
         protected override bool OnDrawingContent ()
         {
         {
             SetAttribute (white);
             SetAttribute (white);
-            ClearViewport ();
+            ClearViewport (null);
 
 
             var canvas = new LineCanvas ();
             var canvas = new LineCanvas ();
 
 

+ 19 - 3
UICatalog/Scenarios/Transparent.cs

@@ -67,7 +67,7 @@ public sealed class Transparent : Scenario
         public TransparentView ()
         public TransparentView ()
         {
         {
             Title = "Transparent View";
             Title = "Transparent View";
-            base.Text = "View.Text.\nThis should be opaque.\nNote how clipping works?";
+            //base.Text = "View.Text.\nThis should be opaque.\nNote how clipping works?";
             TextFormatter.Alignment = Alignment.Center;
             TextFormatter.Alignment = Alignment.Center;
             TextFormatter.VerticalAlignment = Alignment.Center;
             TextFormatter.VerticalAlignment = Alignment.Center;
             Arrangement = ViewArrangement.Overlapped | ViewArrangement.Resizable | ViewArrangement.Movable;
             Arrangement = ViewArrangement.Overlapped | ViewArrangement.Resizable | ViewArrangement.Movable;
@@ -85,18 +85,33 @@ public sealed class Transparent : Scenario
                 Height = 8,
                 Height = 8,
                 BorderStyle = LineStyle.Dashed,
                 BorderStyle = LineStyle.Dashed,
                 Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
                 Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
-                ShadowStyle = ShadowStyle.Transparent,
+               // ShadowStyle = ShadowStyle.Transparent,
             };
             };
             transparentSubView.Border!.Thickness = new (1, 1, 1, 1);
             transparentSubView.Border!.Thickness = new (1, 1, 1, 1);
             transparentSubView.ColorScheme = Colors.ColorSchemes ["Dialog"];
             transparentSubView.ColorScheme = Colors.ColorSchemes ["Dialog"];
+            transparentSubView.Visible = false;
 
 
             Button button = new Button ()
             Button button = new Button ()
             {
             {
                 Title = "_Opaque Shadows No Worky",
                 Title = "_Opaque Shadows No Worky",
                 X = Pos.Center (),
                 X = Pos.Center (),
-                Y = 4,
+                Y = 2,
                 ColorScheme = Colors.ColorSchemes ["Dialog"],
                 ColorScheme = Colors.ColorSchemes ["Dialog"],
             };
             };
+            button.Visible = false;
+
+
+            var shortcut = new Shortcut ()
+            {
+                Id = "shortcut",
+                X = Pos.Center (),
+                Y = Pos.AnchorEnd(),
+                Title = "A _Shortcut",
+                HelpText = "Help!",
+                Key = Key.F11,
+                ColorScheme = Colors.ColorSchemes ["Base"]
+
+            };
 
 
             button.ClearingViewport += (sender, args) =>
             button.ClearingViewport += (sender, args) =>
                                        {
                                        {
@@ -105,6 +120,7 @@ public sealed class Transparent : Scenario
 
 
 
 
             base.Add (button);
             base.Add (button);
+            base.Add (shortcut);
             base.Add (transparentSubView);
             base.Add (transparentSubView);
         }
         }
 
 

+ 46 - 0
UICatalog/Scenarios/ViewExperiments.cs

@@ -56,6 +56,50 @@ public class ViewExperiments : Scenario
             Title = $"TopButton _{GetNextHotKey ()}",
             Title = $"TopButton _{GetNextHotKey ()}",
         };
         };
 
 
+        var popoverView = new View ()
+        {
+            X = Pos.Center (),
+            Y = Pos.Center (),
+            Width = 30,
+            Height = 10,
+            Title = "Popover",
+            Text = "This is a popover",
+            Visible = false,
+            CanFocus = true,
+            Arrangement = ViewArrangement.Resizable | ViewArrangement.Movable
+        };
+        popoverView.BorderStyle = LineStyle.RoundedDotted;
+
+        Button popoverButton = new ()
+        {
+            X = Pos.Center (),
+            Y = Pos.Center (),
+            Title = $"_Close",
+        };
+        //popoverButton.Accepting += (sender, e) => Application.Popover!.Visible = false;
+        popoverView.Add (popoverButton);
+
+        button.Accepting += ButtonAccepting;
+
+        void ButtonAccepting (object sender, CommandEventArgs e)
+        {
+            //Application.Popover = popoverView;
+            //Application.Popover!.Visible = true;
+        }
+
+        testFrame.MouseClick += TestFrameOnMouseClick;
+
+        void TestFrameOnMouseClick (object sender, MouseEventArgs e)
+        {
+            if (e.Flags == MouseFlags.Button3Clicked)
+            {
+                popoverView.X = e.ScreenPosition.X;
+                popoverView.Y = e.ScreenPosition.Y;
+                //Application.Popover = popoverView;
+                //Application.Popover!.Visible = true;
+            }
+        }
+
         testFrame.Add (button);
         testFrame.Add (button);
 
 
         editor.AutoSelectViewToEdit = true;
         editor.AutoSelectViewToEdit = true;
@@ -63,6 +107,7 @@ public class ViewExperiments : Scenario
         editor.AutoSelectAdornments = true;
         editor.AutoSelectAdornments = true;
 
 
         Application.Run (app);
         Application.Run (app);
+        popoverView.Dispose ();
         app.Dispose ();
         app.Dispose ();
 
 
         Application.Shutdown ();
         Application.Shutdown ();
@@ -70,6 +115,7 @@ public class ViewExperiments : Scenario
         return;
         return;
     }
     }
 
 
+
     private int _hotkeyCount;
     private int _hotkeyCount;
 
 
     private char GetNextHotKey ()
     private char GetNextHotKey ()

+ 12 - 9
UICatalog/UICatalog.cs

@@ -74,7 +74,7 @@ public class UICatalogApp
     private static Options _options;
     private static Options _options;
     private static ObservableCollection<Scenario>? _scenarios;
     private static ObservableCollection<Scenario>? _scenarios;
 
 
-    private const string LOGFILE_LOCATION = "./logs";
+    private const string LOGFILE_LOCATION = "logs";
     private static string _logFilePath = string.Empty;
     private static string _logFilePath = string.Empty;
     private static readonly LoggingLevelSwitch _logLevelSwitch = new ();
     private static readonly LoggingLevelSwitch _logLevelSwitch = new ();
 
 
@@ -171,7 +171,7 @@ public class UICatalogApp
         resultsFile.AddAlias ("--f");
         resultsFile.AddAlias ("--f");
 
 
         // what's the app name?
         // what's the app name?
-        _logFilePath = $"{LOGFILE_LOCATION}/{Assembly.GetExecutingAssembly ().GetName ().Name}.log";
+        _logFilePath = $"{LOGFILE_LOCATION}/{Assembly.GetExecutingAssembly ().GetName ().Name}";
         Option<string> debugLogLevel = new Option<string> ("--debug-log-level", $"The level to use for logging (debug console and {_logFilePath})").FromAmong (
         Option<string> debugLogLevel = new Option<string> ("--debug-log-level", $"The level to use for logging (debug console and {_logFilePath})").FromAmong (
              Enum.GetNames<LogLevel> ()
              Enum.GetNames<LogLevel> ()
             );
             );
@@ -278,7 +278,7 @@ public class UICatalogApp
         return loggerFactory.CreateLogger ("Global Logger");
         return loggerFactory.CreateLogger ("Global Logger");
     }
     }
 
 
-    private static void OpenUrl (string url)
+    public static void OpenUrl (string url)
     {
     {
         if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
         if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
         {
         {
@@ -690,7 +690,7 @@ public class UICatalogApp
             return;
             return;
         }
         }
 
 
-        // Validate there are no outstanding Responder-based instances 
+        // Validate there are no outstanding View instances 
         // after a scenario was selected to run. This proves the main UI Catalog
         // after a scenario was selected to run. This proves the main UI Catalog
         // 'app' closed cleanly.
         // 'app' closed cleanly.
         foreach (View? inst in View.Instances)
         foreach (View? inst in View.Instances)
@@ -1354,11 +1354,14 @@ public class UICatalogApp
             menuItems.Add (null!);
             menuItems.Add (null!);
 
 
             menuItems.Add (
             menuItems.Add (
-                           new ()
-                           {
-                               Title = $"Log file: {_logFilePath}"
-                               //CanExecute = () => false
-                           });
+                           new (
+                                $"_Open Log Folder",
+                                "",
+                                () => OpenUrl (LOGFILE_LOCATION),
+                                null,
+                                null,
+                                null
+                               ));
 
 
             return menuItems.ToArray ()!;
             return menuItems.ToArray ()!;
         }
         }

+ 18 - 0
docfx/docs/Popovers.md

@@ -0,0 +1,18 @@
+# Popovers Deep Dive
+
+Normally Views cannot draw outside of their `Viewport`. Options for influencing content outside of the `Viewport` include:
+
+1) Modifying the `Border` behavior
+2) Modifying the `Margin` behavior
+3) Using @Terminal.Gui.Application.Popover
+
+Popovers are useful for scenarios such as menus, autocomplete popups, and drop-down combo boxes.
+
+A Popover is any View that meets these characteristics"
+
+- Implements the @Terminal.Gui.IPopover interface 
+- Is Focusable (`CetFocus = true`)
+- Is Transparent (`ViewportSettings = ViewportSettings.Transparent | ViewportSettings.TransparentMouse`
+- Sets `Visible = false` when it receives `Application.QuitKey`
+
[email protected] provides a sophisticated implementation.

+ 1 - 0
docfx/docs/index.md

@@ -33,6 +33,7 @@ See [What's New in V2 For more](newinv2.md).
 * [Mouse API](mouse.md)
 * [Mouse API](mouse.md)
 * [Multi-tasking and the Application Main Loop](mainloop.md)
 * [Multi-tasking and the Application Main Loop](mainloop.md)
 * [Navigation](navigation.md)
 * [Navigation](navigation.md)
+* [Popovers](Popovers.md)
 * [View Deep Dive](View.md)
 * [View Deep Dive](View.md)
 * [Views](views.md)
 * [Views](views.md)
 * [Scrolling Deep Dive](scrolling.md)
 * [Scrolling Deep Dive](scrolling.md)

+ 11 - 5
docfx/docs/logging.md

@@ -1,14 +1,20 @@
 # Logging
 # Logging
 
 
-Logging has come to Terminal.Gui! You can now enable comprehensive logging of the internals of the libray. This can help diagnose issues with specific terminals, keyboard cultures and/or operating system specific issues.
+Logging has come to Terminal.Gui! You can now enable comprehensive logging of the internals of the library. This can help diagnose issues with specific terminals, keyboard cultures and/or operating system specific issues.
 
 
-To enable file logging you should set the static property `Logging.Logger` to an instance of `Microsoft.Extensions.Logging.ILogger`.  If your program already uses logging you can provide a shared instance or instance from Dependency Injection (DI).
+To enable file logging you should set the static property `Logging.Logger` to an instance of `Microsoft.Extensions.Logging.ILogger`. If your program already uses logging you can provide a shared instance or instance from Dependency Injection (DI).
 
 
 Alternatively you can create a new log to ensure only Terminal.Gui logs appear.
 Alternatively you can create a new log to ensure only Terminal.Gui logs appear.
 
 
-Any logging framework will work  (Serilog, NLog, Log4Net etc) but you should ensure you only log to File or UDP etc (i.e. not to console!).
+Any logging framework will work (Serilog, NLog, Log4Net etc) but you should ensure you don't log to the stdout console (File, Debug Output, or UDP etc... are all fine).
 
 
-## Worked example with Serilog to file
+## UICatalog
+
+UI Catalog has built-in UI for logging. It logs to both the debug console and a file. By default it only logs at the `Warning` level. 
+
+![UICatalog Logging](../images/UICatalog_Logging.png)
+
+## Example with Serilog to file
 
 
 Here is an example of how to add logging of Terminal.Gui internals to your program using Serilog file log.
 Here is an example of how to add logging of Terminal.Gui internals to your program using Serilog file log.
 
 
@@ -81,7 +87,7 @@ Example logs:
 
 
 ## Metrics
 ## Metrics
 
 
-If you are finding that the UI is slow or unresponsive - or are just interested in performance metrics.  You can see these by instaling the `dotnet-counter` tool and running it for your process.
+If you are finding that the UI is slow or unresponsive - or are just interested in performance metrics. You can see these by installing the `dotnet-counter` tool and running it for your process.
 
 
 ```
 ```
 dotnet tool install dotnet-counters --global
 dotnet tool install dotnet-counters --global

+ 2 - 0
docfx/docs/toc.yml

@@ -30,6 +30,8 @@
   href: mainloop.md
   href: mainloop.md
 - name: Navigation
 - name: Navigation
   href: navigation.md
   href: navigation.md
+- name: Popovers
+  href: Popovers.md
 - name: View Deep Dive
 - name: View Deep Dive
   href: View.md  
   href: View.md  
 - name: View List
 - name: View List

二進制
docfx/images/UICatalog_Logging.png