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