diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionHitTestAabbTree.cs b/src/Avalonia.Base/Rendering/Composition/CompositionHitTestAabbTree.cs index c439d26a9b..b7f460754f 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionHitTestAabbTree.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionHitTestAabbTree.cs @@ -7,6 +7,11 @@ namespace Avalonia.Rendering.Composition; internal sealed class CompositionHitTestAabbTree { + internal interface IQueryHitTester + { + CompositionVisual? HitTest(CompositionVisual visual); + } + private const int Null = -1; private const double FatBoundsPadding = 1; private static readonly CandidateComparer s_candidateComparer = new(); @@ -124,6 +129,56 @@ internal sealed class CompositionHitTestAabbTree _queryCandidates.Clear(); } + public CompositionVisual? QueryFirst(Point point, ref T hitTest) + where T : struct, IQueryHitTester + { + _queryCandidates.Clear(); + + if (_root != Null) + { + var stackCount = 0; + PushQueryNode(ref stackCount, _root); + + while (stackCount > 0) + { + var nodeIndex = _queryStack[--stackCount]; + var node = _nodes[nodeIndex]; + + if (!node.Bounds.Contains(point)) + continue; + + if (node.IsLeaf) + { + if (node.Visual != null) + _queryCandidates.Add(new Candidate(node.Visual, node.Order)); + } + else + { + PushQueryNode(ref stackCount, node.Child1); + PushQueryNode(ref stackCount, node.Child2); + } + } + } + + foreach (var candidate in _unbounded) + _queryCandidates.Add(new Candidate(candidate.Key, candidate.Value)); + + _queryCandidates.Sort(s_candidateComparer); + + foreach (var candidate in _queryCandidates) + { + var hit = hitTest.HitTest(candidate.Visual); + if (hit != null) + { + _queryCandidates.Clear(); + return hit; + } + } + + _queryCandidates.Clear(); + return null; + } + private void Add(CompositionVisual visual, int order) { var state = GetBoundsState(visual, out var bounds); diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index 5dd99734bc..d8a0233abf 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -189,23 +189,12 @@ namespace Avalonia.Rendering.Composition var queriedIndexedChildren = false; if (cv.Children.Count >= CompositionContainerVisual.HitTestAabbTreeThreshold) { - var candidates = RentHitTestChildCandidates(out var releaseToField); - try + var query = new FirstHitTestQuery(this, point, filter, resultFilter); + if (cv.TryQueryFirstHitTestChild(point, ref query, out var hit)) { - if (cv.TryQueryHitTestChildren(point, candidates)) - { - queriedIndexedChildren = true; - foreach (var child in candidates) - { - var hit = HitTestFirstCore(child, point, filter, resultFilter); - if (hit != null) - return hit; - } - } - } - finally - { - ReleaseHitTestChildCandidates(candidates, releaseToField); + queriedIndexedChildren = true; + if (hit != null) + return hit; } } @@ -223,6 +212,16 @@ namespace Avalonia.Rendering.Composition return visual.HitTest(point) && (resultFilter == null || resultFilter(visual)) ? visual : null; } + private readonly struct FirstHitTestQuery( + CompositionTarget target, + Point point, + Func? filter, + Func? resultFilter) : CompositionHitTestAabbTree.IQueryHitTester + { + public CompositionVisual? HitTest(CompositionVisual visual) => + target.HitTestFirstCore(visual, point, filter, resultFilter); + } + /// /// Registers the composition target for explicit redraw /// diff --git a/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs index 94a8424666..34a55f2ad5 100644 --- a/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs @@ -8,7 +8,7 @@ namespace Avalonia.Rendering.Composition /// public partial class CompositionContainerVisual : CompositionVisual { - internal static readonly int HitTestAabbTreeThreshold = CompositionHitTestAabbTree.IsEnabled ? 1 : int.MaxValue; + internal static readonly int HitTestAabbTreeThreshold = CompositionHitTestAabbTree.IsEnabled ? 32 : int.MaxValue; private CompositionHitTestAabbTree? _hitTestChildren; private bool _hitTestChildrenDirty = true; @@ -67,5 +67,28 @@ namespace Avalonia.Rendering.Composition _hitTestChildren.Query(point, results); return true; } + + internal bool TryQueryFirstHitTestChild(Point point, ref T hitTest, out CompositionVisual? hit) + where T : struct, CompositionHitTestAabbTree.IQueryHitTester + { + if (Children.Count < HitTestAabbTreeThreshold) + { + _hitTestChildren?.Clear(); + _hitTestChildren = null; + hit = null; + return false; + } + + _hitTestChildren ??= new CompositionHitTestAabbTree(); + + if (_hitTestChildrenDirty) + { + _hitTestChildren.Rebuild(Children); + _hitTestChildrenDirty = false; + } + + hit = _hitTestChildren.QueryFirst(point, ref hitTest); + return true; + } } }