Application.Overlapped.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. #nullable enable
  2. using System.Reflection;
  3. namespace Terminal.Gui;
  4. /// <summary>
  5. /// Helper class for managing overlapped views in the application.
  6. /// </summary>
  7. public static class ApplicationOverlapped
  8. {
  9. /// <summary>
  10. /// Gets or sets if <paramref name="top"/> is in overlapped mode within a Toplevel container.
  11. /// </summary>
  12. /// <param name="top"></param>
  13. /// <returns></returns>
  14. public static bool IsOverlapped (Toplevel? top)
  15. {
  16. return ApplicationOverlapped.OverlappedTop is { } && ApplicationOverlapped.OverlappedTop != top && !top!.Modal;
  17. }
  18. /// <summary>
  19. /// Gets the list of the Overlapped children which are not modal <see cref="Toplevel"/> from the
  20. /// <see cref="OverlappedTop"/>.
  21. /// </summary>
  22. public static List<Toplevel>? OverlappedChildren
  23. {
  24. get
  25. {
  26. if (OverlappedTop is { })
  27. {
  28. List<Toplevel> overlappedChildren = new ();
  29. lock (Application.TopLevels)
  30. {
  31. foreach (Toplevel top in Application.TopLevels)
  32. {
  33. if (top != OverlappedTop && !top.Modal)
  34. {
  35. overlappedChildren.Add (top);
  36. }
  37. }
  38. }
  39. return overlappedChildren;
  40. }
  41. return null;
  42. }
  43. }
  44. /// <summary>
  45. /// The <see cref="Toplevel"/> object used for the application on startup which
  46. /// <see cref="Toplevel.IsOverlappedContainer"/> is true.
  47. /// </summary>
  48. public static Toplevel? OverlappedTop
  49. {
  50. get
  51. {
  52. if (Application.Top is { IsOverlappedContainer: true })
  53. {
  54. return Application.Top;
  55. }
  56. return null;
  57. }
  58. }
  59. /// <summary>Brings the superview of the most focused overlapped view is on front.</summary>
  60. public static void BringOverlappedTopToFront ()
  61. {
  62. if (OverlappedTop is { })
  63. {
  64. return;
  65. }
  66. View? top = FindTopFromView (Application.Top?.MostFocused);
  67. if (top is Toplevel && Application.Top?.Subviews.Count > 1 && Application.Top.Subviews [^1] != top)
  68. {
  69. Application.Top.BringSubviewToFront (top);
  70. }
  71. }
  72. /// <summary>Gets the current visible Toplevel overlapped child that matches the arguments pattern.</summary>
  73. /// <param name="type">The type.</param>
  74. /// <param name="exclude">The strings to exclude.</param>
  75. /// <returns>The matched view.</returns>
  76. public static Toplevel? GetTopOverlappedChild (Type? type = null, string []? exclude = null)
  77. {
  78. if (OverlappedChildren is null || OverlappedTop is null)
  79. {
  80. return null;
  81. }
  82. foreach (Toplevel top in OverlappedChildren)
  83. {
  84. if (type is { } && top.GetType () == type && exclude?.Contains (top.Data.ToString ()) == false)
  85. {
  86. return top;
  87. }
  88. if ((type is { } && top.GetType () != type) || exclude?.Contains (top.Data.ToString ()) == true)
  89. {
  90. continue;
  91. }
  92. return top;
  93. }
  94. return null;
  95. }
  96. /// <summary>
  97. /// Move to the next Overlapped child from the <see cref="OverlappedTop"/> and set it as the <see cref="Application.Top"/> if
  98. /// it is not already.
  99. /// </summary>
  100. /// <param name="top"></param>
  101. /// <returns></returns>
  102. public static bool MoveToOverlappedChild (Toplevel? top)
  103. {
  104. ArgumentNullException.ThrowIfNull (top);
  105. if (top.Visible && OverlappedTop is { } && Application.Current?.Modal == false)
  106. {
  107. lock (Application.TopLevels)
  108. {
  109. Application.TopLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
  110. Application.Current = top;
  111. }
  112. return true;
  113. }
  114. return false;
  115. }
  116. /// <summary>Move to the next Overlapped child from the <see cref="OverlappedTop"/>.</summary>
  117. public static void OverlappedMoveNext ()
  118. {
  119. if (OverlappedTop is { } && !Application.Current!.Modal)
  120. {
  121. lock (Application.TopLevels)
  122. {
  123. Application.TopLevels.MoveNext ();
  124. var isOverlapped = false;
  125. while (Application.TopLevels.Peek () == OverlappedTop || !Application.TopLevels.Peek ().Visible)
  126. {
  127. if (!isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
  128. {
  129. isOverlapped = true;
  130. }
  131. else if (isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
  132. {
  133. MoveCurrent (Application.Top!);
  134. break;
  135. }
  136. Application.TopLevels.MoveNext ();
  137. }
  138. Application.Current = Application.TopLevels.Peek ();
  139. }
  140. }
  141. }
  142. /// <summary>Move to the previous Overlapped child from the <see cref="OverlappedTop"/>.</summary>
  143. public static void OverlappedMovePrevious ()
  144. {
  145. if (OverlappedTop is { } && !Application.Current!.Modal)
  146. {
  147. lock (Application.TopLevels)
  148. {
  149. Application.TopLevels.MovePrevious ();
  150. var isOverlapped = false;
  151. while (Application.TopLevels.Peek () == OverlappedTop || !Application.TopLevels.Peek ().Visible)
  152. {
  153. if (!isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
  154. {
  155. isOverlapped = true;
  156. }
  157. else if (isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
  158. {
  159. MoveCurrent (Application.Top!);
  160. break;
  161. }
  162. Application.TopLevels.MovePrevious ();
  163. }
  164. Application.Current = Application.TopLevels.Peek ();
  165. }
  166. }
  167. }
  168. internal static bool OverlappedChildNeedsDisplay ()
  169. {
  170. if (OverlappedTop is null)
  171. {
  172. return false;
  173. }
  174. lock (Application.TopLevels)
  175. {
  176. foreach (Toplevel top in Application.TopLevels)
  177. {
  178. if (top != Application.Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded))
  179. {
  180. OverlappedTop.SetSubViewNeedsDisplay ();
  181. return true;
  182. }
  183. }
  184. }
  185. return false;
  186. }
  187. internal static bool SetCurrentOverlappedAsTop ()
  188. {
  189. if (OverlappedTop is null && Application.Current != Application.Top && Application.Current?.SuperView is null && Application.Current?.Modal == false)
  190. {
  191. Application.Top = Application.Current;
  192. return true;
  193. }
  194. return false;
  195. }
  196. /// <summary>
  197. /// Finds the first Toplevel in the stack that is Visible and who's Frame contains the <paramref name="location"/>.
  198. /// </summary>
  199. /// <param name="start"></param>
  200. /// <param name="location"></param>
  201. /// <returns></returns>
  202. internal static Toplevel? FindDeepestTop (Toplevel start, in Point location)
  203. {
  204. if (!start.Frame.Contains (location))
  205. {
  206. return null;
  207. }
  208. lock (Application.TopLevels)
  209. {
  210. if (Application.TopLevels is not { Count: > 0 })
  211. {
  212. return start;
  213. }
  214. int rx = location.X - start.Frame.X;
  215. int ry = location.Y - start.Frame.Y;
  216. foreach (Toplevel t in Application.TopLevels)
  217. {
  218. if (t == Application.Current)
  219. {
  220. continue;
  221. }
  222. if (t != start && t.Visible && t.Frame.Contains (rx, ry))
  223. {
  224. start = t;
  225. break;
  226. }
  227. }
  228. }
  229. return start;
  230. }
  231. /// <summary>
  232. /// Given <paramref name="view"/>, returns the first Superview up the chain that is <see cref="Application.Top"/>.
  233. /// </summary>
  234. internal static View? FindTopFromView (View? view)
  235. {
  236. if (view is null)
  237. {
  238. return null;
  239. }
  240. View top = view.SuperView is { } && view.SuperView != Application.Top
  241. ? view.SuperView
  242. : view;
  243. while (top?.SuperView is { } && top?.SuperView != Application.Top)
  244. {
  245. top = top!.SuperView;
  246. }
  247. return top;
  248. }
  249. /// <summary>
  250. /// If the <see cref="Application.Current"/> is not the <paramref name="top"/> then <paramref name="top"/> is moved to the top of
  251. /// the Toplevel stack and made Current.
  252. /// </summary>
  253. /// <param name="top"></param>
  254. /// <returns></returns>
  255. internal static bool MoveCurrent (Toplevel top)
  256. {
  257. // The Current is modal and the top is not modal Toplevel then
  258. // the Current must be moved above the first not modal Toplevel.
  259. if (OverlappedTop is { }
  260. && top != OverlappedTop
  261. && top != Application.Current
  262. && Application.Current?.Modal == true
  263. && !Application.TopLevels.Peek ().Modal)
  264. {
  265. lock (Application.TopLevels)
  266. {
  267. Application.TopLevels.MoveTo (Application.Current, 0, new ToplevelEqualityComparer ());
  268. }
  269. var index = 0;
  270. Toplevel [] savedToplevels = Application.TopLevels.ToArray ();
  271. foreach (Toplevel t in savedToplevels)
  272. {
  273. if (!t!.Modal && t != Application.Current && t != top && t != savedToplevels [index])
  274. {
  275. lock (Application.TopLevels)
  276. {
  277. Application.TopLevels.MoveTo (top, index, new ToplevelEqualityComparer ());
  278. }
  279. }
  280. index++;
  281. }
  282. return false;
  283. }
  284. // The Current and the top are both not running Toplevel then
  285. // the top must be moved above the first not running Toplevel.
  286. if (OverlappedTop is { }
  287. && top != OverlappedTop
  288. && top != Application.Current
  289. && Application.Current?.Running == false
  290. && top?.Running == false)
  291. {
  292. lock (Application.TopLevels)
  293. {
  294. Application.TopLevels.MoveTo (Application.Current, 0, new ToplevelEqualityComparer ());
  295. }
  296. var index = 0;
  297. foreach (Toplevel t in Application.TopLevels.ToArray ())
  298. {
  299. if (!t.Running && t != Application.Current && index > 0)
  300. {
  301. lock (Application.TopLevels)
  302. {
  303. Application.TopLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
  304. }
  305. }
  306. index++;
  307. }
  308. return false;
  309. }
  310. if ((OverlappedTop is { } && top?.Modal == true && Application.TopLevels.Peek () != top)
  311. || (OverlappedTop is { } && Application.Current != OverlappedTop && Application.Current?.Modal == false && top == OverlappedTop)
  312. || (OverlappedTop is { } && Application.Current?.Modal == false && top != Application.Current)
  313. || (OverlappedTop is { } && Application.Current?.Modal == true && top == OverlappedTop))
  314. {
  315. lock (Application.TopLevels)
  316. {
  317. Application.TopLevels.MoveTo (top!, 0, new ToplevelEqualityComparer ());
  318. Application.Current = top;
  319. }
  320. }
  321. return true;
  322. }
  323. }