View.Hierarchy.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. using System.Collections.ObjectModel;
  2. using System.Diagnostics;
  3. using System.Diagnostics.CodeAnalysis;
  4. namespace Terminal.Gui.ViewBase;
  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(View?)"/> and <see cref="Remove(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. // Ensure views don't have focus when being added
  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 SubViews 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. /// <returns>
  262. /// A list of removed Views.
  263. /// </returns>
  264. public virtual IReadOnlyCollection<View> RemoveAll ()
  265. {
  266. List<View> removedList = new List<View> ();
  267. while (InternalSubViews.Count > 0)
  268. {
  269. View? removed = Remove (InternalSubViews [0]);
  270. if (removed is { })
  271. {
  272. removedList.Add (removed);
  273. }
  274. }
  275. return removedList.AsReadOnly ();
  276. }
  277. /// <summary>
  278. /// Removes all SubViews of a type added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
  279. /// </summary>
  280. /// <remarks>
  281. /// <para>
  282. /// Normally SubViews will be disposed when this View is disposed. Removing a SubView causes ownership of the
  283. /// SubView's
  284. /// lifecycle to be transferred to the caller; the caller must call <see cref="Dispose()"/> on any Views that were
  285. /// added.
  286. /// </para>
  287. /// </remarks>
  288. /// <returns>
  289. /// A list of removed Views.
  290. /// </returns>
  291. public virtual IReadOnlyCollection<TView> RemoveAll<TView> () where TView : View
  292. {
  293. List<TView> removedList = new List<TView> ();
  294. foreach (TView view in InternalSubViews.OfType<TView> ().ToList ())
  295. {
  296. Remove (view);
  297. removedList.Add (view);
  298. }
  299. return removedList.AsReadOnly ();
  300. }
  301. #pragma warning disable CS0067 // The event is never used
  302. /// <summary>Raised when a SubView has been removed from this View.</summary>
  303. public event EventHandler<SuperViewChangedEventArgs>? Removed;
  304. #pragma warning restore CS0067 // The event is never used
  305. #endregion AddRemove
  306. // TODO: This drives a weird coupling of Application.TopRunnable and View. It's not clear why this is needed.
  307. /// <summary>Get the top superview of a given <see cref="View"/>.</summary>
  308. /// <returns>The superview view.</returns>
  309. internal View? GetTopSuperView (View? view = null, View? superview = null)
  310. {
  311. View? top = superview ?? App?.TopRunnable;
  312. for (View? v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
  313. {
  314. top = v;
  315. if (top == superview)
  316. {
  317. break;
  318. }
  319. }
  320. return top;
  321. }
  322. /// <summary>
  323. /// Gets whether <paramref name="view"/> is in the View hierarchy of <paramref name="start"/>.
  324. /// </summary>
  325. /// <param name="start">The View at the start of the hierarchy.</param>
  326. /// <param name="view">The View to test.</param>
  327. /// <param name="includeAdornments">Will include all <see cref="Adornment"/>s in addition to Subviews if true.</param>
  328. /// <returns></returns>
  329. public static bool IsInHierarchy (View? start, View? view, bool includeAdornments = false)
  330. {
  331. if (view is null || start is null)
  332. {
  333. return false;
  334. }
  335. if (view == start)
  336. {
  337. return true;
  338. }
  339. foreach (View subView in start.InternalSubViews)
  340. {
  341. if (view == subView)
  342. {
  343. return true;
  344. }
  345. bool found = IsInHierarchy (subView, view, includeAdornments);
  346. if (found)
  347. {
  348. return found;
  349. }
  350. }
  351. if (includeAdornments)
  352. {
  353. bool found = IsInHierarchy (start.Padding, view, includeAdornments);
  354. if (found)
  355. {
  356. return found;
  357. }
  358. found = IsInHierarchy (start.Border, view, includeAdornments);
  359. if (found)
  360. {
  361. return found;
  362. }
  363. found = IsInHierarchy (start.Margin, view, includeAdornments);
  364. if (found)
  365. {
  366. return found;
  367. }
  368. }
  369. return false;
  370. }
  371. #region SubViewOrdering
  372. /// <summary>
  373. /// Moves <paramref name="subview"/> one position towards the end of the <see cref="SubViews"/> list.
  374. /// </summary>
  375. /// <param name="subview">The subview to move.</param>
  376. public void MoveSubViewTowardsEnd (View subview)
  377. {
  378. PerformActionForSubView (
  379. subview,
  380. x =>
  381. {
  382. int idx = InternalSubViews!.IndexOf (x);
  383. if (idx + 1 < InternalSubViews.Count)
  384. {
  385. InternalSubViews.Remove (x);
  386. InternalSubViews.Insert (idx + 1, x);
  387. }
  388. }
  389. );
  390. }
  391. /// <summary>
  392. /// Moves <paramref name="subview"/> to the end of the <see cref="SubViews"/> list.
  393. /// If the <see cref="Arrangement"/> is <see cref="ViewArrangement.Overlapped"/>, keeps the original sorting.
  394. /// </summary>
  395. /// <param name="subview">The subview to move.</param>
  396. public void MoveSubViewToEnd (View subview)
  397. {
  398. if (Arrangement.HasFlag (ViewArrangement.Overlapped))
  399. {
  400. PerformActionForSubView (
  401. subview,
  402. x =>
  403. {
  404. while (InternalSubViews!.IndexOf (x) != InternalSubViews.Count - 1)
  405. {
  406. View v = InternalSubViews [0];
  407. InternalSubViews!.Remove (v);
  408. InternalSubViews.Add (v);
  409. }
  410. }
  411. );
  412. return;
  413. }
  414. PerformActionForSubView (
  415. subview,
  416. x =>
  417. {
  418. InternalSubViews!.Remove (x);
  419. InternalSubViews.Add (x);
  420. }
  421. );
  422. }
  423. /// <summary>
  424. /// Moves <paramref name="subview"/> one position towards the start of the <see cref="SubViews"/> list.
  425. /// </summary>
  426. /// <param name="subview">The subview to move.</param>
  427. public void MoveSubViewTowardsStart (View subview)
  428. {
  429. PerformActionForSubView (
  430. subview,
  431. x =>
  432. {
  433. int idx = InternalSubViews!.IndexOf (x);
  434. if (idx > 0)
  435. {
  436. InternalSubViews.Remove (x);
  437. InternalSubViews.Insert (idx - 1, x);
  438. }
  439. }
  440. );
  441. }
  442. /// <summary>
  443. /// Moves <paramref name="subview"/> to the start of the <see cref="SubViews"/> list.
  444. /// </summary>
  445. /// <param name="subview">The subview to move.</param>
  446. public void MoveSubViewToStart (View subview)
  447. {
  448. PerformActionForSubView (
  449. subview,
  450. x =>
  451. {
  452. InternalSubViews!.Remove (x);
  453. InternalSubViews.Insert (0, subview);
  454. }
  455. );
  456. }
  457. /// <summary>
  458. /// Internal API that runs <paramref name="action"/> on a subview if it is part of the <see cref="SubViews"/> list.
  459. /// </summary>
  460. /// <param name="subview"></param>
  461. /// <param name="action"></param>
  462. private void PerformActionForSubView (View subview, Action<View> action)
  463. {
  464. if (InternalSubViews.Contains (subview))
  465. {
  466. action (subview);
  467. }
  468. // BUGBUG: this is odd. Why is this needed?
  469. SetNeedsDraw ();
  470. subview.SetNeedsDraw ();
  471. }
  472. #endregion SubViewOrdering
  473. }