File: LegacyLayouts\Layout.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Layouts;
 
namespace Microsoft.Maui.Controls.Compatibility
{
	/// <summary>
	/// Base class for layouts that allow you to arrange and group UI controls in your application.
	/// </summary>
	/// <typeparam name="T">The type of <see cref="View"/> that can be added to the layout.</typeparam>
	[ContentProperty(nameof(Children))]
	[Obsolete("Use Microsoft.Maui.Controls.Layout instead. For more information, see https://learn.microsoft.com/dotnet/maui/user-interface/layouts/custom")]
	public abstract partial class Layout<T> : Layout, Microsoft.Maui.ILayout, ILayoutManager, IBindableLayout, IViewContainer<T> where T : View
	{
		readonly ElementCollection<T> _children;
 
		protected Layout() => _children = new ElementCollection<T>(InternalChildren);
 
		/// <summary>
		/// Gets the child objects contained in this layout.
		/// </summary>
		public new IList<T> Children => _children;
 
		/// <summary>
		/// Gets the associated handler for this layout.
		/// </summary>
		public ILayoutHandler LayoutHandler => Handler as ILayoutHandler;
 
		IList IBindableLayout.Children => _children;
 
		bool ISafeAreaView.IgnoreSafeArea => false;
 
		protected override void OnChildAdded(Element child)
		{
			base.OnChildAdded(child);
 
			if (child is T typedChild)
			{
				OnAdded(typedChild);
			}
		}
 
		protected override void OnChildRemoved(Element child, int oldLogicalIndex)
		{
			base.OnChildRemoved(child, oldLogicalIndex);
 
			if (child is T typedChild)
			{
				OnRemoved(typedChild);
			}
		}
 
		/// <summary>
		/// Invoked when a child is added to the layout. Implement this method to add class handling for this event.
		/// </summary>
		/// <param name="view">The view which was added.</param>
		protected virtual void OnAdded(T view)
		{
		}
 
		/// <summary>
		/// Invoked when a child is removed the layout. Implement this method to add class handling for this event.
		/// </summary>
		/// <param name="view">The view which was removed.</param>
		protected virtual void OnRemoved(T view)
		{
		}
 
		Size ILayoutManager.Measure(double widthConstraint, double heightConstraint)
		{
			return OnMeasure(widthConstraint, heightConstraint);
		}
 
		Size ILayoutManager.ArrangeChildren(Rect bounds)
		{
			LayoutChildren(bounds.X, bounds.Y, bounds.Width, bounds.Height);
			return bounds.Size;
		}
	}
 
	/// <summary>
	/// Base class for layouts that allow you to arrange and group UI controls in your application.
	/// </summary>
	[Obsolete("Use Microsoft.Maui.Controls.Layout instead. For more information, see https://learn.microsoft.com/dotnet/maui/user-interface/layouts/custom")]
	public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement, IView, IVisualTreeElement, IInputTransparentContainerElement
	{
		/// <summary>Bindable property for <see cref="IsClippedToBounds"/>.</summary>
		public static readonly BindableProperty IsClippedToBoundsProperty =
			BindableProperty.Create(nameof(IsClippedToBounds), typeof(bool), typeof(Layout), false,
				propertyChanged: IsClippedToBoundsPropertyChanged);
 
		/// <summary>Bindable property for <see cref="CascadeInputTransparent"/>.</summary>
		public static readonly BindableProperty CascadeInputTransparentProperty =
			BindableProperty.Create(nameof(CascadeInputTransparent), typeof(bool), typeof(Layout), true,
				propertyChanged: OnCascadeInputTransparentPropertyChanged);
 
		/// <summary>Bindable property for <see cref="Padding"/>.</summary>
		public static readonly BindableProperty PaddingProperty = PaddingElement.PaddingProperty;
 
		bool _hasDoneLayout;
		Size _lastLayoutSize = new Size(-1, -1);
 
		protected Layout()
		{
			//if things were added in base ctor (through implicit styles), the items added aren't properly parented
			if (InternalChildren.Count > 0)
			{
				InternalChildrenOnCollectionChanged(this,
					new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, InternalChildren));
			}
 
			InternalChildren.CollectionChanged += InternalChildrenOnCollectionChanged;
		}
 
