diff --git a/Examples/Example/Example.cs b/Examples/Example/Example.cs index e85b80703..ea64a5f90 100644 --- a/Examples/Example/Example.cs +++ b/Examples/Example/Example.cs @@ -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 (); diff --git a/Examples/FluentExample/Program.cs b/Examples/FluentExample/Program.cs index 478a3342e..b4461d8e2 100644 --- a/Examples/FluentExample/Program.cs +++ b/Examples/FluentExample/Program.cs @@ -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 (); -// 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?; diff --git a/Examples/RunnableWrapperExample/Program.cs b/Examples/RunnableWrapperExample/Program.cs index 9f859ab5c..646337c93 100644 --- a/Examples/RunnableWrapperExample/Program.cs +++ b/Examples/RunnableWrapperExample/Program.cs @@ -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 diff --git a/Terminal.Gui/App/Application.Lifecycle.cs b/Terminal.Gui/App/Application.Lifecycle.cs index 9fbc9fba1..5a056bf3e 100644 --- a/Terminal.Gui/App/Application.Lifecycle.cs +++ b/Terminal.Gui/App/Application.Lifecycle.cs @@ -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) { + /// + /// Gets the observable collection of all application instances. + /// External observers can subscribe to this collection to monitor application lifecycle. + /// + public static ObservableCollection Apps { get; } = []; /// /// Gets the singleton instance used by the legacy static Application model. /// @@ -29,6 +35,10 @@ public static partial class Application // Lifecycle (Init/Shutdown) /// /// Creates a new instance. /// + /// + /// If , the application will run in example mode where metadata is collected + /// and demo keys are automatically sent when the first TopRunnable is modal. + /// /// /// The recommended pattern is for developers to call Application.Create() and then use the returned /// instance for all subsequent application operations. @@ -37,12 +47,15 @@ public static partial class Application // Lifecycle (Init/Shutdown) /// /// Thrown if the legacy static Application model has already been used in this process. /// - 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; } /// diff --git a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs index acdd2a0cf..6dfe33af2 100644 --- a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs +++ b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs @@ -11,6 +11,9 @@ internal partial class ApplicationImpl /// public bool Initialized { get; set; } + /// + public bool IsExample { get; set; } + /// public event EventHandler>? 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; + + /// + /// Sets up example mode functionality - collecting metadata and sending demo keys + /// when the first TopRunnable is modal. + /// + 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 () + .ToList (); + + if (!demoKeyAttributes.Any ()) + { + return; + } + + // Sort by Order and collect all keystrokes + var sortedSequences = demoKeyAttributes.OrderBy (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 } diff --git a/Terminal.Gui/App/IApplication.cs b/Terminal.Gui/App/IApplication.cs index 4d0959a2f..1e91955ad 100644 --- a/Terminal.Gui/App/IApplication.cs +++ b/Terminal.Gui/App/IApplication.cs @@ -86,6 +86,12 @@ public interface IApplication : IDisposable /// Gets or sets whether the application has been initialized. bool Initialized { get; set; } + /// + /// Gets or sets a value indicating whether this application is running in example mode. + /// When , metadata is collected and demo keys are automatically sent. + /// + bool IsExample { get; set; } + /// /// INTERNAL: Resets the state of this instance. Called by Dispose. ///