File: Window\Window.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Xml.Schema;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Graphics;
 
namespace Microsoft.Maui.Controls
{
	[ContentProperty(nameof(Page))]
	public partial class Window : NavigableElement, IWindow, IToolbarElement, IMenuBarElement, IFlowDirectionController, IWindowController
	{
		/// <summary>Bindable property for <see cref="Title"/>.</summary>
		public static readonly BindableProperty TitleProperty = BindableProperty.Create(
			nameof(Title), typeof(string), typeof(Window), default(string?));
 
		/// <summary>Bindable property for <see cref="Page"/>.</summary>
		public static readonly BindableProperty PageProperty = BindableProperty.Create(
			nameof(Page), typeof(Page), typeof(Window), default(Page?),
			propertyChanging: OnPageChanging,
			propertyChanged: (b, o, n) => ((Window)b).OnPageChanged(o as Page, n as Page));
 
		/// <summary>Bindable property for <see cref="FlowDirection"/>.</summary>
		public static readonly BindableProperty FlowDirectionProperty =
			BindableProperty.Create(nameof(FlowDirection), typeof(FlowDirection), typeof(Window), FlowDirection.MatchParent, propertyChanging: FlowDirectionChanging, propertyChanged: FlowDirectionChanged);
 
		/// <summary>Bindable property for <see cref="X"/>.</summary>
		public static readonly BindableProperty XProperty = BindableProperty.Create(
			nameof(X), typeof(double), typeof(Window), Primitives.Dimension.Unset);
 
		/// <summary>Bindable property for <see cref="Y"/>.</summary>
		public static readonly BindableProperty YProperty = BindableProperty.Create(
			nameof(Y), typeof(double), typeof(Window), Primitives.Dimension.Unset);
 
		/// <summary>Bindable property for <see cref="Width"/>.</summary>
		public static readonly BindableProperty WidthProperty = BindableProperty.Create(
			nameof(Width), typeof(double), typeof(Window), Primitives.Dimension.Unset);
 
		/// <summary>Bindable property for <see cref="Height"/>.</summary>
		public static readonly BindableProperty HeightProperty = BindableProperty.Create(
			nameof(Height), typeof(double), typeof(Window), Primitives.Dimension.Unset);
 
		/// <summary>Bindable property for <see cref="MaximumWidth"/>.</summary>
		public static readonly BindableProperty MaximumWidthProperty = BindableProperty.Create(
			nameof(MaximumWidth), typeof(double), typeof(Window), Primitives.Dimension.Maximum);
 
		/// <summary>Bindable property for <see cref="MaximumHeight"/>.</summary>
		public static readonly BindableProperty MaximumHeightProperty = BindableProperty.Create(
			nameof(MaximumHeight), typeof(double), typeof(Window), Primitives.Dimension.Maximum);
 
		/// <summary>Bindable property for <see cref="MinimumWidth"/>.</summary>
		public static readonly BindableProperty MinimumWidthProperty = BindableProperty.Create(
			nameof(MinimumWidth), typeof(double), typeof(Window), Primitives.Dimension.Minimum);
 
		/// <summary>Bindable property for <see cref="MinimumHeight"/>.</summary>
		public static readonly BindableProperty MinimumHeightProperty = BindableProperty.Create(
			nameof(MinimumHeight), typeof(double), typeof(Window), Primitives.Dimension.Minimum);
 
		/// <summary>Bindable property for <see cref="TitleBar"/>.</summary>
		public static readonly BindableProperty TitleBarProperty = BindableProperty.Create(
			nameof(TitleBar), typeof(TitleBar), typeof(Window), null, propertyChanged: TitleBarChanged);
 
		HashSet<IWindowOverlay> _overlays = new HashSet<IWindowOverlay>();
		List<IVisualTreeElement> _visualChildren;
		Toolbar? _toolbar;
		MenuBarTracker _menuBarTracker;
		bool _isActivated;
 
		IToolbar? IToolbarElement.Toolbar => Toolbar;
		internal Toolbar? Toolbar
		{
			get => _toolbar;
			set => ToolbarElement.SetValue(ref _toolbar, value, Handler);
		}
 
