Skip to content

Commit 401db78

Browse files
Copilottig
andcommitted
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 <[email protected]>
1 parent 215d766 commit 401db78

File tree

6 files changed

+130
-16
lines changed

6 files changed

+130
-16
lines changed

Examples/Example/Example.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,16 @@
2323
// Check for test context to determine driver
2424
string? contextJson = Environment.GetEnvironmentVariable (ExampleContext.ENVIRONMENT_VARIABLE_NAME);
2525
string? driverName = null;
26+
var isExample = false;
2627

2728
if (!string.IsNullOrEmpty (contextJson))
2829
{
2930
ExampleContext? context = ExampleContext.FromJson (contextJson);
3031
driverName = context?.DriverName;
32+
isExample = true;
3133
}
3234

33-
IApplication app = Application.Create ();
34-
35-
// Setup automatic key injection for testing
36-
ExampleContextInjector.SetupAutomaticInjection (app);
37-
35+
IApplication app = Application.Create (example: isExample);
3836
app.Init (driverName);
3937
app.Run<ExampleWindow> ();
4038

Examples/FluentExample/Program.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,19 @@
1717
// Check for test context to determine driver
1818
string? contextJson = Environment.GetEnvironmentVariable (ExampleContext.ENVIRONMENT_VARIABLE_NAME);
1919
string? driverName = null;
20+
var isExample = false;
2021

2122
if (!string.IsNullOrEmpty (contextJson))
2223
{
2324
ExampleContext? context = ExampleContext.FromJson (contextJson);
2425
driverName = context?.DriverName;
26+
isExample = true;
2527
}
2628

27-
IApplication? app = Application.Create ()
29+
IApplication? app = Application.Create (example: isExample)
2830
.Init (driverName)
2931
.Run<ColorPickerView> ();
3032

31-
// Setup automatic key injection for testing
32-
ExampleContextInjector.SetupAutomaticInjection (app);
33-
3433
// Run the application with fluent API - automatically creates, runs, and disposes the runnable
3534
Color? result = app.GetResult () as Color?;
3635

Examples/RunnableWrapperExample/Program.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,16 @@
1919
// Check for test context to determine driver
2020
string? contextJson = Environment.GetEnvironmentVariable (ExampleContext.ENVIRONMENT_VARIABLE_NAME);
2121
string? driverName = null;
22+
var isExample = false;
2223

2324
if (!string.IsNullOrEmpty (contextJson))
2425
{
2526
ExampleContext? context = ExampleContext.FromJson (contextJson);
2627
driverName = context?.DriverName;
28+
isExample = true;
2729
}
2830

29-
IApplication app = Application.Create ();
30-
31-
// Setup automatic key injection for testing
32-
ExampleContextInjector.SetupAutomaticInjection (app);
33-
31+
IApplication app = Application.Create (example: isExample);
3432
app.Init (driverName);
3533

3634
// Example 1: Use extension method with result extraction

Terminal.Gui/App/Application.Lifecycle.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.ObjectModel;
12
using System.Diagnostics;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Reflection;
@@ -10,6 +11,11 @@ namespace Terminal.Gui.App;
1011

