Bladeren bron

Fixes #3968. Menu appears in wrong place when opened in a subview (#3979)

* Fixes #3968. Menu appears in wrong place when opened in a subview

* Fixes #3965. Cannot exit a Window with a CanFocus true MenuBar

* Fixes #3981. Drivers crash when middle or right click on a ContextMenu item

---------

Co-authored-by: Tig <[email protected]>
BDisp 4 maanden geleden
bovenliggende
commit
7aae0c2ad5

+ 30 - 12
Terminal.Gui/Views/Menu/ContextMenu.cs

@@ -104,6 +104,7 @@ public sealed class ContextMenu : IDisposable
         if (_menuBar is { })
         {
             _menuBar.MenuAllClosed -= MenuBar_MenuAllClosed;
+            _container?.Remove (_menuBar);
         }
         Application.UngrabMouse ();
         _menuBar?.Dispose ();
@@ -177,16 +178,16 @@ public sealed class ContextMenu : IDisposable
         }
 
         MenuItems = menuItems;
-        _container = Application.Top;
+        _container = GetTopSuperView (Host);
         _container!.Closing += Container_Closing;
         _container.Deactivate += Container_Deactivate;
         _container.Disposing += Container_Disposing;
-        Rectangle frame = Application.Screen;
+        Rectangle viewport = _container.Viewport;
         Point position = Position;
 
         if (Host is { })
         {
-            Point pos = Host.ViewportToScreen (frame).Location;
+            Point pos = Host.Frame.Location;
             pos.Y += Host.Frame.Height > 0 ? Host.Frame.Height - 1 : 0;
 
             if (position != pos)
@@ -197,11 +198,11 @@ public sealed class ContextMenu : IDisposable
 
         Rectangle rect = Menu.MakeFrame (position.X, position.Y, MenuItems.Children);
 
-        if (rect.Right >= frame.Right)
+        if (rect.Right >= viewport.Right)
         {
-            if (frame.Right - rect.Width >= 0 || !ForceMinimumPosToZero)
+            if (viewport.Right - rect.Width >= 0 || !ForceMinimumPosToZero)
             {
-                position.X = frame.Right - rect.Width;
+                position.X = viewport.Right - rect.Width;
             }
             else if (ForceMinimumPosToZero)
             {
@@ -213,17 +214,17 @@ public sealed class ContextMenu : IDisposable
             position.X = 0;
         }
 
-        if (rect.Bottom >= frame.Bottom)
+        if (rect.Bottom >= viewport.Bottom)
         {
-            if (frame.Bottom - rect.Height - 1 >= 0 || !ForceMinimumPosToZero)
+            if (viewport.Bottom - rect.Height - 1 >= 0 || !ForceMinimumPosToZero)
             {
                 if (Host is null)
                 {
-                    position.Y = frame.Bottom - rect.Height - 1;
+                    position.Y = viewport.Bottom - rect.Height - 1;
                 }
                 else
                 {
-                    Point pos = Host.ViewportToScreen (frame).Location;
+                    Point pos = Host.Frame.Location;
                     position.Y = pos.Y - rect.Height - 1;
                 }
             }
@@ -251,12 +252,29 @@ public sealed class ContextMenu : IDisposable
         _menuBar._isContextMenuLoading = true;
         _menuBar.MenuAllClosed += MenuBar_MenuAllClosed;
 
-        _menuBar.BeginInit ();
-        _menuBar.EndInit ();
+        _container.Add (_menuBar);
         IsShow = true;
         _menuBar.OpenMenu ();
     }
 
+    internal static Toplevel? GetTopSuperView (View? view)
+    {
+        if (view is Toplevel toplevel)
+        {
+            return toplevel;
+        }
+
+        for (View? sv = view?.SuperView; sv != null; sv = sv.SuperView)
+        {
+            if (sv is Toplevel top)
+            {
+                return top;
+            }
+        }
+
+        return (Toplevel?)view?.SuperView ?? Application.Top;
+    }
+
     private void Container_Closing (object? sender, ToplevelClosingEventArgs obj) { Hide (); }
     private void Container_Deactivate (object? sender, ToplevelEventArgs e) { Hide (); }
     private void Container_Disposing (object? sender, EventArgs e) { Dispose (); }

+ 29 - 31
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -73,7 +73,7 @@ public class MenuBar : View, IDesignable
         Y = 0;
         Width = Dim.Fill ();
         Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize ().
-        Menus = new MenuBarItem [] { };
+        Menus = [];
 
         //CanFocus = true;
         _selected = -1;
@@ -111,9 +111,14 @@ public class MenuBar : View, IDesignable
                     Command.Cancel,
                     () =>
                     {
-                        CloseMenuBar ();
+                        if (IsMenuOpen)
+                        {
+                            CloseMenuBar ();
 
-                        return true;
+                            return true;
+                        }
+
+                        return false;
                     }
                    );
 
@@ -556,10 +561,10 @@ public class MenuBar : View, IDesignable
 
     private void CloseOtherOpenedMenuBar ()
     {
-        if (Application.Top is { })
+        if (SuperView is { })
         {
             // Close others menu bar opened
-            Menu? menu = Application.Top.SubViews.FirstOrDefault (v => v is Menu m && m.Host != this && m.Host.IsMenuOpen) as Menu;
+            Menu? menu = SuperView.SubViews.FirstOrDefault (v => v is Menu m && m.Host != this && m.Host.IsMenuOpen) as Menu;
             menu?.Host.CleanUp ();
         }
     }
@@ -595,7 +600,7 @@ public class MenuBar : View, IDesignable
             case false:
                 if (_openMenu is { })
                 {
-                    Application.Top?.Remove (_openMenu);
+                    SuperView?.Remove (_openMenu);
                 }
 
                 SetNeedsDraw ();
@@ -634,7 +639,7 @@ public class MenuBar : View, IDesignable
 
                     if (OpenCurrentMenu is { })
                     {
-                        Application.Top?.Remove (OpenCurrentMenu);
+                        SuperView?.Remove (OpenCurrentMenu);
                         if (Application.MouseGrabView == OpenCurrentMenu)
                         {
                             Application.UngrabMouse ();
@@ -822,7 +827,7 @@ public class MenuBar : View, IDesignable
 
                 if (_openMenu is { })
                 {
-                    Application.Top?.Remove (_openMenu);
+                    SuperView?.Remove (_openMenu);
                     if (Application.MouseGrabView == _openMenu)
                     {
                         Application.UngrabMouse ();
@@ -838,34 +843,23 @@ public class MenuBar : View, IDesignable
                     pos += Menus [i].TitleLength + (Menus [i].Help.GetColumns () > 0 ? Menus [i].Help.GetColumns () + 2 : 0) + _leftPadding + _rightPadding;
                 }
 
-                var locationOffset = Point.Empty;
 
-                // if SuperView is null then it's from a ContextMenu
-                if (SuperView is null)
-                {
-                    locationOffset = GetScreenOffset ();
-                }
 
-                if (SuperView is { } && SuperView != Application.Top)
-                {
-                    locationOffset.X += SuperView.Border.Thickness.Left;
-                    locationOffset.Y += SuperView.Border.Thickness.Top;
-                }
 
                 _openMenu = new ()
                 {
                     Host = this,
-                    X = Frame.X + pos + locationOffset.X,
-                    Y = Frame.Y + 1 + locationOffset.Y,
+                    X = Frame.X + pos,
+                    Y = Frame.Y + 1,
                     BarItems = Menus [index],
                     Parent = null
                 };
                 OpenCurrentMenu = _openMenu;
                 OpenCurrentMenu._previousSubFocused = _openMenu;
 
-                if (Application.Top is { })
+                if (SuperView is { })
                 {
-                    Application.Top.Add (_openMenu);
+                    SuperView.Add (_openMenu);
                    // _openMenu.SetRelativeLayout (Application.Screen.Size);
                 }
                 else
@@ -894,13 +888,11 @@ public class MenuBar : View, IDesignable
 
                     if (!UseSubMenusSingleFrame)
                     {
-                        locationOffset = GetLocationOffset ();
-
                         OpenCurrentMenu = new ()
                         {
                             Host = this,
-                            X = last!.Frame.Left + last.Frame.Width + locationOffset.X,
-                            Y = last.Frame.Top + locationOffset.Y + last._currentChild,
+                            X = last!.Frame.Left + last.Frame.Width,
+                            Y = last.Frame.Top + last._currentChild + 1,
                             BarItems = subMenu,
                             Parent = last
                         };
@@ -931,7 +923,7 @@ public class MenuBar : View, IDesignable
 
                     OpenCurrentMenu._previousSubFocused = last._previousSubFocused;
                     _openSubMenu.Add (OpenCurrentMenu);
-                    Application.Top?.Add (OpenCurrentMenu);
+                    SuperView?.Add (OpenCurrentMenu);
 
                     if (!OpenCurrentMenu.IsInitialized)
                     {
@@ -1014,7 +1006,7 @@ public class MenuBar : View, IDesignable
         {
             foreach (Menu item in _openSubMenu)
             {
-                Application.Top!.Remove (item);
+                SuperView?.Remove (item);
                 if (Application.MouseGrabView == item)
                 {
                     Application.UngrabMouse ();
@@ -1263,7 +1255,7 @@ public class MenuBar : View, IDesignable
             if (_openSubMenu is { })
             {
                 menu = _openSubMenu [i];
-                Application.Top!.Remove (menu);
+                SuperView!.Remove (menu);
                 _openSubMenu.Remove (menu);
 
                 if (Application.MouseGrabView == menu)
@@ -1544,8 +1536,14 @@ public class MenuBar : View, IDesignable
 
                 if (me.View != current)
                 {
+                    View v = current;
                     Application.UngrabMouse ();
-                    View v = me.View;
+
+                    if (((Menu)me.View).Host.SuperView is { } && ((Menu)me.View).Host.SuperView!.InternalSubViews.Contains(me.View))
+                    {
+                        v = me.View;
+                    }
+
                     Application.GrabMouse (v);
                     MouseEventArgs nme;
 

+ 190 - 26
Tests/UnitTests/Views/ContextMenuTests.cs

@@ -530,10 +530,12 @@ public class ContextMenuTests (ITestOutputHelper output)
                                                       output
                                                      );
 
+        View menu = top.SubViews.First (v => v is Menu);
+
         Assert.True (
-                     top.SubViews.ElementAt (0)
+                     menu
                         .NewMouseEvent (
-                                        new MouseEventArgs { Position = new (0, 3), Flags = MouseFlags.ReportMousePosition, View = top.SubViews.ElementAt (0) }
+                                        new MouseEventArgs { Position = new (0, 3), Flags = MouseFlags.ReportMousePosition, View = menu }
                                        )
                     );
         Application.RunIteration (ref rs);
@@ -578,10 +580,11 @@ public class ContextMenuTests (ITestOutputHelper output)
                                                       output
                                                      );
 
+        menu = top.SubViews.First (v => v is Menu);
         Assert.True (
-                     top.SubViews.ElementAt (0)
+                     menu
                         .NewMouseEvent (
-                                        new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = top.SubViews.ElementAt (0) }
+                                        new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = menu }
                                        )
                     );
         Application.RunIteration (ref rs);
@@ -625,10 +628,11 @@ public class ContextMenuTests (ITestOutputHelper output)
                                                       output
                                                      );
 
+        menu = top.SubViews.First (v => v is Menu);
         Assert.True (
-                     top.SubViews.ElementAt (0)
+                     menu
                         .NewMouseEvent (
-                                        new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = top.SubViews.ElementAt (0) }
+                                        new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = menu }
                                        )
                     );
         Application.RunIteration (ref rs);
@@ -669,10 +673,11 @@ public class ContextMenuTests (ITestOutputHelper output)
                                                       output
                                                      );
 
+        menu = top.SubViews.First (v => v is Menu);
         Assert.True (
-                     top.SubViews.ElementAt (0)
+                     menu
                         .NewMouseEvent (
-                                        new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = top.SubViews.ElementAt (0) }
+                                        new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = menu }
                                        )
                     );
         Application.RunIteration (ref rs);
@@ -713,10 +718,11 @@ public class ContextMenuTests (ITestOutputHelper output)
                                                       output
                                                      );
 
