File: Compatibility\Handlers\VisualElementRenderer.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#if WINDOWS || ANDROID || IOS || TIZEN
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Graphics;
#if WINDOWS
using PlatformView = Microsoft.UI.Xaml.FrameworkElement;
#elif ANDROID
using PlatformView = Android.Views.View;
#elif IOS
using PlatformView = UIKit.UIView;
#elif TIZEN
using PlatformView = Tizen.NUI.BaseComponents.View;
#endif
 
namespace Microsoft.Maui.Controls.Handlers.Compatibility
{
#if WINDOWS
	public abstract partial class VisualElementRenderer<TElement, TPlatformElement> : IPlatformViewHandler
		where TElement : VisualElement
		where TPlatformElement : PlatformView
#else
	public abstract partial class VisualElementRenderer<TElement> : IPlatformViewHandler
		where TElement : Element, IView
#endif
	{
		public static IPropertyMapper<TElement, IPlatformViewHandler> VisualElementRendererMapper = new PropertyMapper<TElement, IPlatformViewHandler>(ViewHandler.ViewMapper)
		{
			[nameof(IView.AutomationId)] = MapAutomationId,
			[nameof(IView.Background)] = MapBackground,
			[nameof(IView.IsEnabled)] = MapIsEnabled,
			[nameof(VisualElement.BackgroundColor)] = MapBackgroundColor,
			[AutomationProperties.IsInAccessibleTreeProperty.PropertyName] = MapAutomationPropertiesIsInAccessibleTree,
#if WINDOWS
#pragma warning disable CS0618 // Type or member is obsolete
			[AutomationProperties.NameProperty.PropertyName] = MapAutomationPropertiesName,
			[AutomationProperties.HelpTextProperty.PropertyName] = MapAutomationPropertiesHelpText,
			[AutomationProperties.LabeledByProperty.PropertyName] = MapAutomationPropertiesLabeledBy,
#pragma warning restore CS0618 // Type or member is obsolete
#endif
		};
 
		public static CommandMapper<TElement, IPlatformViewHandler> VisualElementRendererCommandMapper = new CommandMapper<TElement, IPlatformViewHandler>(ViewHandler.ViewCommandMapper);
 
#if IOS || MACCATALYST
		WeakReference<TElement>? _virtualView;
		TElement? _tempElement;
#else
		TElement? _virtualView;
#endif
		IMauiContext? _mauiContext;
		internal IPropertyMapper _mapper;
		internal readonly CommandMapper? _commandMapper;
		internal readonly IPropertyMapper _defaultMapper;
		protected IMauiContext MauiContext => _mauiContext ?? throw new InvalidOperationException("MauiContext not set");
#if IOS || MACCATALYST
		public TElement? Element => _tempElement ?? (_virtualView is not null && _virtualView.TryGetTarget(out var target) ? target : null);
#else
		public TElement? Element => _virtualView;
#endif
		protected bool AutoPackage { get; set; } = true;
 
#if ANDROID
		public VisualElementRenderer(Android.Content.Context context) : this(context, VisualElementRendererMapper, VisualElementRendererCommandMapper)
		{
		}
#else
		public VisualElementRenderer() : this(VisualElementRendererMapper, VisualElementRendererCommandMapper)
		{
		}
#endif
 
 
#if ANDROID
		protected VisualElementRenderer(Android.Content.Context context, IPropertyMapper mapper, CommandMapper? commandMapper = null)
#else
		protected VisualElementRenderer(IPropertyMapper mapper, CommandMapper? commandMapper = null)
#endif
 
#if ANDROID
			: base(context)
#elif IOS
			: base(CoreGraphics.CGRect.Empty)
#else
			: base()
#endif
		{
			_ = mapper ?? throw new ArgumentNullException(nameof(mapper));
			_defaultMapper = mapper;
			_mapper = _defaultMapper;
			_commandMapper = commandMapper;
		}
 
		public event EventHandler<ElementChangedEventArgs<TElement>>? ElementChanged;
		public event EventHandler<PropertyChangedEventArgs>? ElementPropertyChanged;
 
		public void SetElement(IView view)
		{
			((IPlatformViewHandler)this).SetVirtualView(view);
		}
 
		partial void ElementChangedPartial(ElementChangedEventArgs<TElement> e);
		protected virtual void OnElementChanged(ElementChangedEventArgs<TElement> e)
		{
			ElementChanged?.Invoke(this, e);
			ElementChangedPartial(e);
		}
 
		partial void ElementPropertyChangedPartial(object sender, PropertyChangedEventArgs e);
 
		protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (Element is TElement element && e.PropertyName != null)
				_mapper.UpdateProperty(this, element, e.PropertyName);
 
			ElementPropertyChanged?.Invoke(sender, e);
			ElementPropertyChangedPartial(sender, e);
		}
 
 
		internal static Size GetDesiredSize(IPlatformViewHandler handler, double widthConstraint, double heightConstraint, Size? minimumSize)
		{
			var size = handler.GetDesiredSizeFromHandler(widthConstraint, heightConstraint);
 
			if (minimumSize != null)
			{
				var minSize = minimumSize.Value;
 
				if (size.Height < minSize.Height || size.Width < minSize.Width)
				{
					return new Size(
							size.Width < minSize.Width ? minSize.Width : size.Width,
							size.Height < minSize.Height ? minSize.Height : size.Height
						);
				}
			}
 
			return size;
		}
 
