Skip to content

Commit

Permalink
Merge pull request #5923 from AvaloniaUI/fixes/5879-icustomhittest
Browse files Browse the repository at this point in the history
Fix ICustomHitTest
  • Loading branch information
maxkatz6 authored and Dan Walmsley committed May 19, 2021
1 parent 95173f6 commit bf6355e
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 11 deletions.
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)
{
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

0 comments on commit bf6355e

Please sign in to comment.