Files
Terminal.Gui/Examples/UICatalog/Scenarios/ContextMenus.cs
Copilot dd12df7fb7 Fixes #4176. Removes View.MouseClick (#4450)
* Initial plan

* Add comprehensive MouseClick removal analysis

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Fix code review feedback on analysis document

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add actual line numbers to analysis document

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Final polish on analysis document - clarify TextView

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Phase 3: Port 6 simple Views from MouseClick to Selecting/MouseBindings

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Phase 2: Port 10 Scenarios from MouseClick to Selecting/remove

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Update analysis document - Phases 2 & 3 complete

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Fix failing tests - Button multi-button support and Label focus handling

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Phase 4: Port ScrollBar from OnMouseClick to OnSelecting; remove analysis doc

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Phase 5: Update/remove tests that explicitly test MouseClick API

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Code cleanup

* Remove MouseClick event

Consolidated mouse event handling by removing the `MouseClick`
event and integrating its functionality into the `MouseEvent`
event. Simplified `MouseEventArgs` documentation and added
support for invoking commands bound to mouse events.

Reorganized code by removing `Mouse Pressed Events` and
`Mouse Click Events` regions, introducing a new `WhenGrabbed
Handlers` region. Updated tests to replace `MouseClick` with
`MouseEvent`, adjusted test logic, and improved variable
naming for clarity.

Removed redundant assertions and unused code related to
`MouseClick`. Improved event propagation logic to ensure
proper handling of unhandled events. Performed general code
cleanup to enhance readability and maintainability.

* Updated deep dives.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tig <585482+tig@users.noreply.github.com>
Co-authored-by: Tig <tig@users.noreply.github.com>
2025-12-06 14:41:20 -07:00

275 lines
11 KiB
C#

