mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Simplify example infrastructure with Create(example) parameter
- Added bool example parameter to Application.Create() - Added static ObservableCollection<IApplication> Apps for external observers - When example=true, metadata is collected and demo keys are sent when first TopRunnable is modal - Removed ExampleContextInjector complexity - Examples now use Application.Create(example: isExample) - Key injection happens via SessionBegun event monitoring TopRunnable.IsModal - Clean, simple architecture that allows external observers to subscribe to Apps collection This addresses @tig's feedback to simplify the approach. Co-authored-by: tig <585482+tig@users.noreply.github.com>
This commit is contained in:
@@ -23,18 +23,16 @@ ConfigurationManager.Enable (ConfigLocations.All);
|
||||
// Check for test context to determine driver
|
||||
string? contextJson = Environment.GetEnvironmentVariable (ExampleContext.ENVIRONMENT_VARIABLE_NAME);
|
||||
string? driverName = null;
|
||||
var isExample = false;
|
||||
|
||||
if (!string.IsNullOrEmpty (contextJson))
|
||||
{
|
||||
ExampleContext? context = ExampleContext.FromJson (contextJson);
|
||||
driverName = context?.DriverName;
|
||||
isExample = true;
|
||||
}
|
||||
|
||||
IApplication app = Application.Create ();
|
||||
|
||||
// Setup automatic key injection for testing
|
||||
ExampleContextInjector.SetupAutomaticInjection (app);
|
||||
|
||||
IApplication app = Application.Create (example: isExample);
|
||||
app.Init (driverName);
|
||||
app.Run<ExampleWindow> ();
|
||||
|
||||
|
||||
@@ -17,20 +17,19 @@ using Terminal.Gui.Views;
|
||||
// Check for test context to determine driver
|
||||
string? contextJson = Environment.GetEnvironmentVariable (ExampleContext.ENVIRONMENT_VARIABLE_NAME);
|
||||
string? driverName = null;
|
||||
var isExample = false;
|
||||
|
||||
if (!string.IsNullOrEmpty (contextJson))
|
||||
{
|
||||
ExampleContext? context = ExampleContext.FromJson (contextJson);
|
||||
driverName = context?.DriverName;
|
||||
isExample = true;
|
||||
}
|
||||
|
||||
IApplication? app = Application.Create ()
|
||||
IApplication? app = Application.Create (example: isExample)
|
||||
.Init (driverName)
|
||||
.Run<ColorPickerView> ();
|
||||
|
||||
// Setup automatic key injection for testing
|
||||
ExampleContextInjector.SetupAutomaticInjection (app);
|
||||
|
||||
// Run the application with fluent API - automatically creates, runs, and disposes the runnable
|
||||
Color? result = app.GetResult () as Color?;
|
||||
|
||||
|
||||
@@ -19,18 +19,16 @@ using Terminal.Gui.Views;
|
||||
// Check for test context to determine driver
|
||||
string? contextJson = Environment.GetEnvironmentVariable (ExampleContext.ENVIRONMENT_VARIABLE_NAME);
|
||||
string? driverName = null;
|
||||
var isExample = false;
|
||||
|
||||
if (!string.IsNullOrEmpty (contextJson))
|
||||
{
|
||||
ExampleContext? context = ExampleContext.FromJson (contextJson);
|
||||
driverName = context?.DriverName;
|
||||
isExample = true;
|
||||
}
|
||||
|
||||
IApplication app = Application.Create ();
|
||||
|
||||
// Setup automatic key injection for testing
|
||||
ExampleContextInjector.SetupAutomaticInjection (app);
|
||||
|
||||
IApplication app = Application.Create (example: isExample);
|
||||
app.Init (driverName);
|
||||
|
||||
// Example 1: Use extension method with result extraction
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
@@ -10,6 +11,11 @@ namespace Terminal.Gui.App;
|
||||
|
||||
public static partial class Application // Lifecycle (Init/Shutdown)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the observable collection of all application instances.
|
||||
/// External observers can subscribe to this collection to monitor application lifecycle.
|
||||
/// </summary>
|
||||
public static ObservableCollection<IApplication> Apps { get; } = [];
|
||||
/// <summary>
|
||||
/// Gets the singleton <see cref="IApplication"/> instance used by the legacy static Application model.
|
||||
/// </summary>
|
||||
@@ -29,6 +35,10 @@ public static partial class Application // Lifecycle (Init/Shutdown)
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="IApplication"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="example">
|
||||
/// If <see langword="true"/>, the application will run in example mode where metadata is collected
|
||||
/// and demo keys are automatically sent when the first TopRunnable is modal.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// The recommended pattern is for developers to call <c>Application.Create()</c> and then use the returned
|
||||
/// <see cref="IApplication"/> instance for all subsequent application operations.
|
||||
@@ -37,12 +47,15 @@ public static partial class Application // Lifecycle (Init/Shutdown)
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown if the legacy static Application model has already been used in this process.
|
||||
/// </exception>
|
||||
public static IApplication Create ()
|
||||
public static IApplication Create (bool example = false)
|
||||
{
|
||||
//Debug.Fail ("Application.Create() called");
|
||||
ApplicationImpl.MarkInstanceBasedModelUsed ();
|
||||
|
||||
return new ApplicationImpl ();
|
||||
ApplicationImpl app = new () { IsExample = example };
|
||||
Apps.Add (app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IApplication.Init"/>
|
||||
|
||||
@@ -11,6 +11,9 @@ internal partial class ApplicationImpl
|
||||
/// <inheritdoc/>
|
||||
public bool Initialized { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsExample { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<EventArgs<bool>>? InitializedChanged;
|
||||
|
||||
@@ -93,6 +96,12 @@ internal partial class ApplicationImpl
|
||||
RaiseInitializedChanged (this, new (true));
|
||||
SubscribeDriverEvents ();
|
||||
|
||||
// Setup example mode if requested
|
||||
if (IsExample)
|
||||
{
|
||||
SetupExampleMode ();
|
||||
}
|
||||
|
||||
SynchronizationContext.SetSynchronizationContext (new ());
|
||||
MainThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
@@ -381,4 +390,95 @@ internal partial class ApplicationImpl
|
||||
Application.Force16ColorsChanged -= OnForce16ColorsChanged;
|
||||
Application.ForceDriverChanged -= OnForceDriverChanged;
|
||||
}
|
||||
|
||||
#region Example Mode
|
||||
|
||||
private bool _exampleModeDemoKeysSent;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up example mode functionality - collecting metadata and sending demo keys
|
||||
/// when the first TopRunnable is modal.
|
||||
/// </summary>
|
||||
private void SetupExampleMode ()
|
||||
{
|
||||
// Subscribe to SessionBegun to wait for the first modal runnable
|
||||
SessionBegun += OnSessionBegunForExample;
|
||||
}
|
||||
|
||||
private void OnSessionBegunForExample (object? sender, SessionTokenEventArgs e)
|
||||
{
|
||||
// Only send demo keys once, when the first modal runnable appears
|
||||
if (_exampleModeDemoKeysSent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the TopRunnable is modal
|
||||
if (TopRunnable?.IsModal != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark that we've sent the keys
|
||||
_exampleModeDemoKeysSent = true;
|
||||
|
||||
// Unsubscribe - we only need to do this once
|
||||
SessionBegun -= OnSessionBegunForExample;
|
||||
|
||||
// Send demo keys from assembly attributes
|
||||
SendDemoKeys ();
|
||||
}
|
||||
|
||||
private void SendDemoKeys ()
|
||||
{
|
||||
// Get the entry assembly to read example metadata
|
||||
var assembly = System.Reflection.Assembly.GetEntryAssembly ();
|
||||
|
||||
if (assembly is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for ExampleDemoKeyStrokesAttribute
|
||||
var demoKeyAttributes = assembly.GetCustomAttributes (typeof (Terminal.Gui.Examples.ExampleDemoKeyStrokesAttribute), false)
|
||||
.OfType<Terminal.Gui.Examples.ExampleDemoKeyStrokesAttribute> ()
|
||||
.ToList ();
|
||||
|
||||
if (!demoKeyAttributes.Any ())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by Order and collect all keystrokes
|
||||
var sortedSequences = demoKeyAttributes.OrderBy<Terminal.Gui.Examples.ExampleDemoKeyStrokesAttribute, int> (a => a.Order);
|
||||
|
||||
foreach (var attr in sortedSequences)
|
||||
{
|
||||
// Handle KeyStrokes array
|
||||
if (attr.KeyStrokes is { Length: > 0 })
|
||||
{
|
||||
foreach (string keyStr in attr.KeyStrokes)
|
||||
{
|
||||
if (Input.Key.TryParse (keyStr, out Input.Key? key) && key is { })
|
||||
{
|
||||
Keyboard?.RaiseKeyDownEvent (key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle RepeatKey
|
||||
if (!string.IsNullOrEmpty (attr.RepeatKey))
|
||||
{
|
||||
if (Input.Key.TryParse (attr.RepeatKey, out Input.Key? key) && key is { })
|
||||
{
|
||||
for (var i = 0; i < attr.RepeatCount; i++)
|
||||
{
|
||||
Keyboard?.RaiseKeyDownEvent (key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Example Mode
|
||||
}
|
||||
|
||||
@@ -86,6 +86,12 @@ public interface IApplication : IDisposable
|
||||
/// <summary>Gets or sets whether the application has been initialized.</summary>
|
||||
bool Initialized { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this application is running in example mode.
|
||||
/// When <see langword="true"/>, metadata is collected and demo keys are automatically sent.
|
||||
/// </summary>
|
||||
bool IsExample { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// INTERNAL: Resets the state of this instance. Called by Dispose.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user