#nullable enable using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; namespace Terminal.Gui; /// /// Implementation of that boots the new 'v2' /// main loop architecture. /// public class ApplicationV2 : ApplicationImpl { private readonly Func _netInputFactory; private readonly Func _netOutputFactory; private readonly Func _winInputFactory; private readonly Func _winOutputFactory; private IMainLoopCoordinator? _coordinator; private string? _driverName; private readonly ITimedEvents _timedEvents = new TimedEvents (); /// /// Creates anew instance of the Application backend. The provided /// factory methods will be used on Init calls to get things booted. /// public ApplicationV2 () : this ( () => new NetInput (), () => new NetOutput (), () => new WindowsInput (), () => new WindowsOutput () ) { } internal ApplicationV2 ( Func netInputFactory, Func netOutputFactory, Func winInputFactory, Func winOutputFactory ) { _netInputFactory = netInputFactory; _netOutputFactory = netOutputFactory; _winInputFactory = winInputFactory; _winOutputFactory = winOutputFactory; IsLegacy = false; } /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] public override void Init (IConsoleDriver? driver = null, string? driverName = null) { if (Application.Initialized) { Logging.Logger.LogError ("Init called multiple times without shutdown, ignoring."); return; } if (!string.IsNullOrWhiteSpace (driverName)) { _driverName = driverName; } Application.Navigation = new (); Application.AddKeyBindings (); // This is consistent with Application.ForceDriver which magnetically picks up driverName // making it use custom driver in future shutdown/init calls where no driver is specified CreateDriver (driverName ?? _driverName); Application.InitializeConfigurationManagement (); Application.Initialized = true; Application.OnInitializedChanged (this, new (true)); Application.SubscribeDriverEvents (); } private void CreateDriver (string? driverName) { PlatformID p = Environment.OSVersion.Platform; bool definetlyWin = driverName?.Contains ("win") ?? false; bool definetlyNet = driverName?.Contains ("net") ?? false; if (definetlyWin) { _coordinator = CreateWindowsSubcomponents (); } else if (definetlyNet) { _coordinator = CreateNetSubcomponents (); } else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { _coordinator = CreateWindowsSubcomponents (); } else { _coordinator = CreateNetSubcomponents (); } _coordinator.StartAsync ().Wait (); if (Application.Driver == null) { throw new ("Application.Driver was null even after booting MainLoopCoordinator"); } } private IMainLoopCoordinator CreateWindowsSubcomponents () { ConcurrentQueue inputBuffer = new (); MainLoop loop = new (); return new MainLoopCoordinator ( _timedEvents, _winInputFactory, inputBuffer, new WindowsInputProcessor (inputBuffer), _winOutputFactory, loop); } private IMainLoopCoordinator CreateNetSubcomponents () { ConcurrentQueue inputBuffer = new (); MainLoop loop = new (); return new MainLoopCoordinator ( _timedEvents, _netInputFactory, inputBuffer, new NetInputProcessor (inputBuffer), _netOutputFactory, loop); } /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] public override T Run (Func? errorHandler = null, IConsoleDriver? driver = null) { var top = new T (); Run (top, errorHandler); return top; } /// public override void Run (Toplevel view, Func? errorHandler = null) { Logging.Logger.LogInformation ($"Run '{view}'"); ArgumentNullException.ThrowIfNull (view); if (!Application.Initialized) { throw new NotInitializedException (nameof (Run)); } Application.Top = view; Application.Begin (view); // TODO : how to know when we are done? while (Application.TopLevels.TryPeek (out Toplevel? found) && found == view) { if (_coordinator is null) { throw new ($"{nameof (IMainLoopCoordinator)}inexplicably became null during Run"); } _coordinator.RunIteration (); } } /// public override void Shutdown () { _coordinator?.Stop (); base.Shutdown (); Application.Driver = null; } /// public override void RequestStop (Toplevel? top) { Logging.Logger.LogInformation ($"RequestStop '{top}'"); // TODO: This definition of stop seems sketchy Application.TopLevels.TryPop (out _); if (Application.TopLevels.Count > 0) { Application.Top = Application.TopLevels.Peek (); } else { Application.Top = null; } } /// public override void Invoke (Action action) { _timedEvents.AddIdle ( () => { action (); return false; } ); } /// public override void AddIdle (Func func) { _timedEvents.AddIdle (func); } /// /// Removes an idle function added by /// /// Function to remove /// True if it was found and removed public bool RemoveIdle (Func fnTrue) { return _timedEvents.RemoveIdle (fnTrue); } /// public override object AddTimeout (TimeSpan time, Func callback) { return _timedEvents.AddTimeout (time, callback); } /// public override bool RemoveTimeout (object token) { return _timedEvents.RemoveTimeout (token); } /// public override void LayoutAndDraw (bool forceDraw) { // No more ad-hoc drawing, you must wait for iteration to do it Application.Top?.SetNeedsDraw(); Application.Top?.SetNeedsLayout (); } }