		public event EventHandler? SizeChanged;
 
		public IReadOnlyCollection<IWindowOverlay> Overlays => _overlays.ToList().AsReadOnly();
 
		public IVisualDiagnosticsOverlay VisualDiagnosticsOverlay { get; }
 
		public Window()
		{
			_visualChildren = new List<IVisualTreeElement>();
			AlertManager = new AlertManager(this);
			ModalNavigationManager = new ModalNavigationManager(this);
			Navigation = new NavigationImpl(this);
#pragma warning disable CA1416 // TODO: VisualDiagnosticsOverlay is supported on android 23.0 and above
			VisualDiagnosticsOverlay = new VisualDiagnosticsOverlay(this);
#pragma warning restore CA1416
			_menuBarTracker = new MenuBarTracker(this, "MenuBar");
		}
 
		public Window(Page page)
			: this()
		{
			Page = page;
		}
 
		IMenuBar? IMenuBarElement.MenuBar => _menuBarTracker.MenuBar;
 
		public string? Title
		{
			get => (string?)GetValue(TitleProperty);
			set => SetValue(TitleProperty, value);
		}
 
		string? ITitledElement.Title => Title ?? (Page as Shell)?.Title;
 
		public Page? Page
		{
			get => (Page?)GetValue(PageProperty);
			set => SetValue(PageProperty, value);
		}
 
		public double X
		{
			get => (double)GetValue(XProperty);
			set => SetValue(XProperty, value);
		}
 
		public double Y
		{
			get => (double)GetValue(YProperty);
			set => SetValue(YProperty, value);
		}
 
		public double Width
		{
			get => (double)GetValue(WidthProperty);
			set => SetValue(WidthProperty, value);
		}
 
		public double Height
		{
			get => (double)GetValue(HeightProperty);
			set => SetValue(HeightProperty, value);
		}
 
		public double MaximumWidth
		{
			get => (double)GetValue(MaximumWidthProperty);
			set => SetValue(MaximumWidthProperty, value);
		}
 
		public double MaximumHeight
		{
			get => (double)GetValue(MaximumHeightProperty);
			set => SetValue(MaximumHeightProperty, value);
		}
 
		public double MinimumWidth
		{
			get => (double)GetValue(MinimumWidthProperty);
			set => SetValue(MinimumWidthProperty, value);
		}
 
		public double MinimumHeight
		{
			get => (double)GetValue(MinimumHeightProperty);
			set => SetValue(MinimumHeightProperty, value);
		}
 
		public ITitleBar? TitleBar
		{
			get => (ITitleBar?)GetValue(TitleBarProperty);
			set => SetValue(TitleBarProperty, value);
		}
 
		double IWindow.X => GetPositionCoordinate(XProperty);
 
		double IWindow.Y => GetPositionCoordinate(YProperty);
 
		double IWindow.Width => GetSizeCoordinate(WidthProperty);
 
		double IWindow.Height => GetSizeCoordinate(HeightProperty);
 
		double IWindow.MaximumWidth => GetSizeCoordinate(MaximumWidthProperty);
 
		double IWindow.MaximumHeight => GetSizeCoordinate(MaximumHeightProperty);
 
		double IWindow.MinimumWidth => GetSizeCoordinate(MinimumWidthProperty);
 
		double IWindow.MinimumHeight => GetSizeCoordinate(MinimumHeightProperty);
 
		double GetPositionCoordinate(BindableProperty property)
		{
			if (!IsSet(property))
				return Primitives.Dimension.Unset;
			var coord = (double)GetValue(property);
			if (!Primitives.Dimension.IsExplicitSet(coord))
				return Primitives.Dimension.Unset;
			return coord;
		}
 
		double GetSizeCoordinate(BindableProperty property)
		{
			if (!IsSet(property))
				return Primitives.Dimension.Unset;
			var coord = (double)GetValue(property);
			if (coord == -1 || !Primitives.Dimension.IsExplicitSet(coord))
				return Primitives.Dimension.Unset;
			return ValidatePositive(coord);
		}
 
