View.Hierarchy.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. #nullable enable
  2. using System.Diagnostics;
  3. using System.Diagnostics.CodeAnalysis;
  4. namespace Terminal.Gui;
  5. public partial class View // SuperView/SubView hierarchy management (SuperView, SubViews, Add, Remove, etc.)
  6. {
  7. [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
  8. private static readonly IReadOnlyCollection<View> _empty = [];
  9. private readonly List<View>? _subviews = [];
  10. // Internally, we use InternalSubViews rather than subviews, as we do not expect us
  11. // to make the same mistakes our users make when they poke at the SubViews.
  12. internal IList<View> InternalSubViews => _subviews ?? [];
  13. /// <summary>Gets the list of SubViews.</summary>
  14. /// <remarks>
  15. /// Use <see cref="Add(Terminal.Gui.View?)"/> and <see cref="Remove(Terminal.Gui.View?)"/> to add or remove subviews.
  16. /// </remarks>
  17. public IReadOnlyCollection<View> SubViews => InternalSubViews?.AsReadOnly () ?? _empty;
  18. private View? _superView;
  19. /// <summary>
  20. /// Gets this Views SuperView (the View's container), or <see langword="null"/> if this view has not been added as a
  21. /// SubView.
  22. /// </summary>
  23. /// <seealso cref="OnSuperViewChanged"/>
  24. /// <seealso cref="SuperViewChanged"/>
  25. public View? SuperView
  26. {
  27. get => _superView!;
  28. private set => SetSuperView (value);
  29. }
  30. private void SetSuperView (View? value)
  31. {
  32. if (_superView == value)
  33. {
  34. return;
  35. }
  36. _superView = value;
  37. RaiseSuperViewChanged ();
  38. }
  39. private void RaiseSuperViewChanged ()
  40. {
  41. SuperViewChangedEventArgs args = new (SuperView, this);
  42. OnSuperViewChanged (args);
  43. SuperViewChanged?.Invoke (this, args);
  44. }
  45. /// <summary>
  46. /// Called when the SuperView of this View has changed.
  47. /// </summary>
  48. /// <param name="e"></param>
  49. protected virtual void OnSuperViewChanged (SuperViewChangedEventArgs e) { }
  50. /// <summary>Raised when the SuperView of this View has changed.</summary>
  51. public event EventHandler<SuperViewChangedEventArgs>? SuperViewChanged;
  52. #region AddRemove
  53. /// <summary>Adds a SubView (child) to this view.</summary>
  54. /// <remarks>
  55. /// <para>
  56. /// The Views that have been added to this view can be retrieved via the <see cref="SubViews"/> property.
  57. /// </para>
  58. /// <para>
  59. /// To check if a View has been added to this View, compare it's <see cref="SuperView"/> property to this View.
  60. /// </para>
  61. /// <para>
  62. /// SubViews will be disposed when this View is disposed. In other-words, calling this method causes
  63. /// the lifecycle of the subviews to be transferred to this View.
  64. /// </para>
  65. /// <para>
  66. /// Calls/Raises the <see cref="OnSubViewAdded"/>/<see cref="SubViewAdded"/> event.
  67. /// </para>
  68. /// <para>
  69. /// The <see cref="OnSuperViewChanged"/>/<see cref="SuperViewChanged"/> event will be raised on the added View.
  70. /// </para>
  71. /// </remarks>
  72. /// <param name="view">The view to add.</param>
  73. /// <returns>The view that was added.</returns>
  74. /// <seealso cref="Remove(View)"/>
  75. /// <seealso cref="RemoveAll"/>
  76. /// <seealso cref="OnSubViewAdded"/>
  77. /// <seealso cref="SubViewAdded"/>
  78. public virtual View? Add (View? view)
  79. {
  80. if (view is null)
  81. {
  82. return null;
  83. }
  84. //Debug.Assert (view.SuperView is null, $"{view} already has a SuperView: {view.SuperView}.");
  85. if (view.SuperView is {})
  86. {
  87. Logging.Warning ($"{view} already has a SuperView: {view.SuperView}.");
  88. }
  89. //Debug.Assert (!InternalSubViews.Contains (view), $"{view} has already been Added to {this}.");
  90. if (InternalSubViews.Contains (view))
  91. {
  92. Logging.Warning ($"{view} has already been Added to {this}.");
  93. }
  94. // TileView likes to add views that were previously added and have HasFocus = true. No bueno.
  95. view.HasFocus = false;
  96. // TODO: Make this thread safe
  97. InternalSubViews.Add (view);
  98. view.SuperView = this;
  99. if (view is { Enabled: true, Visible: true, CanFocus: true })
  100. {
  101. // Add will cause the newly added subview to gain focus if it's focusable
  102. if (HasFocus)
  103. {
  104. view.SetFocus ();
  105. }
  106. }
  107. if (view.Enabled && !Enabled)
  108. {
  109. view.Enabled = false;
  110. }
  111. // Raise event indicating a subview has been added
  112. // We do this before Init.
  113. RaiseSubViewAdded (view);
  114. if (IsInitialized && !view.IsInitialized)
  115. {
  116. view.BeginInit ();
  117. view.EndInit ();
  118. }
  119. SetNeedsDraw ();
  120. SetNeedsLayout ();
  121. return view;
  122. }
  123. /// <summary>Adds the specified SubView (children) to the view.</summary>
  124. /// <param name="views">Array of one or more views (can be optional parameter).</param>
  125. /// <remarks>
  126. /// <para>
  127. /// The Views that have been added to this view can be retrieved via the <see cref="SubViews"/> property. See also
  128. /// <seealso cref="Remove(View)"/> and <seealso cref="RemoveAll"/>.
  129. /// </para>
  130. /// <para>
  131. /// SubViews will be disposed when this View is disposed. In other-words, calling this method causes
  132. /// the lifecycle of the subviews to be transferred to this View.
  133. /// </para>
  134. /// </remarks>
  135. public void Add (params View []? views)
  136. {
  137. if (views is null)
  138. {
  139. return;
  140. }
  141. foreach (View view in views)
  142. {
  143. Add (view);
  144. }
  145. }
  146. internal void RaiseSubViewAdded (View view)
  147. {
  148. OnSubViewAdded (view);
  149. SubViewAdded?.Invoke (this, new (this, view));
  150. }
  151. /// <summary>
  152. /// Called when a SubView has been added to this View.
  153. /// </summary>
  154. /// <remarks>
  155. /// If the SubView has not been initialized, this happens before BeginInit/EndInit is called.
  156. /// </remarks>
  157. /// <param name="view"></param>
  158. protected virtual void OnSubViewAdded (View view) { }
  159. /// <summary>Raised when a SubView has been added to this View.</summary>
  160. /// <remarks>
  161. /// If the SubView has not been initialized, this happens before BeginInit/EndInit is called.
  162. /// </remarks>
  163. public event EventHandler<SuperViewChangedEventArgs>? SubViewAdded;
  164. /// <summary>Removes a SubView added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.</summary>
  165. /// <remarks>
  166. /// <para>
  167. /// Normally SubViews will be disposed when this View is disposed. Removing a SubView causes ownership of the
  168. /// SubView's
  169. /// lifecycle to be transferred to the caller; the caller must call <see cref="Dispose()"/>.
  170. /// </para>
  171. /// <para>
  172. /// Calls/Raises the <see cref="OnSubViewRemoved"/>/<see cref="SubViewRemoved"/> event.
  173. /// </para>
  174. /// <para>
  175. /// The <see cref="OnSuperViewChanged"/>/<see cref="SuperViewChanged"/> event will be raised on the removed View.
  176. /// </para>
  177. /// </remarks>
  178. /// <returns>
  179. /// The removed View. <see langword="null"/> if the View could not be removed.
  180. /// </returns>
  181. /// <seealso cref="OnSubViewRemoved"/>
  182. /// <seealso cref="SubViewRemoved"/>"/>
  183. public virtual View? Remove (View? view)
  184. {
  185. if (view is null)
  186. {
  187. return null;
  188. }
  189. if (InternalSubViews.Count == 0)
  190. {
  191. return view;
  192. }
  193. if (view.SuperView is null)
  194. {
  195. Logging.Warning ($"{view} cannot be Removed. SuperView is null.");
  196. }
  197. if (view.SuperView != this)
  198. {
  199. Logging.Warning ($"{view} cannot be Removed. SuperView is not this ({view.SuperView}.");
  200. }
  201. if (!InternalSubViews.Contains (view))
  202. {
  203. Logging.Warning ($"{view} cannot be Removed. It has not been added to {this}.");
  204. }
  205. Rectangle touched = view.Frame;
  206. bool hadFocus = view.HasFocus;
  207. bool couldFocus = view.CanFocus;
  208. if (hadFocus)
  209. {
  210. view.CanFocus = false; // If view had focus, this will ensure it doesn't and it stays that way
  211. }
  212. Debug.Assert (!view.HasFocus);
  213. InternalSubViews.Remove (view);
  214. // Clean up focus stuff
  215. _previouslyFocused = null;
  216. if (view.SuperView is { } && view.SuperView._previouslyFocused == this)
  217. {
  218. view.SuperView._previouslyFocused = null;
  219. }
  220. view.SuperView = null;
  221. SetNeedsLayout ();
  222. SetNeedsDraw ();
  223. foreach (View v in InternalSubViews)
  224. {
  225. if (v.Frame.IntersectsWith (touched))
  226. {
  227. view.SetNeedsDraw ();
  228. }
  229. }
  230. view.CanFocus = couldFocus; // Restore to previous value
  231. if (_previouslyFocused == view)
  232. {
  233. _previouslyFocused = null;
  234. }
  235. RaiseSubViewRemoved (view);
  236. return view;
  237. }
  238. internal void RaiseSubViewRemoved (View view)
  239. {
  240. OnSubViewRemoved (view);
  241. SubViewRemoved?.Invoke (this, new (this, view));
  242. }
  243. /// <summary>
  244. /// Called when a SubView has been removed from this View.
  245. /// </summary>
  246. /// <param name="view"></param>
  247. protected virtual void OnSubViewRemoved (View view) { }
  248. /// <summary>Raised when a SubView has been added to this View.</summary>
  249. public event EventHandler<SuperViewChangedEventArgs>? SubViewRemoved;
  250. /// <summary>
  251. /// Removes all SubView (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
  252. /// </summary>
  253. /// <remarks>
  254. /// <para>
  255. /// Normally SubViews will be disposed when this View is disposed. Removing a SubView causes ownership of the
  256. /// SubView's
  257. /// lifecycle to be transferred to the caller; the caller must call <see cref="Dispose()"/> on any Views that were
  258. /// added.
  259. /// </para>
  260. /// </remarks>
  261. public virtual void RemoveAll ()
  262. {
  263. while (InternalSubViews.Count > 0)
  264. {
  265. Remove (InternalSubViews [0]);
  266. }
  267. }
  268. #pragma warning disable CS0067 // The event is never used
  269. /// <summary>Raised when a SubView has been removed from this View.</summary>
  270. public event EventHandler<SuperViewChangedEventArgs>? Removed;
  271. #pragma warning restore CS0067 // The event is never used
  272. #endregion AddRemove
  273. // TODO: This drives a weird coupling of Application.Top and View. It's not clear why this is needed.
  274. /// <summary>Get the top superview of a given <see cref="View"/>.</summary>
  275. /// <returns>The superview view.</returns>
  276. internal View? GetTopSuperView (View? view = null, View? superview = null)
  277. {
  278. View? top = superview ?? Application.Top;
  279. for (View? v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
  280. {
  281. top = v;
  282. if (top == superview)
  283. {
  284. break;
  285. }
  286. }
  287. return top;
  288. }
  289. /// <summary>
  290. /// Gets whether <paramref name="view"/> is in the SubView hierarchy of <paramref name="start"/>.
  291. /// </summary>
  292. /// <param name="start">The View at the start of the hierarchy.</param>
  293. /// <param name="view">The View to test.</param>
  294. /// <param name="includeAdornments">Will search the subview hierarchy of the adornments if true.</param>
  295. /// <returns></returns>
  296. public static bool IsInHierarchy (View? start, View? view, bool includeAdornments = false)
  297. {
  298. if (view is null || start is null)
  299. {
  300. return false;
  301. }
  302. if (view == start)
  303. {
  304. return true;
  305. }
  306. foreach (View subView in start.InternalSubViews)
  307. {
  308. if (view == subView)
  309. {
  310. return true;
  311. }
  312. bool found = IsInHierarchy (subView, view, includeAdornments);
  313. if (found)
  314. {
  315. return found;
  316. }
  317. }
  318. if (includeAdornments)
  319. {
  320. bool found = IsInHierarchy (start.Padding, view, includeAdornments);
  321. if (found)
  322. {
  323. return found;
  324. }
  325. found = IsInHierarchy (start.Border, view, includeAdornments);
  326. if (found)
  327. {
  328. return found;
  329. }
  330. found = IsInHierarchy (start.Margin, view, includeAdornments);
  331. if (found)
  332. {
  333. return found;
  334. }
  335. }
  336. return false;
  337. }
  338. #region SubViewOrdering
  339. /// <summary>
  340. /// Moves <paramref name="subview"/> one position towards the end of the <see cref="SubViews"/> list.
  341. /// </summary>
  342. /// <param name="subview">The subview to move.</param>
  343. public void MoveSubViewTowardsEnd (View subview)
  344. {
  345. PerformActionForSubView (
  346. subview,
  347. x =>
  348. {
  349. int idx = InternalSubViews!.IndexOf (x);
  350. if (idx + 1 < InternalSubViews.Count)
  351. {
  352. InternalSubViews.Remove (x);
  353. InternalSubViews.Insert (idx + 1, x);
  354. }
  355. }
  356. );
  357. }
  358. /// <summary>
  359. /// Moves <paramref name="subview"/> to the end of the <see cref="SubViews"/> list.
  360. /// </summary>
  361. /// <param name="subview">The subview to move.</param>
  362. public void MoveSubViewToEnd (View subview)
  363. {
  364. PerformActionForSubView (
  365. subview,
  366. x =>
  367. {
  368. InternalSubViews!.Remove (x);
  369. InternalSubViews.Add (x);
  370. }
  371. );
  372. }
  373. /// <summary>
  374. /// Moves <paramref name="subview"/> one position towards the start of the <see cref="SubViews"/> list.
  375. /// </summary>
  376. /// <param name="subview">The subview to move.</param>
  377. public void MoveSubViewTowardsStart (View subview)
  378. {
  379. PerformActionForSubView (
  380. subview,
  381. x =>
  382. {
  383. int idx = InternalSubViews!.IndexOf (x);
  384. if (idx > 0)
  385. {
  386. InternalSubViews.Remove (x);
  387. InternalSubViews.Insert (idx - 1, x);
  388. }
  389. }
  390. );
  391. }
  392. /// <summary>
  393. /// Moves <paramref name="subview"/> to the start of the <see cref="SubViews"/> list.
  394. /// </summary>
  395. /// <param name="subview">The subview to move.</param>
  396. public void MoveSubViewToStart (View subview)
  397. {
  398. PerformActionForSubView (
  399. subview,
  400. x =>
  401. {
  402. InternalSubViews!.Remove (x);
  403. InternalSubViews.Insert (0, subview);
  404. }
  405. );
  406. }
  407. /// <summary>
  408. /// Internal API that runs <paramref name="action"/> on a subview if it is part of the <see cref="SubViews"/> list.
  409. /// </summary>
  410. /// <param name="subview"></param>
  411. /// <param name="action"></param>
  412. private void PerformActionForSubView (View subview, Action<View> action)
  413. {
  414. if (InternalSubViews.Contains (subview))
  415. {
  416. action (subview);
  417. }
  418. // BUGBUG: this is odd. Why is this needed?
  419. SetNeedsDraw ();
  420. subview.SetNeedsDraw ();
  421. }
  422. #endregion SubViewOrdering
  423. }