Application.Toplevel.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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. View top = view?.SuperView is { } && view?.SuperView != Top
  84. ? view.SuperView
  85. : view;
  86. while (top?.SuperView is { } && top?.SuperView != Top)
  87. {
  88. top = top.SuperView;
  89. }
  90. return top;
  91. }
  92. /// <summary>
  93. /// 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.
  94. /// </summary>
  95. /// <param name="top"></param>
  96. /// <returns></returns>
  97. private static bool MoveCurrent (Toplevel top)
  98. {
  99. // The Current is modal and the top is not modal Toplevel then
  100. // the Current must be moved above the first not modal Toplevel.
  101. if (OverlappedTop is { }
  102. && top != OverlappedTop
  103. && top != Current
  104. && Current?.Modal == true
  105. && !_topLevels.Peek ().Modal)
  106. {
  107. lock (_topLevels)
  108. {
  109. _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
  110. }
  111. var index = 0;
  112. Toplevel [] savedToplevels = _topLevels.ToArray ();
  113. foreach (Toplevel t in savedToplevels)
  114. {
  115. if (!t!.Modal && t != Current && t != top && t != savedToplevels [index])
  116. {
  117. lock (_topLevels)
  118. {
  119. _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ());
  120. }
  121. }
  122. index++;
  123. }
  124. return false;
  125. }
  126. // The Current and the top are both not running Toplevel then
  127. // the top must be moved above the first not running Toplevel.
  128. if (OverlappedTop is { }
  129. && top != OverlappedTop
  130. && top != Current
  131. && Current?.Running == false
  132. && top?.Running == false)
  133. {
  134. lock (_topLevels)
  135. {
  136. _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
  137. }
  138. var index = 0;
  139. foreach (Toplevel t in _topLevels.ToArray ())
  140. {
  141. if (!t.Running && t != Current && index > 0)
  142. {
  143. lock (_topLevels)
  144. {
  145. _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
  146. }
  147. }
  148. index++;
  149. }
  150. return false;
  151. }
  152. if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top)
  153. || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop)
  154. || (OverlappedTop is { } && Current?.Modal == false && top != Current)
  155. || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop))
  156. {
  157. lock (_topLevels)
  158. {
  159. _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
  160. Current = top;
  161. }
  162. }
  163. return true;
  164. }
  165. /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
  166. /// <remarks>
  167. /// Event handlers can set <see cref="SizeChangedEventArgs.Cancel"/> to <see langword="true"/> to prevent
  168. /// <see cref="Application"/> from changing it's size to match the new terminal size.
  169. /// </remarks>
  170. public static event EventHandler<SizeChangedEventArgs>? SizeChanging;
  171. /// <summary>
  172. /// Called when the application's size changes. Sets the size of all <see cref="Toplevel"/>s and fires the
  173. /// <see cref="SizeChanging"/> event.
  174. /// </summary>
  175. /// <param name="args">The new size.</param>
  176. /// <returns><see lanword="true"/>if the size was changed.</returns>
  177. public static bool OnSizeChanging (SizeChangedEventArgs args)
  178. {
  179. SizeChanging?.Invoke (null, args);
  180. if (args.Cancel || args.Size is null)
  181. {
  182. return false;
  183. }
  184. foreach (Toplevel t in _topLevels)
  185. {
  186. t.SetRelativeLayout (args.Size.Value);
  187. t.LayoutSubviews ();
  188. t.PositionToplevels ();
  189. t.OnSizeChanging (new (args.Size));
  190. if (PositionCursor (t))
  191. {
  192. Driver.UpdateCursor ();
  193. }
  194. }
  195. Refresh ();
  196. return true;
  197. }
  198. }