File: Core\Extensions\VisualTreeElementExtensions.cs
Web Access
Project: src\src\Core\src\Core.csproj (Microsoft.Maui)
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform;
 
#if WINDOWS
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using WinPoint = Windows.Foundation.Point;
using WinRect = Windows.Foundation.Rect;
#endif
 
#if (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
using IPlatformViewHandler = Microsoft.Maui.IViewHandler;
#endif
#if IOS || MACCATALYST
using PlatformView = UIKit.UIView;
using ParentView = UIKit.UIView;
#elif ANDROID
using PlatformView = Android.Views.View;
using ParentView = Android.Views.IViewParent;
#elif WINDOWS
using PlatformView = Microsoft.UI.Xaml.FrameworkElement;
using ParentView = Microsoft.UI.Xaml.DependencyObject;
#elif TIZEN
using PlatformView = Tizen.NUI.BaseComponents.View;
using ParentView = Tizen.NUI.BaseComponents.View;
#else
using PlatformView = System.Object;
using ParentView = System.Object;
#endif
 
namespace Microsoft.Maui
{
	public static class VisualTreeElementExtensions
	{
		/// <summary>
		/// Gets the Window containing the Visual Tree Element, if the element is contained within one.
		/// </summary>
		/// <param name="element"><see cref="IVisualTreeElement"/>.</param>
		/// <returns><see cref="IWindow"/> if element is contained within a Window, else returns null.</returns>
		public static IWindow? GetVisualElementWindow(this IVisualTreeElement element)
		{
			if (element is IWindow window)
				return window;
 
			var parent = element.GetVisualParent();
			if (parent != null)
				return parent.GetVisualElementWindow();
 
			return null;
		}
 
		/// <summary>
		/// Gets the entire hierarchy of descendants as a list of children for a given Visual Tree Element.
		/// </summary>
		/// <param name="visualElement"><see cref="IVisualTreeElement"/> to scan.</param>
		/// <returns>List of Children Elements.</returns>
		public static IReadOnlyList<IVisualTreeElement> GetVisualTreeDescendants(this IVisualTreeElement visualElement) =>
			visualElement.GetVisualTreeDescendantsInternal();
 
		static List<IVisualTreeElement> GetVisualTreeDescendantsInternal(this IVisualTreeElement visualElement, List<IVisualTreeElement>? elements = null)
		{
			if (elements == null)
				elements = new List<IVisualTreeElement>();
 
			elements.Add(visualElement);
 
			foreach (var children in visualElement.GetVisualChildren())
				children.GetVisualTreeDescendantsInternal(elements);
 
			return elements;
		}
 
		/// <summary>
		/// Gets list of a Visual Tree Elements children based off of a rectangle defined by its coordinates which are specified in platform units, not pixels.
		/// </summary>
		/// <param name="visualElement"><see cref="IVisualTreeElement"/> to scan.</param>
		/// <param name="x1">The X coordinate of the top left point.</param>
		/// <param name="y1">The Y coordinate of the top left point.</param>
		/// <param name="x2">The X coordinate of the bottom right point.</param>
		/// <param name="y2">The Y coordinate of the bottom right point.</param>
		/// <returns>List of Children Elements.</returns>
		public static IReadOnlyList<IVisualTreeElement> GetVisualTreeElements(this IVisualTreeElement visualElement, double x1, double y1, double x2, double y2) =>
			GetVisualTreeElements(visualElement, new Rect(x1, y1, x2 - x1, y2 - y1));
 
		/// <summary>
		/// Gets list of a Visual Tree Elements children based off of a rectangle.
		/// </summary>
		/// <param name="visualElement"><see cref="IVisualTreeElement"/> to scan.</param>
		/// <param name="rectangle">The rectangle.</param>
		/// <returns>List of Children Elements.</returns>
		public static IReadOnlyList<IVisualTreeElement> GetVisualTreeElements(this IVisualTreeElement visualElement, Rect rectangle)
		{
#if WINDOWS
			return GetVisualTreeElementsWindowsInternal(visualElement,
				uiElement => VisualTreeHelper.FindElementsInHostCoordinates(new WinRect(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height), uiElement));
#else
			return GetVisualTreeElementsInternal(
				visualElement,
				bounds => bounds.IntersectsWith(rectangle));
#endif
		}
 
