2
0

View.Hierarchy.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  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. if (App?.Mouse.MouseGrabView == view)
  206. {
  207. App.Mouse.UngrabMouse ();
  208. }
  209. Rectangle touched = view.Frame;
  210. bool hadFocus = view.HasFocus;
  211. bool couldFocus = view.CanFocus;
  212. if (hadFocus)
  213. {
  214. view.CanFocus = false; // If view had focus, this will ensure it doesn't and it stays that way
  215. }
  216. Debug.Assert (!view.HasFocus);
  217. InternalSubViews.Remove (view);
  218. // Clean up focus stuff
  219. _previouslyFocused = null;
  220. if (view.SuperView is { } && view.SuperView._previouslyFocused == this)
  221. {
  222. view.SuperView._previouslyFocused = null;
  223. }
  224. view.SuperView = null;
  225. SetNeedsLayout ();
  226. SetNeedsDraw ();
  227. foreach (View v in InternalSubViews)
  228. {
  229. if (v.Frame.IntersectsWith (touched))
  230. {
  231. view.SetNeedsDraw ();
  232. }
  233. }
  234. view.CanFocus = couldFocus; // Restore to previous value
  235. if (_previouslyFocused == view)
  236. {
  237. _previouslyFocused = null;
  238. }
  239. RaiseSubViewRemoved (view);
  240. return view;
  241. }
  242. internal void RaiseSubViewRemoved (View view)
  243. {
  244. OnSubViewRemoved (view);
  245. SubViewRemoved?.Invoke (this, new (this, view));
  246. }
  247. /// <summary>
  248. /// Called when a SubView has been removed from this View.
  249. /// </summary>
  250. /// <param name="view"></param>
  251. protected virtual void OnSubViewRemoved (View view) { }
  252. /// <summary>Raised when a SubView has been added to this View.</summary>
  253. public event EventHandler<SuperViewChangedEventArgs>? SubViewRemoved;
  254. /// <summary>
  255. /// Removes all SubViews added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
  256. /// </summary>
  257. /// <remarks>
  258. /// <para>
  259. /// Normally SubViews will be disposed when this View is disposed. Removing a SubView causes ownership of the
  260. /// SubView's
  261. /// lifecycle to be transferred to the caller; the caller must call <see cref="Dispose()"/> on any Views that were
  262. /// added.
  263. /// </para>
  264. /// </remarks>
  265. /// <returns>
  266. /// A list of removed Views.
  267. /// </returns>
  268. public virtual IReadOnlyCollection<View> RemoveAll ()
  269. {
  270. List<View> removedList = new List<View> ();
  271. while (InternalSubViews.Count > 0)
  272. {
  273. View? removed = Remove (InternalSubViews [0]);
  274. if (removed is { })
  275. {
  276. removedList.Add (removed);
  277. }
  278. }
  279. return removedList.AsReadOnly ();
  280. }
  281. /// <summary>
  282. /// Removes all SubViews of a type added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
  283. /// </summary>
  284. /// <remarks>
  285. /// <para>
  286. /// Normally SubViews will be disposed when this View is disposed. Removing a SubView causes ownership of the
  287. /// SubView's
  288. /// lifecycle to be transferred to the caller; the caller must call <see cref="Dispose()"/> on any Views that were
  289. /// added.
  290. /// </para>
  291. /// </remarks>
  292. /// <returns>
  293. /// A list of removed Views.
  294. /// </returns>
  295. public virtual IReadOnlyCollection<TView> RemoveAll<TView> () where TView : View
  296. {
  297. List<TView> removedList = new List<TView> ();
  298. foreach (TView view in InternalSubViews.OfType<TView> ().ToList ())
  299. {
  300. Remove (view);
  301. removedList.Add (view);
  302. }
  303. return removedList.AsReadOnly ();
  304. }
  305. #pragma warning disable CS0067 // The event is never used
  306. /// <summary>Raised when a SubView has been removed from this View.</summary>
  307. public event EventHandler<SuperViewChangedEventArgs>? Removed;
  308. #pragma warning restore CS0067 // The event is never used
  309. #endregion AddRemove
  310. // TODO: This drives a weird coupling of Application.TopRunnable and View. It's not clear why this is needed.
  311. /// <summary>Get the top superview of a given <see cref="View"/>.</summary>
  312. /// <returns>The superview view.</returns>
  313. internal View? GetTopSuperView (View? view = null, View? superview = null)
  314. {
  315. View? top = superview ?? App?.TopRunnableView;
  316. for (View? v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
  317. {
  318. top = v;
  319. if (top == superview)
  320. {
  321. break;
  322. }
  323. }
  324. return top;
  325. }
  326. /// <summary>
  327. /// Gets whether <paramref name="view"/> is in the View hierarchy of <paramref name="start"/>.
  328. /// </summary>
  329. /// <param name="start">The View at the start of the hierarchy.</param>
  330. /// <param name="view">The View to test.</param>
  331. /// <param name="includeAdornments">Will include all <see cref="Adornment"/>s in addition to Subviews if true.</param>
  332. /// <returns></returns>
  333. public static bool IsInHierarchy (View? start, View? view, bool includeAdornments = false)
  334. {
  335. if (view is null || start is null)
  336. {
  337. return false;
  338. }
  339. if (view == start)
  340. {
  341. return true;
  342. }
  343. foreach (View subView in start.InternalSubViews)
  344. {
  345. if (view == subView)
  346. {
  347. return true;
  348. }
  349. bool found = IsInHierarchy (subView, view, includeAdornments);
  350. if (found)
  351. {
  352. return found;
  353. }
  354. }
  355. if (includeAdornments)
  356. {
  357. bool found = IsInHierarchy (start.Padding, view, includeAdornments);
  358. if (found)
  359. {
  360. return found;
  361. }
  362. found = IsInHierarchy (start.Border, view, includeAdornments);
  363. if (found)
  364. {
  365. return found;
  366. }
  367. found = IsInHierarchy (start.Margin, view, includeAdornments);
  368. if (found)
  369. {
  370. return found;
  371. }
  372. }
  373. return false;
  374. }
  375. #region SubViewOrdering
  376. /// <summary>
  377. /// Moves <paramref name="subview"/> one position towards the end of the <see cref="SubViews"/> list.
  378. /// </summary>
  379. /// <param name="subview">The subview to move.</param>
  380. public void MoveSubViewTowardsEnd (View subview)
  381. {
  382. PerformActionForSubView (
  383. subview,
  384. x =>
  385. {
  386. int idx = InternalSubViews!.IndexOf (x);
  387. if (idx + 1 < InternalSubViews.Count)
  388. {
  389. InternalSubViews.Remove (x);
  390. InternalSubViews.Insert (idx + 1, x);
  391. }
  392. }
  393. );
  394. }
  395. /// <summary>
  396. /// Moves <paramref name="subview"/> to the end of the <see cref="SubViews"/> list.
  397. /// If the <see cref="Arrangement"/> is <see cref="ViewArrangement.Overlapped"/>, keeps the original sorting.
  398. /// </summary>
  399. /// <param name="subview">The subview to move.</param>
  400. public void MoveSubViewToEnd (View subview)
  401. {
  402. if (Arrangement.HasFlag (ViewArrangement.Overlapped))
  403. {
  404. PerformActionForSubView (
  405. subview,
  406. x =>
  407. {
  408. while (InternalSubViews!.IndexOf (x) != InternalSubViews.Count - 1)
  409. {
  410. View v = InternalSubViews [0];
  411. InternalSubViews!.Remove (v);
  412. InternalSubViews.Add (v);
  413. }
  414. }
  415. );
  416. return;
  417. }
  418. PerformActionForSubView (
  419. subview,
  420. x =>
  421. {
  422. InternalSubViews!.Remove (x);
  423. InternalSubViews.Add (x);
  424. }
  425. );
  426. }
  427. /// <summary>
  428. /// Moves <paramref name="subview"/> one position towards the start of the <see cref="SubViews"/> list.
  429. /// </summary>
  430. /// <param name="subview">The subview to move.</param>
  431. public void MoveSubViewTowardsStart (View subview)
  432. {
  433. PerformActionForSubView (
  434. subview,
  435. x =>
  436. {
  437. int idx = InternalSubViews!.IndexOf (x);
  438. if (idx > 0)
  439. {
  440. InternalSubViews.Remove (x);
  441. InternalSubViews.Insert (idx - 1, x);
  442. }
  443. }
  444. );
  445. }
  446. /// <summary>
  447. /// Moves <paramref name="subview"/> to the start of the <see cref="SubViews"/> list.
  448. /// </summary>
  449. /// <param name="subview">The subview to move.</param>
  450. public void MoveSubViewToStart (View subview)
  451. {
  452. PerformActionForSubView (
  453. subview,
  454. x =>
  455. {
  456. InternalSubViews!.Remove (x);
  457. InternalSubViews.Insert (0, subview);
  458. }
  459. );
  460. }
  461. /// <summary>
  462. /// Internal API that runs <paramref name="action"/> on a subview if it is part of the <see cref="SubViews"/> list.
  463. /// </summary>
  464. /// <param name="subview"></param>
  465. /// <param name="action"></param>
  466. private void PerformActionForSubView (View subview, Action<View> action)
  467. {
  468. if (InternalSubViews.Contains (subview))
  469. {
  470. action (subview);
  471. }
  472. // BUGBUG: this is odd. Why is this needed?
  473. SetNeedsDraw ();
  474. subview.SetNeedsDraw ();
  475. }
  476. #endregion SubViewOrdering
  477. }