using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Rendering;
using Avalonia.Utilities;
namespace Avalonia.VisualTree
{
///
/// Provides extension methods for working with the visual tree.
///
public static class VisualExtensions
{
///
/// Calculates the distance from a visual's ancestor.
///
/// The visual.
/// The ancestor visual.
///
/// The number of steps from the visual to the ancestor or -1 if
/// is not a descendent of .
///
public static int CalculateDistanceFromAncestor(this Visual visual, Visual? ancestor)
{
Visual? v = visual ?? throw new ArgumentNullException(nameof(visual));
var result = 0;
while (v != null && v != ancestor)
{
v = v.VisualParent;
result++;
}
return v != null ? result : -1;
}
///
/// Calculates the distance from a visual's root.
///
/// The visual.
///
/// The number of steps from the visual to the root.
///
public static int CalculateDistanceFromRoot(Visual visual)
{
Visual? v = visual ?? throw new ArgumentNullException(nameof(visual));
var result = 0;
v = v.VisualParent;
while (v != null)
{
v = v.VisualParent;
result++;
}
return result;
}
///
/// Tries to get the first common ancestor of two visuals.
///
/// The first visual.
/// The second visual.
/// The common ancestor, or null if not found.
public static Visual? FindCommonVisualAncestor(this Visual? visual, Visual? target)
{
if (visual is null || target is null)
{
return null;
}
void GoUpwards(ref Visual? node, int count)
{
for (int i = 0; i < count; ++i)
{
node = node?.VisualParent;
}
}
Visual? v = visual;
Visual? t = target;
// We want to find lowest node first, then make sure that both nodes are at the same height.
// By doing that we can sometimes find out that other node is our lowest common ancestor.
var firstHeight = CalculateDistanceFromRoot(v);
var secondHeight = CalculateDistanceFromRoot(t);
if (firstHeight > secondHeight)
{
GoUpwards(ref v, firstHeight - secondHeight);
}
else
{
GoUpwards(ref t, secondHeight - firstHeight);
}
if (v == t)
{
return v;
}
while (v != null && t != null)
{
Visual? firstParent = v.VisualParent;
Visual? secondParent = t.VisualParent;
if (firstParent == secondParent)
{
return firstParent;
}
v = v.VisualParent;
t = t.VisualParent;
}
return null;
}
///
/// Enumerates the ancestors of an in the visual tree.
///
/// The visual.
/// The visual's ancestors.
public static IEnumerable GetVisualAncestors(this Visual visual)
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
var v = visual.VisualParent;
while (v != null)
{
yield return v;
v = v.VisualParent;
}
}
///
/// Finds first ancestor of given type.
///
/// Ancestor type.
/// The visual.
/// If given visual should be included in search.
/// First ancestor of given type.
public static T? FindAncestorOfType(this Visual? visual, bool includeSelf = false) where T : class
{
return FindAncestorOfType(visual, includeSelf, predicate: null);
}
///
/// Finds first ancestor of given type that matches a predicate.
///
/// Ancestor type.
/// The visual.
/// If given visual should be included in search.
/// The predicate that the ancestor must match.
/// First ancestor of given type.
public static T? FindAncestorOfType(this Visual? visual, bool includeSelf, Predicate? predicate) where T : class
{
if (visual is null)
{
return null;
}
Visual? parent = includeSelf ? visual : visual.VisualParent;
while (parent != null)
{
if (parent is T result)
{
if (predicate == null || predicate(result))
{
return result;
}
}
parent = parent.VisualParent;
}
return null;
}
///
/// Finds first descendant of given type.
///
/// Descendant type.
/// The visual.
/// If given visual should be included in search.
/// First descendant of given type.
public static T? FindDescendantOfType(this Visual? visual, bool includeSelf = false) where T : class
{
return FindDescendantOfType(visual, includeSelf, predicate: null);
}
///
/// Finds first descendant of given type that matches given predicate.
///
/// Descendant type.
/// The visual.
/// If given visual should be included in search.
/// The predicate that the descendant must match.
/// First descendant of given type that matches given predicate.
public static T? FindDescendantOfType(this Visual? visual, bool includeSelf, Predicate? predicate) where T : class
{
if (visual is null)
{
return null;
}
if (includeSelf && visual is T result && (predicate == null || predicate(result)))
{
return result;
}
return FindDescendantOfTypeCore(visual, predicate);
}
///
/// Enumerates an and its ancestors in the visual tree.
///
/// The visual.
/// The visual and its ancestors.
public static IEnumerable GetSelfAndVisualAncestors(this Visual visual)
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
yield return visual;
foreach (var ancestor in visual.GetVisualAncestors())
{
yield return ancestor;
}
}
public static TransformedBounds? GetTransformedBounds(this Visual visual)
{
if (visual is null)
{
throw new ArgumentNullException(nameof(visual));
}
Rect clip = default;
var transform = Matrix.Identity;
bool Visit(Visual visual)
{
if (!visual.IsVisible)
return false;
// The visual's bounds in local coordinates.
var bounds = new Rect(visual.Bounds.Size);
// If the visual has no parent, we've reached the root. We start the clip
// rectangle with these bounds.
if (visual.GetVisualParent() is not { } parent)
{
clip = bounds;
return true;
}
// Otherwise recurse until the root visual is found, exiting early if one of the
// ancestors is invisible.
if (!Visit(parent))
return false;
// Calculate the transform for this control from its offset and render transform.
var renderTransform = Matrix.Identity;
if (visual.HasMirrorTransform)
{
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0);
renderTransform *= mirrorMatrix;
}
if (visual.RenderTransform != null)
{
var origin = visual.RenderTransformOrigin.ToPixels(bounds.Size);
var offset = Matrix.CreateTranslation(origin);
var finalTransform = (-offset) * visual.RenderTransform.Value * offset;
renderTransform *= finalTransform;
}
transform = renderTransform *
Matrix.CreateTranslation(visual.Bounds.Position) *
transform;
// If the visual is clipped, update the clip bounds.
if (visual.ClipToBounds)
{
var globalBounds = bounds.TransformToAABB(transform);
var clipBounds = visual.ClipToBounds ?
globalBounds.Intersect(clip) :
clip;
clip = clip.Intersect(clipBounds);
}
return true;
}
return Visit(visual) ? new(new(visual.Bounds.Size), clip, transform) : null;
}
///
/// Gets the first visual in the visual tree whose bounds contain a point.
///
/// The root visual to test.
/// The point.
/// The visual at the requested point.
public static Visual? GetVisualAt(this Visual visual, Point p)
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual.GetVisualAt(p, x => x.IsVisible);
}
///
/// Gets the first visual in the visual tree whose bounds contain a point.
///
/// The root visual to test.
/// The point.
///
/// A filter predicate. If the predicate returns false then the visual and all its
/// children will be excluded from the results.
///
/// The visual at the requested point.
public static Visual? GetVisualAt(this Visual visual, Point p, Func filter)
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
var root = visual.GetVisualRoot();
if (root is null)
{
return null;
}
var rootPoint = visual.TranslatePoint(p, (Visual)root);
if (rootPoint.HasValue)
{
return root.HitTester.HitTestFirst(rootPoint.Value, visual, filter);
}
return null;
}
///
/// Enumerates the visible visuals in the visual tree whose bounds contain a point.
///
/// The root visual to test.
/// The point.
/// The visuals at the requested point.
public static IEnumerable GetVisualsAt(
this Visual visual,
Point p)
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual.GetVisualsAt(p, x => x.IsVisible);
}
///
/// Enumerates the visuals in the visual tree whose bounds contain a point.
///
/// The root visual to test.
/// The point.
///
/// A filter predicate. If the predicate returns false then the visual and all its
/// children will be excluded from the results.
///
/// The visuals at the requested point.
public static IEnumerable GetVisualsAt(
this Visual visual,
Point p,
Func filter)
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
var root = visual.GetVisualRoot();
if (root is null)
{
return Array.Empty();
}
return root.HitTester.HitTest(p, visual, filter);
}
///
/// Enumerates the children of an in the visual tree.
///
/// The visual.
/// The visual children.
public static IEnumerable GetVisualChildren(this Visual visual)
{
return visual.VisualChildren;
}
///
/// Enumerates the descendants of an in the visual tree.
///
/// The visual.
/// The visual's ancestors.
public static IEnumerable GetVisualDescendants(this Visual visual)
{
foreach (Visual child in visual.VisualChildren)
{
yield return child;
foreach (Visual descendant in child.GetVisualDescendants())
{
yield return descendant;
}
}
}
///
/// Enumerates an and its descendants in the visual tree.
///
/// The visual.
/// The visual and its ancestors.
public static IEnumerable GetSelfAndVisualDescendants(this Visual visual)
{
yield return visual;
foreach (var ancestor in visual.GetVisualDescendants())
{
yield return ancestor;
}
}
///
/// Gets the visual parent of an .
///
/// The visual.
/// The parent, or null if the visual is unparented.
public static Visual? GetVisualParent(this Visual visual)
{
return visual.VisualParent;
}
///
/// Gets the visual parent of an .
///
/// The type of the visual parent.
/// The visual.
///
/// The parent, or null if the visual is unparented or its parent is not of type .
///
public static T? GetVisualParent(this Visual visual) where T : class
{
return visual.VisualParent as T;
}
///
/// Gets the root visual for an .
///
/// The visual.
///
/// The root visual or null if the visual is not rooted.
///
public static IRenderRoot? GetVisualRoot(this Visual visual)
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual as IRenderRoot ?? visual.VisualRoot;
}
///
/// Returns a value indicating whether this control is attached to a visual root.
///
public static bool IsAttachedToVisualTree(this Visual visual) => visual.IsAttachedToVisualTree;
///
/// Tests whether an is an ancestor of another visual.
///
/// The visual.
/// The potential descendant.
///
/// True if is an ancestor of ;
/// otherwise false.
///
public static bool IsVisualAncestorOf(this Visual? visual, Visual? target)
{
Visual? current = target?.VisualParent;
while (current != null)
{
if (current == visual)
{
return true;
}
current = current.VisualParent;
}
return false;
}
public static IEnumerable SortByZIndex(this IEnumerable elements)
{
return elements
.Select((element, index) => new ZOrderElement
{
Element = element,
Index = index,
ZIndex = element.ZIndex,
})
.OrderBy(x => x, ZOrderElement.Comparer)
.Select(x => x.Element!);
}
private static T? FindDescendantOfTypeCore(Visual visual, Predicate? predicate) where T : class
{
var visualChildren = visual.VisualChildren;
var visualChildrenCount = visualChildren.Count;
for (var i = 0; i < visualChildrenCount; i++)
{
Visual child = visualChildren[i];
if (child is T result)
{
if (predicate == null || predicate(result))
{
return result;
}
}
var childResult = FindDescendantOfTypeCore(child, predicate);
if (!(childResult is null))
{
return childResult;
}
}
return null;
}
private class ZOrderElement : IComparable
{
public Visual? Element { get; set; }
public int Index { get; set; }
public int ZIndex { get; set; }
class ZOrderComparer : IComparer
{
public int Compare(ZOrderElement? x, ZOrderElement? y)
{
if (ReferenceEquals(x, y)) return 0;
if (ReferenceEquals(null, y)) return 1;
if (ReferenceEquals(null, x)) return -1;
return x.CompareTo(y);
}
}
public static IComparer Comparer { get; } = new ZOrderComparer();
public int CompareTo(ZOrderElement? other)
{
if (other is null)
return 1;
var z = other.ZIndex - ZIndex;
if (z != 0)
{
return z;
}
else
{
return other.Index - Index;
}
}
}
}
}