Browse Source

Fixed input hit test Z ordering.

Closes #170.
pull/242/merge
Steven Kirk 10 years ago
parent
commit
92e6274ec3
  1. 8
      src/Perspex.Input/IInputElement.cs
  2. 11
      src/Perspex.Input/InputElement.cs
  3. 59
      src/Perspex.Input/InputExtensions.cs
  4. 5
      src/Perspex.SceneGraph/Visual.cs
  5. 129
      tests/Perspex.Input.UnitTests/InputElement_HitTesting.cs
  6. 1
      tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj

8
src/Perspex.Input/IInputElement.cs

@ -114,14 +114,8 @@ namespace Perspex.Input
void Focus();
/// <summary>
/// Returns the input element that can be found within the current control at the specified
/// position.
/// Gets the key bindings for the element.
/// </summary>
/// <param name="p">The position, in control coordinates.</param>
/// <returns>The <see cref="IInputElement"/> at the specified position.</returns>
IInputElement InputHitTest(Point p);
List<KeyBinding> KeyBindings { get; }
}
}

11
src/Perspex.Input/InputElement.cs

@ -339,17 +339,6 @@ namespace Perspex.Input
public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
/// <summary>
/// Returns the input element that can be found within the current control at the specified
/// position.
/// </summary>
/// <param name="p">The position, in control coordinates.</param>
/// <returns>The <see cref="IInputElement"/> at the specified position.</returns>
public IInputElement InputHitTest(Point p)
{
return this.GetInputElementsAt(p).FirstOrDefault();
}
/// <summary>
/// Focuses the control.
/// </summary>

59
src/Perspex.Input/InputExtensions.cs

@ -7,8 +7,19 @@ using System.Linq;
namespace Perspex.Input
{
/// <summary>
/// Defines extensions for the <see cref="IInputElement"/> interface.
/// </summary>
public static class InputExtensions
{
/// <summary>
/// Returns the active input elements at a point on an <see cref="IInputElement"/>.
/// </summary>
/// <param name="element">The element to test.</param>
/// <param name="p">The point on <paramref name="element"/>.</param>
/// <returns>
/// The active input elements found at the point, ordered topmost first.
/// </returns>
public static IEnumerable<IInputElement> GetInputElementsAt(this IInputElement element, Point p)
{
Contract.Requires<ArgumentNullException>(element != null);
@ -22,7 +33,7 @@ namespace Perspex.Input
if (element.VisualChildren.Any())
{
foreach (var child in element.VisualChildren.OfType<IInputElement>())
foreach (var child in ZSort(element.VisualChildren.OfType<IInputElement>()))
{
foreach (var result in child.GetInputElementsAt(p))
{
@ -34,5 +45,51 @@ namespace Perspex.Input
yield return element;
}
}
/// <summary>
/// Returns the topmost active input element at a point on an <see cref="IInputElement"/>.
/// </summary>
/// <param name="element">The element to test.</param>
/// <param name="p">The point on <paramref name="element"/>.</param>
/// <returns>The topmost <see cref="IInputElement"/> at the specified position.</returns>
public static IInputElement InputHitTest(this IInputElement element, Point p)
{
return element.GetInputElementsAt(p).First();
}
private static IEnumerable<IInputElement> ZSort(IEnumerable<IInputElement> 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<ZOrderElement>
{
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;
}
}
}
}
}

5
src/Perspex.SceneGraph/Visual.cs

@ -192,6 +192,11 @@ namespace Perspex
/// <summary>
/// Gets the Z index of the node.
/// </summary>
/// <remarks>
/// Controls with a higher <see cref="ZIndex"/> 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.
/// </remarks>
public int ZIndex
{
get { return GetValue(ZIndexProperty); }

129
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);
}
}
}

1
tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj

@ -55,6 +55,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="InputElement_HitTesting.cs" />
<Compile Include="KeyboardNavigationTests_Arrows.cs" />
<Compile Include="KeyboardNavigationTests_Tab.cs" />
<Compile Include="KeyGestureParseTests.cs" />

Loading…
Cancel
Save