diff options
author | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 13:02:25 -0700 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 16:13:41 -0700 |
commit | 17fdde66d94155fc62a034fa6658995bef6fd6e5 (patch) | |
tree | b5e5073a2a7b15cdbe826faa5c763e270a505729 /Xamarin.Forms.Platform.WP8/Platform.cs | |
download | xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.gz xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.bz2 xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.zip |
Initial import
Diffstat (limited to 'Xamarin.Forms.Platform.WP8/Platform.cs')
-rw-r--r-- | Xamarin.Forms.Platform.WP8/Platform.cs | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.WP8/Platform.cs b/Xamarin.Forms.Platform.WP8/Platform.cs new file mode 100644 index 00000000..363c6b84 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Platform.cs @@ -0,0 +1,628 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Phone.Controls; +using Microsoft.Phone.Shell; + +namespace Xamarin.Forms.Platform.WinPhone +{ + // from mono + public class Platform : BindableObject, IPlatform, INavigation + { + internal static readonly BindableProperty RendererProperty = BindableProperty.CreateAttached("Renderer", typeof(IVisualElementRenderer), typeof(Platform), default(IVisualElementRenderer)); + + readonly TurnstileTransition _backwardInTransition = new TurnstileTransition { Mode = TurnstileTransitionMode.BackwardIn }; + + readonly TurnstileTransition _backwardOutTransition = new TurnstileTransition { Mode = TurnstileTransitionMode.BackwardOut }; + + readonly TurnstileTransition _forwardInTransition = new TurnstileTransition { Mode = TurnstileTransitionMode.ForwardIn }; + + readonly TurnstileTransition _forwardOutTransition = new TurnstileTransition { Mode = TurnstileTransitionMode.ForwardOut }; + + readonly NavigationModel _navModel = new NavigationModel(); + + readonly PhoneApplicationPage _page; + + readonly Canvas _renderer; + readonly ToolbarTracker _tracker = new ToolbarTracker(); + + Page _currentDisplayedPage; + CustomMessageBox _visibleMessageBox; + + internal Platform(PhoneApplicationPage page) + { + _tracker.SeparateMasterDetail = true; + + page.BackKeyPress += OnBackKeyPress; + _page = page; + + _renderer = new Canvas(); + _renderer.SizeChanged += RendererSizeChanged; + + _tracker.CollectionChanged += (sender, args) => UpdateToolbarItems(); + + ProgressIndicator indicator; + SystemTray.SetProgressIndicator(page, indicator = new ProgressIndicator { IsVisible = false, IsIndeterminate = true }); + + var busyCount = 0; + MessagingCenter.Subscribe(this, Page.BusySetSignalName, (Page sender, bool enabled) => + { + busyCount = Math.Max(0, enabled ? busyCount + 1 : busyCount - 1); + indicator.IsVisible = busyCount > 0; + }); + + MessagingCenter.Subscribe(this, Page.AlertSignalName, (Page sender, AlertArguments arguments) => + { + var messageBox = new CustomMessageBox { Title = arguments.Title, Message = arguments.Message }; + if (arguments.Accept != null) + messageBox.LeftButtonContent = arguments.Accept; + messageBox.RightButtonContent = arguments.Cancel; + messageBox.Show(); + _visibleMessageBox = messageBox; + messageBox.Dismissed += (o, args) => + { + arguments.SetResult(args.Result == CustomMessageBoxResult.LeftButton); + _visibleMessageBox = null; + }; + }); + + MessagingCenter.Subscribe(this, Page.ActionSheetSignalName, (Page sender, ActionSheetArguments arguments) => + { + var messageBox = new CustomMessageBox { Title = arguments.Title }; + + var listBox = new ListBox { FontSize = 36, Margin = new System.Windows.Thickness(12) }; + var itemSource = new List<string>(); + + if (!string.IsNullOrWhiteSpace(arguments.Destruction)) + itemSource.Add(arguments.Destruction); + itemSource.AddRange(arguments.Buttons); + if (!string.IsNullOrWhiteSpace(arguments.Cancel)) + itemSource.Add(arguments.Cancel); + + listBox.ItemsSource = itemSource.Select(s => new TextBlock { Text = s, Margin = new System.Windows.Thickness(0, 12, 0, 12) }); + messageBox.Content = listBox; + + listBox.SelectionChanged += (o, args) => messageBox.Dismiss(); + messageBox.Dismissed += (o, args) => + { + string result = listBox.SelectedItem != null ? ((TextBlock)listBox.SelectedItem).Text : null; + arguments.SetResult(result); + _visibleMessageBox = null; + }; + + messageBox.Show(); + _visibleMessageBox = messageBox; + }); + } + + internal Size Size + { + get { return new Size(_renderer.ActualWidth, _renderer.ActualHeight); } + } + + Page Page { get; set; } + + void INavigation.InsertPageBefore(Page page, Page before) + { + _navModel.InsertPageBefore(page, before); + } + + IReadOnlyList<Page> INavigation.ModalStack + { + get { return _navModel.Roots.ToList(); } + } + + IReadOnlyList<Page> INavigation.NavigationStack + { + get { return _navModel.Tree.Last(); } + } + + Task<Page> INavigation.PopAsync() + { + return ((INavigation)this).PopAsync(true); + } + + Task<Page> INavigation.PopAsync(bool animated) + { + return Pop(Page, animated); + } + + Task<Page> INavigation.PopModalAsync() + { + return ((INavigation)this).PopModalAsync(true); + } + + Task<Page> INavigation.PopModalAsync(bool animated) + { + var tcs = new TaskCompletionSource<Page>(); + Page result = _navModel.PopModal(); + + IReadOnlyList<Page> last = _navModel.Tree.Last(); + IEnumerable<Page> stack = last; + if (last.Count > 1) + stack = stack.Skip(1); + + Page navRoot = stack.First(); + Page current = _navModel.CurrentPage; + if (current == navRoot) + current = _navModel.Roots.Last(); // Navigation page itself, since nav root has a host + + SetCurrent(current, animated, true, () => tcs.SetResult(result)); + return tcs.Task; + } + + Task INavigation.PopToRootAsync() + { + return ((INavigation)this).PopToRootAsync(true); + } + + async Task INavigation.PopToRootAsync(bool animated) + { + await PopToRoot(Page, animated); + } + + Task INavigation.PushAsync(Page root) + { + return ((INavigation)this).PushAsync(root, true); + } + + Task INavigation.PushAsync(Page root, bool animated) + { + return Push(root, Page, animated); + } + + Task INavigation.PushModalAsync(Page modal) + { + return ((INavigation)this).PushModalAsync(modal, true); + } + + Task INavigation.PushModalAsync(Page modal, bool animated) + { + var tcs = new TaskCompletionSource<object>(); + _navModel.PushModal(modal); + SetCurrent(_navModel.CurrentPage, animated, completedCallback: () => tcs.SetResult(null)); + return tcs.Task; + } + + void INavigation.RemovePage(Page page) + { + RemovePage(page, true); + } + + SizeRequest IPlatform.GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint) + { + // Hack around the fact that Canvas ignores the child constraints. + // It is entirely possible using Canvas as our base class is not wise. + // FIXME: This should not be an if statement. Probably need to define an interface here. + if (widthConstraint > 0 && heightConstraint > 0 && GetRenderer(view) != null) + { + IVisualElementRenderer element = GetRenderer(view); + return element.GetDesiredSize(widthConstraint, heightConstraint); + } + + return new SizeRequest(); + } + + public static IVisualElementRenderer CreateRenderer(VisualElement element) + { + IVisualElementRenderer result = Registrar.Registered.GetHandler<IVisualElementRenderer>(element.GetType()) ?? new ViewRenderer(); + result.SetElement(element); + return result; + } + + public static IVisualElementRenderer GetRenderer(VisualElement self) + { + return (IVisualElementRenderer)self.GetValue(RendererProperty); + } + + public static void SetRenderer(VisualElement self, IVisualElementRenderer renderer) + { + self.SetValue(RendererProperty, renderer); + self.IsPlatformEnabled = renderer != null; + } + + internal Canvas GetCanvas() + { + return _renderer; + } + + internal async Task<Page> Pop(Page ancestor, bool animated) + { + Page result = _navModel.Pop(ancestor); + + Page navRoot = _navModel.Tree.Last().Skip(1).First(); + Page current = _navModel.CurrentPage; + + // The following code is a terrible horrible ugly hack that we are kind of stuck with for the time being + // Effectively what can happen is a TabbedPage with many navigation page children needs to have all those children in the + // nav stack. If you have multiple each of those roots needs to be skipped over. + + // In general the check for the NavigationPage will always hit if the check for the Skip(1) hits, but since that check + // was always there it is left behind to ensure compatibility with previous behavior. + bool replaceWithRoot = current == navRoot; + var parent = current.Parent as NavigationPage; + if (parent != null) + { + if (parent.InternalChildren[0] == current) + replaceWithRoot = true; + } + + if (replaceWithRoot) + current = _navModel.Roots.Last(); // Navigation page itself, since nav root has a host + + await SetCurrent(current, animated, true); + return result; + } + + internal async Task PopToRoot(Page ancestor, bool animated) + { + _navModel.PopToRoot(ancestor); + await SetCurrent(_navModel.CurrentPage, animated, true); + } + + internal async Task PushCore(Page root, Page ancester, bool animated, bool realize = true) + { + _navModel.Push(root, ancester); + if (realize) + await SetCurrent(_navModel.CurrentPage, animated); + + if (root.NavigationProxy.Inner == null) + root.NavigationProxy.Inner = this; + } + + internal async void RemovePage(Page page, bool popCurrent) + { + if (popCurrent && _navModel.CurrentPage == page) + await ((INavigation)this).PopAsync(); + else + _navModel.RemovePage(page); + } + + internal Task SetCurrent(Page page, bool animated, bool popping = false, Action completedCallback = null) + { + var tcs = new TaskCompletionSource<bool>(); + if (page == _currentDisplayedPage) + { + tcs.SetResult(true); + return tcs.Task; + } + + if (!animated) + tcs.SetResult(true); + + page.Platform = this; + + if (GetRenderer(page) == null) + SetRenderer(page, CreateRenderer(page)); + + page.Layout(new Rectangle(0, 0, _renderer.ActualWidth, _renderer.ActualHeight)); + IVisualElementRenderer pageRenderer = GetRenderer(page); + if (pageRenderer != null) + { + ((FrameworkElement)pageRenderer.ContainerElement).Width = _renderer.ActualWidth; + ((FrameworkElement)pageRenderer.ContainerElement).Height = _renderer.ActualHeight; + } + + Page current = _currentDisplayedPage; + UIElement currentElement = null; + if (current != null) + currentElement = (UIElement)GetRenderer(current); + + if (popping) + { + ITransition transitionOut = null; + if (current != null) + { + if (animated) + transitionOut = _backwardOutTransition.GetTransition(currentElement); + else + _renderer.Children.Remove(currentElement); + } + + var pageElement = (UIElement)GetRenderer(page); + + if (animated) + { + transitionOut.Completed += (s, e) => + { + transitionOut.Stop(); + _renderer.Children.Remove(currentElement); + UpdateToolbarTracker(); + + _renderer.Children.Add(pageElement); + + ITransition transitionIn = _backwardInTransition.GetTransition(pageElement); + transitionIn.Completed += (si, ei) => + { + transitionIn.Stop(); + if (completedCallback != null) + completedCallback(); + + tcs.SetResult(true); + }; + transitionIn.Begin(); + }; + + transitionOut.Begin(); + } + else + { + UpdateToolbarTracker(); + _renderer.Children.Add(pageElement); + if (completedCallback != null) + completedCallback(); + } + } + else + { + ITransition transitionOut = null; + if (current != null) + { + if (animated) + transitionOut = _forwardOutTransition.GetTransition(currentElement); + else + _renderer.Children.Remove(currentElement); + } + + if (animated) + { + if (transitionOut != null) + { + transitionOut.Completed += (o, e) => + { + _renderer.Children.Remove(currentElement); + transitionOut.Stop(); + + UpdateToolbarTracker(); + + var element = (UIElement)GetRenderer(page); + _renderer.Children.Add(element); + ITransition transitionIn = _forwardInTransition.GetTransition(element); + transitionIn.Completed += (s, ie) => + { + transitionIn.Stop(); + if (completedCallback != null) + completedCallback(); + tcs.SetResult(true); + }; + transitionIn.Begin(); + }; + + transitionOut.Begin(); + } + else + { + UpdateToolbarTracker(); + + _renderer.Children.Add((UIElement)GetRenderer(page)); + ITransition transitionIn = _forwardInTransition.GetTransition((UIElement)GetRenderer(page)); + transitionIn.Completed += (s, e) => + { + transitionIn.Stop(); + if (completedCallback != null) + completedCallback(); + + tcs.SetResult(true); + }; + transitionIn.Begin(); + } + } + else + { + _renderer.Children.Add((UIElement)GetRenderer(page)); + UpdateToolbarTracker(); + if (completedCallback != null) + completedCallback(); + } + } + + _currentDisplayedPage = page; + + return tcs.Task; + } + + internal void SetPage(Page newRoot) + { + if (newRoot == null) + return; + + Page = newRoot; + _navModel.Clear(); + _navModel.PushModal(newRoot); + SetCurrent(newRoot, false, true); + + ((Application)newRoot.RealParent).NavigationProxy.Inner = this; + } + + internal event EventHandler SizeChanged; + + void OnBackKeyPress(object sender, CancelEventArgs e) + { + if (_visibleMessageBox != null) + { + _visibleMessageBox.Dismiss(); + e.Cancel = true; + return; + } + + Page lastRoot = _navModel.Roots.Last(); + + bool handled = lastRoot.SendBackButtonPressed(); + + e.Cancel = handled; + } + + Task Push(Page root, Page ancester, bool animated) + { + return PushCore(root, ancester, animated); + } + + void RendererSizeChanged(object sender, SizeChangedEventArgs e) + { + UpdateFormSizes(); + EventHandler handler = SizeChanged; + if (handler != null) + handler(this, EventArgs.Empty); + } + + void UpdateFormSizes() + { + foreach (Page f in _navModel.Roots) + { + f.Layout(new Rectangle(0, 0, _renderer.ActualWidth, _renderer.ActualHeight)); + IVisualElementRenderer pageRenderer = f.GetRenderer(); + if (pageRenderer != null) + { + ((FrameworkElement)pageRenderer.ContainerElement).Width = _renderer.ActualWidth; + ((FrameworkElement)pageRenderer.ContainerElement).Height = _renderer.ActualHeight; + } + } + } + + void UpdateToolbarItems() + { + if (_page.ApplicationBar == null) + _page.ApplicationBar = new ApplicationBar(); + + ToolbarItem[] items = _tracker.ToolbarItems.ToArray(); + MasterDetailPage masterDetail = _tracker.Target.Descendants().Prepend(_tracker.Target).OfType<MasterDetailPage>().FirstOrDefault(); + + TaggedAppBarButton oldMasterDetailButton = _page.ApplicationBar.Buttons.OfType<TaggedAppBarButton>().FirstOrDefault(b => b.Tag is MasterDetailPage && b.Tag != masterDetail); + + if (oldMasterDetailButton != null) + _page.ApplicationBar.Buttons.Remove(oldMasterDetailButton); + + if (masterDetail != null) + { + if (masterDetail.ShouldShowToolbarButton()) + { + if (_page.ApplicationBar.Buttons.OfType<TaggedAppBarButton>().All(b => b.Tag != masterDetail)) + { + var button = new TaggedAppBarButton + { + IconUri = new Uri(masterDetail.Master.Icon ?? "ApplicationIcon.jpg", UriKind.Relative), + Text = masterDetail.Master.Title, + IsEnabled = true, + Tag = masterDetail + }; + button.Click += (sender, args) => + { + var masterDetailRenderer = GetRenderer(masterDetail) as MasterDetailRenderer; + + if (masterDetailRenderer != null) + masterDetailRenderer.Toggle(); + }; + _page.ApplicationBar.Buttons.Add(button); + } + } + } + + var buttonsToAdd = new List<TaggedAppBarButton>(); + foreach (ToolbarItem item in items.Where(i => i.Order != ToolbarItemOrder.Secondary)) + { + if (_page.ApplicationBar.Buttons.OfType<TaggedAppBarButton>().Any(b => b.Tag == item)) + continue; + + var button = new TaggedAppBarButton + { + IconUri = new Uri(item.Icon ?? "ApplicationIcon.jpg", UriKind.Relative), + Text = !string.IsNullOrWhiteSpace(item.Name) ? item.Text : (string)item.Icon ?? "ApplicationIcon.jpg", + IsEnabled = item.IsEnabled, + Tag = item + }; + button.Click += (sender, args) => item.Activate(); + buttonsToAdd.Add(button); + } + + var menuItemsToAdd = new List<TaggedAppBarMenuItem>(); + foreach (ToolbarItem item in items.Where(i => i.Order == ToolbarItemOrder.Secondary)) + { + if (_page.ApplicationBar.MenuItems.OfType<TaggedAppBarMenuItem>().Any(b => b.Tag == item)) + continue; + + var button = new TaggedAppBarMenuItem { Text = !string.IsNullOrWhiteSpace(item.Name) ? item.Text : (string)item.Icon ?? "MenuItem", IsEnabled = true, Tag = item }; + button.Click += (sender, args) => item.Activate(); + menuItemsToAdd.Add(button); + } + + TaggedAppBarButton[] deadButtons = _page.ApplicationBar.Buttons.OfType<TaggedAppBarButton>().Where(b => b.Tag is ToolbarItem && !items.Contains(b.Tag)).ToArray(); + + TaggedAppBarMenuItem[] deadMenuItems = _page.ApplicationBar.MenuItems.OfType<TaggedAppBarMenuItem>().Where(b => b.Tag is ToolbarItem && !items.Contains(b.Tag)).ToArray(); + + // we must remove the dead buttons before adding the new ones so we dont accidentally go over the limit during the tranistion + foreach (TaggedAppBarButton deadButton in deadButtons) + { + deadButton.Dispose(); + _page.ApplicationBar.Buttons.Remove(deadButton); + } + + foreach (TaggedAppBarMenuItem deadMenuItem in deadMenuItems) + _page.ApplicationBar.MenuItems.Remove(deadMenuItem); + + // fixme, insert in order + foreach (TaggedAppBarButton newButton in buttonsToAdd) + _page.ApplicationBar.Buttons.Add(newButton); + + foreach (TaggedAppBarMenuItem newMenuItem in menuItemsToAdd) + _page.ApplicationBar.MenuItems.Add(newMenuItem); + + _page.ApplicationBar.IsVisible = _page.ApplicationBar.Buttons.Count > 0 || _page.ApplicationBar.MenuItems.Count > 0; + } + + void UpdateToolbarTracker() + { + if (_navModel.Roots.Last() != null) + _tracker.Target = _navModel.Roots.Last(); + } + + class TaggedAppBarButton : ApplicationBarIconButton, IDisposable + { + bool _disposed; + object _tag; + + public object Tag + { + get { return _tag; } + set + { + if (_tag == null && value is ToolbarItem) + (value as ToolbarItem).PropertyChanged += TaggedAppBarButton_PropertyChanged; + _tag = value; + } + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + + if (Tag != null && Tag is ToolbarItem) + (Tag as ToolbarItem).PropertyChanged -= TaggedAppBarButton_PropertyChanged; + } + + void TaggedAppBarButton_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + var item = Tag as ToolbarItem; + if (item == null) + return; + + if (e.PropertyName == MenuItem.IsEnabledProperty.PropertyName) + IsEnabled = item.IsEnabled; + else if (e.PropertyName == MenuItem.TextProperty.PropertyName) + Text = !string.IsNullOrWhiteSpace(item.Name) ? item.Text : (string)item.Icon ?? "ApplicationIcon.jpg"; + else if (e.PropertyName == MenuItem.IconProperty.PropertyName) + IconUri = new Uri(item.Icon ?? "ApplicationIcon.jpg", UriKind.Relative); + } + } + + class TaggedAppBarMenuItem : ApplicationBarMenuItem + { + public object Tag { get; set; } + } + } +}
\ No newline at end of file |