Browse Source

Add OpenMenu(Point?) overload for custom positioning

Co-authored-by: tig <[email protected]>
copilot-swe-agent[bot] 3 weeks ago
parent
commit
50f960b40c
2 changed files with 74 additions and 4 deletions
  1. 30 4
      Terminal.Gui/Views/Menu/MenuBar.cs
  2. 44 0
      Tests/UnitTests/Views/MenuBarTests.cs

+ 30 - 4
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.
     ///     </para>
     /// </remarks>
-    public bool OpenMenu ()
+    public bool OpenMenu () { return OpenMenu (null); }
+
+    /// <summary>
+    ///     Opens the first menu item with a <see cref="PopoverMenu"/> at the specified screen position. 
+    ///     This is useful for programmatically opening the menu, for example when using the MenuBar as a dropdown list.
+    /// </summary>
+    /// <param name="position">
+    ///     The screen position at which to open the menu. If <see langword="null"/>, the menu will be positioned
+    ///     at the default location (bottom-left of the first MenuBarItem).
+    /// </param>
+    /// <returns><see langword="true"/> if a menu was opened; <see langword="false"/> otherwise.</returns>
+    /// <remarks>
+    ///     <para>
+    ///         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.
+    ///     </para>
+    ///     <para>
+    ///         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.
+    ///     </para>
+    /// </remarks>
+    public bool OpenMenu (Point? position)
     {
         if (SubViews.OfType<MenuBarItem> ().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.
     /// </summary>
     /// <param name="menuBarItem"></param>
-    private void ShowItem (MenuBarItem? menuBarItem)
+    /// <param name="position">
+    ///     The screen position at which to show the popover. If <see langword="null"/>, the menu will be positioned
+    ///     at the default location (bottom-left of the MenuBarItem).
+    /// </param>
+    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;

+ 44 - 0
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 ();
+    }
 }