mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-02 01:03:29 +01:00
- Update RaiseInitializedChanged documentation to clarify it delegates to static event - Pass ApplicationImpl.Instance as sender in legacy Init path instead of null for consistency - All tests still passing Co-authored-by: tig <585482+tig@users.noreply.github.com>
690 lines
22 KiB
C#
690 lines
22 KiB
C#
#nullable enable
|
|
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using Microsoft.Extensions.Logging;
|
|
using Terminal.Gui.Drivers;
|
|
|
|
namespace Terminal.Gui.App;
|
|
|
|
/// <summary>
|
|
/// Implementation of core <see cref="Application"/> methods using the modern
|
|
/// main loop architecture with component factories for different platforms.
|
|
/// </summary>
|
|
public class ApplicationImpl : IApplication
|
|
{
|
|
private readonly IComponentFactory? _componentFactory;
|
|
private IMainLoopCoordinator? _coordinator;
|
|
private string? _driverName;
|
|
private readonly ITimedEvents _timedEvents = new TimedEvents ();
|
|
private IConsoleDriver? _driver;
|
|
private bool _initialized;
|
|
private ApplicationPopover? _popover;
|
|
private ApplicationNavigation? _navigation;
|
|
private Toplevel? _top;
|
|
private readonly ConcurrentStack<Toplevel> _topLevels = new ();
|
|
private int _mainThreadId = -1;
|
|
private bool _force16Colors;
|
|
private string _forceDriver = string.Empty;
|
|
private readonly List<SixelToRender> _sixel = new ();
|
|
private readonly object _lockScreen = new ();
|
|
private Rectangle? _screen;
|
|
private bool _clearScreenNextIteration;
|
|
private ushort _maximumIterationsPerSecond = Application.DefaultMaximumIterationsPerSecond;
|
|
private List<CultureInfo>? _supportedCultures;
|
|
|
|
// When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`.
|
|
// This variable is set in `End` in this case so that `Begin` correctly sets `Top`.
|
|
internal Toplevel? _cachedRunStateToplevel;
|
|
|
|
// Private static readonly Lazy instance of Application
|
|
private static Lazy<IApplication> _lazyInstance = new (() => new ApplicationImpl ());
|
|
|
|
/// <summary>
|
|
/// Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
|
|
/// Change to your own implementation by using <see cref="ChangeInstance"/> (before init).
|
|
/// </summary>
|
|
public static IApplication Instance => _lazyInstance.Value;
|
|
|
|
/// <inheritdoc/>
|
|
public ITimedEvents? TimedEvents => _timedEvents;
|
|
|
|
internal IMainLoopCoordinator? Coordinator => _coordinator;
|
|
|
|
private IMouse? _mouse;
|
|
|
|
/// <summary>
|
|
/// Handles mouse event state and processing.
|
|
/// </summary>
|
|
public IMouse Mouse
|
|
{
|
|
get
|
|
{
|
|
if (_mouse is null)
|
|
{
|
|
_mouse = new MouseImpl { Application = this };
|
|
}
|
|
return _mouse;
|
|
}
|
|
set => _mouse = value ?? throw new ArgumentNullException (nameof (value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles which <see cref="View"/> (if any) has captured the mouse
|
|
/// </summary>
|
|
public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler ();
|
|
|
|
private IKeyboard? _keyboard;
|
|
|
|
/// <summary>
|
|
/// Handles keyboard input and key bindings at the Application level
|
|
/// </summary>
|
|
public IKeyboard Keyboard
|
|
{
|
|
get
|
|
{
|
|
if (_keyboard is null)
|
|
{
|
|
_keyboard = new KeyboardImpl { Application = this };
|
|
}
|
|
return _keyboard;
|
|
}
|
|
set => _keyboard = value ?? throw new ArgumentNullException (nameof (value));
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public IConsoleDriver? Driver
|
|
{
|
|
get => _driver;
|
|
set => _driver = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool Initialized
|
|
{
|
|
get => _initialized;
|
|
set => _initialized = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool Force16Colors
|
|
{
|
|
get => _force16Colors;
|
|
set => _force16Colors = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public string ForceDriver
|
|
{
|
|
get => _forceDriver;
|
|
set => _forceDriver = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public List<SixelToRender> Sixel => _sixel;
|
|
|
|
/// <inheritdoc/>
|
|
public Rectangle Screen
|
|
{
|
|
get
|
|
{
|
|
lock (_lockScreen)
|
|
{
|
|
if (_screen == null)
|
|
{
|
|
_screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
|
|
}
|
|
|
|
return _screen.Value;
|
|
}
|
|
}
|
|
set
|
|
{
|
|
if (value is {} && (value.X != 0 || value.Y != 0))
|
|
{
|
|
throw new NotImplementedException ($"Screen locations other than 0, 0 are not yet supported");
|
|
}
|
|
|
|
lock (_lockScreen)
|
|
{
|
|
_screen = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool ClearScreenNextIteration
|
|
{
|
|
get => _clearScreenNextIteration;
|
|
set => _clearScreenNextIteration = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ApplicationPopover? Popover
|
|
{
|
|
get => _popover;
|
|
set => _popover = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ApplicationNavigation? Navigation
|
|
{
|
|
get => _navigation;
|
|
set => _navigation = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public Toplevel? Top
|
|
{
|
|
get => _top;
|
|
set => _top = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ConcurrentStack<Toplevel> TopLevels => _topLevels;
|
|
|
|
/// <inheritdoc/>
|
|
public ushort MaximumIterationsPerSecond
|
|
{
|
|
get => _maximumIterationsPerSecond;
|
|
set => _maximumIterationsPerSecond = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public List<CultureInfo>? SupportedCultures
|
|
{
|
|
get
|
|
{
|
|
if (_supportedCultures is null)
|
|
{
|
|
_supportedCultures = Application.GetSupportedCultures ();
|
|
}
|
|
return _supportedCultures;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal helper to raise InitializedChanged static event. Used by both legacy and modern Init paths.
|
|
/// </summary>
|
|
internal void RaiseInitializedChanged (bool initialized)
|
|
{
|
|
Application.OnInitializedChanged (this, new (initialized));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the main thread ID for the application.
|
|
/// </summary>
|
|
internal int MainThreadId
|
|
{
|
|
get => _mainThreadId;
|
|
set => _mainThreadId = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void RequestStop () => RequestStop (null);
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the Application backend.
|
|
/// </summary>
|
|
public ApplicationImpl ()
|
|
{
|
|
}
|
|
|
|
internal ApplicationImpl (IComponentFactory componentFactory)
|
|
{
|
|
_componentFactory = componentFactory;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="Application"/>.
|
|
/// </summary>
|
|
/// <param name="newApplication"></param>
|
|
public static void ChangeInstance (IApplication newApplication)
|
|
{
|
|
_lazyInstance = new Lazy<IApplication> (newApplication);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
[RequiresUnreferencedCode ("AOT")]
|
|
[RequiresDynamicCode ("AOT")]
|
|
public void Init (IConsoleDriver? driver = null, string? driverName = null)
|
|
{
|
|
if (_initialized)
|
|
{
|
|
Logging.Logger.LogError ("Init called multiple times without shutdown, aborting.");
|
|
|
|
throw new InvalidOperationException ("Init called multiple times without Shutdown");
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace (driverName))
|
|
{
|
|
_driverName = driverName;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace (_driverName))
|
|
{
|
|
_driverName = Application.ForceDriver;
|
|
}
|
|
|
|
Debug.Assert(_navigation is null);
|
|
_navigation = new ();
|
|
|
|
Debug.Assert (_popover is null);
|
|
_popover = new ();
|
|
|
|
// Preserve existing keyboard settings if they exist
|
|
bool hasExistingKeyboard = _keyboard is not null;
|
|
Key existingQuitKey = _keyboard?.QuitKey ?? Key.Esc;
|
|
Key existingArrangeKey = _keyboard?.ArrangeKey ?? Key.F5.WithCtrl;
|
|
Key existingNextTabKey = _keyboard?.NextTabKey ?? Key.Tab;
|
|
Key existingPrevTabKey = _keyboard?.PrevTabKey ?? Key.Tab.WithShift;
|
|
Key existingNextTabGroupKey = _keyboard?.NextTabGroupKey ?? Key.F6;
|
|
Key existingPrevTabGroupKey = _keyboard?.PrevTabGroupKey ?? Key.F6.WithShift;
|
|
|
|
// Reset keyboard to ensure fresh state with default bindings
|
|
_keyboard = new KeyboardImpl { Application = this };
|
|
|
|
// Restore previously set keys if they existed and were different from defaults
|
|
if (hasExistingKeyboard)
|
|
{
|
|
_keyboard.QuitKey = existingQuitKey;
|
|
_keyboard.ArrangeKey = existingArrangeKey;
|
|
_keyboard.NextTabKey = existingNextTabKey;
|
|
_keyboard.PrevTabKey = existingPrevTabKey;
|
|
_keyboard.NextTabGroupKey = existingNextTabGroupKey;
|
|
_keyboard.PrevTabGroupKey = existingPrevTabGroupKey;
|
|
}
|
|
|
|
CreateDriver (driverName ?? _driverName);
|
|
|
|
_initialized = true;
|
|
|
|
Application.OnInitializedChanged (this, new (true));
|
|
Application.SubscribeDriverEvents ();
|
|
|
|
SynchronizationContext.SetSynchronizationContext (new ());
|
|
_mainThreadId = Thread.CurrentThread.ManagedThreadId;
|
|
}
|
|
|
|
private void CreateDriver (string? driverName)
|
|
{
|
|
// When running unit tests, always use FakeDriver unless explicitly specified
|
|
if (ConsoleDriver.RunningUnitTests &&
|
|
string.IsNullOrEmpty (driverName) &&
|
|
_componentFactory is null)
|
|
{
|
|
Logging.Logger.LogDebug ("Unit test safeguard: forcing FakeDriver (RunningUnitTests=true, driverName=null, componentFactory=null)");
|
|
_coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
|
|
_coordinator.StartAsync ().Wait ();
|
|
|
|
if (_driver == null)
|
|
{
|
|
throw new ("Driver was null even after booting MainLoopCoordinator");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
PlatformID p = Environment.OSVersion.Platform;
|
|
|
|
// Check component factory type first - this takes precedence over driverName
|
|
bool factoryIsWindows = _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
|
|
bool factoryIsDotNet = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
|
|
bool factoryIsUnix = _componentFactory is IComponentFactory<char>;
|
|
bool factoryIsFake = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
|
|
|
|
// Then check driverName
|
|
bool nameIsWindows = driverName?.Contains ("win", StringComparison.OrdinalIgnoreCase) ?? false;
|
|
bool nameIsDotNet = (driverName?.Contains ("dotnet", StringComparison.OrdinalIgnoreCase) ?? false);
|
|
bool nameIsUnix = driverName?.Contains ("unix", StringComparison.OrdinalIgnoreCase) ?? false;
|
|
bool nameIsFake = driverName?.Contains ("fake", StringComparison.OrdinalIgnoreCase) ?? false;
|
|
|
|
// Decide which driver to use - component factory type takes priority
|
|
if (factoryIsFake || (!factoryIsWindows && !factoryIsDotNet && !factoryIsUnix && nameIsFake))
|
|
{
|
|
_coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
|
|
}
|
|
else if (factoryIsWindows || (!factoryIsDotNet && !factoryIsUnix && nameIsWindows))
|
|
{
|
|
_coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
|
|
}
|
|
else if (factoryIsDotNet || (!factoryIsWindows && !factoryIsUnix && nameIsDotNet))
|
|
{
|
|
_coordinator = CreateSubcomponents (() => new NetComponentFactory ());
|
|
}
|
|
else if (factoryIsUnix || (!factoryIsWindows && !factoryIsDotNet && nameIsUnix))
|
|
{
|
|
_coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
|
|
}
|
|
else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
|
|
{
|
|
_coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
|
|
}
|
|
else
|
|
{
|
|
_coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
|
|
}
|
|
|
|
_coordinator.StartAsync ().Wait ();
|
|
|
|
if (_driver == null)
|
|
{
|
|
throw new ("Driver was null even after booting MainLoopCoordinator");
|
|
}
|
|
}
|
|
|
|
private IMainLoopCoordinator CreateSubcomponents<T> (Func<IComponentFactory<T>> fallbackFactory)
|
|
{
|
|
ConcurrentQueue<T> inputBuffer = new ();
|
|
ApplicationMainLoop<T> loop = new ();
|
|
|
|
IComponentFactory<T> cf;
|
|
|
|
if (_componentFactory is IComponentFactory<T> typedFactory)
|
|
{
|
|
cf = typedFactory;
|
|
}
|
|
else
|
|
{
|
|
cf = fallbackFactory ();
|
|
}
|
|
|
|
return new MainLoopCoordinator<T> (_timedEvents, inputBuffer, loop, cf);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs the application by creating a <see cref="Toplevel"/> object and calling
|
|
/// <see cref="Run(Toplevel, Func{Exception, bool})"/>.
|
|
/// </summary>
|
|
/// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
|
|
[RequiresUnreferencedCode ("AOT")]
|
|
[RequiresDynamicCode ("AOT")]
|
|
public Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
|
|
|
|
/// <summary>
|
|
/// Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
|
|
/// <see cref="Run(Toplevel, Func{Exception, bool})"/>.
|
|
/// </summary>
|
|
/// <param name="errorHandler"></param>
|
|
/// <param name="driver">
|
|
/// The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
|
|
/// be used. Must be <see langword="null"/> if <see cref="Init"/> has already been called.
|
|
/// </param>
|
|
/// <returns>The created T object. The caller is responsible for disposing this object.</returns>
|
|
[RequiresUnreferencedCode ("AOT")]
|
|
[RequiresDynamicCode ("AOT")]
|
|
public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
|
|
where T : Toplevel, new()
|
|
{
|
|
if (!_initialized)
|
|
{
|
|
// Init() has NOT been called. Auto-initialize as per interface contract.
|
|
Init (driver, null);
|
|
}
|
|
|
|
T top = new ();
|
|
Run (top, errorHandler);
|
|
return top;
|
|
}
|
|
|
|
/// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
|
|
/// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
|
|
/// <param name="errorHandler">Handler for any unhandled exceptions.</param>
|
|
public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
|
|
{
|
|
Logging.Information ($"Run '{view}'");
|
|
ArgumentNullException.ThrowIfNull (view);
|
|
|
|
if (!_initialized)
|
|
{
|
|
throw new NotInitializedException (nameof (Run));
|
|
}
|
|
|
|
if (_driver == null)
|
|
{
|
|
throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view");
|
|
}
|
|
|
|
_top = view;
|
|
|
|
RunState rs = Application.Begin (view);
|
|
|
|
_top.Running = true;
|
|
|
|
while (_topLevels.TryPeek (out Toplevel? found) && found == view && view.Running)
|
|
{
|
|
if (_coordinator is null)
|
|
{
|
|
throw new ($"{nameof (IMainLoopCoordinator)} inexplicably became null during Run");
|
|
}
|
|
|
|
_coordinator.RunIteration ();
|
|
}
|
|
|
|
Logging.Information ($"Run - Calling End");
|
|
Application.End (rs);
|
|
}
|
|
|
|
/// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
|
|
public void Shutdown ()
|
|
{
|
|
_coordinator?.Stop ();
|
|
|
|
bool wasInitialized = _initialized;
|
|
|
|
// Reset Screen before calling ResetState to avoid circular reference
|
|
ResetScreen ();
|
|
|
|
// Call ResetState FIRST so it can properly dispose Popover and other resources
|
|
// that are accessed via Application.* static properties that now delegate to instance fields
|
|
ResetState ();
|
|
ConfigurationManager.PrintJsonErrors ();
|
|
|
|
// Clear instance fields after ResetState has disposed everything
|
|
_driver = null;
|
|
_mouse = null;
|
|
_keyboard = null;
|
|
_initialized = false;
|
|
_navigation = null;
|
|
_popover = null;
|
|
_top = null;
|
|
_topLevels.Clear ();
|
|
_mainThreadId = -1;
|
|
_screen = null;
|
|
_clearScreenNextIteration = false;
|
|
_sixel.Clear ();
|
|
// Don't reset ForceDriver and Force16Colors; they need to be set before Init is called
|
|
|
|
if (wasInitialized)
|
|
{
|
|
bool init = _initialized; // Will be false after clearing fields above
|
|
Application.OnInitializedChanged (this, new (in init));
|
|
}
|
|
|
|
_lazyInstance = new (() => new ApplicationImpl ());
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void RequestStop (Toplevel? top)
|
|
{
|
|
Logging.Logger.LogInformation ($"RequestStop '{(top is {} ? top : "null")}'");
|
|
|
|
top ??= _top;
|
|
|
|
if (top == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ToplevelClosingEventArgs ev = new (top);
|
|
top.OnClosing (ev);
|
|
|
|
if (ev.Cancel)
|
|
{
|
|
return;
|
|
}
|
|
|
|
top.Running = false;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Invoke (Action action)
|
|
{
|
|
// If we are already on the main UI thread
|
|
if (Application.Top is { Running: true } && _mainThreadId == Thread.CurrentThread.ManagedThreadId)
|
|
{
|
|
action ();
|
|
return;
|
|
}
|
|
|
|
_timedEvents.Add (TimeSpan.Zero,
|
|
() =>
|
|
{
|
|
action ();
|
|
return false;
|
|
}
|
|
);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool IsLegacy => false;
|
|
|
|
/// <inheritdoc />
|
|
public object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.Add (time, callback); }
|
|
|
|
/// <inheritdoc />
|
|
public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); }
|
|
|
|
/// <inheritdoc />
|
|
public void LayoutAndDraw (bool forceRedraw = false)
|
|
{
|
|
List<View> tops = [.. _topLevels];
|
|
|
|
if (_popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
|
|
{
|
|
visiblePopover.SetNeedsDraw ();
|
|
visiblePopover.SetNeedsLayout ();
|
|
tops.Insert (0, visiblePopover);
|
|
}
|
|
|
|
bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Screen.Size);
|
|
|
|
if (ClearScreenNextIteration)
|
|
{
|
|
forceRedraw = true;
|
|
ClearScreenNextIteration = false;
|
|
}
|
|
|
|
if (forceRedraw)
|
|
{
|
|
_driver?.ClearContents ();
|
|
}
|
|
|
|
View.SetClipToScreen ();
|
|
View.Draw (tops, neededLayout || forceRedraw);
|
|
View.SetClipToScreen ();
|
|
_driver?.Refresh ();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void ResetState (bool ignoreDisposed = false)
|
|
{
|
|
// Shutdown is the bookend for Init. As such it needs to clean up all resources
|
|
// Init created. Apps that do any threading will need to code defensively for this.
|
|
// e.g. see Issue #537
|
|
foreach (Toplevel? t in _topLevels)
|
|
{
|
|
t!.Running = false;
|
|
}
|
|
|
|
if (_popover?.GetActivePopover () is View popover)
|
|
{
|
|
// This forcefully closes the popover; invoking Command.Quit would be more graceful
|
|
// but since this is shutdown, doing this is ok.
|
|
popover.Visible = false;
|
|
}
|
|
|
|
_popover?.Dispose ();
|
|
_popover = null;
|
|
|
|
_topLevels.Clear ();
|
|
#if DEBUG_IDISPOSABLE
|
|
|
|
// Don't dispose the Top. It's up to caller dispose it
|
|
if (View.EnableDebugIDisposableAsserts && !ignoreDisposed && _top is { })
|
|
{
|
|
Debug.Assert (_top.WasDisposed, $"Title = {_top.Title}, Id = {_top.Id}");
|
|
|
|
// If End wasn't called _cachedRunStateToplevel may be null
|
|
if (_cachedRunStateToplevel is { })
|
|
{
|
|
Debug.Assert (_cachedRunStateToplevel.WasDisposed);
|
|
Debug.Assert (_cachedRunStateToplevel == _top);
|
|
}
|
|
}
|
|
#endif
|
|
_top = null;
|
|
_cachedRunStateToplevel = null;
|
|
|
|
_mainThreadId = -1;
|
|
|
|
// These static properties need to be reset
|
|
Application.EndAfterFirstIteration = false;
|
|
Application.ClearScreenNextIteration = false;
|
|
Application.ClearForceFakeConsole ();
|
|
|
|
// Driver stuff
|
|
if (_driver is { })
|
|
{
|
|
Application.UnsubscribeDriverEvents ();
|
|
_driver?.End ();
|
|
_driver = null;
|
|
}
|
|
|
|
// Reset Screen to null so it will be recalculated on next access
|
|
ResetScreen ();
|
|
|
|
// Run State stuff - these are static events on Application class
|
|
Application.ClearRunStateEvents ();
|
|
|
|
// Mouse and Keyboard will be lazy-initialized in ApplicationImpl on next access
|
|
_initialized = false;
|
|
|
|
// Mouse
|
|
// Do not clear _lastMousePosition; Popovers require it to stay set with
|
|
// last mouse pos.
|
|
//_lastMousePosition = null;
|
|
Application.CachedViewsUnderMouse.Clear ();
|
|
Application.ResetMouseState ();
|
|
|
|
// Keyboard events and bindings are now managed by the Keyboard instance
|
|
|
|
Application.ClearSizeChangingEvent ();
|
|
|
|
_navigation = null;
|
|
|
|
// Reset SupportedCultures so it's re-cached on next access
|
|
_supportedCultures = null;
|
|
|
|
// Reset synchronization context to allow the user to run async/await,
|
|
// as the main loop has been ended, the synchronization context from
|
|
// gui.cs does no longer process any callbacks. See #1084 for more details:
|
|
// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
|
|
SynchronizationContext.SetSynchronizationContext (null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the Screen field to null so it will be recalculated on next access.
|
|
/// </summary>
|
|
internal void ResetScreen ()
|
|
{
|
|
lock (_lockScreen)
|
|
{
|
|
_screen = null;
|
|
}
|
|
}
|
|
}
|