From 296c7a5f8528dc6ca3db53934973ea2f7f6007ad Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 5 Aug 2015 19:09:59 +0200 Subject: [PATCH] More work on directional key handling. Added tests, most passing - one failing in both arrows and tabs. --- Perspex.Input/KeyboardNavigationHandler.cs | 34 +- .../KeyboardNavigationTests_Arrows.cs | 791 ++++++++++++++++++ .../KeyboardNavigationTests_Tab.cs | 27 +- .../Perspex.Input.UnitTests.csproj | 1 + 4 files changed, 833 insertions(+), 20 deletions(-) create mode 100644 Tests/Perspex.Input.UnitTests/KeyboardNavigationTests_Arrows.cs diff --git a/Perspex.Input/KeyboardNavigationHandler.cs b/Perspex.Input/KeyboardNavigationHandler.cs index 718e1bcca2..1825af71e0 100644 --- a/Perspex.Input/KeyboardNavigationHandler.cs +++ b/Perspex.Input/KeyboardNavigationHandler.cs @@ -61,21 +61,15 @@ namespace Perspex.Input if (container != null) { - KeyboardNavigationMode mode; - - if (direction == FocusNavigationDirection.Next || direction == FocusNavigationDirection.Previous) - { - mode = KeyboardNavigation.GetTabNavigation((InputElement)container); - } - else - { - mode = KeyboardNavigation.GetDirectionalNavigation((InputElement)container); - } - - bool forward = direction == FocusNavigationDirection.Next || + var tab = direction == FocusNavigationDirection.Next || + direction == FocusNavigationDirection.Previous; + var forward = direction == FocusNavigationDirection.Next || direction == FocusNavigationDirection.Last || direction == FocusNavigationDirection.Right || direction == FocusNavigationDirection.Down; + var mode = tab ? + KeyboardNavigation.GetTabNavigation((InputElement)container) : + KeyboardNavigation.GetDirectionalNavigation((InputElement)container); switch (mode) { @@ -88,7 +82,7 @@ namespace Perspex.Input case KeyboardNavigationMode.Contained: return GetNextInContainer(element, container, direction); default: - return GetFirstInNextContainer(container, forward); + return tab ? GetFirstInNextContainer(container, forward) : null; } } else @@ -202,13 +196,17 @@ namespace Perspex.Input IInputElement container, FocusNavigationDirection direction) { - var descendent = GetFocusableDescendents(element).FirstOrDefault(); - - if (descendent != null) + if (direction == FocusNavigationDirection.Next || direction == FocusNavigationDirection.Down) { - return descendent; + var descendent = GetFocusableDescendents(element).FirstOrDefault(); + + if (descendent != null) + { + return descendent; + } } - else if (container != null) + + if (container != null) { var navigable = container as INavigableContainer; diff --git a/Tests/Perspex.Input.UnitTests/KeyboardNavigationTests_Arrows.cs b/Tests/Perspex.Input.UnitTests/KeyboardNavigationTests_Arrows.cs new file mode 100644 index 0000000000..05c1fbccdc --- /dev/null +++ b/Tests/Perspex.Input.UnitTests/KeyboardNavigationTests_Arrows.cs @@ -0,0 +1,791 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input.UnitTests +{ + using Perspex.Controls; + using Xunit; + + public class KeyboardNavigationTests_Arrows + { + [Fact] + public void Down_Continue_Returns_Down_Control_In_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + new Button { Name = "Button1" }, + (current = new Button { Name = "Button2" }), + (next = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Down); + + Assert.Equal(next, result); + } + + [Fact] + public void Down_Continue_Returns_First_Control_In_Down_Sibling_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (current = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + (next = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Down); + + Assert.Equal(next, result); + } + + [Fact] + public void Down_Continue_Returns_Down_Sibling() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (current = new Button { Name = "Button3" }), + } + }), + (next = new Button { Name = "Button4" }), + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Down); + + Assert.Equal(next, result); + } + + [Fact] + public void Down_Continue_Returns_First_Control_In_Down_Uncle_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (current = new Button { Name = "Button3" }), + } + }), + }, + }, + new StackPanel + { + Children = new Controls + { + (next = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Down); + + Assert.Equal(next, result); + } + + [Fact] + public void Down_Continue_Returns_Child_Of_Top_Level() + { + Button next; + + var top = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + (next = new Button { Name = "Button1" }), + } + }; + + var result = KeyboardNavigationHandler.GetNext(top, FocusNavigationDirection.Down); + + Assert.Equal(next, result); + } + + [Fact] + public void Down_Continue_Wraps() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + (next = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + new Button { Name = "Button3" }, + } + }), + }, + }, + new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + (current = new Button { Name = "Button6" }), + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Down); + + Assert.Equal(next, result); + } + + [Fact] + public void Down_Cycle_Returns_Down_Control_In_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = new Controls + { + new Button { Name = "Button1" }, + (current = new Button { Name = "Button2" }), + (next = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Down); + + Assert.Equal(next, result); + } + + [Fact] + public void Down_Cycle_Wraps_To_First() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = new Controls + { + (next = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + (current = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Down); + + Assert.Equal(next, result); + } + + [Fact] + public void Down_Contained_Returns_Down_Control_In_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, + Children = new Controls + { + new Button { Name = "Button1" }, + (current = new Button { Name = "Button2" }), + (next = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Down); + + Assert.Equal(next, result); + } + + [Fact] + public void Down_Contained_Stops_At_End() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, + Children = new Controls + { + (next = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + (current = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Down); + + Assert.Null(result); + } + + [Fact] + public void Down_None_Does_Nothing() + { + StackPanel container; + Button current; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.None, + Children = new Controls + { + new Button { Name = "Button1" }, + (current = new Button { Name = "Button2" }), + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Down); + + Assert.Null(result); + } + + [Fact] + public void Up_Continue_Returns_Up_Control_In_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + new Button { Name = "Button1" }, + (next = new Button { Name = "Button2" }), + (current = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Up); + + Assert.Equal(next, result); + } + + [Fact] + public void Up_Continue_Returns_Last_Control_In_Up_Sibling_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (next = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + (current = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Up); + + Assert.Equal(next, result); + } + + [Fact] + public void Up_Continue_Returns_Last_Child_Of_Sibling() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (next = new Button { Name = "Button3" }), + } + }), + (current = new Button { Name = "Button4" }), + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Up); + + Assert.Equal(next, result); + } + + [Fact] + public void Up_Continue_Returns_Last_Control_In_Up_Nephew_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (next = new Button { Name = "Button3" }), + } + }), + }, + }, + new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + (current = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Up); + + Assert.Equal(next, result); + } + + [Fact] + public void Up_Continue_Wraps() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, + Children = new Controls + { + (current = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + new Button { Name = "Button3" }, + } + }), + }, + }, + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + (next = new Button { Name = "Button6" }), + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Up); + + Assert.Equal(next, result); + } + + [Fact] + public void Up_Cycle_Returns_Up_Control_In_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = new Controls + { + (next = new Button { Name = "Button1" }), + (current = new Button { Name = "Button2" }), + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Up); + + Assert.Equal(next, result); + } + + [Fact] + public void Up_Cycle_Wraps_To_Last() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = new Controls + { + (current = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + (next = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Up); + + Assert.Equal(next, result); + } + + [Fact] + public void Up_Contained_Returns_Up_Control_In_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, + Children = new Controls + { + (next = new Button { Name = "Button1" }), + (current = new Button { Name = "Button2" }), + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Up); + + Assert.Equal(next, result); + } + + [Fact] + public void Up_Contained_Stops_At_Beginning() + { + StackPanel container; + Button current; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, + Children = new Controls + { + (current = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Up); + + Assert.Null(result); + } + + [Fact] + public void Up_Contained_Doesnt_Select_Child_Control() + { + Decorator current; + + var top = new StackPanel + { + [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, + Children = new Controls + { + (current = new Decorator + { + Focusable = true, + Child = new Button(), + }) + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Up); + + Assert.Null(result); + } + } +} diff --git a/Tests/Perspex.Input.UnitTests/KeyboardNavigationTests_Tab.cs b/Tests/Perspex.Input.UnitTests/KeyboardNavigationTests_Tab.cs index 6eb06966fb..606e6c9d9f 100644 --- a/Tests/Perspex.Input.UnitTests/KeyboardNavigationTests_Tab.cs +++ b/Tests/Perspex.Input.UnitTests/KeyboardNavigationTests_Tab.cs @@ -449,7 +449,7 @@ namespace Perspex.Input.UnitTests } [Fact] - public void Next_Never_Moves_To_Next_Container() + public void Next_None_Moves_To_Next_Container() { StackPanel container; Button current; @@ -487,7 +487,7 @@ namespace Perspex.Input.UnitTests } [Fact] - public void Next_Never_Skips_Container() + public void Next_None_Skips_Container() { StackPanel container; Button current; @@ -982,5 +982,28 @@ namespace Perspex.Input.UnitTests Assert.Equal(next, result); } + + [Fact] + public void Previous_Contained_Doesnt_Select_Child_Control() + { + Decorator current; + + var top = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, + Children = new Controls + { + (current = new Decorator + { + Focusable = true, + Child = new Button(), + }) + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, FocusNavigationDirection.Previous); + + Assert.Null(result); + } } } diff --git a/Tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj b/Tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj index 66082fdad8..8ce4cfd8cb 100644 --- a/Tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj +++ b/Tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj @@ -60,6 +60,7 @@ +