File: Page\Page.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Graphics;
 
namespace Microsoft.Maui.Controls
{
	/// <summary>
	/// A <see cref="VisualElement" /> that occupies the entire screen.
	/// </summary>
	/// <remarks><see cref = "Page" /> is primarily a base class for more useful derived types. Objects that are derived from the <see cref="Page"/> class are most prominently used as the top level UI element in .NET MAUI applications. In addition to their role as the main pages of applications, <see cref="Page"/> objects and their descendants can be used with navigation classes, such as <see cref="NavigationPage"/> or <see cref="FlyoutPage"/>, among others, to provide rich user experiences that conform to the expected behaviors on each platform.
	/// </remarks>
	public partial class Page : VisualElement, ILayout, IPageController, IElementConfiguration<Page>, IPaddingElement, ISafeAreaView, ISafeAreaView2, IView, ITitledElement, IToolbarElement
#if IOS
	,IiOSPageSpecifics
#endif
	{
		/// <summary>
		/// The identifier used by the internal messaging system to set <see cref="IsBusy"/>.
		/// </summary>
		/// <remarks>For internal use only. This API can be changed or removed without notice at any time.</remarks>
		public const string BusySetSignalName = "Microsoft.Maui.Controls.BusySet";
 
		/// <summary>
		/// The identifier used by the internal messaging system to display an alert dialog.
		/// </summary>
		/// <remarks>For internal use only. This API can be changed or removed without notice at any time.</remarks>
		public const string AlertSignalName = "Microsoft.Maui.Controls.SendAlert";
 
		/// <summary>
		/// The identifier used by the internal messaging system to display a prompt dialog.
		/// </summary>
		/// <remarks>For internal use only. This API can be changed or removed without notice at any time.</remarks>
		public const string PromptSignalName = "Microsoft.Maui.Controls.SendPrompt";
 
		/// <summary>
		/// The identifier used by the internal messaging system to display an action sheet.
		/// </summary>
		/// <remarks>For internal use only. This API can be changed or removed without notice at any time.</remarks>
		public const string ActionSheetSignalName = "Microsoft.Maui.Controls.ShowActionSheet";
 
		internal static readonly BindableProperty IgnoresContainerAreaProperty = BindableProperty.Create(nameof(IgnoresContainerArea), typeof(bool), typeof(Page), false);
 
		/// <summary>Bindable property for <see cref="BackgroundImageSource"/>.</summary>
		public static readonly BindableProperty BackgroundImageSourceProperty = BindableProperty.Create(nameof(BackgroundImageSource), typeof(ImageSource), typeof(Page), default(ImageSource));
 
		/// <summary>Bindable property for <see cref="IsBusy"/>.</summary>
		public static readonly BindableProperty IsBusyProperty = BindableProperty.Create(nameof(IsBusy), typeof(bool), typeof(Page), false, propertyChanged: (bo, o, n) => ((Page)bo).OnPageBusyChanged());
 
		/// <summary>Bindable property for <see cref="Padding"/>.</summary>
		public static readonly BindableProperty PaddingProperty = PaddingElement.PaddingProperty;
 
		/// <summary>Bindable property for <see cref="Title"/>.</summary>
		public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(Page), null);
 
		/// <summary>Bindable property for <see cref="IconImageSource"/>.</summary>
		public static readonly BindableProperty IconImageSourceProperty = BindableProperty.Create(nameof(IconImageSource), typeof(ImageSource), typeof(Page), default(ImageSource));
 
		readonly Lazy<PlatformConfigurationRegistry<Page>> _platformConfigurationRegistry;
 
		Rect _containerArea;
 
		bool _containerAreaSet;
 
		bool _hasAppeared;
		private protected bool HasAppeared => _hasAppeared;
 
		internal View TitleView;
 
		List<Action> _pendingActions = new List<Action>();
 
		/// <summary>
		/// Initializes a new instance of the <see cref="Page"/> class.
		/// </summary>
		public Page()
		{
			var toolbarItems = new ObservableCollection<ToolbarItem>();
			toolbarItems.CollectionChanged += OnToolbarItemsCollectionChanged;
			ToolbarItems = toolbarItems;
 
			var menuBarItems = new ObservableCollection<MenuBarItem>();
			menuBarItems.CollectionChanged += OnToolbarItemsCollectionChanged;
			MenuBarItems = menuBarItems;
 
			//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;
			_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<Page>>(() => new PlatformConfigurationRegistry<Page>(this));
			this.NavigatedTo += FlushPendingActions;
		}
 
