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