Add OpenMenu(Point?) overload for custom positioning

Co-authored-by: tig <585482+tig@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-11-22 01:42:29 +00:00
parent 40e777acc1
commit 50f960b40c
2 changed files with 74 additions and 4 deletions

View File

@@ -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;

View File

@@ -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 ();
}
}