		/// <summary>
		/// Gets or sets a value which determines if the layout should clip its children to its bounds.
		/// The default value is <see langword="false"/>.
		/// </summary>
		public bool IsClippedToBounds
		{
			get => (bool)GetValue(IsClippedToBoundsProperty);
			set => SetValue(IsClippedToBoundsProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the inner padding of the layout.
		/// The default value is a <see cref="Thickness"/> with all values set to 0.
		/// </summary>
		/// <remarks>The padding is the space between the bounds of a layout and the bounding region into which its children should be arranged into.</remarks>
		public Thickness Padding
		{
			get => (Thickness)GetValue(PaddingElement.PaddingProperty);
			set => SetValue(PaddingElement.PaddingProperty, value);
		}
 
		/// <summary>
		/// Gets or sets a value that controls whether child elements
		/// inherit the input transparency of this layout when the tranparency is <see langword="true"/>.
		/// </summary>
		/// <value>
		/// <see langword="true" /> to cause child elements to inherit the input transparency of this layout,
		/// when this layout's <see cref="VisualElement.InputTransparent" /> property is <see langword="true" />.
		/// <see langword="false" /> to cause child elements to ignore the input tranparency of this layout.
		/// </value>
		public bool CascadeInputTransparent
		{
			get => (bool)GetValue(CascadeInputTransparentProperty);
			set => SetValue(CascadeInputTransparentProperty, value);
		}
 
		Thickness IPaddingElement.PaddingDefaultValueCreator() => default(Thickness);
 
		void IPaddingElement.OnPaddingPropertyChanged(Thickness oldValue, Thickness newValue) => InvalidateLayout();
 
		static void IsClippedToBoundsPropertyChanged(BindableObject bindableObject, object oldValue, object newValue)
		{
			if (bindableObject is IView view)
			{
				view.Handler?.UpdateValue(nameof(Maui.ILayout.ClipsToBounds));
			}
		}
 
		private protected override IList<Element> LogicalChildrenInternalBackingStore
			=> InternalChildren;
 
		internal ObservableCollection<Element> InternalChildren { get; } = new ObservableCollection<Element>();
 
		/// <summary>
		/// Occurs at the end of a layout cycle if any of the child element's <see cref="VisualElement.Bounds" /> have changed.
		/// </summary>
		[Obsolete("Use SizeChanged.")]
		public event EventHandler LayoutChanged;
 
		/// <summary>The children contained in this layout.</summary>
		/// <remarks>For internal use only. This API can be changed or removed without notice at any time.</remarks>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public IReadOnlyList<Element> Children => InternalChildren;
 
		/// <summary>
		/// Forces a layout cycle on the element and all of its descendants.
		/// </summary>
		/// <remarks>Calling this method frequently can have negative impacts on performance.</remarks>
		[Obsolete("Call InvalidateMeasure instead depending on your scenario.")]
		public void ForceLayout() => SizeAllocated(Width, Height);
 
		IReadOnlyList<Maui.IVisualTreeElement> IVisualTreeElement.GetVisualChildren() => Children.ToList().AsReadOnly();
 
#pragma warning disable CS0672 // Member overrides obsolete member
#pragma warning disable CS0618 // Type or member is obsolete
		public override SizeRequest Measure(double widthConstraint, double heightConstraint, MeasureFlags flags = MeasureFlags.None)
#pragma warning restore CS0618 // Type or member is obsolete
		{
#pragma warning disable CS0618 // Type or member is obsolete
			SizeRequest size = base.Measure(widthConstraint - Padding.HorizontalThickness, heightConstraint - Padding.VerticalThickness, flags);
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
			return new SizeRequest(new Size(size.Request.Width + Padding.HorizontalThickness, size.Request.Height + Padding.VerticalThickness),
				new Size(size.Minimum.Width + Padding.HorizontalThickness, size.Minimum.Height + Padding.VerticalThickness));
#pragma warning restore CS0618 // Type or member is obsolete
		}
#pragma warning restore CS0672 // Member overrides obsolete member
 
		/// <summary>
		/// Positions a child element into a bounding region while respecting the child elements <see cref="View.HorizontalOptions" /> and <see cref="View.VerticalOptions" />.
		/// </summary>
		/// <param name="child">The child element to be positioned.</param>
		/// <param name="region">The bounding region in which the child should be positioned.</param>
		/// <remarks>This method is called in the layout cycle after the general regions for each child have been calculated.
		/// This method will handle positioning the element relative to the bounding region given if the bounding region given is larger than the child's desired size.</remarks>
		[Obsolete("Use the Arrange method on child instead.")]
		public static void LayoutChildIntoBoundingRegion(VisualElement child, Rect region)
		{
			bool isRightToLeft = false;
			if (child.Parent is IFlowDirectionController parent &&
				(isRightToLeft = parent.ApplyEffectiveFlowDirectionToChildContainer &&
				parent.EffectiveFlowDirection.IsRightToLeft()) &&
				(parent.Width - region.Right) != region.X)
			{
				region = new Rect(parent.Width - region.Right, region.Y, region.Width, region.Height);
			}
 
			if (child is IView fe && fe.Handler != null)
			{
				// The new arrange methods will take care of all the alignment and margins and such
				fe.Arrange(region);
				return;
			}
 
			if (!(child is View view))
			{
				child.Layout(region);
				return;
			}
 
			LayoutOptions horizontalOptions = view.HorizontalOptions;
			if (horizontalOptions.Alignment != LayoutAlignment.Fill)
			{
#pragma warning disable CS0618 // Type or member is obsolete
				SizeRequest request = child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins);
#pragma warning restore CS0618 // Type or member is obsolete
				double diff = Math.Max(0, region.Width - request.Request.Width);
				double horizontalAlign = horizontalOptions.Alignment.ToDouble();
				if (isRightToLeft)
				{
					horizontalAlign = 1 - horizontalAlign;
				}
 
				region.X += (int)(diff * horizontalAlign);
				region.Width -= diff;
			}
 
			LayoutOptions verticalOptions = view.VerticalOptions;
			if (verticalOptions.Alignment != LayoutAlignment.Fill)
			{
#pragma warning disable CS0618 // Type or member is obsolete
				SizeRequest request = child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins);
#pragma warning restore CS0618 // Type or member is obsolete
				double diff = Math.Max(0, region.Height - request.Request.Height);
				region.Y += (int)(diff * verticalOptions.Alignment.ToDouble());
				region.Height -= diff;
			}
 
			Thickness margin = view.Margin;
			region.X += margin.Left;
			region.Width -= margin.HorizontalThickness;
			region.Y += margin.Top;
			region.Height -= margin.VerticalThickness;
 
			child.Layout(region);
		}
 
