ApplicationImpl.cs 7.0 KB

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