#nullable enable
using System.Globalization;
using JetBrains.Annotations;
// ReSharper disable AccessToDisposedClosure
namespace UICatalog.Scenarios;
[ScenarioMetadata ("ContextMenus", "Context Menu Sample.")]
[ScenarioCategory ("Menus")]
public class ContextMenus : Scenario
{
private PopoverMenu? _winContextMenu;
private TextField? _tfTopLeft, _tfTopRight, _tfMiddle, _tfBottomLeft, _tfBottomRight;
private readonly List<CultureInfo>? _cultureInfos = Application.SupportedCultures;
private readonly Key _winContextMenuKey = Key.Space.WithCtrl;
private Window? _appWindow;
public override void Main ()
{
// Init
Application.Init ();
// Setup - Create a top-level application window and configure it.
_appWindow = new ()
{
Title = GetQuitKeyAndName (),
Arrangement = ViewArrangement.Fixed,
SchemeName = "Runnable"
};
_appWindow.Initialized += AppWindowOnInitialized;
// Run - Start the application.
Application.Run (_appWindow);
_appWindow.Dispose ();
_appWindow.KeyDown -= OnAppWindowOnKeyDown;
_appWindow.Selecting -= OnAppWindowOnSelecting;
_winContextMenu?.Dispose ();
// Shutdown - Calling Application.Shutdown is required.
Application.Shutdown ();
return;
void AppWindowOnInitialized (object? sender, EventArgs e)
{
var text = "Context Menu";
var width = 20;
CreateWinContextMenu (Application.Instance);
var label = new Label
{
X = Pos.Center (), Y = 1, Text = $"Press '{_winContextMenuKey}' to open the Window context menu."
};
_appWindow.Add (label);
label = new ()
{
X = Pos.Center (),
Y = Pos.Bottom (label),
Text = $"Press '{PopoverMenu.DefaultKey}' to open the TextField context menu."
};
_appWindow.Add (label);
_tfTopLeft = new () { Id = "_tfTopLeft", Width = width, Text = text };
_appWindow.Add (_tfTopLeft);
_tfTopRight = new () { Id = "_tfTopRight", X = Pos.AnchorEnd (width), Width = width, Text = text };
_appWindow.Add (_tfTopRight);
_tfMiddle = new () { Id = "_tfMiddle", X = Pos.Center (), Y = Pos.Center (), Width = width, Text = text };
_appWindow.Add (_tfMiddle);
_tfBottomLeft = new () { Id = "_tfBottomLeft", Y = Pos.AnchorEnd (1), Width = width, Text = text };
_appWindow.Add (_tfBottomLeft);
_tfBottomRight = new () { Id = "_tfBottomRight", X = Pos.AnchorEnd (width), Y = Pos.AnchorEnd (1), Width = width, Text = text };
_appWindow.Add (_tfBottomRight);
_appWindow.KeyDown += OnAppWindowOnKeyDown;
_appWindow.Selecting += OnAppWindowOnSelecting;
CultureInfo originalCulture = Thread.CurrentThread.CurrentUICulture;
_appWindow.IsRunningChanged += (_, e) => {
if (!e.Value)
{
Thread.CurrentThread.CurrentUICulture = originalCulture;
} };
}
void OnAppWindowOnSelecting (object? s, CommandEventArgs e)
{
if (e.Context is CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
{
if (mouseArgs.Flags == MouseFlags.Button3Clicked)
{
// ReSharper disable once AccessToDisposedClosure
_winContextMenu?.MakeVisible (mouseArgs.ScreenPosition);
e.Handled = true;
}
}
}
void OnAppWindowOnKeyDown (object? s, Key e)
{
if (e == _winContextMenuKey)
{
// ReSharper disable once AccessToDisposedClosure
_winContextMenu?.MakeVisible ();
e.Handled = true;
}
}
}
private void CreateWinContextMenu (IApplication? app)
{
_winContextMenu = new (
[
new MenuItem
{
Title = "C_ultures",
SubMenu = GetSupportedCultureMenu (),
},
new Line (),
new MenuItem
{
Title = "_Configuration...",
HelpText = "Show configuration",
Action = () => MessageBox.Query (app,
50,
10,
"Configuration",
"This would be a configuration dialog",
"Ok"
)
},
new MenuItem
{
Title = "M_ore options",
SubMenu = new (
[
new ()
{
Title = "_Setup...",
HelpText = "Perform setup",
Action = () => MessageBox
.Query (app,
50,
10,
"Setup",
"This would be a setup dialog",
"Ok"
),
Key = Key.T.WithCtrl
},
new ()
{
Title = "_Maintenance...",
HelpText = "Maintenance mode",
Action = () => MessageBox
.Query (app,
50,
10,
"Maintenance",
"This would be a maintenance dialog",
"Ok"
)
}
])
},
new Line (),
new MenuItem
{
Title = "_Quit",
Action = () => Application.RequestStop ()
}
])
{
Key = _winContextMenuKey
};
Application.Popover?.Register (_winContextMenu);
}
private Menu GetSupportedCultureMenu ()
{
List<MenuItem> supportedCultures = [];
int index = -1;
foreach (CultureInfo c in _cultureInfos!)
{
MenuItem culture = new ();
culture.CommandView = new CheckBox { CanFocus = false };
if (index == -1)
{
// Create English because GetSupportedCultures doesn't include it
culture.Id = "_English";
culture.Title = "_English";
culture.HelpText = "en-US";
((CheckBox)culture.CommandView).CheckedState =
Thread.CurrentThread.CurrentUICulture.Name == "en-US" ? CheckState.Checked : CheckState.UnChecked;
CreateAction (supportedCultures, culture);
supportedCultures.Add (culture);
index++;
culture = new ();
culture.CommandView = new CheckBox { CanFocus = false };
}
culture.Id = $"_{c.Parent.EnglishName}";
culture.Title = $"_{c.Parent.EnglishName}";
culture.HelpText = c.Name;
((CheckBox)culture.CommandView).CheckedState =
Thread.CurrentThread.CurrentUICulture.Name == culture.HelpText ? CheckState.Checked : CheckState.UnChecked;
CreateAction (supportedCultures, culture);
supportedCultures.Add (culture);
}
Menu menu = new (supportedCultures.ToArray ());
return menu;
void CreateAction (List<MenuItem> cultures, MenuItem culture)
{
culture.Action += () =>
{
Thread.CurrentThread.CurrentUICulture = new (culture.HelpText);
foreach (MenuItem item in cultures)
{
((CheckBox)item.CommandView).CheckedState =
Thread.CurrentThread.CurrentUICulture.Name == item.HelpText ? CheckState.Checked : CheckState.UnChecked;
}
};
}
}
public override List<Key> GetDemoKeyStrokes ()
{
List<Key> keys =
[
Key.F10.WithShift,
Key.Esc,
Key.Space.WithCtrl,
Key.CursorDown,
Key.Enter,
Key.F10.WithShift,
Key.Esc,
Key.Tab,
Key.Space.WithCtrl,
Key.CursorDown,
Key.CursorDown,
Key.Enter,
Key.F10.WithShift,
Key.Esc,
Key.Tab,
Key.Space.WithCtrl,
Key.CursorDown,
Key.CursorDown,
Key.CursorDown,
Key.Enter,
Key.F10.WithShift,
Key.Esc
];
return keys;
}
}