		/// <summary>
		/// Gets or sets the <see cref="ImageSource"/> that will be used as the background for this page. This is a bindable property.
		/// </summary>
		public ImageSource BackgroundImageSource
		{
			get { return (ImageSource)GetValue(BackgroundImageSourceProperty); }
			set { SetValue(BackgroundImageSourceProperty, value); }
		}
 
		/// <summary>
		/// Gets or sets the <see cref="ImageSource"/> to be used for the icon associated to this page. This is a bindable property.
		/// </summary>
		/// <remarks>For example, this icon might be shown in the flyout menu or a tab bar together with <see cref="Title"/>.</remarks>
		public ImageSource IconImageSource
		{
			get { return (ImageSource)GetValue(IconImageSourceProperty); }
			set { SetValue(IconImageSourceProperty, value); }
		}
 
		/// <summary>
		/// Gets or sets the page busy state. This will cause the platform specific global activity indicator to show a busy state.
		/// This is a bindable property.
		/// </summary>
		/// <remarks>
		/// <para>Setting <see cref="IsBusy"/> to <see langword="true"/> on multiple pages at once will cause the global activity indicator to run until all are set back to <see langword="false"/>. It is the developer's responsibility to unset the <see cref="IsBusy"/> flag before cleaning up a page.</para>
		/// </remarks>
		public bool IsBusy
		{
			get { return (bool)GetValue(IsBusyProperty); }
			set { SetValue(IsBusyProperty, value); }
		}
 
		/// <summary>
		/// Gets or sets the space between the content of the page and its border. This is a bindable property.
		/// </summary>
		public Thickness Padding
		{
			get { return (Thickness)GetValue(PaddingElement.PaddingProperty); }
			set { SetValue(PaddingElement.PaddingProperty, value); }
		}
 
		Thickness IPaddingElement.PaddingDefaultValueCreator()
		{
			return default(Thickness);
		}
 
		void IPaddingElement.OnPaddingPropertyChanged(Thickness oldValue, Thickness newValue)
		{
			(this as IView).InvalidateMeasure();
		}
 
		/// <summary>
		/// Gets or sets the page's title.
		/// </summary>
		/// <remarks>For example, this title might be shown in the flyout menu or a tab bar together with <see cref="IconImageSource"/>.</remarks>
		public string Title
		{
			get { return (string)GetValue(TitleProperty); }
			set { SetValue(TitleProperty, value); }
		}
 
		/// <summary>
		/// Gets the <see cref="ToolbarItem"/> objects for this page, implemented in a platform-specific manner.
		/// </summary>
		public IList<ToolbarItem> ToolbarItems { get; internal set; }
 
		/// <summary>
		/// Gets the <see cref="MenuBarItem"/> objects for this page, implemented in a platform-specific manner.
		/// </summary>
		public IList<MenuBarItem> MenuBarItems { get; internal set; }
 
		/// <summary>
		/// Gets or sets the area this page is contained in.
		/// </summary>
		/// <remarks>For internal use only. This API can be changed or removed without notice at any time.</remarks>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public Rect ContainerArea
		{
			get { return _containerArea; }
			set
			{
				if (_containerArea == value)
					return;
				_containerAreaSet = true;
				_containerArea = value;
				ForceLayout();
			}
		}
 
		/// <summary>
		/// Gets or sets a value that determines whether to ignore the <see cref="ContainerArea"/>. This is a bindable property.
		/// </summary>
		/// <remarks>For internal use only. This API can be changed or removed without notice at any time.</remarks>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public bool IgnoresContainerArea
		{
			get { return (bool)GetValue(IgnoresContainerAreaProperty); }
			set { SetValue(IgnoresContainerAreaProperty, value); }
		}
 
		/// <summary>
		/// Gets the internal collection of child elements contained in this page.
		/// </summary>
		/// <remarks>For internal use only. This API can be changed or removed without notice at any time.</remarks>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public ObservableCollection<Element> InternalChildren { get; } = new ObservableCollection<Element>();
 