		/// <summary>
		/// Gets list of a Visual Tree Elements children based off of a given x, y point.
		/// </summary>
		/// <param name="visualElement"><see cref="IVisualTreeElement"/> to scan.</param>
		/// <param name="x">The X point.</param>
		/// <param name="y">The Y point.</param>
		/// <returns>List of Children Elements.</returns>
		public static IReadOnlyList<IVisualTreeElement> GetVisualTreeElements(this IVisualTreeElement visualElement, double x, double y) =>
			GetVisualTreeElements(visualElement, new Point(x, y));
 
		/// <summary>
		/// Gets list of a Visual Tree Element's children based off of a given Point.
		/// </summary>
		/// <param name="visualElement"><see cref="IVisualTreeElement"/> to scan.</param>
		/// <param name="point"><see cref="Point"/>.</param>
		/// <returns>List of Children Elements.</returns>
		public static IReadOnlyList<IVisualTreeElement> GetVisualTreeElements(this IVisualTreeElement visualElement, Point point)
		{
#if WINDOWS
			return GetVisualTreeElementsWindowsInternal(visualElement,
				uiElement => VisualTreeHelper.FindElementsInHostCoordinates(new WinPoint(point.X, point.Y), uiElement));
#else
			return GetVisualTreeElementsInternal(visualElement, bounds => bounds.Contains(point));
#endif
		}
 
#if WINDOWS
		static List<IVisualTreeElement> GetVisualTreeElementsWindowsInternal(IVisualTreeElement visualElement, Func<UIElement, IEnumerable<UIElement>> findChildren)
		{
			UIElement? uiElement = null;
			var visualElements = new List<IVisualTreeElement>();
			if (visualElement is IWindow window)
			{
				// Get the UI.Xaml.Window so we catch everything in the app window rather than the frame which doesn't include modal content
				var platformView = window.Handler?.PlatformView;
				if (platformView is UI.Xaml.Window winUiWindow)
				{
					uiElement = winUiWindow.Content;
				}
			}
			else if (visualElement is IView view)
			{
				uiElement = view.ToPlatform();
			}
 
			if (uiElement != null)
			{
				var uniqueElements = findChildren(uiElement).ToHashSet();
 
				var descendants = visualElement.GetVisualTreeDescendants();
 
				// Add in reverse order
				for (int i = descendants.Count - 1; i >= 0; i--)
				{
					var descendant = descendants[i];
 
					if (descendant is not IView view || view.Handler is null)
					{
						continue;
					}
					
					if (uniqueElements.Contains(view.ToPlatform()))
					{
						visualElements.Add(descendant);
					}
				}
			}
 
			return visualElements;
		}
#endif
 
		/// <summary>
		/// Locates the <see cref="IVisualTreeElement"/> that's a best fit for the given platform view.		 
		/// </summary>
		/// <remarks>
		/// If an exact <see cref="IVisualTreeElement"/> counterpart isn't found, then the
		/// first <see cref="IVisualTreeElement"/> within the ancestors of the given platform view will 
		/// be returned. 
		/// </remarks>
		/// <param name="platformView">The platform view.</param>
		/// <returns>
		/// A visual tree element if found, <see langword="null"/> otherwise.
		/// </returns>
		internal static IVisualTreeElement? GetVisualTreeElement(
			this PlatformView platformView) =>
				platformView.GetVisualTreeElement(true);
 
