|
|
@ -5,6 +5,7 @@ using System; |
|
|
using System.Collections.Generic; |
|
|
using System.Collections.Generic; |
|
|
using System.Linq; |
|
|
using System.Linq; |
|
|
using Avalonia.VisualTree; |
|
|
using Avalonia.VisualTree; |
|
|
|
|
|
using Collections.Pooled; |
|
|
|
|
|
|
|
|
namespace Avalonia.Rendering.SceneGraph |
|
|
namespace Avalonia.Rendering.SceneGraph |
|
|
{ |
|
|
{ |
|
|
@ -158,37 +159,63 @@ namespace Avalonia.Rendering.SceneGraph |
|
|
return result; |
|
|
return result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private IEnumerable<IVisual> HitTest(IVisualNode node, Point p, Rect? clip, Func<IVisual, bool> filter) |
|
|
private IEnumerable<IVisual> HitTest(IVisualNode root, Point p, Rect? rootClip, Func<IVisual, bool> filter) |
|
|
{ |
|
|
{ |
|
|
if (filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree) |
|
|
bool FilterAndClip(IVisualNode node, ref Rect? clip) |
|
|
{ |
|
|
{ |
|
|
var clipped = false; |
|
|
if (filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree) |
|
|
|
|
|
|
|
|
if (node.ClipToBounds) |
|
|
|
|
|
{ |
|
|
{ |
|
|
clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds); |
|
|
var clipped = false; |
|
|
clipped = !clip.Value.Contains(p); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (node.GeometryClip != null) |
|
|
if (node.ClipToBounds) |
|
|
{ |
|
|
{ |
|
|
var controlPoint = Root.Visual.TranslatePoint(p, node.Visual); |
|
|
clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds); |
|
|
clipped = !node.GeometryClip.FillContains(controlPoint.Value); |
|
|
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; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
else if (FilterAndClip(current, ref currentClip)) |
|
|
if (node.HitTest(p)) |
|
|
|
|
|
{ |
|
|
{ |
|
|
yield return node.Visual; |
|
|
nodeStack.Push((current, true, default)); |
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < childCount; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
nodeStack.Push((current.Children[i], false, currentClip)); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|