#nullable enable using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace Terminal.Gui; /// /// Original Terminal.Gui implementation of core methods. /// public class ApplicationImpl : IApplication { // Private static readonly Lazy instance of Application private static Lazy _lazyInstance = new (() => new ApplicationImpl ()); /// /// Gets the currently configured backend implementation of gateway methods. /// Change to your own implementation by using (before init). /// public static IApplication Instance => _lazyInstance.Value; /// /// Change the singleton implementation, should not be called except before application /// startup. This method lets you provide alternative implementations of core static gateway /// methods of . /// /// public static void ChangeInstance (IApplication newApplication) { _lazyInstance = new Lazy (newApplication); } /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] public virtual void Init (IConsoleDriver? driver = null, string? driverName = null) { Application.InternalInit (driver, driverName); } /// /// Runs the application by creating a object and calling /// . /// /// /// Calling first is not needed as this function will initialize the application. /// /// must be called when the application is closing (typically after Run> has returned) to /// ensure resources are cleaned up and terminal settings restored. /// /// /// The caller is responsible for disposing the object returned by this method. /// /// /// The created object. The caller is responsible for disposing this object. [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] public Toplevel Run (Func? errorHandler = null, IConsoleDriver? driver = null) { return Run (errorHandler, driver); } /// /// Runs the application by creating a -derived object of type T and calling /// . /// /// /// Calling first is not needed as this function will initialize the application. /// /// must be called when the application is closing (typically after Run> has returned) to /// ensure resources are cleaned up and terminal settings restored. /// /// /// The caller is responsible for disposing the object returned by this method. /// /// /// /// /// The to use. If not specified the default driver for the platform will /// be used ( , , or ). Must be /// if has already been called. /// /// The created T object. The caller is responsible for disposing this object. [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] public virtual T Run (Func? errorHandler = null, IConsoleDriver? driver = null) where T : Toplevel, new() { if (!Application.Initialized) { // Init() has NOT been called. Application.InternalInit (driver, null, true); } var top = new T (); Run (top, errorHandler); return top; } /// Runs the Application using the provided view. /// /// /// This method is used to start processing events for the main application, but it is also used to run other /// modal s such as boxes. /// /// /// To make a stop execution, call /// . /// /// /// Calling is equivalent to calling /// , followed by , and then calling /// . /// /// /// Alternatively, to have a program control the main loop and process events manually, call /// to set things up manually and then repeatedly call /// with the wait parameter set to false. By doing this the /// method will only process any pending events, timers, idle handlers and then /// return control immediately. /// /// When using or /// /// will be called automatically. /// /// /// RELEASE builds only: When is any exceptions will be /// rethrown. Otherwise, if will be called. If /// returns the will resume; otherwise this method will /// exit. /// /// /// The to run as a modal. /// /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, /// rethrows when null). /// public virtual void Run (Toplevel view, Func? errorHandler = null) { ArgumentNullException.ThrowIfNull (view); if (Application.Initialized) { if (Application.Driver is null) { // Disposing before throwing view.Dispose (); // This code path should be impossible because Init(null, null) will select the platform default driver throw new InvalidOperationException ( "Init() completed without a driver being set (this should be impossible); Run() cannot be called." ); } } else { // Init() has NOT been called. throw new InvalidOperationException ( "Init() has not been called. Only Run() or Run() can be used without calling Init()." ); } var resume = true; while (resume) { #if !DEBUG try { #endif resume = false; RunState runState = Application.Begin (view); // If EndAfterFirstIteration is true then the user must dispose of the runToken // by using NotifyStopRunState event. Application.RunLoop (runState); if (runState.Toplevel is null) { #if DEBUG_IDISPOSABLE Debug.Assert (Application.TopLevels.Count == 0); #endif runState.Dispose (); return; } if (!Application.EndAfterFirstIteration) { Application.End (runState); } #if !DEBUG } catch (Exception error) { if (errorHandler is null) { throw; } resume = errorHandler (error); } #endif } } /// Shutdown an application initialized with . /// /// Shutdown must be called for every call to or /// to ensure all resources are cleaned /// up (Disposed) /// and terminal settings are restored. /// public virtual void Shutdown () { // TODO: Throw an exception if Init hasn't been called. bool wasInitialized = Application.Initialized; Application.ResetState (); LogJsonErrors (); PrintJsonErrors (); if (wasInitialized) { bool init = Application.Initialized; Application.OnInitializedChanged(this, new (in init)); } } /// public virtual void RequestStop (Toplevel? top) { top ??= Application.Top; if (!top!.Running) { return; } var ev = new ToplevelClosingEventArgs (top); top.OnClosing (ev); if (ev.Cancel) { return; } top.Running = false; Application.OnNotifyStopRunState (top); } /// public virtual void Invoke (Action action) { Application.MainLoop?.AddIdle ( () => { action (); return false; } ); } /// public bool IsLegacy { get; protected set; } = true; /// public virtual void AddIdle (Func func) { if(Application.MainLoop is null) { throw new NotInitializedException ("Cannot add idle before main loop is initialized"); } // Yes in this case we cannot go direct via TimedEvents because legacy main loop // has established behaviour to do other stuff too e.g. 'wake up'. Application.MainLoop.AddIdle (func); } /// public virtual object AddTimeout (TimeSpan time, Func callback) { if (Application.MainLoop is null) { throw new NotInitializedException ("Cannot add timeout before main loop is initialized", null); } return Application.MainLoop.TimedEvents.AddTimeout (time, callback); } /// public virtual bool RemoveTimeout (object token) { return Application.MainLoop?.TimedEvents.RemoveTimeout (token) ?? false; } /// public virtual void LayoutAndDraw (bool forceDraw) { Application.LayoutAndDrawImpl (forceDraw); } }