From d9ce64e1b03da867fdf7981796ad9b5d93f8e672 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Thu, 14 Jul 2022 14:29:43 +0200 Subject: [PATCH] Skip disabled controls when moving to first/last item --- src/Avalonia.Controls/ItemsControl.cs | 29 ++++++++++++------- .../Platform/DefaultMenuInteractionHandler.cs | 4 +-- .../Primitives/SelectingItemsControlTests.cs | 24 +++++++-------- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 56b0014c05..c7348f8609 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -502,25 +502,34 @@ namespace Avalonia.Controls IInputElement? from, bool wrap) { - IInputElement? result; - var c = from; - - do + for(;;) { - result = container.GetControl(direction, c, wrap); + var result = container.GetControl(direction, from, wrap); + + if (result is null || result == from) + { + return null; + } - if (result != null && - result.Focusable && + if (result.Focusable && result.IsEffectivelyEnabled && result.IsEffectivelyVisible) { return result; } - c = result; - } while (c != null && c != from && direction != NavigationDirection.First && direction != NavigationDirection.Last); + direction = direction switch + { + //We did not find an enabled first item. Move downwards until we find one. + NavigationDirection.First => NavigationDirection.Down, + + //We did not find an enabled last item. Move upwards until we find one. + NavigationDirection.Last => NavigationDirection.Up, + _ => direction + }; - return null; + from = result; + } } private void PresenterChildIndexChanged(object? sender, ChildIndexChangedEventArgs e) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 868cce879a..ce1cddc8cd 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls.Platform Menu.PointerPressed += PointerPressed; Menu.PointerReleased += PointerReleased; Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); - Menu.AddHandler(Avalonia.Controls.Menu.MenuOpenedEvent, MenuOpened); + Menu.AddHandler(MenuBase.MenuOpenedEvent, MenuOpened); Menu.AddHandler(MenuItem.PointerEnteredItemEvent, PointerEntered); Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved); @@ -89,7 +89,7 @@ namespace Avalonia.Controls.Platform Menu.PointerPressed -= PointerPressed; Menu.PointerReleased -= PointerReleased; Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); - Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, MenuOpened); + Menu.RemoveHandler(MenuBase.MenuOpenedEvent, MenuOpened); Menu.RemoveHandler(MenuItem.PointerEnteredItemEvent, PointerEntered); Menu.RemoveHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 3d36395c3a..e861f4a5db 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1615,48 +1615,44 @@ namespace Avalonia.Controls.UnitTests.Primitives target.MoveSelection(NavigationDirection.Next, true); } - [Fact(Timeout = 2000)] - public async Task MoveSelection_Does_Not_Hang_With_No_Focusable_Controls_And_Moving_Selection_To_The_First_Item() + [Fact] + public void MoveSelection_Skips_Non_Focusable_Controls_When_Moving_To_Last_Item() { var target = new TestSelector { Template = Template(), Items = new[] { - new ListBoxItem { Focusable = false }, new ListBoxItem(), + new ListBoxItem { Focusable = false }, } }; target.Measure(new Size(100, 100)); target.Arrange(new Rect(0, 0, 100, 100)); + target.MoveSelection(NavigationDirection.Last, true); - // Timeout in xUnit doesen't work with synchronous methods so we need to apply hack below. - // https://github.com/xunit/xunit/issues/2222 - await Task.Run(() => target.MoveSelection(NavigationDirection.First, true)); - Assert.Equal(-1, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); } - [Fact(Timeout = 2000)] - public async Task MoveSelection_Does_Not_Hang_With_No_Focusable_Controls_And_Moving_Selection_To_The_Last_Item() + [Fact] + public void MoveSelection_Skips_Non_Focusable_Controls_When_Moving_To_First_Item() { var target = new TestSelector { Template = Template(), Items = new[] { - new ListBoxItem(), new ListBoxItem { Focusable = false }, + new ListBoxItem(), } }; target.Measure(new Size(100, 100)); target.Arrange(new Rect(0, 0, 100, 100)); + target.MoveSelection(NavigationDirection.Last, true); - // Timeout in xUnit doesen't work with synchronous methods so we need to apply hack below. - // https://github.com/xunit/xunit/issues/2222 - await Task.Run(() => target.MoveSelection(NavigationDirection.Last, true)); - Assert.Equal(-1, target.SelectedIndex); + Assert.Equal(1, target.SelectedIndex); } [Fact]