|
|
@@ -14,7 +14,7 @@ namespace Avalonia.VisualTree
|
|
|
public static class VisualExtensions
|
|
|
{
|
|
|
/// <summary>
|
|
|
- /// Calculates the distance from a visual's <see cref="IRenderRoot"/>.
|
|
|
+ /// Calculates the distance from a visual's ancestor.
|
|
|
/// </summary>
|
|
|
/// <param name="visual">The visual.</param>
|
|
|
/// <param name="ancestor">The ancestor visual.</param>
|
|
|
@@ -30,13 +30,39 @@ namespace Avalonia.VisualTree
|
|
|
|
|
|
while (visual != null && visual != ancestor)
|
|
|
{
|
|
|
- ++result;
|
|
|
visual = visual.VisualParent;
|
|
|
+
|
|
|
+ result++;
|
|
|
}
|
|
|
|
|
|
return visual != null ? result : -1;
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Calculates the distance from a visual's root.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="visual">The visual.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// The number of steps from the visual to the root.
|
|
|
+ /// </returns>
|
|
|
+ public static int CalculateDistanceFromRoot(IVisual visual)
|
|
|
+ {
|
|
|
+ Contract.Requires<ArgumentNullException>(visual != null);
|
|
|
+
|
|
|
+ var result = 0;
|
|
|
+
|
|
|
+ visual = visual?.VisualParent;
|
|
|
+
|
|
|
+ while (visual != null)
|
|
|
+ {
|
|
|
+ visual = visual.VisualParent;
|
|
|
+
|
|
|
+ result++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Tries to get the first common ancestor of two visuals.
|
|
|
/// </summary>
|
|
|
@@ -47,8 +73,53 @@ namespace Avalonia.VisualTree
|
|
|
{
|
|
|
Contract.Requires<ArgumentNullException>(visual != null);
|
|
|
|
|
|
- return visual.GetSelfAndVisualAncestors().Intersect(target.GetSelfAndVisualAncestors())
|
|
|
- .FirstOrDefault();
|
|
|
+ if (target is null)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ void GoUpwards(ref IVisual node, int count)
|
|
|
+ {
|
|
|
+ for (int i = 0; i < count; ++i)
|
|
|
+ {
|
|
|
+ node = node.VisualParent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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(visual);
|
|
|
+ var secondHeight = CalculateDistanceFromRoot(target);
|
|
|
+
|
|
|
+ if (firstHeight > secondHeight)
|
|
|
+ {
|
|
|
+ GoUpwards(ref visual, firstHeight - secondHeight);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ GoUpwards(ref target, secondHeight - firstHeight);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (visual == target)
|
|
|
+ {
|
|
|
+ return visual;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (visual != null && target != null)
|
|
|
+ {
|
|
|
+ IVisual firstParent = visual.VisualParent;
|
|
|
+ IVisual secondParent = target.VisualParent;
|
|
|
+
|
|
|
+ if (firstParent == secondParent)
|
|
|
+ {
|
|
|
+ return firstParent;
|
|
|
+ }
|
|
|
+
|
|
|
+ visual = visual.VisualParent;
|
|
|
+ target = target.VisualParent;
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -69,6 +140,57 @@ namespace Avalonia.VisualTree
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Finds first ancestor of given type.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="T">Ancestor type.</typeparam>
|
|
|
+ /// <param name="visual">The visual.</param>
|
|
|
+ /// <param name="includeSelf">If given visual should be included in search.</param>
|
|
|
+ /// <returns>First ancestor of given type.</returns>
|
|
|
+ public static T FindAncestorOfType<T>(this IVisual visual, bool includeSelf = false) where T : class
|
|
|
+ {
|
|
|
+ if (visual is null)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ IVisual parent = includeSelf ? visual : visual.VisualParent;
|
|
|
+
|
|
|
+ while (parent != null)
|
|
|
+ {
|
|
|
+ if (parent is T result)
|
|
|
+ {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ parent = parent.VisualParent;
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Finds first descendant of given type.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="T">Descendant type.</typeparam>
|
|
|
+ /// <param name="visual">The visual.</param>
|
|
|
+ /// <param name="includeSelf">If given visual should be included in search.</param>
|
|
|
+ /// <returns>First descendant of given type.</returns>
|
|
|
+ public static T FindDescendantOfType<T>(this IVisual visual, bool includeSelf = false) where T : class
|
|
|
+ {
|
|
|
+ if (visual is null)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (includeSelf && visual is T result)
|
|
|
+ {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ return FindDescendantOfTypeCore<T>(visual);
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Enumerates an <see cref="IVisual"/> and its ancestors in the visual tree.
|
|
|
/// </summary>
|
|
|
@@ -249,6 +371,31 @@ namespace Avalonia.VisualTree
|
|
|
.Select(x => x.Element);
|
|
|
}
|
|
|
|
|
|
+ private static T FindDescendantOfTypeCore<T>(IVisual visual) where T : class
|
|
|
+ {
|
|
|
+ var visualChildren = visual.VisualChildren;
|
|
|
+ var visualChildrenCount = visualChildren.Count;
|
|
|
+
|
|
|
+ for (var i = 0; i < visualChildrenCount; i++)
|
|
|
+ {
|
|
|
+ IVisual child = visualChildren[i];
|
|
|
+
|
|
|
+ if (child is T result)
|
|
|
+ {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ var childResult = FindDescendantOfTypeCore<T>(child);
|
|
|
+
|
|
|
+ if (!(childResult is null))
|
|
|
+ {
|
|
|
+ return childResult;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
private class ZOrderElement : IComparable<ZOrderElement>
|
|
|
{
|
|
|
public IVisual Element { get; set; }
|