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 @@
+