diff --git a/Terminal.Gui/App/Application.Driver.cs b/Terminal.Gui/App/Application.Driver.cs
index a14e88d21..e7a3243e2 100644
--- a/Terminal.Gui/App/Application.Driver.cs
+++ b/Terminal.Gui/App/Application.Driver.cs
@@ -8,7 +8,11 @@ public static partial class Application // Driver abstractions
// TODO: Add to IApplication
/// Gets the that has been selected. See also .
- public static IConsoleDriver? Driver { get; internal set; }
+ public static IConsoleDriver? Driver
+ {
+ get => ApplicationImpl.Instance.Driver;
+ internal set => ApplicationImpl.Instance.Driver = value;
+ }
// TODO: Add to IApplication
// BUGBUG: Force16Colors should be nullable.
diff --git a/Terminal.Gui/App/Application.Initialization.cs b/Terminal.Gui/App/Application.Initialization.cs
index 1d061209e..188545366 100644
--- a/Terminal.Gui/App/Application.Initialization.cs
+++ b/Terminal.Gui/App/Application.Initialization.cs
@@ -5,42 +5,10 @@ using System.Reflection;
namespace Terminal.Gui.App;
-public static partial class Application // Lifecycle (Init/Shutdown)
+public static partial class Application // Initialization (Init/Shutdown)
{
- // TODO: Add to IApplication
- /// Gets of list of types and type names that are available.
- ///
- [RequiresUnreferencedCode ("AOT")]
- public static (List, List) GetDriverTypes ()
- {
- // use reflection to get the list of drivers
- List driverTypes = new ();
- // Only inspect the IConsoleDriver assembly
- Assembly asm = typeof (IConsoleDriver).Assembly;
-
- foreach (Type? type in asm.GetTypes ())
- {
- if (typeof (IConsoleDriver).IsAssignableFrom (type) && type is { IsAbstract: false, IsClass: true })
- {
- driverTypes.Add (type);
- }
- }
-
- List driverTypeNames = driverTypes
- .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
- .Select (d => d!.Name)
- .Union (["dotnet", "windows", "unix", "fake"])
- .ToList ()!;
-
- return (driverTypes, driverTypeNames);
- }
-
- // TODO: Add to IApplicationLifecycle
- ///
- /// Initializes a new instance of a Terminal.Gui Application. must be called when the
- /// application is closing.
- ///
+ /// Initializes a new instance of a Terminal.Gui Application. must be called when the application is closing.
/// Call this method once per instance (or after has been called).
///
/// This function loads the right for the platform, Creates a . and
@@ -76,60 +44,31 @@ public static partial class Application // Lifecycle (Init/Shutdown)
// that isn't supported by the modern application architecture
if (driver is null)
{
- string driverNameToCheck = string.IsNullOrWhiteSpace (driverName) ? ForceDriver : driverName;
-
+ var driverNameToCheck = string.IsNullOrWhiteSpace (driverName) ? ForceDriver : driverName;
if (!string.IsNullOrEmpty (driverNameToCheck))
{
(List drivers, List driverTypeNames) = GetDriverTypes ();
Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (driverNameToCheck, StringComparison.InvariantCultureIgnoreCase));
-
+
// If it's a legacy IConsoleDriver (not a Facade), use InternalInit which supports legacy drivers
if (driverType is { } && !typeof (IConsoleDriverFacade).IsAssignableFrom (driverType))
{
InternalInit (driver, driverName);
-
return;
}
}
}
-
+
// Otherwise delegate to the ApplicationImpl instance (which uses the modern architecture)
ApplicationImpl.Instance.Init (driver, driverName ?? ForceDriver);
}
- // TODO: Add to IApplicationLifecycle
- ///
- /// Gets whether the application has been initialized with and not yet shutdown with
- /// .
- ///
- ///
- ///
- /// The event is raised after the and
- /// methods have been called.
- ///
- ///
- public static bool Initialized { get; internal set; }
+ internal static int MainThreadId
+ {
+ get => ((ApplicationImpl)ApplicationImpl.Instance).MainThreadId;
+ set => ((ApplicationImpl)ApplicationImpl.Instance).MainThreadId = value;
+ }
- // TODO: Add to IApplicationLifecycle
- ///
- /// This event is raised after the and methods have been called.
- ///
- ///
- /// Intended to support unit tests that need to know when the application has been initialized.
- ///
- public static event EventHandler>? InitializedChanged;
-
- // TODO: Add to IApplicationLifecycle
- /// 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 static void Shutdown () { ApplicationImpl.Instance.Shutdown (); }
-
- // TODO: Add to IApplicationLifecycle
// INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop.
//
// Called from:
@@ -160,7 +99,7 @@ public static partial class Application // Lifecycle (Init/Shutdown)
if (!calledViaRunT)
{
// Reset all class variables (Application is a singleton).
- ResetState (true);
+ ResetState (ignoreDisposed: true);
}
// For UnitTests
@@ -185,7 +124,7 @@ public static partial class Application // Lifecycle (Init/Shutdown)
//{
// (List drivers, List driverTypeNames) = GetDriverTypes ();
// Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase));
-
+
// if (driverType is { } && !typeof (IConsoleDriverFacade).IsAssignableFrom (driverType))
// {
// // This is a legacy driver (not a ConsoleDriverFacade)
@@ -193,13 +132,12 @@ public static partial class Application // Lifecycle (Init/Shutdown)
// useLegacyDriver = true;
// }
//}
-
+
//// Use the modern application architecture
//if (!useLegacyDriver)
{
ApplicationImpl.Instance.Init (driver, driverName);
Debug.Assert (Driver is { });
-
return;
}
}
@@ -240,14 +178,6 @@ public static partial class Application // Lifecycle (Init/Shutdown)
InitializedChanged?.Invoke (null, new (init));
}
- internal static int MainThreadId { get; set; } = -1;
-
- // TODO: Add to IApplicationLifecycle
- ///
- /// Raises the event.
- ///
- internal static void OnInitializedChanged (object sender, EventArgs e) { InitializedChanged?.Invoke (sender, e); }
-
internal static void SubscribeDriverEvents ()
{
ArgumentNullException.ThrowIfNull (Driver);
@@ -268,9 +198,78 @@ public static partial class Application // Lifecycle (Init/Shutdown)
Driver.MouseEvent -= Driver_MouseEvent;
}
+ private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
private static void Driver_KeyDown (object? sender, Key e) { RaiseKeyDownEvent (e); }
private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); }
private static void Driver_MouseEvent (object? sender, MouseEventArgs e) { RaiseMouseEvent (e); }
- private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
+ /// Gets of list of types and type names that are available.
+ ///
+ [RequiresUnreferencedCode ("AOT")]
+ public static (List, List) GetDriverTypes ()
+ {
+ // use reflection to get the list of drivers
+ List driverTypes = new ();
+
+ // Only inspect the IConsoleDriver assembly
+ var asm = typeof (IConsoleDriver).Assembly;
+
+ foreach (Type? type in asm.GetTypes ())
+ {
+ if (typeof (IConsoleDriver).IsAssignableFrom (type) &&
+ type is { IsAbstract: false, IsClass: true })
+ {
+ driverTypes.Add (type);
+ }
+ }
+
+ List driverTypeNames = driverTypes
+ .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
+ .Select (d => d!.Name)
+ .Union (["dotnet", "windows", "unix", "fake"])
+ .ToList ()!;
+
+
+
+ return (driverTypes, driverTypeNames);
+ }
+
+ /// 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 static void Shutdown () => ApplicationImpl.Instance.Shutdown ();
+
+ ///
+ /// Gets whether the application has been initialized with and not yet shutdown with .
+ ///
+ ///
+ ///
+ /// The event is raised after the and methods have been called.
+ ///
+ ///
+ public static bool Initialized
+ {
+ get => ApplicationImpl.Instance.Initialized;
+ internal set => ApplicationImpl.Instance.Initialized = value;
+ }
+
+ ///
+ /// This event is raised after the and methods have been called.
+ ///
+ ///
+ /// Intended to support unit tests that need to know when the application has been initialized.
+ ///
+ public static event EventHandler>? InitializedChanged;
+
+ ///
+ /// Raises the event.
+ ///
+ internal static void OnInitializedChanged (object sender, EventArgs e)
+ {
+ Application.InitializedChanged?.Invoke (sender, e);
+ }
}
diff --git a/Terminal.Gui/App/Application.Navigation.cs b/Terminal.Gui/App/Application.Navigation.cs
index 0b35b80c6..28e86d2eb 100644
--- a/Terminal.Gui/App/Application.Navigation.cs
+++ b/Terminal.Gui/App/Application.Navigation.cs
@@ -7,7 +7,11 @@ public static partial class Application // Navigation stuff
///
/// Gets the instance for the current .
///
- public static ApplicationNavigation? Navigation { get; internal set; }
+ public static ApplicationNavigation? Navigation
+ {
+ get => ApplicationImpl.Instance.Navigation;
+ internal set => ApplicationImpl.Instance.Navigation = value;
+ }
/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
[ConfigurationProperty (Scope = typeof (SettingsScope))]
diff --git a/Terminal.Gui/App/Application.Popover.cs b/Terminal.Gui/App/Application.Popover.cs
index 25a8ebe9c..31522f80f 100644
--- a/Terminal.Gui/App/Application.Popover.cs
+++ b/Terminal.Gui/App/Application.Popover.cs
@@ -5,5 +5,9 @@ namespace Terminal.Gui.App;
public static partial class Application // Popover handling
{
/// Gets the Application manager.
- public static ApplicationPopover? Popover { get; internal set; }
+ public static ApplicationPopover? Popover
+ {
+ get => ApplicationImpl.Instance.Popover;
+ internal set => ApplicationImpl.Instance.Popover = value;
+ }
}
\ No newline at end of file
diff --git a/Terminal.Gui/App/Application.Toplevel.cs b/Terminal.Gui/App/Application.Toplevel.cs
index add62a5a5..cea9818ef 100644
--- a/Terminal.Gui/App/Application.Toplevel.cs
+++ b/Terminal.Gui/App/Application.Toplevel.cs
@@ -7,22 +7,14 @@ public static partial class Application // Toplevel handling
{
// BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What
- private static readonly ConcurrentStack _topLevels = new ();
- private static readonly object _topLevelsLock = new ();
-
/// Holds the stack of TopLevel views.
- internal static ConcurrentStack TopLevels
- {
- get
- {
- lock (_topLevelsLock)
- {
- return _topLevels;
- }
- }
- }
+ internal static ConcurrentStack TopLevels => ApplicationImpl.Instance.TopLevels;
/// The that is currently active.
/// The top.
- public static Toplevel? Top { get; internal set; }
+ public static Toplevel? Top
+ {
+ get => ApplicationImpl.Instance.Top;
+ internal set => ApplicationImpl.Instance.Top = value;
+ }
}
diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs
index 7d2b69135..5668d9450 100644
--- a/Terminal.Gui/App/ApplicationImpl.cs
+++ b/Terminal.Gui/App/ApplicationImpl.cs
@@ -3,35 +3,42 @@ using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
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 static readonly Lazy instance of Application
private static Lazy _lazyInstance = new (() => new ApplicationImpl ());
///
- /// Creates a new instance of the Application backend.
+ /// Gets the currently configured backend implementation of gateway methods.
+ /// Change to your own implementation by using (before init).
///
- public ApplicationImpl () { }
-
- internal ApplicationImpl (IComponentFactory componentFactory)
- {
- _componentFactory = componentFactory;
- }
-
- private readonly IComponentFactory? _componentFactory;
- private readonly ITimedEvents _timedEvents = new TimedEvents ();
- private string? _driverName;
+ public static IApplication Instance => _lazyInstance.Value;
///
public ITimedEvents? TimedEvents => _timedEvents;
+ internal IMainLoopCoordinator? Coordinator => _coordinator;
+
private IMouse? _mouse;
///
@@ -50,6 +57,11 @@ public class ApplicationImpl : IApplication
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;
///
@@ -71,74 +83,74 @@ public class ApplicationImpl : IApplication
///
public IConsoleDriver? Driver
{
- get => Application.Driver;
- set => Application.Driver = value;
+ get => _driver;
+ set => _driver = value;
}
///
public bool Initialized
{
- get => Application.Initialized;
- set => Application.Initialized = value;
+ get => _initialized;
+ set => _initialized = value;
}
///
public ApplicationPopover? Popover
{
- get => Application.Popover;
- set => Application.Popover = value;
+ get => _popover;
+ set => _popover = value;
}
///
public ApplicationNavigation? Navigation
{
- get => Application.Navigation;
- set => Application.Navigation = value;
+ get => _navigation;
+ set => _navigation = value;
}
- // TODO: Create an IViewHierarchy that encapsulates Top and TopLevels and LayoutAndDraw
///
public Toplevel? Top
{
- get => Application.Top;
- set => Application.Top = value;
+ get => _top;
+ set => _top = value;
}
///
- public ConcurrentStack TopLevels => Application.TopLevels;
+ public ConcurrentStack TopLevels => _topLevels;
- ///
- public void LayoutAndDraw (bool forceRedraw = false)
+ ///
+ /// Gets or sets the main thread ID for the application.
+ ///
+ internal int MainThreadId
{
- List tops = [.. TopLevels];
+ get => _mainThreadId;
+ set => _mainThreadId = value;
+ }
- if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
- {
- visiblePopover.SetNeedsDraw ();
- visiblePopover.SetNeedsLayout ();
- tops.Insert (0, visiblePopover);
- }
+ ///
+ public void RequestStop () => RequestStop (null);
- // BUGBUG: Application.Screen needs to be moved to IApplication
- bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Application.Screen.Size);
+ ///
+ /// Creates a new instance of the Application backend.
+ ///
+ public ApplicationImpl ()
+ {
+ }
- // BUGBUG: Application.ClearScreenNextIteration needs to be moved to IApplication
- if (Application.ClearScreenNextIteration)
- {
- forceRedraw = true;
- // BUGBUG: Application.Screen needs to be moved to IApplication
- Application.ClearScreenNextIteration = false;
- }
+ internal ApplicationImpl (IComponentFactory componentFactory)
+ {
+ _componentFactory = componentFactory;
+ }
- if (forceRedraw)
- {
- Driver?.ClearContents ();
- }
-
- View.SetClipToScreen ();
- View.Draw (tops, neededLayout || forceRedraw);
- View.SetClipToScreen ();
- Driver?.Refresh ();
+ ///
+ /// 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);
}
///
@@ -146,7 +158,7 @@ public class ApplicationImpl : IApplication
[RequiresDynamicCode ("AOT")]
public void Init (IConsoleDriver? driver = null, string? driverName = null)
{
- if (Application.Initialized)
+ if (_initialized)
{
Logging.Logger.LogError ("Init called multiple times without shutdown, aborting.");
@@ -163,13 +175,12 @@ public class ApplicationImpl : IApplication
_driverName = Application.ForceDriver;
}
- Debug.Assert (Application.Navigation is null);
- Application.Navigation = new ();
+ Debug.Assert(_navigation is null);
+ _navigation = new ();
- Debug.Assert (Application.Popover is null);
- Application.Popover = new ();
+ Debug.Assert (_popover is null);
+ _popover = new ();
- // TODO: Move this into IKeyboard and Keyboard implementation
// Preserve existing keyboard settings if they exist
bool hasExistingKeyboard = _keyboard is not null;
Key existingQuitKey = _keyboard?.QuitKey ?? Key.Esc;
@@ -195,13 +206,99 @@ public class ApplicationImpl : IApplication
CreateDriver (driverName ?? _driverName);
- Application.Initialized = true;
+ _initialized = true;
Application.OnInitializedChanged (this, new (true));
Application.SubscribeDriverEvents ();
SynchronizationContext.SetSynchronizationContext (new ());
- Application.MainThreadId = Thread.CurrentThread.ManagedThreadId;
+ _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;
+ bool factoryIsDotNet = _componentFactory is IComponentFactory;
+ bool factoryIsUnix = _componentFactory is IComponentFactory;
+ bool factoryIsFake = _componentFactory is IComponentFactory;
+
+ // 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 (Func> fallbackFactory)
+ {
+ ConcurrentQueue inputBuffer = new ();
+ ApplicationMainLoop loop = new ();
+
+ IComponentFactory cf;
+
+ if (_componentFactory is IComponentFactory typedFactory)
+ {
+ cf = typedFactory;
+ }
+ else
+ {
+ cf = fallbackFactory ();
+ }
+
+ return new MainLoopCoordinator (_timedEvents, inputBuffer, loop, cf);
}
///
@@ -228,15 +325,14 @@ public class ApplicationImpl : IApplication
public T Run (Func? errorHandler = null, IConsoleDriver? driver = null)
where T : Toplevel, new()
{
- if (!Application.Initialized)
+ if (!_initialized)
{
// Init() has NOT been called. Auto-initialize as per interface contract.
- Init (driver);
+ Init (driver, null);
}
T top = new ();
Run (top, errorHandler);
-
return top;
}
@@ -248,62 +344,74 @@ public class ApplicationImpl : IApplication
Logging.Information ($"Run '{view}'");
ArgumentNullException.ThrowIfNull (view);
- if (!Application.Initialized)
+ if (!_initialized)
{
throw new NotInitializedException (nameof (Run));
}
- if (Application.Driver == null)
+ if (_driver == null)
{
- throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view");
+ throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view");
}
- Application.Top = view;
+ _top = view;
RunState rs = Application.Begin (view);
- Application.Top.Running = true;
+ _top.Running = true;
- while (Application.TopLevels.TryPeek (out Toplevel? found) && found == view && view.Running)
+ while (_topLevels.TryPeek (out Toplevel? found) && found == view && view.Running)
{
- if (Coordinator is null)
+ if (_coordinator is null)
{
throw new ($"{nameof (IMainLoopCoordinator)} inexplicably became null during Run");
}
- Coordinator.RunIteration ();
+ _coordinator.RunIteration ();
}
- Logging.Information ("Run - Calling End");
+ Logging.Information ($"Run - Calling End");
Application.End (rs);
}
/// Shutdown an application initialized with .
public void Shutdown ()
{
- Coordinator?.Stop ();
-
- bool wasInitialized = Application.Initialized;
+ _coordinator?.Stop ();
+
+ bool wasInitialized = _initialized;
+
+ // 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
Application.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;
if (wasInitialized)
{
- bool init = Application.Initialized;
+ bool init = _initialized; // Will be false after clearing fields above
Application.OnInitializedChanged (this, new (in init));
}
- Application.Driver = null;
- _keyboard = null;
_lazyInstance = new (() => new ApplicationImpl ());
}
- ///
+ ///
public void RequestStop (Toplevel? top)
{
- Logging.Logger.LogInformation ($"RequestStop '{(top is { } ? top : "null")}'");
+ Logging.Logger.LogInformation ($"RequestStop '{(top is {} ? top : "null")}'");
- top ??= Application.Top;
+ top ??= _top;
if (top == null)
{
@@ -321,138 +429,65 @@ public class ApplicationImpl : IApplication
top.Running = false;
}
- ///
- public void RequestStop () => Application.RequestStop ();
-
-
- ///
+ ///
public void Invoke (Action action)
{
// If we are already on the main UI thread
- if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId)
+ if (_mainThreadId == Thread.CurrentThread.ManagedThreadId)
{
action ();
-
return;
}
- _timedEvents.Add (
- TimeSpan.Zero,
- () =>
- {
- action ();
-
- return false;
- }
- );
+ _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); }
- ///
- /// 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; }
-
- private void CreateDriver (string? driverName)
+ ///
+ public void LayoutAndDraw (bool forceRedraw = false)
{
- // When running unit tests, always use FakeDriver unless explicitly specified
- if (ConsoleDriver.RunningUnitTests && string.IsNullOrEmpty (driverName) && _componentFactory is null)
+ List tops = [.. _topLevels];
+
+ if (_popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
{
- Logging.Logger.LogDebug ("Unit test safeguard: forcing FakeDriver (RunningUnitTests=true, driverName=null, componentFactory=null)");
- Coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
- Coordinator.StartAsync ().Wait ();
-
- if (Application.Driver == null)
- {
- throw new ("Application.Driver was null even after booting MainLoopCoordinator");
- }
-
- return;
+ visiblePopover.SetNeedsDraw ();
+ visiblePopover.SetNeedsLayout ();
+ tops.Insert (0, visiblePopover);
}
- PlatformID p = Environment.OSVersion.Platform;
+ // BUGBUG: Application.Screen needs to be moved to IApplication
+ bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Application.Screen.Size);
- // Check component factory type first - this takes precedence over driverName
- bool factoryIsWindows = _componentFactory is IComponentFactory;
- bool factoryIsDotNet = _componentFactory is IComponentFactory;
- bool factoryIsUnix = _componentFactory is IComponentFactory;
- bool factoryIsFake = _componentFactory is IComponentFactory;
-
- // 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))
+ // BUGBUG: Application.ClearScreenNextIteration needs to be moved to IApplication
+ if (Application.ClearScreenNextIteration)
{
- 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 ());
+ forceRedraw = true;
+ // BUGBUG: Application.Screen needs to be moved to IApplication
+ Application.ClearScreenNextIteration = false;
}
- Coordinator.StartAsync ().Wait ();
-
- if (Application.Driver == null)
+ if (forceRedraw)
{
- throw new ("Application.Driver was null even after booting MainLoopCoordinator");
- }
- }
-
- private IMainLoopCoordinator CreateSubcomponents (Func> fallbackFactory)
- {
- ConcurrentQueue inputBuffer = new ();
- ApplicationMainLoop loop = new ();
-
- IComponentFactory cf;
-
- if (_componentFactory is IComponentFactory typedFactory)
- {
- cf = typedFactory;
- }
- else
- {
- cf = fallbackFactory ();
+ _driver?.ClearContents ();
}
- return new MainLoopCoordinator (_timedEvents, inputBuffer, loop, cf);
+ View.SetClipToScreen ();
+ View.Draw (tops, neededLayout || forceRedraw);
+ View.SetClipToScreen ();
+ _driver?.Refresh ();
}
}
diff --git a/Tests/UnitTests/Application/ApplicationImplTests.cs b/Tests/UnitTests/Application/ApplicationImplTests.cs
index 7b9d81f53..c04b6dfad 100644
--- a/Tests/UnitTests/Application/ApplicationImplTests.cs
+++ b/Tests/UnitTests/Application/ApplicationImplTests.cs
@@ -583,4 +583,52 @@ public class ApplicationImplTests
Assert.True (result);
}
+
+ [Fact]
+ public void ApplicationImpl_UsesInstanceFields_NotStaticReferences()
+ {
+ // This test verifies that ApplicationImpl uses instance fields instead of static Application references
+ var orig = ApplicationImpl.Instance;
+
+ var v2 = NewApplicationImpl();
+ ApplicationImpl.ChangeInstance(v2);
+
+ // Before Init, all fields should be null/default
+ Assert.Null(v2.Driver);
+ Assert.False(v2.Initialized);
+ Assert.Null(v2.Popover);
+ Assert.Null(v2.Navigation);
+ Assert.Null(v2.Top);
+ Assert.Empty(v2.TopLevels);
+
+ // Init should populate instance fields
+ v2.Init();
+
+ // After Init, Driver, Navigation, and Popover should be populated
+ Assert.NotNull(v2.Driver);
+ Assert.True(v2.Initialized);
+ Assert.NotNull(v2.Popover);
+ Assert.NotNull(v2.Navigation);
+ Assert.Null(v2.Top); // Top is still null until Run
+
+ // Verify that static Application properties delegate to instance
+ Assert.Equal(v2.Driver, Application.Driver);
+ Assert.Equal(v2.Initialized, Application.Initialized);
+ Assert.Equal(v2.Popover, Application.Popover);
+ Assert.Equal(v2.Navigation, Application.Navigation);
+ Assert.Equal(v2.Top, Application.Top);
+ Assert.Same(v2.TopLevels, Application.TopLevels);
+
+ // Shutdown should clean up instance fields
+ v2.Shutdown();
+
+ Assert.Null(v2.Driver);
+ Assert.False(v2.Initialized);
+ Assert.Null(v2.Popover);
+ Assert.Null(v2.Navigation);
+ Assert.Null(v2.Top);
+ Assert.Empty(v2.TopLevels);
+
+ ApplicationImpl.ChangeInstance(orig);
+ }
}