ApplicationImpl.cs 8.0 KB

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