ApplicationImpl.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. using System.Collections.Concurrent;
  2. namespace Terminal.Gui.App;
  3. /// <summary>
  4. /// Implementation of core <see cref="Application"/> methods using the modern
  5. /// main loop architecture with component factories for different platforms.
  6. /// </summary>
  7. public partial class ApplicationImpl : IApplication
  8. {
  9. /// <summary>
  10. /// INTERNAL: Creates a new instance of the Application backend and subscribes to Application configuration property
  11. /// events.
  12. /// </summary>
  13. internal ApplicationImpl ()
  14. {
  15. // Subscribe to Application static property change events
  16. Application.Force16ColorsChanged += OnForce16ColorsChanged;
  17. Application.ForceDriverChanged += OnForceDriverChanged;
  18. }
  19. /// <summary>
  20. /// INTERNAL: Creates a new instance of the Application backend.
  21. /// </summary>
  22. /// <param name="componentFactory"></param>
  23. internal ApplicationImpl (IComponentFactory componentFactory) : this () { _componentFactory = componentFactory; }
  24. private string? _driverName;
  25. #region Clipboard
  26. /// <inheritdoc/>
  27. public IClipboard? Clipboard => Driver?.Clipboard;
  28. #endregion Clipboard
  29. /// <inheritdoc/>
  30. public new string ToString () => Driver?.ToString () ?? string.Empty;
  31. #region Singleton
  32. /// <summary>
  33. /// Lock object for synchronizing access to ModelUsage and _instance.
  34. /// </summary>
  35. private static readonly object _modelUsageLock = new ();
  36. /// <summary>
  37. /// Tracks which application model has been used in this process.
  38. /// </summary>
  39. public static ApplicationModelUsage ModelUsage { get; private set; } = ApplicationModelUsage.None;
  40. /// <summary>
  41. /// Error message for when trying to use modern model after legacy static model.
  42. /// </summary>
  43. internal const string ERROR_MODERN_AFTER_LEGACY =
  44. "Cannot use modern instance-based model (Application.Create) after using legacy static Application model (Application.Init/ApplicationImpl.Instance). "
  45. + "Use only one model per process.";
  46. /// <summary>
  47. /// Error message for when trying to use legacy static model after modern model.
  48. /// </summary>
  49. internal const string ERROR_LEGACY_AFTER_MODERN =
  50. "Cannot use legacy static Application model (Application.Init/ApplicationImpl.Instance) after using modern instance-based model (Application.Create). "
  51. + "Use only one model per process.";
  52. /// <summary>
  53. /// Configures the singleton instance of <see cref="Application"/> to use the specified backend implementation.
  54. /// </summary>
  55. /// <param name="app"></param>
  56. public static void SetInstance (IApplication? app)
  57. {
  58. lock (_modelUsageLock)
  59. {
  60. ModelUsage = ApplicationModelUsage.LegacyStatic;
  61. _instance = app;
  62. }
  63. }
  64. // Private static readonly Lazy instance of Application
  65. private static IApplication? _instance;
  66. /// <summary>
  67. /// Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
  68. /// </summary>
  69. public static IApplication Instance
  70. {
  71. get
  72. {
  73. //Debug.Fail ("ApplicationImpl.Instance accessed - parallelizable tests should not use legacy static Application model");
  74. // Thread-safe: Use lock to make check-and-create atomic
  75. lock (_modelUsageLock)
  76. {
  77. // If an instance already exists, return it without fence checking
  78. // This allows for cleanup/reset operations
  79. if (_instance is { })
  80. {
  81. return _instance;
  82. }
  83. // Check if the instance-based model has already been used
  84. if (ModelUsage == ApplicationModelUsage.InstanceBased)
  85. {
  86. throw new InvalidOperationException (ERROR_LEGACY_AFTER_MODERN);
  87. }
  88. // Mark the usage and create the instance
  89. ModelUsage = ApplicationModelUsage.LegacyStatic;
  90. return _instance = new ApplicationImpl ();
  91. }
  92. }
  93. }
  94. /// <summary>
  95. /// INTERNAL: Marks that the instance-based model has been used. Called by Application.Create().
  96. /// </summary>
  97. internal static void MarkInstanceBasedModelUsed ()
  98. {
  99. lock (_modelUsageLock)
  100. {
  101. // Check if the legacy static model has already been initialized
  102. if (ModelUsage == ApplicationModelUsage.LegacyStatic && _instance?.Initialized == true)
  103. {
  104. throw new InvalidOperationException (ERROR_MODERN_AFTER_LEGACY);
  105. }
  106. ModelUsage = ApplicationModelUsage.InstanceBased;
  107. }
  108. }
  109. /// <summary>
  110. /// INTERNAL: Resets the model usage tracking. Only for testing purposes.
  111. /// </summary>
  112. internal static void ResetModelUsageTracking ()
  113. {
  114. lock (_modelUsageLock)
  115. {
  116. ModelUsage = ApplicationModelUsage.None;
  117. _instance = null;
  118. }
  119. }
  120. /// <summary>
  121. /// INTERNAL: Resets state without going through the fence-checked Instance property.
  122. /// Used by Application.ResetState() to allow cleanup regardless of which model was used.
  123. /// </summary>
  124. internal static void ResetStateStatic (bool ignoreDisposed = false)
  125. {
  126. // If an instance exists, reset it
  127. _instance?.ResetState (ignoreDisposed);
  128. // Reset Application static properties to their defaults
  129. // This ensures tests start with clean state
  130. Application.ForceDriver = string.Empty;
  131. Application.Force16Colors = false;
  132. Application.IsMouseDisabled = false;
  133. Application.QuitKey = Key.Esc;
  134. Application.ArrangeKey = Key.F5.WithCtrl;
  135. Application.NextTabGroupKey = Key.F6;
  136. Application.NextTabKey = Key.Tab;
  137. Application.PrevTabGroupKey = Key.F6.WithShift;
  138. Application.PrevTabKey = Key.Tab.WithShift;
  139. // Always reset the model tracking to allow tests to use either model after reset
  140. ResetModelUsageTracking ();
  141. }
  142. #endregion Singleton
  143. #region Input
  144. private IMouse? _mouse;
  145. /// <summary>
  146. /// Handles mouse event state and processing.
  147. /// </summary>
  148. public IMouse Mouse
  149. {
  150. get
  151. {
  152. _mouse ??= new MouseImpl { App = this };
  153. return _mouse;
  154. }
  155. set => _mouse = value ?? throw new ArgumentNullException (nameof (value));
  156. }
  157. private IKeyboard? _keyboard;
  158. /// <summary>
  159. /// Handles keyboard input and key bindings at the Application level
  160. /// </summary>
  161. public IKeyboard Keyboard
  162. {
  163. get
  164. {
  165. _keyboard ??= new KeyboardImpl { App = this };
  166. return _keyboard;
  167. }
  168. set => _keyboard = value ?? throw new ArgumentNullException (nameof (value));
  169. }
  170. #endregion Input
  171. #region View Management
  172. private ApplicationPopover? _popover;
  173. /// <inheritdoc/>
  174. public ApplicationPopover? Popover
  175. {
  176. get
  177. {
  178. _popover ??= new () { App = this };
  179. return _popover;
  180. }
  181. set => _popover = value;
  182. }
  183. private ApplicationNavigation? _navigation;
  184. /// <inheritdoc/>
  185. public ApplicationNavigation? Navigation
  186. {
  187. get
  188. {
  189. _navigation ??= new () { App = this };
  190. return _navigation;
  191. }
  192. set => _navigation = value ?? throw new ArgumentNullException (nameof (value));
  193. }
  194. private Toplevel? _topRunnable;
  195. /// <inheritdoc/>
  196. public Toplevel? TopRunnable
  197. {
  198. get => _topRunnable;
  199. set
  200. {
  201. _topRunnable = value;
  202. if (_topRunnable is { })
  203. {
  204. _topRunnable.App = this;
  205. }
  206. }
  207. }
  208. /// <inheritdoc/>
  209. public ConcurrentStack<Toplevel> SessionStack { get; } = new ();
  210. /// <inheritdoc/>
  211. public Toplevel? CachedSessionTokenToplevel { get; set; }
  212. /// <inheritdoc/>
  213. public ConcurrentStack<RunnableSessionToken>? RunnableSessionStack { get; } = new ();
  214. /// <inheritdoc/>
  215. public IRunnable? FrameworkOwnedRunnable { get; set; }
  216. #endregion View Management
  217. }