|
|
@@ -18,13 +18,17 @@ namespace Avalonia.Input.Navigation
|
|
|
/// </summary>
|
|
|
/// <param name="element">The element.</param>
|
|
|
/// <param name="direction">The tab direction. Must be Next or Previous.</param>
|
|
|
+ /// <param name="outsideElement">
|
|
|
+ /// If true will not descend into <paramref name="element"/> to find next control.
|
|
|
+ /// </param>
|
|
|
/// <returns>
|
|
|
/// The next element in the specified direction, or null if <paramref name="element"/>
|
|
|
/// was the last in the requested direction.
|
|
|
/// </returns>
|
|
|
public static IInputElement GetNextInTabOrder(
|
|
|
IInputElement element,
|
|
|
- NavigationDirection direction)
|
|
|
+ NavigationDirection direction,
|
|
|
+ bool outsideElement = false)
|
|
|
{
|
|
|
Contract.Requires<ArgumentNullException>(element != null);
|
|
|
Contract.Requires<ArgumentException>(
|
|
|
@@ -40,20 +44,20 @@ namespace Avalonia.Input.Navigation
|
|
|
switch (mode)
|
|
|
{
|
|
|
case KeyboardNavigationMode.Continue:
|
|
|
- return GetNextInContainer(element, container, direction) ??
|
|
|
- GetFirstInNextContainer(element, direction);
|
|
|
+ return GetNextInContainer(element, container, direction, outsideElement) ??
|
|
|
+ GetFirstInNextContainer(element, element, direction);
|
|
|
case KeyboardNavigationMode.Cycle:
|
|
|
- return GetNextInContainer(element, container, direction) ??
|
|
|
+ return GetNextInContainer(element, container, direction, outsideElement) ??
|
|
|
GetFocusableDescendant(container, direction);
|
|
|
case KeyboardNavigationMode.Contained:
|
|
|
- return GetNextInContainer(element, container, direction);
|
|
|
+ return GetNextInContainer(element, container, direction, outsideElement);
|
|
|
default:
|
|
|
- return GetFirstInNextContainer(container, direction);
|
|
|
+ return GetFirstInNextContainer(element, container, direction);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- return GetFocusableDescendants(element).FirstOrDefault();
|
|
|
+ return GetFocusableDescendants(element, direction).FirstOrDefault();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -66,16 +70,17 @@ namespace Avalonia.Input.Navigation
|
|
|
private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
|
|
|
{
|
|
|
return direction == NavigationDirection.Next ?
|
|
|
- GetFocusableDescendants(container).FirstOrDefault() :
|
|
|
- GetFocusableDescendants(container).LastOrDefault();
|
|
|
+ GetFocusableDescendants(container, direction).FirstOrDefault() :
|
|
|
+ GetFocusableDescendants(container, direction).LastOrDefault();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets the focusable descendants of the specified element.
|
|
|
/// </summary>
|
|
|
/// <param name="element">The element.</param>
|
|
|
+ /// <param name="direction">The tab direction. Must be Next or Previous.</param>
|
|
|
/// <returns>The element's focusable descendants.</returns>
|
|
|
- private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element)
|
|
|
+ private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element, NavigationDirection direction)
|
|
|
{
|
|
|
var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
|
|
|
|
|
|
@@ -103,16 +108,25 @@ namespace Avalonia.Input.Navigation
|
|
|
|
|
|
foreach (var child in children)
|
|
|
{
|
|
|
- if (child.CanFocus())
|
|
|
+ var customNext = GetCustomNext(child, direction);
|
|
|
+
|
|
|
+ if (customNext.handled)
|
|
|
{
|
|
|
- yield return child;
|
|
|
+ yield return customNext.next;
|
|
|
}
|
|
|
-
|
|
|
- if (child.CanFocusDescendants())
|
|
|
+ else
|
|
|
{
|
|
|
- foreach (var descendant in GetFocusableDescendants(child))
|
|
|
+ if (child.CanFocus())
|
|
|
{
|
|
|
- yield return descendant;
|
|
|
+ yield return child;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (child.CanFocusDescendants())
|
|
|
+ {
|
|
|
+ foreach (var descendant in GetFocusableDescendants(child, direction))
|
|
|
+ {
|
|
|
+ yield return descendant;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -124,15 +138,19 @@ namespace Avalonia.Input.Navigation
|
|
|
/// <param name="element">The starting element/</param>
|
|
|
/// <param name="container">The container.</param>
|
|
|
/// <param name="direction">The direction.</param>
|
|
|
+ /// <param name="outsideElement">
|
|
|
+ /// If true will not descend into <paramref name="element"/> to find next control.
|
|
|
+ /// </param>
|
|
|
/// <returns>The next element, or null if the element is the last.</returns>
|
|
|
private static IInputElement GetNextInContainer(
|
|
|
IInputElement element,
|
|
|
IInputElement container,
|
|
|
- NavigationDirection direction)
|
|
|
+ NavigationDirection direction,
|
|
|
+ bool outsideElement)
|
|
|
{
|
|
|
- if (direction == NavigationDirection.Next)
|
|
|
+ if (direction == NavigationDirection.Next && !outsideElement)
|
|
|
{
|
|
|
- var descendant = GetFocusableDescendants(element).FirstOrDefault();
|
|
|
+ var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
|
|
|
|
|
|
if (descendant != null)
|
|
|
{
|
|
|
@@ -167,7 +185,7 @@ namespace Avalonia.Input.Navigation
|
|
|
|
|
|
if (element != null && direction == NavigationDirection.Previous)
|
|
|
{
|
|
|
- var descendant = GetFocusableDescendants(element).LastOrDefault();
|
|
|
+ var descendant = GetFocusableDescendants(element, direction).LastOrDefault();
|
|
|
|
|
|
if (descendant != null)
|
|
|
{
|
|
|
@@ -184,10 +202,12 @@ namespace Avalonia.Input.Navigation
|
|
|
/// <summary>
|
|
|
/// Gets the first item that should be focused in the next container.
|
|
|
/// </summary>
|
|
|
+ /// <param name="element">The element being navigated away from.</param>
|
|
|
/// <param name="container">The container.</param>
|
|
|
/// <param name="direction">The direction of the search.</param>
|
|
|
/// <returns>The first element, or null if there are no more elements.</returns>
|
|
|
private static IInputElement GetFirstInNextContainer(
|
|
|
+ IInputElement element,
|
|
|
IInputElement container,
|
|
|
NavigationDirection direction)
|
|
|
{
|
|
|
@@ -210,6 +230,13 @@ namespace Avalonia.Input.Navigation
|
|
|
|
|
|
if (sibling != null)
|
|
|
{
|
|
|
+ var customNext = GetCustomNext(sibling, direction);
|
|
|
+
|
|
|
+ if (customNext.handled)
|
|
|
+ {
|
|
|
+ return customNext.next;
|
|
|
+ }
|
|
|
+
|
|
|
if (sibling.CanFocus())
|
|
|
{
|
|
|
next = sibling;
|
|
|
@@ -217,24 +244,34 @@ namespace Avalonia.Input.Navigation
|
|
|
else
|
|
|
{
|
|
|
next = direction == NavigationDirection.Next ?
|
|
|
- GetFocusableDescendants(sibling).FirstOrDefault() :
|
|
|
- GetFocusableDescendants(sibling).LastOrDefault();
|
|
|
+ GetFocusableDescendants(sibling, direction).FirstOrDefault() :
|
|
|
+ GetFocusableDescendants(sibling, direction).LastOrDefault();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (next == null)
|
|
|
{
|
|
|
- next = GetFirstInNextContainer(parent, direction);
|
|
|
+ next = GetFirstInNextContainer(element, parent, direction);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
next = direction == NavigationDirection.Next ?
|
|
|
- GetFocusableDescendants(container).FirstOrDefault() :
|
|
|
- GetFocusableDescendants(container).LastOrDefault();
|
|
|
+ GetFocusableDescendants(container, direction).FirstOrDefault() :
|
|
|
+ GetFocusableDescendants(container, direction).LastOrDefault();
|
|
|
}
|
|
|
|
|
|
return next;
|
|
|
}
|
|
|
+
|
|
|
+ private static (bool handled, IInputElement next) GetCustomNext(IInputElement element, NavigationDirection direction)
|
|
|
+ {
|
|
|
+ if (element is ICustomKeyboardNavigation custom)
|
|
|
+ {
|
|
|
+ return custom.GetNext(element, direction);
|
|
|
+ }
|
|
|
+
|
|
|
+ return (false, null);
|
|
|
+ }
|
|
|
}
|
|
|
}
|