		Size IViewHandler.GetDesiredSize(double widthConstraint, double heightConstraint)
		{
			var sizeRequest = GetDesiredSize(widthConstraint, heightConstraint);
			var minSize = sizeRequest.Minimum;
			var size = sizeRequest.Request;
 
			if (size.Height < minSize.Height || size.Width < minSize.Width)
			{
				return new Size(
						size.Width < minSize.Width ? minSize.Width : size.Width,
						size.Height < minSize.Height ? minSize.Height : size.Height
					);
			}
 
			return size;
		}
 
#pragma warning disable CS0618 // Type or member is obsolete
		public virtual SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
		{
			var minSize = MinimumSize();
			var size = GetDesiredSize(this, widthConstraint, heightConstraint, minSize);
			return new SizeRequest(size, minSize);
		}
#pragma warning restore CS0618 // Type or member is obsolete
 
#if TIZEN
		protected new virtual Size MinimumSize()
#else
		protected virtual Size MinimumSize()
#endif
		{
			return new Size();
		}
 
 
#if IOS
		protected virtual void SetBackgroundColor(Color? color)
#else
		protected virtual void UpdateBackgroundColor()
#endif
		{
			if (Element is TElement element)
				ViewHandler.MapBackground(this, element);
		}
 
#if IOS
		protected virtual void SetBackground(Brush brush)
#else
		protected virtual void UpdateBackground()
#endif
		{
			if (Element is TElement element)
				ViewHandler.MapBackground(this, element);
		}
 
 
		protected virtual void SetAutomationId(string id)
		{
			if (Element is TElement element)
				ViewHandler.MapAutomationId(this, element);
		}
 
		protected virtual void SetIsEnabled()
		{
			if (Element is TElement element)
				ViewHandler.MapIsEnabled(this, element);
		}
 
#if WINDOWS
		protected virtual void SetAutomationPropertiesAccessibilityView()
#else
		protected virtual void SetImportantForAccessibility()
#endif
		{
			if (Element is TElement element)
				VisualElement.MapAutomationPropertiesIsInAccessibleTree(this, element);
		}
 
 
		bool IViewHandler.HasContainer { get => true; set { } }
 
		object? IViewHandler.ContainerView => this;
 
		IView? IViewHandler.VirtualView => Element;
 
		Maui.IElement? IElementHandler.VirtualView => Element;
 
		IMauiContext? IElementHandler.MauiContext => _mauiContext;
 
		PlatformView? IPlatformViewHandler.PlatformView
		{
			get => ((Element?.Handler)?.PlatformView as PlatformView) ?? this;
		}
 
		PlatformView? IPlatformViewHandler.ContainerView => this;
 
		void IViewHandler.PlatformArrange(Rect rect) =>
			this.PlatformArrangeHandler(rect);
 
		void IElementHandler.SetMauiContext(IMauiContext mauiContext)
		{
			_mauiContext = mauiContext;
		}
 