		/// <summary>
		/// Locates the <see cref="IVisualTreeElement"/> that's a best fit for the given platform view.		 
		/// </summary>
		/// <remarks>
		/// If an exact <see cref="IVisualTreeElement"/> counterpart isn't found, then the
		/// first <see cref="IVisualTreeElement"/> within the ancestors of the given platform view will 
		/// be returned. 
		/// </remarks>
		/// <param name="platformView">The platform view.</param>
		/// <param name="searchAncestors">
		/// <see langword="true"/> to search within the ancestors of the given platform view;  
		/// otherwise, <see langword="false"/>.</param>
		/// <returns>
		/// A visual tree element if found, <see langword="null"/> otherwise.
		/// </returns>
		internal static IVisualTreeElement? GetVisualTreeElement(
			this PlatformView platformView, bool searchAncestors)
		{
			var platformParentPath = new List<PlatformView>();
			IVisualTreeElement? foundParent = null;
 
			// Locate the first Platform View we can find that can return us its Maui Element
			var nearestParentContainer =
				platformView
					.FindParent(x =>
					{
						if (x is PlatformView pv)
							platformParentPath.Add(pv);
 
						if (x is IVisualTreeElementProvidable backing)
						{
							foundParent = backing.GetElement();
							return foundParent is not null;
						}
 
						return false;
					});
 
			platformParentPath.Reverse();
 
			if (foundParent?.IsThisMyPlatformView(platformView) == true)
				return foundParent;
 
			if (nearestParentContainer is null || foundParent is null)
				return null;
 
			// Now that we have an xplat starting point
			// Let's search back down the xplat tree to figure out what IElement to return
			// This searches down the xplat tree to figure out what path going down the xplat tree
			// matches up against the path we took to go up the platform tree
			var returnValue = FindNextChild(foundParent, platformView, platformParentPath);
 
			// If we aren't searching ancestors, then we only want to return
			// IVTE if it matches the found platformView
			if (!searchAncestors &&
				returnValue != null &&
				!returnValue.IsThisMyPlatformView(platformView))
			{
				return null;
			}
 
			return returnValue;
 
			static IVisualTreeElement? FindNextChild(
				IVisualTreeElement parent,
				PlatformView platformView,
				List<PlatformView> platformParentPath)
			{
				var children = parent.GetVisualChildren();
				IVisualTreeElement? childMatch = null;
				foreach (var child in children)
				{
					if (child is not IVisualTreeElement childVTE)
					{
						return parent;
					}
 
					if (childVTE.IsThisMyPlatformView(platformView))
					{
						return childVTE;
					}
 
					// We only want to check children with platform components that have been realized
					if (childVTE is IElement element &&
						element.Handler is IPlatformViewHandler pvh &&
						pvh.PlatformView is not null)
					{
						var indexOfPlatformView = platformParentPath.IndexOf(pvh.PlatformView);
 
						if (indexOfPlatformView < 0)
							continue;
 
						childMatch = child;
						platformParentPath.RemoveRange(0, indexOfPlatformView + 1);
						break;
					}
				}
 
				// If I've ran out of children then we just return the parent 
				// as the furthest down element we've been able to match to
				if (childMatch is null)
					return parent;
 
				return FindNextChild(childMatch, platformView, platformParentPath);
			}
		}
 
		internal static bool IsThisMyPlatformView(this IVisualTreeElement? visualTreeElement, PlatformView platformView)
		{
			if (visualTreeElement is IElement element)
				return element.IsThisMyPlatformView(platformView);
 
			return false;
		}
 
		static List<IVisualTreeElement> GetVisualTreeElementsInternal(IVisualTreeElement visualElement, Predicate<Rect> intersectElementBounds)
		{
			var elements = new List<IVisualTreeElement>();
 
			Impl(visualElement, intersectElementBounds, elements);
 
			elements.Reverse();
			return elements;
 
			static void Impl(IVisualTreeElement visualElement, Predicate<Rect> intersectElementBounds, List<IVisualTreeElement> elements)
			{
				if (visualElement is IView view && view.Handler is not null)
				{
					Rect bounds = view.GetBoundingBox();
					if (intersectElementBounds(bounds))
						elements.Add(visualElement);
				}
				var children = visualElement.GetVisualChildren();
 
				foreach (var child in children)
				{
					Impl(child, intersectElementBounds, elements);
				}
			}
		}
	}
}