		/// <summary>
		/// Sends a child to the back of the visual stack.
		/// </summary>
		/// <param name="view">The view to lower in the visual stack.</param>
		/// <remarks>Children are internally stored in visual stack order.
		/// This means that raising or lowering a child also changes the order in which the children are enumerated.</remarks>
		[Obsolete("Use the ZIndex Property instead")]
		public void LowerChild(View view)
		{
			if (!InternalChildren.Contains(view) || InternalChildren.First() == view)
			{
				return;
			}
 
			InternalChildren.Move(InternalChildren.IndexOf(view), 0);
			OnChildrenReordered();
		}
 
		/// <summary>
		/// Sends a child to the front of the visual stack.
		/// </summary>
		/// <param name="view">The view to raise in the visual stack.</param>
		/// <remarks>Children are internally stored in visual stack order.
		/// This means that raising or lowering a child also changes the order in which the children are enumerated.</remarks>
		[Obsolete("Use the ZIndex Property instead")]
		public void RaiseChild(View view)
		{
			if (!InternalChildren.Contains(view) || InternalChildren.Last() == view)
			{
				return;
			}
 
			InternalChildren.Move(InternalChildren.IndexOf(view), InternalChildren.Count - 1);
			OnChildrenReordered();
		}
 
		/// <summary>
		/// Invalidates the current layout.
		/// </summary>
		/// <remarks>Calling this method will invalidate the measure and triggers a new layout cycle.</remarks>
		[Obsolete("Use InvalidateMeasure depending on your scenario")]
		protected virtual void InvalidateLayout()
		{
			_hasDoneLayout = false;
			InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
			if (!_hasDoneLayout)
			{
				ForceLayout();
			}
		}
 
