diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
index 2cc7741bbb..750fd4448d 100644
--- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj
+++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
@@ -3,6 +3,9 @@
netstandard2.0
Avalonia
+
+
+
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
index 1afc096c98..30f25e97ad 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.VisualTree;
+using Collections.Pooled;
namespace Avalonia.Rendering.SceneGraph
{
@@ -158,37 +159,63 @@ namespace Avalonia.Rendering.SceneGraph
return result;
}
- private IEnumerable HitTest(IVisualNode node, Point p, Rect? clip, Func filter)
+ private IEnumerable HitTest(IVisualNode root, Point p, Rect? rootClip, Func filter)
{
- if (filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree)
+ bool FilterAndClip(IVisualNode node, ref Rect? clip)
{
- var clipped = false;
-
- if (node.ClipToBounds)
+ if (filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree)
{
- clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds);
- clipped = !clip.Value.Contains(p);
- }
+ var clipped = false;
- if (node.GeometryClip != null)
- {
- var controlPoint = Root.Visual.TranslatePoint(p, node.Visual);
- clipped = !node.GeometryClip.FillContains(controlPoint.Value);
+ if (node.ClipToBounds)
+ {
+ clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds);
+ clipped = !clip.Value.Contains(p);
+ }
+
+ if (node.GeometryClip != null)
+ {
+ var controlPoint = Root.Visual.TranslatePoint(p, node.Visual);
+ clipped = !node.GeometryClip.FillContains(controlPoint.Value);
+ }
+
+ return !clipped;
}
- if (!clipped)
+ return false;
+ }
+
+ using (var nodeStack = new PooledStack<(IVisualNode, bool, Rect?)>())
+ {
+ nodeStack.Push((root, false, rootClip));
+
+ while (nodeStack.Count > 0)
{
- for (var i = node.Children.Count - 1; i >= 0; --i)
+ (IVisualNode current, var wasVisited, Rect? currentClip) = nodeStack.Pop();
+
+ if (wasVisited && current == root)
{
- foreach (var h in HitTest(node.Children[i], p, clip, filter))
+ break;
+ }
+
+ var children = current.Children;
+ int childCount = children.Count;
+
+ if (childCount == 0 || wasVisited)
+ {
+ if ((wasVisited || FilterAndClip(current, ref currentClip)) && current.HitTest(p))
{
- yield return h;
+ yield return current.Visual;
}
}
-
- if (node.HitTest(p))
+ else if (FilterAndClip(current, ref currentClip))
{
- yield return node.Visual;
+ nodeStack.Push((current, true, default));
+
+ for (var i = 0; i < childCount; i++)
+ {
+ nodeStack.Push((current.Children[i], false, currentClip));
+ }
}
}
}