using System.Collections.Concurrent;
namespace Terminal.Gui.App;
///
/// Implementation of core methods using the modern
/// main loop architecture with component factories for different platforms.
///
public partial class ApplicationImpl : IApplication
{
///
/// INTERNAL: Creates a new instance of the Application backend and subscribes to Application configuration property
/// events.
///
internal ApplicationImpl ()
{
// Subscribe to Application static property change events
Application.Force16ColorsChanged += OnForce16ColorsChanged;
Application.ForceDriverChanged += OnForceDriverChanged;
}
///
/// INTERNAL: Creates a new instance of the Application backend.
///
///
internal ApplicationImpl (IComponentFactory componentFactory) : this () { _componentFactory = componentFactory; }
private string? _driverName;
#region Clipboard
///
public IClipboard? Clipboard => Driver?.Clipboard;
#endregion Clipboard
///
public new string ToString () => Driver?.ToString () ?? string.Empty;
#region Singleton
///
/// Lock object for synchronizing access to ModelUsage and _instance.
///
private static readonly object _modelUsageLock = new ();
///
/// Tracks which application model has been used in this process.
///
public static ApplicationModelUsage ModelUsage { get; private set; } = ApplicationModelUsage.None;
///
/// Error message for when trying to use modern model after legacy static model.
///
internal const string ERROR_MODERN_AFTER_LEGACY =
"Cannot use modern instance-based model (Application.Create) after using legacy static Application model (Application.Init/ApplicationImpl.Instance). "
+ "Use only one model per process.";
///
/// Error message for when trying to use legacy static model after modern model.
///
internal const string ERROR_LEGACY_AFTER_MODERN =
"Cannot use legacy static Application model (Application.Init/ApplicationImpl.Instance) after using modern instance-based model (Application.Create). "
+ "Use only one model per process.";
///
/// Configures the singleton instance of to use the specified backend implementation.
///
///
public static void SetInstance (IApplication? app)
{
lock (_modelUsageLock)
{
ModelUsage = ApplicationModelUsage.LegacyStatic;
_instance = app;
}
}
// Private static readonly Lazy instance of Application
private static IApplication? _instance;
///
/// Gets the currently configured backend implementation of gateway methods.
///
public static IApplication Instance
{
get
{
//Debug.Fail ("ApplicationImpl.Instance accessed - parallelizable tests should not use legacy static Application model");
// Thread-safe: Use lock to make check-and-create atomic
lock (_modelUsageLock)
{
// If an instance already exists, return it without fence checking
// This allows for cleanup/reset operations
if (_instance is { })
{
return _instance;
}
// Check if the instance-based model has already been used
if (ModelUsage == ApplicationModelUsage.InstanceBased)
{
throw new InvalidOperationException (ERROR_LEGACY_AFTER_MODERN);
}
// Mark the usage and create the instance
ModelUsage = ApplicationModelUsage.LegacyStatic;
return _instance = new ApplicationImpl ();
}
}
}
///
/// INTERNAL: Marks that the instance-based model has been used. Called by Application.Create().
///
internal static void MarkInstanceBasedModelUsed ()
{
lock (_modelUsageLock)
{
// Check if the legacy static model has already been initialized
if (ModelUsage == ApplicationModelUsage.LegacyStatic && _instance?.Initialized == true)
{
throw new InvalidOperationException (ERROR_MODERN_AFTER_LEGACY);
}
ModelUsage = ApplicationModelUsage.InstanceBased;
}
}
///
/// INTERNAL: Resets the model usage tracking. Only for testing purposes.
///
internal static void ResetModelUsageTracking ()
{
lock (_modelUsageLock)
{
ModelUsage = ApplicationModelUsage.None;
_instance = null;
}
}
///
/// INTERNAL: Resets state without going through the fence-checked Instance property.
/// Used by Application.ResetState() to allow cleanup regardless of which model was used.
///
internal static void ResetStateStatic (bool ignoreDisposed = false)
{
// If an instance exists, reset it
_instance?.ResetState (ignoreDisposed);
// Reset Application static properties to their defaults
// This ensures tests start with clean state
Application.ForceDriver = string.Empty;
Application.Force16Colors = false;
Application.IsMouseDisabled = false;
Application.QuitKey = Key.Esc;
Application.ArrangeKey = Key.F5.WithCtrl;
Application.NextTabGroupKey = Key.F6;
Application.NextTabKey = Key.Tab;
Application.PrevTabGroupKey = Key.F6.WithShift;
Application.PrevTabKey = Key.Tab.WithShift;
// Always reset the model tracking to allow tests to use either model after reset
ResetModelUsageTracking ();
}
#endregion Singleton
#region Input
private IMouse? _mouse;
///
/// Handles mouse event state and processing.
///
public IMouse Mouse
{
get
{
_mouse ??= new MouseImpl { App = this };
return _mouse;
}
set => _mouse = value ?? throw new ArgumentNullException (nameof (value));
}
private IKeyboard? _keyboard;
///
/// Handles keyboard input and key bindings at the Application level
///
public IKeyboard Keyboard
{
get
{
_keyboard ??= new KeyboardImpl { App = this };
return _keyboard;
}
set => _keyboard = value ?? throw new ArgumentNullException (nameof (value));
}
#endregion Input
#region View Management
private ApplicationPopover? _popover;
///
public ApplicationPopover? Popover
{
get
{
_popover ??= new () { App = this };
return _popover;
}
set => _popover = value;
}
private ApplicationNavigation? _navigation;
///
public ApplicationNavigation? Navigation
{
get
{
_navigation ??= new () { App = this };
return _navigation;
}
set => _navigation = value ?? throw new ArgumentNullException (nameof (value));
}
private Toplevel? _topRunnable;
///
public Toplevel? TopRunnable
{
get => _topRunnable;
set
{
_topRunnable = value;
if (_topRunnable is { })
{
_topRunnable.App = this;
}
}
}
///
public ConcurrentStack SessionStack { get; } = new ();
///
public Toplevel? CachedSessionTokenToplevel { get; set; }
///
public ConcurrentStack? RunnableSessionStack { get; } = new ();
///
public IRunnable? FrameworkOwnedRunnable { get; set; }
#endregion View Management
}