		/// <summary>
		/// Positions and sizes the children of a layout.
		/// </summary>
		/// <param name="x">A value representing the x coordinate of the child region bounding box.</param>
		/// <param name="y">A value representing the y coordinate of the child region bounding box.</param>
		/// <param name="width">A value representing the width of the child region bounding box.</param>
		/// <param name="height">A value representing the height of the child region bounding box.</param>
		/// <remarks>Implementors wishing to change the default behavior of a Layout should override this method.
		/// It is suggested to still call the base method and modify its calculated results.</remarks>
 
		[Obsolete("Use ArrangeOverride")]
		protected abstract void LayoutChildren(double x, double y, double width, double height);
 
		internal override void OnChildMeasureInvalidatedInternal(VisualElement child, InvalidationTrigger trigger, int depth)
		{
			// TODO: once we remove old Xamarin public signatures we can invoke `OnChildMeasureInvalidated(VisualElement, InvalidationTrigger)` directly
			OnChildMeasureInvalidated(child, new InvalidationEventArgs(trigger, depth));
		}
 
		/// <summary>
		/// Invoked whenever a child of the layout has emitted <see cref="VisualElement.MeasureInvalidated" />.
		/// Implement this method to add class handling for this event.
		/// </summary>
		/// <param name="sender">The child element whose preferred size changed.</param>
		/// <param name="e">The event data.</param>
		/// <remarks>This method has a default implementation and application developers must call the base implementation.</remarks>
		protected void OnChildMeasureInvalidated(object sender, EventArgs e)
		{
			var depth = 0;
			InvalidationTrigger trigger;
			if (e is InvalidationEventArgs args)
			{
				trigger = args.Trigger;
				depth = args.CurrentInvalidationDepth;
			}
			else
			{
				trigger = InvalidationTrigger.Undefined;
			}
 
			OnChildMeasureInvalidated((VisualElement)sender, trigger, depth);
			OnChildMeasureInvalidated();
		}
 
		/// <summary>
		/// Invoked whenever a child of the layout has emitted <see cref="VisualElement.MeasureInvalidated" />.
		/// Implement this method to add class handling for this event.
		/// </summary>
		[Obsolete("Subscribe to the MeasureInvalidated Event on the Children.")]
		protected virtual void OnChildMeasureInvalidated()
		{
		}
 
		Size IView.Measure(double widthConstraint, double heightConstraint)
		{
			DesiredSize = MeasureOverride(widthConstraint, heightConstraint);
			return DesiredSize;
		}
 
		protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
		{
#pragma warning disable CS0618 // Type or member is obsolete
			var sansMargins = OnMeasure(widthConstraint, heightConstraint).Request;
#pragma warning restore CS0618 // Type or member is obsolete
			return new Size(sansMargins.Width + Margin.HorizontalThickness, sansMargins.Height + Margin.VerticalThickness);
		}
 
		protected override void OnSizeAllocated(double width, double height)
		{
			base.OnSizeAllocated(width, height);
			UpdateChildrenLayout();
		}
 
