From bf6355e03201b24a4331e97e490e8e3b821e3b58 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 17 May 2021 20:26:32 -0400 Subject: [PATCH] Merge pull request #5923 from AvaloniaUI/fixes/5879-icustomhittest Fix ICustomHitTest --- .../Rendering/ICustomSimpleHitTest.cs | 5 +-- .../Rendering/ImmediateRenderer.cs | 2 +- .../Rendering/SceneGraph/Scene.cs | 6 +-- .../Rendering/CustomHitTestBorder.cs | 17 ++++++++ .../DeferredRendererTests_HitTesting.cs | 43 +++++++++++++++++-- .../ImmediateRendererTests_HitTesting.cs | 37 ++++++++++++++++ 6 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs diff --git a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs index 245888a351..354a344ffe 100644 --- a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs +++ b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs @@ -11,16 +11,13 @@ namespace Avalonia.Rendering /// public interface ICustomSimpleHitTest { + /// The point to hit test in global coordinate space. bool HitTest(Point point); } /// /// Allows customization of hit-testing for all renderers. /// - /// - /// Note that this interface can only used to make a portion of a control non-hittable, it - /// cannot expand the hittable area of a control. - /// public interface ICustomHitTest : ICustomSimpleHitTest { } diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 9ea1b84311..85feb06c44 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -225,7 +225,7 @@ namespace Avalonia.Rendering if (filter?.Invoke(visual) != false) { - bool containsPoint = false; + bool containsPoint; if (visual is ICustomSimpleHitTest custom) { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index 6a4c532d4a..36aa08c2f9 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -256,7 +256,8 @@ namespace Avalonia.Rendering.SceneGraph if (childCount == 0 || wasVisited) { - if ((wasVisited || FilterAndClip(node, ref clip)) && node.HitTest(_point)) + if ((wasVisited || FilterAndClip(node, ref clip)) && + (node.Visual is ICustomSimpleHitTest custom ? custom.HitTest(_point) : node.HitTest(_point))) { _current = node.Visual; @@ -311,8 +312,7 @@ namespace Avalonia.Rendering.SceneGraph if (!clipped && node.Visual is ICustomHitTest custom) { - var controlPoint = _sceneRoot.Visual.TranslatePoint(_point, node.Visual); - clipped = !custom.HitTest(controlPoint.Value); + clipped = !custom.HitTest(_point); } return !clipped; diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs new file mode 100644 index 0000000000..3221f097ad --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Rendering; + +namespace Avalonia.Visuals.UnitTests.Rendering +{ + internal class CustomHitTestBorder : Border, ICustomHitTest + { + public bool HitTest(Point point) + { + // Move hit testing window halfway to the left + return Bounds + .WithX(Bounds.X - Bounds.Width / 2) + .Contains(point); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs index 90b01e2ddb..d24d183709 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -1,6 +1,9 @@ -using System.Linq; +using System; +using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Presenters; +using Avalonia.Controls.Shapes; +using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; @@ -9,8 +12,6 @@ using Avalonia.UnitTests; using Avalonia.VisualTree; using Moq; using Xunit; -using System; -using Avalonia.Controls.Shapes; namespace Avalonia.Visuals.UnitTests.Rendering { @@ -501,6 +502,42 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } + [Fact] + public void HitTest_Should_Accommodate_ICustomHitTest() + { + using (TestApplication()) + { + Border border; + + var root = new TestRoot + { + Width = 300, + Height = 200, + Child = border = new CustomHitTestBorder + { + Width = 100, + Height = 100, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } + }; + + root.Renderer = new DeferredRenderer(root, null); + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var result = root.Renderer.HitTest(new Point(75, 100), root, null); + Assert.Equal(new[] { border }, result); + + result = root.Renderer.HitTest(new Point(125, 100), root, null); + Assert.Equal(new[] { border }, result); + + result = root.Renderer.HitTest(new Point(175, 100), root, null); + Assert.Empty(result); + } + } + private IDisposable TestApplication() { return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs index c4ef9d5ee7..a3b0a0cdd5 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs @@ -413,6 +413,43 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } + [Fact] + public void HitTest_Should_Accommodate_ICustomHitTest() + { + using (TestApplication()) + { + Border border; + + var root = new TestRoot + { + Width = 300, + Height = 200, + Child = border = new CustomHitTestBorder + { + Width = 100, + Height = 100, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } + }; + + root.Renderer = new ImmediateRenderer(root); + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + root.Renderer.Paint(new Rect(root.ClientSize)); + + var result = root.Renderer.HitTest(new Point(75, 100), root, null).First(); + Assert.Equal(border, result); + + result = root.Renderer.HitTest(new Point(125, 100), root, null).First(); + Assert.Equal(border, result); + + result = root.Renderer.HitTest(new Point(175, 100), root, null).First(); + Assert.Equal(root, result); + } + } + private IDisposable TestApplication() { return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);