diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 979ad0b66..8685119bc 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -141,6 +141,12 @@ namespace Terminal.Gui { return CanExecute == null ? true : CanExecute (); } + // ┌──────────────────────────────┐ + // │ Quit Quit UI Catalog Ctrl+Q │ + // └──────────────────────────────┘ + // ┌─────────────────┐ + // │ ◌ TopLevel Alt+T │ + // └─────────────────┘ internal int Width => 1 + TitleLength + (Help.ConsoleWidth > 0 ? Help.ConsoleWidth + 2 : 0) + (Checked || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) + (ShortcutTag.ConsoleWidth > 0 ? ShortcutTag.ConsoleWidth + 2 : 0) + 2; @@ -459,6 +465,7 @@ namespace Terminal.Gui { return GetNormalColor (); } + // Draws the Menu, within the Frame public override void Redraw (Rect bounds) { Driver.SetAttribute (GetNormalColor ()); @@ -484,6 +491,7 @@ namespace Terminal.Gui { Driver.AddRune (Driver.HLine); else if (i == 0 && p == 0 && host.UseSubMenusSingleFrame && item.Parent.Parent != null) Driver.AddRune (Driver.LeftArrow); + // TODO: Change this `- 3` to a const (is it spacesAfterTitle?) else if (p == Frame.Width - 3 && barItems.SubMenu (barItems.Children [i]) != null) Driver.AddRune (Driver.RightArrow); else @@ -516,6 +524,7 @@ namespace Terminal.Gui { textToDraw = item.Title; } + // Draw the item. The `2` is for the left border and the space before the text ViewToScreen (2, i + 1, out int vtsCol, out _, false); if (vtsCol < Driver.Cols) { Move (2, i + 1); @@ -527,6 +536,7 @@ namespace Terminal.Gui { HotKeySpecifier = MenuBar.HotKeySpecifier, Text = textToDraw }; + // TODO: Change this `- 3` to a const (is it spacesAfterTitle?) tf.Draw (ViewToScreen (new Rect (2, i + 1, Frame.Width - 3, 1)), i == current ? ColorScheme.Focus : GetNormalColor (), i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal, @@ -1025,7 +1035,8 @@ namespace Terminal.Gui { } static int leftPadding = 1; - static int rightPadding = 1; + static int rightPadding = 1; + static int spaceAfterTitle = 3; /// public override void Redraw (Rect bounds) { @@ -1053,7 +1064,7 @@ namespace Terminal.Gui { normalColor = GetNormalColor (); } DrawHotString (menu.Help.IsEmpty ? $" {menu.Title} " : $" {menu.Title} ({menu.Help}) ", hotColor, normalColor); - pos += leftPadding + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? menu.Help.ConsoleWidth + 3 : 0) + rightPadding; + pos += leftPadding + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? menu.Help.ConsoleWidth + spaceAfterTitle : 0) + rightPadding; } PositionCursor (); } @@ -1076,7 +1087,7 @@ namespace Terminal.Gui { } return; } else { - pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 3 : 0) + rightPadding; + pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + spaceAfterTitle : 0)+ rightPadding; } } } @@ -1211,6 +1222,7 @@ namespace Terminal.Gui { int pos = 0; switch (subMenu) { case null: + // Open a submenu below a MenuBar lastFocused = lastFocused ?? (SuperView == null ? Application.Current.MostFocused : SuperView.MostFocused); if (openSubMenu != null && !CloseMenu (false, true)) return; @@ -1223,6 +1235,8 @@ namespace Terminal.Gui { openMenu.Dispose (); } + // This positions the submenu horizontally aligned with the first character of the + // menu it belongs to's text for (int i = 0; i < index; i++) pos += Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + 2; openMenu = new Menu (this, Frame.X + pos, Frame.Y + 1, Menus [index]); @@ -1237,6 +1251,7 @@ namespace Terminal.Gui { openMenu.SetFocus (); break; default: + // Opens a submenu next to another submenu (openSubMenu) if (openSubMenu == null) openSubMenu = new List (); if (sIndex > -1) { diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 895228942..8cd4f3dce 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -169,7 +169,7 @@ namespace UICatalog { _menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_Quit", "", () => Application.RequestStop(), null, null, Key.Q | Key.CtrlMask) + new MenuItem ("_Quit", "Quit UI Catalog", () => Application.RequestStop(), null, null, Key.Q | Key.CtrlMask) }), new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()), new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()), diff --git a/UnitTests/MenuTests.cs b/UnitTests/MenuTests.cs index 5f14632d1..549551fc6 100644 --- a/UnitTests/MenuTests.cs +++ b/UnitTests/MenuTests.cs @@ -1097,14 +1097,24 @@ Edit Assert.True (copyAction); } - // Defines the expected strings for a Menu + // 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 + // + // TODO: Enable multiple sub-menus + // TODO: Enable checked sub-menus + // TODO: Enable sub-menus with sub-menus (perhaps better to put this in a separate class with focused unit tests?) + // + // E.g: // // File Edit // New Copy - public class ExpectedMenu : MenuBar { + public class ExpectedMenuBar : MenuBar { FakeDriver d = ((FakeDriver)Application.Driver); - public string expectedMenuBarText { + // Each MenuBar title has a 1 space pad on each side + // See `static int leftPadding` and `static int rightPadding` on line 1037 of Menu.cs + public string MenuBarText { get { string txt = string.Empty; foreach (var m in Menus) { @@ -1116,10 +1126,11 @@ Edit } // The expected strings when the menu is closed - public string expectedClosed => expectedMenuBarText + "\n"; + public string ClosedMenuText => MenuBarText + "\n"; - // left padding for the sub menu - public string padding (int i) + // Padding for the X of the sub menu Frane + // Menu.cs - Line 1239 in `internal void OpenMenu` is where the Menu is created + string padding (int i) { int n = 0; while (i > 0){ @@ -1133,13 +1144,16 @@ Edit // "┌──────┐" // "│ New │" // "└──────┘" - // BUGBUG: The extra 4 spaces on these should not be there + // + // The width of the Frame is determined in Menu.cs line 144, where `Width` is calculated + // 1 space before the Title and 2 spaces after the Title/Check/Help public string expectedTopRow (int i) => $"{d.ULCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.URCorner} \n"; - public string expectedMenuItemRow (int i) => $"{d.VLine} {Menus [i].Children [0].Title} {d.VLine}" + " \n"; + // The 3 spaces at end are a result of Menu.cs line 1062 where `pos` is calculated (` + spacesAfterTitle`) + public string expectedMenuItemRow (int i) => $"{d.VLine} {Menus [i].Children [0].Title} {d.VLine} \n"; public string expectedBottomRow (int i) => $"{d.LLCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.LRCorner} \n"; // The fulll expected string for an open sub menu - public string expectedSubMenuOpen (int i) => expectedClosed + + public string expectedSubMenuOpen (int i) => ClosedMenuText + (Menus [i].Children.Length > 0 ? padding (i) + expectedTopRow (i) + padding (i) + expectedMenuItemRow (i) + @@ -1147,7 +1161,7 @@ Edit : ""); - public ExpectedMenu (MenuBarItem [] menus) : base (menus) + public ExpectedMenuBar (MenuBarItem [] menus) : base (menus) { } } @@ -1156,7 +1170,7 @@ Edit public void MenuBar_Submenus_Alignment_Correct () { // Define the expected menu - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("Really Long Sub Menu", "", null) }), @@ -1191,7 +1205,7 @@ Edit Application.Top.Add (menu); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); for (var i = 0; i < expectedMenu.Menus.Length; i++) { menu.OpenMenu (i); @@ -1208,7 +1222,7 @@ Edit var copyAction = false; // Define the expected menu - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("New", "", null) }), @@ -1255,7 +1269,7 @@ Edit public void MenuBar_Position_And_Size_With_HotKeys_Is_The_Same_As_Without_HotKeys () { // Define the expected menu - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("12", "", null) }), @@ -1292,7 +1306,7 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); Application.Top.Remove (menu); @@ -1324,14 +1338,14 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); } [Fact, AutoInitShutdown] public void MenuBar_ButtonPressed_Open_The_Menu_ButtonPressed_Again_Close_The_Menu () { // Define the expected menu - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("Open", "", null) }), @@ -1361,7 +1375,7 @@ Edit Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); } [Fact] @@ -1389,7 +1403,7 @@ Edit //└──────┘ └───────┘ // Define the expected menu - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("New", "", null) }), @@ -1436,7 +1450,7 @@ Edit Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (menu.IsMenuOpen); @@ -1449,13 +1463,13 @@ Edit Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); } [Fact, AutoInitShutdown] public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Keyboard () { - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("New", "", null) }), @@ -1517,7 +1531,7 @@ Edit Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); } } }