Application.Toplevel.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. #nullable enable
  2. namespace Terminal.Gui;
  3. public static partial class Application // Toplevel handling
  4. {
  5. // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What
  6. /// <summary>Holds the stack of TopLevel views.</summary>
  7. // about TopLevels that are just a SubView of another View?
  8. internal static readonly Stack<Toplevel> _topLevels = new ();
  9. /// <summary>The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Top"/>)</summary>
  10. /// <value>The top.</value>
  11. public static Toplevel? Top { get; private set; }
  12. // TODO: Determine why this can't just return _topLevels.Peek()?
  13. /// <summary>
  14. /// The current <see cref="Toplevel"/> object. This is updated in <see cref="Application.Begin"/> enters and leaves to
  15. /// point to the current
  16. /// <see cref="Toplevel"/> .
  17. /// </summary>
  18. /// <remarks>
  19. /// This will only be distinct from <see cref="Application.Top"/> in scenarios where <see cref="Toplevel.IsOverlappedContainer"/> is <see langword="true"/>.
  20. /// </remarks>
  21. /// <value>The current.</value>
  22. public static Toplevel? Current { get; private set; }
  23. /// <summary>
  24. /// If <paramref name="topLevel"/> is not already Current and visible, finds the last Modal Toplevel in the stack and makes it Current.
  25. /// </summary>
  26. private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel)
  27. {
  28. if (!topLevel.Running
  29. || (topLevel == Current && topLevel.Visible)
  30. || OverlappedTop == null
  31. || _topLevels.Peek ().Modal)
  32. {
  33. return;
  34. }
  35. foreach (Toplevel top in _topLevels.Reverse ())
  36. {
  37. if (top.Modal && top != Current)
  38. {
  39. MoveCurrent (top);
  40. return;
  41. }
  42. }
  43. if (!topLevel.Visible && topLevel == Current)
  44. {
  45. OverlappedMoveNext ();
  46. }
  47. }
  48. /// <summary>
  49. /// Finds the first Toplevel in the stack that is Visible and who's Frame contains the <paramref name="location"/>.
  50. /// </summary>
  51. /// <param name="start"></param>
  52. /// <param name="location"></param>
  53. /// <returns></returns>
  54. private static Toplevel? FindDeepestTop (Toplevel start, in Point location)
  55. {
  56. if (!start.Frame.Contains (location))
  57. {
  58. return null;
  59. }
  60. if (_topLevels is { Count: > 0 })
  61. {
  62. int rx = location.X - start.Frame.X;
  63. int ry = location.Y - start.Frame.Y;
  64. foreach (Toplevel t in _topLevels)
  65. {
  66. if (t != Current)
  67. {
  68. if (t != start && t.Visible && t.Frame.Contains (rx, ry))
  69. {
  70. start = t;
  71. break;
  72. }
  73. }
  74. }
  75. }
  76. return start;
  77. }
  78. /// <summary>
  79. /// Given <paramref name="view"/>, returns the first Superview up the chain that is <see cref="Top"/>.
  80. /// </summary>
  81. private static View? FindTopFromView (View? view)
  82. {
  83. if (view is null)
  84. {
  85. return null;
  86. }
  87. View top = view.SuperView is { } && view.SuperView != Top
  88. ? view.SuperView
  89. : view;
  90. while (top?.SuperView is { } && top?.SuperView != Top)
  91. {
  92. top = top!.SuperView;
  93. }
  94. return top;
  95. }
  96. /// <summary>
  97. /// If the <see cref="Current"/> is not the <paramref name="top"/> then <paramref name="top"/> is moved to the top of the Toplevel stack and made Current.
  98. /// </summary>
  99. /// <param name="top"></param>
  100. /// <returns></returns>
  101. private static bool MoveCurrent (Toplevel top)
  102. {
  103. // The Current is modal and the top is not modal Toplevel then
  104. // the Current must be moved above the first not modal Toplevel.
  105. if (OverlappedTop is { }
  106. && top != OverlappedTop
  107. && top != Current
  108. && Current?.Modal == true
  109. && !_topLevels.Peek ().Modal)
  110. {
  111. lock (_topLevels)
  112. {
  113. _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
  114. }
  115. var index = 0;
  116. Toplevel [] savedToplevels = _topLevels.ToArray ();
  117. foreach (Toplevel t in savedToplevels)
  118. {
  119. if (!t!.Modal && t != Current && t != top && t != savedToplevels [index])
  120. {
  121. lock (_topLevels)
  122. {
  123. _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ());
  124. }
  125. }
  126. index++;
  127. }
  128. return false;
  129. }
  130. // The Current and the top are both not running Toplevel then
  131. // the top must be moved above the first not running Toplevel.
  132. if (OverlappedTop is { }
  133. && top != OverlappedTop
  134. && top != Current
  135. && Current?.Running == false
  136. && top?.Running == false)
  137. {
  138. lock (_topLevels)
  139. {
  140. _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
  141. }
  142. var index = 0;
  143. foreach (Toplevel t in _topLevels.ToArray ())
  144. {
  145. if (!t.Running && t != Current && index > 0)
  146. {
  147. lock (_topLevels)
  148. {
  149. _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
  150. }
  151. }
  152. index++;
  153. }
  154. return false;
  155. }
  156. if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top)
  157. || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop)
  158. || (OverlappedTop is { } && Current?.Modal == false && top != Current)
  159. || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop))
  160. {
  161. lock (_topLevels)
  162. {
  163. _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
  164. Current = top;
  165. }
  166. }
  167. return true;
  168. }
  169. /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
  170. /// <remarks>
  171. /// Event handlers can set <see cref="SizeChangedEventArgs.Cancel"/> to <see langword="true"/> to prevent
  172. /// <see cref="Application"/> from changing it's size to match the new terminal size.
  173. /// </remarks>
  174. public static event EventHandler<SizeChangedEventArgs>? SizeChanging;
  175. /// <summary>
  176. /// Called when the application's size changes. Sets the size of all <see cref="Toplevel"/>s and fires the
  177. /// <see cref="SizeChanging"/> event.
  178. /// </summary>
  179. /// <param name="args">The new size.</param>
  180. /// <returns><see lanword="true"/>if the size was changed.</returns>
  181. public static bool OnSizeChanging (SizeChangedEventArgs args)
  182. {
  183. SizeChanging?.Invoke (null, args);
  184. if (args.Cancel || args.Size is null)
  185. {
  186. return false;
  187. }
  188. foreach (Toplevel t in _topLevels)
  189. {
  190. t.SetRelativeLayout (args.Size.Value);
  191. t.LayoutSubviews ();
  192. t.PositionToplevels ();
  193. t.OnSizeChanging (new (args.Size));
  194. if (PositionCursor (t))
  195. {
  196. Driver?.UpdateCursor ();
  197. }
  198. }
  199. Refresh ();
  200. return true;
  201. }
  202. }