#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);
}
}