diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs
index 1e101fd15..5b6809ee5 100644
--- a/Terminal.Gui/App/ApplicationImpl.cs
+++ b/Terminal.Gui/App/ApplicationImpl.cs
@@ -6,58 +6,45 @@ using System.Globalization;
using System.Reflection;
using System.Resources;
using Microsoft.Extensions.Logging;
-using Terminal.Gui.Drivers;
namespace Terminal.Gui.App;
///
-/// Implementation of core methods using the modern
-/// main loop architecture with component factories for different platforms.
+/// Implementation of core methods using the modern
+/// main loop architecture with component factories for different platforms.
///
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 _topLevels = new ();
- private int _mainThreadId = -1;
- private bool _force16Colors;
- private string _forceDriver = string.Empty;
- private readonly List _sixel = new ();
- private readonly object _lockScreen = new ();
- private Rectangle? _screen;
- private bool _clearScreenNextIteration;
- private ushort _maximumIterationsPerSecond = 25; // Default value for MaximumIterationsPerSecond
- private List? _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 _lazyInstance = new (() => new ApplicationImpl ());
///
- /// Gets the currently configured backend implementation of gateway methods.
- /// Change to your own implementation by using (before init).
+ /// Creates a new instance of the Application backend.
///
- public static IApplication Instance => _lazyInstance.Value;
+ public ApplicationImpl () { }
+
+ internal ApplicationImpl (IComponentFactory componentFactory) { _componentFactory = componentFactory; }
+
+ // 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 readonly IComponentFactory? _componentFactory;
+ private readonly ITimedEvents _timedEvents = new TimedEvents ();
+ private readonly object _lockScreen = new ();
+ private string? _driverName;
+ private IConsoleDriver? _driver;
+ private Rectangle? _screen;
+ private List? _supportedCultures;
+
+ private IMouse? _mouse;
+
+ private IKeyboard? _keyboard;
///
public ITimedEvents? TimedEvents => _timedEvents;
- internal IMainLoopCoordinator? Coordinator => _coordinator;
-
- private IMouse? _mouse;
-
///
- /// Handles mouse event state and processing.
+ /// Handles mouse event state and processing.
///
public IMouse Mouse
{
@@ -67,20 +54,14 @@ public class ApplicationImpl : IApplication
{
_mouse = new MouseImpl { Application = this };
}
+
return _mouse;
}
set => _mouse = value ?? throw new ArgumentNullException (nameof (value));
}
///
- /// Handles which (if any) has captured the mouse
- ///
- public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler ();
-
- private IKeyboard? _keyboard;
-
- ///
- /// Handles keyboard input and key bindings at the Application level
+ /// Handles keyboard input and key bindings at the Application level
///
public IKeyboard Keyboard
{
@@ -90,6 +71,7 @@ public class ApplicationImpl : IApplication
{
_keyboard = new KeyboardImpl { Application = this };
}
+
return _keyboard;
}
set => _keyboard = value ?? throw new ArgumentNullException (nameof (value));
@@ -103,28 +85,16 @@ public class ApplicationImpl : IApplication
}
///
- public bool Initialized
- {
- get => _initialized;
- set => _initialized = value;
- }
+ public bool Initialized { get; set; }
///
- public bool Force16Colors
- {
- get => _force16Colors;
- set => _force16Colors = value;
- }
+ public bool Force16Colors { get; set; }
///
- public string ForceDriver
- {
- get => _forceDriver;
- set => _forceDriver = value;
- }
+ public string ForceDriver { get; set; } = string.Empty;
///
- public List Sixel => _sixel;
+ public List Sixel { get; } = [];
///
public Rectangle Screen
@@ -143,9 +113,9 @@ public class ApplicationImpl : IApplication
}
set
{
- if (value is {} && (value.X != 0 || value.Y != 0))
+ if (value is { } && (value.X != 0 || value.Y != 0))
{
- throw new NotImplementedException ($"Screen locations other than 0, 0 are not yet supported");
+ throw new NotImplementedException ("Screen locations other than 0, 0 are not yet supported");
}
lock (_lockScreen)
@@ -156,42 +126,22 @@ public class ApplicationImpl : IApplication
}
///
- public bool ClearScreenNextIteration
- {
- get => _clearScreenNextIteration;
- set => _clearScreenNextIteration = value;
- }
+ public bool ClearScreenNextIteration { get; set; }
///
- public ApplicationPopover? Popover
- {
- get => _popover;
- set => _popover = value;
- }
+ public ApplicationPopover? Popover { get; set; }
///
- public ApplicationNavigation? Navigation
- {
- get => _navigation;
- set => _navigation = value;
- }
+ public ApplicationNavigation? Navigation { get; set; }
///
- public Toplevel? Top
- {
- get => _top;
- set => _top = value;
- }
+ public Toplevel? Top { get; set; }
///
- public ConcurrentStack TopLevels => _topLevels;
+ public ConcurrentStack TopLevels { get; } = new ();
///
- public ushort MaximumIterationsPerSecond
- {
- get => _maximumIterationsPerSecond;
- set => _maximumIterationsPerSecond = value;
- }
+ public ushort MaximumIterationsPerSecond { get; set; } = 25;
///
public List? SupportedCultures
@@ -202,59 +152,20 @@ public class ApplicationImpl : IApplication
{
_supportedCultures = GetSupportedCultures ();
}
+
return _supportedCultures;
}
}
- ///
- /// Internal helper to raise InitializedChanged static event. Used by both legacy and modern Init paths.
- ///
- internal void RaiseInitializedChanged (bool initialized)
- {
- Application.OnInitializedChanged (this, new (initialized));
- }
-
- ///
- /// Gets or sets the main thread ID for the application.
- ///
- internal int MainThreadId
- {
- get => _mainThreadId;
- set => _mainThreadId = value;
- }
-
///
- public void RequestStop () => RequestStop (null);
-
- ///
- /// Creates a new instance of the Application backend.
- ///
- public ApplicationImpl ()
- {
- }
-
- internal ApplicationImpl (IComponentFactory componentFactory)
- {
- _componentFactory = componentFactory;
- }
-
- ///
- /// 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);
- }
+ public void RequestStop () { RequestStop (null); }
///
[RequiresUnreferencedCode ("AOT")]
[RequiresDynamicCode ("AOT")]
public void Init (IConsoleDriver? driver = null, string? driverName = null)
{
- if (_initialized)
+ if (Initialized)
{
Logging.Logger.LogError ("Init called multiple times without shutdown, aborting.");
@@ -268,17 +179,17 @@ public class ApplicationImpl : IApplication
if (string.IsNullOrWhiteSpace (_driverName))
{
- _driverName = _forceDriver;
+ _driverName = ForceDriver;
}
- Debug.Assert(_navigation is null);
- _navigation = new ();
+ Debug.Assert (Navigation is null);
+ Navigation = new ();
- Debug.Assert (_popover is null);
- _popover = new ();
+ Debug.Assert (Popover is null);
+ Popover = new ();
// Preserve existing keyboard settings if they exist
- bool hasExistingKeyboard = _keyboard is not null;
+ bool hasExistingKeyboard = _keyboard is { };
Key existingQuitKey = _keyboard?.QuitKey ?? Key.Esc;
Key existingArrangeKey = _keyboard?.ArrangeKey ?? Key.F5.WithCtrl;
Key existingNextTabKey = _keyboard?.NextTabKey ?? Key.Tab;
@@ -302,25 +213,340 @@ public class ApplicationImpl : IApplication
CreateDriver (driverName ?? _driverName);
- _initialized = true;
+ Initialized = true;
Application.OnInitializedChanged (this, new (true));
SubscribeDriverEvents ();
SynchronizationContext.SetSynchronizationContext (new ());
- _mainThreadId = Thread.CurrentThread.ManagedThreadId;
+ MainThreadId = Thread.CurrentThread.ManagedThreadId;
+ }
+
+ ///
+ /// Runs the application by creating a object and calling
+ /// .
+ ///
+ /// 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
+ /// .
+ ///
+ ///
+ ///
+ /// The to use. If not specified the default driver for the platform will
+ /// be used. Must be if has already been called.
+ ///
+ /// The created T object. The caller is responsible for disposing this object.
+ [RequiresUnreferencedCode ("AOT")]
+ [RequiresDynamicCode ("AOT")]
+ public T Run (Func? errorHandler = null, IConsoleDriver? driver = null)
+ where T : Toplevel, new ()
+ {
+ if (!Initialized)
+ {
+ // Init() has NOT been called. Auto-initialize as per interface contract.
+ Init (driver);
+ }
+
+ T top = new ();
+ Run (top, errorHandler);
+
+ return top;
+ }
+
+ /// Runs the Application using the provided view.
+ /// The to run as a modal.
+ /// Handler for any unhandled exceptions.
+ public void Run (Toplevel view, Func? 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);
+ }
+
+ /// Shutdown an application initialized with .
+ 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 ());
+ }
+
+ ///
+ 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;
+ }
+
+ ///
+ public void Invoke (Action action)
+ {
+ // If we are already on the main UI thread
+ if (Top is { Running: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId)
+ {
+ action ();
+
+ return;
+ }
+
+ _timedEvents.Add (
+ TimeSpan.Zero,
+ () =>
+ {
+ action ();
+
+ return false;
+ }
+ );
+ }
+
+ ///
+ public bool IsLegacy => false;
+
+ ///
+ public object AddTimeout (TimeSpan time, Func callback) { return _timedEvents.Add (time, callback); }
+
+ ///
+ public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); }
+
+ ///
+ public void LayoutAndDraw (bool forceRedraw = false)
+ {
+ List 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 ();
+ }
+
+ ///
+ 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;
+
+ // Driver stuff
+ if (_driver is { })
+ {
+ 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);
+ }
+
+ ///
+ /// 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 (newApplication); }
+
+ ///
+ /// Gets the currently configured backend implementation of gateway methods.
+ /// Change to your own implementation by using (before init).
+ ///
+ public static IApplication Instance => _lazyInstance.Value;
+
+ internal IMainLoopCoordinator? Coordinator { get; private set; }
+
+ ///
+ /// Gets or sets the main thread ID for the application.
+ ///
+ internal int MainThreadId { get; set; } = -1;
+
+ ///
+ /// Resets the Screen field to null so it will be recalculated on next access.
+ ///
+ internal void ResetScreen ()
+ {
+ lock (_lockScreen)
+ {
+ _screen = null;
+ }
}
private void CreateDriver (string? driverName)
{
// When running unit tests, always use FakeDriver unless explicitly specified
- if (ConsoleDriver.RunningUnitTests &&
- string.IsNullOrEmpty (driverName) &&
- _componentFactory is null)
+ 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 ();
+ Coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
+ Coordinator.StartAsync ().Wait ();
if (_driver == null)
{
@@ -340,37 +566,37 @@ public class ApplicationImpl : IApplication
// Then check driverName
bool nameIsWindows = driverName?.Contains ("win", StringComparison.OrdinalIgnoreCase) ?? false;
- bool nameIsDotNet = (driverName?.Contains ("dotnet", 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 ());
+ Coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
}
else if (factoryIsWindows || (!factoryIsDotNet && !factoryIsUnix && nameIsWindows))
{
- _coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
+ Coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
}
else if (factoryIsDotNet || (!factoryIsWindows && !factoryIsUnix && nameIsDotNet))
{
- _coordinator = CreateSubcomponents (() => new NetComponentFactory ());
+ Coordinator = CreateSubcomponents (() => new NetComponentFactory ());
}
else if (factoryIsUnix || (!factoryIsWindows && !factoryIsDotNet && nameIsUnix))
{
- _coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
+ Coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
}
else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
{
- _coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
+ Coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
}
else
{
- _coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
+ Coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
}
- _coordinator.StartAsync ().Wait ();
+ Coordinator.StartAsync ().Wait ();
if (_driver == null)
{
@@ -397,296 +623,51 @@ public class ApplicationImpl : IApplication
return new MainLoopCoordinator (_timedEvents, inputBuffer, loop, cf);
}
- ///
- /// Runs the application by creating a object and calling
- /// .
- ///
- /// 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); }
+ private void Driver_KeyDown (object? sender, Key e) { Application.RaiseKeyDownEvent (e); }
+ private void Driver_KeyUp (object? sender, Key e) { Application.RaiseKeyUpEvent (e); }
+ private void Driver_MouseEvent (object? sender, MouseEventArgs e) { Application.RaiseMouseEvent (e); }
- ///
- /// Runs the application by creating a -derived object of type T and calling
- /// .
- ///
- ///
- ///
- /// The to use. If not specified the default driver for the platform will
- /// be used. Must be if has already been called.
- ///
- /// The created T object. The caller is responsible for disposing this object.
- [RequiresUnreferencedCode ("AOT")]
- [RequiresDynamicCode ("AOT")]
- public T Run (Func? errorHandler = null, IConsoleDriver? driver = null)
- where T : Toplevel, new()
+ private void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { Application.OnSizeChanging (e); }
+
+ private static List GetAvailableCulturesFromEmbeddedResources ()
{
- if (!_initialized)
- {
- // Init() has NOT been called. Auto-initialize as per interface contract.
- Init (driver, null);
- }
+ ResourceManager rm = new (typeof (Strings));
- T top = new ();
- Run (top, errorHandler);
- return top;
+ CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
+
+ return cultures.Where (cultureInfo =>
+ !cultureInfo.Equals (CultureInfo.InvariantCulture)
+ && rm.GetResourceSet (cultureInfo, true, false) is { }
+ )
+ .ToList ();
}
- /// Runs the Application using the provided view.
- /// The to run as a modal.
- /// Handler for any unhandled exceptions.
- public void Run (Toplevel view, Func? errorHandler = null)
+ // BUGBUG: This does not return en-US even though it's supported by default
+ private static List GetSupportedCultures ()
{
- Logging.Information ($"Run '{view}'");
- ArgumentNullException.ThrowIfNull (view);
+ CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
- if (!_initialized)
+ // Get the assembly
+ var assembly = Assembly.GetExecutingAssembly ();
+
+ //Find the location of the assembly
+ string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory;
+
+ // Find the resource file name of the assembly
+ var resourceFilename = $"{assembly.GetName ().Name}.resources.dll";
+
+ if (cultures.Length > 1 && Directory.Exists (Path.Combine (assemblyLocation, "pt-PT")))
{
- throw new NotInitializedException (nameof (Run));
+ // Return all culture for which satellite folder found with culture code.
+ return cultures.Where (cultureInfo =>
+ Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name))
+ && File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename))
+ )
+ .ToList ();
}
- 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);
- }
-
- /// Shutdown an application initialized with .
- 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 ());
- }
-
- ///
- 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;
- }
-
- ///
- public void Invoke (Action action)
- {
- // If we are already on the main UI thread
- if (_top is { Running: true } && _mainThreadId == Thread.CurrentThread.ManagedThreadId)
- {
- action ();
- return;
- }
-
- _timedEvents.Add (TimeSpan.Zero,
- () =>
- {
- action ();
- return false;
- }
- );
- }
-
- ///
- public bool IsLegacy => false;
-
- ///
- public object AddTimeout (TimeSpan time, Func callback) { return _timedEvents.Add (time, callback); }
-
- ///
- public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); }
-
- ///
- public void LayoutAndDraw (bool forceRedraw = false)
- {
- List 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 ();
- }
-
- ///
- 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 { })
- {
- 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);
- }
-
- ///
- /// Resets the Screen field to null so it will be recalculated on next access.
- ///
- internal void ResetScreen ()
- {
- lock (_lockScreen)
- {
- _screen = null;
- }
+ // It's called from a self-contained single-file and get available cultures from the embedded resources strings.
+ return GetAvailableCulturesFromEmbeddedResources ();
}
private void SubscribeDriverEvents ()
@@ -714,52 +695,4 @@ public class ApplicationImpl : IApplication
_driver.KeyUp -= Driver_KeyUp;
_driver.MouseEvent -= Driver_MouseEvent;
}
-
- private void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { Application.OnSizeChanging (e); }
- private void Driver_KeyDown (object? sender, Key e) { Application.RaiseKeyDownEvent (e); }
- private void Driver_KeyUp (object? sender, Key e) { Application.RaiseKeyUpEvent (e); }
- private void Driver_MouseEvent (object? sender, MouseEventArgs e) { Application.RaiseMouseEvent (e); }
-
- private static List GetAvailableCulturesFromEmbeddedResources ()
- {
- ResourceManager rm = new (typeof (Strings));
-
- CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
-
- return cultures.Where (
- cultureInfo =>
- !cultureInfo.Equals (CultureInfo.InvariantCulture)
- && rm.GetResourceSet (cultureInfo, true, false) is { }
- )
- .ToList ();
- }
-
- // BUGBUG: This does not return en-US even though it's supported by default
- private static List GetSupportedCultures ()
- {
- CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
-
- // Get the assembly
- var assembly = Assembly.GetExecutingAssembly ();
-
- //Find the location of the assembly
- string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory;
-
- // Find the resource file name of the assembly
- var resourceFilename = $"{assembly.GetName ().Name}.resources.dll";
-
- if (cultures.Length > 1 && Directory.Exists (Path.Combine (assemblyLocation, "pt-PT")))
- {
- // Return all culture for which satellite folder found with culture code.
- return cultures.Where (
- cultureInfo =>
- Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name))
- && File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename))
- )
- .ToList ();
- }
-
- // It's called from a self-contained single-file and get available cultures from the embedded resources strings.
- return GetAvailableCulturesFromEmbeddedResources ();
- }
}
diff --git a/Terminal.Gui/App/IApplication.cs b/Terminal.Gui/App/IApplication.cs
index c7c91fab9..a2777327b 100644
--- a/Terminal.Gui/App/IApplication.cs
+++ b/Terminal.Gui/App/IApplication.cs
@@ -1,4 +1,5 @@
#nullable enable
+using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -19,21 +20,13 @@ public interface IApplication
object AddTimeout (TimeSpan time, Func callback);
///
- /// Handles keyboard input and key bindings at the Application level.
+ /// Gets or sets whether the screen will be cleared, and all Views redrawn, during the next Application iteration.
///
- IKeyboard Keyboard { get; set; }
-
- ///
- /// Handles mouse event state and processing.
- ///
- IMouse Mouse { get; set; }
+ bool ClearScreenNextIteration { get; set; }
/// Gets or sets the console driver being used.
IConsoleDriver? Driver { get; set; }
- /// Gets or sets whether the application has been initialized.
- bool Initialized { get; set; }
-
///
/// Gets or sets whether will be forced to output only the 16 colors defined in
/// . The default is , meaning 24-bit (TrueColor) colors will be output
@@ -47,48 +40,6 @@ public interface IApplication
///
string ForceDriver { get; set; }
- ///
- /// Collection of sixel images to write out to screen when updating.
- /// Only add to this collection if you are sure terminal supports sixel format.
- ///
- List Sixel { get; }
-
- ///
- /// Gets or sets the size of the screen. By default, this is the size of the screen as reported by the .
- ///
- Rectangle Screen { get; set; }
-
- ///
- /// Gets or sets whether the screen will be cleared, and all Views redrawn, during the next Application iteration.
- ///
- bool ClearScreenNextIteration { get; set; }
-
- /// Gets or sets the popover manager.
- ApplicationPopover? Popover { get; set; }
-
- /// Gets or sets the navigation manager.
- ApplicationNavigation? Navigation { get; set; }
-
- /// Gets the currently active Toplevel.
- Toplevel? Top { get; set; }
-
- /// Gets the stack of all Toplevels.
- System.Collections.Concurrent.ConcurrentStack TopLevels { get; }
-
- /// Requests that the application stop running.
- void RequestStop ();
-
- ///
- /// Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that
- /// need to be laid out (see ) will be laid out.
- /// Only Views that need to be drawn (see ) will be drawn.
- ///
- ///
- /// If the entire View hierarchy will be redrawn. The default is and
- /// should only be overriden for testing.
- ///
- public void LayoutAndDraw (bool forceRedraw = false);
-
/// Initializes a new instance of Application.
/// Call this method once per instance (or after has been called).
///
@@ -121,6 +72,9 @@ public interface IApplication
[RequiresDynamicCode ("AOT")]
public void Init (IConsoleDriver? driver = null, string? driverName = null);
+ /// Gets or sets whether the application has been initialized.
+ bool Initialized { get; set; }
+
/// Runs on the main UI loop thread
/// the action to be invoked on the main processing thread.
void Invoke (Action action);
@@ -131,6 +85,45 @@ public interface IApplication
///
bool IsLegacy { get; }
+ ///
+ /// Handles keyboard input and key bindings at the Application level.
+ ///
+ IKeyboard Keyboard { get; set; }
+
+ ///
+ /// Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that
+ /// need to be laid out (see ) will be laid out.
+ /// Only Views that need to be drawn (see ) will be drawn.
+ ///
+ ///
+ /// If the entire View hierarchy will be redrawn. The default is and
+ /// should only be overriden for testing.
+ ///
+ public void LayoutAndDraw (bool forceRedraw = false);
+
+ ///
+ /// Maximum number of iterations of the main loop (and hence draws)
+ /// to allow to occur per second. Defaults to which is
+ /// a 40ms sleep
+ /// after iteration (factoring in how long iteration took to run).
+ ///
+ /// Note that not every iteration draws (see ).
+ /// Only affects v2 drivers.
+ ///
+ ///
+ ushort MaximumIterationsPerSecond { get; set; }
+
+ ///
+ /// Handles mouse event state and processing.
+ ///
+ IMouse Mouse { get; set; }
+
+ /// Gets or sets the navigation manager.
+ ApplicationNavigation? Navigation { get; set; }
+
+ /// Gets or sets the popover manager.
+ ApplicationPopover? Popover { get; set; }
+
/// Removes a previously scheduled timeout
/// The token parameter is the value returned by .
///
@@ -144,6 +137,9 @@ public interface IApplication
///
bool RemoveTimeout (object token);
+ /// Requests that the application stop running.
+ void RequestStop ();
+
/// Stops the provided , causing or the if provided.
/// The to stop.
///
@@ -155,6 +151,12 @@ public interface IApplication
///
void RequestStop (Toplevel? top);
+ ///
+ /// Resets the application state to defaults. This is called by .
+ ///
+ /// If true, will not assert that views are disposed.
+ void ResetState (bool ignoreDisposed = false);
+
///
/// Runs the application by creating a object and calling
/// .
@@ -198,7 +200,7 @@ public interface IApplication
[RequiresUnreferencedCode ("AOT")]
[RequiresDynamicCode ("AOT")]
public T Run (Func? errorHandler = null, IConsoleDriver? driver = null)
- where T : Toplevel, new();
+ where T : Toplevel, new ();
/// Runs the Application using the provided view.
///
@@ -244,6 +246,14 @@ public interface IApplication
///
public void Run (Toplevel view, Func? errorHandler = null);
+ ///
+ /// Gets or sets the size of the screen. By default, this is the size of the screen as reported by the
+ /// .
+ /// Setting the position is not supported and may throw . The size may be set
+ /// but will not persist if the terminal is resized and will not impact the actual terminal size.
+ ///
+ Rectangle Screen { get; set; }
+
/// Shutdown an application initialized with .
///
/// Shutdown must be called for every call to or
@@ -254,26 +264,23 @@ public interface IApplication
public void Shutdown ();
///
- /// Handles recurring events. These are invoked on the main UI thread - allowing for
- /// safe updates to instances.
+ /// Collection of sixel images to write out to screen when updating.
+ /// Only add to this collection if you are sure terminal supports sixel format.
///
- ITimedEvents? TimedEvents { get; }
-
- ///
- /// Maximum number of iterations of the main loop (and hence draws)
- /// to allow to occur per second. Defaults to which is a 40ms sleep
- /// after iteration (factoring in how long iteration took to run).
- /// Note that not every iteration draws (see ).
- /// Only affects v2 drivers.
- ///
- ushort MaximumIterationsPerSecond { get; set; }
+ List Sixel { get; }
/// Gets all cultures supported by the application without the invariant language.
List? SupportedCultures { get; }
///
- /// Resets the application state to defaults. This is called by .
+ /// Handles recurring events. These are invoked on the main UI thread - allowing for
+ /// safe updates to instances.
///
- /// If true, will not assert that views are disposed.
- void ResetState (bool ignoreDisposed = false);
+ ITimedEvents? TimedEvents { get; }
+
+ /// Gets the currently active Toplevel.
+ Toplevel? Top { get; set; }
+
+ /// Gets the stack of all Toplevels.
+ ConcurrentStack TopLevels { get; }
}