Browse Source

Added arrow key scroll navigation to ScrollViewer control (#20482)

* Added arrow key scroll navigation to ScrollViewer control

* Check event source before handling Left/Right/Up/Down keys

* Handle right-to-left flow direction in ScrollViewer keys

* Added ScrollViewer test

* Added ScrollBar test

---------

Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
pull/21921/head
Melissa 2 months ago
committed by GitHub
parent
commit
e8692155e8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 38
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  2. 30
      src/Avalonia.Controls/ScrollViewer.cs
  3. 49
      tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs
  4. 50
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

38
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -250,15 +250,41 @@ namespace Avalonia.Controls.Primitives
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.PageUp)
if (Orientation == Orientation.Vertical)
{
LargeDecrement();
e.Handled = true;
if (e.Key == Key.PageUp)
{
LargeDecrement();
e.Handled = true;
}
else if (e.Key == Key.PageDown)
{
LargeIncrement();
e.Handled = true;
}
else if (e.Key == Key.Up)
{
SmallDecrement();
e.Handled = true;
}
else if (e.Key == Key.Down)
{
SmallIncrement();
e.Handled = true;
}
}
else if (e.Key == Key.PageDown)
else if (Orientation == Orientation.Horizontal)
{
LargeIncrement();
e.Handled = true;
if (e.Key == Key.Left)
{
SmallDecrement();
e.Handled = true;
}
else if (e.Key == Key.Right)
{
SmallIncrement();
e.Handled = true;
}
}
}

30
src/Avalonia.Controls/ScrollViewer.cs

@ -806,6 +806,36 @@ namespace Avalonia.Controls
PageDown();
e.Handled = true;
}
else if (e.Source == this)
{
bool rtl = FlowDirection == Media.FlowDirection.RightToLeft;
if (e.Key == Key.Left)
{
if (rtl)
LineRight();
else
LineLeft();
e.Handled = true;
}
else if (e.Key == Key.Right)
{
if (rtl)
LineLeft();
else
LineRight();
e.Handled = true;
}
else if (e.Key == Key.Up)
{
LineUp();
e.Handled = true;
}
else if (e.Key == Key.Down)
{
LineDown();
e.Handled = true;
}
}
}
/// <summary>

49
tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs

@ -379,6 +379,55 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.False(args.Handled);
}
[Fact]
public void Focus_KeyInput_Should_Scroll()
{
// Vertical scrollbar
var target = CreateScrollBar(Orientation.Vertical);
// Page down and page up
KeyDown(target, Key.PageDown);
Assert.Equal(target.LargeChange, target.Value);
KeyDown(target, Key.PageUp);
Assert.Equal(0, target.Value);
// Small scrolling with arrow keys
KeyDown(target, Key.Down);
Assert.Equal(target.SmallChange, target.Value);
KeyDown(target, Key.Up);
Assert.Equal(0, target.Value);
// Vertical scrollbars shouldn't scroll for left and right keys
KeyDown(target, Key.Right);
Assert.Equal(0, target.Value);
KeyDown(target, Key.Left);
Assert.Equal(0, target.Value);
// Horizontal scrollbar
target = CreateScrollBar(Orientation.Horizontal);
// Small scrolling with arrow keys
KeyDown(target, Key.Right);
Assert.Equal(target.SmallChange, target.Value);
KeyDown(target, Key.Left);
Assert.Equal(0, target.Value);
// Horizontal scrollbars shouldn't scroll for up and down keys
KeyDown(target, Key.Down);
Assert.Equal(0, target.Value);
KeyDown(target, Key.Up);
Assert.Equal(0, target.Value);
}
private static void KeyDown(IInputElement target, Key key)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = key,
});
}
private static ContextRequestedEventArgs CreateContextRequested(ScrollBar target, PointerType pointerType)
{
var pointer = new Pointer(Pointer.GetNextFreeId(), pointerType, true);

50
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -4,6 +4,7 @@ using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.UnitTests;
@ -583,6 +584,55 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, panel.ArrangeOverrideCalls);
}
[Fact]
public void Focus_KeyInput_Should_Scroll()
{
var panel = new Panel
{
Width = 100_000,
Height = 100_000,
};
var target = new ScrollViewer
{
Content = panel,
Template = new FuncControlTemplate<ScrollViewer>(CreateTemplate),
};
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
// Page down and page up
KeyDown(target, Key.PageDown);
Assert.Equal(new(0, target.Viewport.Height), target.Offset);
KeyDown(target, Key.PageUp);
Assert.Equal(new(0, 0), target.Offset);
// Per-line scrolling in all directions with arrow keys
KeyDown(target, Key.Down);
Assert.Equal(new(0, target.SmallChange.Height), target.Offset);
KeyDown(target, Key.Right);
Assert.Equal(new(target.SmallChange.Width, target.SmallChange.Height), target.Offset);
KeyDown(target, Key.Up);
Assert.Equal(new(ScrollViewer.DefaultSmallChange, 0), target.Offset);
KeyDown(target, Key.Left);
Assert.Equal(new(0, 0), target.Offset);
// Scrolling horizontally with a right-to-left flow direction
target.FlowDirection = FlowDirection.RightToLeft;
KeyDown(target, Key.Left);
Assert.Equal(new(target.SmallChange.Width, 0), target.Offset);
KeyDown(target, Key.Right);
Assert.Equal(new(0, 0), target.Offset);
}
private static void KeyDown(IInputElement target, Key key)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = key,
});
}
public class TestPanel : Panel
{
public int DesiredWidth { get; set; }

Loading…
Cancel
Save