diff --git a/src/Perspex.Input/IInputElement.cs b/src/Perspex.Input/IInputElement.cs
index d6e5378f02..e8658c99b0 100644
--- a/src/Perspex.Input/IInputElement.cs
+++ b/src/Perspex.Input/IInputElement.cs
@@ -114,14 +114,8 @@ namespace Perspex.Input
void Focus();
///
- /// Returns the input element that can be found within the current control at the specified
- /// position.
+ /// Gets the key bindings for the element.
///
- /// The position, in control coordinates.
- /// The at the specified position.
- IInputElement InputHitTest(Point p);
-
-
List KeyBindings { get; }
}
}
diff --git a/src/Perspex.Input/InputElement.cs b/src/Perspex.Input/InputElement.cs
index 43cfc840c6..7e0907c9e8 100644
--- a/src/Perspex.Input/InputElement.cs
+++ b/src/Perspex.Input/InputElement.cs
@@ -339,17 +339,6 @@ namespace Perspex.Input
public List KeyBindings { get; } = new List();
- ///
- /// Returns the input element that can be found within the current control at the specified
- /// position.
- ///
- /// The position, in control coordinates.
- /// The at the specified position.
- public IInputElement InputHitTest(Point p)
- {
- return this.GetInputElementsAt(p).FirstOrDefault();
- }
-
///
/// Focuses the control.
///
diff --git a/src/Perspex.Input/InputExtensions.cs b/src/Perspex.Input/InputExtensions.cs
index ef3f848073..50d826b2a9 100644
--- a/src/Perspex.Input/InputExtensions.cs
+++ b/src/Perspex.Input/InputExtensions.cs
@@ -7,8 +7,19 @@ using System.Linq;
namespace Perspex.Input
{
+ ///
+ /// Defines extensions for the interface.
+ ///
public static class InputExtensions
{
+ ///
+ /// Returns the active input elements at a point on an .
+ ///
+ /// The element to test.
+ /// The point on .
+ ///
+ /// The active input elements found at the point, ordered topmost first.
+ ///
public static IEnumerable GetInputElementsAt(this IInputElement element, Point p)
{
Contract.Requires(element != null);
@@ -22,7 +33,7 @@ namespace Perspex.Input
if (element.VisualChildren.Any())
{
- foreach (var child in element.VisualChildren.OfType())
+ foreach (var child in ZSort(element.VisualChildren.OfType()))
{
foreach (var result in child.GetInputElementsAt(p))
{
@@ -34,5 +45,51 @@ namespace Perspex.Input
yield return element;
}
}
+
+ ///
+ /// Returns the topmost active input element at a point on an .
+ ///
+ /// The element to test.
+ /// The point on .
+ /// The topmost at the specified position.
+ public static IInputElement InputHitTest(this IInputElement element, Point p)
+ {
+ return element.GetInputElementsAt(p).First();
+ }
+
+ private static IEnumerable ZSort(IEnumerable elements)
+ {
+ return elements
+ .Select((element, index) => new ZOrderElement
+ {
+ Element = element,
+ Index = index,
+ ZIndex = element.ZIndex,
+ })
+ .OrderBy(x => x, null)
+ .Select(x => x.Element);
+
+ }
+
+ private class ZOrderElement : IComparable
+ {
+ public IInputElement Element { get; set; }
+ public int Index { get; set; }
+ public int ZIndex { get; set; }
+
+ public int CompareTo(ZOrderElement other)
+ {
+ var z = other.ZIndex - ZIndex;
+
+ if (z != 0)
+ {
+ return z;
+ }
+ else
+ {
+ return other.Index - Index;
+ }
+ }
+ }
}
}
diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs
index e835c6a76c..9050f76a4f 100644
--- a/src/Perspex.SceneGraph/Visual.cs
+++ b/src/Perspex.SceneGraph/Visual.cs
@@ -192,6 +192,11 @@ namespace Perspex
///
/// Gets the Z index of the node.
///
+ ///
+ /// Controls with a higher will appear in front of controls with
+ /// a lower ZIndex. If two controls have the same ZIndex then the control that appears
+ /// later in the containing element's children collection will appear on top.
+ ///
public int ZIndex
{
get { return GetValue(ZIndexProperty); }
diff --git a/tests/Perspex.Input.UnitTests/InputElement_HitTesting.cs b/tests/Perspex.Input.UnitTests/InputElement_HitTesting.cs
new file mode 100644
index 0000000000..aeb11aa413
--- /dev/null
+++ b/tests/Perspex.Input.UnitTests/InputElement_HitTesting.cs
@@ -0,0 +1,129 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Perspex.Controls;
+using Perspex.Layout;
+using Xunit;
+
+namespace Perspex.Input.UnitTests
+{
+ public class InputElement_HitTesting
+ {
+ [Fact]
+ public void InputHitTest_Should_Find_Control_At_Point()
+ {
+ var container = new Decorator
+ {
+ Width = 200,
+ Height = 200,
+ Child = new Border
+ {
+ Width = 100,
+ Height = 100,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
+ };
+
+ container.Measure(Size.Infinity);
+ container.Arrange(new Rect(container.DesiredSize));
+
+ var result = container.InputHitTest(new Point(100, 100));
+
+ Assert.Equal(container.Child, result);
+ }
+
+ [Fact]
+ public void InputHitTest_Should_Not_Find_Control_Outside_Point()
+ {
+ var container = new Decorator
+ {
+ Width = 200,
+ Height = 200,
+ Child = new Border
+ {
+ Width = 100,
+ Height = 100,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
+ };
+
+ container.Measure(Size.Infinity);
+ container.Arrange(new Rect(container.DesiredSize));
+
+ var result = container.InputHitTest(new Point(10, 10));
+
+ Assert.Equal(container, result);
+ }
+
+ [Fact]
+ public void InputHitTest_Should_Find_Top_Control_At_Point()
+ {
+ var container = new Panel
+ {
+ Width = 200,
+ Height = 200,
+ Children = new Controls.Controls
+ {
+ new Border
+ {
+ Width = 100,
+ Height = 100,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ },
+ new Border
+ {
+ Width = 50,
+ Height = 50,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
+ }
+ };
+
+ container.Measure(Size.Infinity);
+ container.Arrange(new Rect(container.DesiredSize));
+
+ var result = container.InputHitTest(new Point(100, 100));
+
+ Assert.Equal(container.Children[1], result);
+ }
+
+ [Fact]
+ public void InputHitTest_Should_Find_Top_Control_At_Point_With_ZOrder()
+ {
+ var container = new Panel
+ {
+ Width = 200,
+ Height = 200,
+ Children = new Controls.Controls
+ {
+ new Border
+ {
+ Width = 100,
+ Height = 100,
+ ZIndex = 1,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ },
+ new Border
+ {
+ Width = 50,
+ Height = 50,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
+ }
+ };
+
+ container.Measure(Size.Infinity);
+ container.Arrange(new Rect(container.DesiredSize));
+
+ var result = container.InputHitTest(new Point(100, 100));
+
+ Assert.Equal(container.Children[0], result);
+ }
+ }
+}
diff --git a/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj b/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj
index 1aff689782..051e2a966f 100644
--- a/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj
+++ b/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj
@@ -55,6 +55,7 @@
+