		int _batchFrameUpdate = 0;
 
		void IWindow.FrameChanged(Rect frame)
		{
			if (new Rect(X, Y, Width, Height) == frame)
			{
				return;
			}
 
			_batchFrameUpdate++;
 
			var shouldTriggerSizeChanged = (Width != frame.Width) || (Height != frame.Height);
 
			SetValue(XProperty, frame.X, SetterSpecificity.FromHandler);
			SetValue(YProperty, frame.Y, SetterSpecificity.FromHandler);
			SetValue(WidthProperty, frame.Width, SetterSpecificity.FromHandler);
			SetValue(HeightProperty, frame.Height, SetterSpecificity.FromHandler);
 
			_batchFrameUpdate--;
			if (_batchFrameUpdate < 0)
				_batchFrameUpdate = 0;
 
			if (_batchFrameUpdate == 0 && shouldTriggerSizeChanged)
			{
				SizeChanged?.Invoke(this, EventArgs.Empty);
			}
		}
 
		private protected override void UpdateHandlerValue(string property, bool valueChanged)
		{
			if (valueChanged && _batchFrameUpdate > 0 && (property == nameof(X) || property == nameof(Y) || property == nameof(Width) || property == nameof(Height)))
			{
				return;
			}
 
			base.UpdateHandlerValue(property, valueChanged);
		}
 
		public event EventHandler<ModalPoppedEventArgs>? ModalPopped;
		public event EventHandler<ModalPoppingEventArgs>? ModalPopping;
		public event EventHandler<ModalPushedEventArgs>? ModalPushed;
		public event EventHandler<ModalPushingEventArgs>? ModalPushing;
		public event EventHandler? PopCanceled;
 
		public event EventHandler? Created;
		public event EventHandler? Resumed;
		public event EventHandler? Activated;
		public event EventHandler? Deactivated;
		public event EventHandler? Stopped;
		public event EventHandler? Destroying;
		public event EventHandler<BackgroundingEventArgs>? Backgrounding;
		public event EventHandler<DisplayDensityChangedEventArgs>? DisplayDensityChanged;
 
		protected virtual void OnCreated() { }
		protected virtual void OnResumed() { }
		protected virtual void OnActivated() { }
		protected virtual void OnDeactivated() { }
		protected virtual void OnStopped() { }
		protected virtual void OnDestroying() { }
		protected virtual void OnBackgrounding(IPersistedState state) { }
		protected virtual void OnDisplayDensityChanged(float displayDensity) { }
 
		protected override void OnPropertyChanged([CallerMemberName] string? propertyName = null)
		{
			base.OnPropertyChanged(propertyName);
 
			if (propertyName == nameof(Page))
				Handler?.UpdateValue(nameof(IWindow.Content));
		}
 
		/// <inheritdoc/>
		public bool AddOverlay(IWindowOverlay overlay)
		{
			if (overlay is IVisualDiagnosticsOverlay)
				return false;
 
			// Add the overlay. If it's added, 
			// Initalize the native layer if it wasn't already,
			// and call invalidate so it will be drawn.
			var result = _overlays.Add(overlay);
			if (result)
			{
				overlay.Initialize();
				overlay.Invalidate();
			}
 
			return result;
		}
 
		/// <inheritdoc/>
		public bool RemoveOverlay(IWindowOverlay overlay)
		{
			if (overlay is IVisualDiagnosticsOverlay)
				return false;
 
			var result = _overlays.Remove(overlay);
			if (result)
				overlay.Deinitialize();
 
			return result;
		}
 
		internal AlertManager AlertManager { get; }
 
		internal ModalNavigationManager ModalNavigationManager { get; }
 
		internal IMauiContext MauiContext =>
			Handler?.MauiContext ?? throw new InvalidOperationException("MauiContext is null.");
 
		internal bool IsActivated
		{
			get
			{
				return _isActivated;
			}
			private set
			{
				if (_isActivated == value)
					return;
 
				_isActivated = value;
 
				if (value)
					SendWindowAppearing();
			}
		}
 
		internal bool IsDestroyed { get; private set; }
		internal bool IsCreated { get; private set; }
 
