diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index f446a1157..5b3d36f02 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -236,12 +236,33 @@ public class MenuBar : Menu, IDesignable /// The first menu item in the PopoverMenu will be selected and focused. /// /// - public bool OpenMenu () + public bool OpenMenu () { return OpenMenu (null); } + + /// + /// Opens the first menu item with a at the specified screen position. + /// This is useful for programmatically opening the menu, for example when using the MenuBar as a dropdown list. + /// + /// + /// The screen position at which to open the menu. If , the menu will be positioned + /// at the default location (bottom-left of the first MenuBarItem). + /// + /// if a menu was opened; otherwise. + /// + /// + /// This method activates the MenuBar and shows the first MenuBarItem that has a PopoverMenu. + /// The first menu item in the PopoverMenu will be selected and focused. + /// + /// + /// When using MenuBar as a dropdown button next to a TextField, you can position the menu + /// to align with the left edge of the TextField by passing the TextField's screen position. + /// + /// + public bool OpenMenu (Point? position) { if (SubViews.OfType ().FirstOrDefault (mbi => mbi.PopoverMenu is { }) is { } first) { Active = true; - ShowItem (first); + ShowItem (first, position); return true; } @@ -401,7 +422,11 @@ public class MenuBar : Menu, IDesignable /// Shows the specified popover, but only if the menu bar is active. /// /// - private void ShowItem (MenuBarItem? menuBarItem) + /// + /// The screen position at which to show the popover. If , the menu will be positioned + /// at the default location (bottom-left of the MenuBarItem). + /// + private void ShowItem (MenuBarItem? menuBarItem, Point? position = null) { // Logging.Debug ($"{Title} - {menuBarItem?.Id}"); @@ -447,7 +472,8 @@ public class MenuBar : Menu, IDesignable if (menuBarItem.PopoverMenu is { }) { menuBarItem.PopoverMenu.App ??= App; - menuBarItem.PopoverMenu.MakeVisible (new Point (menuBarItem.FrameToScreen ().X, menuBarItem.FrameToScreen ().Bottom)); + Point menuPosition = position ?? new Point (menuBarItem.FrameToScreen ().X, menuBarItem.FrameToScreen ().Bottom); + menuBarItem.PopoverMenu.MakeVisible (menuPosition); } menuBarItem.Accepting += OnMenuItemAccepted; diff --git a/Tests/UnitTests/Views/MenuBarTests.cs b/Tests/UnitTests/Views/MenuBarTests.cs index f67fcb779..25273bc9f 100644 --- a/Tests/UnitTests/Views/MenuBarTests.cs +++ b/Tests/UnitTests/Views/MenuBarTests.cs @@ -794,4 +794,48 @@ public class MenuBarTests () Application.End (rs); top.Dispose (); } + + [Fact] + [AutoInitShutdown] + public void OpenMenu_With_Position_Opens_At_Specified_Location () + { + // Arrange + var top = new Toplevel () + { + App = ApplicationImpl.Instance + }; + + var menuBar = new MenuBar () { Id = "menuBar", X = 10, Y = 0 }; + top.Add (menuBar); + + var menuItem1 = new MenuItem { Id = "menuItem1", Title = "Item _1" }; + var menu = new Menu ([menuItem1]) { Id = "menu" }; + var menuBarItem = new MenuBarItem { Id = "menuBarItem", Title = "_File" }; + var menuBarItemPopover = new PopoverMenu (); + + menuBar.Add (menuBarItem); + menuBarItem.PopoverMenu = menuBarItemPopover; + menuBarItemPopover.Root = menu; + + SessionToken rs = Application.Begin (top); + Assert.False (menuBar.Active); + Assert.False (menuBar.IsOpen ()); + + // Act - Open menu at custom position (0, 1) + Point customPosition = new Point (0, 1); + bool result = menuBar.OpenMenu (customPosition); + + // Assert + Assert.True (result); + Assert.True (menuBar.Active); + Assert.True (menuBar.IsOpen ()); + Assert.True (menuBarItem.PopoverMenu.Visible); + + // The menu's Root should be positioned at or near the custom position + // (GetMostVisibleLocationForSubMenu may adjust it to fit on screen) + Assert.NotNull (menuBarItemPopover.Root); + + Application.End (rs); + top.Dispose (); + } }