From 7aae0c2ad5f597179a908ba556fe4e0e475f6150 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 13 Mar 2025 17:15:14 +0000 Subject: [PATCH] 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 --- Terminal.Gui/Views/Menu/ContextMenu.cs | 42 +++-- Terminal.Gui/Views/Menu/MenuBar.cs | 60 +++--- Tests/UnitTests/Views/ContextMenuTests.cs | 216 +++++++++++++++++++--- Tests/UnitTests/Views/MenuBarTests.cs | 28 +++ UICatalog/Scenarios/DynamicMenuBar.cs | 4 +- UICatalog/Scenarios/DynamicStatusBar.cs | 2 +- 6 files changed, 280 insertions(+), 72 deletions(-) diff --git a/Terminal.Gui/Views/Menu/ContextMenu.cs b/Terminal.Gui/Views/Menu/ContextMenu.cs index 7c6f2d623..90f06c675 100644 --- a/Terminal.Gui/Views/Menu/ContextMenu.cs +++ b/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 (); } diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 6084df0d6..416acb653 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/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; diff --git a/Tests/UnitTests/Views/ContextMenuTests.cs b/Tests/UnitTests/Views/ContextMenuTests.cs index c600fb30a..17a60e572 100644 --- a/Tests/UnitTests/Views/ContextMenuTests.cs +++ b/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 (); } -} \ No newline at end of file + + [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 (); + } +} diff --git a/Tests/UnitTests/Views/MenuBarTests.cs b/Tests/UnitTests/Views/MenuBarTests.cs index 19980ac18..e5428e7b0 100644 --- a/Tests/UnitTests/Views/MenuBarTests.cs +++ b/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 diff --git a/UICatalog/Scenarios/DynamicMenuBar.cs b/UICatalog/Scenarios/DynamicMenuBar.cs index 987ee9786..e2efbf3d2 100644 --- a/UICatalog/Scenarios/DynamicMenuBar.cs +++ b/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; diff --git a/UICatalog/Scenarios/DynamicStatusBar.cs b/UICatalog/Scenarios/DynamicStatusBar.cs index 7916e32d6..f610d947a 100644 --- a/UICatalog/Scenarios/DynamicStatusBar.cs +++ b/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;