// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq; using Avalonia.Interactivity; using Avalonia.VisualTree; namespace Avalonia.Input { /// /// Manages focus for the application. /// public class FocusManager : IFocusManager { /// /// The focus scopes in which the focus is currently defined. /// private readonly Dictionary _focusScopes = new Dictionary(); /// /// Initializes a new instance of the class. /// static FocusManager() { InputElement.PointerPressedEvent.AddClassHandler( typeof(IInputElement), new EventHandler(OnPreviewPointerPressed), RoutingStrategies.Tunnel); } /// /// Gets the instance of the . /// public static IFocusManager Instance => AvaloniaLocator.Current.GetService(); /// /// Gets the currently focused . /// public IInputElement Current => KeyboardDevice.Instance?.FocusedElement; /// /// Gets the current focus scope. /// public IFocusScope Scope { get; private set; } /// /// Focuses a control. /// /// The control to focus. /// The method by which focus was changed. /// Any input modifiers active at the time of focus. public void Focus( IInputElement control, NavigationMethod method = NavigationMethod.Unspecified, InputModifiers modifiers = InputModifiers.None) { if (control != null) { var scope = GetFocusScopeAncestors(control) .FirstOrDefault(); if (scope != null) { Scope = scope; SetFocusedElement(scope, control, method, modifiers); } } else if (Current != null) { // If control is null, set focus to the topmost focus scope. foreach (var scope in GetFocusScopeAncestors(Current).Reverse().ToList()) { IInputElement element; if (_focusScopes.TryGetValue(scope, out element) && element != null) { Focus(element, method); return; } } // Couldn't find a focus scope, clear focus. SetFocusedElement(Scope, null); } } /// /// Sets the currently focused element in the specified scope. /// /// The focus scope. /// The element to focus. May be null. /// The method by which focus was changed. /// Any input modifiers active at the time of focus. /// /// If the specified scope is the current then the keyboard focus /// will change. /// public void SetFocusedElement( IFocusScope scope, IInputElement element, NavigationMethod method = NavigationMethod.Unspecified, InputModifiers modifiers = InputModifiers.None) { Contract.Requires(scope != null); _focusScopes[scope] = element; if (Scope == scope) { KeyboardDevice.Instance?.SetFocusedElement(element, method, modifiers); } } /// /// 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 (!_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; _focusScopes.Add(scope, e); } Scope = scope; 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.IsEffectivelyEnabled && 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 static void OnPreviewPointerPressed(object sender, RoutedEventArgs e) { var ev = (PointerPressedEventArgs)e; if (sender == e.Source && ev.MouseButton == MouseButton.Left) { var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement); if (element == null || !CanFocus(element)) { element = element.GetSelfAndVisualAncestors() .OfType() .FirstOrDefault(CanFocus); } if (element != null) { Instance?.Focus(element, NavigationMethod.Pointer, ev.InputModifiers); } } } } }