Application.Navigation.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #nullable enable
  2. using System.Security.Cryptography;
  3. namespace Terminal.Gui;
  4. /// <summary>
  5. /// Helper class for <see cref="Application"/> navigation.
  6. /// </summary>
  7. internal static class ApplicationNavigation
  8. {
  9. /// <summary>
  10. /// Gets the deepest focused subview of the specified <paramref name="view"/>.
  11. /// </summary>
  12. /// <param name="view"></param>
  13. /// <returns></returns>
  14. internal static View? GetDeepestFocusedSubview (View? view)
  15. {
  16. if (view is null)
  17. {
  18. return null;
  19. }
  20. foreach (View v in view.Subviews)
  21. {
  22. if (v.HasFocus)
  23. {
  24. return GetDeepestFocusedSubview (v);
  25. }
  26. }
  27. return view;
  28. }
  29. /// <summary>
  30. /// Sets the focus to the next view in the specified direction within the provided list of views.
  31. /// If the end of the list is reached, the focus wraps around to the first view in the list.
  32. /// The method considers the current focused view (`Application.Current`) and attempts to move the focus
  33. /// to the next view in the specified direction. If the focus cannot be set to the next view, it wraps around
  34. /// to the first view in the list.
  35. /// </summary>
  36. /// <param name="viewsInTabIndexes"></param>
  37. /// <param name="direction"></param>
  38. internal static void SetFocusToNextViewWithWrap (IEnumerable<View>? viewsInTabIndexes, NavigationDirection direction)
  39. {
  40. if (viewsInTabIndexes is null)
  41. {
  42. return;
  43. }
  44. bool foundCurrentView = false;
  45. bool focusSet = false;
  46. IEnumerable<View> indexes = viewsInTabIndexes as View [] ?? viewsInTabIndexes.ToArray ();
  47. int viewCount = indexes.Count ();
  48. int currentIndex = 0;
  49. foreach (View view in indexes)
  50. {
  51. if (view == Application.Current)
  52. {
  53. foundCurrentView = true;
  54. }
  55. else if (foundCurrentView && !focusSet)
  56. {
  57. // One of the views is Current, but view is not. Attempt to Advance...
  58. Application.Current!.SuperView?.AdvanceFocus (direction);
  59. // QUESTION: AdvanceFocus returns false AND sets Focused to null if no view was found to advance to. Should't we only set focusProcessed if it returned true?
  60. focusSet = true;
  61. if (Application.Current.SuperView?.Focused != Application.Current)
  62. {
  63. return;
  64. }
  65. // Either AdvanceFocus didn't set focus or the view it set focus to is not current...
  66. // continue...
  67. }
  68. currentIndex++;
  69. if (foundCurrentView && !focusSet && currentIndex == viewCount)
  70. {
  71. // One of the views is Current AND AdvanceFocus didn't set focus AND we are at the last view in the list...
  72. // This means we should wrap around to the first view in the list.
  73. indexes.First ().SetFocus ();
  74. }
  75. }
  76. }
  77. /// <summary>
  78. /// Moves the focus to the next focusable view.
  79. /// Honors <see cref="ViewArrangement.Overlapped"/> and will only move to the next subview
  80. /// if the current and next subviews are not overlapped.
  81. /// </summary>
  82. internal static void MoveNextView ()
  83. {
  84. View? old = GetDeepestFocusedSubview (Application.Current!.Focused);
  85. if (!Application.Current.AdvanceFocus (NavigationDirection.Forward))
  86. {
  87. Application.Current.AdvanceFocus (NavigationDirection.Forward);
  88. }
  89. if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
  90. {
  91. old?.SetNeedsDisplay ();
  92. Application.Current.Focused?.SetNeedsDisplay ();
  93. }
  94. else
  95. {
  96. SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward);
  97. }
  98. }
  99. /// <summary>
  100. /// Moves the focus to the next <see cref="Toplevel"/> subview or the next subview that has <see cref="ApplicationOverlapped.OverlappedTop"/> set.
  101. /// </summary>
  102. internal static void MoveNextViewOrTop ()
  103. {
  104. if (ApplicationOverlapped.OverlappedTop is null)
  105. {
  106. Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top;
  107. if (!Application.Current.AdvanceFocus (NavigationDirection.Forward))
  108. {
  109. Application.Current.AdvanceFocus (NavigationDirection.Forward);
  110. }
  111. if (top != Application.Current.Focused && top != Application.Current.Focused?.Focused)
  112. {
  113. top?.SetNeedsDisplay ();
  114. Application.Current.Focused?.SetNeedsDisplay ();
  115. }
  116. else
  117. {
  118. SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward);
  119. }
  120. //top!.AdvanceFocus (NavigationDirection.Forward);
  121. //if (top.Focused is null)
  122. //{
  123. // top.AdvanceFocus (NavigationDirection.Forward);
  124. //}
  125. //top.SetNeedsDisplay ();
  126. ApplicationOverlapped.BringOverlappedTopToFront ();
  127. }
  128. else
  129. {
  130. ApplicationOverlapped.OverlappedMoveNext ();
  131. }
  132. }
  133. /// <summary>
  134. /// Moves the focus to the next view. Honors <see cref="ViewArrangement.Overlapped"/> and will only move to the next subview
  135. /// if the current and next subviews are not overlapped.
  136. /// </summary>
  137. internal static void MovePreviousView ()
  138. {
  139. View? old = GetDeepestFocusedSubview (Application.Current!.Focused);
  140. if (!Application.Current.AdvanceFocus (NavigationDirection.Backward))
  141. {
  142. Application.Current.AdvanceFocus (NavigationDirection.Backward);
  143. }
  144. if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused)
  145. {
  146. old?.SetNeedsDisplay ();
  147. Application.Current.Focused?.SetNeedsDisplay ();
  148. }
  149. else
  150. {
  151. SetFocusToNextViewWithWrap (Application.Current.SuperView?.TabIndexes?.Reverse (), NavigationDirection.Backward);
  152. }
  153. }
  154. internal static void MovePreviousViewOrTop ()
  155. {
  156. if (ApplicationOverlapped.OverlappedTop is null)
  157. {
  158. Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top;
  159. top!.AdvanceFocus (NavigationDirection.Backward);
  160. if (top.Focused is null)
  161. {
  162. top.AdvanceFocus (NavigationDirection.Backward);
  163. }
  164. top.SetNeedsDisplay ();
  165. ApplicationOverlapped.BringOverlappedTopToFront ();
  166. }
  167. else
  168. {
  169. ApplicationOverlapped.OverlappedMovePrevious ();
  170. }
  171. }
  172. }