2016-03-22 23:02:25 +03:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
using System.Collections.Specialized;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
2016-05-03 01:19:12 +03:00
|
|
|
using Xamarin.Forms.Internals;
|
2016-03-22 23:02:25 +03:00
|
|
|
using Xamarin.Forms.Platform;
|
|
|
|
|
|
|
|
namespace Xamarin.Forms
|
|
|
|
{
|
|
|
|
[RenderWith(typeof(_PageRenderer))]
|
2016-08-30 20:46:14 +03:00
|
|
|
public class Page : VisualElement, ILayout, IPageController, IElementConfiguration<Page>
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-06-16 18:45:09 +03:00
|
|
|
public const string BusySetSignalName = "Xamarin.BusySet";
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
public const string AlertSignalName = "Xamarin.SendAlert";
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
public const string ActionSheetSignalName = "Xamarin.ShowActionSheet";
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
internal static readonly BindableProperty IgnoresContainerAreaProperty = BindableProperty.Create("IgnoresContainerArea", typeof(bool), typeof(Page), false);
|
|
|
|
|
|
|
|
public static readonly BindableProperty BackgroundImageProperty = BindableProperty.Create("BackgroundImage", typeof(string), typeof(Page), default(string));
|
|
|
|
|
|
|
|
public static readonly BindableProperty IsBusyProperty = BindableProperty.Create("IsBusy", typeof(bool), typeof(Page), false, propertyChanged: (bo, o, n) => ((Page)bo).OnPageBusyChanged());
|
|
|
|
|
|
|
|
public static readonly BindableProperty PaddingProperty = BindableProperty.Create("Padding", typeof(Thickness), typeof(Page), default(Thickness), propertyChanged: (bindable, old, newValue) =>
|
|
|
|
{
|
|
|
|
var layout = (Page)bindable;
|
|
|
|
layout.UpdateChildrenLayout();
|
|
|
|
});
|
|
|
|
|
|
|
|
public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(Page), null);
|
|
|
|
|
|
|
|
public static readonly BindableProperty IconProperty = BindableProperty.Create("Icon", typeof(FileImageSource), typeof(Page), default(FileImageSource));
|
|
|
|
|
2016-08-30 20:46:14 +03:00
|
|
|
readonly Lazy<PlatformConfigurationRegistry<Page>> _platformConfigurationRegistry;
|
|
|
|
|
2016-03-22 23:02:25 +03:00
|
|
|
bool _allocatedFlag;
|
|
|
|
Rectangle _containerArea;
|
|
|
|
|
|
|
|
bool _containerAreaSet;
|
|
|
|
|
|
|
|
bool _hasAppeared;
|
|
|
|
|
|
|
|
ReadOnlyCollection<Element> _logicalChildren;
|
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
IPageController PageController => this as IPageController;
|
|
|
|
IElementController ElementController => this as IElementController;
|
|
|
|
|
2016-03-22 23:02:25 +03:00
|
|
|
public Page()
|
|
|
|
{
|
|
|
|
var toolbarItems = new ObservableCollection<ToolbarItem>();
|
|
|
|
toolbarItems.CollectionChanged += OnToolbarItemsCollectionChanged;
|
|
|
|
ToolbarItems = toolbarItems;
|
2016-06-16 18:45:09 +03:00
|
|
|
PageController.InternalChildren.CollectionChanged += InternalChildrenOnCollectionChanged;
|
2016-08-30 20:46:14 +03:00
|
|
|
_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<Page>>(() => new PlatformConfigurationRegistry<Page>(this));
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
public string BackgroundImage
|
|
|
|
{
|
|
|
|
get { return (string)GetValue(BackgroundImageProperty); }
|
|
|
|
set { SetValue(BackgroundImageProperty, value); }
|
|
|
|
}
|
|
|
|
|
|
|
|
public FileImageSource Icon
|
|
|
|
{
|
|
|
|
get { return (FileImageSource)GetValue(IconProperty); }
|
|
|
|
set { SetValue(IconProperty, value); }
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool IsBusy
|
|
|
|
{
|
|
|
|
get { return (bool)GetValue(IsBusyProperty); }
|
|
|
|
set { SetValue(IsBusyProperty, value); }
|
|
|
|
}
|
|
|
|
|
|
|
|
public Thickness Padding
|
|
|
|
{
|
|
|
|
get { return (Thickness)GetValue(PaddingProperty); }
|
|
|
|
set { SetValue(PaddingProperty, value); }
|
|
|
|
}
|
|
|
|
|
|
|
|
public string Title
|
|
|
|
{
|
|
|
|
get { return (string)GetValue(TitleProperty); }
|
|
|
|
set { SetValue(TitleProperty, value); }
|
|
|
|
}
|
|
|
|
|
|
|
|
public IList<ToolbarItem> ToolbarItems { get; internal set; }
|
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
Rectangle IPageController.ContainerArea
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
|
|
|
get { return _containerArea; }
|
|
|
|
set
|
|
|
|
{
|
|
|
|
if (_containerArea == value)
|
|
|
|
return;
|
|
|
|
_containerAreaSet = true;
|
|
|
|
_containerArea = value;
|
|
|
|
ForceLayout();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
bool IPageController.IgnoresContainerArea
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
|
|
|
get { return (bool)GetValue(IgnoresContainerAreaProperty); }
|
|
|
|
set { SetValue(IgnoresContainerAreaProperty, value); }
|
|
|
|
}
|
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
ObservableCollection<Element> IPageController.InternalChildren { get; } = new ObservableCollection<Element>();
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
internal override ReadOnlyCollection<Element> LogicalChildrenInternal =>
|
|
|
|
_logicalChildren ?? (_logicalChildren = new ReadOnlyCollection<Element>(PageController.InternalChildren));
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
public event EventHandler LayoutChanged;
|
|
|
|
|
|
|
|
public event EventHandler Appearing;
|
|
|
|
|
|
|
|
public event EventHandler Disappearing;
|
|
|
|
|
|
|
|
public Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons)
|
|
|
|
{
|
|
|
|
var args = new ActionSheetArguments(title, cancel, destruction, buttons);
|
|
|
|
MessagingCenter.Send(this, ActionSheetSignalName, args);
|
|
|
|
return args.Result.Task;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task DisplayAlert(string title, string message, string cancel)
|
|
|
|
{
|
|
|
|
return DisplayAlert(title, message, null, cancel);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
|
|
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(cancel))
|
|
|
|
throw new ArgumentNullException("cancel");
|
|
|
|
|
|
|
|
var args = new AlertArguments(title, message, accept, cancel);
|
|
|
|
MessagingCenter.Send(this, AlertSignalName, args);
|
|
|
|
return args.Result.Task;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ForceLayout()
|
|
|
|
{
|
|
|
|
SizeAllocated(Width, Height);
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool SendBackButtonPressed()
|
|
|
|
{
|
|
|
|
return OnBackButtonPressed();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual void LayoutChildren(double x, double y, double width, double height)
|
|
|
|
{
|
|
|
|
var area = new Rectangle(x, y, width, height);
|
|
|
|
Rectangle originalArea = area;
|
|
|
|
if (_containerAreaSet)
|
|
|
|
{
|
2016-06-16 18:45:09 +03:00
|
|
|
area = PageController.ContainerArea;
|
2016-03-22 23:02:25 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2016-12-06 15:09:05 +03:00
|
|
|
List<Element> elements = ElementController.LogicalChildren.ToList();
|
|
|
|
foreach (Element element in elements)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
|
|
|
var child = element as VisualElement;
|
|
|
|
if (child == null)
|
|
|
|
continue;
|
|
|
|
var page = child as Page;
|
2016-06-16 18:45:09 +03:00
|
|
|
if (page != null && ((IPageController)page).IgnoresContainerArea)
|
2016-03-22 23:02:25 +03:00
|
|
|
Forms.Layout.LayoutChildIntoBoundingRegion(child, originalArea);
|
|
|
|
else
|
|
|
|
Forms.Layout.LayoutChildIntoBoundingRegion(child, area);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual void OnAppearing()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual bool OnBackButtonPressed()
|
|
|
|
{
|
|
|
|
var application = RealParent as Application;
|
|
|
|
if (application == null || this == application.MainPage)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
var canceled = false;
|
|
|
|
EventHandler handler = (sender, args) => { canceled = true; };
|
|
|
|
application.PopCanceled += handler;
|
|
|
|
Navigation.PopModalAsync().ContinueWith(t => { throw t.Exception; }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
|
|
|
|
|
|
|
|
application.PopCanceled -= handler;
|
|
|
|
return !canceled;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnBindingContextChanged()
|
|
|
|
{
|
|
|
|
base.OnBindingContextChanged();
|
|
|
|
foreach (ToolbarItem toolbarItem in ToolbarItems)
|
|
|
|
{
|
|
|
|
SetInheritedBindingContext(toolbarItem, BindingContext);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual void OnChildMeasureInvalidated(object sender, EventArgs e)
|
|
|
|
{
|
|
|
|
InvalidationTrigger trigger = (e as InvalidationEventArgs)?.Trigger ?? InvalidationTrigger.Undefined;
|
|
|
|
OnChildMeasureInvalidated((VisualElement)sender, trigger);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual void OnDisappearing()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnParentSet()
|
|
|
|
{
|
|
|
|
if (!Application.IsApplicationOrNull(RealParent) && !(RealParent is Page))
|
|
|
|
throw new InvalidOperationException("Parent of a Page must also be a Page");
|
|
|
|
base.OnParentSet();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnSizeAllocated(double width, double height)
|
|
|
|
{
|
|
|
|
_allocatedFlag = true;
|
|
|
|
base.OnSizeAllocated(width, height);
|
|
|
|
UpdateChildrenLayout();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void UpdateChildrenLayout()
|
|
|
|
{
|
|
|
|
if (!ShouldLayoutChildren())
|
|
|
|
return;
|
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
var startingLayout = new List<Rectangle>(ElementController.LogicalChildren.Count);
|
|
|
|
foreach (VisualElement c in ElementController.LogicalChildren)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
for (var i = 0; i < ElementController.LogicalChildren.Count; i++)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-06-16 18:45:09 +03:00
|
|
|
var c = (VisualElement)ElementController.LogicalChildren[i];
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
if (c.Bounds != startingLayout[i])
|
|
|
|
{
|
|
|
|
EventHandler handler = LayoutChanged;
|
|
|
|
if (handler != null)
|
|
|
|
handler(this, EventArgs.Empty);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal virtual void OnChildMeasureInvalidated(VisualElement child, InvalidationTrigger trigger)
|
|
|
|
{
|
|
|
|
var container = this as IPageContainer<Page>;
|
|
|
|
if (container != null)
|
|
|
|
{
|
|
|
|
Page page = container.CurrentPage;
|
|
|
|
if (page != null && page.IsVisible && (!page.IsPlatformEnabled || !page.IsNativeStateConsistent))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-06-16 18:45:09 +03:00
|
|
|
for (var i = 0; i < ElementController.LogicalChildren.Count; i++)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-06-16 18:45:09 +03:00
|
|
|
var v = ElementController.LogicalChildren[i] as VisualElement;
|
2016-03-22 23:02:25 +03:00
|
|
|
if (v != null && v.IsVisible && (!v.IsPlatformEnabled || !v.IsNativeStateConsistent))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_allocatedFlag = false;
|
2016-05-03 01:19:12 +03:00
|
|
|
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
2016-03-22 23:02:25 +03:00
|
|
|
if (!_allocatedFlag && Width >= 0 && Height >= 0)
|
|
|
|
{
|
|
|
|
SizeAllocated(Width, Height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
void IPageController.SendAppearing()
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
|
|
|
if (_hasAppeared)
|
|
|
|
return;
|
|
|
|
|
|
|
|
_hasAppeared = true;
|
|
|
|
|
|
|
|
if (IsBusy)
|
|
|
|
MessagingCenter.Send(this, BusySetSignalName, true);
|
|
|
|
|
|
|
|
OnAppearing();
|
|
|
|
EventHandler handler = Appearing;
|
|
|
|
if (handler != null)
|
|
|
|
handler(this, EventArgs.Empty);
|
|
|
|
|
|
|
|
var pageContainer = this as IPageContainer<Page>;
|
2016-06-16 18:45:09 +03:00
|
|
|
((IPageController)pageContainer?.CurrentPage)?.SendAppearing();
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
|
|
|
|
2016-06-16 18:45:09 +03:00
|
|
|
void IPageController.SendDisappearing()
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
|
|
|
if (!_hasAppeared)
|
|
|
|
return;
|
|
|
|
|
|
|
|
_hasAppeared = false;
|
|
|
|
|
|
|
|
if (IsBusy)
|
|
|
|
MessagingCenter.Send(this, BusySetSignalName, false);
|
|
|
|
|
|
|
|
var pageContainer = this as IPageContainer<Page>;
|
2016-06-16 18:45:09 +03:00
|
|
|
((IPageController)pageContainer?.CurrentPage)?.SendDisappearing();
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
OnDisappearing();
|
|
|
|
EventHandler handler = Disappearing;
|
|
|
|
if (handler != null)
|
|
|
|
handler(this, EventArgs.Empty);
|
|
|
|
}
|
|
|
|
|
|
|
|
void InternalChildrenOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
|
|
{
|
|
|
|
if (e.OldItems != null)
|
|
|
|
{
|
|
|
|
foreach (VisualElement item in e.OldItems.OfType<VisualElement>())
|
|
|
|
OnInternalRemoved(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.NewItems != null)
|
|
|
|
{
|
|
|
|
foreach (VisualElement item in e.NewItems.OfType<VisualElement>())
|
|
|
|
OnInternalAdded(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OnInternalAdded(VisualElement view)
|
|
|
|
{
|
|
|
|
view.MeasureInvalidated += OnChildMeasureInvalidated;
|
|
|
|
|
|
|
|
OnChildAdded(view);
|
2016-05-03 01:19:12 +03:00
|
|
|
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void OnInternalRemoved(VisualElement view)
|
|
|
|
{
|
|
|
|
view.MeasureInvalidated -= OnChildMeasureInvalidated;
|
|
|
|
|
|
|
|
OnChildRemoved(view);
|
|
|
|
}
|
|
|
|
|
|
|
|
void OnPageBusyChanged()
|
|
|
|
{
|
|
|
|
if (!_hasAppeared)
|
|
|
|
return;
|
|
|
|
|
|
|
|
MessagingCenter.Send(this, BusySetSignalName, IsBusy);
|
|
|
|
}
|
|
|
|
|
|
|
|
void OnToolbarItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
|
|
|
|
{
|
|
|
|
if (args.Action != NotifyCollectionChangedAction.Add)
|
|
|
|
return;
|
|
|
|
foreach (IElement item in args.NewItems)
|
|
|
|
item.Parent = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ShouldLayoutChildren()
|
|
|
|
{
|
2016-06-16 18:45:09 +03:00
|
|
|
if (!ElementController.LogicalChildren.Any() || Width <= 0 || Height <= 0 || !IsNativeStateConsistent)
|
2016-03-22 23:02:25 +03:00
|
|
|
return false;
|
|
|
|
|
|
|
|
var container = this as IPageContainer<Page>;
|
2016-06-16 18:45:09 +03:00
|
|
|
if (container?.CurrentPage != null)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-06-16 18:45:09 +03:00
|
|
|
if (PageController.InternalChildren.Contains(container.CurrentPage))
|
2016-03-22 23:02:25 +03:00
|
|
|
return container.CurrentPage.IsPlatformEnabled && container.CurrentPage.IsNativeStateConsistent;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
var any = false;
|
2016-06-16 18:45:09 +03:00
|
|
|
for (var i = 0; i < ElementController.LogicalChildren.Count; i++)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-06-16 18:45:09 +03:00
|
|
|
var v = ElementController.LogicalChildren[i] as VisualElement;
|
2016-03-22 23:02:25 +03:00
|
|
|
if (v != null && (!v.IsPlatformEnabled || !v.IsNativeStateConsistent))
|
|
|
|
{
|
|
|
|
any = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return !any;
|
|
|
|
}
|
2016-08-30 20:46:14 +03:00
|
|
|
|
|
|
|
public IPlatformElementConfiguration<T, Page> On<T>() where T : IConfigPlatform
|
|
|
|
{
|
|
|
|
return _platformConfigurationRegistry.Value.On<T>();
|
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
|
|
|
}
|