+        menu = top.SubViews.First (v => v is Menu);
         Assert.True (
-                     top.SubViews.ElementAt (0)
+                     menu
                         .NewMouseEvent (
-                                        new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = top.SubViews.ElementAt (0) }
+                                        new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = menu }
                                        )
                     );
         Application.RunIteration (ref rs);
@@ -1223,7 +1229,8 @@ public class ContextMenuTests (ITestOutputHelper output)
         Toplevel top = new ();
         RunState rs = Application.Begin (top);
         cm.Show (menuItems);
-        Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top!.SubViews.ElementAt (0).Frame);
+        var menu = Application.Top!.SubViews.First (v => v is Menu);
+        Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame);
         Application.LayoutAndDraw ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
@@ -1241,8 +1248,10 @@ public class ContextMenuTests (ITestOutputHelper output)
 
         var firstIteration = false;
         Application.RunIteration (ref rs, firstIteration);
-        Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.SubViews.ElementAt (0).Frame);
-        Assert.Equal (new Rectangle (5, 11, 15, 6), Application.Top.SubViews.ElementAt (1).Frame);
+        menu = Application.Top!.SubViews.First (v => v is Menu);
+        Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame);
+        menu = Application.Top!.SubViews.Last (v => v is Menu);
+        Assert.Equal (new Rectangle (5, 11, 15, 6), menu.Frame);
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       @"
@@ -1259,7 +1268,8 @@ public class ContextMenuTests (ITestOutputHelper output)
 
         firstIteration = false;
         Application.RunIteration (ref rs, firstIteration);
