// ----------------------------------------------------------------------- // // 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; using Perspex.Interactivity; /// /// Manages focus for the application. /// public class FocusManager : IFocusManager { /// /// The focus scopes in which the focus is currently defined. /// private Dictionary focusScopes = new Dictionary(); /// /// Initializes a new instance of the class. /// public FocusManager() { InputElement.PointerPressedEvent.AddClassHandler( typeof(IInputElement), new EventHandler(this.OnPreviewPointerPressed), RoutingStrategies.Tunnel); } /// /// Gets the instance of the . /// public static IFocusManager Instance { get { return Locator.Current.GetService(); } } /// /// Gets the currently focused . /// public IInputElement Current { get { return KeyboardDevice.Instance.FocusedElement; } } /// /// Gets the current focus scope. /// public IFocusScope Scope { get; private set; } /// /// Focuses a control. /// /// The control to focus. /// /// Whether the control was focused by a keypress (e.g. the Tab key). /// public void Focus(IInputElement control, bool keyboardNavigated = false) { if (control != null) { var scope = GetFocusScopeAncestors(control) .FirstOrDefault(); if (scope != null) { this.Scope = scope; this.SetFocusedElement(scope, control, keyboardNavigated); } } else if (this.Current != null) { // If control is null, set focus to the topmost focus scope. foreach (var scope in GetFocusScopeAncestors(this.Current).Reverse().ToList()) { IInputElement element; if (this.focusScopes.TryGetValue(scope, out element)) { this.Focus(element, keyboardNavigated); break; } } } } /// /// Sets the currently focused element in the specified scope. /// /// The focus scope. /// The element to focus. May be null. /// /// Whether the control was focused by a keypress (e.g. the Tab key). /// /// /// If the specified scope is the current then the keyboard focus /// will change. /// public void SetFocusedElement( IFocusScope scope, IInputElement element, bool keyboardNavigated = false) { Contract.Requires(scope != null); this.focusScopes[scope] = element; if (this.Scope == scope) { KeyboardDevice.Instance.SetFocusedElement(element, keyboardNavigated); } } /// /// Notifies the focus manager of a change in focus scope. /// /// The new focus scope. public void SetFocusScope(IFocusScope scope) { Contract.Requires(scope != null); IInputElement e; if (!this.focusScopes.TryGetValue(scope, out e)) { // TODO: Make this do something useful, i.e. select the first focusable // control, select a control that the user has specified to have default // focus etc. e = scope as IInputElement; this.focusScopes.Add(scope, e); } this.Scope = scope; this.Focus(e); } /// /// Checks if the specified element can be focused. /// /// The element. /// True if the element can be focused. private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible; /// /// Gets the focus scope ancestors of the specified control, traversing popups. /// /// The control. /// The focus scopes. private static IEnumerable GetFocusScopeAncestors(IInputElement control) { while (control != null) { var scope = control as IFocusScope; if (scope != null) { yield return scope; } control = control.GetVisualParent() ?? ((control as IHostedVisualTreeRoot)?.Host as IInputElement); } } /// /// Global handler for pointer pressed events. /// /// The event sender. /// The event args. private void OnPreviewPointerPressed(object sender, RoutedEventArgs e) { if (sender == e.Source) { var ev = (PointerPressEventArgs)e; var element = (ev.Device.Captured as IInputElement) ?? (e.Source as IInputElement); if (element == null || !CanFocus(element)) { element = element.GetSelfAndVisualAncestors() .OfType() .FirstOrDefault(x => CanFocus(x)); } if (element != null) { this.Focus(element); } } } } }