ApplicationImpl.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. /// <inheritdoc/>
  26. public new string ToString () => Driver?.ToString () ?? string.Empty;
  27. #region Singleton - Legacy Static Support
  28. /// <summary>
  29. /// Lock object for synchronizing access to ModelUsage and _instance.
  30. /// </summary>
  31. private static readonly object _modelUsageLock = new ();
  32. /// <summary>
  33. /// Tracks which application model has been used in this process.
  34. /// </summary>
  35. public static ApplicationModelUsage ModelUsage { get; private set; } = ApplicationModelUsage.None;
  36. /// <summary>
  37. /// Error message for when trying to use modern model after legacy static model.
  38. /// </summary>
  39. internal const string ERROR_MODERN_AFTER_LEGACY =
  40. "Cannot use modern instance-based model (Application.Create) after using legacy static Application model (Application.Init/ApplicationImpl.Instance). "
  41. + "Use only one model per process.";
  42. /// <summary>
  43. /// Error message for when trying to use legacy static model after modern model.
  44. /// </summary>
  45. internal const string ERROR_LEGACY_AFTER_MODERN =
  46. "Cannot use legacy static Application model (Application.Init/ApplicationImpl.Instance) after using modern instance-based model (Application.Create). "
  47. + "Use only one model per process.";
  48. /// <summary>
  49. /// Configures the singleton instance of <see cref="Application"/> to use the specified backend implementation.
  50. /// </summary>
  51. /// <param name="app"></param>
  52. public static void SetInstance (IApplication? app)
  53. {
  54. lock (_modelUsageLock)
  55. {
  56. ModelUsage = ApplicationModelUsage.LegacyStatic;
  57. _instance = app;
  58. }
  59. }
  60. // Private static readonly Lazy instance of Application
  61. private static IApplication? _instance;
  62. /// <summary>
  63. /// Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
  64. /// </summary>
  65. public static IApplication Instance
  66. {
  67. get
  68. {
  69. //Debug.Fail ("ApplicationImpl.Instance accessed - parallelizable tests should not use legacy static Application model");
  70. // Thread-safe: Use lock to make check-and-create atomic
  71. lock (_modelUsageLock)
  72. {
  73. // If an instance already exists, return it without fence checking
  74. // This allows for cleanup/reset operations
  75. if (_instance is { })
  76. {
  77. return _instance;
  78. }
  79. // Check if the instance-based model has already been used
  80. if (ModelUsage == ApplicationModelUsage.InstanceBased)
  81. {
  82. throw new InvalidOperationException (ERROR_LEGACY_AFTER_MODERN);
  83. }
  84. // Mark the usage and create the instance
  85. ModelUsage = ApplicationModelUsage.LegacyStatic;
  86. return _instance = new ApplicationImpl ();
  87. }
  88. }
  89. }
  90. /// <summary>
  91. /// INTERNAL: Marks that the instance-based model has been used. Called by Application.Create().
  92. /// </summary>
  93. internal static void MarkInstanceBasedModelUsed ()
  94. {
  95. lock (_modelUsageLock)
  96. {
  97. // Check if the legacy static model has already been initialized
  98. if (ModelUsage == ApplicationModelUsage.LegacyStatic && _instance?.Initialized == true)
  99. {
  100. throw new InvalidOperationException (ERROR_MODERN_AFTER_LEGACY);
  101. }
  102. ModelUsage = ApplicationModelUsage.InstanceBased;
  103. }
  104. }
  105. /// <summary>
  106. /// INTERNAL: Resets the model usage tracking. Only for testing purposes.
  107. /// </summary>
  108. internal static void ResetModelUsageTracking ()
  109. {
  110. lock (_modelUsageLock)
  111. {
  112. ModelUsage = ApplicationModelUsage.None;
  113. _instance = null;
  114. }
  115. }
  116. /// <summary>
  117. /// INTERNAL: Resets state without going through the fence-checked Instance property.
  118. /// Used by Application.ResetState() to allow cleanup regardless of which model was used.
  119. /// </summary>
  120. internal static void ResetStateStatic (bool ignoreDisposed = false)
  121. {
  122. // If an instance exists, reset it
  123. _instance?.ResetState (ignoreDisposed);
  124. // Reset Application static properties to their defaults
  125. // This ensures tests start with clean state
  126. Application.ForceDriver = string.Empty;
  127. Application.Force16Colors = false;
  128. Application.IsMouseDisabled = false;
  129. Application.QuitKey = Key.Esc;
  130. Application.ArrangeKey = Key.F5.WithCtrl;
  131. Application.NextTabGroupKey = Key.F6;
  132. Application.NextTabKey = Key.Tab;
  133. Application.PrevTabGroupKey = Key.F6.WithShift;
  134. Application.PrevTabKey = Key.Tab.WithShift;
  135. // Always reset the model tracking to allow tests to use either model after reset
  136. ResetModelUsageTracking ();
  137. }
  138. #endregion Singleton - Legacy Static Support
  139. #region Screen and Driver
  140. /// <inheritdoc/>
  141. public IClipboard? Clipboard => Driver?.Clipboard;
  142. #endregion Screen and Driver
  143. #region Keyboard
  144. private IKeyboard? _keyboard;
  145. /// <inheritdoc/>
  146. public IKeyboard Keyboard
  147. {
  148. get
  149. {
  150. _keyboard ??= new KeyboardImpl { App = this };
  151. return _keyboard;
  152. }
  153. set => _keyboard = value ?? throw new ArgumentNullException (nameof (value));
  154. }
  155. #endregion Keyboard
  156. #region Mouse
  157. private IMouse? _mouse;
  158. /// <inheritdoc/>
  159. public IMouse Mouse
  160. {
  161. get
  162. {
  163. _mouse ??= new MouseImpl { App = this };
  164. return _mouse;
  165. }
  166. set => _mouse = value ?? throw new ArgumentNullException (nameof (value));
  167. }
  168. #endregion Mouse
  169. #region Navigation and Popover
  170. private ApplicationNavigation? _navigation;
  171. /// <inheritdoc/>
  172. public ApplicationNavigation? Navigation
  173. {
  174. get
  175. {
  176. _navigation ??= new () { App = this };
  177. return _navigation;
  178. }
  179. set => _navigation = value ?? throw new ArgumentNullException (nameof (value));
  180. }
  181. private ApplicationPopover? _popover;
  182. /// <inheritdoc/>
  183. public ApplicationPopover? Popover
  184. {
  185. get
  186. {
  187. _popover ??= new () { App = this };
  188. return _popover;
  189. }
  190. set => _popover = value;
  191. }
  192. #endregion Navigation and Popover
  193. }