-        Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.SubViews.ElementAt (0).Frame);
+        menu = Application.Top!.SubViews.First (v => v is Menu);
+        Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame);
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       @"
@@ -1316,7 +1326,10 @@ public class ContextMenuTests (ITestOutputHelper output)
         RunState rs = Application.Begin (top);
         cm.Show (menuItems);
 
-        Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.SubViews.ElementAt (0).Frame);
+
+        var menu = Application.Top!.SubViews.First (v => v is Menu);
+
+        Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame);
         Application.LayoutAndDraw ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
@@ -1333,7 +1346,8 @@ public class ContextMenuTests (ITestOutputHelper output)
 
         var firstIteration = false;
         Application.RunIteration (ref rs, firstIteration);
-        Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.SubViews.ElementAt (0).Frame);
+        menu = Application.Top!.SubViews.First (v => v is Menu);
+        Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame);
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       @"
@@ -1350,7 +1364,8 @@ public class ContextMenuTests (ITestOutputHelper output)
 
         firstIteration = false;
         Application.RunIteration (ref rs, firstIteration);
-        Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.SubViews.ElementAt (0).Frame);
+        menu = Application.Top!.SubViews.First (v => v is Menu);
+        Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame);
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       @"
@@ -1368,7 +1383,8 @@ public class ContextMenuTests (ITestOutputHelper output)
 
         firstIteration = false;
         Application.RunIteration (ref rs, firstIteration);