		/// <summary>
		/// When implemented, should return <see langword="true" /> if <paramref name="child" /> should call <see cref="VisualElement.InvalidateMeasure" /> when added,
		/// and should return <see langword="false" /> if it should not call <see cref="VisualElement.InvalidateMeasure" />. The default value is <see langword="true" />.
		/// </summary>
		/// <param name="child">The child for which to specify whether or not to track invalidation.</param>
		/// <returns><see langword="true" /> if <paramref name="child" /> should call <see cref="VisualElement.InvalidateMeasure" />, otherwise <see langword="false"/>.</returns>
		[Obsolete("If you want to influence invalidation override InvalidateMeasureOverride")]
		protected virtual bool ShouldInvalidateOnChildAdded(View child) => true;
 
		/// <summary>
		/// When implemented, should return <see langword="true" /> if <paramref name="child" /> should call <see cref="VisualElement.InvalidateMeasure" /> when removed,
		/// and should return <see langword="false" /> if it should not call <see cref="VisualElement.InvalidateMeasure" />. The default value is <see langword="true" />.
		/// </summary>
		/// <param name="child">The child for which to specify whether or not to track invalidation.</param>
		/// <returns><see langword="true" /> if <paramref name="child" /> should call <see cref="VisualElement.InvalidateMeasure" />, otherwise <see langword="false"/>.</returns>
		[Obsolete("If you want to influence invalidation override InvalidateMeasureOverride")]
		protected virtual bool ShouldInvalidateOnChildRemoved(View child) => true;
 
