View.Navigation.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. using System.Diagnostics;
  2. using static Terminal.Gui.FakeDriver;
  3. namespace Terminal.Gui;
  4. public partial class View // Focus and cross-view navigation management (TabStop, TabIndex, etc...)
  5. {
  6. #region HasFocus
  7. // Backs `HasFocus` and is the ultimate source of truth whether a View has focus or not.
  8. private bool _hasFocus;
  9. /// <summary>
  10. /// Gets or sets whether this view has focus.
  11. /// </summary>
  12. /// <remarks>
  13. /// <para>
  14. /// Only Views that are visible, enabled, and have <see cref="CanFocus"/> set to <see langword="true"/> are focusable. If
  15. /// these conditions are not met when this property is set to <see langword="true"/> <see cref="HasFocus"/> will not change.
  16. /// </para>
  17. /// <para>
  18. /// Setting this property causes the <see cref="OnEnter"/> and <see cref="OnLeave"/> virtual methods (and <see cref="Enter"/> and
  19. /// <see cref="Leave"/> events to be raised). If the event is cancelled, <see cref="HasFocus"/> will not be changed.
  20. /// </para>
  21. /// <para>
  22. /// Setting this property to <see langword="true"/> will recursively set <see cref="HasFocus"/> to
  23. /// <see langword="true"/> for all SuperViews up the hierarchy.
  24. /// </para>
  25. /// <para>
  26. /// Setting this property to <see langword="true"/> will cause the subview furthest down the hierarchy that is
  27. /// focusable to also gain focus (as long as <see cref="TabStop"/>
  28. /// </para>
  29. /// <para>
  30. /// Setting this property to <see langword="false"/> will recursively set <see cref="HasFocus"/> to
  31. /// <see langword="false"/> for any focused subviews.
  32. /// </para>
  33. /// </remarks>
  34. public bool HasFocus
  35. {
  36. set => SetHasFocus (value, this);
  37. get => _hasFocus;
  38. }
  39. /// <summary>
  40. /// Causes this view to be focused. Calling this method has the same effect as setting <see cref="HasFocus"/> to
  41. /// <see langword="true"/>.
  42. /// </summary>
  43. public void SetFocus ()
  44. {
  45. HasFocus = true;
  46. }
  47. /// <summary>
  48. /// Internal API that sets <see cref="HasFocus"/>. This method is called by `HasFocus_set` and other methods that
  49. /// need to set or remove focus from a view.
  50. /// </summary>
  51. /// <param name="newHasFocus">The new setting for <see cref="HasFocus"/>.</param>
  52. /// <param name="view">The view that will be gaining or losing focus.</param>
  53. /// <remarks>
  54. /// If <paramref name="newHasFocus"/> is <see langword="false"/> and there is a focused subview (<see cref="Focused"/>
  55. /// is not <see langword="null"/>),
  56. /// this method will recursively remove focus from any focused subviews of <see cref="Focused"/>.
  57. /// </remarks>
  58. private bool SetHasFocus (bool newHasFocus, View view)
  59. {
  60. if (HasFocus != newHasFocus)
  61. {
  62. if (newHasFocus)
  63. {
  64. Debug.Assert (view is null || SuperView is null || ApplicationNavigation.IsInHierarchy (SuperView, view));
  65. if (EnterFocus (view))
  66. {
  67. // The change happened
  68. // HasFocus is now true
  69. }
  70. else
  71. {
  72. // The event was cancelled or view is not focusable
  73. return false;
  74. }
  75. }
  76. else
  77. {
  78. if (LeaveFocus (view))
  79. {
  80. // The change happened
  81. // HasFocus is now false
  82. }
  83. else
  84. {
  85. // The event was cancelled or view was not focused
  86. return false;
  87. }
  88. }
  89. SetNeedsDisplay ();
  90. }
  91. return true;
  92. }
  93. /// <summary>
  94. /// Called when view is entering focus. This method is called by <see cref="SetHasFocus"/> and other methods that
  95. /// set or remove focus from a view.
  96. /// </summary>
  97. /// <param name="leavingView">The previously focused view. If <see langword="null"/> there is no previously focused view.</param>
  98. /// <returns><see langword="true"/> if <see cref="HasFocus"/> was changed to <see langword="true"/>.</returns>
  99. /// <exception cref="InvalidOperationException"></exception>
  100. private bool EnterFocus ([CanBeNull] View leavingView)
  101. {
  102. // Pre-conditions
  103. if (_hasFocus)
  104. {
  105. throw new InvalidOperationException ($"EnterFocus should not be called if the view already has focus.");
  106. }
  107. if (CanFocus && SuperView?.CanFocus == false)
  108. {
  109. throw new InvalidOperationException ($"It is not possible to EnterFocus if the View's SuperView has CanFocus = false.");
  110. }
  111. if (!CanBeVisible (this) || !Enabled)
  112. {
  113. return false;
  114. }
  115. if (!CanFocus)
  116. {
  117. return false;
  118. }
  119. // Verify all views up the superview-hierarchy are focusable.
  120. if (SuperView is { })
  121. {
  122. View super = SuperView;
  123. while (super is { })
  124. {
  125. // TODO: Check Visible & Enabled too!
  126. if (!super.CanFocus)
  127. {
  128. return false;
  129. }
  130. super = super.SuperView;
  131. }
  132. }
  133. bool previousValue = HasFocus;
  134. // Call the virtual method
  135. if (OnEnter (leavingView))
  136. {
  137. // The event was cancelled
  138. return false;
  139. }
  140. var args = new FocusEventArgs (leavingView, this);
  141. Enter?.Invoke (this, args);
  142. if (args.Cancel)
  143. {
  144. // The event was cancelled
  145. return false;
  146. }
  147. // If we're here, we can be focused. But we may have subviews.
  148. // Restore focus to the most focused subview in the subview-hierarchy
  149. if (!RestoreFocus (TabStop))
  150. {
  151. // Couldn't restore focus, so use Advance to navigate to the next focusable subview
  152. if (!AdvanceFocus (NavigationDirection.Forward, TabStop))
  153. {
  154. // Couldn't advance, so we're the most-focusable view in the application
  155. // We now need to ensure all views up the superview hierarchy have focus.
  156. // Any of them may cancel gaining focus. In which case we need to back out.
  157. // All views down the superview-hierarchy need to have HasFocus == true
  158. // PLACEHOLDER:But just using SetHasFocus on them will infinitely recurse. So we need to do it manually.
  159. if (SuperView is { HasFocus: false } super)
  160. {
  161. if (!super.SetHasFocus (true, this))
  162. {
  163. // The change was cancelled
  164. return false;
  165. }
  166. SuperView.Focused = this;
  167. }
  168. Application.Navigation?.SetFocused (this);
  169. }
  170. }
  171. // Now, these things need to be true for us to set _hasFocus to true:
  172. // All views down v's view-hierarchy need to gain focus, if they don't already have it.
  173. // Any of them may cancel gaining focus, so we need to do that first.
  174. _hasFocus = true;
  175. // By setting _hasFocus to true we've definitively changed HasFocus for this view.
  176. // But we can back out below if a subview or superview cancels gaining focus.
  177. // All views down the superview-hierarchy need to have HasFocus == true
  178. if (SuperView is { })
  179. {
  180. SuperView.Focused = this;
  181. }
  182. // Post-conditions - prove correctness
  183. if (HasFocus == previousValue)
  184. {
  185. throw new InvalidOperationException ($"EnterFocus was not cancelled and the HasFocus value did not change.");
  186. }
  187. return true;
  188. }
  189. /// <summary>Virtual method invoked when this view is gaining focus (entering).</summary>
  190. /// <param name="leavingView">The view that is leaving focus.</param>
  191. /// <returns> <see langword="true"/>, if the event is to be cancelled, <see langword="false"/> otherwise.</returns>
  192. protected virtual bool OnEnter ([CanBeNull] View leavingView)
  193. {
  194. return false;
  195. }
  196. /// <summary>Raised when the view is gaining (entering) focus. Can be cancelled.</summary>
  197. /// <remarks>
  198. /// Raised by <see cref="EnterFocus"/>.
  199. /// </remarks>
  200. public event EventHandler<FocusEventArgs> Enter;
  201. // TODO: Leave does not need to be cancelable. That's overkill. Just make it so that it can't be canceled.
  202. /// <summary>
  203. /// Called when view is entering focus. This method is called by <see cref="SetHasFocus"/> and other methods that
  204. /// set or remove focus from a view.
  205. /// </summary>
  206. /// <param name="enteringView">The previously focused view. If <see langword="null"/> there is no previously focused view.</param>
  207. /// <returns><see langword="true"/> if <see cref="HasFocus"/> was changed.</returns>
  208. /// <exception cref="InvalidOperationException"></exception>
  209. private bool LeaveFocus ([CanBeNull] View enteringView)
  210. {
  211. // Pre-conditions
  212. if (_hasFocus)
  213. {
  214. throw new InvalidOperationException ($"LeaveFocus should not be called if the view does not have focus.");
  215. }
  216. // Before we can leave focus, we need to make sure that all views down the subview-hierarchy have left focus.
  217. // Any of them may cancel losing focus, so we need to do that first.
  218. if (Application.Navigation?.GetFocused () != this)
  219. {
  220. // Save the most focused view in the subview-hierarchy
  221. View originalBottom = Application.Navigation?.GetFocused ();
  222. // Start at the bottom and work our way up to us
  223. View bottom = originalBottom;
  224. while (bottom is { } && bottom != this)
  225. {
  226. if (bottom.HasFocus && !bottom.SetHasFocus (false, enteringView))
  227. {
  228. // The change was cancelled
  229. return false;
  230. }
  231. bottom = bottom.SuperView;
  232. }
  233. PreviouslyMostFocused = originalBottom;
  234. }
  235. bool previousValue = HasFocus;
  236. // Call the virtual method
  237. if (OnLeave (enteringView))
  238. {
  239. // The event was cancelled
  240. return false;
  241. }
  242. var args = new FocusEventArgs (enteringView, this);
  243. Leave?.Invoke (this, args);
  244. if (args.Cancel)
  245. {
  246. // The event was cancelled
  247. return false;
  248. }
  249. Focused = null;
  250. if (SuperView is { })
  251. {
  252. SuperView.Focused = null;
  253. }
  254. _hasFocus = false;
  255. if (Application.Navigation?.GetFocused () != this)
  256. {
  257. PreviouslyMostFocused = null;
  258. if (SuperView is { })
  259. {
  260. SuperView.PreviouslyMostFocused = this;
  261. }
  262. }
  263. // Post-conditions - prove correctness
  264. if (HasFocus == previousValue)
  265. {
  266. throw new InvalidOperationException ($"LeaveFocus was not cancelled and the HasFocus value did not change.");
  267. }
  268. return true;
  269. }
  270. /// <summary>
  271. /// Caches the most focused subview when this view is losing focus. This is used by <see cref="RestoreFocus"/>.
  272. /// </summary>
  273. [CanBeNull]
  274. internal View PreviouslyMostFocused { get; set; }
  275. /// <summary>Virtual method invoked when this view is losing focus (leaving).</summary>
  276. /// <param name="enteringView">The view that is gaining focus.</param>
  277. /// <returns> <see langword="true"/>, if the event is to be cancelled, <see langword="false"/> otherwise.</returns>
  278. protected virtual bool OnLeave ([CanBeNull] View enteringView)
  279. {
  280. return false;
  281. }
  282. /// <summary>Raised when the view is gaining (entering) focus. Can be cancelled.</summary>
  283. /// <remarks>
  284. /// Raised by <see cref="LeaveFocus"/>.
  285. /// </remarks>
  286. public event EventHandler<FocusEventArgs> Leave;
  287. #endregion HasFocus
  288. /// <summary>
  289. /// Advances the focus to the next or previous view in <see cref="View.TabIndexes"/>, based on
  290. /// <paramref name="direction"/>.
  291. /// itself.
  292. /// </summary>
  293. /// <remarks>
  294. /// <para>
  295. /// If there is no next/previous view, the focus is set to the view itself.
  296. /// </para>
  297. /// </remarks>
  298. /// <param name="direction"></param>
  299. /// <param name="behavior"></param>
  300. /// <returns>
  301. /// <see langword="true"/> if focus was changed to another subview (or stayed on this one), <see langword="false"/>
  302. /// otherwise.
  303. /// </returns>
  304. public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
  305. {
  306. if (!CanBeVisible (this)) // TODO: is this check needed?
  307. {
  308. return false;
  309. }
  310. if (TabIndexes is null || TabIndexes.Count == 0)
  311. {
  312. return false;
  313. }
  314. if (Focused is null)
  315. {
  316. FocusDeepest (behavior, direction);
  317. return Focused is { };
  318. }
  319. if (Focused is { })
  320. {
  321. if (Focused.AdvanceFocus (direction, behavior))
  322. {
  323. // TODO: Temporary hack to make Application.Navigation.FocusChanged work
  324. if (Focused.Focused is null)
  325. {
  326. Application.Navigation?.SetFocused (Focused);
  327. }
  328. return true;
  329. }
  330. }
  331. var index = GetScopedTabIndexes (behavior, direction);
  332. if (index.Length == 0)
  333. {
  334. return false;
  335. }
  336. var focusedIndex = index.IndexOf (Focused);
  337. int next = 0;
  338. if (focusedIndex < index.Length - 1)
  339. {
  340. next = focusedIndex + 1;
  341. }
  342. else
  343. {
  344. if (behavior == TabBehavior.TabGroup && behavior == TabStop && SuperView?.TabStop == TabBehavior.TabGroup)
  345. {
  346. // Go up the hierarchy and leave
  347. Focused.SetHasFocus (false, this);
  348. // TODO: Should we check the return value of SetHasFocus?
  349. return false;
  350. }
  351. }
  352. View view = index [next];
  353. if (view.HasFocus)
  354. {
  355. return true;
  356. }
  357. // The subview does not have focus, but at least one other that can. Can this one be focused?
  358. if (view.CanFocus && view.Visible && view.Enabled)
  359. {
  360. // Make Focused Leave
  361. Focused.SetHasFocus (false, view);
  362. view.FocusDeepest (TabBehavior.TabStop, direction);
  363. // TODO: Temporary hack to make Application.Navigation.FocusChanged work
  364. if (view.Focused is null)
  365. {
  366. Application.Navigation?.SetFocused (view);
  367. }
  368. return true;
  369. }
  370. if (Focused is { })
  371. {
  372. // Leave
  373. Focused.SetHasFocus (false, this);
  374. // Signal that nothing is focused, and callers should try a peer-subview
  375. Focused = null;
  376. }
  377. return false;
  378. }
  379. /// <summary>
  380. /// INTERNAL API to restore focus to the subview that had focus before this view lost focused.
  381. /// </summary>
  382. /// <returns>
  383. /// Returns true if focus was restored to a subview, false otherwise.
  384. /// </returns>
  385. internal bool RestoreFocus (TabBehavior? behavior)
  386. {
  387. if (Focused is null && _subviews?.Count > 0)
  388. {
  389. // TODO: Find the previous focused view and set focus to it
  390. if (PreviouslyMostFocused is { } && PreviouslyMostFocused.TabStop == behavior)
  391. {
  392. SetFocus (PreviouslyMostFocused);
  393. return true;
  394. }
  395. return true;
  396. }
  397. return false;
  398. }
  399. /// <summary>
  400. /// Internal API that causes <paramref name="viewToEnterFocus"/> to enter focus.
  401. /// <paramref name="viewToEnterFocus"/> must be a subview.
  402. /// Recursively sets focus up the superview hierarchy.
  403. /// </summary>
  404. /// <param name="viewToEnterFocus"></param>
  405. /// <returns><see langword="true"/> if <paramref name="viewToEnterFocus"/> got focus.</returns>
  406. private bool SetFocus (View viewToEnterFocus)
  407. {
  408. if (viewToEnterFocus is null)
  409. {
  410. return false;
  411. }
  412. if (!viewToEnterFocus.CanFocus || !viewToEnterFocus.Visible || !viewToEnterFocus.Enabled)
  413. {
  414. return false;
  415. }
  416. // If viewToEnterFocus is already the focused view, don't do anything
  417. if (Focused?._hasFocus == true && Focused == viewToEnterFocus)
  418. {
  419. return false;
  420. }
  421. // If a subview has focus and viewToEnterFocus is the focused view's superview OR viewToEnterFocus is this view,
  422. // then make viewToEnterFocus.HasFocus = true and return
  423. if ((Focused?._hasFocus == true && Focused?.SuperView == viewToEnterFocus) || viewToEnterFocus == this)
  424. {
  425. if (!viewToEnterFocus._hasFocus)
  426. {
  427. viewToEnterFocus._hasFocus = true;
  428. }
  429. // viewToEnterFocus is already focused
  430. return true;
  431. }
  432. // Make sure that viewToEnterFocus is a subview of this view
  433. View c;
  434. for (c = viewToEnterFocus._superView; c != null; c = c._superView)
  435. {
  436. if (c == this)
  437. {
  438. break;
  439. }
  440. }
  441. if (c is null)
  442. {
  443. throw new ArgumentException (@$"The specified view {viewToEnterFocus} is not part of the hierarchy of {this}.");
  444. }
  445. // If a subview has focus, make it leave focus. This will leave focus up the hierarchy.
  446. Focused?.SetHasFocus (false, viewToEnterFocus);
  447. // make viewToEnterFocus Focused and enter focus
  448. View f = Focused;
  449. Focused = viewToEnterFocus;
  450. Focused?.SetHasFocus (true, f, true);
  451. Focused?.FocusDeepest (null, NavigationDirection.Forward);
  452. // Recursively set focus up the superview hierarchy
  453. if (SuperView is { })
  454. {
  455. // BUGBUG: If focus is cancelled at any point, we should stop and restore focus to the previous focused view
  456. SuperView.SetFocus (this);
  457. }
  458. else
  459. {
  460. // BUGBUG: this makes no sense in the new design
  461. // If there is no SuperView, then this is a top-level view
  462. SetFocus (this);
  463. }
  464. // TODO: Temporary hack to make Application.Navigation.FocusChanged work
  465. if (HasFocus && Focused.Focused is null)
  466. {
  467. Application.Navigation?.SetFocused (Focused);
  468. }
  469. // TODO: This is a temporary hack to make overlapped non-Toplevels have a zorder. See also: View.OnDrawContent.
  470. if (viewToEnterFocus is { } && (viewToEnterFocus.TabStop == TabBehavior.TabGroup && viewToEnterFocus.Arrangement.HasFlag (ViewArrangement.Overlapped)))
  471. {
  472. viewToEnterFocus.TabIndex = 0;
  473. }
  474. return true;
  475. }
  476. #if AUTO_CANFOCUS
  477. // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Instead, callers to Add should be explicit about what they want.
  478. // Set to true in Add() to indicate that the view being added to a SuperView has CanFocus=true.
  479. // Makes it so CanFocus will update the SuperView's CanFocus property.
  480. internal bool _addingViewSoCanFocusAlsoUpdatesSuperView;
  481. // Used to cache CanFocus on subviews when CanFocus is set to false so that it can be restored when CanFocus is changed back to true
  482. private bool _oldCanFocus;
  483. #endif
  484. private bool _canFocus;
  485. /// <summary>Gets or sets a value indicating whether this <see cref="View"/> can be focused.</summary>
  486. /// <remarks>
  487. /// <para>
  488. /// <see cref="SuperView"/> must also have <see cref="CanFocus"/> set to <see langword="true"/>.
  489. /// </para>
  490. /// <para>
  491. /// When set to <see langword="false"/>, if an attempt is made to make this view focused, the focus will be set to
  492. /// the next focusable view.
  493. /// </para>
  494. /// <para>
  495. /// When set to <see langword="false"/>, the <see cref="TabIndex"/> will be set to -1.
  496. /// </para>
  497. /// <para>
  498. /// When set to <see langword="false"/>, the values of <see cref="CanFocus"/> and <see cref="TabIndex"/> for all
  499. /// subviews will be cached so that when <see cref="CanFocus"/> is set back to <see langword="true"/>, the subviews
  500. /// will be restored to their previous values.
  501. /// </para>
  502. /// <para>
  503. /// Changing this property to <see langword="true"/> will cause <see cref="TabStop"/> to be set to
  504. /// <see cref="TabBehavior.TabStop"/>" as a convenience. Changing this property to
  505. /// <see langword="false"/> will have no effect on <see cref="TabStop"/>.
  506. /// </para>
  507. /// </remarks>
  508. public bool CanFocus
  509. {
  510. get => _canFocus;
  511. set
  512. {
  513. #if AUTO_CANFOCUS
  514. if (!_addingViewSoCanFocusAlsoUpdatesSuperView && IsInitialized && SuperView?.CanFocus == false && value)
  515. {
  516. throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
  517. }
  518. #endif
  519. if (_canFocus == value)
  520. {
  521. return;
  522. }
  523. _canFocus = value;
  524. #if AUTO_CANFOCUS
  525. switch (_canFocus)
  526. {
  527. case false when _tabIndex > -1:
  528. // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Callers should adjust TabIndex explicitly.
  529. //TabIndex = -1;
  530. break;
  531. case true when SuperView?.CanFocus == false && _addingViewSoCanFocusAlsoUpdatesSuperView:
  532. SuperView.CanFocus = true;
  533. break;
  534. }
  535. #endif
  536. if (TabStop is null && _canFocus)
  537. {
  538. TabStop = TabBehavior.TabStop;
  539. }
  540. if (!_canFocus && SuperView?.Focused == this)
  541. {
  542. SuperView.Focused = null;
  543. }
  544. if (!_canFocus && HasFocus)
  545. {
  546. SetHasFocus (false, this);
  547. SuperView?.RestoreFocus (null);
  548. // If EnsureFocus () didn't set focus to a view, focus the next focusable view in the application
  549. if (SuperView is { Focused: null })
  550. {
  551. SuperView.AdvanceFocus (NavigationDirection.Forward, null);
  552. if (SuperView.Focused is null && Application.Current is { })
  553. {
  554. Application.Current.AdvanceFocus (NavigationDirection.Forward, null);
  555. }
  556. ApplicationOverlapped.BringOverlappedTopToFront ();
  557. }
  558. }
  559. if (_subviews is { } && IsInitialized)
  560. {
  561. #if AUTO_CANFOCUS
  562. // Change the CanFocus of all subviews to the same value as this view
  563. // if the CanFocus of the subview is different from the value being set
  564. foreach (View view in _subviews)
  565. {
  566. if (view.CanFocus != value)
  567. {
  568. if (!value)
  569. {
  570. // Cache the old CanFocus and TabIndex so that they can be restored when CanFocus is changed back to true
  571. view._oldCanFocus = view.CanFocus;
  572. view._oldTabIndex = view._tabIndex;
  573. view.CanFocus = false;
  574. //view._tabIndex = -1;
  575. }
  576. else
  577. {
  578. if (_addingViewSoCanFocusAlsoUpdatesSuperView)
  579. {
  580. view._addingViewSoCanFocusAlsoUpdatesSuperView = true;
  581. }
  582. // Restore the old CanFocus and TabIndex to the values they held before CanFocus was set to false
  583. view.CanFocus = view._oldCanFocus;
  584. view._tabIndex = view._oldTabIndex;
  585. view._addingViewSoCanFocusAlsoUpdatesSuperView = false;
  586. }
  587. }
  588. }
  589. #endif
  590. if (this is Toplevel && Application.Current!.Focused != this)
  591. {
  592. ApplicationOverlapped.BringOverlappedTopToFront ();
  593. }
  594. }
  595. OnCanFocusChanged ();
  596. SetNeedsDisplay ();
  597. }
  598. }
  599. /// <summary>Raised when <see cref="CanFocus"/> has been changed.</summary>
  600. /// <remarks>
  601. /// Raised by the <see cref="OnCanFocusChanged"/> virtual method.
  602. /// </remarks>
  603. public event EventHandler CanFocusChanged;
  604. /// <summary>Returns the currently focused Subview inside this view, or <see langword="null"/> if nothing is focused.</summary>
  605. /// <value>The currently focused Subview.</value>
  606. [CanBeNull]
  607. public View Focused { get; private set; }
  608. /// <summary>
  609. /// Focuses the deepest focusable view in <see cref="View.TabIndexes"/> if one exists. If there are no views in
  610. /// <see cref="View.TabIndexes"/> then the focus is set to the view itself.
  611. /// </summary>
  612. /// <param name="behavior"></param>
  613. /// <param name="direction"></param>
  614. public void FocusDeepest (TabBehavior? behavior, NavigationDirection direction)
  615. {
  616. if (!CanBeVisible (this))
  617. {
  618. return;
  619. }
  620. //View deepest = FindDeepestFocusableView (behavior, direction);
  621. //if (deepest is { })
  622. //{
  623. // deepest.SetFocus ();
  624. //}
  625. if (_tabIndexes is null)
  626. {
  627. SuperView?.SetFocus (this);
  628. return;
  629. }
  630. SetFocus ();
  631. foreach (View view in _tabIndexes)
  632. {
  633. if (view.CanFocus && (behavior is null || view.TabStop == behavior) && view.Visible && view.Enabled)
  634. {
  635. SetFocus (view);
  636. return;
  637. }
  638. }
  639. }
  640. [CanBeNull]
  641. private View FindDeepestFocusableView (TabBehavior? behavior, NavigationDirection direction)
  642. {
  643. var indicies = GetScopedTabIndexes (behavior, direction);
  644. foreach (View v in indicies)
  645. {
  646. if (v.TabIndexes.Count == 0)
  647. {
  648. return v;
  649. }
  650. return v.FindDeepestFocusableView (behavior, direction);
  651. }
  652. return null;
  653. }
  654. /// <summary>Returns a value indicating if this View is currently on Top (Active)</summary>
  655. public bool IsCurrentTop => Application.Current == this;
  656. /// <summary>
  657. /// Returns the most focused Subview in the chain of subviews (the leaf view that has the focus), or
  658. /// <see langword="null"/> if nothing is focused.
  659. /// </summary>
  660. /// <value>The most focused Subview.</value>
  661. public View MostFocused
  662. {
  663. get
  664. {
  665. if (Focused is null)
  666. {
  667. return null;
  668. }
  669. View most = Focused.MostFocused;
  670. if (most is { })
  671. {
  672. return most;
  673. }
  674. return Focused;
  675. }
  676. }
  677. /// <summary>Invoked when the <see cref="CanFocus"/> property from a view is changed.</summary>
  678. /// <remarks>
  679. /// Raises the <see cref="CanFocusChanged"/> event.
  680. /// </remarks>
  681. public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); }
  682. #region Tab/Focus Handling
  683. #nullable enable
  684. private List<View>? _tabIndexes;
  685. // TODO: This should be a get-only property?
  686. // BUGBUG: This returns an AsReadOnly list, but isn't declared as such.
  687. /// <summary>Gets a list of the subviews that are a <see cref="TabStop"/>.</summary>
  688. /// <value>The tabIndexes.</value>
  689. public IList<View> TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty;
  690. /// <summary>
  691. /// Gets TabIndexes that are scoped to the specified behavior and direction. If behavior is null, all TabIndexes are returned.
  692. /// </summary>
  693. /// <param name="behavior"></param>
  694. /// <param name="direction"></param>
  695. /// <returns></returns>GetScopedTabIndexes
  696. private View [] GetScopedTabIndexes (TabBehavior? behavior, NavigationDirection direction)
  697. {
  698. IEnumerable<View>? indicies;
  699. if (behavior.HasValue)
  700. {
  701. indicies = _tabIndexes?.Where (v => v.TabStop == behavior && v is { CanFocus: true, Visible: true, Enabled: true });
  702. }
  703. else
  704. {
  705. indicies = _tabIndexes?.Where (v => v is { CanFocus: true, Visible: true, Enabled: true });
  706. }
  707. if (direction == NavigationDirection.Backward)
  708. {
  709. indicies = indicies?.Reverse ();
  710. }
  711. return indicies?.ToArray () ?? Array.Empty<View> ();
  712. }
  713. private int? _tabIndex; // null indicates the view has not yet been added to TabIndexes
  714. private int? _oldTabIndex;
  715. /// <summary>
  716. /// Indicates the order of the current <see cref="View"/> in <see cref="TabIndexes"/> list.
  717. /// </summary>
  718. /// <remarks>
  719. /// <para>
  720. /// If <see langword="null"/>, the view is not part of the tab order.
  721. /// </para>
  722. /// <para>
  723. /// On set, if <see cref="SuperView"/> is <see langword="null"/> or has not TabStops, <see cref="TabIndex"/> will
  724. /// be set to 0.
  725. /// </para>
  726. /// <para>
  727. /// On set, if <see cref="SuperView"/> has only one TabStop, <see cref="TabIndex"/> will be set to 0.
  728. /// </para>
  729. /// <para>
  730. /// See also <seealso cref="TabStop"/>.
  731. /// </para>
  732. /// </remarks>
  733. public int? TabIndex
  734. {
  735. get => _tabIndex;
  736. // TOOD: This should be a get-only property. Introduce SetTabIndex (int value) (or similar).
  737. set
  738. {
  739. // Once a view is in the tab order, it should not be removed from the tab order; set TabStop to NoStop instead.
  740. Debug.Assert (value >= 0);
  741. Debug.Assert (value is { });
  742. if (SuperView?._tabIndexes is null || SuperView?._tabIndexes.Count == 1)
  743. {
  744. // BUGBUG: Property setters should set the property to the value passed in and not have side effects.
  745. _tabIndex = 0;
  746. return;
  747. }
  748. if (_tabIndex == value && TabIndexes.IndexOf (this) == value)
  749. {
  750. return;
  751. }
  752. _tabIndex = value > SuperView!.TabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 :
  753. value < 0 ? 0 : value;
  754. _tabIndex = GetGreatestTabIndexInSuperView ((int)_tabIndex);
  755. if (SuperView._tabIndexes.IndexOf (this) != _tabIndex)
  756. {
  757. // BUGBUG: we have to use _tabIndexes and not TabIndexes because TabIndexes returns is a read-only version of _tabIndexes
  758. SuperView._tabIndexes.Remove (this);
  759. SuperView._tabIndexes.Insert ((int)_tabIndex, this);
  760. UpdatePeerTabIndexes ();
  761. }
  762. return;
  763. // Updates the <see cref="TabIndex"/>s of the views in the <see cref="SuperView"/>'s to match their order in <see cref="TabIndexes"/>.
  764. void UpdatePeerTabIndexes ()
  765. {
  766. if (SuperView is null)
  767. {
  768. return;
  769. }
  770. var i = 0;
  771. foreach (View superViewTabStop in SuperView._tabIndexes)
  772. {
  773. if (superViewTabStop._tabIndex is null)
  774. {
  775. continue;
  776. }
  777. superViewTabStop._tabIndex = i;
  778. i++;
  779. }
  780. }
  781. }
  782. }
  783. /// <summary>
  784. /// Gets the greatest <see cref="TabIndex"/> of the <see cref="SuperView"/>'s <see cref="TabIndexes"/> that is less
  785. /// than or equal to <paramref name="idx"/>.
  786. /// </summary>
  787. /// <param name="idx"></param>
  788. /// <returns>The minimum of <paramref name="idx"/> and the <see cref="SuperView"/>'s <see cref="TabIndexes"/>.</returns>
  789. private int GetGreatestTabIndexInSuperView (int idx)
  790. {
  791. if (SuperView is null)
  792. {
  793. return 0;
  794. }
  795. var i = 0;
  796. foreach (View superViewTabStop in SuperView._tabIndexes)
  797. {
  798. if (superViewTabStop._tabIndex is null || superViewTabStop == this)
  799. {
  800. continue;
  801. }
  802. i++;
  803. }
  804. return Math.Min (i, idx);
  805. }
  806. private TabBehavior? _tabStop;
  807. /// <summary>
  808. /// Gets or sets the behavior of <see cref="AdvanceFocus"/> for keyboard navigation.
  809. /// </summary>
  810. /// <remarks>
  811. /// <para>
  812. /// If <see langword="null"/> the tab stop has not been set and setting <see cref="CanFocus"/> to true will set it
  813. /// to
  814. /// <see cref="TabBehavior.TabStop"/>.
  815. /// </para>
  816. /// <para>
  817. /// TabStop is independent of <see cref="CanFocus"/>. If <see cref="CanFocus"/> is <see langword="false"/>, the
  818. /// view will not gain
  819. /// focus even if this property is set and vice-versa.
  820. /// </para>
  821. /// <para>
  822. /// The default <see cref="TabBehavior.TabStop"/> keys are <see cref="Application.NextTabKey"/> (<c>Key.Tab</c>) and <see cref="Application.PrevTabKey"/> (<c>Key>Tab.WithShift</c>).
  823. /// </para>
  824. /// <para>
  825. /// The default <see cref="TabBehavior.TabGroup"/> keys are <see cref="Application.NextTabGroupKey"/> (<c>Key.F6</c>) and <see cref="Application.PrevTabGroupKey"/> (<c>Key>Key.F6.WithShift</c>).
  826. /// </para>
  827. /// </remarks>
  828. public TabBehavior? TabStop
  829. {
  830. get => _tabStop;
  831. set
  832. {
  833. if (_tabStop == value)
  834. {
  835. return;
  836. }
  837. Debug.Assert (value is { });
  838. if (_tabStop is null && TabIndex is null)
  839. {
  840. // This view has not yet been added to TabIndexes (TabStop has not been set previously).
  841. TabIndex = GetGreatestTabIndexInSuperView (SuperView is { } ? SuperView._tabIndexes.Count : 0);
  842. }
  843. _tabStop = value;
  844. }
  845. }
  846. #endregion Tab/Focus Handling
  847. }