-        Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.SubViews.ElementAt (0).Frame);
+        menu = Application.Top!.SubViews.First (v => v is Menu);
+        Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame);
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       @"
@@ -1404,7 +1420,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 3), Flags = MouseFlags.Button3Clicked });
         Assert.False (tf1.HasFocus);
         Assert.False (tf2.HasFocus);
-        Assert.Equal (5, win.SubViews.Count);
+        Assert.Equal (6, win.SubViews.Count);
         Assert.True (tf2.ContextMenu.MenuBar.IsMenuOpen);
         Assert.True (win.Focused is Menu);
         Assert.True (Application.MouseGrabView is MenuBar);
@@ -1414,7 +1430,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 1), Flags = MouseFlags.Button1Clicked });
         Assert.True (tf1.HasFocus);
         Assert.False (tf2.HasFocus);
-        Assert.Equal (4, win.SubViews.Count);
+        Assert.Equal (5, win.SubViews.Count);
 
         // The last context menu bar opened is always preserved
         Assert.NotNull (tf2.ContextMenu.MenuBar);
@@ -1426,7 +1442,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 3), Flags = MouseFlags.Button1Clicked });
         Assert.False (tf1.HasFocus);
         Assert.True (tf2.HasFocus);
-        Assert.Equal (4, win.SubViews.Count);
+        Assert.Equal (5, win.SubViews.Count);
 
         // The last context menu bar opened is always preserved
         Assert.NotNull (tf2.ContextMenu.MenuBar);
@@ -1710,7 +1726,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.R.NoShift, out _));
         Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.D.WithAlt, out _));
         Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.D.NoShift, out _));