		/// <summary>
		/// Instructs the layout to relayout all of its children.
		/// </summary>
		/// <remarks>This method starts a new layout cycle for the layout. Invoking this method frequently can negatively impact performance.</remarks>
		[Obsolete("Use InvalidateMeasure depending on your scenario")]
		protected void UpdateChildrenLayout()
		{
			_hasDoneLayout = true;
 
			if (!ShouldLayoutChildren())
			{
				return;
			}
 
			var oldBounds = new Rect[LogicalChildrenInternal.Count];
			for (var index = 0; index < oldBounds.Length; index++)
			{
				if (LogicalChildrenInternal[index] is VisualElement c)
				{
					oldBounds[index] = c.Bounds;
				}
				else
				{
					// The Logical Children Of the Layout aren't VisualElements
					// This means layout won't automatically be performed by this code
					// This is really only relevant for controls that still inherit from the 
					// legacy layouts. I think SwipeView is the only control that runs into this
					// Because the children of SwipeView are all logical elements handled by the handler
					// not by this code.
					return;
				}
			}
 
			double width = Width;
			double height = Height;
 
			double x = Padding.Left;
			double y = Padding.Top;
			double w = Math.Max(0, width - Padding.HorizontalThickness);
			double h = Math.Max(0, height - Padding.VerticalThickness);
 
			var isHeadless = CompressedLayout.GetIsHeadless(this);
			var headlessOffset = CompressedLayout.GetHeadlessOffset(this);
			for (var i = 0; i < LogicalChildrenInternal.Count; i++)
			{
				CompressedLayout.SetHeadlessOffset((VisualElement)LogicalChildrenInternal[i], isHeadless ? new Point(headlessOffset.X + Bounds.X, headlessOffset.Y + Bounds.Y) : new Point());
			}
 
			_lastLayoutSize = new Size(width, height);
 
			LayoutChildren(x, y, w, h);
 
			for (var i = 0; i < oldBounds.Length; i++)
			{
				Rect oldBound = oldBounds[i];
				Rect newBound = ((VisualElement)LogicalChildrenInternal[i]).Bounds;
				if (oldBound != newBound)
				{
					LayoutChanged?.Invoke(this, EventArgs.Empty);
					return;
				}
			}
		}
 
#pragma warning disable CS0618 // Type or member is obsolete
		internal static void LayoutChildIntoBoundingRegion(View child, Rect region, SizeRequest childSizeRequest)
#pragma warning restore CS0618 // Type or member is obsolete
		{
			bool isRightToLeft = false;
			if (child.Parent is IFlowDirectionController parent && (isRightToLeft = parent.ApplyEffectiveFlowDirectionToChildContainer && parent.EffectiveFlowDirection.IsRightToLeft()))
			{
				region = new Rect(parent.Width - region.Right, region.Y, region.Width, region.Height);
			}
 
			if (child is IView fe && fe.Handler != null)
			{
				// The new arrange methods will take care of all the alignment and margins and such
				fe.Arrange(region);
				return;
			}
 
			if (region.Size != childSizeRequest.Request)
			{
				bool canUseAlreadyDoneRequest = region.Width >= childSizeRequest.Request.Width && region.Height >= childSizeRequest.Request.Height;
 
				LayoutOptions horizontalOptions = child.HorizontalOptions;
				if (horizontalOptions.Alignment != LayoutAlignment.Fill)
				{
#pragma warning disable CS0618 // Type or member is obsolete
					SizeRequest request = canUseAlreadyDoneRequest ? childSizeRequest : child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins);
#pragma warning restore CS0618 // Type or member is obsolete
					double diff = Math.Max(0, region.Width - request.Request.Width);
					double horizontalAlign = horizontalOptions.Alignment.ToDouble();
					if (isRightToLeft)
					{
						horizontalAlign = 1 - horizontalAlign;
					}
 
					region.X += (int)(diff * horizontalAlign);
					region.Width -= diff;
				}
 
				LayoutOptions verticalOptions = child.VerticalOptions;
				if (verticalOptions.Alignment != LayoutAlignment.Fill)
				{
#pragma warning disable CS0618 // Type or member is obsolete
					SizeRequest request = canUseAlreadyDoneRequest ? childSizeRequest : child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins);
#pragma warning restore CS0618 // Type or member is obsolete
					double diff = Math.Max(0, region.Height - request.Request.Height);
					region.Y += (int)(diff * verticalOptions.Alignment.ToDouble());
					region.Height -= diff;
				}
			}
 
			Thickness margin = child.Margin;
			region.X += margin.Left;
			region.Width -= margin.HorizontalThickness;
			region.Y += margin.Top;
			region.Height -= margin.VerticalThickness;
 
			child.Layout(region);
		}
 
		internal virtual void OnChildMeasureInvalidated(VisualElement child, InvalidationTrigger trigger, int depth)
		{
			IReadOnlyList<Element> children = LogicalChildrenInternal;
			int count = children.Count;
			for (var index = 0; index < count; index++)
			{
				if (LogicalChildrenInternal[index] is VisualElement v && v.IsVisible && (!v.IsPlatformEnabled || !v.IsPlatformStateConsistent))
				{
					return;
				}
			}
 
			if (child is View view)
			{
				// we can ignore the request if we are either fully constrained or when the size request changes and we were already fully constrained
				if ((trigger == InvalidationTrigger.MeasureChanged && view.Constraint == LayoutConstraint.Fixed) ||
					(trigger == InvalidationTrigger.SizeRequestChanged && view.ComputedConstraint == LayoutConstraint.Fixed))
				{
					return;
				}
				if (trigger == InvalidationTrigger.HorizontalOptionsChanged || trigger == InvalidationTrigger.VerticalOptionsChanged)
				{
					ComputeConstraintForView(view);
				}
			}
 
			InvalidateMeasureLegacy(trigger, depth, int.MaxValue);
		}
 
		// This lets us override the rules for invalidation on MAUI controls that unfortunately still inheirt from the legacy layout
		private protected virtual void InvalidateMeasureLegacy(InvalidationTrigger trigger, int depth, int depthLeveltoInvalidate)
		{
			if (depth <= depthLeveltoInvalidate)
			{
				if (trigger == InvalidationTrigger.RendererReady)
				{
					InvalidateMeasureInternal(new InvalidationEventArgs(InvalidationTrigger.RendererReady, depth));
				}
				else
				{
					InvalidateMeasureInternal(new InvalidationEventArgs(InvalidationTrigger.MeasureChanged, depth));
				}
			}
			else
			{
				FireMeasureChanged(trigger, depth);
			}
		}
 
		internal override void OnIsVisibleChanged(bool oldValue, bool newValue)
		{
			base.OnIsVisibleChanged(oldValue, newValue);
			if (newValue)
			{
				if (_lastLayoutSize != new Size(Width, Height))
				{
					UpdateChildrenLayout();
				}
			}
		}
 
		static int GetElementDepth(Element view)
		{
			var result = 0;
			while (view.Parent != null)
			{
				result++;
				view = view.Parent;
			}
			return result;
		}
 
		void InternalChildrenOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if (e.Action == NotifyCollectionChangedAction.Move)
			{
				return;
			}
 
			if (e.OldItems != null)
			{
				for (int i = 0; i < e.OldItems.Count; i++)
				{
					object item = e.OldItems[i];
					var v = item as View;
					if (v == null)
					{
						continue;
					}
 
					OnInternalRemoved(v, e.OldStartingIndex + i);
				}
			}
 
			if (e.NewItems != null)
			{
				for (int i = 0; i < e.NewItems.Count; i++)
				{
					object item = e.NewItems[i];
					var v = item as View;
					if (v == null)
					{
						continue;
					}
 
					if (item == this)
					{
						throw new InvalidOperationException("Cannot add self to own child collection.");
					}
 
					OnInternalAdded(v);
				}
			}
		}
 
		void OnInternalAdded(View view)
		{
			var parent = view.Parent as Layout;
			parent?.InternalChildren.Remove(view);
 
			OnChildAdded(view);
			if (ShouldInvalidateOnChildAdded(view))
			{
				InvalidateLayout();
			}
		}
 
		void OnInternalRemoved(View view, int oldIndex)
		{
			OnChildRemoved(view, oldIndex);
			if (ShouldInvalidateOnChildRemoved(view))
			{
				InvalidateLayout();
			}
		}
 
		bool ShouldLayoutChildren()
		{
			if (Width <= 0 || Height <= 0 || !LogicalChildrenInternal.Any() || !IsVisible || !IsPlatformStateConsistent || DisableLayout)
			{
				return false;
			}
 
			foreach (Element element in VisibleDescendants())
			{
				var visual = element as VisualElement;
				if (visual == null || !visual.IsVisible)
				{
					continue;
				}
 
				if (!visual.IsPlatformEnabled || !visual.IsPlatformStateConsistent)
				{
					return false;
				}
			}
			return true;
		}
 
		protected override void InvalidateMeasureOverride()
		{
			base.InvalidateMeasureOverride();
 
			foreach (var child in ((IElementController)this).LogicalChildren)
			{
				if (child is IView fe)
				{
					fe.InvalidateMeasure();
				}
			}
		}
 
		protected override Size ArrangeOverride(Rect bounds)
		{
			base.ArrangeOverride(bounds);
 
			// The SholdLayoutChildren check will catch impossible sizes (negative widths/heights), not-yet-loaded controls,
			// and other weirdness that comes from the legacy layouts trying to run layout before the native side is ready. 
			if (!ShouldLayoutChildren())
			{
				return bounds.Size;
			}
 
			UpdateChildrenLayout();
 
			return Frame.Size;
		}
 
		/// <inheritdoc cref="ICrossPlatformLayout.CrossPlatformMeasure(double, double)" />
		public Size CrossPlatformMeasure(double widthConstraint, double heightConstraint)
		{
#pragma warning disable CS0618 // Type or member is obsolete
			return OnMeasure(widthConstraint, heightConstraint).Request;
#pragma warning restore CS0618 // Type or member is obsolete
		}
 
		/// <inheritdoc cref="ICrossPlatformLayout.CrossPlatformArrange(Rect)" />
		public Size CrossPlatformArrange(Rect bounds)
		{
			UpdateChildrenLayout();
 
			return Frame.Size;
		}
 
		static void OnCascadeInputTransparentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
		{
			// We only need to update if the cascade changes anything, namely when InputTransparent=true.
			// When InputTransparent=false, then the cascade property has no effect.
			if (bindable is Layout layout && layout.InputTransparent)
			{
				layout.RefreshInputTransparentProperty();
			}
		}
	}
}