Browse Source

Merge pull request #8514 from pr8x/move-first-last-skip-disabled

Skip disabled controls when moving to first/last item
pull/9947/head
Max Katz 4 years ago
committed by Steven Kirk
parent
commit
d3723c63f1
  1. 2
      samples/ControlCatalog.Android/Resources/Resource.Designer.cs
  2. 2
      src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
  3. 43
      src/Avalonia.Controls/ItemsControl.cs
  4. 10
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  5. 63
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

2
samples/ControlCatalog.Android/Resources/Resource.Designer.cs

@ -14,7 +14,7 @@ namespace ControlCatalog.Android
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.1.0.5")]
public partial class Resource
{

2
src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs

@ -14,7 +14,7 @@ namespace Avalonia.AndroidTestApplication
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.1.0.5")]
public partial class Resource
{

43
src/Avalonia.Controls/ItemsControl.cs

@ -503,29 +503,50 @@ namespace Avalonia.Controls
IInputElement from,
bool wrap)
{
IInputElement result;
var c = from;
var current = from;
do
for (;;)
{
result = container.GetControl(direction, c, wrap);
from = from ?? result;
var result = container.GetControl(direction, current, wrap);
if (result != null &&
result.Focusable &&
if (result is null)
{
return null;
}
if (result.Focusable &&
result.IsEffectivelyEnabled &&
result.IsEffectivelyVisible)
{
return result;
}
c = result;
} while (c != null && c != from && direction != NavigationDirection.First && direction != NavigationDirection.Last);
current = result;
return null;
if (current == from)
{
return null;
}
switch (direction)
{
//We did not find an enabled first item. Move downwards until we find one.
case NavigationDirection.First:
direction = NavigationDirection.Down;
from = result;
break;
//We did not find an enabled last item. Move upwards until we find one.
case NavigationDirection.Last:
direction = NavigationDirection.Up;
from = result;
break;
}
}
}
private void PresenterChildIndexChanged(object sender, ChildIndexChangedEventArgs e)
private void PresenterChildIndexChanged(object? sender, ChildIndexChangedEventArgs e)
{
_childIndexChanged?.Invoke(this, e);
}

10
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -52,7 +52,7 @@ namespace Avalonia.Controls.Platform
Menu.PointerPressed += PointerPressed;
Menu.PointerReleased += PointerReleased;
Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
Menu.AddHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened);
Menu.AddHandler(MenuBase.MenuOpenedEvent, MenuOpened);
Menu.AddHandler(MenuItem.PointerEnterItemEvent, PointerEnter);
Menu.AddHandler(MenuItem.PointerLeaveItemEvent, PointerLeave);
Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
@ -88,7 +88,7 @@ namespace Avalonia.Controls.Platform
Menu.PointerPressed -= PointerPressed;
Menu.PointerReleased -= PointerReleased;
Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened);
Menu.RemoveHandler(MenuBase.MenuOpenedEvent, MenuOpened);
Menu.RemoveHandler(MenuItem.PointerEnterItemEvent, PointerEnter);
Menu.RemoveHandler(MenuItem.PointerLeaveItemEvent, PointerLeave);
Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved);
@ -169,7 +169,11 @@ namespace Avalonia.Controls.Platform
case Key.Left:
{
if (item?.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen)
if (item is { IsSubMenuOpen: true, SelectedItem: null })
{
item.Close();
}
else if (item?.Parent is IMenuItem { IsTopLevel: false, IsSubMenuOpen: true } parent)
{
parent.Close();
parent.Focus();

63
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -1605,8 +1605,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(new[] { "Bar" }, selectedItems);
}
[Fact]
public void MoveSelection_Wrap_Does_Not_Hang_With_No_Focusable_Controls()
[Fact(Timeout = 2000)]
public async Task MoveSelection_Wrap_Does_Not_Hang_With_No_Focusable_Controls()
{
// Issue #3094.
var target = new TestSelector
@ -1622,11 +1622,34 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
target.MoveSelection(NavigationDirection.Next, true);
// Timeout in xUnit doesn'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.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(),
new ListBoxItem { Focusable = false },
}
};
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
target.MoveSelection(NavigationDirection.Last, true);
Assert.Equal(0, target.SelectedIndex);
}
[Fact]
public void MoveSelection_Skips_Non_Focusable_Controls_When_Moving_To_First_Item()
{
var target = new TestSelector
{
@ -1640,22 +1663,43 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
target.MoveSelection(NavigationDirection.Last, true);
Assert.Equal(1, target.SelectedIndex);
}
// Timeout in xUnit doesen't work with synchronous methods so we need to apply hack below.
[Fact(Timeout = 2000)]
public async Task MoveSelection_Does_Not_Hang_When_All_Items_Are_Non_Focusable_And_We_Move_To_First_Item()
{
var target = new TestSelector
{
Template = Template(),
Items = new[]
{
new ListBoxItem { Focusable = false },
new ListBoxItem { Focusable = false },
}
};
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
// Timeout in xUnit doesn'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);
}
[Fact(Timeout = 2000)]
public async Task MoveSelection_Does_Not_Hang_With_No_Focusable_Controls_And_Moving_Selection_To_The_Last_Item()
public async Task MoveSelection_Does_Not_Hang_When_All_Items_Are_Non_Focusable_And_We_Move_To_Last_Item()
{
var target = new TestSelector
{
Template = Template(),
Items = new[]
{
new ListBoxItem(),
new ListBoxItem { Focusable = false },
new ListBoxItem { Focusable = false },
}
};
@ -1663,9 +1707,10 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
// Timeout in xUnit doesen't work with synchronous methods so we need to apply hack below.
// Timeout in xUnit doesn'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);
}

Loading…
Cancel
Save