-        Assert.Single (Application.Top!.SubViews);
+        Assert.Equal (2, Application.Top!.SubViews.Count);
         View [] menus = Application.Top!.SubViews.Where (v => v is Menu m && m.Host == cm.MenuBar).ToArray ();
         Assert.True (menus [0].HotKeyBindings.TryGet (Key.N.WithAlt, out _));
         Assert.True (menus [0].HotKeyBindings.TryGet (Key.N.NoShift, out _));
@@ -1835,7 +1851,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Assert.False (cm.MenuBar!.HotKeyBindings.TryGet (Key.E.NoShift, out _));
         Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.R.WithAlt, out _));
         Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.R.NoShift, out _));
-        Assert.Equal (3, Application.Top!.SubViews.Count);
+        Assert.Equal (4, Application.Top!.SubViews.Count);
         menus = Application.Top!.SubViews.Where (v => v is Menu m && m.Host == cm.MenuBar).ToArray ();
         Assert.True (menus [0].HotKeyBindings.TryGet (Key.E.WithAlt, out _));
         Assert.True (menus [0].HotKeyBindings.TryGet (Key.E.NoShift, out _));
@@ -1850,7 +1866,7 @@ public class ContextMenuTests (ITestOutputHelper output)
 
         cm.Show (menuItems);
         Assert.True (cm.MenuBar.IsMenuOpen);
-        Assert.Equal (3, Application.Top!.SubViews.Count);
+        Assert.Equal (4, Application.Top!.SubViews.Count);
         menus = Application.Top!.SubViews.Where (v => v is Menu m && m.Host == cm.MenuBar).ToArray ();
         Assert.True (menus [0].HotKeyBindings.TryGet (Key.E.WithAlt, out _));
         Assert.True (menus [0].HotKeyBindings.TryGet (Key.E.NoShift, out _));
@@ -1866,7 +1882,7 @@ public class ContextMenuTests (ITestOutputHelper output)
         Application.MainLoop!.RunIteration ();
         Assert.True (renameFile);
 
-        Assert.Single (Application.Top!.SubViews);
+        Assert.Equal (2, Application.Top!.SubViews.Count);
         Assert.True (menuBar.HotKeyBindings.TryGet (Key.F.WithAlt, out _));
         Assert.True (menuBar.HotKeyBindings.TryGet (Key.F.NoShift, out _));
         Assert.False (menuBar.HotKeyBindings.TryGet (Key.N.WithAlt, out _));
@@ -1938,4 +1954,152 @@ public class ContextMenuTests (ITestOutputHelper output)
 
         top.Dispose ();
     }
-}
+
+    [Theory]
+    [InlineData (1)]
+    [InlineData (2)]
+    [InlineData (3)]
+    [AutoInitShutdown]
+    public void Mouse_Pressed_Released_Clicked (int button)
+    {
+        var actionRaised = false;
+
+        var menuBar = new MenuBar
+        {
+            Menus =
+            [
+                new (
+                     "_File",
+                     new MenuItem []
+                     {
+                         new ("_New", string.Empty, () => actionRaised = true)
+                     })
+            ]
+        };
+        var cm = new ContextMenu ();
+
+        var menuItems = new MenuBarItem (
+                                         [
+                                             new ("_Rename File", string.Empty, () => actionRaised = true)
+                                         ]
+                                        );
+        var top = new Toplevel ();
+
+        top.MouseClick += (s, e) =>
+                          {
+                              if (e.Flags == cm.MouseFlags)
+                              {
+                                  cm.Position = new (e.Position.X, e.Position.Y);
+                                  cm.Show (menuItems);
+                                  e.Handled = true;
+                              }
+                          };
+
+        top.Add (menuBar);
+        Application.Begin (top);
+
+        // MenuBar
+        Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
+        Assert.True (menuBar.IsMenuOpen);
+
+        switch (button)
+        {
+            // Left Button
+            case 1:
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 2), Flags = MouseFlags.Button1Pressed });
+                Assert.True (menuBar.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 2), Flags = MouseFlags.Button1Released });
+                Assert.True (menuBar.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 2), Flags = MouseFlags.Button1Clicked });
+                Assert.False (menuBar.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.True (actionRaised);
+                actionRaised = false;
+
+                break;
+            // Middle Button
+            case 2:
+            // Right Button
+            case 3:
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 2), Flags = MouseFlags.Button3Pressed });
+                Assert.False (menuBar.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 2), Flags = MouseFlags.Button3Released });
+                Assert.False (menuBar.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 2), Flags = MouseFlags.Button3Clicked });
+                Assert.False (menuBar.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+
+                break;
+        }
+
+        // ContextMenu
+        Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 2), Flags = cm.MouseFlags });
+        Assert.False (menuBar.IsMenuOpen);
+        Assert.True (cm.MenuBar!.IsMenuOpen);
+
+        switch (button)
+        {
+            // Left Button
+            case 1:
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 4), Flags = MouseFlags.Button1Pressed });
+                Assert.True (cm.MenuBar!.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 4), Flags = MouseFlags.Button1Released });
+                Assert.True (cm.MenuBar!.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 4), Flags = MouseFlags.Button1Clicked });
+                Assert.False (cm.MenuBar!.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.True (actionRaised);
+                actionRaised = false;
+
+                break;
+            // Middle Button
+            case 2:
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 4), Flags = MouseFlags.Button2Pressed });
+                Assert.False (cm.MenuBar!.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 4), Flags = MouseFlags.Button2Released });
+                Assert.False (cm.MenuBar!.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 4), Flags = MouseFlags.Button2Clicked });
+                Assert.False (cm.MenuBar!.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+
+                break;
+            // Right Button
+            case 3:
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 4), Flags = MouseFlags.Button3Pressed });
+                Assert.False (cm.MenuBar!.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 4), Flags = MouseFlags.Button3Released });
+                Assert.False (cm.MenuBar!.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+                Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 4), Flags = MouseFlags.Button3Clicked });
+                // MouseFlags is the same as cm.MouseFlags. So the context menu is closed and reopened again
+                Assert.True (cm.MenuBar!.IsMenuOpen);
+                Application.MainLoop.RunIteration ();
+                Assert.False (actionRaised);
+
+                break;
+        }
+
+        top.Dispose ();
+    }
+}

