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)); + } } } }