File: VisualDiagnostics\VisualDiagnosticsOverlay.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;
 
namespace Microsoft.Maui
{
#if ANDROID
	[System.Runtime.Versioning.SupportedOSPlatform("android23.0")]
#endif
	public partial class VisualDiagnosticsOverlay : WindowOverlay, IVisualDiagnosticsOverlay
	{
		bool _enableElementSelector;
 
		/// <summary>
		/// Initializes a new instance of the <see cref="VisualDiagnosticsOverlay"/> class.
		/// </summary>
		/// <param name="window">The parent Window.</param>
		public VisualDiagnosticsOverlay(IWindow window)
			: base(window)
		{
			Tapped += VisualDiagnosticsOverlayOnTapped;
		}
 
		/// <inheritdoc/>
		public IReadOnlyCollection<IScrollView> ScrollViews => _scrollViews.Keys;
 
		/// <inheritdoc/>
		public bool ScrollToElement { get; set; }
 
		/// <inheritdoc/>
		public bool EnableElementSelector
		{
			get => _enableElementSelector;
			set
			{
				_enableElementSelector = value;
				DisableUITouchEventPassthrough = value;
 
				// If we enable the element picker, make sure the view itself is enabled and visible.
				if (value)
					IsVisible = true;
			}
		}
 
		/// <inheritdoc/>
		public Point Offset { get; internal set; }
 
		public void AddScrollableElementHandlers()
		{
			var scrollBars = GetScrollViews();
			foreach (var scrollBar in scrollBars)
			{
				if (!ScrollViews.Contains(scrollBar))
				{
					AddScrollableElementHandler(scrollBar);
				}
			}
		}
 
		/// <inheritdoc/>
		public bool AddAdorner(IAdorner adorner, bool scrollToView = false)
		{
			if (adorner == null)
				throw new ArgumentNullException(nameof(adorner));
 
			AddScrollableElementHandlers();
			var result = base.AddWindowElement(adorner);
 
			if (ScrollToElement || scrollToView)
				ScrollToView((IVisualTreeElement)adorner.VisualView);
 
			Invalidate();
			return result;
		}
 
		/// <inheritdoc/>
		public bool AddAdorner(IVisualTreeElement visualElement, bool scrollToView = false)
		{
			if (visualElement == null)
				throw new ArgumentNullException(nameof(visualElement));
 
			if (visualElement is not IView view)
				return false;
 
			foreach (var element in WindowElements)
			{
				if (element is IAdorner adorner && adorner.VisualView == view)
					return false;
			}
 
			var result = base.AddWindowElement(new RectangleGridAdorner(view, Density, Offset));
			AddScrollableElementHandlers();
 
			if (ScrollToElement || scrollToView)
				ScrollToView(visualElement);
 
			Invalidate();
			return result;
		}
 
		/// <inheritdoc/>
		public bool RemoveAdorner(IAdorner adorner)
		{
			if (adorner == null)
				throw new ArgumentNullException(nameof(adorner));
 
			var result = base.RemoveWindowElement(adorner);
			if (WindowElements.Count == 0)
				RemoveScrollableElementHandler();
 
			Invalidate();
			return result;
		}
 
		/// <inheritdoc/>
		public void RemoveAdorners()
		{
			RemoveScrollableElementHandler();
			base.RemoveWindowElements();
		}
 
		/// <inheritdoc/>
		public bool RemoveAdorners(IVisualTreeElement visualElement)
		{
			if (visualElement == null)
				throw new ArgumentNullException(nameof(visualElement));
 
			if (visualElement is not IView view)
				return false;
 
			// make a copy because we will edit
			var removed = false;
			foreach (var element in WindowElements.ToList())
			{
				if (element is IAdorner adorner && adorner.VisualView == view)
					removed = base.RemoveWindowElement(element) || removed;
			}
 
			Invalidate();
			return removed;
		}
 
		/// <inheritdoc/>
		public void ScrollToView(IVisualTreeElement element)
		{
			var parentScrollView = GetParentScrollView(element);
			if (parentScrollView == null)
				return;
 
			if (element is not IView view)
				return;
 
			var platformView = view.GetPlatformViewBounds();
			parentScrollView.RequestScrollTo(platformView.X, platformView.Y, true);
		}
 
		/// <inheritdoc/>
		public override bool AddWindowElement(IWindowOverlayElement drawable)
		{
			if (drawable is not IAdorner adorner)
				return false;
 
			return AddAdorner(adorner, ScrollToElement);
		}
 
		/// <inheritdoc/>
		public override bool RemoveWindowElement(IWindowOverlayElement drawable)
		{
			if (drawable is not IAdorner adorner)
				return false;
 
			return RemoveAdorner(adorner);
		}
 
		/// <inheritdoc/>
		public override void RemoveWindowElements() =>
			RemoveAdorners();
 
		public override bool Deinitialize()
		{
			RemoveScrollableElementHandler();
 
			Tapped -= VisualDiagnosticsOverlayOnTapped;
 
			return base.Deinitialize();
		}
 
		List<IScrollView> GetScrollViews()
		{
			if (Window == null)
				return new List<IScrollView>();
 
			if (Window.Content is not IVisualTreeElement content)
				return new List<IScrollView>();
 
			return content.GetVisualTreeDescendants()
				.OfType<IScrollView>()
				.ToList();
		}
 
		static IScrollView? GetParentScrollView(IVisualTreeElement element)
		{
			if (element == null)
				return null;
 
			if (element is IScrollView scrollView)
				return scrollView;
 
			var parent = element.GetVisualParent();
			if (parent != null)
				return GetParentScrollView(parent);
 
			return null;
		}
 
		void VisualDiagnosticsOverlayOnTapped(object? sender, WindowOverlayTappedEventArgs e)
		{
			if (!EnableElementSelector)
				return;
 
			RemoveAdorners();
 
			if (e.VisualTreeElements.Any())
				AddAdorner(e.VisualTreeElements.First());
		}
	}
}