		internal static void SetVirtualView(
			Maui.IElement view,
			IPlatformViewHandler nativeViewHandler,
			Action<ElementChangedEventArgs<TElement>> onElementChanged,
			ref TElement? currentVirtualView,
			ref IPropertyMapper _mapper,
			IPropertyMapper _defaultMapper,
			bool autoPackage)
		{
			if (currentVirtualView == view)
				return;
 
			var oldElement = currentVirtualView;
			currentVirtualView = view as TElement;
			onElementChanged?.Invoke(new ElementChangedEventArgs<TElement>(oldElement, currentVirtualView));
 
			_ = view ?? throw new ArgumentNullException(nameof(view));
 
			if (oldElement?.Handler != null)
				oldElement.Handler = null;
 
			currentVirtualView = (TElement)view;
 
			if (currentVirtualView.Handler != nativeViewHandler)
				currentVirtualView.Handler = nativeViewHandler;
 
			_mapper = _defaultMapper;
 
			if (currentVirtualView is IPropertyMapperView imv)
			{
				var map = imv.GetPropertyMapperOverrides();
				if (map is not null)
				{
					map.Chained = new[] { _defaultMapper };
					_mapper = map;
				}
			}
 
			if (autoPackage)
			{
				ProcessAutoPackage(view);
			}
 
			_mapper.UpdateProperties(nativeViewHandler, currentVirtualView);
		}
 
		static partial void ProcessAutoPackage(Maui.IElement element);
 
#if IOS || MACCATALYST
		void IElementHandler.SetVirtualView(Maui.IElement view)
		{
			// _tempElement is used here, because the Element property is accessed before SetVirtualView() returns
			_tempElement = Element;
			SetVirtualView(view, this, OnElementChanged, ref _tempElement, ref _mapper, _defaultMapper, AutoPackage);
 
			// We use _virtualView as a WeakReference, and clear _tempElement
			_virtualView = _tempElement is null ? null : new(_tempElement);
			_tempElement = null;
		}
#else
		void IElementHandler.SetVirtualView(Maui.IElement view) =>
			SetVirtualView(view, this, OnElementChanged, ref _virtualView, ref _mapper, _defaultMapper, AutoPackage);
#endif
 
		void IElementHandler.UpdateValue(string property)
		{
			if (Element is IElement element)
			{
				OnElementPropertyChanged(element, new PropertyChangedEventArgs(property));
			}
		}
 
		void IElementHandler.Invoke(string command, object? args)
		{
			_commandMapper?.Invoke(this, Element, command, args);
		}
 
		void IElementHandler.DisconnectHandler()
		{
			DisconnectHandlerCore();
			if (Element is IElement element && element.Handler == (IPlatformViewHandler)this)
				element.Handler = null;
 
			_virtualView = null;
		}
 
		private protected virtual void DisconnectHandlerCore()
		{
 
		}
 
		public static void MapAutomationPropertiesIsInAccessibleTree(IPlatformViewHandler handler, TElement view)
		{
#if WINDOWS
			if (handler is VisualElementRenderer<TElement, TPlatformElement> ver)
				ver.SetAutomationPropertiesAccessibilityView();
#else
			if (handler is VisualElementRenderer<TElement> ver)
				ver.SetImportantForAccessibility();
#endif
		}
 
		public static void MapAutomationId(IPlatformViewHandler handler, TElement view)
		{
#if WINDOWS
			if (handler is VisualElementRenderer<TElement, TPlatformElement> ver)
#else
			if (handler is VisualElementRenderer<TElement> ver)
#endif
				ver.SetAutomationId(view.AutomationId);
		}
 
		public static void MapBackgroundColor(IPlatformViewHandler handler, TElement view)
		{
#if WINDOWS
			if (handler is VisualElementRenderer<TElement, TPlatformElement> ver)
#else
			if (handler is VisualElementRenderer<TElement> ver)
#endif
#if IOS
				ver.SetBackgroundColor(view.Background?.ToColor());
#else
				ver.UpdateBackgroundColor();
#endif
		}
 
		public static void MapBackground(IPlatformViewHandler handler, TElement view)
		{
#if WINDOWS
			if (handler is VisualElementRenderer<TElement, TPlatformElement> ver)
#else
			if (handler is VisualElementRenderer<TElement> ver)
#endif
#if IOS
				ver.SetBackground(view.Background);
#else
				ver.UpdateBackground();
#endif
		}
 
		public static void MapIsEnabled(IPlatformViewHandler handler, TElement view)
		{
#if WINDOWS
			if (handler is VisualElementRenderer<TElement, TPlatformElement> ver)
#else
			if (handler is VisualElementRenderer<TElement> ver)
#endif
				ver.SetIsEnabled();
		}
	}
}
#endif