PageRenderTime 134ms CodeModel.GetById 51ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Avalonia.Input/Navigation/TabNavigation.cs

https://gitlab.com/kush/Avalonia
C# | 280 lines | 198 code | 32 blank | 50 comment | 53 complexity | c1990b29e5fff8b677b98775a41cd210 MD5 | raw file
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using Avalonia.VisualTree;
  7. namespace Avalonia.Input.Navigation
  8. {
  9. /// <summary>
  10. /// The implementation for default tab navigation.
  11. /// </summary>
  12. internal static class TabNavigation
  13. {
  14. /// <summary>
  15. /// Gets the next control in the specified tab direction.
  16. /// </summary>
  17. /// <param name="element">The element.</param>
  18. /// <param name="direction">The tab direction. Must be Next or Previous.</param>
  19. /// <param name="outsideElement">
  20. /// If true will not descend into <paramref name="element"/> to find next control.
  21. /// </param>
  22. /// <returns>
  23. /// The next element in the specified direction, or null if <paramref name="element"/>
  24. /// was the last in the requested direction.
  25. /// </returns>
  26. public static IInputElement GetNextInTabOrder(
  27. IInputElement element,
  28. NavigationDirection direction,
  29. bool outsideElement = false)
  30. {
  31. Contract.Requires<ArgumentNullException>(element != null);
  32. Contract.Requires<ArgumentException>(
  33. direction == NavigationDirection.Next ||
  34. direction == NavigationDirection.Previous);
  35. var container = element.GetVisualParent<IInputElement>();
  36. if (container != null)
  37. {
  38. var mode = KeyboardNavigation.GetTabNavigation((InputElement)container);
  39. switch (mode)
  40. {
  41. case KeyboardNavigationMode.Continue:
  42. return GetNextInContainer(element, container, direction, outsideElement) ??
  43. GetFirstInNextContainer(element, element, direction);
  44. case KeyboardNavigationMode.Cycle:
  45. return GetNextInContainer(element, container, direction, outsideElement) ??
  46. GetFocusableDescendant(container, direction);
  47. case KeyboardNavigationMode.Contained:
  48. return GetNextInContainer(element, container, direction, outsideElement);
  49. default:
  50. return GetFirstInNextContainer(element, container, direction);
  51. }
  52. }
  53. else
  54. {
  55. return GetFocusableDescendants(element, direction).FirstOrDefault();
  56. }
  57. }
  58. /// <summary>
  59. /// Gets the first or last focusable descendant of the specified element.
  60. /// </summary>
  61. /// <param name="container">The element.</param>
  62. /// <param name="direction">The direction to search.</param>
  63. /// <returns>The element or null if not found.##</returns>
  64. private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
  65. {
  66. return direction == NavigationDirection.Next ?
  67. GetFocusableDescendants(container, direction).FirstOrDefault() :
  68. GetFocusableDescendants(container, direction).LastOrDefault();
  69. }
  70. /// <summary>
  71. /// Gets the focusable descendants of the specified element.
  72. /// </summary>
  73. /// <param name="element">The element.</param>
  74. /// <param name="direction">The tab direction. Must be Next or Previous.</param>
  75. /// <returns>The element's focusable descendants.</returns>
  76. private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element, NavigationDirection direction)
  77. {
  78. var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
  79. if (mode == KeyboardNavigationMode.None)
  80. {
  81. yield break;
  82. }
  83. var children = element.GetVisualChildren().OfType<IInputElement>();
  84. if (mode == KeyboardNavigationMode.Once)
  85. {
  86. var active = KeyboardNavigation.GetTabOnceActiveElement((InputElement)element);
  87. if (active != null)
  88. {
  89. yield return active;
  90. yield break;
  91. }
  92. else
  93. {
  94. children = children.Take(1);
  95. }
  96. }
  97. foreach (var child in children)
  98. {
  99. var customNext = GetCustomNext(child, direction);
  100. if (customNext.handled)
  101. {
  102. yield return customNext.next;
  103. }
  104. else
  105. {
  106. if (child.CanFocus())
  107. {
  108. yield return child;
  109. }
  110. if (child.CanFocusDescendants())
  111. {
  112. foreach (var descendant in GetFocusableDescendants(child, direction))
  113. {
  114. yield return descendant;
  115. }
  116. }
  117. }
  118. }
  119. }
  120. /// <summary>
  121. /// Gets the next item that should be focused in the specified container.
  122. /// </summary>
  123. /// <param name="element">The starting element/</param>
  124. /// <param name="container">The container.</param>
  125. /// <param name="direction">The direction.</param>
  126. /// <param name="outsideElement">
  127. /// If true will not descend into <paramref name="element"/> to find next control.
  128. /// </param>
  129. /// <returns>The next element, or null if the element is the last.</returns>
  130. private static IInputElement GetNextInContainer(
  131. IInputElement element,
  132. IInputElement container,
  133. NavigationDirection direction,
  134. bool outsideElement)
  135. {
  136. if (direction == NavigationDirection.Next && !outsideElement)
  137. {
  138. var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
  139. if (descendant != null)
  140. {
  141. return descendant;
  142. }
  143. }
  144. if (container != null)
  145. {
  146. var navigable = container as INavigableContainer;
  147. // TODO: Do a spatial search here if the container doesn't implement
  148. // INavigableContainer.
  149. if (navigable != null)
  150. {
  151. while (element != null)
  152. {
  153. element = navigable.GetControl(direction, element, false);
  154. if (element != null && element.CanFocus())
  155. {
  156. break;
  157. }
  158. }
  159. }
  160. else
  161. {
  162. // TODO: Do a spatial search here if the container doesn't implement
  163. // INavigableContainer.
  164. element = null;
  165. }
  166. if (element != null && direction == NavigationDirection.Previous)
  167. {
  168. var descendant = GetFocusableDescendants(element, direction).LastOrDefault();
  169. if (descendant != null)
  170. {
  171. return descendant;
  172. }
  173. }
  174. return element;
  175. }
  176. return null;
  177. }
  178. /// <summary>
  179. /// Gets the first item that should be focused in the next container.
  180. /// </summary>
  181. /// <param name="element">The element being navigated away from.</param>
  182. /// <param name="container">The container.</param>
  183. /// <param name="direction">The direction of the search.</param>
  184. /// <returns>The first element, or null if there are no more elements.</returns>
  185. private static IInputElement GetFirstInNextContainer(
  186. IInputElement element,
  187. IInputElement container,
  188. NavigationDirection direction)
  189. {
  190. var parent = container.GetVisualParent<IInputElement>();
  191. IInputElement next = null;
  192. if (parent != null)
  193. {
  194. if (direction == NavigationDirection.Previous && parent.CanFocus())
  195. {
  196. return parent;
  197. }
  198. var allSiblings = parent.GetVisualChildren()
  199. .OfType<IInputElement>()
  200. .Where(FocusExtensions.CanFocusDescendants);
  201. var siblings = direction == NavigationDirection.Next ?
  202. allSiblings.SkipWhile(x => x != container).Skip(1) :
  203. allSiblings.TakeWhile(x => x != container).Reverse();
  204. foreach (var sibling in siblings)
  205. {
  206. var customNext = GetCustomNext(sibling, direction);
  207. if (customNext.handled)
  208. {
  209. return customNext.next;
  210. }
  211. if (sibling.CanFocus())
  212. {
  213. return sibling;
  214. }
  215. else
  216. {
  217. next = direction == NavigationDirection.Next ?
  218. GetFocusableDescendants(sibling, direction).FirstOrDefault() :
  219. GetFocusableDescendants(sibling, direction).LastOrDefault();
  220. if(next != null)
  221. {
  222. return next;
  223. }
  224. }
  225. }
  226. if (next == null)
  227. {
  228. next = GetFirstInNextContainer(element, parent, direction);
  229. }
  230. }
  231. else
  232. {
  233. next = direction == NavigationDirection.Next ?
  234. GetFocusableDescendants(container, direction).FirstOrDefault() :
  235. GetFocusableDescendants(container, direction).LastOrDefault();
  236. }
  237. return next;
  238. }
  239. private static (bool handled, IInputElement next) GetCustomNext(IInputElement element, NavigationDirection direction)
  240. {
  241. if (element is ICustomKeyboardNavigation custom)
  242. {
  243. return custom.GetNext(element, direction);
  244. }
  245. return (false, null);
  246. }
  247. }
  248. }