+ 28 - 0
Tests/UnitTests/Views/MenuBarTests.cs

@@ -3734,6 +3734,34 @@ Edit
         top.Dispose ();
     }
 
+    [Fact]
+    [AutoInitShutdown]
+    public void CanFocus_True_Key_Esc_Exit_Toplevel_If_IsMenuOpen_False ()
+    {
+        var menu = new MenuBar
+        {
+            Menus =
+            [
+                new ("File", new MenuItem [] { new ("New", "", null) })
+            ],
+            CanFocus = true
+        };
+        var top = new Toplevel ();
+        top.Add (menu);
+        Application.Begin (top);
+
+        Assert.True (menu.CanFocus);
+        Assert.True (menu.NewKeyDownEvent (menu.Key));
+        Assert.True (menu.IsMenuOpen);
+
+        Assert.True (menu.NewKeyDownEvent (Key.Esc));
+        Assert.False (menu.IsMenuOpen);
+
+        Assert.False (menu.NewKeyDownEvent (Key.Esc));
+        Assert.False (menu.IsMenuOpen);
+        top.Dispose ();
+    }
+
     // Defines the expected strings for a Menu. Currently supports 
     //   - MenuBar with any number of MenuItems 
     //   - Each top-level MenuItem can have a SINGLE sub-menu

+ 2 - 2
UICatalog/Scenarios/DynamicMenuBar.cs

@@ -414,10 +414,10 @@ public class DynamicMenuBar : Scenario
                                 };
 
             var dialog = new Dialog
-                { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel], Height = Dim.Auto (DimAutoStyle.Content, 22, Application.Screen.Height) };
+                { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel], Height = Dim.Auto (DimAutoStyle.Content, 23, Application.Screen.Height) };
 
             Width = Dim.Fill ();
-            Height = Dim.Fill () - 1;
+            Height = Dim.Fill () - 2;
             dialog.Add (this);
             TextTitle.SetFocus ();
             TextTitle.CursorPosition = TextTitle.Text.Length;

+ 1 - 1
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -204,7 +204,7 @@ public class DynamicStatusBar : Scenario
             var dialog = new Dialog { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel], Height = Dim.Auto (DimAutoStyle.Content, 17, Application.Screen.Height) };
 
             Width = Dim.Fill ();
-            Height = Dim.Fill () - 1;
+            Height = Dim.Fill () - 2;
             dialog.Add (this);
             TextTitle.SetFocus ();
             TextTitle.CursorPosition = TextTitle.Text.Length;