Application.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. #nullable enable
  2. using System.Diagnostics;
  3. using System.Globalization;
  4. using System.Reflection;
  5. namespace Terminal.Gui;
  6. /// <summary>A static, singleton class representing the application. This class is the entry point for the application.</summary>
  7. /// <example>
  8. /// <code>
  9. /// Application.Init();
  10. /// var win = new Window()
  11. /// {
  12. /// Title = $"Example App ({Application.QuitKey} to quit)"
  13. /// };
  14. /// Application.Run(win);
  15. /// win.Dispose();
  16. /// Application.Shutdown();
  17. /// </code>
  18. /// </example>
  19. /// <remarks></remarks>
  20. public static partial class Application
  21. {
  22. /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
  23. public static List<CultureInfo>? SupportedCultures { get; private set; }
  24. internal static List<CultureInfo> GetSupportedCultures ()
  25. {
  26. CultureInfo [] culture = CultureInfo.GetCultures (CultureTypes.AllCultures);
  27. // Get the assembly
  28. var assembly = Assembly.GetExecutingAssembly ();
  29. //Find the location of the assembly
  30. string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory;
  31. // Find the resource file name of the assembly
  32. var resourceFilename = $"{Path.GetFileNameWithoutExtension (AppContext.BaseDirectory)}.resources.dll";
  33. // Return all culture for which satellite folder found with culture code.
  34. return culture.Where (
  35. cultureInfo =>
  36. Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name))
  37. && File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename))
  38. )
  39. .ToList ();
  40. }
  41. // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
  42. // Encapsulate all setting of initial state for Application; Having
  43. // this in a function like this ensures we don't make mistakes in
  44. // guaranteeing that the state of this singleton is deterministic when Init
  45. // starts running and after Shutdown returns.
  46. internal static void ResetState (bool ignoreDisposed = false)
  47. {
  48. // Shutdown is the bookend for Init. As such it needs to clean up all resources
  49. // Init created. Apps that do any threading will need to code defensively for this.
  50. // e.g. see Issue #537
  51. foreach (Toplevel? t in _topLevels)
  52. {
  53. t!.Running = false;
  54. }
  55. _topLevels.Clear ();
  56. Current = null;
  57. #if DEBUG_IDISPOSABLE
  58. // Don't dispose the Top. It's up to caller dispose it
  59. if (!ignoreDisposed && Top is { })
  60. {
  61. Debug.Assert (Top.WasDisposed);
  62. // If End wasn't called _cachedRunStateToplevel may be null
  63. if (_cachedRunStateToplevel is { })
  64. {
  65. Debug.Assert (_cachedRunStateToplevel.WasDisposed);
  66. Debug.Assert (_cachedRunStateToplevel == Top);
  67. }
  68. }
  69. #endif
  70. Top = null;
  71. _cachedRunStateToplevel = null;
  72. // MainLoop stuff
  73. MainLoop?.Dispose ();
  74. MainLoop = null;
  75. _mainThreadId = -1;
  76. Iteration = null;
  77. EndAfterFirstIteration = false;
  78. // Driver stuff
  79. if (Driver is { })
  80. {
  81. Driver.SizeChanged -= Driver_SizeChanged;
  82. Driver.KeyDown -= Driver_KeyDown;
  83. Driver.KeyUp -= Driver_KeyUp;
  84. Driver.MouseEvent -= Driver_MouseEvent;
  85. Driver?.End ();
  86. Driver = null;
  87. }
  88. // Don't reset ForceDriver; it needs to be set before Init is called.
  89. //ForceDriver = string.Empty;
  90. //Force16Colors = false;
  91. _forceFakeConsole = false;
  92. // Run State stuff
  93. NotifyNewRunState = null;
  94. NotifyStopRunState = null;
  95. MouseGrabView = null;
  96. _initialized = false;
  97. // Mouse
  98. _mouseEnteredView = null;
  99. WantContinuousButtonPressedView = null;
  100. MouseEvent = null;
  101. GrabbedMouse = null;
  102. UnGrabbingMouse = null;
  103. GrabbedMouse = null;
  104. UnGrabbedMouse = null;
  105. // Keyboard
  106. AlternateBackwardKey = Key.Empty;
  107. AlternateForwardKey = Key.Empty;
  108. QuitKey = Key.Empty;
  109. KeyDown = null;
  110. KeyUp = null;
  111. SizeChanging = null;
  112. ClearKeyBindings ();
  113. Colors.Reset ();
  114. // Reset synchronization context to allow the user to run async/await,
  115. // as the main loop has been ended, the synchronization context from
  116. // gui.cs does no longer process any callbacks. See #1084 for more details:
  117. // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
  118. SynchronizationContext.SetSynchronizationContext (null);
  119. }
  120. // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`.
  121. // This field is set in `End` in this case so that `Begin` correctly sets `Top`.
  122. // TODO: Determine if this is really needed. The only code that calls WakeUp I can find
  123. // is ProgressBarStyles, and it's not clear it needs to.
  124. #region Toplevel handling
  125. /// <summary>Holds the stack of TopLevel views.</summary>
  126. // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What
  127. // about TopLevels that are just a SubView of another View?
  128. internal static readonly Stack<Toplevel> _topLevels = new ();
  129. /// <summary>The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)</summary>
  130. /// <value>The top.</value>
  131. public static Toplevel? Top { get; private set; }
  132. /// <summary>
  133. /// The current <see cref="Toplevel"/> object. This is updated in <see cref="Application.Begin"/> enters and leaves to
  134. /// point to the current
  135. /// <see cref="Toplevel"/> .
  136. /// </summary>
  137. /// <remarks>
  138. /// Only relevant in scenarios where <see cref="Toplevel.IsOverlappedContainer"/> is <see langword="true"/>.
  139. /// </remarks>
  140. /// <value>The current.</value>
  141. public static Toplevel? Current { get; private set; }
  142. private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel)
  143. {
  144. if (!topLevel.Running
  145. || (topLevel == Current && topLevel.Visible)
  146. || OverlappedTop == null
  147. || _topLevels.Peek ().Modal)
  148. {
  149. return;
  150. }
  151. foreach (Toplevel? top in _topLevels.Reverse ())
  152. {
  153. if (top.Modal && top != Current)
  154. {
  155. MoveCurrent (top);
  156. return;
  157. }
  158. }
  159. if (!topLevel.Visible && topLevel == Current)
  160. {
  161. OverlappedMoveNext ();
  162. }
  163. }
  164. #nullable enable
  165. private static Toplevel? FindDeepestTop (Toplevel start, in Point location)
  166. {
  167. if (!start.Frame.Contains (location))
  168. {
  169. return null;
  170. }
  171. if (_topLevels is { Count: > 0 })
  172. {
  173. int rx = location.X - start.Frame.X;
  174. int ry = location.Y - start.Frame.Y;
  175. foreach (Toplevel? t in _topLevels)
  176. {
  177. if (t != Current)
  178. {
  179. if (t != start && t.Visible && t.Frame.Contains (rx, ry))
  180. {
  181. start = t;
  182. break;
  183. }
  184. }
  185. }
  186. }
  187. return start;
  188. }
  189. #nullable restore
  190. private static View FindTopFromView (View view)
  191. {
  192. View top = view?.SuperView is { } && view?.SuperView != Top
  193. ? view.SuperView
  194. : view;
  195. while (top?.SuperView is { } && top?.SuperView != Top)
  196. {
  197. top = top.SuperView;
  198. }
  199. return top;
  200. }
  201. #nullable enable
  202. // Only return true if the Current has changed.
  203. private static bool MoveCurrent (Toplevel top)
  204. {
  205. // The Current is modal and the top is not modal Toplevel then
  206. // the Current must be moved above the first not modal Toplevel.
  207. if (OverlappedTop is { }
  208. && top != OverlappedTop
  209. && top != Current
  210. && Current?.Modal == true
  211. && !_topLevels.Peek ().Modal)
  212. {
  213. lock (_topLevels)
  214. {
  215. _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
  216. }
  217. var index = 0;
  218. Toplevel? [] savedToplevels = _topLevels.ToArray ();
  219. foreach (Toplevel? t in savedToplevels)
  220. {
  221. if (!t!.Modal && t != Current && t != top && t != savedToplevels [index])
  222. {
  223. lock (_topLevels)
  224. {
  225. _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ());
  226. }
  227. }
  228. index++;
  229. }
  230. return false;
  231. }
  232. // The Current and the top are both not running Toplevel then
  233. // the top must be moved above the first not running Toplevel.
  234. if (OverlappedTop is { }
  235. && top != OverlappedTop
  236. && top != Current
  237. && Current?.Running == false
  238. && top?.Running == false)
  239. {
  240. lock (_topLevels)
  241. {
  242. _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
  243. }
  244. var index = 0;
  245. foreach (Toplevel? t in _topLevels.ToArray ())
  246. {
  247. if (!t.Running && t != Current && index > 0)
  248. {
  249. lock (_topLevels)
  250. {
  251. _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
  252. }
  253. }
  254. index++;
  255. }
  256. return false;
  257. }
  258. if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top)
  259. || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop)
  260. || (OverlappedTop is { } && Current?.Modal == false && top != Current)
  261. || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop))
  262. {
  263. lock (_topLevels)
  264. {
  265. _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
  266. Current = top;
  267. }
  268. }
  269. return true;
  270. }
  271. #nullable restore
  272. /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
  273. /// <remarks>
  274. /// Event handlers can set <see cref="SizeChangedEventArgs.Cancel"/> to <see langword="true"/> to prevent
  275. /// <see cref="Application"/> from changing it's size to match the new terminal size.
  276. /// </remarks>
  277. public static event EventHandler<SizeChangedEventArgs> SizeChanging;
  278. /// <summary>
  279. /// Called when the application's size changes. Sets the size of all <see cref="Toplevel"/>s and fires the
  280. /// <see cref="SizeChanging"/> event.
  281. /// </summary>
  282. /// <param name="args">The new size.</param>
  283. /// <returns><see lanword="true"/>if the size was changed.</returns>
  284. public static bool OnSizeChanging (SizeChangedEventArgs args)
  285. {
  286. SizeChanging?.Invoke (null, args);
  287. if (args.Cancel || args.Size is null)
  288. {
  289. return false;
  290. }
  291. foreach (Toplevel t in _topLevels)
  292. {
  293. t.SetRelativeLayout (args.Size.Value);
  294. t.LayoutSubviews ();
  295. t.PositionToplevels ();
  296. t.OnSizeChanging (new (args.Size));
  297. if (PositionCursor (t))
  298. {
  299. Driver.UpdateCursor ();
  300. }
  301. }
  302. Refresh ();
  303. return true;
  304. }
  305. #endregion Toplevel handling
  306. /// <summary>
  307. /// Gets a string representation of the Application as rendered by <see cref="Driver"/>.
  308. /// </summary>
  309. /// <returns>A string representation of the Application </returns>
  310. public new static string ToString ()
  311. {
  312. ConsoleDriver driver = Driver;
  313. if (driver is null)
  314. {
  315. return string.Empty;
  316. }
  317. return ToString (driver);
  318. }
  319. /// <summary>
  320. /// Gets a string representation of the Application rendered by the provided <see cref="ConsoleDriver"/>.
  321. /// </summary>
  322. /// <param name="driver">The driver to use to render the contents.</param>
  323. /// <returns>A string representation of the Application </returns>
  324. public static string ToString (ConsoleDriver driver)
  325. {
  326. var sb = new StringBuilder ();
  327. Cell [,] contents = driver.Contents;
  328. for (var r = 0; r < driver.Rows; r++)
  329. {
  330. for (var c = 0; c < driver.Cols; c++)
  331. {
  332. Rune rune = contents [r, c].Rune;
  333. if (rune.DecodeSurrogatePair (out char [] sp))
  334. {
  335. sb.Append (sp);
  336. }
  337. else
  338. {
  339. sb.Append ((char)rune.Value);
  340. }
  341. if (rune.GetColumns () > 1)
  342. {
  343. c++;
  344. }
  345. // See Issue #2616
  346. //foreach (var combMark in contents [r, c].CombiningMarks) {
  347. // sb.Append ((char)combMark.Value);
  348. //}
  349. }
  350. sb.AppendLine ();
  351. }
  352. return sb.ToString ();
  353. }
  354. }