1112
public static partial class Application // Lifecycle (Init/Shutdown)
1213
{
14+
/// <summary>
15+
/// Gets the observable collection of all application instances.
16+
/// External observers can subscribe to this collection to monitor application lifecycle.
17+
/// </summary>
18+
public static ObservableCollection<IApplication> Apps { get; } = [];
1319
/// <summary>
1420
/// Gets the singleton <see cref="IApplication"/> instance used by the legacy static Application model.
1521
/// </summary>
@@ -29,6 +35,10 @@ public static partial class Application // Lifecycle (Init/Shutdown)
2935
/// <summary>
3036
/// Creates a new <see cref="IApplication"/> instance.
3137
/// </summary>
38+
/// <param name="example">
39+
/// If <see langword="true"/>, the application will run in example mode where metadata is collected
40+
/// and demo keys are automatically sent when the first TopRunnable is modal.
41+
/// </param>
3242
/// <remarks>
3343
/// The recommended pattern is for developers to call <c>Application.Create()</c> and then use the returned
3444
/// <see cref="IApplication"/> instance for all subsequent application operations.
@@ -37,12 +47,15 @@ public static partial class Application // Lifecycle (Init/Shutdown)
3747
/// <exception cref="InvalidOperationException">
3848
/// Thrown if the legacy static Application model has already been used in this process.
3949
/// </exception>
40-
public static IApplication Create ()
50+
public static IApplication Create (bool example = false)
4151
{
4252
//Debug.Fail ("Application.Create() called");
4353
ApplicationImpl.MarkInstanceBasedModelUsed ();
4454

45-
return new ApplicationImpl ();
55+
ApplicationImpl app = new () { IsExample = example };
56+
Apps.Add (app);
57+
58+
return app;
4659
}
4760

4861
/// <inheritdoc cref="IApplication.Init"/>

Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ internal partial class ApplicationImpl
1111
/// <inheritdoc/>
1212
public bool Initialized { get; set; }
1313

14+
/// <inheritdoc/>
15+
public bool IsExample { get; set; }
16+
1417
/// <inheritdoc/>
1518
public event EventHandler<EventArgs<bool>>? InitializedChanged;
1619

@@ -93,6 +96,12 @@ public IApplication Init (string? driverName = null)
9396
RaiseInitializedChanged (this, new (true));
9497
SubscribeDriverEvents ();
9598

99+
// Setup example mode if requested
100+
if (IsExample)
101+
{
102+
SetupExampleMode ();
103+
}
104+
96105
SynchronizationContext.SetSynchronizationContext (new ());
97106
MainThreadId = Thread.CurrentThread.ManagedThreadId;
98107

@@ -381,4 +390,95 @@ private void UnsubscribeApplicationEvents ()
381390
Application.Force16ColorsChanged -= OnForce16ColorsChanged;
382391
Application.ForceDriverChanged -= OnForceDriverChanged;
383392
}
393+
394+
#region Example Mode
395+
396+
private bool _exampleModeDemoKeysSent;
397+
398+
/// <summary>
399+
/// Sets up example mode functionality - collecting metadata and sending demo keys
400+
/// when the first TopRunnable is modal.
401+
/// </summary>
402+
private void SetupExampleMode ()
403+
{
404+
// Subscribe to SessionBegun to wait for the first modal runnable
405+
SessionBegun += OnSessionBegunForExample;
406+
}
407+
408+
private void OnSessionBegunForExample (object? sender, SessionTokenEventArgs e)
409+
{
410+
// Only send demo keys once, when the first modal runnable appears
411+
if (_exampleModeDemoKeysSent)
412+
{
413+
return;
414+
}
415+
416+
// Check if the TopRunnable is modal
417+
if (TopRunnable?.IsModal != true)
418+
{
419+
return;
420+
}
421+
422+
// Mark that we've sent the keys
423+
_exampleModeDemoKeysSent = true;
424+
425+
// Unsubscribe - we only need to do this once
426+
SessionBegun -= OnSessionBegunForExample;
427+
428+
// Send demo keys from assembly attributes
429+
SendDemoKeys ();
430+
}
431+
432+
private void SendDemoKeys ()
433+
{
434+
// Get the entry assembly to read example metadata
435+
var assembly = System.Reflection.Assembly.GetEntryAssembly ();
436+
437+
if (assembly is null)
438+
{
439+
return;
440+
}
441+
442+
// Look for ExampleDemoKeyStrokesAttribute
443+
var demoKeyAttributes = assembly.GetCustomAttributes (typeof (Terminal.Gui.Examples.ExampleDemoKeyStrokesAttribute), false)
444+
.OfType<Terminal.Gui.Examples.ExampleDemoKeyStrokesAttribute> ()
445+
.ToList ();
446+
447+
if (!demoKeyAttributes.Any ())
448+
{
449+
return;
450+
}
451+
452+
// Sort by Order and collect all keystrokes
453+
var sortedSequences = demoKeyAttributes.OrderBy<Terminal.Gui.Examples.ExampleDemoKeyStrokesAttribute, int> (a => a.Order);
454+
455+
foreach (var attr in sortedSequences)
456+
{
457+
// Handle KeyStrokes array
458+
if (attr.KeyStrokes is { Length: > 0 })
459+
{
460+
foreach (string keyStr in attr.KeyStrokes)
461+
{
462+
if (Input.Key.TryParse (keyStr, out Input.Key? key) && key is { })
463+
{
464+
Keyboard?.RaiseKeyDownEvent (key);
465+
}
466+
}
467+
}
468+
469+
// Handle RepeatKey
470+
if (!string.IsNullOrEmpty (attr.RepeatKey))
471+
{
472+
if (Input.Key.TryParse (attr.RepeatKey, out Input.Key? key) && key is { })
473+
{
474+
for (var i = 0; i < attr.RepeatCount; i++)
475+
{
476+
Keyboard?.RaiseKeyDownEvent (key);
477+
}
478+
}
479+
}
480+
}
481+
}
482+
483+
#endregion Example Mode
384484
}

Terminal.Gui/App/IApplication.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ public interface IApplication : IDisposable
8686
/// <summary>Gets or sets whether the application has been initialized.</summary>
8787
bool Initialized { get; set; }
8888

89+
/// <summary>
90+
/// Gets or sets a value indicating whether this application is running in example mode.
91+
/// When <see langword="true"/>, metadata is collected and demo keys are automatically sent.
92+
/// </summary>
93+
bool IsExample { get; set; }
94+
8995
/// <summary>
9096
/// INTERNAL: Resets the state of this instance. Called by Dispose.
9197
/// </summary>

0 commit comments

Comments
 (0)