		IFlowDirectionController FlowController => this;
 
		public FlowDirection FlowDirection
		{
			get { return (FlowDirection)GetValue(FlowDirectionProperty); }
			set { SetValue(FlowDirectionProperty, value); }
		}
 
		EffectiveFlowDirection _effectiveFlowDirection = default(EffectiveFlowDirection);
		EffectiveFlowDirection IFlowDirectionController.EffectiveFlowDirection
		{
			get => _effectiveFlowDirection;
			set => SetEffectiveFlowDirection(value, true);
		}
 
		double IFlowDirectionController.Width => (Page as VisualElement)?.Width ?? 0;
 
		void SetEffectiveFlowDirection(EffectiveFlowDirection value, bool fireFlowDirectionPropertyChanged)
		{
			if (value == _effectiveFlowDirection)
				return;
 
			_effectiveFlowDirection = value;
 
			if (fireFlowDirectionPropertyChanged)
				OnPropertyChanged(FlowDirectionProperty.PropertyName);
 
		}
 
		static void FlowDirectionChanging(BindableObject bindable, object oldValue, object newValue)
		{
			var self = (IFlowDirectionController)bindable;
 
			if (self.EffectiveFlowDirection.IsExplicit() && oldValue == newValue)
				return;
 
			var newFlowDirection = ((FlowDirection)newValue).ToEffectiveFlowDirection(isExplicit: true);
			self.EffectiveFlowDirection = newFlowDirection;
		}
 
		static void FlowDirectionChanged(BindableObject bindable, object oldValue, object newValue)
		{
			PropertyPropagationExtensions.PropagatePropertyChanged(
				FlowDirectionProperty.PropertyName,
				(Element)bindable,
				((IVisualTreeElement)bindable).GetVisualChildren());
		}
 
		static void TitleBarChanged(BindableObject bindable, object oldValue, object newValue)
		{
			var self = (Window)bindable;
			if (self != null)
			{
				if (oldValue is TitleBar prevTitleBar)
				{
					self.RemoveLogicalChild(prevTitleBar);
				}
 
				if (newValue is TitleBar titleBar)
				{
					self.AddLogicalChild(titleBar);
				}
			}
		}
 
		bool IFlowDirectionController.ApplyEffectiveFlowDirectionToChildContainer => true;
 
		Window IWindowController.Window
		{
			get => this;
			set => throw new InvalidOperationException("A window cannot set a window.");
		}
 
		IView? IWindow.Content =>
			Page ?? throw new InvalidOperationException("No page was set on the window.");
 
		Application? Application => Parent as Application;
 
		internal void FinishedAddingWindowToApplication(Application application)
		{
			if (Parent != null)
				SendWindowAppearing();
		}
 
		void SendWindowAppearing()
		{
			if (Navigation.ModalStack.Count == 0)
				Page?.SendAppearing();
		}
 
		void SendWindowDisppearing()
		{
			if (Navigation.ModalStack.Count == 0)
				Page?.SendDisappearing();
 
			IsActivated = false;
		}
 
		internal void OnModalPopped(Page modalPage)
		{
			int index = _visualChildren.IndexOf(modalPage);
			_visualChildren.Remove(modalPage);
 
			var args = new ModalPoppedEventArgs(modalPage);
			ModalPopped?.Invoke(this, args);
			Application?.NotifyOfWindowModalEvent(args);
 
#if WINDOWS
			this.Handler?.UpdateValue(nameof(IWindow.TitleBarDragRectangles));
			this.Handler?.UpdateValue(nameof(ITitledElement.Title));
#endif
		}
 
		internal bool OnModalPopping(Page modalPage)
		{
			var args = new ModalPoppingEventArgs(modalPage);
			ModalPopping?.Invoke(this, args);
			Application?.NotifyOfWindowModalEvent(args);
			return args.Cancel;
		}
 
