diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index b4971a405c..70c78a3ad0 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/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; + } } } diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 57b6664440..3026ba8455 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/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; + } + } } /// diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs index f6aff58cee..794652078e 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs +++ b/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); diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs index 752f2ce567..a3aa2f8b92 100644 --- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs +++ b/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(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; }