Browse Source

Initial implementation of IsKeyboardFocusWithin

Dan Walmsley 5 years ago
parent
commit
98c2b0f9e0

+ 4 - 0
src/Avalonia.Input/ApiCompatBaseline.txt

@@ -0,0 +1,4 @@
+Compat issues with assembly Avalonia.Input:
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Input.IInputElement.IsKeyboardFocusWithin' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Input.IInputElement.IsKeyboardFocusWithin.get()' is present in the implementation but not in the contract.
+Total Issues: 2

+ 5 - 0
src/Avalonia.Input/IInputElement.cs

@@ -89,6 +89,11 @@ namespace Avalonia.Input
         /// <see cref="IsEnabled"/> value of this control and its parent controls.
         /// </remarks>
         bool IsEffectivelyEnabled { get; }
+        
+        /// <summary>
+        /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements.
+        /// </summary>
+        bool IsKeyboardFocusWithin { get; }
 
         /// <summary>
         /// Gets a value indicating whether the control is focused.

+ 23 - 1
src/Avalonia.Input/InputElement.cs

@@ -42,6 +42,14 @@ namespace Avalonia.Input
         public static readonly StyledProperty<Cursor?> CursorProperty =
             AvaloniaProperty.Register<InputElement, Cursor?>(nameof(Cursor), null, true);
 
+        /// <summary>
+        /// Defines the <see cref="IsKeyboardFocusWithin"/> property.
+        /// </summary>
+        public static readonly DirectProperty<InputElement, bool> IsKeyboardFocusWithinProperty =
+            AvaloniaProperty.RegisterDirect<InputElement, bool>(
+                nameof(IsKeyboardFocusWithin),
+                o => o.IsKeyboardFocusWithin);
+        
         /// <summary>
         /// Defines the <see cref="IsFocused"/> property.
         /// </summary>
@@ -160,6 +168,7 @@ namespace Avalonia.Input
 
         private bool _isEffectivelyEnabled = true;
         private bool _isFocused;
+        private bool _isKeyboardFocusWithin;
         private bool _isFocusVisible;
         private bool _isPointerOver;
         private GestureRecognizerCollection? _gestureRecognizers;
@@ -343,6 +352,15 @@ namespace Avalonia.Input
             get { return GetValue(CursorProperty); }
             set { SetValue(CursorProperty, value); }
         }
+        
+        /// <summary>
+        /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements.
+        /// </summary>
+        public bool IsKeyboardFocusWithin
+        {
+            get => _isKeyboardFocusWithin;
+            internal set => SetAndRaise(IsKeyboardFocusWithinProperty, ref _isKeyboardFocusWithin, value); 
+        }
 
         /// <summary>
         /// Gets a value indicating whether the control is focused.
@@ -423,7 +441,7 @@ namespace Avalonia.Input
             base.OnAttachedToVisualTreeCore(e);
             UpdateIsEffectivelyEnabled();
         }
-
+        
         /// <summary>
         /// Called before the <see cref="GotFocus"/> event occurs.
         /// </summary>
@@ -544,6 +562,10 @@ namespace Avalonia.Input
             {
                 UpdatePseudoClasses(null, change.NewValue.GetValueOrDefault<bool>());
             }
+            else if (change.Property == IsKeyboardFocusWithinProperty)
+            {
+                PseudoClasses.Set(":focus-within", _isKeyboardFocusWithin);
+            }
         }
 
         /// <summary>

+ 71 - 0
src/Avalonia.Input/KeyboardDevice.cs

@@ -31,6 +31,74 @@ namespace Avalonia.Input
                 RaisePropertyChanged();
             }
         }
+        
+        private void ClearFocusWithin(IInputElement element, bool clearRoot)
+        {
+            foreach (IInputElement el in element.VisualChildren)
+            {
+                if (el.IsKeyboardFocusWithin)
+                {
+                    ClearFocusWithin(el, true);
+                    break;
+                }
+            }
+            
+            if(clearRoot)
+            {
+                if (element is InputElement ie)
+                {
+                    ie.IsKeyboardFocusWithin = false;
+                }
+            }
+        }
+
+        private void SetIsFocusWithin(InputElement oldElement, InputElement newElement)
+        {
+            InputElement? branch = null;
+
+            InputElement el = newElement;
+
+            while (el != null)
+            {
+                if (el.IsKeyboardFocusWithin)
+                {
+                    branch = el;
+                    break;
+                }
+
+                if ((el as IInputElement).VisualParent is InputElement ie)
+                {
+                    el = ie;
+                }
+                else
+                {
+                    break;
+                }
+            }
+
+            el = oldElement;
+
+            if (el != null && branch != null)
+            {
+                ClearFocusWithin(branch, false);
+            }
+
+            el = newElement;
+            
+            while (el != null && el != branch)
+            {
+                el.IsKeyboardFocusWithin = true;
+                
+                if ((el as IInputElement).VisualParent is InputElement ie)
+                {
+                    el = ie;
+                }
+                else
+                {
+                    break;
+                }
+            }    
+        }
 
         public void SetFocusedElement(
             IInputElement? element, 
@@ -40,6 +108,9 @@ namespace Avalonia.Input
             if (element != FocusedElement)
             {
                 var interactive = FocusedElement as IInteractive;
+                
+                SetIsFocusWithin(FocusedElement as InputElement, element as InputElement);
+                
                 FocusedElement = element;
 
                 interactive?.RaiseEvent(new RoutedEventArgs