using Avalonia.Controls; using Avalonia.Input; using Avalonia.UnitTests; using Xunit; namespace Avalonia.Base.UnitTests.Input { public class InputElement_Focus { [Fact] public void Focus_Should_Set_FocusManager_Current() { Button target; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = target = new Button() }; target.Focus(); Assert.Same(target, root.FocusManager.GetFocusedElement()); } } [Fact] public void Invisible_Controls_Should_Not_Receive_Focus() { Button target; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = target = new Button() { IsVisible = false} }; Assert.Null(root.FocusManager.GetFocusedElement()); target.Focus(); Assert.False(target.IsFocused); Assert.False(target.IsKeyboardFocusWithin); Assert.Null(root.FocusManager.GetFocusedElement()); } } [Fact] public void Effectively_Invisible_Controls_Should_Not_Receive_Focus() { var target = new Button(); Panel container; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = container = new Panel { IsVisible = false, Children = { target } } }; Assert.Null(root.FocusManager.GetFocusedElement()); target.Focus(); Assert.False(target.IsFocused); Assert.False(target.IsKeyboardFocusWithin); Assert.Null(root.FocusManager.GetFocusedElement()); } } [Fact] public void Trying_To_Focus_Invisible_Control_Should_Not_Change_Focus() { Button first; Button second; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = new StackPanel { Children = { (first = new Button()), (second = new Button() { IsVisible = false}), } } }; first.Focus(); Assert.Same(first, root.FocusManager.GetFocusedElement()); second.Focus(); Assert.Same(first, root.FocusManager.GetFocusedElement()); } } [Fact] public void Disabled_Controls_Should_Not_Receive_Focus() { Button target; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = target = new Button() { IsEnabled = false } }; Assert.Null(root.FocusManager.GetFocusedElement()); target.Focus(); Assert.False(target.IsFocused); Assert.False(target.IsKeyboardFocusWithin); Assert.Null(root.FocusManager.GetFocusedElement()); } } [Fact] public void Effectively_Disabled_Controls_Should_Not_Receive_Focus() { var target = new Button(); Panel container; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = container = new Panel { IsEnabled = false, Children = { target } } }; Assert.Null(root.FocusManager.GetFocusedElement()); target.Focus(); Assert.False(target.IsFocused); Assert.False(target.IsKeyboardFocusWithin); Assert.Null(root.FocusManager.GetFocusedElement()); } } [Fact] public void Focus_Should_Not_Get_Restored_To_Enabled_Control() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var sp = new StackPanel(); Button target = new Button(); Button target1 = new Button(); target.Click += (s, e) => target.IsEnabled = false; target1.Click += (s, e) => target.IsEnabled = true; sp.Children.Add(target); sp.Children.Add(target1); var root = new TestRoot { Child = sp }; target.Focus(); target.RaiseEvent(new AccessKeyEventArgs("b1", false)); Assert.False(target.IsEnabled); Assert.False(target.IsFocused); target1.RaiseEvent(new AccessKeyEventArgs("b2", false)); Assert.True(target.IsEnabled); Assert.False(target.IsFocused); } } [Fact] public void Focus_Should_Be_Cleared_When_Control_Is_Hidden() { Button target; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = target = new Button() }; target.Focus(); target.IsVisible = false; Assert.Null(root.FocusManager.GetFocusedElement()); } } [Fact] public void Focus_Should_Be_Cleared_When_Control_Is_Effectively_Hidden() { Border container; Button target; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = container = new Border { Child = target = new Button(), } }; target.Focus(); container.IsVisible = false; Assert.Null(root.FocusManager.GetFocusedElement()); } } [Fact] public void Focus_Should_Be_Cleared_When_Control_Is_Disabled() { Button target; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = target = new Button() }; target.Focus(); target.IsEnabled = false; Assert.Null(root.FocusManager.GetFocusedElement()); } } [Fact] public void Focus_Should_Be_Cleared_When_Control_Is_Effectively_Disabled() { Border container; Button target; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = container = new Border { Child = target = new Button(), } }; target.Focus(); container.IsEnabled = false; Assert.Null(root.FocusManager.GetFocusedElement()); } } [Fact] public void Focus_Should_Be_Cleared_When_Control_Is_Removed_From_VisualTree() { Button target; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = target = new Button() }; target.Focus(); root.Child = null; Assert.Null(root.FocusManager.GetFocusedElement()); } } [Fact] public void Focus_Pseudoclass_Should_Be_Applied_On_Focus() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Decorator { Focusable = true }; var target2 = new Decorator { Focusable = true }; var root = new TestRoot { Child = new StackPanel { Children = { target1, target2 } } }; target1.ApplyTemplate(); target2.ApplyTemplate(); target1.Focus(); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus")); Assert.False(target2.IsFocused); Assert.False(target2.Classes.Contains(":focus")); target2.Focus(NavigationMethod.Tab); Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus")); Assert.True(target2.IsFocused); Assert.True(target2.Classes.Contains(":focus")); } } [Fact] public void Control_FocusVsisible_Pseudoclass_Should_Be_Applied_On_Tab_And_DirectionalFocus() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Decorator { Focusable = true }; var target2 = new Decorator { Focusable = true }; var root = new TestRoot { Child = new StackPanel { Children = { target1, target2 } } }; target1.ApplyTemplate(); target2.ApplyTemplate(); target1.Focus(); Assert.True(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-visible")); Assert.False(target2.IsFocused); Assert.False(target2.Classes.Contains(":focus-visible")); target2.Focus(NavigationMethod.Tab); Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-visible")); Assert.True(target2.IsFocused); Assert.True(target2.Classes.Contains(":focus-visible")); target1.Focus(NavigationMethod.Directional); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus-visible")); Assert.False(target2.IsFocused); Assert.False(target2.Classes.Contains(":focus-visible")); } } [Fact] public void Control_FocusWithin_PseudoClass_Should_Be_Applied() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Decorator { Focusable = true }; var target2 = new Decorator { Focusable = true }; var root = new TestRoot { Child = new StackPanel { Children = { target1, target2 } } }; target1.ApplyTemplate(); target2.ApplyTemplate(); target1.Focus(); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.IsKeyboardFocusWithin); Assert.True(root.Child.Classes.Contains(":focus-within")); Assert.True(root.Child.IsKeyboardFocusWithin); Assert.True(root.Classes.Contains(":focus-within")); Assert.True(root.IsKeyboardFocusWithin); } } [Fact] public void Control_FocusWithin_PseudoClass_Should_Be_Applied_and_Removed() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Decorator { Focusable = true }; var target2 = new Decorator { Focusable = true }; var panel1 = new Panel { Children = { target1 } }; var panel2 = new Panel { Children = { target2 } }; var root = new TestRoot { Child = new StackPanel { Children = { panel1, panel2 } } }; target1.ApplyTemplate(); target2.ApplyTemplate(); target1.Focus(); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.IsKeyboardFocusWithin); Assert.True(panel1.Classes.Contains(":focus-within")); Assert.True(panel1.IsKeyboardFocusWithin); Assert.True(root.Child.Classes.Contains(":focus-within")); Assert.True(root.Child.IsKeyboardFocusWithin); Assert.True(root.Classes.Contains(":focus-within")); Assert.True(root.IsKeyboardFocusWithin); target2.Focus(); Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-within")); Assert.False(target1.IsKeyboardFocusWithin); Assert.False(panel1.Classes.Contains(":focus-within")); Assert.False(panel1.IsKeyboardFocusWithin); Assert.True(root.Child.Classes.Contains(":focus-within")); Assert.True(root.Child.IsKeyboardFocusWithin); Assert.True(root.Classes.Contains(":focus-within")); Assert.True(root.IsKeyboardFocusWithin); Assert.True(target2.IsFocused); Assert.True(target2.Classes.Contains(":focus-within")); Assert.True(target2.IsKeyboardFocusWithin); Assert.True(panel2.Classes.Contains(":focus-within")); Assert.True(panel2.IsKeyboardFocusWithin); } } [Fact] public void Control_FocusWithin_Pseudoclass_Should_Be_Removed_When_Removed_From_Tree() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Decorator { Focusable = true }; var target2 = new Decorator { Focusable = true }; var root = new TestRoot { Child = new StackPanel { Children = { target1, target2 } } }; target1.ApplyTemplate(); target2.ApplyTemplate(); target1.Focus(); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.IsKeyboardFocusWithin); Assert.True(root.Child.Classes.Contains(":focus-within")); Assert.True(root.Child.IsKeyboardFocusWithin); Assert.True(root.Classes.Contains(":focus-within")); Assert.True(root.IsKeyboardFocusWithin); Assert.Equal(KeyboardDevice.Instance.FocusedElement, target1); root.Child = null; Assert.Null(KeyboardDevice.Instance.FocusedElement); Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-within")); Assert.False(target1.IsKeyboardFocusWithin); Assert.False(root.Classes.Contains(":focus-within")); Assert.False(root.IsKeyboardFocusWithin); } } [Fact] public void Control_FocusWithin_Pseudoclass_Should_Be_Removed_Focus_Moves_To_Different_Root() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Decorator { Focusable = true }; var target2 = new Decorator { Focusable = true }; var root1 = new TestRoot { Child = new StackPanel { Children = { target1, } } }; var root2 = new TestRoot { Child = new StackPanel { Children = { target2, } } }; target1.ApplyTemplate(); target2.ApplyTemplate(); target1.Focus(); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.IsKeyboardFocusWithin); Assert.True(root1.Child.Classes.Contains(":focus-within")); Assert.True(root1.Child.IsKeyboardFocusWithin); Assert.True(root1.Classes.Contains(":focus-within")); Assert.True(root1.IsKeyboardFocusWithin); Assert.Equal(KeyboardDevice.Instance.FocusedElement, target1); target2.Focus(); Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-within")); Assert.False(target1.IsKeyboardFocusWithin); Assert.False(root1.Child.Classes.Contains(":focus-within")); Assert.False(root1.Child.IsKeyboardFocusWithin); Assert.False(root1.Classes.Contains(":focus-within")); Assert.False(root1.IsKeyboardFocusWithin); Assert.True(target2.IsFocused); Assert.True(target2.Classes.Contains(":focus-within")); Assert.True(target2.IsKeyboardFocusWithin); Assert.True(root2.Child.Classes.Contains(":focus-within")); Assert.True(root2.Child.IsKeyboardFocusWithin); Assert.True(root2.Classes.Contains(":focus-within")); Assert.True(root2.IsKeyboardFocusWithin); } } [Fact] public void Can_Clear_Focus() { Button target; using (UnitTestApplication.Start(TestServices.RealFocus)) { var root = new TestRoot { Child = target = new Button() }; target.Focus(); root.FocusManager.ClearFocus(); Assert.Null(root.FocusManager.GetFocusedElement()); } } [Fact] public void Removing_Focused_Element_Inside_Focus_Scope_Activates_Root_Focus_Scope() { // Issue #13325 using var app = UnitTestApplication.Start(TestServices.RealFocus); Button innerButton, intermediateButton, outerButton; TestFocusScope innerScope; var root = new TestRoot { Child = new StackPanel { Children = { // Intermediate focus scope to make sure that the root focus scope gets // activated, not this one. new TestFocusScope { Children = { (innerScope = new TestFocusScope { Children = { (innerButton = new Button()), } }), (intermediateButton = new Button()), } }, (outerButton = new Button()), } } }; // Focus a control in each scope, ending with the innermost one. outerButton.Focus(); intermediateButton.Focus(); innerButton.Focus(); // Remove the focused control from the tree. ((Panel)innerButton.Parent).Children.Remove(innerButton); var focusManager = Assert.IsType(root.FocusManager); Assert.Same(outerButton, focusManager.GetFocusedElement()); Assert.Null(focusManager.GetFocusedElement(innerScope)); } [Fact] public void Removing_Focus_Scope_Activates_Root_Focus_Scope() { using var app = UnitTestApplication.Start(TestServices.RealFocus); Button innerButton, outerButton; TestFocusScope innerScope; var root = new TestRoot { Child = new StackPanel { Children = { (innerScope = new TestFocusScope { Children = { (innerButton = new Button()), } }), (outerButton = new Button()), } } }; // Focus a control in the top-level and inner focus scopes. outerButton.Focus(); innerButton.Focus(); // Remove the inner focus scope. ((Panel)innerScope.Parent).Children.Remove(innerScope); var focusManager = Assert.IsType(root.FocusManager); Assert.Same(outerButton, focusManager.GetFocusedElement()); } [Fact] public void Switching_Focus_Scope_Changes_Focus() { using var app = UnitTestApplication.Start(TestServices.RealFocus); Button innerButton, outerButton; TestFocusScope innerScope; var root = new TestRoot { Child = new StackPanel { Children = { (innerScope = new TestFocusScope { Children = { (innerButton = new Button()), } }), (outerButton = new Button()), } } }; // Focus a control in the top-level and inner focus scopes. outerButton.Focus(); innerButton.Focus(); var focusManager = Assert.IsType(root.FocusManager); Assert.Same(innerButton, focusManager.GetFocusedElement()); focusManager.SetFocusScope(root); Assert.Same(outerButton, focusManager.GetFocusedElement()); focusManager.SetFocusScope(innerScope); Assert.Same(innerButton, focusManager.GetFocusedElement()); } [Fact] public void Can_Get_First_Focusable_Element() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Button { Focusable = true, Content = "1" }; var target2 = new Button { Focusable = true, Content = "2" }; var target3 = new Button { Focusable = true, Content = "3" }; var target4 = new Button { Focusable = true, Content = "4" }; var container = new StackPanel { Children = { target1, target2, target3, target4 } }; var root = new TestRoot { Child = container }; var firstFocusable = FocusManager.FindFirstFocusableElement(container); Assert.Equal(target1, firstFocusable); firstFocusable = (root.FocusManager as FocusManager)?.FindFirstFocusableElement(); Assert.Equal(target1, firstFocusable); } } [Fact] public void Can_Get_Last_Focusable_Element() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Button { Focusable = true, Content = "1" }; var target2 = new Button { Focusable = true, Content = "2" }; var target3 = new Button { Focusable = true, Content = "3" }; var target4 = new Button { Focusable = true, Content = "4" }; var container = new StackPanel { Children = { target1, target2, target3, target4 } }; var root = new TestRoot { Child = container }; var lastFocusable = FocusManager.FindLastFocusableElement(container); Assert.Equal(target4, lastFocusable); lastFocusable = (root.FocusManager as FocusManager)?.FindLastFocusableElement(); Assert.Equal(target4, lastFocusable); } } [Fact] public void Can_Get_Next_Element() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Button { Focusable = true, Content = "1" }; var target2 = new Button { Focusable = true, Content = "2" }; var target3 = new Button { Focusable = true, Content = "3" }; var target4 = new Button { Focusable = true, Content = "4" }; var container = new StackPanel { Children = { target1, target2, target3, target4 } }; var root = new TestRoot { Child = container }; var focusManager = FocusManager.GetFocusManager(container); target1.Focus(); var next = focusManager.FindNextElement(NavigationDirection.Next); Assert.Equal(next, target2); } } [Fact] public void Can_Get_Next_Element_With_Options() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Button { Focusable = true, Content = "1" }; var target2 = new Button { Focusable = true, Content = "2" }; var target3 = new Button { Focusable = true, Content = "3" }; var target4 = new Button { Focusable = true, Content = "4" }; var target5 = new Button { Focusable = true, Content = "5" }; var seachStack = new StackPanel() { Children = { target3, target4 } }; var container = new StackPanel { Orientation = Avalonia.Layout.Orientation.Horizontal, Children = { target1, target2, seachStack, target5 } }; var root = new TestRoot { Child = container }; root.InvalidateMeasure(); root.ExecuteInitialLayoutPass(); var focusManager = FocusManager.GetFocusManager(container); target1.Focus(); var options = new FindNextElementOptions() { SearchRoot = seachStack }; // Search root is right of the current focus, should return the first focusable element in the search root var next = focusManager.FindNextElement(NavigationDirection.Right, options); Assert.Equal(next, target3); target5.Focus(); // Search root is right of the current focus, should return the first focusable element in the search root next = focusManager.FindNextElement(NavigationDirection.Left, options); Assert.Equal(next, target3); // Search root isn't to the right of the current focus, should return null next = focusManager.FindNextElement(NavigationDirection.Right, options); Assert.Null(next); } } [Fact] public void Focus_Should_Move_According_To_Direction() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Button { Focusable = true, Content = "1" }; var target2 = new Button { Focusable = true, Content = "2" }; var target3 = new Button { Focusable = true, Content = "3" }; var target4 = new Button { Focusable = true, Content = "4" }; var container = new StackPanel { Children = { target1, target2, target3, target4 } }; var root = new TestRoot { Child = container }; var focusManager = FocusManager.GetFocusManager(container); var hasMoved = focusManager.TryMoveFocus(NavigationDirection.Next); Assert.True(target1.IsFocused); Assert.True(hasMoved); hasMoved = focusManager.TryMoveFocus(NavigationDirection.Previous); Assert.True(target4.IsFocused); Assert.True(hasMoved); } } [Fact] public void Focus_Should_Move_According_To_XY_Direction() { using (UnitTestApplication.Start(TestServices.RealFocus)) { var target1 = new Button { Focusable = true, Content = "1" }; var target2 = new Button { Focusable = true, Content = "2" }; var target3 = new Button { Focusable = true, Content = "3" }; var target4 = new Button { Focusable = true, Content = "4" }; var center = new Button { [XYFocus.LeftProperty] = target1, [XYFocus.RightProperty] = target2, [XYFocus.UpProperty] = target3, [XYFocus.DownProperty] = target4, }; var container = new Canvas { Children = { target1, target2, target3, target4, center } }; var root = new TestRoot { Child = container }; var focusManager = FocusManager.GetFocusManager(container); center.Focus(); var options = new FindNextElementOptions() { SearchRoot = container }; var hasMoved = focusManager.TryMoveFocus(NavigationDirection.Up, options); Assert.True(target3.IsFocused); Assert.True(hasMoved); } } private class TestFocusScope : Panel, IFocusScope { } } }