		/// <inheritdoc/>
		bool ISafeAreaView.IgnoreSafeArea => !On<PlatformConfiguration.iOS>().UsingSafeArea();
 
#if IOS
		/// <inheritdoc/>
		bool IiOSPageSpecifics.IsHomeIndicatorAutoHidden
		{
			get
			{
				if (Parent is Page page && page.IsSet(PlatformConfiguration.iOSSpecific.Page.PrefersHomeIndicatorAutoHiddenProperty))
					return page.On<PlatformConfiguration.iOS>().PrefersHomeIndicatorAutoHidden();
 
				return On<PlatformConfiguration.iOS>().PrefersHomeIndicatorAutoHidden();
			}
		}
 
		/// <inheritdoc/>
		int IiOSPageSpecifics.PrefersStatusBarHiddenMode
		{
			get
			{
				if (Parent is Page page && page.IsSet(PlatformConfiguration.iOSSpecific.Page.PrefersHomeIndicatorAutoHiddenProperty))
					return (int)page.On<PlatformConfiguration.iOS>().PrefersStatusBarHidden();
 
				return (int)On<PlatformConfiguration.iOS>().PrefersStatusBarHidden();
			}
		}
 
		/// <inheritdoc/>
		int IiOSPageSpecifics.PreferredStatusBarUpdateAnimationMode
		{
			get
			{
				if (Parent is Page page && page.IsSet(PlatformConfiguration.iOSSpecific.Page.PrefersHomeIndicatorAutoHiddenProperty))
					return (int)page.On<PlatformConfiguration.iOS>().PreferredStatusBarUpdateAnimation();
 
				return (int)On<PlatformConfiguration.iOS>().PreferredStatusBarUpdateAnimation();
			}
		}
#endif
 
		/// <inheritdoc/>
		Thickness ISafeAreaView2.SafeAreaInsets
		{
			set
			{
				On<PlatformConfiguration.iOS>().SetSafeAreaInsets(value);
			}
		}
 
		/// <summary>
		/// Raised when the children of this page, and thus potentially the layout, have changed.
		/// </summary>
		public event EventHandler LayoutChanged;
 
		/// <summary>
		/// Raised when this page is visually appearing on screen.
		/// </summary>
		public event EventHandler Appearing;
 
		/// <summary>
		/// Raised when this page is visually disappearing from the screen.
		/// </summary>
		public event EventHandler Disappearing;
 
