File: iOS\VisualElementRenderer.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using RectangleF = CoreGraphics.CGRect;
using SizeF = CoreGraphics.CGSize;
using CoreAnimation;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Controls.Platform;
 
#if __MOBILE__
using ObjCRuntime;
using UIKit;
using NativeView = UIKit.UIView;
using NativeViewController = UIKit.UIViewController;
using NativeColor = UIKit.UIColor;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS
#else
using AppKit;
using NativeView = AppKit.NSView;
using NativeViewController = AppKit.NSViewController;
using NativeColor = AppKit.NSColor;
using Microsoft.Maui.Controls.Compatibility.Platform.macOS.Extensions;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.MacOS
#endif
{
	[Flags]
	public enum VisualElementRendererFlags
	{
		Disposed = 1 << 0,
		AutoTrack = 1 << 1,
		AutoPackage = 1 << 2
	}
 
	[Obsolete("Use Microsoft.Maui.Controls.Handlers.Compatibility.VisualElementRenderer instead")]
	public class VisualElementRenderer<TElement> : NativeView, IVisualElementRenderer, IEffectControlProvider where TElement : VisualElement
	{
		readonly NativeColor _defaultColor = NativeColor.Clear;
 
		readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers = new List<EventHandler<VisualElementChangedEventArgs>>();
 
		readonly PropertyChangedEventHandler _propertyChangedHandler;
		string _defaultAccessibilityLabel;
		string _defaultAccessibilityHint;
		bool? _defaultIsAccessibilityElement;
		bool? _defaultAccessibilityElementsHidden;
		EventTracker _events;
 
		VisualElementRendererFlags _flags = VisualElementRendererFlags.AutoPackage | VisualElementRendererFlags.AutoTrack;
 
		VisualElementPackager _packager;
		VisualElementTracker _tracker;
 
#if __MOBILE__
		UIVisualEffectView _blur;
		BlurEffectStyle _previousBlur;
#endif
 
		protected VisualElementRenderer() : base(RectangleF.Empty)
		{
			_propertyChangedHandler = OnElementPropertyChanged;
#if __MOBILE__
			BackgroundColor = _defaultColor;
#else
			WantsLayer = true;
			Layer.BackgroundColor = _defaultColor.CGColor;
#endif
		}
 
#if __MOBILE__
		// prevent possible crashes in overrides
		public sealed override NativeColor BackgroundColor
		{
			get { return base.BackgroundColor; }
			set { base.BackgroundColor = value; }
		}
#endif
 
		public TElement Element { get; private set; }
 
		protected bool AutoPackage
		{
			get { return (_flags & VisualElementRendererFlags.AutoPackage) != 0; }
			set
			{
				if (value)
					_flags |= VisualElementRendererFlags.AutoPackage;
				else
					_flags &= ~VisualElementRendererFlags.AutoPackage;
			}
		}
 
		protected bool AutoTrack
		{
			get { return (_flags & VisualElementRendererFlags.AutoTrack) != 0; }
			set
			{
				if (value)
					_flags |= VisualElementRendererFlags.AutoTrack;
				else
					_flags &= ~VisualElementRendererFlags.AutoTrack;
			}
		}
 
		public static void RegisterEffect(Effect effect, NativeView container, NativeView control = null)
		{
			var platformEffect = effect as PlatformEffect;
			if (platformEffect == null)
				return;
 
			platformEffect.Container = container;
			platformEffect.Control = control;
		}
 
#if __MOBILE__
		public override bool CanBecomeFirstResponder
		{
			get
			{
				if (Element != null && Element.IsSet(PlatformConfiguration.iOSSpecific.VisualElement.CanBecomeFirstResponderProperty))
					return PlatformConfiguration.iOSSpecific.VisualElement.GetCanBecomeFirstResponder(Element);
 
				return base.CanBecomeFirstResponder;
			}
		}
#endif
 
		void IEffectControlProvider.RegisterEffect(Effect effect)
		{
			var platformEffect = effect as PlatformEffect;
			if (platformEffect != null)
				OnRegisterEffect(platformEffect);
		}
 
		VisualElement IVisualElementRenderer.Element
		{
			get { return Element; }
		}
 
		event EventHandler<VisualElementChangedEventArgs> IVisualElementRenderer.ElementChanged
		{
			add { _elementChangedHandlers.Add(value); }
			remove { _elementChangedHandlers.Remove(value); }
		}
 
 
 
		public virtual SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
		{
			return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
		}
 
		public NativeView NativeView => this;
 
 
		protected internal virtual NativeView GetControl() => NativeView;
 
		void IVisualElementRenderer.SetElement(VisualElement element)
		{
			SetElement((TElement)element);
		}
 
		public void SetElementSize(Size size)
		{
			Layout.LayoutChildIntoBoundingRegion(Element, new Rect(Element.X, Element.Y, size.Width, size.Height));
		}
 
		public virtual NativeViewController ViewController => null;
 
		public event EventHandler<ElementChangedEventArgs<TElement>> ElementChanged;
 
		public void SetElement(TElement element)
		{
			var oldElement = Element;
			Element = element;
 
			Performance.Start(out string reference);
 
			if (oldElement != null)
				oldElement.PropertyChanged -= _propertyChangedHandler;
 
			if (element != null)
			{
				if (element.BackgroundColor != null || (oldElement != null && element.BackgroundColor != oldElement.BackgroundColor))
					SetBackgroundColor(element.BackgroundColor);
 
				if (element.Background != null && (!element.Background.IsEmpty || (oldElement != null && element.Background != oldElement.Background)))
					SetBackground(element.Background);
 
				UpdateClipToBounds();
 
				if (_tracker == null)
				{
					_tracker = new VisualElementTracker(this);
					_tracker.NativeControlUpdated += (sender, e) => UpdateNativeWidget();
				}
 
				if (AutoPackage && _packager == null)
				{
					_packager = new VisualElementPackager(this);
					_packager.Load();
				}
 
 
				// TODO MAUI AUTOTRACK?
				if (AutoTrack && _events == null)
				{
					// _events = new EventTracker(this);
					//_events.LoadEvents(this);
				}
 
				element.PropertyChanged += _propertyChangedHandler;
 
			}
 
			OnElementChanged(new ElementChangedEventArgs<TElement>(oldElement, element));
 
			if (element != null)
				SendVisualElementInitialized(element, this);
 
			EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
 
			if (Element != null && !string.IsNullOrEmpty(Element.AutomationId))
				SetAutomationId(Element.AutomationId);
 
			if (element != null)
				SetAccessibilityLabel();
 
			SetAccessibilityHint();
			SetIsAccessibilityElement();
			SetAccessibilityElementsHidden();
			Performance.Stop(reference);
		}
 
#if __MOBILE__
 
		public override SizeF SizeThatFits(SizeF size)
		{
			return new SizeF(0, 0);
		}
 
		public override void LayoutSubviews()
		{
			base.LayoutSubviews();
 
			if (_blur != null && Superview != null)
			{
				_blur.Frame = Frame;
				if (_blur.Superview == null)
					Superview.Add(_blur);
			}
 
			bool hasBackground = Element?.Background != null && !Element.Background.IsEmpty;
 
			if (hasBackground)
				NativeView.UpdateBackgroundLayer();
		}
 
#else
		public override void MouseDown(NSEvent theEvent)
		{
			bool inViewCell = IsOnViewCell(Element);
 
			if (Element.InputTransparent || inViewCell)
				base.MouseDown(theEvent);
		}
 
		public override void RightMouseUp(NSEvent theEvent)
		{
			var menu = Microsoft.Maui.Controls.Compatibility.Element.GetMenu(Element);
			if (menu != null && NativeView != null)
				NSMenu.PopUpContextMenu(menu.ToNSMenu(), theEvent, NativeView);
 
			base.RightMouseUp(theEvent);
		}
#endif
 
		protected override void Dispose(bool disposing)
		{
			if ((_flags & VisualElementRendererFlags.Disposed) != 0)
				return;
			_flags |= VisualElementRendererFlags.Disposed;
 
			if (disposing)
			{
				if (_events != null)
				{
					_events.Dispose();
					_events = null;
				}
				if (_tracker != null)
				{
					_tracker.Dispose();
					_tracker = null;
				}
				if (_packager != null)
				{
					_packager.Dispose();
					_packager = null;
				}
 
				// The ListView can create renderers and unhook them from the Element before Dispose is called in CalculateHeightForCell.
				// Thus, it is possible that this work is already completed.
				if (Element != null)
				{
					Element.ClearValue(Platform.RendererProperty);
					SetElement(null);
				}
			}
			base.Dispose(disposing);
		}
 
		protected virtual void OnElementChanged(ElementChangedEventArgs<TElement> e)
		{
			var args = new VisualElementChangedEventArgs(e.OldElement, e.NewElement);
			for (var i = 0; i < _elementChangedHandlers.Count; i++)
				_elementChangedHandlers[i](this, args);
 
			ElementChanged?.Invoke(this, e);
#if __MOBILE__
			if (e.NewElement != null)
				SetBlur((BlurEffectStyle)e.NewElement.GetValue(PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty));
#endif
		}
 
		protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
				SetBackgroundColor(Element.BackgroundColor);
			else if (e.PropertyName == VisualElement.BackgroundProperty.PropertyName)
				SetBackground(Element.Background);
			else if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName)
				UpdateClipToBounds();
#if __MOBILE__
			else if (e.PropertyName == PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty.PropertyName)
				SetBlur((BlurEffectStyle)Element.GetValue(PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty));
#endif
			else if (e.PropertyName == AutomationProperties.HelpTextProperty.PropertyName)
				SetAccessibilityHint();
			else if (e.PropertyName == AutomationProperties.NameProperty.PropertyName)
				SetAccessibilityLabel();
			else if (e.PropertyName == AutomationProperties.IsInAccessibleTreeProperty.PropertyName)
				SetIsAccessibilityElement();
			else if (e.PropertyName == AutomationProperties.ExcludedWithChildrenProperty.PropertyName)
				SetAccessibilityElementsHidden();
		}
 
		protected virtual void OnRegisterEffect(PlatformEffect effect)
		{
			effect.Container = this;
		}
 
		protected virtual void SetAccessibilityHint()
		{
			_defaultAccessibilityHint = this.SetAccessibilityHint(Element, _defaultAccessibilityHint);
		}
 
		protected virtual void SetAccessibilityLabel()
		{
			_defaultAccessibilityLabel = this.SetAccessibilityLabel(Element, _defaultAccessibilityLabel);
		}
 
		protected virtual void SetIsAccessibilityElement()
		{
			_defaultIsAccessibilityElement = this.SetIsAccessibilityElement(Element, _defaultIsAccessibilityElement);
		}
 
		protected virtual void SetAccessibilityElementsHidden()
		{
			_defaultAccessibilityElementsHidden = this.SetAccessibilityElementsHidden(Element, _defaultAccessibilityElementsHidden);
		}
 
		protected virtual void SetAutomationId(string id)
		{
			AccessibilityIdentifier = id;
		}
 
		protected virtual void SetBackgroundColor(Color color)
		{
			if (color == null)
#if __MOBILE__
 
				BackgroundColor = _defaultColor;
			else
				BackgroundColor = color.ToPlatform();
 
#else
				Layer.BackgroundColor = _defaultColor.CGColor;
			else
				Layer.BackgroundColor = color.ToCGColor();
#endif
		}
 
		protected virtual void SetBackground(Brush brush)
		{
			NativeView.UpdateBackground(brush);
		}
 
