diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs index 54a9ff733d..19b751f66c 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; @@ -82,8 +83,32 @@ namespace Avalonia.Rendering.SceneGraph public override bool HitTest(Point p) { - // TODO: Implement line hit testing. - return false; + var a = P1; + var b = P2; + + //If dot1 or dot2 is negative, then the angle between the perpendicular and the segment is obtuse. + //The distance from a point to a straight line is defined as the + //length of the vector formed by the point and the closest point of the segment + + Vector ap = p - a; + var dot1 = Vector.Dot(b - a, ap); + + if (dot1 < 0) + return ap.Length <= Pen.Thickness / 2; + + Vector bp = p - b; + var dot2 = Vector.Dot(a - b, bp); + + if (dot2 < 0) + return bp.Length <= Pen.Thickness / 2; + + var bXaX = b.X - a.X; + var bYaY = b.Y - a.Y; + + var distance = (bXaX * (p.Y - a.Y) - bYaY * (p.X - a.X)) / + (Math.Sqrt(bXaX * bXaX + bYaY * bYaY)); + + return Math.Abs(distance) <= Pen.Thickness / 2; } } } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/LineNodeTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/LineNodeTests.cs new file mode 100644 index 0000000000..d4d4a29dfd --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/LineNodeTests.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using Avalonia.Media; +using Avalonia.Rendering.SceneGraph; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public class LineNodeTests + { + [Fact] + public void HitTest_Should_Be_True() + { + var lineNode = new LineNode( + Matrix.Identity, + new Pen(Brushes.Black, 3), + new Point(15, 15), + new Point(150, 150)); + + + var pointsInside = new List() + { + new Point(14, 14), + new Point(15, 15), + new Point(32.1, 30), + new Point(30, 32.1), + new Point(150, 150), + new Point(151, 151), + }; + + foreach (var point in pointsInside) + { + Assert.True(lineNode.HitTest(point)); + } + } + + [Fact] + public void HitTest_Should_Be_False() + { + var lineNode = new LineNode( + Matrix.Identity, + new Pen(Brushes.Black, 3), + new Point(15, 15), + new Point(150, 150)); + + + var pointsOutside= new List() + { + new Point(13.9, 13.9), + new Point(30, 32.2), + new Point(32.2, 30), + new Point(151.1, 151.1), + new Point(200, 200), + }; + + foreach (var point in pointsOutside) + { + Assert.False(lineNode.HitTest(point)); + } + } + } +}