		/// <inheritdoc cref="DisplayActionSheet(string, string, string, FlowDirection, string[])"/>
		public Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons)
		{
			return DisplayActionSheet(title, cancel, destruction, FlowDirection.MatchParent, buttons);
		}
 
		/// <summary>
		/// Displays a platform action sheet, allowing the application user to choose from several buttons.
		/// </summary>
		/// <param name="title">Title of the displayed action sheet. Can be <see langword="null"/> to hide the title.</param>
		/// <param name="cancel">Text to be displayed in the 'Cancel' button. Can be null to hide the cancel action.</param>
		/// <param name="destruction">Text to be displayed in the 'Destruct' button. Can be <see langword="null"/> to hide the destructive option.</param>
		/// <param name="flowDirection">The flow direction to be used by the action sheet.</param>
		/// <param name="buttons">Text labels for additional buttons.</param>
		/// <returns>A <see cref="Task"/> that displays an action sheet and returns the string caption of the button pressed by the user.</returns>
		/// <remarks>Developers should be aware that Windows line endings, CR-LF, only work on Windows systems, and are incompatible with iOS and Android. A particular consequence of this is that characters that appear after a CR-LF, (For example, in the title) may not be displayed on non-Windows platforms. Developers must use the correct line endings for each of the targeted systems.</remarks>
		public Task<string> DisplayActionSheet(string title, string cancel, string destruction, FlowDirection flowDirection, params string[] buttons)
		{
			var args = new ActionSheetArguments(title, cancel, destruction, buttons);
 
			args.FlowDirection = flowDirection;
#pragma warning disable CS0618 // TODO: Remove when we internalize/replace MessagingCenter
			if (IsPlatformEnabled)
				MessagingCenter.Send(this, ActionSheetSignalName, args);
			else
				_pendingActions.Add(() => MessagingCenter.Send(this, ActionSheetSignalName, args));
#pragma warning restore CS0618 // Type or member is obsolete
 
			return args.Result.Task;
		}
 
		/// <inheritdoc cref="DisplayAlert(string, string, string, string, FlowDirection)"/>
		public Task DisplayAlert(string title, string message, string cancel)
		{
			return DisplayAlert(title, message, null, cancel, FlowDirection.MatchParent);
		}
 
		/// <inheritdoc cref="DisplayAlert(string, string, string, string, FlowDirection)"/>
		public Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
		{
			return DisplayAlert(title, message, accept, cancel, FlowDirection.MatchParent);
		}
 
		/// <inheritdoc cref="DisplayAlert(string, string, string, string, FlowDirection)"/>
		public Task DisplayAlert(string title, string message, string cancel, FlowDirection flowDirection)
		{
			return DisplayAlert(title, message, null, cancel, flowDirection);
		}
 
		/// <summary>
		/// Displays an alert dialog to the application user with a single cancel button.
		/// </summary>
		/// <param name="title">The title of the alert dialog. Can be <see langword="null"/> to hide the title.</param>
		/// <param name="message">The body text of the alert dialog.</param>
		/// <param name="accept">Text to be displayed on the 'Accept' button. Can be <see langword="null"/> to hide this button.</param>
		/// <param name="cancel">Text to be displayed on the 'Cancel' button.</param>
		/// <param name="flowDirection">The flow direction to be used by the alert.</param>
		/// <returns>A <see cref="Task"/> that contains the user's choice as a <see cref="bool"/> value. <see langword="true"/> indicates that the user accepted the alert. <see langword="false"/> indicates that the user cancelled the alert.</returns>
		/// <exception cref="ArgumentNullException">Thrown when <paramref name="cancel"/> is <see langword="null"/> or empty.</exception>
		public Task<bool> DisplayAlert(string title, string message, string accept, string cancel, FlowDirection flowDirection)
		{
			if (string.IsNullOrEmpty(cancel))
				throw new ArgumentNullException(nameof(cancel));
 
			var args = new AlertArguments(title, message, accept, cancel);
			args.FlowDirection = flowDirection;
 
#pragma warning disable CS0618 // TODO: Remove when we internalize/replace MessagingCenter
			if (IsPlatformEnabled)
				MessagingCenter.Send(this, AlertSignalName, args);
			else
				_pendingActions.Add(() => MessagingCenter.Send(this, AlertSignalName, args));
#pragma warning restore CS0618 // Type or member is obsolete
 
			return args.Result.Task;
		}
 
		/// <summary>
		/// Displays a prompt dialog to the application user with the intent to capture a single string value.
		/// </summary>
		/// <param name="title">The title of the prompt dialog.</param>
		/// <param name="message">The body text of the prompt dialog.</param>
		/// <param name="accept">Text to be displayed on the 'Accept' button.</param>
		/// <param name="cancel">Text to be displayed on the 'Cancel' button.</param>
		/// <param name="placeholder">The placeholder text to display in the prompt. Can be <see langword="null"/> when no placeholder is desired.</param>
		/// <param name="maxLength">The maximum length of the user response.</param>
		/// <param name="keyboard">The keyboard type to use for the user response.</param>
		/// <param name="initialValue">A pre-defined response that will be displayed, and which can be edited by the user.</param>
		/// <returns>A <see cref="Task"/> that displays a prompt display and returns the string value as entered by the user.</returns>
		public Task<string> DisplayPromptAsync(string title, string message, string accept = "OK", string cancel = "Cancel", string placeholder = null, int maxLength = -1, Keyboard keyboard = default(Keyboard), string initialValue = "")
		{
			var args = new PromptArguments(title, message, accept, cancel, placeholder, maxLength, keyboard, initialValue);
 
#pragma warning disable CS0618 // TODO: Remove when we internalize/replace MessagingCenter
			if (IsPlatformEnabled)
				MessagingCenter.Send(this, PromptSignalName, args);
			else
				_pendingActions.Add(() => MessagingCenter.Send(this, PromptSignalName, args));
#pragma warning restore CS0618 // Type or member is obsolete
 
			return args.Result.Task;
		}
 
		void FlushPendingActions(object sender, EventArgs e)
		{
			if (_pendingActions.Count > 0)
			{
				var actionsToProcess = _pendingActions.ToList();
				_pendingActions.Clear();
				foreach (var pendingAction in actionsToProcess)
					pendingAction();
			}
 
			this.NavigatedTo -= FlushPendingActions;
		}
 
		/// <summary>
		/// Forces the page to perform a layout pass.
		/// </summary>
		public void ForceLayout()
		{
			SizeAllocated(Width, Height);
		}
 
		/// <summary>
		/// Calls <see cref="OnBackButtonPressed"/>.
		/// </summary>
		/// <returns><see langword="true"/> when the back navigation was handled by the <see cref="OnBackButtonPressed"/> override, otherwise <see langword="false"/>.</returns>
		public bool SendBackButtonPressed()
		{
			return OnBackButtonPressed();
		}
 
		/// <summary>
		/// Lays out the child elements when the layout is invalidated.
		/// </summary>
		/// <param name="x">X-coordinate of the top left corner of the bounding rectangle.</param>
		/// <param name="y">Y-coordinate of the top left corner of the bounding rectangle.</param>
		/// <param name="width">Width of the bounding rectangle.</param>
		/// <param name="height">Height of the bounding rectangle.</param>
		[Obsolete("Use ArrangeOverride instead")]
		protected virtual void LayoutChildren(double x, double y, double width, double height)
		{
			var area = new Rect(x, y, width, height);
			Rect originalArea = area;
			if (_containerAreaSet)
			{
				area = ContainerArea;
				area.X += Padding.Left;
				area.Y += Padding.Right;
				area.Width -= Padding.HorizontalThickness;
				area.Height -= Padding.VerticalThickness;
				area.Width = Math.Max(0, area.Width);
				area.Height = Math.Max(0, area.Height);
			}
 
			IList<Element> elements = this.InternalChildren;
			foreach (Element element in elements)
			{
				var child = element as VisualElement;
				if (child == null)
					continue;
 
				var page = child as Page;
#pragma warning disable CS0618 // Type or member is obsolete
				if (page != null && page.IgnoresContainerArea)
					Maui.Controls.Compatibility.Layout.LayoutChildIntoBoundingRegion(child, originalArea);
				else
					Maui.Controls.Compatibility.Layout.LayoutChildIntoBoundingRegion(child, area);
#pragma warning restore CS0618 // Type or member is obsolete
			}
		}
 
		/// <summary>
		/// When overridden in a derived class, allows application developers to customize behavior immediately prior to the page becoming visible.
		/// </summary>
		protected virtual void OnAppearing()
		{
		}
 
		/// <summary>
		/// Determines the behavior when the back button of the page is pressed.
		/// </summary>
		/// <returns><see langword="true"/> when the back navigation was handled by the override, otherwise <see langword="false"/>.</returns>
		/// <remarks>
		/// <para>Application developers can override this method to provide behavior when the back button is pressed. 
		/// When overridden to handle or cancel the navigation yourself, this method should return <see langword="true"/>.</para>
		/// <para>This only works on Android and UWP for the hardware back button. On iOS, this method will never be called because there is no hardware back button.</para>
		/// </remarks>
		protected virtual bool OnBackButtonPressed()
		{
			if (RealParent is BaseShellItem || RealParent is Shell)
				return false;
 
			var window = RealParent as Window;
			if (window == null || this == window.Page)
				return false;
 
			var canceled = false;
			EventHandler handler = (sender, args) => { canceled = true; };
			window.PopCanceled += handler;
			Navigation
				.PopModalAsync()
				.FireAndForget(Handler);
 
			window.PopCanceled -= handler;
			return !canceled;
		}
 
		/// <summary>
		/// Invoked whenever the binding context of the page changes. Override this method to add class handling for this event.
		/// </summary>
		protected override void OnBindingContextChanged()
		{
			base.OnBindingContextChanged();
			foreach (ToolbarItem toolbarItem in ToolbarItems)
			{
				SetInheritedBindingContext(toolbarItem, BindingContext);
			}
 
			foreach (MenuBarItem menubarItem in MenuBarItems)
			{
				SetInheritedBindingContext(menubarItem, BindingContext);
			}
 
			if (TitleView != null)
				SetInheritedBindingContext(TitleView, BindingContext);
		}
 
 
		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>
		/// Indicates that the preferred size of a child <see cref="Element"/> has changed.
		/// </summary>
		/// <param name="sender">The object that raised the event.</param>
		/// <param name="e">The event arguments.</param>
		protected virtual 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);
		}
 
		/// <summary>
		/// When overridden in a derived class, allows the application developer to customize behavior as the page disappears.
		/// </summary>
		/// <remarks>
		/// This method is called when the page disappears due to navigating away from the page within the app. It is <b>not</b> called when the app disappears due to an event external to the app (e.g. user navigates to the home screen or another app, a phone call is received, the device is locked, the device is turned off).
		/// </remarks>
		protected virtual void OnDisappearing()
		{
		}
 
		/// <summary>
		/// Called when the page's <see cref="Element.Parent"/> property has changed.
		/// </summary>
		/// <exception cref="InvalidOperationException">Thrown when the page's <see cref="Element.RealParent"/> can't be casted to <see cref="Page"/> or <see cref="BaseShellItem"/>.</exception>
		protected override void OnParentSet()
		{
			if (!Application.IsApplicationOrWindowOrNull(RealParent) && !(RealParent is Page) && !(RealParent is BaseShellItem))
				throw new InvalidOperationException("Parent of a Page must also be a Page");
			base.OnParentSet();
		}
 
		/// <summary>
		/// Indicates that the page has been assigned a size.
		/// </summary>
		/// <param name="width">The width allocated to the page.</param>
		/// <param name="height">The height allocated to the page.</param>
		protected override void OnSizeAllocated(double width, double height)
		{
			base.OnSizeAllocated(width, height);
 
			if (Handler is null)
			{
#pragma warning disable CS0618 // Type or member is obsolete
				UpdateChildrenLayout();
#pragma warning restore CS0618 // Type or member is obsolete
			}
		}
 
		/// <summary>
		/// Requests that the child <see cref="Element"/>s of the page update their layouts.
		/// </summary>
		[Obsolete("Use ArrangeOverride instead")]
		protected void UpdateChildrenLayout()
		{
			if (!ShouldLayoutChildren())
				return;
 
			var logicalChildren = this.InternalChildren;
			var startingLayout = new List<Rect>(logicalChildren.Count);
			foreach (Element el in logicalChildren)
			{
				if (el is VisualElement c)
					startingLayout.Add(c.Bounds);
			}
 
			double x = Padding.Left;
			double y = Padding.Top;
			double w = Math.Max(0, Width - Padding.HorizontalThickness);
			double h = Math.Max(0, Height - Padding.VerticalThickness);
 
			LayoutChildren(x, y, w, h);
 
			for (var i = 0; i < logicalChildren.Count; i++)
			{
				var element = logicalChildren[i];
				if (element is VisualElement c)
				{
					if (startingLayout.Count <= i || c.Bounds != startingLayout[i])
					{
						LayoutChanged?.Invoke(this, EventArgs.Empty);
						return;
					}
				}
			}
		}
 
		internal virtual void OnChildMeasureInvalidated(VisualElement child, InvalidationTrigger trigger, int depth)
		{
			var container = this as IPageContainer<Page>;
			if (container != null)
			{
				Page page = container.CurrentPage;
				if (page != null && page.IsVisible && (!page.IsPlatformEnabled || !page.IsPlatformStateConsistent))
					return;
			}
			else
			{
				var logicalChildren = this.InternalChildren;
				for (var i = 0; i < logicalChildren.Count; i++)
				{
					var v = logicalChildren[i] as VisualElement;
					if (v != null && v.IsVisible && (!v.IsPlatformEnabled || !v.IsPlatformStateConsistent))
						return;
				}
			}
 
			if (depth <= 1)
			{
				InvalidateMeasureInternal(new InvalidationEventArgs(InvalidationTrigger.MeasureChanged, depth));
			}
			else
			{
				FireMeasureChanged(trigger, depth);
			}
		}
 
		internal void OnAppearing(Action action)
		{
			if (_hasAppeared)
				action();
			else
			{
				EventHandler eventHandler = null;
				eventHandler = (_, __) =>
				{
					this.Appearing -= eventHandler;
					action();
				};
 
				this.Appearing += eventHandler;
			}
		}
 
		/// <summary>
		/// Sends the signal to the page that it is about to visually appear on screen.
		/// </summary>
		/// <remarks>For internal use only. This API can be changed or removed without notice at any time.</remarks>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public void SendAppearing()
		{
			// Only fire appearing if the page has been added to the windows
			// Visual Hierarchy
			// The window/application will take care of re-firing this appearing 
			// if it needs to
			var window = this.FindParentOfType<IWindow>();
			if (window?.Parent == null)
			{
				return;
			}
 
			if (_hasAppeared)
				return;
 
			_hasAppeared = true;
 
			if (IsBusy)
			{
#pragma warning disable CS0618 // TODO: Remove when we internalize/replace MessagingCenter
				if (IsPlatformEnabled)
					MessagingCenter.Send(this, BusySetSignalName, true);
				else
					_pendingActions.Add(() => MessagingCenter.Send(this, BusySetSignalName, true));
			}
#pragma warning restore CS0618 // Type or member is obsolete
 
			OnAppearing();
			Appearing?.Invoke(this, EventArgs.Empty);
 
			var pageContainer = this as IPageContainer<Page>;
			pageContainer?.CurrentPage?.SendAppearing();
 
			FindApplication(this)?.OnPageAppearing(this);
		}
 
		/// <summary>
		/// Sends the signal to the page that it is about to be visually hidden from the screen.
		/// </summary>
		/// <remarks>For internal use only. This API can be changed or removed without notice at any time.</remarks>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public void SendDisappearing()
		{
			if (!_hasAppeared)
				return;
 
			_hasAppeared = false;
 
#pragma warning disable CS0618 // TODO: Remove when we internalize/replace MessagingCenter
			if (IsBusy)
				MessagingCenter.Send(this, BusySetSignalName, false);
#pragma warning restore CS0618 // Type or member is obsolete
 
			var pageContainer = this as IPageContainer<Page>;
			pageContainer?.CurrentPage?.SendDisappearing();
 
			OnDisappearing();
			Disappearing?.Invoke(this, EventArgs.Empty);
 
			FindApplication(this)?.OnPageDisappearing(this);
		}
 
		Application FindApplication(Element element)
		{
			if (element == null)
				return null;
 
			return (element.Parent is Application app) ? app : FindApplication(element.Parent);
		}
 
		void InternalChildrenOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if (e.OldItems != null)
			{
				for (var i = 0; i < e.OldItems.Count; i++)
				{
					var item = (Element)e.OldItems[i];
					RemoveLogicalChild(item);
				}
			}
 
			if (e.NewItems != null)
			{
				int index = e.NewStartingIndex;
 
				foreach (Element item in e.NewItems)
				{
					int insertIndex = index;
					if (insertIndex < 0)
					{
						insertIndex = InternalChildren.IndexOf(item);
					}
 
					InsertLogicalChild(insertIndex, item);
 
					if (item is VisualElement)
					{
						InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
					}
 
					if (index >= 0)
					{
						index++;
					}
				}
			}
		}
 
		void OnPageBusyChanged()
		{
			if (!_hasAppeared)
				return;
#pragma warning disable CS0618 // TODO: Remove when we internalize/replace MessagingCenter
			MessagingCenter.Send(this, BusySetSignalName, IsBusy);
#pragma warning restore CS0618 // Type or member is obsolete
		}
 
		void OnToolbarItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
		{
			if (args.NewItems != null)
			{
				foreach (IElementDefinition item in args.NewItems)
					item.Parent = this;
			}
 
			if (args.OldItems != null)
			{
				foreach (IElementDefinition item in args.OldItems)
					item.Parent = null;
			}
		}
 
		bool ShouldLayoutChildren()
		{
			var logicalChildren = this.InternalChildren;
			if (logicalChildren.Count == 0 || Width <= 0 || Height <= 0 || !IsPlatformStateConsistent)
				return false;
 
			var container = this as IPageContainer<Page>;
			if (container?.CurrentPage != null)
			{
				if (InternalChildren.Contains(container.CurrentPage))
					return container.CurrentPage.IsPlatformEnabled && container.CurrentPage.IsPlatformStateConsistent;
				return true;
			}
 
			var any = false;
			for (var i = 0; i < logicalChildren.Count; i++)
			{
				var v = logicalChildren[i] as VisualElement;
				if (v != null && (!v.IsPlatformEnabled || !v.IsPlatformStateConsistent))
				{
					any = true;
					break;
				}
			}
			return !any;
		}
 
		/// <inheritdoc/>
		public IPlatformElementConfiguration<T, Page> On<T>() where T : IConfigPlatform
		{
			return _platformConfigurationRegistry.Value.On<T>();
		}
 
		internal void SetTitleView(View oldTitleView, View newTitleView)
		{
			TitleView = newTitleView;
		}
 
		internal bool HasNavigatedTo { get; private set; }
 
		/// <inheritdoc/>
		Paint IView.Background
		{
			get
			{
				if (!Brush.IsNullOrEmpty(Background))
					return Background;
				if (!ImageSource.IsNullOrEmpty(BackgroundImageSource))
					return new ImageSourcePaint(BackgroundImageSource);
				if (BackgroundColor.IsNotDefault())
					return new SolidColorBrush(BackgroundColor);
 
				return null;
			}
		}
 
		Toolbar _toolbar;
 
		IToolbar IToolbarElement.Toolbar
		{
			get => _toolbar;
		}
 
		internal Toolbar Toolbar
		{
			get => _toolbar;
			set => ToolbarElement.SetValue(ref _toolbar, value, Handler);
		}
 
		internal void SendNavigatedTo(NavigatedToEventArgs args)
		{
			HasNavigatedTo = true;
			NavigatedTo?.Invoke(this, args);
			OnNavigatedTo(args);
			(this as IPageContainer<Page>)?.CurrentPage?.SendNavigatedTo(args);
		}
 
		internal void SendNavigatingFrom(NavigatingFromEventArgs args)
		{
			NavigatingFrom?.Invoke(this, args);
			OnNavigatingFrom(args);
			(this as IPageContainer<Page>)?.CurrentPage?.SendNavigatingFrom(args);
		}
 
		internal void SendNavigatedFrom(NavigatedFromEventArgs args, bool disconnectHandlers = true)
		{
			HasNavigatedTo = false;
			NavigatedFrom?.Invoke(this, args);
			OnNavigatedFrom(args);
			(this as IPageContainer<Page>)?.CurrentPage?.SendNavigatedFrom(args, false);
 
			if (!disconnectHandlers)
			{
				return;
			}
 
			if (args.NavigationType == NavigationType.Pop ||
				args.NavigationType == NavigationType.PopToRoot)
			{
				if (!this.IsLoaded)
				{
					this.DisconnectHandlers();
				}
				else
				{
					this.OnUnloaded(() => this.DisconnectHandlers());
				}
			}
		}
 
		/// <summary>
		/// Raised after the page was navigated to.
		/// </summary>
		public event EventHandler<NavigatedToEventArgs> NavigatedTo;
 
		/// <summary>
		/// Raised before navigating away from the page.
		/// </summary>
		public event EventHandler<NavigatingFromEventArgs> NavigatingFrom;
 
		/// <summary>
		/// Raised after the page was navigated away from.
		/// </summary>
		public event EventHandler<NavigatedFromEventArgs> NavigatedFrom;
 
		/// <summary>
		/// When overridden in a derived class, allows application developers to customize behavior immediately after the page was navigated to.
		/// </summary>
		/// <param name="args">The event arguments.</param>
		protected virtual void OnNavigatedTo(NavigatedToEventArgs args) { }
 
		/// <summary>
		/// When overridden in a derived class, allows application developers to customize behavior immediately prior to the page being navigated away from.
		/// </summary>
		/// <param name="args">The event arguments.</param>
		protected virtual void OnNavigatingFrom(NavigatingFromEventArgs args) { }
 
		/// <summary>
		/// When overridden in a derived class, allows application developers to customize behavior immediately after the page was being navigated away from.
		/// </summary>
		/// <param name="args">The event arguments.</param>
		protected virtual void OnNavigatedFrom(NavigatedFromEventArgs args) { }
 
		/// <summary>
		/// Retrieves the parent window that contains the page.
		/// </summary>
		/// <returns>The <see cref="Window"/> instance that parents the page.</returns>
		public virtual Window GetParentWindow()
			=> this.FindParentOfType<Window>();
	}
}