#if __MOBILE__
		protected virtual void SetBlur(BlurEffectStyle blur)
		{
			if (_previousBlur == blur)
				return;
 
			_previousBlur = blur;
 
			if (_blur != null)
			{
				_blur.RemoveFromSuperview();
				_blur = null;
			}
 
			if (blur == BlurEffectStyle.None)
			{
				SetNeedsDisplay();
				return;
			}
 
			UIBlurEffect blurEffect;
			switch (blur)
			{
				default:
				case BlurEffectStyle.ExtraLight:
					blurEffect = UIBlurEffect.FromStyle(UIBlurEffectStyle.ExtraLight);
					break;
				case BlurEffectStyle.Light:
					blurEffect = UIBlurEffect.FromStyle(UIBlurEffectStyle.Light);
					break;
				case BlurEffectStyle.Dark:
					blurEffect = UIBlurEffect.FromStyle(UIBlurEffectStyle.Dark);
					break;
			}
 
			_blur = new UIVisualEffectView(blurEffect);
			_blur.UserInteractionEnabled = false;
			LayoutSubviews();
		}
#endif
 
		protected virtual void UpdateNativeWidget()
		{
 
		}
 
		internal virtual void SendVisualElementInitialized(VisualElement element, NativeView nativeView)
		{
			element.SendViewInitialized(nativeView);
		}
 
		void UpdateClipToBounds()
		{
#if __MOBILE__
			var clippableLayout = Element as Layout;
			if (clippableLayout != null)
				ClipsToBounds = clippableLayout.IsClippedToBounds;
#endif
		}
 
		static bool IsOnViewCell(Element element)
		{
 
			if (element.Parent == null)
				return false;
			else if (element.Parent is ViewCell)
				return true;
			else
				return IsOnViewCell(element.Parent);
		}
	}
}