Skip to content

Commit 4cc4409

Browse files
authored
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
1 parent 39d4c7d commit 4cc4409

40 files changed

+710
-3728
lines changed

Terminal.Gui/Application/Application.Initialization.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ internal static void InternalInit (
8282
ResetState (ignoreDisposed: true);
8383
}
8484

85+
Debug.Assert (Navigation is null);
8586
Navigation = new ();
87+
88+
Debug.Assert(Popover is null);
8689
Popover = new ();
8790

8891
// For UnitTests

Terminal.Gui/Application/Application.Run.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ public static void End (RunState runState)
564564
{
565565
ArgumentNullException.ThrowIfNull (runState);
566566

567-
Popover?.HidePopover (Popover?.GetActivePopover ());
567+
Popover?.Hide (Popover?.GetActivePopover ());
568568

569569
runState.Toplevel.OnUnloaded ();
570570

Terminal.Gui/Application/Application.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ internal static void ResetState (bool ignoreDisposed = false)
153153
{
154154
popover.Visible = false;
155155
}
156+
Popover?.Dispose ();
156157
Popover = null;
157158

158159
TopLevels.Clear ();

Terminal.Gui/Application/ApplicationPopover.cs

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
#nullable enable
22

3-
using System.Diagnostics;
4-
53
namespace Terminal.Gui;
64

75
/// <summary>
8-
/// Helper class for support of <see cref="IPopover"/> views for <see cref="Application"/>. Held by <see cref="Application.Popover"/>
6+
/// Helper class for support of <see cref="IPopover"/> views for <see cref="Application"/>. Held by
7+
/// <see cref="Application.Popover"/>
98
/// </summary>
10-
public class ApplicationPopover
9+
public sealed class ApplicationPopover : IDisposable
1110
{
1211
/// <summary>
1312
/// Initializes a new instance of the <see cref="ApplicationPopover"/> class.
@@ -16,27 +15,41 @@ public ApplicationPopover () { }
1615

1716
private readonly List<IPopover> _popovers = [];
1817

19-
/// <summary></summary>
18+
/// <summary>
19+
/// Gets the list of popovers registered with the application.
20+
/// </summary>
2021
public IReadOnlyCollection<IPopover> Popovers => _popovers.AsReadOnly ();
2122

2223
/// <summary>
2324
/// Registers <paramref name="popover"/> with the application.
24-
/// This enables the popover to receive keyboard events even when when it is not active.
25+
/// This enables the popover to receive keyboard events even when it is not active.
2526
/// </summary>
27+
/// <remarks>
28+
/// When a popover is registered, the View instance lifetime is managed by the application. Call
29+
/// <see cref="DeRegister"/>
30+
/// to manage the lifetime of the popover directly.
31+
/// </remarks>
2632
/// <param name="popover"></param>
27-
public void Register (IPopover? popover)
33+
/// <returns><paramref name="popover"/>, after it has been registered.</returns>
34+
public IPopover? Register (IPopover? popover)
2835
{
2936
if (popover is { } && !_popovers.Contains (popover))
3037
{
3138
_popovers.Add (popover);
32-
3339
}
40+
41+
return popover;
3442
}
3543

3644
/// <summary>
3745
/// De-registers <paramref name="popover"/> with the application. Use this to remove the popover and it's
3846
/// keyboard bindings from the application.
3947
/// </summary>
48+
/// <remarks>
49+
/// When a popover is registered, the View instance lifetime is managed by the application. Call
50+
/// <see cref="DeRegister"/>
51+
/// to manage the lifetime of the popover directly.
52+
/// </remarks>
4053
/// <param name="popover"></param>
4154
/// <returns></returns>
4255
public bool DeRegister (IPopover? popover)
@@ -61,22 +74,25 @@ public bool DeRegister (IPopover? popover)
6174
/// <summary>
6275
/// Gets the active popover, if any.
6376
/// </summary>
77+
/// <remarks>
78+
/// Note, the active pop over does not necessarily to be registered with the application.
79+
/// </remarks>
6480
/// <returns></returns>
6581
public IPopover? GetActivePopover () { return _activePopover; }
6682

6783
/// <summary>
68-
/// Shows <paramref name="popover"/>. IPopover implementations should use OnVisibleChnaged/VisibleChanged to be
84+
/// Shows <paramref name="popover"/>. IPopover implementations should use OnVisibleChanaged/VisibleChanged to be
6985
/// notified when the user has done something to cause the popover to be hidden.
7086
/// </summary>
7187
/// <remarks>
7288
/// <para>
73-
/// Note, this API calls <see cref="Register"/>. To disable the popover from processing keyboard events,
89+
/// This API calls <see cref="Register"/>. To disable the popover from processing keyboard events,
7490
/// either call <see cref="DeRegister"/> to
7591
/// remove the popover from the application or set <see cref="View.Enabled"/> to <see langword="false"/>.
7692
/// </para>
7793
/// </remarks>
7894
/// <param name="popover"></param>
79-
public void ShowPopover (IPopover? popover)
95+
public void Show (IPopover? popover)
8096
{
8197
// If there's an existing popover, hide it.
8298
if (_activePopover is View popoverView)
@@ -87,8 +103,6 @@ public void ShowPopover (IPopover? popover)
87103

88104
if (popover is View newPopover)
89105
{
90-
Register (popover);
91-
92106
if (!newPopover.IsInitialized)
93107
{
94108
newPopover.BeginInit ();
@@ -103,10 +117,11 @@ public void ShowPopover (IPopover? popover)
103117

104118
/// <summary>
105119
/// Causes the specified popover to be hidden.
106-
/// If the popover is dervied from <see cref="PopoverBaseImpl"/>, this is the same as setting <see cref="View.Visible"/> to <see langword="false"/>.
120+
/// If the popover is dervied from <see cref="PopoverBaseImpl"/>, this is the same as setting
121+
/// <see cref="View.Visible"/> to <see langword="false"/>.
107122
/// </summary>
108123
/// <param name="popover"></param>
109-
public void HidePopover (IPopover? popover)
124+
public void Hide (IPopover? popover)
110125
{
111126
// If there's an existing popover, hide it.
112127
if (_activePopover is View popoverView && popoverView == popover)
@@ -117,7 +132,6 @@ public void HidePopover (IPopover? popover)
117132
}
118133
}
119134

120-
121135
/// <summary>
122136
/// Called when the user presses a key. Dispatches the key to the active popover, if any,
123137
/// otherwise to the popovers in the order they were registered. Inactive popovers only get hotkeys.
@@ -127,9 +141,11 @@ public void HidePopover (IPopover? popover)
127141
internal bool DispatchKeyDown (Key key)
128142
{
129143
// Do active first - Active gets all key down events.
130-
if (GetActivePopover () as View is { Visible: true } visiblePopover)
144+
var activePopover = GetActivePopover () as View;
145+
146+
if (activePopover is { Visible: true })
131147
{
132-
if (visiblePopover.NewKeyDownEvent (key))
148+
if (activePopover.NewKeyDownEvent (key))
133149
{
134150
return true;
135151
}
@@ -141,7 +157,7 @@ internal bool DispatchKeyDown (Key key)
141157

142158
foreach (IPopover popover in _popovers)
143159
{
144-
if (GetActivePopover () == popover || popover is not View popoverView)
160+
if (popover == activePopover || popover is not View popoverView)
145161
{
146162
continue;
147163
}
@@ -157,4 +173,18 @@ internal bool DispatchKeyDown (Key key)
157173

158174
return hotKeyHandled is true;
159175
}
176+
177+
/// <inheritdoc/>
178+
public void Dispose ()
179+
{
180+
foreach (IPopover popover in _popovers)
181+
{
182+
if (popover is View view)
183+
{
184+
view.Dispose ();
185+
}
186+
}
187+
188+
_popovers.Clear ();
189+
}
160190
}

Terminal.Gui/Application/PopoverBaseImpl.cs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,18 @@ namespace Terminal.Gui;
66
/// </summary>
77
/// <remarks>
88
/// <para>
9-
/// To show a Popover, use <see cref="ApplicationPopover.ShowPopover"/>. To hide a popover,
10-
/// call <see cref="ApplicationPopover.ShowPopover"/> with <see langword="null"/> set <see cref="View.Visible"/> to <see langword="false"/>.
9+
/// To show a Popover, use <see cref="ApplicationPopover.Show"/>. To hide a popover,
10+
/// call <see cref="ApplicationPopover.Show"/> with <see langword="null"/> set <see cref="View.Visible"/> to <see langword="false"/>.
1111
/// </para>
1212
/// <para>
13-
/// If the user clicks anywhere not occulded by a SubView of the Popover, presses <see cref="Application.QuitKey"/>,
13+
/// If the user clicks anywhere not occluded by a SubView of the Popover, presses <see cref="Application.QuitKey"/>,
1414
/// or causes another popover to show, the Popover will be hidden.
1515
/// </para>
1616
/// </remarks>
17-
1817
public abstract class PopoverBaseImpl : View, IPopover
1918
{
2019
/// <summary>
21-
///
20+
/// Creates a new PopoverBaseImpl.
2221
/// </summary>
2322
protected PopoverBaseImpl ()
2423
{
@@ -28,10 +27,10 @@ protected PopoverBaseImpl ()
2827
Height = Dim.Fill ();
2928
ViewportSettings = ViewportSettings.Transparent | ViewportSettings.TransparentMouse;
3029

31-
//// TODO: Add a diagnostic setting for this?
32-
TextFormatter.VerticalAlignment = Alignment.End;
33-
TextFormatter.Alignment = Alignment.End;
34-
base.Text = "popover";
30+
// TODO: Add a diagnostic setting for this?
31+
//TextFormatter.VerticalAlignment = Alignment.End;
32+
//TextFormatter.Alignment = Alignment.End;
33+
//base.Text = "popover";
3534

3635
AddCommand (Command.Quit, Quit);
3736
KeyBindings.Add (Application.QuitKey, Command.Quit);
@@ -55,24 +54,13 @@ protected PopoverBaseImpl ()
5554
protected override bool OnVisibleChanging ()
5655
{
5756
bool ret = base.OnVisibleChanging ();
58-
if (!ret & !Visible)
57+
if (!ret && !Visible)
5958
{
60-
// Whenvver visible is changing to true, we need to resize;
59+
// Whenever visible is changing to true, we need to resize;
6160
// it's our only chance because we don't get laid out until we're visible
6261
Layout (Application.Screen.Size);
6362
}
6463

6564
return ret;
6665
}
67-
68-
// TODO: Pretty sure this is not needed. set_Visible SetFocus already
69-
///// <inheritdoc />
70-
//protected override void OnVisibleChanged ()
71-
//{
72-
// base.OnVisibleChanged ();
73-
// if (Visible)
74-
// {
75-
// //SetFocus ();
76-
// }
77-
//}
7866
}

Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#nullable enable
22
using System.Collections.Concurrent;
3+
using System.Diagnostics;
34
using System.Diagnostics.CodeAnalysis;
45
using Microsoft.Extensions.Logging;
56

@@ -63,7 +64,10 @@ public override void Init (IConsoleDriver? driver = null, string? driverName = n
6364
_driverName = driverName;
6465
}
6566

67+
Debug.Assert(Application.Navigation is null);
6668
Application.Navigation = new ();
69+
70+
Debug.Assert (Application.Popover is null);
6771
Application.Popover = new ();
6872

6973
Application.AddKeyBindings ();

Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ static Colors ()
4444
/// <item>
4545
/// <term>Menu</term>
4646
/// <description>
47-
/// The menu color scheme; used for <see cref="MenuBar"/>, <see cref="ContextMenu"/>, and
47+
/// The menu color scheme; used for <see cref="Menu"/>, <see cref="MenuBar"/>, and
4848
/// <see cref="StatusBar"/>.
4949
/// </description>
5050
/// </item>

Terminal.Gui/Resources/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434

3535
// --------------- View Specific Settings ---------------
36-
"ContextMenu.DefaultKey": "Shift+F10",
36+
"PopoverMenu.DefaultKey": "Shift+F10",
3737
"FileDialog.MaxSearchResults": 10000,
3838
"FileDialogStyle.DefaultUseColors": false,
3939
"FileDialogStyle.DefaultUseUnicodeCharacters": false,

Terminal.Gui/Terminal.Gui.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
<!-- Assembly names for which internal items are visible -->
8282
<!-- =================================================================== -->
8383
<ItemGroup>
84-
<InternalsVisibleTo Include="Benchmarks"/>
84+
<InternalsVisibleTo Include="Benchmarks" />
8585
<InternalsVisibleTo Include="UnitTests" />
8686
<InternalsVisibleTo Include="UnitTests.Parallelizable" />
8787
<InternalsVisibleTo Include="StressTests" />

Terminal.Gui/Views/CharMap/CharMap.cs

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ public class CharMap : View, IDesignable
1818
private const int HEADER_HEIGHT = 1; // Height of the header
1919
private int _rowHeight = 1; // Height of each row of 16 glyphs - changing this is not tested
2020

21-
private ContextMenu _contextMenu = new ();
22-
2321
/// <summary>
2422
/// Initializes a new instance.
2523
/// </summary>
@@ -58,7 +56,7 @@ public CharMap ()
5856
KeyBindings.Add (Key.PageDown, Command.PageDown);
5957
KeyBindings.Add (Key.Home, Command.Start);
6058
KeyBindings.Add (Key.End, Command.End);
61-
KeyBindings.Add (ContextMenu.DefaultKey, Command.Context);
59+
KeyBindings.Add (PopoverMenu.DefaultKey, Command.Context);
6260

6361
MouseBindings.Add (MouseFlags.Button1DoubleClicked, Command.Accept);
6462
MouseBindings.ReplaceCommands (MouseFlags.Button3Clicked, Command.Context);
@@ -505,32 +503,20 @@ public static string ToCamelCase (string str)
505503

506504
SelectedCodePoint = newCodePoint;
507505

508-
_contextMenu = new ()
509-
{
510-
Position = ViewportToScreen (GetCursor (SelectedCodePoint))
511-
};
506+
// This demonstrates how to create an ephemeral Popover; one that exists
507+
// ony as long as the popover is visible.
508+
// Note, for ephemeral Popovers, hotkeys are not supported.
509+
PopoverMenu? contextMenu = new (
510+
[
511+
new (Strings.charMapCopyGlyph, string.Empty, CopyGlyph),
512+
new (Strings.charMapCopyCP, string.Empty, CopyCodePoint)
513+
]);
514+
515+
// Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused
516+
// and the context menu is disposed when it is closed.
517+
Application.Popover?.Register (contextMenu);
512518

513-
MenuBarItem menuItems = new (
514-
[
515-
new (
516-
Strings.charMapCopyGlyph,
517-
"",
518-
CopyGlyph,
519-
null,
520-
null,
521-
(KeyCode)Key.G.WithCtrl
522-
),
523-
new (
524-
Strings.charMapCopyCP,
525-
"",
526-
CopyCodePoint,
527-
null,
528-
null,
529-
(KeyCode)Key.P.WithCtrl
530-
)
531-
]
532-
);
533-
_contextMenu.Show (menuItems);
519+
contextMenu?.MakeVisible (ViewportToScreen (GetCursor (SelectedCodePoint)));
534520

535521
return true;
536522
}

0 commit comments

Comments
 (0)