From ac5c7e5d9e5b74e232a2861928805f7e3571b2ad Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 31 Mar 2025 17:12:55 -0600 Subject: [PATCH] Partially Fixes #2975 - Replaces old `ContextMenu` with new `Bar/Shortcut` based implementation (#4008) * touching publish.yml * Nuked ContextMenuv2 - use PopverMenu instead * WIP context menu stuff * More robust dispose * Removed ConextMenu; use PopoverMenu instead * Code cleanup * Code cleanup2 --- .../Application/Application.Initialization.cs | 3 + Terminal.Gui/Application/Application.Run.cs | 2 +- Terminal.Gui/Application/Application.cs | 1 + .../Application/ApplicationPopover.cs | 68 +- Terminal.Gui/Application/PopoverBaseImpl.cs | 32 +- .../ConsoleDrivers/V2/ApplicationV2.cs | 4 + .../Drawing/Color/ColorScheme.Colors.cs | 2 +- Terminal.Gui/Resources/config.json | 2 +- Terminal.Gui/Terminal.Gui.csproj | 2 +- Terminal.Gui/Views/CharMap/CharMap.cs | 42 +- Terminal.Gui/Views/FileDialog.cs | 189 +- Terminal.Gui/Views/Menu/ContextMenu.cs | 283 --- Terminal.Gui/Views/Menu/ContextMenuv2.cs | 104 - Terminal.Gui/Views/Menu/MenuBarItemv2.cs | 2 +- Terminal.Gui/Views/Menu/MenuBarv2.cs | 174 +- Terminal.Gui/Views/Menu/MenuItemv2.cs | 21 +- Terminal.Gui/Views/Menu/Menuv2.cs | 62 +- Terminal.Gui/Views/Menu/PopoverMenu.cs | 184 +- Terminal.Gui/Views/{Menu => Menuv1}/Menu.cs | 3 +- .../Views/{Menu => Menuv1}/MenuBar.cs | 1 - .../Views/{Menu => Menuv1}/MenuBarItem.cs | 0 .../{Menu => Menuv1}/MenuClosingEventArgs.cs | 0 .../Views/{Menu => Menuv1}/MenuItem.cs | 2 +- .../{Menu => Menuv1}/MenuItemCheckStyle.cs | 0 .../{Menu => Menuv1}/MenuOpenedEventArgs.cs | 0 .../{Menu => Menuv1}/MenuOpeningEventArgs.cs | 0 Terminal.Gui/Views/TextField.cs | 10 +- Terminal.Gui/Views/TextView.cs | 22 +- TerminalGuiFluentTesting/GuiTestContext.cs | 42 +- .../FluentTests/BasicFluentAssertionTests.cs | 56 +- .../Application/ApplicationPopoverTests.cs | 420 +--- Tests/UnitTests/Views/ContextMenuTests.cs | 2218 ----------------- Tests/UnitTests/Views/TextViewTests.cs | 2 +- .../Application/ApplicationPopoverTests.cs | 18 +- UICatalog/Scenarios/ContextMenus.cs | 4 +- UICatalog/Scenarios/MenusV2.cs | 18 - UICatalog/Scenarios/Notepad.cs | 44 +- UICatalog/Scenarios/TableEditor.cs | 65 +- UICatalog/Scenarios/TreeViewFileSystem.cs | 326 ++- docfx/docs/migratingfromv1.md | 18 + 40 files changed, 714 insertions(+), 3732 deletions(-) delete mode 100644 Terminal.Gui/Views/Menu/ContextMenu.cs delete mode 100644 Terminal.Gui/Views/Menu/ContextMenuv2.cs rename Terminal.Gui/Views/{Menu => Menuv1}/Menu.cs (99%) rename Terminal.Gui/Views/{Menu => Menuv1}/MenuBar.cs (99%) rename Terminal.Gui/Views/{Menu => Menuv1}/MenuBarItem.cs (100%) rename Terminal.Gui/Views/{Menu => Menuv1}/MenuClosingEventArgs.cs (100%) rename Terminal.Gui/Views/{Menu => Menuv1}/MenuItem.cs (99%) rename Terminal.Gui/Views/{Menu => Menuv1}/MenuItemCheckStyle.cs (100%) rename Terminal.Gui/Views/{Menu => Menuv1}/MenuOpenedEventArgs.cs (100%) rename Terminal.Gui/Views/{Menu => Menuv1}/MenuOpeningEventArgs.cs (100%) delete mode 100644 Tests/UnitTests/Views/ContextMenuTests.cs diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs index 7093b8702..30f2f5821 100644 --- a/Terminal.Gui/Application/Application.Initialization.cs +++ b/Terminal.Gui/Application/Application.Initialization.cs @@ -82,7 +82,10 @@ public static partial class Application // Initialization (Init/Shutdown) ResetState (ignoreDisposed: true); } + Debug.Assert (Navigation is null); Navigation = new (); + + Debug.Assert(Popover is null); Popover = new (); // For UnitTests diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 4412f5d23..f4a4cf44e 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -564,7 +564,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) { ArgumentNullException.ThrowIfNull (runState); - Popover?.HidePopover (Popover?.GetActivePopover ()); + Popover?.Hide (Popover?.GetActivePopover ()); runState.Toplevel.OnUnloaded (); diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 5d01106ae..07bec47c7 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -153,6 +153,7 @@ public static partial class Application { popover.Visible = false; } + Popover?.Dispose (); Popover = null; TopLevels.Clear (); diff --git a/Terminal.Gui/Application/ApplicationPopover.cs b/Terminal.Gui/Application/ApplicationPopover.cs index 9c0b94462..bc4a0e010 100644 --- a/Terminal.Gui/Application/ApplicationPopover.cs +++ b/Terminal.Gui/Application/ApplicationPopover.cs @@ -1,13 +1,12 @@ #nullable enable -using System.Diagnostics; - namespace Terminal.Gui; /// -/// Helper class for support of views for . Held by +/// Helper class for support of views for . Held by +/// /// -public class ApplicationPopover +public sealed class ApplicationPopover : IDisposable { /// /// Initializes a new instance of the class. @@ -16,27 +15,41 @@ public class ApplicationPopover private readonly List _popovers = []; - /// + /// + /// Gets the list of popovers registered with the application. + /// public IReadOnlyCollection Popovers => _popovers.AsReadOnly (); /// /// Registers with the application. - /// This enables the popover to receive keyboard events even when when it is not active. + /// This enables the popover to receive keyboard events even when it is not active. /// + /// + /// When a popover is registered, the View instance lifetime is managed by the application. Call + /// + /// to manage the lifetime of the popover directly. + /// /// - public void Register (IPopover? popover) + /// , after it has been registered. + public IPopover? Register (IPopover? popover) { if (popover is { } && !_popovers.Contains (popover)) { _popovers.Add (popover); - } + + return popover; } /// /// De-registers with the application. Use this to remove the popover and it's /// keyboard bindings from the application. /// + /// + /// When a popover is registered, the View instance lifetime is managed by the application. Call + /// + /// to manage the lifetime of the popover directly. + /// /// /// public bool DeRegister (IPopover? popover) @@ -61,22 +74,25 @@ public class ApplicationPopover /// /// Gets the active popover, if any. /// + /// + /// Note, the active pop over does not necessarily to be registered with the application. + /// /// public IPopover? GetActivePopover () { return _activePopover; } /// - /// Shows . IPopover implementations should use OnVisibleChnaged/VisibleChanged to be + /// Shows . IPopover implementations should use OnVisibleChanaged/VisibleChanged to be /// notified when the user has done something to cause the popover to be hidden. /// /// /// - /// Note, this API calls . To disable the popover from processing keyboard events, + /// This API calls . To disable the popover from processing keyboard events, /// either call to /// remove the popover from the application or set to . /// /// /// - public void ShowPopover (IPopover? popover) + public void Show (IPopover? popover) { // If there's an existing popover, hide it. if (_activePopover is View popoverView) @@ -87,8 +103,6 @@ public class ApplicationPopover if (popover is View newPopover) { - Register (popover); - if (!newPopover.IsInitialized) { newPopover.BeginInit (); @@ -103,10 +117,11 @@ public class ApplicationPopover /// /// Causes the specified popover to be hidden. - /// If the popover is dervied from , this is the same as setting to . + /// If the popover is dervied from , this is the same as setting + /// to . /// /// - public void HidePopover (IPopover? popover) + public void Hide (IPopover? popover) { // If there's an existing popover, hide it. if (_activePopover is View popoverView && popoverView == popover) @@ -117,7 +132,6 @@ public class ApplicationPopover } } - /// /// Called when the user presses a key. Dispatches the key to the active popover, if any, /// otherwise to the popovers in the order they were registered. Inactive popovers only get hotkeys. @@ -127,9 +141,11 @@ public class ApplicationPopover internal bool DispatchKeyDown (Key key) { // Do active first - Active gets all key down events. - if (GetActivePopover () as View is { Visible: true } visiblePopover) + var activePopover = GetActivePopover () as View; + + if (activePopover is { Visible: true }) { - if (visiblePopover.NewKeyDownEvent (key)) + if (activePopover.NewKeyDownEvent (key)) { return true; } @@ -141,7 +157,7 @@ public class ApplicationPopover foreach (IPopover popover in _popovers) { - if (GetActivePopover () == popover || popover is not View popoverView) + if (popover == activePopover || popover is not View popoverView) { continue; } @@ -157,4 +173,18 @@ public class ApplicationPopover return hotKeyHandled is true; } + + /// + public void Dispose () + { + foreach (IPopover popover in _popovers) + { + if (popover is View view) + { + view.Dispose (); + } + } + + _popovers.Clear (); + } } diff --git a/Terminal.Gui/Application/PopoverBaseImpl.cs b/Terminal.Gui/Application/PopoverBaseImpl.cs index fce7d7866..64b90532c 100644 --- a/Terminal.Gui/Application/PopoverBaseImpl.cs +++ b/Terminal.Gui/Application/PopoverBaseImpl.cs @@ -6,19 +6,18 @@ namespace Terminal.Gui; /// /// /// -/// To show a Popover, use . To hide a popover, -/// call with set to . +/// To show a Popover, use . To hide a popover, +/// call with set to . /// /// -/// If the user clicks anywhere not occulded by a SubView of the Popover, presses , +/// If the user clicks anywhere not occluded by a SubView of the Popover, presses , /// or causes another popover to show, the Popover will be hidden. /// /// - public abstract class PopoverBaseImpl : View, IPopover { /// - /// + /// Creates a new PopoverBaseImpl. /// protected PopoverBaseImpl () { @@ -28,10 +27,10 @@ public abstract class PopoverBaseImpl : View, IPopover Height = Dim.Fill (); ViewportSettings = ViewportSettings.Transparent | ViewportSettings.TransparentMouse; - //// TODO: Add a diagnostic setting for this? - TextFormatter.VerticalAlignment = Alignment.End; - TextFormatter.Alignment = Alignment.End; - base.Text = "popover"; + // TODO: Add a diagnostic setting for this? + //TextFormatter.VerticalAlignment = Alignment.End; + //TextFormatter.Alignment = Alignment.End; + //base.Text = "popover"; AddCommand (Command.Quit, Quit); KeyBindings.Add (Application.QuitKey, Command.Quit); @@ -55,24 +54,13 @@ public abstract class PopoverBaseImpl : View, IPopover protected override bool OnVisibleChanging () { bool ret = base.OnVisibleChanging (); - if (!ret & !Visible) + if (!ret && !Visible) { - // Whenvver visible is changing to true, we need to resize; + // Whenever visible is changing to true, we need to resize; // it's our only chance because we don't get laid out until we're visible Layout (Application.Screen.Size); } return ret; } - - // TODO: Pretty sure this is not needed. set_Visible SetFocus already - ///// - //protected override void OnVisibleChanged () - //{ - // base.OnVisibleChanged (); - // if (Visible) - // { - // //SetFocus (); - // } - //} } diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs index 9baeba301..e6461b144 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs @@ -1,5 +1,6 @@ #nullable enable using System.Collections.Concurrent; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; @@ -63,7 +64,10 @@ public class ApplicationV2 : ApplicationImpl _driverName = driverName; } + Debug.Assert(Application.Navigation is null); Application.Navigation = new (); + + Debug.Assert (Application.Popover is null); Application.Popover = new (); Application.AddKeyBindings (); diff --git a/Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs b/Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs index 5b8b130fd..a311032de 100644 --- a/Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs +++ b/Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs @@ -44,7 +44,7 @@ public sealed class Colors : INotifyCollectionChanged, IDictionary /// Menu /// - /// The menu color scheme; used for , , and + /// The menu color scheme; used for , , and /// . /// /// diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json index 31752d43b..230533b29 100644 --- a/Terminal.Gui/Resources/config.json +++ b/Terminal.Gui/Resources/config.json @@ -33,7 +33,7 @@ // --------------- View Specific Settings --------------- - "ContextMenu.DefaultKey": "Shift+F10", + "PopoverMenu.DefaultKey": "Shift+F10", "FileDialog.MaxSearchResults": 10000, "FileDialogStyle.DefaultUseColors": false, "FileDialogStyle.DefaultUseUnicodeCharacters": false, diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index d8efda92d..67444b57c 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -81,7 +81,7 @@ - + diff --git a/Terminal.Gui/Views/CharMap/CharMap.cs b/Terminal.Gui/Views/CharMap/CharMap.cs index e15a732dc..a6753dfa7 100644 --- a/Terminal.Gui/Views/CharMap/CharMap.cs +++ b/Terminal.Gui/Views/CharMap/CharMap.cs @@ -18,8 +18,6 @@ public class CharMap : View, IDesignable private const int HEADER_HEIGHT = 1; // Height of the header private int _rowHeight = 1; // Height of each row of 16 glyphs - changing this is not tested - private ContextMenu _contextMenu = new (); - /// /// Initializes a new instance. /// @@ -58,7 +56,7 @@ public class CharMap : View, IDesignable KeyBindings.Add (Key.PageDown, Command.PageDown); KeyBindings.Add (Key.Home, Command.Start); KeyBindings.Add (Key.End, Command.End); - KeyBindings.Add (ContextMenu.DefaultKey, Command.Context); + KeyBindings.Add (PopoverMenu.DefaultKey, Command.Context); MouseBindings.Add (MouseFlags.Button1DoubleClicked, Command.Accept); MouseBindings.ReplaceCommands (MouseFlags.Button3Clicked, Command.Context); @@ -505,32 +503,20 @@ public class CharMap : View, IDesignable SelectedCodePoint = newCodePoint; - _contextMenu = new () - { - Position = ViewportToScreen (GetCursor (SelectedCodePoint)) - }; + // This demonstrates how to create an ephemeral Popover; one that exists + // ony as long as the popover is visible. + // Note, for ephemeral Popovers, hotkeys are not supported. + PopoverMenu? contextMenu = new ( + [ + new (Strings.charMapCopyGlyph, string.Empty, CopyGlyph), + new (Strings.charMapCopyCP, string.Empty, CopyCodePoint) + ]); - MenuBarItem menuItems = new ( - [ - new ( - Strings.charMapCopyGlyph, - "", - CopyGlyph, - null, - null, - (KeyCode)Key.G.WithCtrl - ), - new ( - Strings.charMapCopyCP, - "", - CopyCodePoint, - null, - null, - (KeyCode)Key.P.WithCtrl - ) - ] - ); - _contextMenu.Show (menuItems); + // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused + // and the context menu is disposed when it is closed. + Application.Popover?.Register (contextMenu); + + contextMenu?.MakeVisible (ViewportToScreen (GetCursor (SelectedCodePoint))); return true; } diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index b21da3d12..cdeb939a8 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -70,14 +70,15 @@ public class FileDialog : Dialog, IDesignable Canceled = true; _fileSystem = fileSystem; - Style = new FileDialogStyle (fileSystem); + Style = new (fileSystem); - _btnOk = new Button + _btnOk = new() { X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, alignmentGroupComplete), Y = Pos.AnchorEnd (), IsDefault = true, Text = Style.OkButtonText }; + _btnOk.Accepting += (s, e) => { if (e.Cancel) @@ -88,8 +89,7 @@ public class FileDialog : Dialog, IDesignable Accept (true); }; - - _btnCancel = new Button + _btnCancel = new() { X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, alignmentGroupComplete), Y = Pos.AnchorEnd (), @@ -97,30 +97,31 @@ public class FileDialog : Dialog, IDesignable }; _btnCancel.Accepting += (s, e) => - { - if (e.Cancel) - { - return; - } - if (Modal) - { - Application.RequestStop (); - } - }; + { + if (e.Cancel) + { + return; + } - _btnUp = new Button { X = 0, Y = 1, NoPadding = true }; + if (Modal) + { + Application.RequestStop (); + } + }; + + _btnUp = new() { X = 0, Y = 1, NoPadding = true }; _btnUp.Text = GetUpButtonText (); _btnUp.Accepting += (s, e) => _history.Up (); - _btnBack = new Button { X = Pos.Right (_btnUp) + 1, Y = 1, NoPadding = true }; + _btnBack = new() { X = Pos.Right (_btnUp) + 1, Y = 1, NoPadding = true }; _btnBack.Text = GetBackButtonText (); _btnBack.Accepting += (s, e) => _history.Back (); - _btnForward = new Button { X = Pos.Right (_btnBack) + 1, Y = 1, NoPadding = true }; + _btnForward = new() { X = Pos.Right (_btnBack) + 1, Y = 1, NoPadding = true }; _btnForward.Text = GetForwardButtonText (); _btnForward.Accepting += (s, e) => _history.Forward (); - _tbPath = new TextField { Width = Dim.Fill (), CaptionColor = new Color (Color.Black) }; + _tbPath = new() { Width = Dim.Fill (), CaptionColor = new (Color.Black) }; _tbPath.KeyDown += (s, k) => { @@ -134,12 +135,12 @@ public class FileDialog : Dialog, IDesignable _tbPath.Autocomplete = new AppendAutocomplete (_tbPath); _tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator (); - _splitContainer = new TileView + _splitContainer = new() { X = 0, Y = Pos.Bottom (_btnBack), Width = Dim.Fill (), - Height = Dim.Fill (Dim.Func (() => IsInitialized ? _btnOk.Frame.Height : 1)), + Height = Dim.Fill (Dim.Func (() => IsInitialized ? _btnOk.Frame.Height : 1)) }; Initialized += (s, e) => @@ -150,7 +151,7 @@ public class FileDialog : Dialog, IDesignable // this.splitContainer.Border.BorderStyle = BorderStyle.None; - _tableView = new TableView + _tableView = new() { Width = Dim.Fill (), Height = Dim.Fill (), @@ -178,7 +179,7 @@ public class FileDialog : Dialog, IDesignable typeStyle.MinWidth = 6; typeStyle.ColorGetter = ColorGetter; - _treeView = new TreeView { Width = Dim.Fill (), Height = Dim.Fill () }; + _treeView = new() { Width = Dim.Fill (), Height = Dim.Fill () }; var fileDialogTreeBuilder = new FileSystemTreeBuilder (); _treeView.TreeBuilder = fileDialogTreeBuilder; @@ -190,31 +191,33 @@ public class FileDialog : Dialog, IDesignable _splitContainer.Tiles.ElementAt (0).ContentView.Add (_treeView); _splitContainer.Tiles.ElementAt (1).ContentView.Add (_tableView); - _btnToggleSplitterCollapse = new Button + _btnToggleSplitterCollapse = new() { X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput), Y = Pos.AnchorEnd (), Text = GetToggleSplitterText (false) }; _btnToggleSplitterCollapse.Accepting += (s, e) => - { - Tile tile = _splitContainer.Tiles.ElementAt (0); + { + Tile tile = _splitContainer.Tiles.ElementAt (0); - bool newState = !tile.ContentView.Visible; - tile.ContentView.Visible = newState; - _btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState); - SetNeedsLayout (); - }; + bool newState = !tile.ContentView.Visible; + tile.ContentView.Visible = newState; + _btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState); + SetNeedsLayout (); + }; - _tbFind = new TextField + _tbFind = new() { X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput), - CaptionColor = new Color (Color.Black), + CaptionColor = new (Color.Black), Width = 30, Y = Pos.Top (_btnToggleSplitterCollapse), HotKey = Key.F.WithAlt }; - _spinnerView = new SpinnerView { X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput), Y = Pos.AnchorEnd (1), Visible = false }; + + _spinnerView = new() + { X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput), Y = Pos.AnchorEnd (1), Visible = false }; _tbFind.TextChanged += (s, o) => RestartSearch (); @@ -242,7 +245,7 @@ public class FileDialog : Dialog, IDesignable _tableView.Style.ShowHorizontalHeaderUnderline = true; _tableView.Style.ShowHorizontalScrollIndicators = true; - _history = new FileDialogHistory (this); + _history = new (this); _tbPath.TextChanged += (s, e) => PathChanged (); @@ -398,10 +401,10 @@ public class FileDialog : Dialog, IDesignable Move (0, Viewport.Height / 2); - SetAttribute (new Attribute (Color.Red, ColorScheme.Normal.Background)); - Driver.AddStr (new string (' ', feedbackPadLeft)); + SetAttribute (new (Color.Red, ColorScheme.Normal.Background)); + Driver.AddStr (new (' ', feedbackPadLeft)); Driver.AddStr (_feedback); - Driver.AddStr (new string (' ', feedbackPadRight)); + Driver.AddStr (new (' ', feedbackPadRight)); } return true; @@ -430,9 +433,9 @@ public class FileDialog : Dialog, IDesignable _tbPath.Caption = Style.PathCaption; _tbFind.Caption = Style.SearchCaption; - _tbPath.Autocomplete.ColorScheme = new ColorScheme (_tbPath.ColorScheme) + _tbPath.Autocomplete.ColorScheme = new (_tbPath.ColorScheme) { - Normal = new Attribute (Color.Black, _tbPath.ColorScheme.Normal.Background) + Normal = new (Color.Black, _tbPath.ColorScheme.Normal.Background) }; _treeRoots = Style.TreeRootGetter (); @@ -449,18 +452,18 @@ public class FileDialog : Dialog, IDesignable // Fiddle factor int width = AllowedTypes.Max (a => a.ToString ().Length) + 6; - _allowedTypeMenu = new MenuBarItem ( - "", - _allowedTypeMenuItems = AllowedTypes.Select ( - (a, i) => new MenuItem ( - a.ToString (), - null, - () => { AllowedTypeMenuClicked (i); }) - ) - .ToArray () - ); + _allowedTypeMenu = new ( + "", + _allowedTypeMenuItems = AllowedTypes.Select ( + (a, i) => new MenuItem ( + a.ToString (), + null, + () => { AllowedTypeMenuClicked (i); }) + ) + .ToArray () + ); - _allowedTypeMenuBar = new MenuBar + _allowedTypeMenuBar = new() { Width = width, Y = 1, @@ -476,10 +479,10 @@ public class FileDialog : Dialog, IDesignable // TODO: Using v1's menu bar here is a hack. Need to upgrade this. _allowedTypeMenuBar.DrawingContent += (s, e) => - { - _allowedTypeMenuBar.Move (e.NewViewport.Width - 1, 0); - Driver.AddRune (Glyphs.DownArrow); - }; + { + _allowedTypeMenuBar.Move (e.NewViewport.Width - 1, 0); + Driver.AddRune (Glyphs.DownArrow); + }; Add (_allowedTypeMenuBar); } @@ -803,12 +806,12 @@ public class FileDialog : Dialog, IDesignable var black = new Color (Color.Black); // TODO: Add some kind of cache for this - return new ColorScheme + return new() { - Normal = new Attribute (color, black), - HotNormal = new Attribute (color, black), - Focus = new Attribute (black, color), - HotFocus = new Attribute (black, color) + Normal = new (color, black), + HotNormal = new (color, black), + Focus = new (black, color), + HotFocus = new (black, color) }; } @@ -895,7 +898,7 @@ public class FileDialog : Dialog, IDesignable private string GetToggleSplitterText (bool isExpanded) { return isExpanded - ? new string ((char)Glyphs.LeftArrow.Value, 2) + ? new ((char)Glyphs.LeftArrow.Value, 2) : new string ((char)Glyphs.RightArrow.Value, 2); } @@ -1225,49 +1228,48 @@ public class FileDialog : Dialog, IDesignable return; } - var contextMenu = new ContextMenu - { - Position = new Point (e.Position.X + 1, e.Position.Y + 1) - }; + PopoverMenu? contextMenu = new ( + [ + new (Strings.fdCtxNew, string.Empty, New), + new (Strings.fdCtxRename, string.Empty, Rename), + new (Strings.fdCtxDelete, string.Empty, Delete) + ]); - var menuItems = new MenuBarItem ( - [ - new MenuItem (Strings.fdCtxNew, string.Empty, New), - new MenuItem (Strings.fdCtxRename, string.Empty, Rename), - new MenuItem (Strings.fdCtxDelete, string.Empty, Delete) - ] - ); _tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false); - contextMenu.Show (menuItems); + // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused + // and the context menu is disposed when it is closed. + Application.Popover?.Register (contextMenu); + + contextMenu?.MakeVisible (e.ScreenPosition); } private void ShowHeaderContextMenu (int clickedCol, MouseEventArgs e) { string sort = GetProposedNewSortOrder (clickedCol, out bool isAsc); - var contextMenu = new ContextMenu - { - Position = new Point (e.Position.X + 1, e.Position.Y + 1) - }; + PopoverMenu? contextMenu = new ( + [ + new ( + string.Format ( + Strings.fdCtxHide, + StripArrows (_tableView.Table.ColumnNames [clickedCol]) + ), + string.Empty, + () => HideColumn (clickedCol) + ), + new ( + StripArrows (sort), + string.Empty, + () => SortColumn (clickedCol, isAsc)) + ] + ); - var menuItems = new MenuBarItem ( - [ - new MenuItem ( - string.Format ( - Strings.fdCtxHide, - StripArrows (_tableView.Table.ColumnNames [clickedCol]) - ), - string.Empty, - () => HideColumn (clickedCol) - ), - new MenuItem ( - StripArrows (sort), - string.Empty, - () => SortColumn (clickedCol, isAsc)) - ] - ); - contextMenu.Show (menuItems); + // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused + // and the context menu is disposed when it is closed. + Application.Popover?.Register (contextMenu); + + contextMenu?.MakeVisible (e.ScreenPosition); } private void SortColumn (int clickedCol) @@ -1618,6 +1620,7 @@ public class FileDialog : Dialog, IDesignable { Modal = false; OnLoaded (); + return true; } } diff --git a/Terminal.Gui/Views/Menu/ContextMenu.cs b/Terminal.Gui/Views/Menu/ContextMenu.cs deleted file mode 100644 index 90f06c675..000000000 --- a/Terminal.Gui/Views/Menu/ContextMenu.cs +++ /dev/null @@ -1,283 +0,0 @@ -#nullable enable - -namespace Terminal.Gui; - -/// -/// ContextMenu provides a pop-up menu that can be positioned anywhere within a . ContextMenu is -/// analogous to and, once activated, works like a sub-menu of a (but -/// can be positioned anywhere). -/// -/// By default, a ContextMenu with sub-menus is displayed in a cascading manner, where each sub-menu pops out of -/// the ContextMenu frame (either to the right or left, depending on where the ContextMenu is relative to the edge -/// of the screen). By setting to , this behavior can be -/// changed such that all sub-menus are drawn within the ContextMenu frame. -/// -/// -/// ContextMenus can be activated using the Shift-F10 key (by default; use the to change to -/// another key). -/// -/// -/// Callers can cause the ContextMenu to be activated on a right-mouse click (or other interaction) by calling -/// . -/// -/// ContextMenus are located using screen coordinates and appear above all other Views. -/// -public sealed class ContextMenu : IDisposable -{ - private static MenuBar? _menuBar; - - private Toplevel? _container; - private Key _key = DefaultKey; - private MouseFlags _mouseFlags = MouseFlags.Button3Clicked; - - /// Initializes a context menu with no menu items. - public ContextMenu () - { - if (IsShow) - { - Hide (); - IsShow = false; - } - } - - /// The default shortcut key for activating the context menu. - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static Key DefaultKey { get; set; } = Key.F10.WithShift; - - /// - /// Sets or gets whether the context menu be forced to the right, ensuring it is not clipped, if the x position is - /// less than zero. The default is which means the context menu will be forced to the right. If - /// set to , the context menu will be clipped on the left if x is less than zero. - /// - public bool ForceMinimumPosToZero { get; set; } = true; - - /// The host which position will be used, otherwise if it's null the container will be used. - public View? Host { get; set; } - - /// Gets whether the ContextMenu is showing or not. - public static bool IsShow { get; private set; } - - /// Specifies the key that will activate the context menu. - public Key Key - { - get => _key; - set - { - Key oldKey = _key; - _key = value; - KeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, _key)); - } - } - - /// Gets the that is hosting this context menu. - public MenuBar? MenuBar => _menuBar; - - /// Gets or sets the menu items for this context menu. - public MenuBarItem? MenuItems { get; private set; } - - /// specifies the mouse action used to activate the context menu by mouse. - public MouseFlags MouseFlags - { - get => _mouseFlags; - set - { - MouseFlags oldFlags = _mouseFlags; - _mouseFlags = value; - MouseFlagsChanged?.Invoke (this, new MouseFlagsChangedEventArgs (oldFlags, value)); - } - } - - /// Gets or sets the menu position. - public Point Position { get; set; } - - /// - /// Gets or sets if sub-menus will be displayed using a "single frame" menu style. If , the - /// ContextMenu and any sub-menus that would normally cascade will be displayed within a single frame. If - /// (the default), sub-menus will cascade using separate frames for each level of the menu - /// hierarchy. - /// - public bool UseSubMenusSingleFrame { get; set; } - - /// Disposes the context menu object. - public void Dispose () - { - if (_menuBar is { }) - { - _menuBar.MenuAllClosed -= MenuBar_MenuAllClosed; - _container?.Remove (_menuBar); - } - Application.UngrabMouse (); - _menuBar?.Dispose (); - _menuBar = null; - IsShow = false; - - if (_container is { }) - { - _container.Closing -= Container_Closing; - _container.Deactivate -= Container_Deactivate; - _container.Disposing -= Container_Disposing; - } - } - - /// Hides (closes) the ContextMenu. - public void Hide () - { - RemoveKeyBindings (MenuItems); - _menuBar?.CleanUp (); - IsShow = false; - } - - private void RemoveKeyBindings (MenuBarItem? menuBarItem) - { - if (menuBarItem is null) - { - return; - } - - foreach (MenuItem? menuItem in menuBarItem.Children!) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (menuItem is null) - { - continue; - } - - if (menuItem is MenuBarItem barItem) - { - RemoveKeyBindings (barItem); - } - else - { - if (menuItem.ShortcutKey != Key.Empty) - { - // Remove an existent ShortcutKey - _menuBar?.HotKeyBindings.Remove (menuItem.ShortcutKey!); - } - } - } - } - - /// Event invoked when the is changed. - public event EventHandler? KeyChanged; - - /// Event invoked when the is changed. - public event EventHandler? MouseFlagsChanged; - - /// Shows (opens) the ContextMenu, displaying the s it contains. - public void Show (MenuBarItem? menuItems) - { - if (_menuBar is { }) - { - Hide (); - Dispose (); - } - - if (menuItems is null || menuItems.Children!.Length == 0) - { - return; - } - - MenuItems = menuItems; - _container = GetTopSuperView (Host); - _container!.Closing += Container_Closing; - _container.Deactivate += Container_Deactivate; - _container.Disposing += Container_Disposing; - Rectangle viewport = _container.Viewport; - Point position = Position; - - if (Host is { }) - { - Point pos = Host.Frame.Location; - pos.Y += Host.Frame.Height > 0 ? Host.Frame.Height - 1 : 0; - - if (position != pos) - { - Position = position = pos; - } - } - - Rectangle rect = Menu.MakeFrame (position.X, position.Y, MenuItems.Children); - - if (rect.Right >= viewport.Right) - { - if (viewport.Right - rect.Width >= 0 || !ForceMinimumPosToZero) - { - position.X = viewport.Right - rect.Width; - } - else if (ForceMinimumPosToZero) - { - position.X = 0; - } - } - else if (ForceMinimumPosToZero && position.X < 0) - { - position.X = 0; - } - - if (rect.Bottom >= viewport.Bottom) - { - if (viewport.Bottom - rect.Height - 1 >= 0 || !ForceMinimumPosToZero) - { - if (Host is null) - { - position.Y = viewport.Bottom - rect.Height - 1; - } - else - { - Point pos = Host.Frame.Location; - position.Y = pos.Y - rect.Height - 1; - } - } - else if (ForceMinimumPosToZero) - { - position.Y = 0; - } - } - else if (ForceMinimumPosToZero && position.Y < 0) - { - position.Y = 0; - } - - _menuBar = new MenuBar - { - X = position.X, - Y = position.Y, - Width = 0, - Height = 0, - UseSubMenusSingleFrame = UseSubMenusSingleFrame, - Key = Key, - Menus = [MenuItems] - }; - - _menuBar._isContextMenuLoading = true; - _menuBar.MenuAllClosed += MenuBar_MenuAllClosed; - - _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 (); } - - private void MenuBar_MenuAllClosed (object? sender, EventArgs e) { Hide (); } -} diff --git a/Terminal.Gui/Views/Menu/ContextMenuv2.cs b/Terminal.Gui/Views/Menu/ContextMenuv2.cs deleted file mode 100644 index 994aec4a0..000000000 --- a/Terminal.Gui/Views/Menu/ContextMenuv2.cs +++ /dev/null @@ -1,104 +0,0 @@ -#nullable enable - -using System.Diagnostics; - -namespace Terminal.Gui; - -/// -/// ContextMenuv2 provides a Popover menu that can be positioned anywhere within a . -/// -/// To show the ContextMenu, set to the ContextMenu object and set -/// property to . -/// -/// -/// The menu will be hidden when the user clicks outside the menu or when the user presses . -/// -/// -/// To explicitly hide the menu, set property to . -/// -/// -/// is the key used to activate the ContextMenus (Shift+F10 by default). Callers can use this in -/// their keyboard handling code. -/// -/// The menu will be displayed at the current mouse coordinates. -/// -public class ContextMenuv2 : PopoverMenu, IDesignable -{ - - /// - /// The mouse flags that will trigger the context menu. The default is which is typically the right mouse button. - /// - public MouseFlags MouseFlags { get; set; } = MouseFlags.Button3Clicked; - - /// Initializes a context menu with no menu items. - public ContextMenuv2 () : this ([]) { } - - /// - public ContextMenuv2 (Menuv2? menu) : base (menu) - { - Key = DefaultKey; - } - - /// - public ContextMenuv2 (IEnumerable? menuItems) : this (new Menuv2 (menuItems)) - { - } - - private Key _key = DefaultKey; - - /// Specifies the key that will activate the context menu. - public Key Key - { - get => _key; - set - { - Key oldKey = _key; - _key = value; - KeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, _key)); - } - } - - /// Event raised when the is changed. - public event EventHandler? KeyChanged; - - /// - public bool EnableForDesign () - { - var shortcut = new Shortcut - { - Text = "Quit", - Title = "Q_uit", - Key = Key.Z.WithCtrl, - }; - - Add (shortcut); - - shortcut = new Shortcut - { - Text = "Help Text", - Title = "Help", - Key = Key.F1, - }; - - Add (shortcut); - - shortcut = new Shortcut - { - Text = "Czech", - CommandView = new CheckBox () - { - Title = "_Check" - }, - Key = Key.F9, - CanFocus = false - }; - - Add (shortcut); - - // HACK: This enables All Views Tester to show the CM if DefaultKey is pressed - AddCommand (Command.Context, () => Visible = true); - HotKeyBindings.Add (DefaultKey, Command.Context); - - return true; - } -} diff --git a/Terminal.Gui/Views/Menu/MenuBarItemv2.cs b/Terminal.Gui/Views/Menu/MenuBarItemv2.cs index 6fe9f121e..0ed8cdfcd 100644 --- a/Terminal.Gui/Views/Menu/MenuBarItemv2.cs +++ b/Terminal.Gui/Views/Menu/MenuBarItemv2.cs @@ -74,7 +74,7 @@ public class MenuBarItemv2 : MenuItemv2 null, Command.NotBound, commandText, - new (new (menuItems))) + new (menuItems)) { } // TODO: Hide base.SubMenu? diff --git a/Terminal.Gui/Views/Menu/MenuBarv2.cs b/Terminal.Gui/Views/Menu/MenuBarv2.cs index a129321d5..6ad4560eb 100644 --- a/Terminal.Gui/Views/Menu/MenuBarv2.cs +++ b/Terminal.Gui/Views/Menu/MenuBarv2.cs @@ -55,7 +55,7 @@ public class MenuBarv2 : Menuv2, IDesignable Border.LineStyle = LineStyle.None; } - // TODO: This needs to be done whenever a menuitem in any memubaritem changes + // TODO: This needs to be done whenever a menuitem in any MenuBarItem changes foreach (MenuBarItemv2? mbi in SubViews.Select(s => s as MenuBarItemv2)) { Application.Popover?.Register (mbi?.PopoverMenu); @@ -86,7 +86,7 @@ public class MenuBarv2 : Menuv2, IDesignable && Application.Popover?.GetActivePopover () is PopoverMenu popoverMenu && popoverMenu?.Root?.SuperMenuItem?.SuperView == this) { - Application.Popover?.HidePopover (popoverMenu); + Application.Popover?.Hide (popoverMenu); } menuBarItem?.PopoverMenu?.MakeVisible (new Point (menuBarItem.FrameToScreen ().X, menuBarItem.FrameToScreen ().Bottom)); @@ -167,176 +167,6 @@ public class MenuBarv2 : Menuv2, IDesignable ] ) ); - - // if (context is not Func actionFn) - // { - // actionFn = (_) => true; - // } - - // View? targetView = context as View; - - // Add (new MenuItemv2 (targetView, - // Command.NotBound, - // "_File", - // new MenuItem [] - // { - // new ( - // "_New", - // "", - // () => actionFn ("New"), - // null, - // null, - // KeyCode.CtrlMask | KeyCode.N - // ), - // new ( - // "_Open", - // "", - // () => actionFn ("Open"), - // null, - // null, - // KeyCode.CtrlMask | KeyCode.O - // ), - // new ( - // "_Save", - // "", - // () => actionFn ("Save"), - // null, - // null, - // KeyCode.CtrlMask | KeyCode.S - // ), - //#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - // null, - //#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - - // // Don't use Application.Quit so we can disambiguate between quitting and closing the toplevel - // new ( - // "_Quit", - // "", - // () => actionFn ("Quit"), - // null, - // null, - // KeyCode.CtrlMask | KeyCode.Q - // ) - // } - // ), - // new MenuBarItem ( - // "_Edit", - // new MenuItem [] - // { - // new ( - // "_Copy", - // "", - // () => actionFn ("Copy"), - // null, - // null, - // KeyCode.CtrlMask | KeyCode.C - // ), - // new ( - // "C_ut", - // "", - // () => actionFn ("Cut"), - // null, - // null, - // KeyCode.CtrlMask | KeyCode.X - // ), - // new ( - // "_Paste", - // "", - // () => actionFn ("Paste"), - // null, - // null, - // KeyCode.CtrlMask | KeyCode.V - // ), - // new MenuBarItem ( - // "_Find and Replace", - // new MenuItem [] - // { - // new ( - // "F_ind", - // "", - // () => actionFn ("Find"), - // null, - // null, - // KeyCode.CtrlMask | KeyCode.F - // ), - // new ( - // "_Replace", - // "", - // () => actionFn ("Replace"), - // null, - // null, - // KeyCode.CtrlMask | KeyCode.H - // ), - // new MenuBarItem ( - // "_3rd Level", - // new MenuItem [] - // { - // new ( - // "_1st", - // "", - // () => actionFn ( - // "1" - // ), - // null, - // null, - // KeyCode.F1 - // ), - // new ( - // "_2nd", - // "", - // () => actionFn ( - // "2" - // ), - // null, - // null, - // KeyCode.F2 - // ) - // } - // ), - // new MenuBarItem ( - // "_4th Level", - // new MenuItem [] - // { - // new ( - // "_5th", - // "", - // () => actionFn ( - // "5" - // ), - // null, - // null, - // KeyCode.CtrlMask - // | KeyCode.D5 - // ), - // new ( - // "_6th", - // "", - // () => actionFn ( - // "6" - // ), - // null, - // null, - // KeyCode.CtrlMask - // | KeyCode.D6 - // ) - // } - // ) - // } - // ), - // new ( - // "_Select All", - // "", - // () => actionFn ("Select All"), - // null, - // null, - // KeyCode.CtrlMask - // | KeyCode.ShiftMask - // | KeyCode.S - // ) - // } - // ), - // new MenuBarItem ("_About", "Top-Level", () => actionFn ("About")) - // ]; return true; } } diff --git a/Terminal.Gui/Views/Menu/MenuItemv2.cs b/Terminal.Gui/Views/Menu/MenuItemv2.cs index acf031127..f655d0b86 100644 --- a/Terminal.Gui/Views/Menu/MenuItemv2.cs +++ b/Terminal.Gui/Views/Menu/MenuItemv2.cs @@ -6,7 +6,7 @@ using Terminal.Gui.Resources; namespace Terminal.Gui; /// -/// A -dervied object to be used as a menu item in a . Has title, an +/// A -derived object to be used as a menu item in a . Has title, an /// associated help text, and an action to execute on activation. /// public class MenuItemv2 : Shortcut @@ -45,7 +45,18 @@ public class MenuItemv2 : Shortcut { TargetView = targetView; Command = command; + SubMenu = subMenu; + } + /// + public MenuItemv2 (string? commandText = null, string? helpText = null, Action? action = null, Key? key = null) + : base (key ?? Key.Empty, commandText, action, helpText) + { } + + /// + public MenuItemv2 (string? commandText = null, string? helpText = null, Menuv2? subMenu = null) + : base (Key.Empty, commandText, null, helpText) + { SubMenu = subMenu; } @@ -108,7 +119,7 @@ public class MenuItemv2 : Shortcut ret = base.DispatchCommand (commandContext); } - Logging.Trace ($"{commandContext?.Source?.Title}"); + //Logging.Trace ($"{commandContext?.Source?.Title}"); RaiseAccepted (commandContext); @@ -150,7 +161,7 @@ public class MenuItemv2 : Shortcut // TODO: Consider moving Accepted to Shortcut? /// - /// Riases the / event indicating this item (or submenu) + /// Raises the / event indicating this item (or submenu) /// was accepted. This is used to determine when to hide the menu. /// /// @@ -167,7 +178,7 @@ public class MenuItemv2 : Shortcut } /// - /// Called when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the + /// Called when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the /// menu. /// /// @@ -176,7 +187,7 @@ public class MenuItemv2 : Shortcut protected virtual void OnAccepted (CommandEventArgs args) { } /// - /// Raised when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the + /// Raised when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the /// menu. /// /// diff --git a/Terminal.Gui/Views/Menu/Menuv2.cs b/Terminal.Gui/Views/Menu/Menuv2.cs index 88df61b67..269acf4e4 100644 --- a/Terminal.Gui/Views/Menu/Menuv2.cs +++ b/Terminal.Gui/Views/Menu/Menuv2.cs @@ -2,13 +2,16 @@ namespace Terminal.Gui; /// -/// A -derived object to be used as a verticaly-oriented menu. Each subview is a . +/// A -derived object to be used as a vertically-oriented menu. Each subview is a . /// public class Menuv2 : Bar { /// public Menuv2 () : this ([]) { } + /// + public Menuv2 (IEnumerable? shortcuts) : this (shortcuts?.Cast()) { } + /// public Menuv2 (IEnumerable? shortcuts) : base (shortcuts) { @@ -18,7 +21,6 @@ public class Menuv2 : Bar Border!.Thickness = new Thickness (1, 1, 1, 1); Border.LineStyle = LineStyle.Single; - } /// @@ -50,52 +52,44 @@ public class Menuv2 : Bar { base.OnSubViewAdded (view); - if (view is MenuItemv2 menuItem) + switch (view) { - menuItem.CanFocus = true; - - AddCommand (menuItem.Command, RaiseAccepted); - - menuItem.Selecting += MenuItemOnSelecting; - menuItem.Accepting += MenuItemOnAccepting; - menuItem.Accepted += MenuItemOnAccepted; - - void MenuItemOnSelecting (object? sender, CommandEventArgs e) + case MenuItemv2 menuItem: { - Logging.Trace ($"Selecting: {e.Context?.Source?.Title}"); - } + menuItem.CanFocus = true; - void MenuItemOnAccepting (object? sender, CommandEventArgs e) - { - Logging.Trace ($"Accepting: {e.Context?.Source?.Title}"); - } + AddCommand (menuItem.Command, RaiseAccepted); - void MenuItemOnAccepted (object? sender, CommandEventArgs e) - { - Logging.Trace ($"Accepted: {e.Context?.Source?.Title}"); - RaiseAccepted (e.Context); - } - } + menuItem.Accepted += MenuItemOnAccepted; - if (view is Line line) - { - // Grow line so we get autojoin line - line.X = Pos.Func (() => -Border!.Thickness.Left); - line.Width = Dim.Fill ()! + Dim.Func (() => Border!.Thickness.Right); + break; + + void MenuItemOnAccepted (object? sender, CommandEventArgs e) + { + //Logging.Trace ($"Accepted: {e.Context?.Source?.Title}"); + RaiseAccepted (e.Context); + } + } + case Line line: + // Grow line so we get auto-join line + line.X = Pos.Func (() => -Border!.Thickness.Left); + line.Width = Dim.Fill ()! + Dim.Func (() => Border!.Thickness.Right); + + break; } } // TODO: Consider moving Accepted to Bar? /// - /// Riases the / event indicating an item in this menu (or submenu) + /// Raises the / event indicating an item in this menu (or submenu) /// was accepted. This is used to determine when to hide the menu. /// /// /// protected bool? RaiseAccepted (ICommandContext? ctx) { - Logging.Trace ($"RaiseAccepted: {ctx}"); + //Logging.Trace ($"RaiseAccepted: {ctx}"); CommandEventArgs args = new () { Context = ctx }; OnAccepted (args); @@ -105,7 +99,7 @@ public class Menuv2 : Bar } /// - /// Called when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the menu. + /// Called when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the menu. /// /// /// @@ -113,7 +107,7 @@ public class Menuv2 : Bar protected virtual void OnAccepted (CommandEventArgs args) { } /// - /// Raised when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the menu. + /// Raised when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the menu. /// /// /// @@ -157,7 +151,7 @@ public class Menuv2 : Bar } /// - /// Called when the the selected menu item has changed. + /// Called when the selected menu item has changed. /// /// protected virtual void OnSelectedMenuItemChanged (MenuItemv2? selected) diff --git a/Terminal.Gui/Views/Menu/PopoverMenu.cs b/Terminal.Gui/Views/Menu/PopoverMenu.cs index 804c1a9a3..555dd9175 100644 --- a/Terminal.Gui/Views/Menu/PopoverMenu.cs +++ b/Terminal.Gui/Views/Menu/PopoverMenu.cs @@ -1,24 +1,38 @@ #nullable enable -using System.Diagnostics; - namespace Terminal.Gui; /// -/// Provides a cascading popover menu. +/// Provides a cascading menu that pops over all other content. Can be used as a context menu or a drop-down +/// menu as part of . /// -public class PopoverMenu : PopoverBaseImpl +/// +/// +/// To use as a context menu, register the popover menu with and call +/// . +/// +/// +public class PopoverMenu : PopoverBaseImpl, IDesignable { /// /// Initializes a new instance of the class. /// - public PopoverMenu () : this (null) { } + public PopoverMenu () : this ((Menuv2?)null) { } + + /// + public PopoverMenu (IEnumerable? menuItems) : this (new Menuv2 (menuItems)) { } + + /// + public PopoverMenu (IEnumerable? menuItems) : this (new Menuv2 (menuItems)) { } /// /// Initializes a new instance of the class with the specified root . /// public PopoverMenu (Menuv2? root) { + Key = DefaultKey; + base.Visible = false; + //base.ColorScheme = Colors.ColorSchemes ["Menu"]; Root = root; @@ -77,11 +91,6 @@ public class PopoverMenu : PopoverBaseImpl bool? MoveRight (ICommandContext? ctx) { - if (Focused == Root) - { - return false; - } - if (MostFocused is MenuItemv2 { SubMenu.Visible: true } focused) { focused.SubMenu.SetFocus (); @@ -93,18 +102,36 @@ public class PopoverMenu : PopoverBaseImpl } } - /// - /// The mouse flags that will cause the popover menu to be visible. The default is - /// which is typically the right mouse button. - /// - public static MouseFlags MouseFlags { get; set; } = MouseFlags.Button3Clicked; + private Key _key = DefaultKey; + + /// Specifies the key that will activate the context menu. + public Key Key + { + get => _key; + set + { + Key oldKey = _key; + _key = value; + KeyChanged?.Invoke (this, new (oldKey, _key)); + } + } + + /// Raised when is changed. + public event EventHandler? KeyChanged; /// The default key for activating popover menus. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] public static Key DefaultKey { get; set; } = Key.F10.WithShift; /// - /// Makes the popover menu visible and locates it at . The actual position of the menu + /// The mouse flags that will cause the popover menu to be visible. The default is + /// which is typically the right mouse button. + /// + public MouseFlags MouseFlags { get; set; } = MouseFlags.Button3Clicked; + + /// + /// Makes the popover menu visible and locates it at . The actual position of the + /// menu /// will be adjusted to /// ensure the menu fully fits on the screen, and the mouse cursor is over the first cell of the /// first MenuItem. @@ -114,11 +141,12 @@ public class PopoverMenu : PopoverBaseImpl { UpdateKeyBindings (); SetPosition (idealScreenPosition); - Application.Popover?.ShowPopover (this); + Application.Popover?.Show (this); } /// - /// Locates the popover menu at . The actual position of the menu will be adjusted to + /// Locates the popover menu at . The actual position of the menu will be + /// adjusted to /// ensure the menu fully fits on the screen, and the mouse cursor is over the first cell of the /// first MenuItem (if possible). /// @@ -127,21 +155,24 @@ public class PopoverMenu : PopoverBaseImpl { idealScreenPosition ??= Application.GetLastMousePosition (); - if (idealScreenPosition is { } && Root is { }) + if (idealScreenPosition is null || Root is null) { - Point pos = idealScreenPosition.Value; - - if (!Root.IsInitialized) - { - Root.BeginInit(); - Root.EndInit (); - Root.Layout (); - } - pos = GetMostVisibleLocationForSubMenu (Root, pos); - - Root.X = pos.X; - Root.Y = pos.Y; + return; } + + Point pos = idealScreenPosition.Value; + + if (!Root.IsInitialized) + { + Root.BeginInit (); + Root.EndInit (); + Root.Layout (); + } + + pos = GetMostVisibleLocationForSubMenu (Root, pos); + + Root.X = pos.X; + Root.Y = pos.Y; } /// @@ -156,7 +187,7 @@ public class PopoverMenu : PopoverBaseImpl else { HideAndRemoveSubMenu (_root); - Application.Popover?.HidePopover (this); + Application.Popover?.Hide (this); } } @@ -208,48 +239,34 @@ public class PopoverMenu : PopoverBaseImpl // TODO: And it needs to clear them first IEnumerable all = GetMenuItemsOfAllSubMenus (); - foreach (MenuItemv2 menuItem in all.Where(mi => mi.Command != Command.NotBound)) + foreach (MenuItemv2 menuItem in all.Where (mi => mi.Command != Command.NotBound)) { + Key? key; if (menuItem.TargetView is { }) { // A TargetView implies HotKey - // Automatically set MenuItem.Key - Key? key = menuItem.TargetView.HotKeyBindings.GetFirstFromCommands (menuItem.Command); - - if (key is { IsValid: true }) - { - if (menuItem.Key.IsValid) - { - //Logging.Warning ("Do not specify a Key for MenuItems where a Command is specified. Key will be determined automatically."); - } - - menuItem.Key = key; - Logging.Trace ($"HotKey: {menuItem.Key}->{menuItem.Command}"); - } + key = menuItem.TargetView.HotKeyBindings.GetFirstFromCommands (menuItem.Command); } else { // No TargetView implies Application HotKey - Key? key = Application.KeyBindings.GetFirstFromCommands (menuItem.Command); - - if (key is { IsValid: true }) - { - if (menuItem.Key.IsValid) - { - // Logging.Warning ("App HotKey: Do not specify a Key for MenuItems where a Command is specified. Key will be determined automatically."); - } - - menuItem.Key = key; - Logging.Trace ($"App HotKey: {menuItem.Key}->{menuItem.Command}"); - } + key = Application.KeyBindings.GetFirstFromCommands (menuItem.Command); } + + if (key is not { IsValid: true }) + { + continue; + } + + if (menuItem.Key.IsValid) + { + //Logging.Warning ("Do not specify a Key for MenuItems where a Command is specified. Key will be determined automatically."); + } + + menuItem.Key = key; + + //Logging.Trace ($"HotKey: {menuItem.Key}->{menuItem.Command}"); } - - foreach (MenuItemv2 menuItem in all.Where (mi => mi is { Command: Command.NotBound, Key.IsValid: true })) - { - - } - } /// @@ -332,14 +349,9 @@ public class PopoverMenu : PopoverBaseImpl { var menu = menuItem?.SuperView as Menuv2; - if (menu is { }) - { - menu.Layout (); - } + menu?.Layout (); + // If there's a visible peer, remove / hide it - - // Debug.Assert (menu is null || menu?.SubViews.Count (v => v is MenuItemv2 { SubMenu.Visible: true }) < 2); - if (menu?.SubViews.FirstOrDefault (v => v is MenuItemv2 { SubMenu.Visible: true }) is MenuItemv2 visiblePeer) { HideAndRemoveSubMenu (visiblePeer.SubMenu); @@ -391,7 +403,7 @@ public class PopoverMenu : PopoverBaseImpl // TODO: Find the menu item below the mouse, if any, and select it // TODO: Enable No Border menu style - menu.Border.LineStyle = LineStyle.Single; + menu.Border!.LineStyle = LineStyle.Single; menu.Border.Thickness = new (1); if (!menu.IsInitialized) @@ -403,7 +415,6 @@ public class PopoverMenu : PopoverBaseImpl menu.ClearFocus (); base.Add (menu); - // IMPORTANT: This must be done after adding the menu to the super view or Add will try // to set focus to it. menu.Visible = true; @@ -417,8 +428,6 @@ public class PopoverMenu : PopoverBaseImpl if (menu is { Visible: true }) { // If there's a visible submenu, remove / hide it - // Debug.Assert (menu.SubViews.Count (v => v is MenuItemv2 { SubMenu.Visible: true }) <= 1); - if (menu.SubViews.FirstOrDefault (v => v is MenuItemv2 { SubMenu.Visible: true }) is MenuItemv2 visiblePeer) { HideAndRemoveSubMenu (visiblePeer.SubMenu); @@ -448,12 +457,12 @@ public class PopoverMenu : PopoverBaseImpl e.Cancel = true; } - Logging.Trace ($"{e.Context?.Source?.Title}"); + //Logging.Trace ($"{e.Context?.Source?.Title}"); } private void MenuAccepted (object? sender, CommandEventArgs e) { - Logging.Trace ($"{e.Context?.Source?.Title}"); + //Logging.Trace ($"{e.Context?.Source?.Title}"); if (e.Context?.Source is MenuItemv2 { SubMenu: null }) { @@ -467,14 +476,14 @@ public class PopoverMenu : PopoverBaseImpl } /// - /// Riases the / event indicating a menu (or submenu) + /// Raises the / event indicating a menu (or submenu) /// was accepted and the Menus in the PopoverMenu were hidden. Use this to determine when to hide the PopoverMenu. /// /// /// protected bool? RaiseAccepted (ICommandContext? ctx) { - Logging.Trace ($"RaiseAccepted: {ctx}"); + //Logging.Trace ($"RaiseAccepted: {ctx}"); CommandEventArgs args = new () { Context = ctx }; OnAccepted (args); @@ -529,4 +538,21 @@ public class PopoverMenu : PopoverBaseImpl base.Dispose (disposing); } + + + /// + public bool EnableForDesign (ref readonly TContext context) where TContext : notnull + { + Root = new Menuv2 ( + [ + new MenuItemv2 (this, Command.Cut), + new MenuItemv2 (this, Command.Copy), + new MenuItemv2 (this, Command.Paste), + new Line (), + new MenuItemv2 (this, Command.SelectAll) + ]); + + Visible = true; + return true; + } } diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menuv1/Menu.cs similarity index 99% rename from Terminal.Gui/Views/Menu/Menu.cs rename to Terminal.Gui/Views/Menuv1/Menu.cs index b7d14d3f0..a4d241140 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menuv1/Menu.cs @@ -3,8 +3,7 @@ namespace Terminal.Gui; /// -/// An internal class used to represent a menu pop-up menu. Created and managed by and -/// . +/// An internal class used to represent a menu pop-up menu. Created and managed by . /// internal sealed class Menu : View { diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menuv1/MenuBar.cs similarity index 99% rename from Terminal.Gui/Views/Menu/MenuBar.cs rename to Terminal.Gui/Views/Menuv1/MenuBar.cs index 38d5dc778..bfde40c1b 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menuv1/MenuBar.cs @@ -17,7 +17,6 @@ namespace Terminal.Gui; /// The appears on the first row of the SuperView and uses the full /// width. /// -/// See also: /// The provides global hot keys for the application. See . /// /// When the menu is created key bindings for each menu item and its sub-menu items are added for each menu diff --git a/Terminal.Gui/Views/Menu/MenuBarItem.cs b/Terminal.Gui/Views/Menuv1/MenuBarItem.cs similarity index 100% rename from Terminal.Gui/Views/Menu/MenuBarItem.cs rename to Terminal.Gui/Views/Menuv1/MenuBarItem.cs diff --git a/Terminal.Gui/Views/Menu/MenuClosingEventArgs.cs b/Terminal.Gui/Views/Menuv1/MenuClosingEventArgs.cs similarity index 100% rename from Terminal.Gui/Views/Menu/MenuClosingEventArgs.cs rename to Terminal.Gui/Views/Menuv1/MenuClosingEventArgs.cs diff --git a/Terminal.Gui/Views/Menu/MenuItem.cs b/Terminal.Gui/Views/Menuv1/MenuItem.cs similarity index 99% rename from Terminal.Gui/Views/Menu/MenuItem.cs rename to Terminal.Gui/Views/Menuv1/MenuItem.cs index 7a222ebca..d5dd714bc 100644 --- a/Terminal.Gui/Views/Menu/MenuItem.cs +++ b/Terminal.Gui/Views/Menuv1/MenuItem.cs @@ -255,7 +255,7 @@ public class MenuItem /// /// Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the - /// that is the parent of the or this + /// that is the parent of the this /// . /// /// The will be drawn on the MenuItem to the right of the and diff --git a/Terminal.Gui/Views/Menu/MenuItemCheckStyle.cs b/Terminal.Gui/Views/Menuv1/MenuItemCheckStyle.cs similarity index 100% rename from Terminal.Gui/Views/Menu/MenuItemCheckStyle.cs rename to Terminal.Gui/Views/Menuv1/MenuItemCheckStyle.cs diff --git a/Terminal.Gui/Views/Menu/MenuOpenedEventArgs.cs b/Terminal.Gui/Views/Menuv1/MenuOpenedEventArgs.cs similarity index 100% rename from Terminal.Gui/Views/Menu/MenuOpenedEventArgs.cs rename to Terminal.Gui/Views/Menuv1/MenuOpenedEventArgs.cs diff --git a/Terminal.Gui/Views/Menu/MenuOpeningEventArgs.cs b/Terminal.Gui/Views/Menuv1/MenuOpeningEventArgs.cs similarity index 100% rename from Terminal.Gui/Views/Menu/MenuOpeningEventArgs.cs rename to Terminal.Gui/Views/Menuv1/MenuOpeningEventArgs.cs diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 604e34e28..a6e5d58c1 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -418,9 +418,9 @@ public class TextField : View /// Gets or sets the foreground to use when rendering . public Color CaptionColor { get; set; } - /// Get the for this view. + /// Get the Context Menu for this view. [CanBeNull] - public ContextMenuv2 ContextMenu { get; private set; } + public PopoverMenu ContextMenu { get; private set; } /// Sets or gets the current cursor position. public virtual int CursorPosition @@ -800,7 +800,7 @@ public class TextField : View && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition) && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) - && !ev.Flags.HasFlag (PopoverMenu.MouseFlags)) + && !ev.Flags.HasFlag (ContextMenu!.MouseFlags)) { return false; } @@ -900,7 +900,7 @@ public class TextField : View ClearAllSelection (); PrepareSelection (0, _text.Count); } - else if (ev.Flags == PopoverMenu.MouseFlags) + else if (ev.Flags == ContextMenu!.MouseFlags) { PositionCursor (ev); ShowContextMenu (false); @@ -1226,7 +1226,7 @@ public class TextField : View private void CreateContextMenu () { DisposeContextMenu (); - ContextMenuv2 menu = new (new List () + PopoverMenu menu = new (new List () { new (this, Command.SelectAll, Strings.ctxSelectAll), new (this, Command.DeleteAll, Strings.ctxDeleteAll), diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index f89c6aeab..c3c768ad6 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -2490,8 +2490,8 @@ public class TextView : View /// public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete (); - /// Get the for this view. - public ContextMenuv2? ContextMenu { get; private set; } + /// Get the Context Menu. + public PopoverMenu? ContextMenu { get; private set; } /// Gets the cursor column. /// The cursor column. @@ -4148,17 +4148,17 @@ public class TextView : View private void AppendClipboard (string text) { Clipboard.Contents += text; } - private ContextMenuv2 CreateContextMenu () + private PopoverMenu CreateContextMenu () { - ContextMenuv2 menu = new (new List () + PopoverMenu menu = new (new List () { - new (this, Command.SelectAll, Strings.ctxSelectAll), - new (this, Command.DeleteAll, Strings.ctxDeleteAll), - new (this, Command.Copy, Strings.ctxCopy), - new (this, Command.Cut, Strings.ctxCut), - new (this, Command.Paste, Strings.ctxPaste), - new (this, Command.Undo, Strings.ctxUndo), - new (this, Command.Redo, Strings.ctxRedo), + new MenuItemv2 (this, Command.SelectAll, Strings.ctxSelectAll), + new MenuItemv2 (this, Command.DeleteAll, Strings.ctxDeleteAll), + new MenuItemv2 (this, Command.Copy, Strings.ctxCopy), + new MenuItemv2 (this, Command.Cut, Strings.ctxCut), + new MenuItemv2 (this, Command.Paste, Strings.ctxPaste), + new MenuItemv2 (this, Command.Undo, Strings.ctxUndo), + new MenuItemv2 (this, Command.Redo, Strings.ctxRedo), }); menu.KeyChanged += ContextMenu_KeyChanged; diff --git a/TerminalGuiFluentTesting/GuiTestContext.cs b/TerminalGuiFluentTesting/GuiTestContext.cs index 9a1195df8..bd3f8d974 100644 --- a/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/TerminalGuiFluentTesting/GuiTestContext.cs @@ -57,7 +57,7 @@ public class GuiTestContext : IDisposable .CreateLogger ("Test Logging"); Logging.Logger = logger; - v2.Init (null, GetDriverName()); + v2.Init (null, GetDriverName ()); booting.Release (); @@ -93,12 +93,12 @@ public class GuiTestContext : IDisposable private string GetDriverName () { return _driver switch - { - V2TestDriver.V2Win => "v2win", - V2TestDriver.V2Net => "v2net", - _ => - throw new ArgumentOutOfRangeException () - }; + { + V2TestDriver.V2Win => "v2win", + V2TestDriver.V2Net => "v2net", + _ => + throw new ArgumentOutOfRangeException () + }; } /// @@ -299,14 +299,14 @@ public class GuiTestContext : IDisposable case V2TestDriver.V2Net: int netButton = btn switch - { - WindowsConsole.ButtonState.Button1Pressed => 0, - WindowsConsole.ButtonState.Button2Pressed => 1, - WindowsConsole.ButtonState.Button3Pressed => 2, - WindowsConsole.ButtonState.RightmostButtonPressed => 2, - _ => throw new ArgumentOutOfRangeException(nameof(btn)) - }; - foreach (var k in NetSequences.Click(netButton,screenX,screenY)) + { + WindowsConsole.ButtonState.Button1Pressed => 0, + WindowsConsole.ButtonState.Button2Pressed => 1, + WindowsConsole.ButtonState.Button3Pressed => 2, + WindowsConsole.ButtonState.RightmostButtonPressed => 2, + _ => throw new ArgumentOutOfRangeException (nameof (btn)) + }; + foreach (var k in NetSequences.Click (netButton, screenX, screenY)) { SendNetKey (k); } @@ -452,18 +452,20 @@ public class GuiTestContext : IDisposable /// /// Registers a right click handler on the added view (or root view) that - /// will open the supplied . + /// will open the supplied . /// - /// - /// + /// /// - public GuiTestContext WithContextMenu (ContextMenu ctx, MenuBarItem menuItems) + public GuiTestContext WithContextMenu (PopoverMenu? contextMenu) { LastView.MouseEvent += (s, e) => { if (e.Flags.HasFlag (MouseFlags.Button3Clicked)) { - ctx.Show (menuItems); + // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused + // and the context menu is disposed when it is closed. + Application.Popover?.Register (contextMenu); + contextMenu?.MakeVisible (e.ScreenPosition); } }; diff --git a/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs b/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs index 345d7acc1..93dff6a86 100644 --- a/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs +++ b/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs @@ -63,22 +63,16 @@ public class BasicFluentAssertionTests { var clicked = false; - var ctx = new ContextMenu (); - - var menuItems = new MenuBarItem ( - [ - new ("_New File", string.Empty, () => { clicked = true; }) - ] - ); + MenuItemv2 [] menuItems = [new ("_New File", string.Empty, () => { clicked = true; })]; using GuiTestContext c = With.A (40, 10, d) - .WithContextMenu (ctx, menuItems) + .WithContextMenu (new PopoverMenu(menuItems)) .ScreenShot ("Before open menu", _out) // Click in main area inside border .RightClick (1, 1) .ScreenShot ("After open menu", _out) - .LeftClick (3, 3) + .LeftClick (2, 2) .Stop () .WriteOutLogs (_out); Assert.True (clicked); @@ -90,34 +84,26 @@ public class BasicFluentAssertionTests { var clicked = false; - var ctx = new ContextMenu (); - - - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null), - new MenuItem ("Three", "", null), - new MenuBarItem ( - "Four", - [ - new MenuItem ("SubMenu1", "", null), - new MenuItem ("SubMenu2", "", ()=>clicked=true), - new MenuItem ("SubMenu3", "", null), - new MenuItem ("SubMenu4", "", null), - new MenuItem ("SubMenu5", "", null), - new MenuItem ("SubMenu6", "", null), - new MenuItem ("SubMenu7", "", null) - ] - ), - new MenuItem ("Five", "", null), - new MenuItem ("Six", "", null) - ] - ); + MenuItemv2 [] menuItems = [ + new ("One", "", null), + new ("Two", "", null), + new ("Three", "", null), + new ("Four", "", new ( + [ + new ("SubMenu1", "", null), + new ("SubMenu2", "", ()=>clicked=true), + new ("SubMenu3", "", null), + new ("SubMenu4", "", null), + new ("SubMenu5", "", null), + new ("SubMenu6", "", null), + new ("SubMenu7", "", null) + ])), + new ("Five", "", null), + new ("Six", "", null) + ]; using GuiTestContext c = With.A (40, 10,d) - .WithContextMenu (ctx, menuItems) + .WithContextMenu (new PopoverMenu (menuItems)) .ScreenShot ("Before open menu", _out) // Click in main area inside border diff --git a/Tests/UnitTests/Application/ApplicationPopoverTests.cs b/Tests/UnitTests/Application/ApplicationPopoverTests.cs index 20ca40108..b491f1eb9 100644 --- a/Tests/UnitTests/Application/ApplicationPopoverTests.cs +++ b/Tests/UnitTests/Application/ApplicationPopoverTests.cs @@ -5,7 +5,7 @@ namespace Terminal.Gui.ApplicationTests; public class ApplicationPopoverTests { [Fact] - public void Popover_ApplicationInit_Inits () + public void ApplicationInit_Initializes_PopoverManager () { // Arrange Assert.Null (Application.Popover); @@ -18,7 +18,7 @@ public class ApplicationPopoverTests } [Fact] - public void Popover_ApplicationShutdown_CleansUp () + public void Application_Shutdown_CleansUp_PopoverManager () { // Arrange Assert.Null (Application.Popover); @@ -34,7 +34,7 @@ public class ApplicationPopoverTests } [Fact] - public void Popover_NotCleanedUp_On_End () + public void Application_End_Does_Not_CleanedUp () { // Arrange Assert.Null (Application.Popover); @@ -56,7 +56,7 @@ public class ApplicationPopoverTests } [Fact] - public void Popover_Active_Hidden_On_End () + public void Application_End_Hides_Active () { // Arrange Assert.Null (Application.Popover); @@ -66,9 +66,9 @@ public class ApplicationPopoverTests var top = new Toplevel (); RunState rs = Application.Begin (top); - IPopoverTestClass popover = new (); + PopoverTestClass popover = new (); - Application.Popover?.ShowPopover (popover); + Application.Popover?.Show (popover); Assert.True (popover.Visible); // Act @@ -83,17 +83,80 @@ public class ApplicationPopoverTests Application.Shutdown (); } - public class IPopoverTestClass : View, IPopover + [Fact] + public void Application_Shutdown_Disposes_Registered_Popovers () { - public List HandledKeys { get; } = new List (); + // Arrange + Assert.Null (Application.Popover); + Application.Init (new FakeDriver ()); + + PopoverTestClass popover = new (); + + // Act + Application.Popover?.Register (popover); + Application.Shutdown (); + + // Test + Assert.Equal(1, popover.DisposedCount); + } + + [Fact] + public void Application_Shutdown_Does_Not_Dispose_DeRegistered_Popovers () + { + // Arrange + Assert.Null (Application.Popover); + Application.Init (new FakeDriver ()); + + PopoverTestClass popover = new (); + + Application.Popover?.Register (popover); + + // Act + Application.Popover?.DeRegister (popover); + Application.Shutdown (); + + // Test + Assert.Equal (0, popover.DisposedCount); + + popover.Dispose (); + } + + [Fact] + public void Application_Shutdown_Does_Not_Dispose_ActiveNotRegistered_Popover () + { + // Arrange + Assert.Null (Application.Popover); + Application.Init (new FakeDriver ()); + + PopoverTestClass popover = new (); + + Application.Popover?.Show (popover); + + // Act + Application.Shutdown (); + + // Test + Assert.Equal (0, popover.DisposedCount); + + popover.Dispose (); + } + + public class PopoverTestClass : View, IPopover + { + public List HandledKeys { get; } = []; public int NewCommandInvokeCount { get; private set; } - public IPopoverTestClass () + // NOTE: Hides the base DisposedCount property + public new int DisposedCount { get; private set; } + + public PopoverTestClass () { CanFocus = true; AddCommand (Command.New, NewCommandHandler); HotKeyBindings.Add (Key.N.WithCtrl, Command.New); + return; + bool? NewCommandHandler (ICommandContext ctx) { NewCommandInvokeCount++; @@ -107,338 +170,13 @@ public class ApplicationPopoverTests HandledKeys.Add (key); return false; } + + /// + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + DisposedCount++; + } } - //[Fact] - //public void Popover_SetToNull () - //{ - // // Arrange - // var popover = new View (); - // Application.Popover = popover; - // // Act - // Application.Popover = null; - - // // Assert - // Assert.Null (Application.Popover); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_VisibleChangedEvent () - //{ - // // Arrange - // var popover = new View () - // { - // Visible = false - // }; - // Application.Popover = popover; - // bool eventTriggered = false; - - // popover.VisibleChanged += (sender, e) => eventTriggered = true; - - // // Act - // popover.Visible = true; - - // // Assert - // Assert.True (eventTriggered); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_InitializesCorrectly () - //{ - // // Arrange - // var popover = new View (); - - // // Act - // Application.Popover = popover; - - // // Assert - // Assert.True (popover.IsInitialized); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_SetsColorScheme () - //{ - // // Arrange - // var popover = new View (); - // var topColorScheme = new ColorScheme (); - // Application.Top = new Toplevel { ColorScheme = topColorScheme }; - - // // Act - // Application.Popover = popover; - - // // Assert - // Assert.Equal (topColorScheme, popover.ColorScheme); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_VisibleChangedToTrue_SetsFocus () - //{ - // // Arrange - // var popover = new View () - // { - // Visible = false, - // CanFocus = true - // }; - // Application.Popover = popover; - - // // Act - // popover.Visible = true; - - // // Assert - // Assert.True (popover.Visible); - // Assert.True (popover.HasFocus); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Theory] - //[InlineData(-1, -1)] - //[InlineData (0, 0)] - //[InlineData (2048, 2048)] - //[InlineData (2049, 2049)] - //public void Popover_VisibleChangedToTrue_Locates_In_Visible_Position (int x, int y) - //{ - // // Arrange - // var popover = new View () - // { - // X = x, - // Y = y, - // Visible = false, - // CanFocus = true, - // Width = 1, - // Height = 1 - // }; - // Application.Popover = popover; - - // // Act - // popover.Visible = true; - // Application.LayoutAndDraw(); - - // // Assert - // Assert.True (Application.Screen.Contains (popover.Frame)); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_VisibleChangedToFalse_Hides_And_Removes_Focus () - //{ - // // Arrange - // var popover = new View () - // { - // Visible = false, - // CanFocus = true - // }; - // Application.Popover = popover; - // popover.Visible = true; - - // // Act - // popover.Visible = false; - - // // Assert - // Assert.False (popover.Visible); - // Assert.False (popover.HasFocus); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_Quit_Command_Hides () - //{ - // // Arrange - // var popover = new View () - // { - // Visible = false, - // CanFocus = true - // }; - // Application.Popover = popover; - // popover.Visible = true; - // Assert.True (popover.Visible); - // Assert.True (popover.HasFocus); - - // // Act - // Application.RaiseKeyDownEvent (Application.QuitKey); - - // // Assert - // Assert.False (popover.Visible); - // Assert.False (popover.HasFocus); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_MouseClick_Outside_Hides_Passes_Event_On () - //{ - // // Arrange - // Application.Top = new Toplevel () - // { - // Id = "top", - // Height = 10, - // Width = 10, - // }; - - // View otherView = new () - // { - // X = 1, - // Y = 1, - // Height = 1, - // Width = 1, - // Id = "otherView", - // }; - - // bool otherViewPressed = false; - // otherView.MouseEvent += (sender, e) => - // { - // otherViewPressed = e.Flags.HasFlag(MouseFlags.Button1Pressed); - // }; - - // Application.Top.Add (otherView); - - // var popover = new View () - // { - // Id = "popover", - // X = 5, - // Y = 5, - // Width = 1, - // Height = 1, - // Visible = false, - // CanFocus = true - // }; - - // Application.Popover = popover; - // popover.Visible = true; - // Assert.True (popover.Visible); - // Assert.True (popover.HasFocus); - - // // Act - // // Click on popover - // Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed, ScreenPosition = new (5, 5) }); - // Assert.True (popover.Visible); - - // // Click outside popover (on button) - // Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed, ScreenPosition = new (1, 1) }); - - // // Assert - // Assert.True (otherViewPressed); - // Assert.False (popover.Visible); - - // Application.Top.Dispose (); - // Application.ResetState (ignoreDisposed: true); - //} - - //[Theory] - //[InlineData (0, 0, false)] - //[InlineData (5, 5, true)] - //[InlineData (10, 10, false)] - //[InlineData (5, 10, false)] - //[InlineData (9, 9, false)] - //public void Popover_MouseClick_Outside_Hides (int mouseX, int mouseY, bool expectedVisible) - //{ - // // Arrange - // Application.Top = new Toplevel () - // { - // Id = "top", - // Height = 10, - // Width = 10, - // }; - // var popover = new View () - // { - // Id = "popover", - // X = 5, - // Y = 5, - // Width = 1, - // Height = 1, - // Visible = false, - // CanFocus = true - // }; - - // Application.Popover = popover; - // popover.Visible = true; - // Assert.True (popover.Visible); - // Assert.True (popover.HasFocus); - - // // Act - // Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed, ScreenPosition = new (mouseX, mouseY) }); - - // // Assert - // Assert.Equal (expectedVisible, popover.Visible); - - // Application.Top.Dispose (); - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_SetAndGet_ReturnsCorrectValue () - //{ - // // Arrange - // var view = new View (); - - // // Act - // Application.Popover = view; - - // // Assert - // Assert.Equal (view, Application.Popover); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_SetToNull_HidesPreviousPopover () - //{ - // // Arrange - // var view = new View { Visible = true }; - // Application.Popover = view; - - // // Act - // Application.Popover = null; - - // // Assert - // Assert.False (view.Visible); - // Assert.Null (Application.Popover); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_SetNewPopover_HidesPreviousPopover () - //{ - // // Arrange - // var oldView = new View { Visible = true }; - // var newView = new View (); - // Application.Popover = oldView; - - // // Act - // Application.Popover = newView; - - // // Assert - // Assert.False (oldView.Visible); - // Assert.Equal (newView, Application.Popover); - - // Application.ResetState (ignoreDisposed: true); - //} - - //[Fact] - //public void Popover_SetNewPopover_InitializesAndSetsProperties () - //{ - // // Arrange - // var view = new View (); - - // // Act - // Application.Popover = view; - - // // Assert - // Assert.True (view.IsInitialized); - // Assert.True (view.Arrangement.HasFlag (ViewArrangement.Overlapped)); - // Assert.Equal (Application.Top?.ColorScheme, view.ColorScheme); - - // Application.ResetState (ignoreDisposed: true); - //} } diff --git a/Tests/UnitTests/Views/ContextMenuTests.cs b/Tests/UnitTests/Views/ContextMenuTests.cs deleted file mode 100644 index b6a69063e..000000000 --- a/Tests/UnitTests/Views/ContextMenuTests.cs +++ /dev/null @@ -1,2218 +0,0 @@ -using UnitTests; -using Xunit.Abstractions; - -namespace Terminal.Gui.ViewsTests; - -public class ContextMenuTests (ITestOutputHelper output) -{ - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void ContextMenu_Constructors () - { - var cm = new ContextMenu (); - var top = new Toplevel (); - Application.Begin (top); - - Assert.Equal (Point.Empty, cm.Position); - Assert.Null (cm.MenuItems); - Assert.Null (cm.Host); - cm.Position = new Point (20, 10); - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("First", "", null) - ] - ); - cm.Show (menuItems); - Assert.Equal (new Point (20, 10), cm.Position); - Assert.Single (cm.MenuItems!.Children); - - cm = new ContextMenu - { - Position = new Point (5, 10) - }; - - menuItems = new MenuBarItem ( - new [] { new MenuItem ("One", "", null), new MenuItem ("Two", "", null) } - ); - cm.Show (menuItems); - Assert.Equal (new Point (5, 10), cm.Position); - Assert.Equal (2, cm.MenuItems!.Children.Length); - Assert.Null (cm.Host); - - var view = new View { X = 5, Y = 10 }; - top.Add (view); - - cm = new ContextMenu - { - Host = view, - Position = new Point (5, 10) - }; - - menuItems = new MenuBarItem ( - new [] { new MenuItem ("One", "", null), new MenuItem ("Two", "", null) } - ); - cm.Show (menuItems); - Assert.Equal (new Point (5, 10), cm.Position); - Assert.Equal (2, cm.MenuItems.Children.Length); - Assert.NotNull (cm.Host); - - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void ContextMenu_Is_Closed_If_Another_MenuBar_Is_Open_Or_Vice_Versa () - { - var cm = new ContextMenu - { - Position = new Point (10, 5) - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null) - ] - ); - - var menuBar = new MenuBar - { - Menus = - [ - new MenuBarItem ("File", "", null), - new MenuBarItem ("Edit", "", null) - ] - }; - - var top = new Toplevel (); - top.Add (menuBar); - Application.Begin (top); - - Assert.Null (Application.MouseGrabView); - - cm.Show (menuItems); - Assert.True (ContextMenu.IsShow); - Menu menu = (Menu)top.SubViews.First (v => v is Menu); - Assert.Equal (menu, Application.MouseGrabView); - Assert.False (menuBar.IsMenuOpen); - Assert.True (menuBar.NewKeyDownEvent (menuBar.Key)); - Assert.False (ContextMenu.IsShow); - Assert.Equal (menuBar, Application.MouseGrabView); - Assert.True (menuBar.IsMenuOpen); - - cm.Show (menuItems); - Assert.True (ContextMenu.IsShow); - menu = (Menu)top.SubViews.First (v => v is Menu); - Assert.Equal (menu, Application.MouseGrabView); - Assert.False (menuBar.IsMenuOpen); -#if SUPPORT_ALT_TO_ACTIVATE_MENU - Assert.True (Application.Top.ProcessKeyUp (new (Key.AltMask))); - Assert.False (ContextMenu.IsShow); - Assert.Equal (menu, Application.MouseGrabView); - Assert.True (menu.IsMenuOpen); -#endif - - cm.Show (menuItems); - Assert.True (ContextMenu.IsShow); - menu = (Menu)top.SubViews.First (v => v is Menu); - Assert.Equal (menu, Application.MouseGrabView); - Assert.False (menuBar.IsMenuOpen); - Assert.False (menuBar.NewMouseEvent (new MouseEventArgs { Position = new (1, 0), Flags = MouseFlags.ReportMousePosition, View = menuBar })); - Assert.True (ContextMenu.IsShow); - Assert.Equal (menu, Application.MouseGrabView); - Assert.False (menuBar.IsMenuOpen); - Assert.True (menuBar.NewMouseEvent (new MouseEventArgs { Position = new (1, 0), Flags = MouseFlags.Button1Clicked, View = menuBar })); - Assert.False (ContextMenu.IsShow); - Assert.Equal (menuBar, Application.MouseGrabView); - Assert.True (menuBar.IsMenuOpen); - top.Dispose (); - } - - [Fact (Skip = "#3798 Broke. Will fix in #2975")] - [AutoInitShutdown] - public void Draw_A_ContextMenu_Over_A_Borderless_Top () - { - ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); - - Assert.Equal (new Rectangle (0, 0, 20, 15), View.GetClip ()!.GetBounds ()); - DriverAssert.AssertDriverContentsWithFrameAre ("", output); - - var top = new Toplevel { X = 2, Y = 2, Width = 15, Height = 4 }; - top.Add (new TextField { X = Pos.Center (), Width = 10, Text = "Test" }); - RunState rs = Application.Begin (top); - Application.RunIteration (ref rs); - - Assert.Equal (new Rectangle (2, 2, 15, 4), top.Frame); - Assert.Equal (top, Application.Top); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - Test", - output - ); - - Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (8, 2), Flags = MouseFlags.Button3Clicked }); - - Application.RunIteration (ref rs); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - Test -┌─────────────────── -│ Select All Ctrl+ -│ Delete All Ctrl+ -│ Copy Ctrl+ -│ Cut Ctrl+ -│ Paste Ctrl+ -│ Undo Ctrl+ -│ Redo Ctrl+ -└───────────────────", - output - ); - - Application.End (rs); - top.Dispose (); - } - - [Fact (Skip = "#3798 Broke. Will fix in #2975")] - [AutoInitShutdown] - public void Draw_A_ContextMenu_Over_A_Dialog () - { - Toplevel top = new (); - var win = new Window (); - top.Add (win); - RunState rsTop = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); - - Assert.Equal (new Rectangle (0, 0, 20, 15), win.Frame); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -┌──────────────────┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└──────────────────┘", - output - ); - - // Don't use Dialog here as it has more layout logic. Use Window instead. - var testWindow = new Window { X = 2, Y = 2, Width = 15, Height = 4 }; - testWindow.Add (new TextField { X = Pos.Center (), Width = 10, Text = "Test" }); - RunState rsDialog = Application.Begin (testWindow); - Application.LayoutAndDraw (); - - Assert.Equal (new Rectangle (2, 2, 15, 4), testWindow.Frame); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -┌──────────────────┐ -│ │ -│ ┌─────────────┐ │ -│ │ Test │ │ -│ │ │ │ -│ └─────────────┘ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└──────────────────┘", - output - ); - - Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (9, 3), Flags = MouseFlags.Button3Clicked }); - - Application.RunIteration (ref rsDialog); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -┌──────────────────┐ -│ │ -│ ┌─────────────┐ │ -│ │ Test │ │ -┌─────────────────── -│ Select All Ctrl+ -│ Delete All Ctrl+ -│ Copy Ctrl+ -│ Cut Ctrl+ -│ Paste Ctrl+ -│ Undo Ctrl+ -│ Redo Ctrl+ -└─────────────────── -│ │ -└──────────────────┘", - output - ); - - Application.End (rsDialog); - Application.End (rsTop); - top.Dispose (); - } - - [Fact (Skip = "#3798 Broke. Will fix in #2975")] - [AutoInitShutdown] - public void Draw_A_ContextMenu_Over_A_Top_Dialog () - { - ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); - - Assert.Equal (new Rectangle (0, 0, 20, 15), View.GetClip ()!.GetBounds ()); - DriverAssert.AssertDriverContentsWithFrameAre ("", output); - - // Don't use Dialog here as it has more layout logic. Use Window instead. - var dialog = new Window { X = 2, Y = 2, Width = 15, Height = 4 }; - dialog.Add (new TextField { X = Pos.Center (), Width = 10, Text = "Test" }); - RunState rs = Application.Begin (dialog); - Application.LayoutAndDraw (); - - Assert.Equal (new Rectangle (2, 2, 15, 4), dialog.Frame); - Assert.Equal (dialog, Application.Top); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌─────────────┐ - │ Test │ - │ │ - └─────────────┘", - output - ); - - Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (9, 3), Flags = MouseFlags.Button3Clicked }); - - var firstIteration = false; - Application.RunIteration (ref rs, firstIteration); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌─────────────┐ - │ Test │ -┌─────────────────── -│ Select All Ctrl+ -│ Delete All Ctrl+ -│ Copy Ctrl+ -│ Cut Ctrl+ -│ Paste Ctrl+ -│ Undo Ctrl+ -│ Redo Ctrl+ -└───────────────────", - output - ); - - Application.End (rs); - dialog.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void ForceMinimumPosToZero_True_False () - { - var cm = new ContextMenu - { - Position = new Point (-1, -2) - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null) - ] - ); - Assert.Equal (new Point (-1, -2), cm.Position); - - Toplevel top = new (); - Application.Begin (top); - - cm.Show (menuItems); - Assert.Equal (new Point (-1, -2), cm.Position); - Application.LayoutAndDraw (); - - var expected = @" -┌──────┐ -│ One │ -│ Two │ -└──────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rectangle (0, 1, 8, 4), pos); - - cm.ForceMinimumPosToZero = false; - cm.Show (menuItems); - Assert.Equal (new Point (-1, -2), cm.Position); - Application.LayoutAndDraw (); - - expected = @" - One │ - Two │ -──────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rectangle (1, 0, 7, 3), pos); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Hide_Is_Invoke_At_Container_Closing () - { - var cm = new ContextMenu - { - Position = new Point (80, 25) - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null) - ] - ); - Toplevel top = new (); - Application.Begin (top); - top.Running = true; - - Assert.False (ContextMenu.IsShow); - - cm.Show (menuItems); - Assert.True (ContextMenu.IsShow); - - top.RequestStop (); - Assert.False (ContextMenu.IsShow); - top.Dispose (); - } - - //[Fact (Skip = "Redo for CMv2")] - //[AutoInitShutdown] - //public void Key_Open_And_Close_The_ContextMenu () - //{ - // var tf = new TextField (); - // var top = new Toplevel (); - // top.Add (tf); - // Application.Begin (top); - - // Assert.True (Application.RaiseKeyDownEvent (ContextMenu.DefaultKey)); - // Assert.True (tf.ContextMenu.MenuBar!.IsMenuOpen); - // Assert.True (Application.RaiseKeyDownEvent (ContextMenu.DefaultKey)); - - // // The last context menu bar opened is always preserved - // Assert.False (tf.ContextMenu.Visible); - // top.Dispose (); - //} - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void KeyChanged_Event () - { - var oldKey = Key.Empty; - var cm = new ContextMenu (); - - cm.KeyChanged += (s, e) => oldKey = e.OldKey; - - cm.Key = Key.Space.WithCtrl; - Assert.Equal (Key.Space.WithCtrl, cm.Key); - Assert.Equal (ContextMenu.DefaultKey, oldKey); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void MenuItens_Changing () - { - var cm = new ContextMenu - { - Position = new Point (10, 5) - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null) - ] - ); - Toplevel top = new (); - Application.Begin (top); - cm.Show (menuItems); - Application.LayoutAndDraw (); - - var expected = @" - ┌──────┐ - │ One │ - │ Two │ - └──────┘ -"; - - DriverAssert.AssertDriverContentsAre (expected, output); - - menuItems = new MenuBarItem ( - [ - new MenuItem ("First", "", null), - new MenuItem ("Second", "", null), - new MenuItem ("Third", "", null) - ] - ); - - cm.Show (menuItems); - Application.LayoutAndDraw (); - - expected = @" - ┌─────────┐ - │ First │ - │ Second │ - │ Third │ - └─────────┘ -"; - - DriverAssert.AssertDriverContentsAre (expected, output); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Menus_And_SubMenus_Always_Try_To_Be_On_Screen () - { - var cm = new ContextMenu - { - Position = new Point (-1, -2) - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null), - new MenuItem ("Three", "", null), - new MenuBarItem ( - "Four", - [ - new MenuItem ("SubMenu1", "", null), - new MenuItem ("SubMenu2", "", null), - new MenuItem ("SubMenu3", "", null), - new MenuItem ("SubMenu4", "", null), - new MenuItem ("SubMenu5", "", null), - new MenuItem ("SubMenu6", "", null), - new MenuItem ("SubMenu7", "", null) - ] - ), - new MenuItem ("Five", "", null), - new MenuItem ("Six", "", null) - ] - ); - Assert.Equal (new Point (-1, -2), cm.Position); - - Toplevel top = new (); - RunState rs = Application.Begin (top); - - cm.Show (menuItems); - Application.RunIteration (ref rs); - - Assert.Equal (new Point (-1, -2), cm.Position); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -┌────────┐ -│ One │ -│ Two │ -│ Three │ -│ Four ►│ -│ Five │ -│ Six │ -└────────┘ -", - output - ); - - View menu = top.SubViews.First (v => v is Menu); - - Assert.True ( - menu - .NewMouseEvent ( - new MouseEventArgs { Position = new (0, 3), Flags = MouseFlags.ReportMousePosition, View = menu } - ) - ); - Application.RunIteration (ref rs); - Assert.Equal (new Point (-1, -2), cm.Position); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -┌────────┐ -│ One │ -│ Two │ -│ Three │ -│ Four ►│┌───────────┐ -│ Five ││ SubMenu1 │ -│ Six ││ SubMenu2 │ -└────────┘│ SubMenu3 │ - │ SubMenu4 │ - │ SubMenu5 │ - │ SubMenu6 │ - │ SubMenu7 │ - └───────────┘ -", - output - ); - - ((FakeDriver)Application.Driver!).SetBufferSize (40, 20); - cm.Position = new Point (41, -2); - cm.Show (menuItems); - Application.RunIteration (ref rs); - Assert.Equal (new Point (41, -2), cm.Position); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two │ - │ Three │ - │ Four ►│ - │ Five │ - │ Six │ - └────────┘ -", - output - ); - - menu = top.SubViews.First (v => v is Menu); - Assert.True ( - menu - .NewMouseEvent ( - new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = menu } - ) - ); - Application.RunIteration (ref rs); - Assert.Equal (new Point (41, -2), cm.Position); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two │ - │ Three │ - ┌───────────┐│ Four ►│ - │ SubMenu1 ││ Five │ - │ SubMenu2 ││ Six │ - │ SubMenu3 │└────────┘ - │ SubMenu4 │ - │ SubMenu5 │ - │ SubMenu6 │ - │ SubMenu7 │ - └───────────┘ -", - output - ); - - cm.Position = new Point (41, 9); - cm.Show (menuItems); - Application.RunIteration (ref rs); - Assert.Equal (new Point (41, 9), cm.Position); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two │ - │ Three │ - │ Four ►│ - │ Five │ - │ Six │ - └────────┘ -", - output - ); - - menu = top.SubViews.First (v => v is Menu); - Assert.True ( - menu - .NewMouseEvent ( - new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = menu } - ) - ); - Application.RunIteration (ref rs); - Assert.Equal (new Point (41, 9), cm.Position); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - ┌───────────┐│ One │ - │ SubMenu1 ││ Two │ - │ SubMenu2 ││ Three │ - │ SubMenu3 ││ Four ►│ - │ SubMenu4 ││ Five │ - │ SubMenu5 ││ Six │ - │ SubMenu6 │└────────┘ - │ SubMenu7 │ - └───────────┘ -", - output - ); - - cm.Position = new Point (41, 22); - cm.Show (menuItems); - Application.RunIteration (ref rs); - Assert.Equal (new Point (41, 22), cm.Position); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two │ - │ Three │ - │ Four ►│ - │ Five │ - │ Six │ - └────────┘ -", - output - ); - - menu = top.SubViews.First (v => v is Menu); - Assert.True ( - menu - .NewMouseEvent ( - new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = menu } - ) - ); - Application.RunIteration (ref rs); - Assert.Equal (new Point (41, 22), cm.Position); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌───────────┐ - │ SubMenu1 │┌────────┐ - │ SubMenu2 ││ One │ - │ SubMenu3 ││ Two │ - │ SubMenu4 ││ Three │ - │ SubMenu5 ││ Four ►│ - │ SubMenu6 ││ Five │ - │ SubMenu7 ││ Six │ - └───────────┘└────────┘ -", - output - ); - - ((FakeDriver)Application.Driver!).SetBufferSize (18, 8); - cm.Position = new Point (19, 10); - cm.Show (menuItems); - Application.RunIteration (ref rs); - Assert.Equal (new Point (19, 10), cm.Position); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two │ - │ Three │ - │ Four ►│ - │ Five │ - │ Six │ - └────────┘ -", - output - ); - - menu = top.SubViews.First (v => v is Menu); - Assert.True ( - menu - .NewMouseEvent ( - new MouseEventArgs { Position = new (30, 3), Flags = MouseFlags.ReportMousePosition, View = menu } - ) - ); - Application.RunIteration (ref rs); - Assert.Equal (new Point (19, 10), cm.Position); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -┌───────────┐────┐ -│ SubMenu1 │ │ -│ SubMenu2 │ │ -│ SubMenu3 │ee │ -│ SubMenu4 │r ►│ -│ SubMenu5 │e │ -│ SubMenu6 │ │ -│ SubMenu7 │────┘ -", - output - ); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void MouseFlags_Changing () - { - var lbl = new Label { Text = "Original" }; - - var cm = new ContextMenu (); - - lbl.MouseClick += (s, e) => - { - if (e.Flags == cm.MouseFlags) - { - lbl.Text = "Replaced"; - e.Handled = true; - } - }; - - Toplevel top = new (); - top.Add (lbl); - Application.Begin (top); - - Assert.True (lbl.NewMouseEvent (new MouseEventArgs { Flags = cm.MouseFlags })); - Assert.Equal ("Replaced", lbl.Text); - - lbl.Text = "Original"; - cm.MouseFlags = MouseFlags.Button2Clicked; - Assert.True (lbl.NewMouseEvent (new MouseEventArgs { Flags = cm.MouseFlags })); - Assert.Equal ("Replaced", lbl.Text); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - public void MouseFlagsChanged_Event () - { - var oldMouseFlags = new MouseFlags (); - var cm = new ContextMenu (); - - cm.MouseFlagsChanged += (s, e) => oldMouseFlags = e.OldValue; - - cm.MouseFlags = MouseFlags.Button2Clicked; - Assert.Equal (MouseFlags.Button2Clicked, cm.MouseFlags); - Assert.Equal (MouseFlags.Button3Clicked, oldMouseFlags); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Position_Changing () - { - var cm = new ContextMenu - { - Position = new Point (10, 5) - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null) - ] - ); - Toplevel top = new (); - Application.Begin (top); - cm.Show (menuItems); - Application.LayoutAndDraw (); - - var expected = @" - ┌──────┐ - │ One │ - │ Two │ - └──────┘ -"; - - DriverAssert.AssertDriverContentsAre (expected, output); - - cm.Position = new Point (5, 10); - - cm.Show (menuItems); - Application.LayoutAndDraw (); - - expected = @" - ┌──────┐ - │ One │ - │ Two │ - └──────┘ -"; - - DriverAssert.AssertDriverContentsAre (expected, output); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void RequestStop_While_ContextMenu_Is_Open_Does_Not_Throws () - { - ContextMenu cm = new ContextMenu - { - Position = new Point (10, 5) - }; - - var menuItems = new MenuBarItem ( - new MenuItem [] { new ("One", "", null), new ("Two", "", null) } - ); - Toplevel top = new (); - var isMenuAllClosed = false; - MenuBarItem mi = null; - int iterations = -1; - - Application.Iteration += (s, a) => - { - iterations++; - - if (iterations == 0) - { - cm.Show (menuItems); - Assert.True (ContextMenu.IsShow); - mi = cm.MenuBar.Menus [0]; - - mi.Action = () => - { - Assert.True (ContextMenu.IsShow); - - var dialog1 = new Dialog () { Id = "dialog1" }; - Application.Run (dialog1); - dialog1.Dispose (); - Assert.False (ContextMenu.IsShow); - Assert.True (isMenuAllClosed); - }; - cm.MenuBar.MenuAllClosed += (_, _) => isMenuAllClosed = true; - } - else if (iterations == 1) - { - mi.Action (); - } - else if (iterations == 2) - { - Application.RequestStop (); - } - else if (iterations == 3) - { - isMenuAllClosed = false; - cm.Show (menuItems); - Assert.True (ContextMenu.IsShow); - cm.MenuBar.MenuAllClosed += (_, _) => isMenuAllClosed = true; - } - else if (iterations == 4) - { - Exception exception = Record.Exception (() => Application.RequestStop ()); - Assert.Null (exception); - } - else - { - Application.RequestStop (); - } - }; - - var isTopClosed = false; - - top.Closing += (_, _) => - { - var dialog2 = new Dialog () { Id = "dialog2" }; - Application.Run (dialog2); - dialog2.Dispose (); - Assert.False (ContextMenu.IsShow); - Assert.True (isMenuAllClosed); - isTopClosed = true; - }; - - Application.Run (top); - - Assert.True (isTopClosed); - Assert.False (ContextMenu.IsShow); - Assert.True (isMenuAllClosed); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Show_Display_At_Zero_If_The_Toplevel_Height_Is_Less_Than_The_Menu_Height () - { - ((FakeDriver)Application.Driver!).SetBufferSize (80, 3); - - var cm = new ContextMenu - { - Position = Point.Empty - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null) - ] - ); - Assert.Equal (Point.Empty, cm.Position); - - Toplevel top = new (); - Application.Begin (top); - cm.Show (menuItems); - Assert.Equal (Point.Empty, cm.Position); - Application.LayoutAndDraw (); - - var expected = @" -┌──────┐ -│ One │ -│ Two │"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rectangle (0, 0, 8, 3), pos); - - cm.Hide (); - Assert.Equal (Point.Empty, cm.Position); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Show_Display_At_Zero_If_The_Toplevel_Width_Is_Less_Than_The_Menu_Width () - { - ((FakeDriver)Application.Driver!).SetBufferSize (5, 25); - - var cm = new ContextMenu - { - Position = Point.Empty - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null) - ] - ); - Assert.Equal (Point.Empty, cm.Position); - - Toplevel top = new (); - Application.Begin (top); - cm.Show (menuItems); - Assert.Equal (Point.Empty, cm.Position); - Application.LayoutAndDraw (); - - var expected = @" -┌──── -│ One -│ Two -└────"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rectangle (0, 1, 5, 4), pos); - - cm.Hide (); - Assert.Equal (Point.Empty, cm.Position); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Show_Display_Below_The_Bottom_Host_If_Has_Enough_Space () - { - var view = new View - { - X = 10, - Y = 5, - Width = 10, - Height = 1, - Text = "View" - }; - - var cm = new ContextMenu - { - Host = view, - Position = new Point (10, 5) - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null) - ] - ); - var top = new Toplevel (); - top.Add (view); - Application.Begin (top); - - Assert.Equal (new Point (10, 5), cm.Position); - - cm.Show (menuItems); - top.Draw (); - Assert.Equal (new Point (10, 5), cm.Position); - - var expected = @" - View - ┌──────┐ - │ One │ - │ Two │ - └──────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rectangle (10, 5, 18, 5), pos); - - cm.Hide (); - Assert.Equal (new Point (10, 5), cm.Position); - cm.Host.X = 5; - cm.Host.Y = 10; - cm.Host.Height = 3; - - cm.Show (menuItems); - View.SetClipToScreen (); - Application.Top.Draw (); - Assert.Equal (new Point (5, 12), cm.Position); - - expected = @" - View - - - ┌──────┐ - │ One │ - │ Two │ - └──────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rectangle (5, 10, 13, 7), pos); - - cm.Hide (); - Assert.Equal (new Point (5, 12), cm.Position); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Show_Ensures_Display_Inside_The_Container_But_Preserves_Position () - { - var cm = new ContextMenu - { - Position = new Point (80, 25) - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null) - ] - ); - Assert.Equal (new Point (80, 25), cm.Position); - - Toplevel top = new (); - Application.Begin (top); - cm.Show (menuItems); - Assert.Equal (new Point (80, 25), cm.Position); - Application.LayoutAndDraw (); - - var expected = @" - ┌──────┐ - │ One │ - │ Two │ - └──────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rectangle (72, 21, 80, 4), pos); - - cm.Hide (); - Assert.Equal (new Point (80, 25), cm.Position); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Show_Ensures_Display_Inside_The_Container_Without_Overlap_The_Host () - { - var view = new View - { - X = Pos.AnchorEnd (10), - Y = Pos.AnchorEnd (1), - Width = 10, - Height = 1, - Text = "View" - }; - - var cm = new ContextMenu - { - Host = view - }; - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("One", "", null), - new MenuItem ("Two", "", null) - ] - ); - var top = new Toplevel (); - top.Add (view); - Application.Begin (top); - - Assert.Equal (new Rectangle (70, 24, 10, 1), view.Frame); - Assert.Equal (Point.Empty, cm.Position); - - cm.Show (menuItems); - Assert.Equal (new Point (70, 24), cm.Position); - top.Draw (); - - var expected = @" - ┌──────┐ - │ One │ - │ Two │ - └──────┘ - View -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rectangle (70, 20, 78, 5), pos); - - cm.Hide (); - Assert.Equal (new Point (70, 24), cm.Position); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Show_Hide_IsShow () - { - ContextMenu cm = new ContextMenu - { - Position = new Point (10, 5) - }; - - var menuItems = new MenuBarItem ( - new MenuItem [] { new ("One", "", null), new ("Two", "", null) } - ); - - Toplevel top = new (); - Application.Begin (top); - cm.Show (menuItems); - Assert.True (ContextMenu.IsShow); - Application.LayoutAndDraw (); - - var expected = @" - ┌──────┐ - │ One │ - │ Two │ - └──────┘ -"; - - DriverAssert.AssertDriverContentsAre (expected, output); - - cm.Hide (); - Assert.False (ContextMenu.IsShow); - - Application.LayoutAndDraw (); - - expected = ""; - - DriverAssert.AssertDriverContentsAre (expected, output); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void UseSubMenusSingleFrame_True_By_Mouse () - { - var cm = new ContextMenu - { - Position = new Point (5, 10), - UseSubMenusSingleFrame = true - }; - - var menuItems = new MenuBarItem ( - "Numbers", - [ - new MenuItem ("One", "", null), - new MenuBarItem ( - "Two", - [ - new MenuItem ( - "Sub-Menu 1", - "", - null - ), - new MenuItem ("Sub-Menu 2", "", null) - ] - ), - new MenuItem ("Three", "", null) - ] - ); - Toplevel top = new (); - RunState rs = Application.Begin (top); - cm.Show (menuItems); - var menu = Application.Top!.SubViews.First (v => v is Menu); - Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame); - Application.LayoutAndDraw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two ►│ - │ Three │ - └────────┘", - output - ); - - // X=5 is the border and so need to use at least one more - Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (6, 13), Flags = MouseFlags.Button1Clicked }); - - var firstIteration = false; - Application.RunIteration (ref rs, firstIteration); - 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 ( - @" - ┌─────────────┐ - │◄ Two │ - ├─────────────┤ - │ Sub-Menu 1 │ - │ Sub-Menu 2 │ - └─────────────┘", - output - ); - - Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (6, 12), Flags = MouseFlags.Button1Clicked }); - - firstIteration = false; - Application.RunIteration (ref rs, firstIteration); - menu = Application.Top!.SubViews.First (v => v is Menu); - Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two ►│ - │ Three │ - └────────┘", - output - ); - - Application.End (rs); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void UseSubMenusSingleFrame_False_By_Mouse () - { - var cm = new ContextMenu - { - Position = new Point (5, 10) - }; - - var menuItems = new MenuBarItem ( - "Numbers", - [ - new MenuItem ("One", "", null), - new MenuBarItem ( - "Two", - [ - new MenuItem ( - "Two-Menu 1", - "", - null - ), - new MenuItem ("Two-Menu 2", "", null) - ] - ), - new MenuBarItem ( - "Three", - [ - new MenuItem ( - "Three-Menu 1", - "", - null - ), - new MenuItem ("Three-Menu 2", "", null) - ] - ) - ] - ); - Toplevel top = new (); - RunState rs = Application.Begin (top); - cm.Show (menuItems); - - - var menu = Application.Top!.SubViews.First (v => v is Menu); - - Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame); - Application.LayoutAndDraw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two ►│ - │ Three ►│ - └────────┘", - output - ); - - Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (6, 13), Flags = MouseFlags.ReportMousePosition }); - - var firstIteration = false; - Application.RunIteration (ref rs, firstIteration); - menu = Application.Top!.SubViews.First (v => v is Menu); - Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two ►│┌─────────────┐ - │ Three ►││ Two-Menu 1 │ - └────────┘│ Two-Menu 2 │ - └─────────────┘", - output - ); - - Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (6, 14), Flags = MouseFlags.ReportMousePosition }); - - firstIteration = false; - Application.RunIteration (ref rs, firstIteration); - menu = Application.Top!.SubViews.First (v => v is Menu); - Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two ►│ - │ Three ►│┌───────────────┐ - └────────┘│ Three-Menu 1 │ - │ Three-Menu 2 │ - └───────────────┘", - output - ); - - Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (6, 13), Flags = MouseFlags.ReportMousePosition }); - - firstIteration = false; - Application.RunIteration (ref rs, firstIteration); - menu = Application.Top!.SubViews.First (v => v is Menu); - Assert.Equal (new Rectangle (5, 11, 10, 5), menu.Frame); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌────────┐ - │ One │ - │ Two ►│┌─────────────┐ - │ Three ►││ Two-Menu 1 │ - └────────┘│ Two-Menu 2 │ - └─────────────┘", - output - ); - - Application.End (rs); - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Handling_TextField_With_Opened_ContextMenu_By_Mouse_HasFocus () - { - var tf1 = new TextField { Width = 10, Text = "TextField 1" }; - var tf2 = new TextField { Y = 2, Width = 10, Text = "TextField 2" }; - var win = new Window (); - win.Add (tf1, tf2); - var rs = Application.Begin (win); - - Assert.True (tf1.HasFocus); - Assert.False (tf2.HasFocus); - Assert.Equal (4, win.SubViews.Count); // TF & TV add autocomplete popup's to their superviews. - Assert.Empty (Application._cachedViewsUnderMouse); - - // Right click on tf2 to open context menu - Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 3), Flags = MouseFlags.Button3Clicked }); - Assert.False (tf1.HasFocus); - Assert.False (tf2.HasFocus); - Assert.Equal (6, win.SubViews.Count); - //Assert.True (tf2.ContextMenu.IsMenuOpen); - Assert.True (win.Focused is Menu); - Assert.True (Application.MouseGrabView is Menu); - Assert.Equal (tf2, Application._cachedViewsUnderMouse.LastOrDefault ()); - - // Click on tf1 to focus it, which cause context menu being closed - Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 1), Flags = MouseFlags.Button1Clicked }); - Assert.True (tf1.HasFocus); - Assert.False (tf2.HasFocus); - Assert.Equal (5, win.SubViews.Count); - - // The last context menu bar opened is always preserved - Assert.NotNull (tf2.ContextMenu); - Assert.Equal (win.Focused, tf1); - Assert.Null (Application.MouseGrabView); - Assert.Equal (tf1, Application._cachedViewsUnderMouse.LastOrDefault ()); - - // Click on tf2 to focus it - Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 3), Flags = MouseFlags.Button1Clicked }); - Assert.False (tf1.HasFocus); - Assert.True (tf2.HasFocus); - Assert.Equal (5, win.SubViews.Count); - - // The last context menu bar opened is always preserved - Assert.NotNull (tf2.ContextMenu); - Assert.Equal (win.Focused, tf2); - Assert.Null (Application.MouseGrabView); - Assert.Equal (tf2, Application._cachedViewsUnderMouse.LastOrDefault ()); - - Application.End (rs); - win.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Empty_Menus_Items_Children_Does_Not_Open_The_Menu () - { - var cm = new ContextMenu (); - Assert.Null (cm.MenuItems); - - var top = new Toplevel (); - Application.Begin (top); - - cm.Show (cm.MenuItems); - Assert.Null (cm.MenuBar); - - top.Dispose (); - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void KeyBindings_Removed_On_Close_ContextMenu () - { - var newFile = false; - var renameFile = false; - var deleteFile = false; - - var cm = new ContextMenu (); - - var menuItems = new MenuBarItem ( - [ - new MenuItem ("New File", string.Empty, New, null, null, Key.N.WithCtrl), - new MenuItem ("Rename File", string.Empty, Rename, null, null, Key.R.WithCtrl), - new MenuItem ("Delete File", string.Empty, Delete, null, null, Key.D.WithCtrl) - ] - ); - var top = new Toplevel (); - Application.Begin (top); - - Assert.Null (cm.MenuBar); - Assert.False (Application.RaiseKeyDownEvent (Key.N.WithCtrl)); - Assert.False (Application.RaiseKeyDownEvent (Key.R.WithCtrl)); - Assert.False (Application.RaiseKeyDownEvent (Key.D.WithCtrl)); - Assert.False (newFile); - Assert.False (renameFile); - Assert.False (deleteFile); - - cm.Show (menuItems); - Assert.True (cm.MenuBar!.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - Assert.True (cm.MenuBar.HotKeyBindings.TryGet (Key.R.WithCtrl, out _)); - Assert.True (cm.MenuBar.HotKeyBindings.TryGet (Key.D.WithCtrl, out _)); - - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithCtrl)); - Application.MainLoop!.RunIteration (); - Assert.True (newFile); - Assert.False (cm.MenuBar!.IsMenuOpen); - cm.Show (menuItems); - Assert.True (Application.RaiseKeyDownEvent (Key.R.WithCtrl)); - Application.MainLoop!.RunIteration (); - Assert.True (renameFile); - Assert.False (cm.MenuBar.IsMenuOpen); - cm.Show (menuItems); - Assert.True (Application.RaiseKeyDownEvent (Key.D.WithCtrl)); - Application.MainLoop!.RunIteration (); - Assert.True (deleteFile); - Assert.False (cm.MenuBar.IsMenuOpen); - - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.R.WithCtrl, out _)); - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.D.WithCtrl, out _)); - - newFile = false; - renameFile = false; - deleteFile = false; - Assert.False (Application.RaiseKeyDownEvent (Key.N.WithCtrl)); - Assert.False (Application.RaiseKeyDownEvent (Key.R.WithCtrl)); - Assert.False (Application.RaiseKeyDownEvent (Key.D.WithCtrl)); - Assert.False (newFile); - Assert.False (renameFile); - Assert.False (deleteFile); - - top.Dispose (); - - void New () { newFile = true; } - - void Rename () { renameFile = true; } - - void Delete () { deleteFile = true; } - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void KeyBindings_With_ContextMenu_And_MenuBar () - { - var newFile = false; - var renameFile = false; - - var menuBar = new MenuBar - { - Menus = - [ - new ( - "File", - new MenuItem [] - { - new ("New", string.Empty, New, null, null, Key.N.WithCtrl) - }) - ] - }; - var cm = new ContextMenu (); - - var menuItems = new MenuBarItem ( - [ - new ("Rename File", string.Empty, Rename, null, null, Key.R.WithCtrl), - ] - ); - var top = new Toplevel (); - top.Add (menuBar); - Application.Begin (top); - - Assert.True (menuBar.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.R.WithCtrl, out _)); - Assert.Null (cm.MenuBar); - - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithCtrl)); - Assert.False (Application.RaiseKeyDownEvent (Key.R.WithCtrl)); - Application.MainLoop!.RunIteration (); - Assert.True (newFile); - Assert.False (renameFile); - - newFile = false; - - cm.Show (menuItems); - Assert.True (menuBar.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.R.WithCtrl, out _)); - Assert.False (cm.MenuBar!.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - Assert.True (cm.MenuBar.HotKeyBindings.TryGet (Key.R.WithCtrl, out _)); - - Assert.True (cm.MenuBar.IsMenuOpen); - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithCtrl)); - Application.MainLoop!.RunIteration (); - Assert.True (newFile); - Assert.False (cm.MenuBar!.IsMenuOpen); - cm.Show (menuItems); - Assert.True (Application.RaiseKeyDownEvent (Key.R.WithCtrl)); - Application.MainLoop!.RunIteration (); - Assert.True (renameFile); - Assert.False (cm.MenuBar.IsMenuOpen); - - Assert.True (menuBar.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.R.WithCtrl, out _)); - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.R.WithCtrl, out _)); - - newFile = false; - renameFile = false; - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithCtrl)); - Assert.False (Application.RaiseKeyDownEvent (Key.R.WithCtrl)); - Application.MainLoop!.RunIteration (); - Assert.True (newFile); - Assert.False (renameFile); - - top.Dispose (); - - void New () { newFile = true; } - - void Rename () { renameFile = true; } - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void KeyBindings_With_Same_Shortcut_ContextMenu_And_MenuBar () - { - var newMenuBar = false; - var newContextMenu = false; - - var menuBar = new MenuBar - { - Menus = - [ - new ( - "File", - new MenuItem [] - { - new ("New", string.Empty, NewMenuBar, null, null, Key.N.WithCtrl) - }) - ] - }; - var cm = new ContextMenu (); - - var menuItems = new MenuBarItem ( - [ - new ("New File", string.Empty, NewContextMenu, null, null, Key.N.WithCtrl), - ] - ); - var top = new Toplevel (); - top.Add (menuBar); - Application.Begin (top); - - Assert.True (menuBar.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - Assert.Null (cm.MenuBar); - - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithCtrl)); - Application.MainLoop!.RunIteration (); - Assert.True (newMenuBar); - Assert.False (newContextMenu); - - newMenuBar = false; - - cm.Show (menuItems); - Assert.True (menuBar.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - Assert.True (cm.MenuBar!.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - - Assert.True (cm.MenuBar.IsMenuOpen); - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithCtrl)); - Application.MainLoop!.RunIteration (); - Assert.False (newMenuBar); - - // The most focused shortcut is executed - Assert.True (newContextMenu); - Assert.False (cm.MenuBar!.IsMenuOpen); - - Assert.True (menuBar.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.N.WithCtrl, out _)); - - newMenuBar = false; - newContextMenu = false; - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithCtrl)); - Application.MainLoop!.RunIteration (); - Assert.True (newMenuBar); - Assert.False (newContextMenu); - - top.Dispose (); - - void NewMenuBar () { newMenuBar = true; } - - void NewContextMenu () { newContextMenu = true; } - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void HotKeys_Removed_On_Close_ContextMenu () - { - var newFile = false; - var renameFile = false; - var deleteFile = false; - - var cm = new ContextMenu (); - - var menuItems = new MenuBarItem ( - [ - new ("_New File", string.Empty, New, null, null), - new ("_Rename File", string.Empty, Rename, null, null), - new ("_Delete File", string.Empty, Delete, null, null) - ] - ); - var top = new Toplevel (); - Application.Begin (top); - - Assert.Null (cm.MenuBar); - Assert.False (Application.RaiseKeyDownEvent (Key.N.WithAlt)); - Assert.False (Application.RaiseKeyDownEvent (Key.R.WithAlt)); - Assert.False (Application.RaiseKeyDownEvent (Key.D.WithAlt)); - Assert.False (newFile); - Assert.False (renameFile); - Assert.False (deleteFile); - - cm.Show (menuItems); - Assert.True (cm.MenuBar!.IsMenuOpen); - Assert.False (cm.MenuBar!.HotKeyBindings.TryGet (Key.N.WithAlt, out _)); - Assert.False (cm.MenuBar!.HotKeyBindings.TryGet (Key.N.NoShift, out _)); - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.R.WithAlt, out _)); - 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.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 _)); - Assert.True (menus [0].HotKeyBindings.TryGet (Key.R.WithAlt, out _)); - Assert.True (menus [0].HotKeyBindings.TryGet (Key.R.NoShift, out _)); - Assert.True (menus [0].HotKeyBindings.TryGet (Key.D.WithAlt, out _)); - Assert.True (menus [0].HotKeyBindings.TryGet (Key.D.NoShift, out _)); - - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithAlt)); - Assert.False (cm.MenuBar!.IsMenuOpen); - Application.MainLoop!.RunIteration (); - Assert.True (newFile); - cm.Show (menuItems); - Assert.True (Application.RaiseKeyDownEvent (Key.R.WithAlt)); - Assert.False (cm.MenuBar.IsMenuOpen); - Application.MainLoop!.RunIteration (); - Assert.True (renameFile); - cm.Show (menuItems); - Assert.True (Application.RaiseKeyDownEvent (Key.D.WithAlt)); - Assert.False (cm.MenuBar.IsMenuOpen); - Application.MainLoop!.RunIteration (); - Assert.True (deleteFile); - - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.N.WithAlt, out _)); - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.N.NoShift, out _)); - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.R.WithAlt, out _)); - 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 _)); - - newFile = false; - renameFile = false; - deleteFile = false; - Assert.False (Application.RaiseKeyDownEvent (Key.N.WithAlt)); - Assert.False (Application.RaiseKeyDownEvent (Key.R.WithAlt)); - Assert.False (Application.RaiseKeyDownEvent (Key.D.WithAlt)); - Assert.False (newFile); - Assert.False (renameFile); - Assert.False (deleteFile); - - top.Dispose (); - - void New () { newFile = true; } - - void Rename () { renameFile = true; } - - void Delete () { deleteFile = true; } - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void HotKeys_With_ContextMenu_And_MenuBar () - { - var newFile = false; - var renameFile = false; - - var menuBar = new MenuBar - { - Menus = - [ - new ( - "_File", - new MenuItem [] - { - new ("_New", string.Empty, New) - }) - ] - }; - var cm = new ContextMenu (); - - var menuItems = new MenuBarItem ( - [ - new MenuBarItem ( - "_Edit", - new MenuItem [] - { - new ("_Rename File", string.Empty, Rename) - } - ) - ] - ); - var top = new Toplevel (); - top.Add (menuBar); - Application.Begin (top); - - Assert.True (menuBar.HotKeyBindings.TryGet (Key.F.WithAlt, out _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.N.WithAlt, out _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.R.WithAlt, out _)); - View [] menus = Application.Top!.SubViews.Where (v => v is Menu m && m.Host == menuBar).ToArray (); - Assert.Empty (menus); - Assert.Null (cm.MenuBar); - - Assert.True (Application.RaiseKeyDownEvent (Key.F.WithAlt)); - Assert.True (menuBar.IsMenuOpen); - Assert.Equal (2, Application.Top!.SubViews.Count); - menus = Application.Top!.SubViews.Where (v => v is Menu m && m.Host == menuBar).ToArray (); - Assert.True (menus [0].HotKeyBindings.TryGet (Key.N.WithAlt, out _)); - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithAlt)); - Assert.False (menuBar.IsMenuOpen); - Assert.False (Application.RaiseKeyDownEvent (Key.R.WithAlt)); - Application.MainLoop!.RunIteration (); - Assert.True (newFile); - Assert.False (renameFile); - - newFile = false; - - cm.Show (menuItems); - 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 _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.N.NoShift, out _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.E.WithAlt, out _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.E.NoShift, out _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.R.WithAlt, out _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.R.NoShift, out _)); - Assert.True (cm.MenuBar!.IsMenuOpen); - Assert.False (cm.MenuBar!.HotKeyBindings.TryGet (Key.F.WithAlt, out _)); - Assert.False (cm.MenuBar!.HotKeyBindings.TryGet (Key.F.NoShift, out _)); - Assert.False (cm.MenuBar!.HotKeyBindings.TryGet (Key.N.WithAlt, out _)); - Assert.False (cm.MenuBar!.HotKeyBindings.TryGet (Key.N.NoShift, out _)); - Assert.False (cm.MenuBar!.HotKeyBindings.TryGet (Key.E.WithAlt, out _)); - 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 (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 _)); - Assert.True (menus [1].HotKeyBindings.TryGet (Key.R.WithAlt, out _)); - Assert.True (menus [1].HotKeyBindings.TryGet (Key.R.NoShift, out _)); - Assert.True (cm.MenuBar.IsMenuOpen); - Assert.True (Application.RaiseKeyDownEvent (Key.F.WithAlt)); - Assert.False (cm.MenuBar.IsMenuOpen); - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithAlt)); - Application.MainLoop!.RunIteration (); - Assert.True (newFile); - - cm.Show (menuItems); - Assert.True (cm.MenuBar.IsMenuOpen); - 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 _)); - Assert.False (menus [0].HotKeyBindings.TryGet (Key.R.WithAlt, out _)); - Assert.False (menus [0].HotKeyBindings.TryGet (Key.R.NoShift, out _)); - Assert.False (menus [1].HotKeyBindings.TryGet (Key.E.WithAlt, out _)); - Assert.False (menus [1].HotKeyBindings.TryGet (Key.E.NoShift, out _)); - Assert.True (menus [1].HotKeyBindings.TryGet (Key.R.WithAlt, out _)); - Assert.True (menus [1].HotKeyBindings.TryGet (Key.R.NoShift, out _)); - Assert.True (Application.RaiseKeyDownEvent (Key.E.NoShift)); - Assert.True (Application.RaiseKeyDownEvent (Key.R.WithAlt)); - Assert.False (cm.MenuBar.IsMenuOpen); - Application.MainLoop!.RunIteration (); - Assert.True (renameFile); - - 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 _)); - Assert.False (menuBar.HotKeyBindings.TryGet (Key.N.NoShift, out _)); - Assert.False (cm.MenuBar.HotKeyBindings.TryGet (Key.E.WithAlt, out _)); - 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 _)); - - newFile = false; - renameFile = false; - Assert.True (Application.RaiseKeyDownEvent (Key.F.WithAlt)); - Assert.True (Application.RaiseKeyDownEvent (Key.N.WithAlt)); - Assert.False (Application.RaiseKeyDownEvent (Key.R.WithAlt)); - Application.MainLoop!.RunIteration (); - Assert.True (newFile); - Assert.False (renameFile); - - top.Dispose (); - - void New () { newFile = true; } - - void Rename () { renameFile = true; } - } - - [Fact (Skip = "Redo for CMv2")] - [AutoInitShutdown] - public void Opened_MenuBar_Is_Closed_When_Another_MenuBar_Is_Opening_Also_By_HotKey () - { - var menuBar = new MenuBar - { - Menus = - [ - new ( - "_File", - new MenuItem [] - { - new ("_New", string.Empty, null) - }) - ] - }; - var cm = new ContextMenu (); - - var menuItems = new MenuBarItem ( - [ - new MenuBarItem ( - "_Edit", - new MenuItem [] - { - new ("_Rename File", string.Empty, null) - } - ) - ] - ); - var top = new Toplevel (); - top.Add (menuBar); - Application.Begin (top); - - Assert.True (Application.RaiseKeyDownEvent (Key.F.WithAlt)); - Assert.True (menuBar.IsMenuOpen); - - cm.Show (menuItems); - Assert.False (menuBar.IsMenuOpen); - Assert.True (cm.MenuBar!.IsMenuOpen); - - Assert.True (Application.RaiseKeyDownEvent (Key.F.WithAlt)); - Assert.True (menuBar.IsMenuOpen); - Assert.False (cm.MenuBar!.IsMenuOpen); - - top.Dispose (); - } - - [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.True (menuBar.IsMenuOpen); - Application.MainLoop.RunIteration (); - Assert.False (actionRaised); - Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 2), Flags = MouseFlags.Button3Released }); - Assert.True (menuBar.IsMenuOpen); - Application.MainLoop.RunIteration (); - Assert.False (actionRaised); - Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 2), Flags = MouseFlags.Button3Clicked }); - Assert.True (menuBar.IsMenuOpen); - Application.MainLoop.RunIteration (); - Assert.False (actionRaised); - - break; - } - - // ContextMenu - Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 4), Flags = cm.MouseFlags }); - Assert.False (menuBar.IsMenuOpen); - Assert.True (cm.MenuBar!.IsMenuOpen); - - switch (button) - { - // Left Button - case 1: - Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 6), Flags = MouseFlags.Button1Pressed }); - Assert.True (cm.MenuBar!.IsMenuOpen); - Application.MainLoop.RunIteration (); - Assert.False (actionRaised); - Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 6), Flags = MouseFlags.Button1Released }); - Assert.True (cm.MenuBar!.IsMenuOpen); - Application.MainLoop.RunIteration (); - Assert.False (actionRaised); - Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 6), 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 (); - } - - [Fact] - [AutoInitShutdown] - public void Menu_Without_SubMenu_Is_Closed_When_Pressing_Key_Right_Or_Key_Left () - { - var cm = new ContextMenu (); - - var menuItems = new MenuBarItem ( - [ - new ("_New", string.Empty, null), - new ("_Save", string.Empty, null) - ] - ); - var top = new Toplevel (); - Application.Begin (top); - - cm.Show (menuItems); - Assert.True (cm.MenuBar!.IsMenuOpen); - - Assert.True (Application.RaiseKeyDownEvent (Key.CursorRight)); - Assert.False (cm.MenuBar!.IsMenuOpen); - - cm.Show (menuItems); - Assert.True (cm.MenuBar!.IsMenuOpen); - - Assert.True (Application.RaiseKeyDownEvent (Key.CursorLeft)); - Assert.False (cm.MenuBar!.IsMenuOpen); - - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Menu_Opened_In_SuperView_With_TabView_Has_Precedence_On_Key_Press () - { - var win = new Window - { - Title = "My Window", - X = 0, - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill () - }; - - // Tab View - var tabView = new TabView - { - X = 1, - Y = 1, - Width = Dim.Fill () - 2, - Height = Dim.Fill () - 2 - }; - tabView.AddTab (new () { DisplayText = "Tab 1" }, true); - tabView.AddTab (new () { DisplayText = "Tab 2" }, false); - win.Add (tabView); - - // Context Menu - var menuItems = new MenuBarItem ( - [ - new ("Item 1", "First item", () => MessageBox.Query ("Action", "Item 1 Clicked", "OK")), - new MenuBarItem ( - "Submenu", - new List - { - new [] - { - new MenuItem ( - "Sub Item 1", - "Submenu item", - () => { MessageBox.Query ("Action", "Sub Item 1 Clicked", "OK"); }) - } - }) - ]); - - var cm = new ContextMenu (); - - win.MouseClick += (s, e) => - { - if (e.Flags.HasFlag (MouseFlags.Button3Clicked)) // Right-click - { - cm.Position = e.Position; - cm.Show (menuItems); - } - }; - Application.Begin (win); - - cm.Show (menuItems); - Assert.True (cm.MenuBar!.IsMenuOpen); - - Assert.True (Application.RaiseKeyDownEvent (Key.CursorDown)); - Assert.True (cm.MenuBar!.IsMenuOpen); - - Assert.True (Application.RaiseKeyDownEvent (Key.CursorUp)); - Assert.True (cm.MenuBar!.IsMenuOpen); - - Assert.True (Application.RaiseKeyDownEvent (Key.CursorDown)); - Assert.True (cm.MenuBar!.IsMenuOpen); - - Assert.True (Application.RaiseKeyDownEvent (Key.CursorRight)); - Assert.True (cm.MenuBar!.IsMenuOpen); - - Assert.True (Application.RaiseKeyDownEvent (Key.CursorLeft)); - Assert.True (cm.MenuBar!.IsMenuOpen); - - Assert.True (Application.RaiseKeyDownEvent (Key.CursorLeft)); - Assert.False (cm.MenuBar!.IsMenuOpen); - Assert.True (tabView.HasFocus); - - win.Dispose (); - } -} diff --git a/Tests/UnitTests/Views/TextViewTests.cs b/Tests/UnitTests/Views/TextViewTests.cs index 02de26bfb..de4db0a04 100644 --- a/Tests/UnitTests/Views/TextViewTests.cs +++ b/Tests/UnitTests/Views/TextViewTests.cs @@ -5533,7 +5533,7 @@ This is the second line. Assert.False (tv.NewKeyDownEvent (Key.F6.WithShift)); Assert.False (tv.NewKeyDownEvent (Application.PrevTabGroupKey)); - Assert.True (tv.NewKeyDownEvent (ContextMenu.DefaultKey)); + Assert.True (tv.NewKeyDownEvent (PopoverMenu.DefaultKey)); Assert.True (tv.ContextMenu != null && tv.ContextMenu.Visible); top.Dispose (); } diff --git a/Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs b/Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs index 060c26f58..17e9d3321 100644 --- a/Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs +++ b/Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs @@ -35,29 +35,29 @@ public class ApplicationPopoverTests } [Fact] - public void ShowPopover_SetsActivePopover () + public void Show_SetsActivePopover () { // Arrange var popover = new Mock ().Object; var popoverManager = new ApplicationPopover (); // Act - popoverManager.ShowPopover (popover); + popoverManager.Show (popover); // Assert Assert.Equal (popover, popoverManager.GetActivePopover ()); } [Fact] - public void HidePopover_ClearsActivePopover () + public void Hide_ClearsActivePopover () { // Arrange var popover = new Mock ().Object; var popoverManager = new ApplicationPopover (); - popoverManager.ShowPopover (popover); + popoverManager.Show (popover); // Act - popoverManager.HidePopover (popover); + popoverManager.Hide (popover); // Assert Assert.Null (popoverManager.GetActivePopover ()); @@ -70,7 +70,7 @@ public class ApplicationPopoverTests // Arrange var popover = new IPopoverTestClass (); var popoverManager = new ApplicationPopover (); - popoverManager.ShowPopover (popover); + popoverManager.Show (popover); // Act popoverManager.DispatchKeyDown (Key.A); @@ -86,7 +86,7 @@ public class ApplicationPopoverTests // Arrange var popover = new IPopoverTestClass (); var popoverManager = new ApplicationPopover (); - popoverManager.ShowPopover (popover); + popoverManager.Show (popover); // Act popoverManager.DispatchKeyDown (Key.N.WithCtrl); @@ -104,7 +104,7 @@ public class ApplicationPopoverTests var activePopover = new IPopoverTestClass () { Id = "activePopover" }; var inactivePopover = new IPopoverTestClass () { Id = "inactivePopover" }; ; var popoverManager = new ApplicationPopover (); - popoverManager.ShowPopover (activePopover); + popoverManager.Show (activePopover); popoverManager.Register (inactivePopover); // Act @@ -124,7 +124,7 @@ public class ApplicationPopoverTests var activePopover = new IPopoverTestClass (); var inactivePopover = new IPopoverTestClass (); var popoverManager = new ApplicationPopover (); - popoverManager.ShowPopover (activePopover); + popoverManager.Show (activePopover); popoverManager.Register (inactivePopover); // Act diff --git a/UICatalog/Scenarios/ContextMenus.cs b/UICatalog/Scenarios/ContextMenus.cs index 0d9d50e38..0f0d24242 100644 --- a/UICatalog/Scenarios/ContextMenus.cs +++ b/UICatalog/Scenarios/ContextMenus.cs @@ -9,7 +9,7 @@ namespace UICatalog.Scenarios; public class ContextMenus : Scenario { [CanBeNull] - private ContextMenuv2 _winContextMenu; + private PopoverMenu _winContextMenu; private TextField _tfTopLeft, _tfTopRight, _tfMiddle, _tfBottomLeft, _tfBottomRight; private readonly List _cultureInfos = Application.SupportedCultures; private readonly Key _winContextMenuKey = Key.Space.WithCtrl; @@ -42,7 +42,7 @@ public class ContextMenus : Scenario { X = Pos.Center (), Y = Pos.Bottom (label), - Text = $"Press '{ContextMenu.DefaultKey}' to open the TextField context menu." + Text = $"Press '{PopoverMenu.DefaultKey}' to open the TextField context menu." }; appWindow.Add (label); diff --git a/UICatalog/Scenarios/MenusV2.cs b/UICatalog/Scenarios/MenusV2.cs index 756efd9df..9239f527b 100644 --- a/UICatalog/Scenarios/MenusV2.cs +++ b/UICatalog/Scenarios/MenusV2.cs @@ -121,8 +121,6 @@ public class MenusV2 : Scenario KeyBindings.Add (PopoverMenu.DefaultKey, Command.Context); - MouseBindings.ReplaceCommands (PopoverMenu.MouseFlags, Command.Context); - AddCommand ( Command.Cancel, ctx => @@ -504,22 +502,6 @@ public class MenusV2 : Scenario menu.Add (deeperDetail, new Line (), shortcut4); } - - /// - protected override void Dispose (bool disposing) - { - if (disposing) - { - // if (FilePopoverMenu is { }) - // { - // FilePopoverMenu.Visible = false; - // FilePopoverMenu?.Dispose (); - // FilePopoverMenu = null; - // } - } - - base.Dispose (disposing); - } } private const string LOGFILE_LOCATION = "./logs"; diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index 1a0871d55..3c079d27e 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -333,39 +333,37 @@ public class Notepad : Scenario return; } - MenuBarItem items; + View [] items; if (e.Tab == null) { - items = new ( - new MenuItem [] { new ("Open", "", () => Open ()) } - ); + items = [new MenuItemv2 ("Open", "", Open)]; } else { var tv = (TabView)sender; var t = (OpenedFile)e.Tab; - items = new ( - new MenuItem [] - { - new ("Save", "", () => Save (_focusedTabView, e.Tab)), - new ("Close", "", () => Close (tv, e.Tab)), - null, - new ("Split Up", "", () => SplitUp (tv, t)), - new ("Split Down", "", () => SplitDown (tv, t)), - new ("Split Right", "", () => SplitRight (tv, t)), - new ("Split Left", "", () => SplitLeft (tv, t)) - } - ); + items = + [ + new MenuItemv2 ("Save", "", () => Save (_focusedTabView, e.Tab)), + new MenuItemv2 ("Close", "", () => Close (tv, e.Tab)), + new Line (), + new MenuItemv2 ("Split Up", "", () => SplitUp (tv, t)), + new MenuItemv2 ("Split Down", "", () => SplitDown (tv, t)), + new MenuItemv2 ("Split Right", "", () => SplitRight (tv, t)), + new MenuItemv2 ("Split Left", "", () => SplitLeft (tv, t)) + ]; + + PopoverMenu? contextMenu = new (items); + + // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused + // and the context menu is disposed when it is closed. + Application.Popover?.Register (contextMenu); + contextMenu?.MakeVisible (e.MouseEvent.ScreenPosition); + + e.MouseEvent.Handled = true; } - - var screen = ((View)sender).ViewportToScreen (e.MouseEvent.Position); - - var contextMenu = new ContextMenu { Position = screen }; - - contextMenu.Show (items); - e.MouseEvent.Handled = true; } private class OpenedFile (Notepad notepad) : Tab diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index a85d30ecc..20b459b93 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Data; +using System.Data; using System.Globalization; -using System.IO; -using System.Linq; using System.Text; using Terminal.Gui; @@ -827,10 +823,10 @@ public class TableEditor : Scenario var ok = new Button { Text = "Ok", IsDefault = true }; ok.Accepting += (s, e) => - { - okPressed = true; - Application.RequestStop (); - }; + { + okPressed = true; + Application.RequestStop (); + }; var cancel = new Button { Text = "Cancel" }; cancel.Accepting += (s, e) => { Application.RequestStop (); }; var d = new Dialog { Title = title, Buttons = [ok, cancel] }; @@ -1025,12 +1021,13 @@ public class TableEditor : Scenario var ok = new Button { Text = "Ok", IsDefault = true }; ok.Accepting += (s, e) => - { - accepted = true; - Application.RequestStop (); - }; + { + accepted = true; + Application.RequestStop (); + }; var cancel = new Button { Text = "Cancel" }; cancel.Accepting += (s, e) => { Application.RequestStop (); }; + var d = new Dialog { Title = prompt, @@ -1212,30 +1209,24 @@ public class TableEditor : Scenario string sort = GetProposedNewSortOrder (clickedCol, out bool isAsc); string colName = _tableView.Table.ColumnNames [clickedCol]; - var contextMenu = new ContextMenu - { - Position = new (e.Position.X + 1, e.Position.Y + 1) - }; + PopoverMenu? contextMenu = new ( + [ + new ( + $"Hide {TrimArrows (colName)}", + "", + () => HideColumn (clickedCol) + ), + new ( + $"Sort {StripArrows (sort)}", + "", + () => SortColumn (clickedCol, sort, isAsc) + ) + ]); - MenuBarItem menuItems = new ( - [ - new ( - $"Hide {TrimArrows (colName)}", - "", - () => HideColumn (clickedCol) - ), - new ( - $"Sort {StripArrows (sort)}", - "", - () => SortColumn ( - clickedCol, - sort, - isAsc - ) - ) - ] - ); - contextMenu.Show (menuItems); + // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused + // and the context menu is disposed when it is closed. + Application.Popover?.Register (contextMenu); + contextMenu?.MakeVisible (new (e.ScreenPosition.X + 1, e.ScreenPosition.Y + 1)); } private void SortColumn (int clickedCol) @@ -1413,7 +1404,7 @@ public class TableEditor : Scenario _checkedFileSystemInfos.Contains, CheckOrUncheckFile ) - { UseRadioButtons = radio }; + { UseRadioButtons = radio }; } else { diff --git a/UICatalog/Scenarios/TreeViewFileSystem.cs b/UICatalog/Scenarios/TreeViewFileSystem.cs index f371a18f8..b194d9517 100644 --- a/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.IO; using System.IO.Abstractions; -using System.Linq; using System.Text; using Terminal.Gui; @@ -37,6 +34,7 @@ public class TreeViewFileSystem : Scenario public override void Main () { Application.Init (); + var win = new Window { Title = GetName (), @@ -49,142 +47,142 @@ public class TreeViewFileSystem : Scenario { Menus = [ - new MenuBarItem ( - "_File", - new MenuItem [] - { - new ( - "_Quit", - $"{Application.QuitKey}", - () => Quit () - ) - } - ), - new MenuBarItem ( - "_View", - new [] - { - _miFullPaths = - new MenuItem ("_Full Paths", "", () => SetFullName ()) - { - Checked = false, CheckType = MenuItemCheckStyle.Checked - }, - _miMultiSelect = new MenuItem ( - "_Multi Select", - "", - () => SetMultiSelect () - ) - { - Checked = true, - CheckType = MenuItemCheckStyle - .Checked - } - } - ), - new MenuBarItem ( - "_Style", - new [] - { - _miShowLines = - new MenuItem ("_Show Lines", "", () => ShowLines ()) - { - Checked = true, CheckType = MenuItemCheckStyle.Checked - }, - null /*separator*/, - _miPlusMinus = - new MenuItem ( - "_Plus Minus Symbols", - "+ -", - () => SetExpandableSymbols ( - (Rune)'+', - (Rune)'-' - ) - ) { Checked = true, CheckType = MenuItemCheckStyle.Radio }, - _miArrowSymbols = - new MenuItem ( - "_Arrow Symbols", - "> v", - () => SetExpandableSymbols ( - (Rune)'>', - (Rune)'v' - ) - ) { Checked = false, CheckType = MenuItemCheckStyle.Radio }, - _miNoSymbols = - new MenuItem ( - "_No Symbols", - "", - () => SetExpandableSymbols ( - default (Rune), - null - ) - ) { Checked = false, CheckType = MenuItemCheckStyle.Radio }, - null /*separator*/, - _miColoredSymbols = - new MenuItem ( - "_Colored Symbols", - "", - () => ShowColoredExpandableSymbols () - ) { Checked = false, CheckType = MenuItemCheckStyle.Checked }, - _miInvertSymbols = - new MenuItem ( - "_Invert Symbols", - "", - () => InvertExpandableSymbols () - ) { Checked = false, CheckType = MenuItemCheckStyle.Checked }, - null /*separator*/, - _miBasicIcons = - new MenuItem ("_Basic Icons", null, SetNoIcons) - { - Checked = false, CheckType = MenuItemCheckStyle.Radio - }, - _miUnicodeIcons = - new MenuItem ("_Unicode Icons", null, SetUnicodeIcons) - { - Checked = false, CheckType = MenuItemCheckStyle.Radio - }, - _miNerdIcons = - new MenuItem ("_Nerd Icons", null, SetNerdIcons) - { - Checked = false, CheckType = MenuItemCheckStyle.Radio - }, - null /*separator*/, - _miLeaveLastRow = - new MenuItem ( - "_Leave Last Row", - "", - () => SetLeaveLastRow () - ) { Checked = true, CheckType = MenuItemCheckStyle.Checked }, - _miHighlightModelTextOnly = - new MenuItem ( - "_Highlight Model Text Only", - "", - () => SetCheckHighlightModelTextOnly () - ) { Checked = false, CheckType = MenuItemCheckStyle.Checked }, - null /*separator*/, - _miCustomColors = - new MenuItem ( - "C_ustom Colors Hidden Files", - "Yellow/Red", - () => SetCustomColors () - ) { Checked = false, CheckType = MenuItemCheckStyle.Checked }, - null /*separator*/, - _miCursor = new MenuItem ( - "Curs_or (MultiSelect only)", - "", - () => SetCursor () - ) { Checked = false, CheckType = MenuItemCheckStyle.Checked } - } - ) + new ( + "_File", + new MenuItem [] + { + new ( + "_Quit", + $"{Application.QuitKey}", + () => Quit () + ) + } + ), + new ( + "_View", + new [] + { + _miFullPaths = + new ("_Full Paths", "", () => SetFullName ()) + { + Checked = false, CheckType = MenuItemCheckStyle.Checked + }, + _miMultiSelect = new ( + "_Multi Select", + "", + () => SetMultiSelect () + ) + { + Checked = true, + CheckType = MenuItemCheckStyle + .Checked + } + } + ), + new ( + "_Style", + new [] + { + _miShowLines = + new ("_Show Lines", "", () => ShowLines ()) + { + Checked = true, CheckType = MenuItemCheckStyle.Checked + }, + null /*separator*/, + _miPlusMinus = + new ( + "_Plus Minus Symbols", + "+ -", + () => SetExpandableSymbols ( + (Rune)'+', + (Rune)'-' + ) + ) { Checked = true, CheckType = MenuItemCheckStyle.Radio }, + _miArrowSymbols = + new ( + "_Arrow Symbols", + "> v", + () => SetExpandableSymbols ( + (Rune)'>', + (Rune)'v' + ) + ) { Checked = false, CheckType = MenuItemCheckStyle.Radio }, + _miNoSymbols = + new ( + "_No Symbols", + "", + () => SetExpandableSymbols ( + default (Rune), + null + ) + ) { Checked = false, CheckType = MenuItemCheckStyle.Radio }, + null /*separator*/, + _miColoredSymbols = + new ( + "_Colored Symbols", + "", + () => ShowColoredExpandableSymbols () + ) { Checked = false, CheckType = MenuItemCheckStyle.Checked }, + _miInvertSymbols = + new ( + "_Invert Symbols", + "", + () => InvertExpandableSymbols () + ) { Checked = false, CheckType = MenuItemCheckStyle.Checked }, + null /*separator*/, + _miBasicIcons = + new ("_Basic Icons", null, SetNoIcons) + { + Checked = false, CheckType = MenuItemCheckStyle.Radio + }, + _miUnicodeIcons = + new ("_Unicode Icons", null, SetUnicodeIcons) + { + Checked = false, CheckType = MenuItemCheckStyle.Radio + }, + _miNerdIcons = + new ("_Nerd Icons", null, SetNerdIcons) + { + Checked = false, CheckType = MenuItemCheckStyle.Radio + }, + null /*separator*/, + _miLeaveLastRow = + new ( + "_Leave Last Row", + "", + () => SetLeaveLastRow () + ) { Checked = true, CheckType = MenuItemCheckStyle.Checked }, + _miHighlightModelTextOnly = + new ( + "_Highlight Model Text Only", + "", + () => SetCheckHighlightModelTextOnly () + ) { Checked = false, CheckType = MenuItemCheckStyle.Checked }, + null /*separator*/, + _miCustomColors = + new ( + "C_ustom Colors Hidden Files", + "Yellow/Red", + () => SetCustomColors () + ) { Checked = false, CheckType = MenuItemCheckStyle.Checked }, + null /*separator*/, + _miCursor = new ( + "Curs_or (MultiSelect only)", + "", + () => SetCursor () + ) { Checked = false, CheckType = MenuItemCheckStyle.Checked } + } + ) ] }; top.Add (menu); - _treeViewFiles = new TreeView { X = 0, Y = 0, Width = Dim.Percent (50), Height = Dim.Fill () }; + _treeViewFiles = new() { X = 0, Y = 0, Width = Dim.Percent (50), Height = Dim.Fill () }; _treeViewFiles.DrawLine += TreeViewFiles_DrawLine; _treeViewFiles.VerticalScrollBar.AutoShow = false; - _detailsFrame = new DetailsFrame (_iconProvider) + _detailsFrame = new (_iconProvider) { X = Pos.Right (_treeViewFiles), Y = 0, Width = Dim.Fill (), Height = Dim.Fill () }; @@ -249,16 +247,16 @@ public class TreeViewFileSystem : Scenario { if (m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) { - return new ColorScheme + return new() { - Focus = new Attribute ( - Color.BrightRed, - _treeViewFiles.ColorScheme.Focus.Background - ), - Normal = new Attribute ( - Color.BrightYellow, - _treeViewFiles.ColorScheme.Normal.Background - ) + Focus = new ( + Color.BrightRed, + _treeViewFiles.ColorScheme.Focus.Background + ), + Normal = new ( + Color.BrightYellow, + _treeViewFiles.ColorScheme.Normal.Background + ) }; ; @@ -266,16 +264,16 @@ public class TreeViewFileSystem : Scenario if (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) { - return new ColorScheme + return new() { - Focus = new Attribute ( - Color.BrightRed, - _treeViewFiles.ColorScheme.Focus.Background - ), - Normal = new Attribute ( - Color.BrightYellow, - _treeViewFiles.ColorScheme.Normal.Background - ) + Focus = new ( + Color.BrightRed, + _treeViewFiles.ColorScheme.Focus.Background + ), + Normal = new ( + Color.BrightYellow, + _treeViewFiles.ColorScheme.Normal.Background + ) }; ; @@ -417,13 +415,13 @@ public class TreeViewFileSystem : Scenario private void ShowContextMenu (Point screenPoint, IFileSystemInfo forObject) { - var menu = new ContextMenu { Position = screenPoint }; + PopoverMenu? contextMenu = new ([new ("Properties", $"Show {forObject.Name} properties", () => ShowPropertiesOf (forObject))]); - var menuItems = new MenuBarItem ( - new [] { new MenuItem ("Properties", null, () => ShowPropertiesOf (forObject)) } - ); + // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused + // and the context menu is disposed when it is closed. + Application.Popover?.Register (contextMenu); - Application.Invoke (() => menu.Show (menuItems)); + Application.Invoke (() => contextMenu?.MakeVisible (screenPoint)); } private void ShowLines () @@ -477,10 +475,10 @@ public class TreeViewFileSystem : Scenario } ShowContextMenu ( - new Point ( - 5 + _treeViewFiles.Frame.X, - location.Value + _treeViewFiles.Frame.Y + 2 - ), + new ( + 5 + _treeViewFiles.Frame.X, + location.Value + _treeViewFiles.Frame.Y + 2 + ), selected ); } @@ -500,10 +498,10 @@ public class TreeViewFileSystem : Scenario } ShowContextMenu ( - new Point ( - obj.Position.X + _treeViewFiles.Frame.X, - obj.Position.Y + _treeViewFiles.Frame.Y + 2 - ), + new ( + obj.Position.X + _treeViewFiles.Frame.X, + obj.Position.Y + _treeViewFiles.Frame.Y + 2 + ), rightClicked ); } @@ -543,7 +541,7 @@ public class TreeViewFileSystem : Scenario if (_fileInfo is IFileInfo f) { Title = $"{_iconProvider.GetIconWithOptionalSpace (f)}{f.Name}".Trim (); - sb = new StringBuilder (); + sb = new (); sb.AppendLine ($"Path:\n {f.FullName}\n"); sb.AppendLine ($"Size:\n {f.Length:N0} bytes\n"); sb.AppendLine ($"Modified:\n {f.LastWriteTime}\n"); @@ -553,7 +551,7 @@ public class TreeViewFileSystem : Scenario if (_fileInfo is IDirectoryInfo dir) { Title = $"{_iconProvider.GetIconWithOptionalSpace (dir)}{dir.Name}".Trim (); - sb = new StringBuilder (); + sb = new (); sb.AppendLine ($"Path:\n {dir?.FullName}\n"); sb.AppendLine ($"Modified:\n {dir.LastWriteTime}\n"); sb.AppendLine ($"Created:\n {dir.CreationTime}\n"); diff --git a/docfx/docs/migratingfromv1.md b/docfx/docs/migratingfromv1.md index 56506e584..374d1e9f0 100644 --- a/docfx/docs/migratingfromv1.md +++ b/docfx/docs/migratingfromv1.md @@ -470,6 +470,24 @@ In v1, only Views derived from `Toplevel` could be overlapped. In v2, any view c v1 conflated the concepts of +## `ContextMenu` replaced by `PopoverMenu` + +`PopoverMenu` replaces `ContrextMenu`. + +## `MenuItem` is now based on `Shortcut` + + +```diff +new ( + Strings.charMapCopyGlyph, + "", + CopyGlyph, +- null, +- null, + (KeyCode)Key.G.WithCtrl + ), +``` + ## Others... * `View` and all subclasses support `IDisposable` and must be disposed (by calling `view.Dispose ()`) by whatever code owns the instance when the instance is longer needed.