| File: FlyoutPage\FlyoutPage.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.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Devices; using Microsoft.Maui.Graphics; namespace Microsoft.Maui.Controls { /// <include file="../../docs/Microsoft.Maui.Controls/FlyoutPage.xml" path="Type[@FullName='Microsoft.Maui.Controls.FlyoutPage']/Docs/*" /> [ContentProperty(nameof(Detail))] [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] public partial class FlyoutPage : Page, IFlyoutPageController, IElementConfiguration<FlyoutPage>, IFlyoutView { /// <summary>Bindable property for <see cref="IsGestureEnabled"/>.</summary> public static readonly BindableProperty IsGestureEnabledProperty = BindableProperty.Create(nameof(IsGestureEnabled), typeof(bool), typeof(FlyoutPage), true); /// <summary>Bindable property for <see cref="IsPresented"/>.</summary> public static readonly BindableProperty IsPresentedProperty = BindableProperty.Create(nameof(IsPresented), typeof(bool), typeof(FlyoutPage), default(bool), propertyChanged: OnIsPresentedPropertyChanged, propertyChanging: OnIsPresentedPropertyChanging, defaultValueCreator: GetDefaultValue); /// <summary>Bindable property for <see cref="FlyoutLayoutBehavior"/>.</summary> public static readonly BindableProperty FlyoutLayoutBehaviorProperty = BindableProperty.Create(nameof(FlyoutLayoutBehavior), typeof(FlyoutLayoutBehavior), typeof(FlyoutPage), default(FlyoutLayoutBehavior), propertyChanged: OnFlyoutLayoutBehaviorPropertyChanged); Page _detail; Rect _detailBounds; Page _flyout; Rect _flyoutBounds; IFlyoutPageController FlyoutPageController => this; /// <include file="../../docs/Microsoft.Maui.Controls/FlyoutPage.xml" path="//Member[@MemberName='Detail']/Docs/*" /> public Page Detail { get { return _detail; } set { if (_detail != null && value == null) throw new ArgumentNullException(nameof(value), "Detail cannot be set to null once a value is set."); if (_detail == value) return; if (value.RealParent != null) throw new InvalidOperationException("Detail must not already have a parent."); var previousDetail = _detail; // TODO MAUI refine this to fire earlier _detail?.SendNavigatingFrom(new NavigatingFromEventArgs()); OnPropertyChanging(); if (_detail != null) InternalChildren.Remove(_detail); _detail = value; InternalChildren.Add(_detail); OnPropertyChanged(); if (this.HasAppeared) { previousDetail?.SendDisappearing(); _detail?.SendAppearing(); } previousDetail?.SendNavigatedFrom(new NavigatedFromEventArgs(_detail, NavigationType.PageSwap)); _detail?.SendNavigatedTo(new NavigatedToEventArgs(previousDetail)); } } /// <include file="../../docs/Microsoft.Maui.Controls/FlyoutPage.xml" path="//Member[@MemberName='IsGestureEnabled']/Docs/*" /> public bool IsGestureEnabled { get { return (bool)GetValue(IsGestureEnabledProperty); } set { SetValue(IsGestureEnabledProperty, value); } } /// <include file="../../docs/Microsoft.Maui.Controls/FlyoutPage.xml" path="//Member[@MemberName='IsPresented']/Docs/*" /> public bool IsPresented { get { return (bool)GetValue(IsPresentedProperty); } set { SetValue(IsPresentedProperty, value); } } /// <include file="../../docs/Microsoft.Maui.Controls/FlyoutPage.xml" path="//Member[@MemberName='Flyout']/Docs/*" /> public Page Flyout { get { return _flyout; } set { if (_flyout != null && value == null) throw new ArgumentNullException(nameof(value), "Flyout cannot be set to null once a value is set"); if (string.IsNullOrEmpty(value.Title)) throw new InvalidOperationException("Title property must be set on Flyout page"); if (_flyout == value) return; if (value.RealParent != null) throw new InvalidOperationException("Flyout must not already have a parent."); // TODO MAUI refine this to fire earlier var previousFlyout = _flyout; // TODO MAUI refine this to fire earlier _flyout?.SendNavigatingFrom(new NavigatingFromEventArgs()); OnPropertyChanging(); if (_flyout != null) InternalChildren.Remove(_flyout); _flyout = value; InternalChildren.Add(_flyout); OnPropertyChanged(); if (this.HasAppeared) { previousFlyout?.SendDisappearing(); _flyout?.SendAppearing(); } previousFlyout?.SendNavigatedFrom(new NavigatedFromEventArgs(_flyout, NavigationType.PageSwap)); _flyout?.SendNavigatedTo(new NavigatedToEventArgs(previousFlyout)); } } /// <include file="../../docs/Microsoft.Maui.Controls/FlyoutPage.xml" path="//Member[@MemberName='FlyoutLayoutBehavior']/Docs/*" /> public FlyoutLayoutBehavior FlyoutLayoutBehavior { get { return (FlyoutLayoutBehavior)GetValue(FlyoutLayoutBehaviorProperty); } set { SetValue(FlyoutLayoutBehaviorProperty, value); } } bool IFlyoutPageController.CanChangeIsPresented { get; set; } = true; Rect IFlyoutPageController.DetailBounds { get { return _detailBounds; } set { _detailBounds = value; if (_detail == null) throw new InvalidOperationException("Detail must be set before using a FlyoutPage"); _detail.Layout(value); } } Rect IFlyoutPageController.FlyoutBounds { get { return _flyoutBounds; } set { _flyoutBounds = value; if (_flyout == null) throw new InvalidOperationException("Flyout must be set before using a FlyoutPage"); _flyout.Layout(value); } } bool IFlyoutPageController.ShouldShowSplitMode { get { if (DeviceInfo.Idiom == DeviceIdiom.Phone) return false; FlyoutLayoutBehavior behavior = FlyoutLayoutBehavior; var orientation = Window.GetOrientation(); bool isSplitOnLandscape = (behavior == FlyoutLayoutBehavior.SplitOnLandscape || behavior == FlyoutLayoutBehavior.Default) && orientation.IsLandscape(); bool isSplitOnPortrait = behavior == FlyoutLayoutBehavior.SplitOnPortrait && orientation.IsPortrait(); return behavior == FlyoutLayoutBehavior.Split || isSplitOnLandscape || isSplitOnPortrait; } } public event EventHandler IsPresentedChanged; /// <include file="../../docs/Microsoft.Maui.Controls/FlyoutPage.xml" path="//Member[@MemberName='ShouldShowToolbarButton']/Docs/*" /> public virtual bool ShouldShowToolbarButton() { if (DeviceInfo.Idiom == DeviceIdiom.Phone) return true; FlyoutLayoutBehavior behavior = FlyoutLayoutBehavior; var orientation = DeviceDisplay.MainDisplayInfo.Orientation; bool isSplitOnLandscape = (behavior == FlyoutLayoutBehavior.SplitOnLandscape || behavior == FlyoutLayoutBehavior.Default) && orientation.IsLandscape(); bool isSplitOnPortrait = behavior == FlyoutLayoutBehavior.SplitOnPortrait && orientation.IsPortrait(); return behavior != FlyoutLayoutBehavior.Split && !isSplitOnLandscape && !isSplitOnPortrait; } [Obsolete("Use ArrangeOverride instead")] protected override void LayoutChildren(double x, double y, double width, double height) { if (Flyout == null || Detail == null) throw new InvalidOperationException("Flyout and Detail must be set before using a FlyoutPage"); #if !ANDROID _flyout.Layout(_flyoutBounds); _detail.Layout(_detailBounds); #endif } protected override void OnAppearing() { Flyout?.SendAppearing(); Detail?.SendAppearing(); FlyoutPageController.CanChangeIsPresented = true; UpdateFlyoutLayoutBehavior(this); base.OnAppearing(); } protected override void OnDisappearing() { Flyout?.SendDisappearing(); Detail?.SendDisappearing(); base.OnDisappearing(); } protected override bool OnBackButtonPressed() { if (IsPresented) { if (Flyout.SendBackButtonPressed()) return true; } EventHandler<BackButtonPressedEventArgs> handler = BackButtonPressed; if (handler != null) { var args = new BackButtonPressedEventArgs(); handler(this, args); if (args.Handled) return true; } if (Detail.SendBackButtonPressed()) { return true; } return base.OnBackButtonPressed(); } protected override void OnParentSet() { if (RealParent != null && (Flyout == null || Detail == null)) throw new InvalidOperationException("Flyout and Detail must be set before adding FlyoutPage to a container"); base.OnParentSet(); } [EditorBrowsable(EditorBrowsableState.Never)] public event EventHandler<BackButtonPressedEventArgs> BackButtonPressed; /// <include file="../../docs/Microsoft.Maui.Controls/FlyoutPage.xml" path="//Member[@MemberName='UpdateFlyoutLayoutBehavior']/Docs/*" /> [EditorBrowsable(EditorBrowsableState.Never)] public void UpdateFlyoutLayoutBehavior() { UpdateFlyoutLayoutBehavior(this); } internal static void UpdateFlyoutLayoutBehavior(FlyoutPage page) { if (page is IFlyoutPageController fpc && fpc.ShouldShowSplitMode) { page.SetValue(IsPresentedProperty, true); if (page.FlyoutLayoutBehavior != FlyoutLayoutBehavior.Default) fpc.CanChangeIsPresented = false; } } static void OnIsPresentedPropertyChanged(BindableObject sender, object oldValue, object newValue) => ((FlyoutPage)sender).IsPresentedChanged?.Invoke(sender, EventArgs.Empty); static void OnIsPresentedPropertyChanging(BindableObject sender, object oldValue, object newValue) { if (sender is Maui.IElement element && element.IsShimmed()) { if (sender is FlyoutPage fp && fp is IFlyoutPageController fpc && !fpc.CanChangeIsPresented) throw new InvalidOperationException(string.Format("Can't change IsPresented when setting {0}", fp.FlyoutLayoutBehavior)); } else { if ((!(bool)newValue) && sender is IFlyoutPageController fpc && fpc.ShouldShowSplitMode && sender is FlyoutPage fp) { throw new InvalidOperationException(string.Format("Can't change IsPresented when setting {0}", fp.FlyoutLayoutBehavior)); } } } static void OnFlyoutLayoutBehaviorPropertyChanged(BindableObject sender, object oldValue, object newValue) { var page = (FlyoutPage)sender; UpdateFlyoutLayoutBehavior(page); } static object GetDefaultValue(BindableObject bindable) { return DeviceInfo.Platform == DevicePlatform.macOS; } /// <include file="../../docs/Microsoft.Maui.Controls/FlyoutPage.xml" path="//Member[@MemberName='.ctor']/Docs/*" /> public FlyoutPage() { _platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<FlyoutPage>>(() => new PlatformConfigurationRegistry<FlyoutPage>(this)); (this as IControlsVisualElement).WindowChanged += OnWindowChanged; this.SizeChanged += OnSizeChanged; } readonly Lazy<PlatformConfigurationRegistry<FlyoutPage>> _platformConfigurationRegistry; /// <inheritdoc/> public new IPlatformElementConfiguration<T, FlyoutPage> On<T>() where T : IConfigPlatform { return _platformConfigurationRegistry.Value.On<T>(); } void OnSizeChanged(object sender, EventArgs e) { if (Handler is not null) { Handler?.UpdateValue(nameof(FlyoutBehavior)); SizeChanged -= OnSizeChanged; } } void OnWindowChanged(object sender, EventArgs e) { if (Window is null) { SizeChanged -= OnSizeChanged; SizeChanged += OnSizeChanged; DeviceDisplay.MainDisplayInfoChanged -= OnMainDisplayInfoChanged; } else { DeviceDisplay.MainDisplayInfoChanged += OnMainDisplayInfoChanged; } } void OnMainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e) { Handler?.UpdateValue(nameof(FlyoutBehavior)); } IView IFlyoutView.Flyout => this.Flyout; IView IFlyoutView.Detail => this.Detail; Maui.FlyoutBehavior IFlyoutView.FlyoutBehavior { get { if (((IFlyoutPageController)this).ShouldShowSplitMode) return Maui.FlyoutBehavior.Locked; return Maui.FlyoutBehavior.Flyout; } } #if ANDROID const double DefaultFlyoutSize = 320; const double DefaultSmallFlyoutSize = 240; double IFlyoutView.FlyoutWidth { get { if (DeviceInfo.Idiom == DeviceIdiom.Phone) return -1; var scaledScreenSize = DeviceDisplay.MainDisplayInfo.GetScaledScreenSize(); double w = scaledScreenSize.Width; return w < DefaultSmallFlyoutSize ? w : (w < DefaultFlyoutSize ? DefaultSmallFlyoutSize : DefaultFlyoutSize); } } #else double IFlyoutView.FlyoutWidth => -1; #endif private protected override string GetDebuggerDisplay() { var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(Detail), Detail, "FlyoutPage", Flyout, nameof(BindingContext), BindingContext); return $"{GetType().FullName}: {debugText}"; } } }