diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index 5434a35464..f7befa646a 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -106,7 +106,7 @@ namespace Avalonia.Rendering
public void Dispose() => Stop();
///
- public IEnumerable HitTest(Point p, Func filter)
+ public IEnumerable HitTest(Point p, IVisual root, Func filter)
{
if (_renderLoop == null && (_dirty == null || _dirty.Count > 0))
{
@@ -114,7 +114,7 @@ namespace Avalonia.Rendering
UpdateScene();
}
- return _scene?.HitTest(p, filter) ?? Enumerable.Empty();
+ return _scene?.HitTest(p, root, filter) ?? Enumerable.Empty();
}
///
diff --git a/src/Avalonia.Visuals/Rendering/IRenderer.cs b/src/Avalonia.Visuals/Rendering/IRenderer.cs
index aa2413bdfb..9085e63aa9 100644
--- a/src/Avalonia.Visuals/Rendering/IRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/IRenderer.cs
@@ -33,9 +33,13 @@ namespace Avalonia.Rendering
/// Hit tests a location to find the visuals at the specified point.
///
/// The point, in client coordinates.
- /// An optional filter.
+ /// The root of the subtree to search.
+ ///
+ /// A filter predicate. If the predicate returns false then the visual and all its
+ /// children will be excluded from the results.
+ ///
/// The visuals at the specified point, topmost first.
- IEnumerable HitTest(Point p, Func filter);
+ IEnumerable HitTest(Point p, IVisual root, Func filter);
///
/// Called when a resize notification is received by the control being rendered.
diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
index 4ef2b30463..2d5a864089 100644
--- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
@@ -136,9 +136,9 @@ namespace Avalonia.Rendering
}
///
- public IEnumerable HitTest(Point p, Func filter)
+ public IEnumerable HitTest(Point p, IVisual root, Func filter)
{
- return HitTest(_root, p, filter);
+ return HitTest(root, p, filter);
}
///
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
index a4af106a73..9216bae8ad 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
@@ -113,11 +114,13 @@ namespace Avalonia.Rendering.SceneGraph
/// Gets the visuals at a point in the scene.
///
/// The point.
+ /// The root of the subtree to search.
/// A filter. May be null.
/// The visuals at the specified point.
- public IEnumerable HitTest(Point p, Func filter)
+ public IEnumerable HitTest(Point p, IVisual root, Func filter)
{
- return HitTest(Root, p, null, filter);
+ var node = FindNode(root);
+ return (node != null) ? HitTest(node, p, null, filter) : Enumerable.Empty();
}
///
diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
index 4f4bf4b9fe..2d417852fe 100644
--- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
+++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
@@ -133,7 +133,7 @@ namespace Avalonia.VisualTree
var root = visual.GetVisualRoot();
p = visual.TranslatePoint(p, root);
- return root.Renderer.HitTest(p, filter);
+ return root.Renderer.HitTest(p, visual, filter);
}
///
diff --git a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs
index 6aca69b88f..7764c47dbf 100644
--- a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs
+++ b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs
@@ -59,7 +59,7 @@ namespace Avalonia.Input.UnitTests
}
};
- renderer.Setup(x => x.HitTest(It.IsAny(), It.IsAny>()))
+ renderer.Setup(x => x.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
.Returns(new[] { decorator });
inputManager.ProcessInput(new RawMouseEventArgs(
@@ -75,7 +75,7 @@ namespace Avalonia.Input.UnitTests
Assert.False(canvas.IsPointerOver);
Assert.True(root.IsPointerOver);
- renderer.Setup(x => x.HitTest(It.IsAny(), It.IsAny>()))
+ renderer.Setup(x => x.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
.Returns(new[] { canvas });
inputManager.ProcessInput(new RawMouseEventArgs(
diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs
index 12ac8d3af6..979127cd14 100644
--- a/tests/Avalonia.LeakTests/ControlTests.cs
+++ b/tests/Avalonia.LeakTests/ControlTests.cs
@@ -357,7 +357,7 @@ namespace Avalonia.LeakTests
{
}
- public IEnumerable HitTest(Point p, Func filter) => null;
+ public IEnumerable HitTest(Point p, IVisual root, Func filter) => null;
public void Paint(Rect rect)
{
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
index 1044c1f2a3..9e2f1fc293 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
@@ -42,7 +42,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
- var result = root.Renderer.HitTest(new Point(100, 100), null);
+ var result = root.Renderer.HitTest(new Point(100, 100), root, null);
Assert.Equal(new[] { root.Child }, result);
}
@@ -70,7 +70,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
- var result = root.Renderer.HitTest(new Point(100, 100), null);
+ var result = root.Renderer.HitTest(new Point(100, 100), root, null);
Assert.Empty(result);
}
@@ -107,7 +107,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
- var result = root.Renderer.HitTest(new Point(100, 100), null);
+ var result = root.Renderer.HitTest(new Point(100, 100), root, null);
Assert.Empty(result);
}
@@ -136,7 +136,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
- var result = root.Renderer.HitTest(new Point(10, 10), null);
+ var result = root.Renderer.HitTest(new Point(10, 10), root, null);
Assert.Empty(result);
}
@@ -180,7 +180,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
- var result = root.Renderer.HitTest(new Point(100, 100), null);
+ var result = root.Renderer.HitTest(new Point(100, 100), root, null);
Assert.Equal(new[] { container.Children[1], container.Children[0] }, result);
}
@@ -234,7 +234,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
- var result = root.Renderer.HitTest(new Point(100, 100), null);
+ var result = root.Renderer.HitTest(new Point(100, 100), root, null);
Assert.Equal(new[] { container.Children[2], container.Children[0], container.Children[1] }, result);
}
@@ -283,7 +283,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
- var result = root.Renderer.HitTest(new Point(120, 120), null);
+ var result = root.Renderer.HitTest(new Point(120, 120), root, null);
Assert.Equal(new IVisual[] { target, container }, result);
}
@@ -331,7 +331,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
- var result = root.Renderer.HitTest(new Point(50, 50), null);
+ var result = root.Renderer.HitTest(new Point(50, 50), root, null);
Assert.Equal(new[] { container }, result);
}
@@ -404,11 +404,11 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
- var result = root.Renderer.HitTest(new Point(50, 150), null).First();
+ var result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
Assert.Equal(item1, result);
- result = root.Renderer.HitTest(new Point(50, 50), null).First();
+ result = root.Renderer.HitTest(new Point(50, 50), root, null).First();
Assert.Equal(target, result);
@@ -419,10 +419,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering
container.InvalidateArrange();
container.Arrange(new Rect(container.DesiredSize));
- result = root.Renderer.HitTest(new Point(50, 150), null).First();
+ result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
Assert.Equal(item2, result);
- result = root.Renderer.HitTest(new Point(50, 50), null).First();
+ result = root.Renderer.HitTest(new Point(50, 50), root, null).First();
Assert.Equal(target, result);
}
}
@@ -452,10 +452,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var context = new DrawingContext(Mock.Of());
- var result = root.Renderer.HitTest(new Point(100, 100), null);
+ var result = root.Renderer.HitTest(new Point(100, 100), root, null);
Assert.Equal(new[] { path }, result);
- result = root.Renderer.HitTest(new Point(10, 10), null);
+ result = root.Renderer.HitTest(new Point(10, 10), root, null);
Assert.Empty(result);
}
}
@@ -492,10 +492,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var context = new DrawingContext(Mock.Of());
- var result = root.Renderer.HitTest(new Point(200, 200), null);
+ var result = root.Renderer.HitTest(new Point(200, 200), root, null);
Assert.Equal(new IVisual[] { canvas, border }, result);
- result = root.Renderer.HitTest(new Point(110, 110), null);
+ result = root.Renderer.HitTest(new Point(110, 110), root, null);
Assert.Empty(result);
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
index 2c3e9bf11a..c8a19a9f46 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
@@ -40,7 +40,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
- var result = root.Renderer.HitTest(new Point(100, 100), null);
+ var result = root.Renderer.HitTest(new Point(100, 100), root, null);
Assert.Equal(new[] { root.Child, root }, result);
}
@@ -78,7 +78,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
- var result = root.Renderer.HitTest(new Point(100, 100), null);
+ var result = root.Renderer.HitTest(new Point(100, 100), root, null);
Assert.Equal(new[] { root }, result);
}
@@ -108,7 +108,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
- var result = root.Renderer.HitTest(new Point(10, 10), null);
+ var result = root.Renderer.HitTest(new Point(10, 10), root, null);
Assert.Equal(new[] { root }, result);
}
@@ -153,7 +153,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
- var result = root.Renderer.HitTest(new Point(100, 100), null);
+ var result = root.Renderer.HitTest(new Point(100, 100), root, null);
Assert.Equal(new[] { container.Children[1], container.Children[0], container, root }, result);
}
@@ -208,7 +208,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
- var result = root.Renderer.HitTest(new Point(100, 100), null);
+ var result = root.Renderer.HitTest(new Point(100, 100), root, null);
Assert.Equal(
new[]
@@ -267,7 +267,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
container.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
- var result = root.Renderer.HitTest(new Point(120, 120), null);
+ var result = root.Renderer.HitTest(new Point(120, 120), root, null);
Assert.Equal(new IVisual[] { target, container }, result);
}
@@ -316,7 +316,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
- var result = root.Renderer.HitTest(new Point(50, 50), null);
+ var result = root.Renderer.HitTest(new Point(50, 50), root, null);
Assert.Equal(new IVisual[] { container, root }, result);
}
@@ -390,11 +390,11 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
- var result = root.Renderer.HitTest(new Point(50, 150), null).First();
+ var result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
Assert.Equal(item1, result);
- result = root.Renderer.HitTest(new Point(50, 50), null).First();
+ result = root.Renderer.HitTest(new Point(50, 50), root, null).First();
Assert.Equal(target, result);
@@ -406,10 +406,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering
container.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
- result = root.Renderer.HitTest(new Point(50, 150), null).First();
+ result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
Assert.Equal(item2, result);
- result = root.Renderer.HitTest(new Point(50, 50), null).First();
+ result = root.Renderer.HitTest(new Point(50, 50), root, null).First();
Assert.Equal(target, result);
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs
new file mode 100644
index 0000000000..867d4d7450
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Rendering;
+using Avalonia.UnitTests;
+using Avalonia.VisualTree;
+using Xunit;
+
+namespace Avalonia.Visuals.UnitTests.VisualTree
+{
+ public class VisualExtensions_GetVisualsAt
+ {
+ [Fact]
+ public void Should_Find_Control()
+ {
+ using (TestApplication())
+ {
+ Border target;
+ var root = new TestRoot
+ {
+ Width = 200,
+ Height = 200,
+ Child = new StackPanel
+ {
+ Background = Brushes.White,
+ Children =
+ {
+ (target = new Border
+ {
+ Width = 100,
+ Height = 200,
+ Background = Brushes.Red,
+ }),
+ new Border
+ {
+ Width = 100,
+ Height = 200,
+ Background = Brushes.Green,
+ }
+ },
+ Orientation = Orientation.Horizontal,
+ }
+ };
+
+ root.Renderer = new DeferredRenderer(root, null);
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
+
+ var result = target.GetVisualsAt(new Point(50, 50));
+
+ Assert.Same(target, result.Single());
+ }
+ }
+
+ [Fact]
+ public void Should_Not_Find_Sibling_Control()
+ {
+ using (TestApplication())
+ {
+ Border target;
+ var root = new TestRoot
+ {
+ Width = 200,
+ Height = 200,
+ Child = new StackPanel
+ {
+ Background = Brushes.White,
+ Children =
+ {
+ (target = new Border
+ {
+ Width = 100,
+ Height = 200,
+ Background = Brushes.Red,
+ }),
+ new Border
+ {
+ Width = 100,
+ Height = 200,
+ Background = Brushes.Green,
+ }
+ },
+ Orientation = Orientation.Horizontal,
+ }
+ };
+
+ root.Renderer = new DeferredRenderer(root, null);
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
+
+ var result = target.GetVisualsAt(new Point(150, 50));
+
+ Assert.Empty(result);
+ }
+ }
+
+ private IDisposable TestApplication()
+ {
+ return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
+ }
+ }
+}