Files
Terminal.Gui/Examples/UICatalog/Scenarios/ContextMenus.cs
Tig 0f72cf8a74 Fixes #4425 - ApplicationImpl internal (#4426)
* Pulled from v2_release

* Refactor migration guide for Terminal.Gui v2

Restructured and expanded the migration guide to provide a comprehensive resource for transitioning from Terminal.Gui v1 to v2. Key updates include:

- Added a Table of Contents for easier navigation.
- Summarized major architectural changes in v2, including the instance-based application model, IRunnable architecture, and 24-bit TrueColor support.
- Updated examples to reflect new patterns, such as initializers replacing constructors and explicit disposal using `IDisposable`.
- Documented changes to the layout system, including the removal of `Absolute`/`Computed` styles and the introduction of `Viewport`.
- Standardized event patterns to use `object sender, EventArgs args`.
- Detailed updates to the Keyboard, Mouse, and Navigation APIs, including configurable key bindings and viewport-relative mouse coordinates.
- Replaced legacy components like `ScrollView` and `ContextMenu` with built-in scrolling and `PopoverMenu`.
- Clarified disposal rules and introduced best practices for resource management.
- Provided a complete migration example and a summary of breaking changes.

This update aims to simplify the migration process by addressing breaking changes, introducing new features, and aligning with modern .NET conventions.

* Refactor to use Application.Instance for lifecycle management

Replaced all occurrences of `ApplicationImpl.Instance` with the new `Application.Instance` property across the codebase to align with the updated application lifecycle model.

Encapsulated the `ApplicationImpl` class by making it `internal`, ensuring it is no longer directly accessible outside its assembly. Introduced the `[Obsolete]` `Application.Instance` property as a backward-compatible singleton for the legacy static `Application` model, while encouraging the use of `Application.Create()` for new code.

Updated `MessageBox` methods to use `Application.Instance` for consistent modal dialog management. Improved documentation to reflect these changes and emphasize the transition to the instance-based application model.

Performed code cleanup in multiple classes to ensure consistency and maintainability. These changes maintain backward compatibility while preparing the codebase for the eventual removal of the legacy `ApplicationImpl` class.

* Fix doc bug

* - Removed obsolete `.cd` class diagram files.
- Introduced `IRunnable` interface for decoupling component execution.
- Added fluent API for running dialogs and retrieving results.
- Enhanced `View` with `App` and `Driver` properties for better decoupling.
- Improved testability with support for mock and real applications.
- Implemented `IDisposable` for proper resource cleanup.
- Replaced `RunnableSessionStack` with `SessionStack` for session management.
- Updated driver architecture to align with the new model.
- Scoped `IKeyboard` to application contexts for modularity.
- Updated documentation with migration strategies and best practices.

These changes modernize the library, improve maintainability, and align with current development practices.
2025-12-01 14:40:31 -07:00

279 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.MouseClick -= OnAppWindowOnMouseClick;
_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.MouseClick += OnAppWindowOnMouseClick;
CultureInfo originalCulture = Thread.CurrentThread.CurrentUICulture;
_appWindow.IsRunningChanged += (s, e) => {
if (!e.Value)
{
Thread.CurrentThread.CurrentUICulture = originalCulture;
} };
}
void OnAppWindowOnMouseClick (object? s, MouseEventArgs e)
{
if (e.Flags == MouseFlags.Button3Clicked)
{
// ReSharper disable once AccessToDisposedClosure
_winContextMenu?.MakeVisible (e.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 MenuItem
{
Title = "_Setup...",
HelpText = "Perform setup",
Action = () => MessageBox
.Query (app,
50,
10,
"Setup",
"This would be a setup dialog",
"Ok"
),
Key = Key.T.WithCtrl
},
new MenuItem
{
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 GetSupportedCutures 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 = new ();
keys.Add (Key.F10.WithShift);
keys.Add (Key.Esc);
keys.Add (Key.Space.WithCtrl);
keys.Add (Key.CursorDown);
keys.Add (Key.Enter);
keys.Add (Key.F10.WithShift);
keys.Add (Key.Esc);
keys.Add (Key.Tab);
keys.Add (Key.Space.WithCtrl);
keys.Add (Key.CursorDown);
keys.Add (Key.CursorDown);
keys.Add (Key.Enter);
keys.Add (Key.F10.WithShift);
keys.Add (Key.Esc);
keys.Add (Key.Tab);
keys.Add (Key.Space.WithCtrl);
keys.Add (Key.CursorDown);
keys.Add (Key.CursorDown);
keys.Add (Key.CursorDown);
keys.Add (Key.Enter);
keys.Add (Key.F10.WithShift);
keys.Add (Key.Esc);
return keys;
}
}