ApplicationImpl.cs 6.8 KB

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