		internal void OnModalPushed(Page modalPage)
		{
			_visualChildren.Add(modalPage);
			var args = new ModalPushedEventArgs(modalPage);
			ModalPushed?.Invoke(this, args);
			Application?.NotifyOfWindowModalEvent(args);
 
#if WINDOWS
			this.Handler?.UpdateValue(nameof(IWindow.TitleBarDragRectangles));
			this.Handler?.UpdateValue(nameof(ITitledElement.Title));
#endif
		}
 
		internal void OnModalPushing(Page modalPage)
		{
			var args = new ModalPushingEventArgs(modalPage);
			ModalPushing?.Invoke(this, args);
			Application?.NotifyOfWindowModalEvent(args);
		}
 
		internal void OnPopCanceled()
		{
			PopCanceled?.Invoke(this, EventArgs.Empty);
		}
 
		void IWindow.Created()
		{
			if (IsCreated)
				throw new InvalidOperationException("Window was already created");
 
			IsCreated = true;
			IsDestroyed = false;
 
			Created?.Invoke(this, EventArgs.Empty);
			OnCreated();
			Application?.SendStart();
		}
 
		void IWindow.Activated()
		{
			if (IsActivated)
				throw new InvalidOperationException("Window was already activated");
 
			IsActivated = true;
			Activated?.Invoke(this, EventArgs.Empty);
			OnActivated();
		}
 
		void IWindow.Deactivated()
		{
			if (!IsActivated)
				throw new InvalidOperationException("Window was already deactivated");
 
			IsActivated = false;
			Deactivated?.Invoke(this, EventArgs.Empty);
			OnDeactivated();
		}
 
		void IWindow.Stopped()
		{
			Stopped?.Invoke(this, EventArgs.Empty);
			OnStopped();
			Application?.SendSleep();
		}
 
		void IWindow.Destroying()
		{
			if (IsDestroyed)
				throw new InvalidOperationException("Window was already destroyed");
 
			IsDestroyed = true;
			IsCreated = false;
 
			SendWindowDisppearing();
			Destroying?.Invoke(this, EventArgs.Empty);
			OnDestroying();
 
			AlertManager.Unsubscribe();
			Application?.RemoveWindow(this);
			Handler?.DisconnectHandler();
		}
 
		void IWindow.Resumed()
		{
			Resumed?.Invoke(this, EventArgs.Empty);
			OnResumed();
			Application?.SendResume();
		}
 
		void IWindow.Backgrounding(IPersistedState state)
		{
			Backgrounding?.Invoke(this, new BackgroundingEventArgs(state));
			OnBackgrounding(state);
		}
 
		void IWindow.DisplayDensityChanged(float displayDensity)
		{
			DisplayDensityChanged?.Invoke(this, new DisplayDensityChangedEventArgs(displayDensity));
			OnDisplayDensityChanged(displayDensity);
		}
 
		float IWindow.RequestDisplayDensity()
		{
			var request = new DisplayDensityRequest();
			var result = Handler?.InvokeWithResult(nameof(IWindow.RequestDisplayDensity), request);
			return result ?? 1.0f;
		}
 
		FlowDirection IWindow.FlowDirection
		{
			get
			{
				// If the user has set the root page to be RTL
				// Then we want the window to also reflect RTL
				// We don't want to force the user to reach into the window
				// in order to enable RTL Window features on WinUI
				if (FlowDirection == FlowDirection.MatchParent &&
					Page is IFlowDirectionController controller &&
					controller.EffectiveFlowDirection.IsExplicit())
				{
					return controller.EffectiveFlowDirection.ToFlowDirection();
				}
 
				return _effectiveFlowDirection.ToFlowDirection();
			}
		}
 
		public float DisplayDensity => ((IWindow)this).RequestDisplayDensity();
 
		private protected override void OnHandlerChangingCore(HandlerChangingEventArgs args)
		{
			base.OnHandlerChangingCore(args);
			var mauiContext = args?.NewHandler?.MauiContext;
 
			if (FlowDirection == FlowDirection.MatchParent && mauiContext != null)
			{
				var flowDirection = AppInfo.Current.RequestedLayoutDirection.ToFlowDirection();
				FlowController.EffectiveFlowDirection = flowDirection.ToEffectiveFlowDirection(true);
			}
		}
 
