@ -1,7 +1,4 @@
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Runtime.CompilerServices ;
using Avalonia.Interactivity ;
using Avalonia.Metadata ;
using Avalonia.VisualTree ;
@ -15,10 +12,16 @@ namespace Avalonia.Input
public class FocusManager : IFocusManager
{
/// <summary>
/// The focus scopes in which the focus is currently defined .
/// Private attached property for storing the currently focused element in a focus scope .
/// </summary>
private readonly ConditionalWeakTable < IFocusScope , IInputElement ? > _f ocusScopes =
new ConditionalWeakTable < IFocusScope , IInputElement ? > ( ) ;
/// <remarks>
/// This property is set on the control which defines a focus scope and tracks the currently
/// focused element within that scope.
/// </remarks>
private static readonly AttachedProperty < IInputElement > FocusedElementProperty =
AvaloniaProperty . RegisterAttached < FocusManager , StyledElement , IInputElement > ( "FocusedElement" ) ;
private StyledElement ? _f ocusRoot ;
/// <summary>
/// Initializes a new instance of the <see cref="FocusManager"/> class.
@ -38,15 +41,6 @@ namespace Avalonia.Input
/// </summary>
public IInputElement ? GetFocusedElement ( ) = > Current ;
/// <summary>
/// Gets the current focus scope.
/// </summary>
public IFocusScope ? Scope
{
get ;
private set ;
}
/// <summary>
/// Focuses a control.
/// </summary>
@ -58,38 +52,35 @@ namespace Avalonia.Input
NavigationMethod method = NavigationMethod . Unspecified ,
KeyModifiers keyModifiers = KeyModifiers . None )
{
if ( control ! = null )
if ( KeyboardDevice . Instance is not { } keyboardDevice )
return false ;
if ( control is not null )
{
var scope = GetFocusScopeAncestors ( control )
. FirstOrDefault ( ) ;
if ( ! CanFocu s( control ) )
return false ;
if ( scope ! = null )
if ( GetFocusScope ( control ) is StyledElement scope )
{
Scope = scope ;
return SetFocusedElement ( scope , control , method , keyModifiers ) ;
scope . SetValue ( FocusedElementProperty , control ) ;
_f ocusRoot = GetFocusRoot ( scope ) ;
}
keyboardDevice . SetFocusedElement ( control , method , keyModifiers ) ;
return true ;
}
else if ( Current ! = null )
else if ( _f ocusRoot ? . GetValue ( FocusedElementProperty ) is { } restore & &
restore ! = Current & &
Focus ( restore ) )
{
// If control is null, set focus to the topmost focus scope.
foreach ( var scope in GetFocusScopeAncestors ( Current ) . Reverse ( ) . ToArray ( ) )
{
if ( scope ! = Scope & &
_f ocusScopes . TryGetValue ( scope , out var element ) & &
element ! = null )
{
return Focus ( element , method ) ;
}
}
if ( Scope is object )
{
// Couldn't find a focus scope, clear focus.
return SetFocusedElement ( Scope , null ) ;
}
return true ;
}
else
{
_f ocusRoot = null ;
keyboardDevice . SetFocusedElement ( null , NavigationMethod . Unspecified , KeyModifiers . None ) ;
return false ;
}
return false ;
}
public void ClearFocus ( )
@ -97,55 +88,23 @@ namespace Avalonia.Input
Focus ( null ) ;
}
public IInputElement ? GetFocusedElement ( IFocusScope scope )
{
_f ocusScopes . TryGetValue ( scope , out var result ) ;
return result ;
}
/// <summary>
/// Sets the currently focused element in the specified scope.
/// </summary>
/// <param name="scope">The focus scope.</param>
/// <param name="element">The element to focus. May be null.</param>
/// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
/// <remarks>
/// If the specified scope is the current <see cref="Scope"/> then the keyboard focus
/// will change.
/// </remarks>
public bool SetFocusedElement (
IFocusScope scope ,
IInputElement ? element ,
NavigationMethod method = NavigationMethod . Unspecified ,
KeyModifiers keyModifiers = KeyModifiers . None )
public void ClearFocusOnElementRemoved ( IInputElement removedElement , Visual oldParent )
{
scope = scope ? ? throw new ArgumentNullException ( nameof ( scope ) ) ;
if ( element is not null & & ! CanFocus ( element ) )
if ( oldParent is IInputElement parentElement & &
GetFocusScope ( parentElement ) is StyledElement scope & &
scope . GetValue ( FocusedElementProperty ) is IInputElement focused & &
focused = = removedElement )
{
return false ;
}
if ( _f ocusScopes . TryGetValue ( scope , out var existingElement ) )
{
if ( element ! = existingElement )
{
_f ocusScopes . Remove ( scope ) ;
_f ocusScopes . Add ( scope , element ) ;
}
}
else
{
_f ocusScopes . Add ( scope , element ) ;
scope . ClearValue ( FocusedElementProperty ) ;
}
if ( Scope = = scope )
{
KeyboardDevice . Instance ? . SetFocusedElement ( element , method , keyModifiers ) ;
}
if ( Current = = removedElement )
Focus ( null ) ;
}
return true ;
public IInputElement ? GetFocusedElement ( IFocusScope scope )
{
return ( scope as StyledElement ) ? . GetValue ( FocusedElementProperty ) ;
}
/// <summary>
@ -154,35 +113,23 @@ namespace Avalonia.Input
/// <param name="scope">The new focus scope.</param>
public void SetFocusScope ( IFocusScope scope )
{
scope = scope ? ? throw new ArgumentNullException ( nameof ( scope ) ) ;
if ( ! _f ocusScopes . TryGetValue ( scope , out var e ) )
if ( GetFocusedElement ( scope ) is { } focused )
{
Focus ( focused ) ;
}
else if ( scope is IInputElement scopeElement & & CanFocus ( scopeElement ) )
{
// 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 ;
_f ocusScopes . Add ( scope , e ) ;
Focus ( scopeElement ) ;
}
Scope = scope ;
Focus ( e ) ;
}
public void RemoveFocusScope ( IFocusScope scope )
public void RemoveFocusRoot ( IFocusScope scope )
{
scope = scope ? ? throw new ArgumentNullException ( nameof ( scope ) ) ;
if ( _f ocusScopes . TryGetValue ( scope , out _ ) )
{
SetFocusedElement ( scope , null ) ;
_f ocusScopes . Remove ( scope ) ;
}
if ( Scope = = scope )
{
Scope = null ;
}
if ( scope = = _f ocusRoot )
ClearFocus ( ) ;
}
public static bool GetIsFocusScope ( IInputElement e ) = > e is IFocusScope ;
@ -220,27 +167,45 @@ namespace Avalonia.Input
private static bool CanFocus ( IInputElement e ) = > e . Focusable & & e . IsEffectivelyEnabled & & IsVisible ( e ) ;
/// <summary>
/// Gets the focus scope ancestors of the specified control, traversing popups.
/// Gets the focus scope of the specified control, traversing popups.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The focus scopes .</returns>
private static IEnumerable < IFocusScope > GetFocusScopeAncestors ( IInputElement control )
/// <returns>The focus scope.</returns>
private static StyledElement ? GetFocusScope ( IInputElement control )
{
IInputElement ? c = control ;
while ( c ! = null )
{
if ( c is IFocusScope scope & &
if ( c is IFocusScope & &
c is Visual v & &
v . VisualRoot is Visual root & &
root . IsVisible )
{
yield return scope ;
return v ;
}
c = ( c as Visual ) ? . GetVisualParent < IInputElement > ( ) ? ?
( ( c as IHostedVisualTreeRoot ) ? . Host as IInputElement ) ;
}
return null ;
}
private static StyledElement ? GetFocusRoot ( StyledElement scope )
{
if ( scope is not Visual v )
return null ;
var root = v . VisualRoot as Visual ;
while ( root is IHostedVisualTreeRoot hosted & &
hosted . Host ? . VisualRoot is Visual parentRoot )
{
root = parentRoot ;
}
return root ;
}
/// <summary>
@ -274,6 +239,11 @@ namespace Avalonia.Input
}
}
private static bool IsVisible ( IInputElement e ) = > ( e as Visual ) ? . IsEffectivelyVisible ? ? true ;
private static bool IsVisible ( IInputElement e )
{
if ( e is Visual v )
return v . IsAttachedToVisualTree & & e . IsEffectivelyVisible ;
return true ;
}
}
}