// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Input { using System; using System.Collections.Generic; using System.Linq; using Perspex.VisualTree; using Splat; public class KeyboardNavigation : IKeyboardNavigation { public static readonly PerspexProperty TabNavigationProperty = PerspexProperty.RegisterAttached("TabNavigation"); public static readonly PerspexProperty TabOnceActiveElementProperty = PerspexProperty.RegisterAttached("TabOnceActiveElement"); public static IKeyboardNavigation Instance { get { return Locator.Current.GetService(); } } public static KeyboardNavigationMode GetTabNavigation(InputElement element) { return element.GetValue(TabNavigationProperty); } public static void SetTabNavigation(InputElement element, KeyboardNavigationMode value) { element.SetValue(TabNavigationProperty, value); } public static IInputElement GetTabOnceActiveElement(InputElement element) { return element.GetValue(TabOnceActiveElementProperty); } public static void SetTabOnceActiveElement(InputElement element, IInputElement value) { element.SetValue(TabOnceActiveElementProperty, value); } public IInputElement GetNextInTabOrder(IInputElement element) { Contract.Requires(element != null); var container = element.GetVisualParent(); if (container != null) { var mode = GetTabNavigation((InputElement)container); switch (mode) { case KeyboardNavigationMode.Continue: return GetNextInContainer(element, container) ?? GetFirstInNextContainer(container); case KeyboardNavigationMode.Cycle: return GetNextInContainer(element, container) ?? GetDescendents(container).FirstOrDefault(); default: return GetFirstInNextContainer(container); } } else { return GetDescendents(element).FirstOrDefault(); } } public IInputElement GetPreviousInTabOrder(IInputElement element) { Contract.Requires(element != null); var container = element.GetVisualParent(); if (container != null) { var mode = GetTabNavigation((InputElement)container); switch (mode) { case KeyboardNavigationMode.Continue: return GetPreviousInContainer(element, container) ?? GetLastInPreviousContainer(element); case KeyboardNavigationMode.Cycle: return GetPreviousInContainer(element, container) ?? GetDescendents(container).LastOrDefault(); default: return GetLastInPreviousContainer(container); } } else { return GetDescendents(element).LastOrDefault(); } } public void TabNext(IInputElement element) { Contract.Requires(element != null); var next = GetNextInTabOrder(element); if (next != null) { TabTo(next); } } public void TabPrevious(IInputElement element) { Contract.Requires(element != null); var next = GetPreviousInTabOrder(element); if (next != null) { TabTo(next); } } public void TabTo(IInputElement element) { Contract.Requires(element != null); FocusManager.Instance.Focus(element, true); } private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible; private static bool CanFocusDescendent(IInputElement e) => e.IsEnabledCore && e.IsVisible; private static IEnumerable GetDescendents(IInputElement element) { var mode = GetTabNavigation((InputElement)element); if (mode == KeyboardNavigationMode.Never) { yield break; } var children = element.GetVisualChildren().OfType(); if (mode == KeyboardNavigationMode.Once) { var active = GetTabOnceActiveElement((InputElement)element); if (active != null) { yield return active; yield break; } else { children = children.Take(1); } } foreach (var child in children) { if (CanFocus(child)) { yield return child; } if (CanFocusDescendent(child)) { foreach (var descendent in GetDescendents(child)) { yield return descendent; } } } } private static IInputElement GetNextInContainer(IInputElement element, IInputElement container) { var descendent = GetDescendents(element).FirstOrDefault(); if (descendent != null) { return descendent; } else if (container != null) { var sibling = container.GetVisualChildren() .OfType() .Where(CanFocus) .SkipWhile(x => x != element) .Skip(1) .FirstOrDefault(); if (sibling != null) { return sibling; } } return null; } private static IInputElement GetPreviousInContainer(IInputElement element, IInputElement container) { return container.GetVisualChildren() .OfType() .Where(CanFocus) .TakeWhile(x => x != element) .LastOrDefault(); } private static IInputElement GetFirstInNextContainer(IInputElement container) { var parent = container.GetVisualParent(); IInputElement next = null; if (parent != null) { var sibling = parent.GetVisualChildren() .OfType() .Where(CanFocusDescendent) .SkipWhile(x => x != container) .Skip(1) .FirstOrDefault(); if (sibling != null) { if (CanFocus(sibling)) { next = sibling; } else { next = GetDescendents(sibling).FirstOrDefault(); } } if (next == null) { next = GetFirstInNextContainer(parent); } } else { next = GetDescendents(container).FirstOrDefault(); } return next; } private static IInputElement GetLastInPreviousContainer(IInputElement container) { var parent = container.GetVisualParent(); IInputElement next = null; if (parent != null) { var sibling = parent.GetVisualChildren() .OfType() .Where(CanFocusDescendent) .TakeWhile(x => x != container) .LastOrDefault(); if (sibling != null) { if (CanFocus(sibling)) { next = sibling; } else { next = GetDescendents(sibling).LastOrDefault(); } } if (next == null) { next = GetLastInPreviousContainer(parent); } } else { next = GetDescendents(container).LastOrDefault(); } return next; } } }