Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ICustomHitTest #5923

Merged
merged 7 commits into from
May 18, 2021
5 changes: 1 addition & 4 deletions src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@ namespace Avalonia.Rendering
/// </summary>
public interface ICustomSimpleHitTest
{
/// <param name="point">The point to hit test in global coordinate space.</param>
bool HitTest(Point point);
}

/// <summary>
/// Allows customization of hit-testing for all renderers.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public interface ICustomHitTest : ICustomSimpleHitTest
{
}
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ private static Rect GetTransformedBounds(IVisual visual)

if (filter?.Invoke(visual) != false)
{
bool containsPoint = false;
bool containsPoint;

if (visual is ICustomSimpleHitTest custom)
{
Expand Down
6 changes: 3 additions & 3 deletions src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ public bool MoveNext()

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;

Expand Down Expand Up @@ -311,8 +312,7 @@ private bool FilterAndClip(IVisualNode node, ref Rect? clip)

if (!clipped && node.Visual is ICustomHitTest custom)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about ICustomSimpleHitTest ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch

{
var controlPoint = _sceneRoot.Visual.TranslatePoint(_point, node.Visual);
clipped = !custom.HitTest(controlPoint.Value);
clipped = !custom.HitTest(_point);
}

return !clipped;
Expand Down
17 changes: 17 additions & 0 deletions tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,8 +12,6 @@
using Avalonia.VisualTree;
using Moq;
using Xunit;
using System;
using Avalonia.Controls.Shapes;

namespace Avalonia.Visuals.UnitTests.Rendering
{
Expand Down Expand Up @@ -501,6 +502,42 @@ public void HitTest_Should_Respect_Geometry_Clip()
}
}

[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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,43 @@ public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport()
}
}

[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);
Expand Down