		static void OnPageChanging(BindableObject bindable, object oldValue, object newValue)
		{
			if (oldValue is Page oldPage)
				oldPage.SendDisappearing();
		}
 
		void OnPageChanged(Page? oldPage, Page? newPage)
		{
			if (oldPage != null)
			{
				_menuBarTracker.Target = null;
				_visualChildren.Remove(oldPage);
				RemoveLogicalChild(oldPage);
				oldPage.HandlerChanged -= OnPageHandlerChanged;
				oldPage.HandlerChanging -= OnPageHandlerChanging;
			}
 
			if (oldPage is Shell shell)
				shell.PropertyChanged -= ShellPropertyChanged;
 
			if (newPage != null)
			{
				_visualChildren.Add(newPage);
				AddLogicalChild(newPage);
				newPage.NavigationProxy.Inner = NavigationProxy;
				_menuBarTracker.Target = newPage;
 
				if (Parent != null)
				{
					SendWindowAppearing();
				}
 
				newPage.HandlerChanged += OnPageHandlerChanged;
				newPage.HandlerChanging += OnPageHandlerChanging;
 
				if (newPage.Handler != null)
					OnPageHandlerChanged(newPage, EventArgs.Empty);
			}
 
			if (newPage is Shell newShell)
			{
				newShell.PropertyChanged += ShellPropertyChanged;
			}
 
			if (oldPage?.IsLoaded == true)
			{
				this.OnUnloaded(() => oldPage.DisconnectHandlers());
			}
			else
			{
				oldPage?.DisconnectHandlers();
			}
 
			Handler?.UpdateValue(nameof(IWindow.FlowDirection));
		}
 
		void OnPageHandlerChanged(object? sender, EventArgs e)
		{
			ModalNavigationManager.PageAttachedHandler();
			AlertManager.Subscribe();
		}
 
		void OnPageHandlerChanging(object? sender, HandlerChangingEventArgs e)
		{
			AlertManager.Unsubscribe();
		}
 
		void ShellPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			if (e.PropertyName == nameof(Shell.Title))
				Handler?.UpdateValue(nameof(ITitledElement.Title));
		}
 
		bool IWindow.BackButtonClicked()
		{
			if (Navigation.ModalStack.Count > 0)
			{
				return Navigation.ModalStack[Navigation.ModalStack.Count - 1].SendBackButtonPressed();
			}
 
			return this.Page?.SendBackButtonPressed() ?? false;
		}
 
		static double ValidatePositive(double value, [CallerMemberName] string? name = null) =>
			value >= 0
				? value
				: throw new InvalidOperationException($"{name} cannot be less than zero.");
 
		class NavigationImpl : NavigationProxy
		{
			readonly Window _owner;
 
			public NavigationImpl(Window owner)
			{
				_owner = owner;
			}
 
			protected override void OnInsertPageBefore(Page page, Page before)
			{
				throw new InvalidOperationException("InsertPageBefore is not supported, please use a NavigationPage.");
			}
 
			protected override Task OnPushAsync(Page page, bool animated)
			{
				throw new InvalidOperationException("PushAsync is not supported, please use a NavigationPage.");
			}
 
			protected override Task<Page> OnPopAsync(bool animated)
			{
				throw new InvalidOperationException("PopAsync is not supported, please use a NavigationPage.");
			}
 
			protected override Task OnPopToRootAsync(bool animated)
			{
				throw new InvalidOperationException("PopToRootAsync is not supported, please use a NavigationPage.");
			}
 
			protected override void OnRemovePage(Page page)
			{
				throw new InvalidOperationException("RemovePage is not supported, please use a NavigationPage.");
			}
 
			protected override IReadOnlyList<Page> GetModalStack()
			{
				return _owner.ModalNavigationManager.ModalStack;
			}
 
			protected override Task<Page?> OnPopModal(bool animated)
			{
				return _owner.ModalNavigationManager.PopModalAsync(animated);
			}
 
			protected override Task OnPushModal(Page modal, bool animated)
			{
				return _owner.ModalNavigationManager.PushModalAsync(modal, animated);
			}
		}
	}
}