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 | |
download | xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.gz xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.bz2 xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.zip |
Initial import
Diffstat (limited to 'Xamarin.Forms.Platform.WP8')
86 files changed, 8834 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.WP8/ActivityIndicatorRenderer.cs b/Xamarin.Forms.Platform.WP8/ActivityIndicatorRenderer.cs new file mode 100644 index 00000000..0e0ceb61 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ActivityIndicatorRenderer.cs @@ -0,0 +1,36 @@ +using System.ComponentModel; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class ActivityIndicatorRenderer : ViewRenderer<ActivityIndicator, System.Windows.Controls.ProgressBar> + { + protected override void OnElementChanged(ElementChangedEventArgs<ActivityIndicator> e) + { + base.OnElementChanged(e); + + SetNativeControl(new System.Windows.Controls.ProgressBar()); + + Control.IsIndeterminate = Element.IsRunning; + UpdateColor(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == ActivityIndicator.IsRunningProperty.PropertyName) + Control.IsIndeterminate = Element.IsRunning; + else if (e.PropertyName == ActivityIndicator.ColorProperty.PropertyName) + UpdateColor(); + } + + void UpdateColor() + { + Color color = Element.Color; + if (color == Color.Default) + Control.ClearValue(System.Windows.Controls.Control.ForegroundProperty); + else + Control.Foreground = color.ToBrush(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/AlignmentExtensions.cs b/Xamarin.Forms.Platform.WP8/AlignmentExtensions.cs new file mode 100644 index 00000000..7629d3eb --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/AlignmentExtensions.cs @@ -0,0 +1,33 @@ +using System.Windows; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal static class AlignmentExtensions + { + internal static System.Windows.TextAlignment ToNativeTextAlignment(this TextAlignment alignment) + { + switch (alignment) + { + case TextAlignment.Center: + return System.Windows.TextAlignment.Center; + case TextAlignment.End: + return System.Windows.TextAlignment.Right; + default: + return System.Windows.TextAlignment.Left; + } + } + + internal static VerticalAlignment ToNativeVerticalAlignment(this TextAlignment alignment) + { + switch (alignment) + { + case TextAlignment.Center: + return VerticalAlignment.Center; + case TextAlignment.End: + return VerticalAlignment.Bottom; + default: + return VerticalAlignment.Top; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Animatable.cs b/Xamarin.Forms.Platform.WP8/Animatable.cs new file mode 100644 index 00000000..c8a36dff --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Animatable.cs @@ -0,0 +1,13 @@ +namespace Xamarin.Forms.Platform.WinPhone +{ + internal class Animatable : IAnimatable + { + public void BatchBegin() + { + } + + public void BatchCommit() + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/AsyncValue.cs b/Xamarin.Forms.Platform.WP8/AsyncValue.cs new file mode 100644 index 00000000..0b41177d --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/AsyncValue.cs @@ -0,0 +1,89 @@ +// +// AsyncValue.cs +// +// Author: +// Eric Maupin <me@ermau.com> +// +// Copyright (c) 2013-2014 Xamarin, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal sealed class AsyncValue<T> : INotifyPropertyChanged + { + readonly T _defaultValue; + readonly Task<T> _valueTask; + bool _isRunning = true; + + public AsyncValue(Task<T> valueTask, T defaultValue) + { + if (valueTask == null) + throw new ArgumentNullException("valueTask"); + + _valueTask = valueTask; + _defaultValue = defaultValue; + + TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext(); + + _valueTask.ContinueWith(t => { IsRunning = false; }, scheduler); + + _valueTask.ContinueWith(t => { OnPropertyChanged("Value"); }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, scheduler); + } + + public bool IsRunning + { + get { return _isRunning; } + set + { + if (_isRunning == value) + return; + + _isRunning = value; + OnPropertyChanged(); + } + } + + public T Value + { + get + { + if (_valueTask.Status != TaskStatus.RanToCompletion) + return _defaultValue; + + return _valueTask.Result; + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChangedEventHandler handler = PropertyChanged; + if (handler != null) + handler(this, new PropertyChangedEventArgs(propertyName)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/BoxViewRenderer.cs b/Xamarin.Forms.Platform.WP8/BoxViewRenderer.cs new file mode 100644 index 00000000..84650996 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/BoxViewRenderer.cs @@ -0,0 +1,17 @@ +using System.Windows.Shapes; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class BoxViewRenderer : ViewRenderer<BoxView, System.Windows.Shapes.Rectangle> + { + protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e) + { + base.OnElementChanged(e); + + var rect = new System.Windows.Shapes.Rectangle(); + rect.DataContext = Element; + rect.SetBinding(Shape.FillProperty, new System.Windows.Data.Binding("Color") { Converter = new ColorConverter() }); + SetNativeControl(rect); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ButtonRenderer.cs b/Xamarin.Forms.Platform.WP8/ButtonRenderer.cs new file mode 100644 index 00000000..886a7768 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ButtonRenderer.cs @@ -0,0 +1,119 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using WButton = System.Windows.Controls.Button; +using WImage = System.Windows.Controls.Image; +using WThickness = System.Windows.Thickness; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class ButtonRenderer : ViewRenderer<Button, WButton> + { + bool _fontApplied; + + protected override void OnElementChanged(ElementChangedEventArgs<Button> e) + { + base.OnElementChanged(e); + + var button = new WButton(); + button.Click += HandleButtonClick; + SetNativeControl(button); + + UpdateContent(); + + if (Element.BackgroundColor != Color.Default) + UpdateBackground(); + + if (Element.TextColor != Color.Default) + UpdateTextColor(); + + if (Element.BorderColor != Color.Default) + UpdateBorderColor(); + + if (Element.BorderWidth != 0) + UpdateBorderWidth(); + + UpdateFont(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Button.TextProperty.PropertyName || e.PropertyName == Button.ImageProperty.PropertyName) + UpdateContent(); + else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackground(); + else if (e.PropertyName == Button.TextColorProperty.PropertyName) + UpdateTextColor(); + else if (e.PropertyName == Button.FontProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Button.BorderColorProperty.PropertyName) + UpdateBorderColor(); + else if (e.PropertyName == Button.BorderWidthProperty.PropertyName) + UpdateBorderWidth(); + } + + void HandleButtonClick(object sender, RoutedEventArgs e) + { + Button buttonView = Element; + if (buttonView != null) + ((IButtonController)buttonView).SendClicked(); + } + + void UpdateBackground() + { + Control.Background = Element.BackgroundColor != Color.Default ? Element.BackgroundColor.ToBrush() : (Brush)System.Windows.Application.Current.Resources["PhoneBackgroundBrush"]; + } + + void UpdateBorderColor() + { + Control.BorderBrush = Element.BorderColor != Color.Default ? Element.BorderColor.ToBrush() : (Brush)System.Windows.Application.Current.Resources["PhoneForegroundBrush"]; + } + + void UpdateBorderWidth() + { + Control.BorderThickness = Element.BorderWidth == 0d ? new WThickness(3) : new WThickness(Element.BorderWidth); + } + + void UpdateContent() + { + if (Element.Image != null) + { + Control.Content = new StackPanel + { + Orientation = Orientation.Horizontal, + Children = + { + new WImage { Source = new BitmapImage(new Uri("/" + Element.Image.File, UriKind.Relative)), Width = 30, Height = 30, Margin = new WThickness(0, 0, 20, 0) }, + new TextBlock { Text = Element.Text } + } + }; + } + else + Control.Content = Element.Text; + } + + void UpdateFont() + { + if (Control == null || Element == null) + return; + + if (Element.Font == Font.Default && !_fontApplied) + return; + + Font fontToApply = Element.Font == Font.Default ? Font.SystemFontOfSize(NamedSize.Medium) : Element.Font; + + Control.ApplyFont(fontToApply); + _fontApplied = true; + } + + void UpdateTextColor() + { + Control.Foreground = Element.TextColor != Color.Default ? Element.TextColor.ToBrush() : (Brush)System.Windows.Application.Current.Resources["PhoneForegroundBrush"]; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/CarouselPageRenderer.cs b/Xamarin.Forms.Platform.WP8/CarouselPageRenderer.cs new file mode 100644 index 00000000..29446c43 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/CarouselPageRenderer.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal class BackgroundTracker<T> : VisualElementTracker<Page, T> where T : FrameworkElement + { + readonly DependencyProperty _backgroundProperty; + + bool _backgroundNeedsUpdate = true; + + public BackgroundTracker(DependencyProperty backgroundProperty) + { + if (backgroundProperty == null) + throw new ArgumentNullException("backgroundProperty"); + + _backgroundProperty = backgroundProperty; + } + + protected override void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName || e.PropertyName == Page.BackgroundImageProperty.PropertyName) + UpdateBackground(); + + base.HandlePropertyChanged(sender, e); + } + + protected override void UpdateNativeControl() + { + base.UpdateNativeControl(); + + if (_backgroundNeedsUpdate) + UpdateBackground(); + } + + void UpdateBackground() + { + if (Model == null || Element == null) + return; + + if (Model.BackgroundImage != null) + { + Element.SetValue(_backgroundProperty, new ImageBrush { ImageSource = new BitmapImage(new Uri(Model.BackgroundImage, UriKind.Relative)) }); + } + else if (Model.BackgroundColor != Color.Default) + Element.SetValue(_backgroundProperty, Model.BackgroundColor.ToBrush()); + + _backgroundNeedsUpdate = false; + } + } + + public class CarouselPagePresenter : System.Windows.Controls.ContentPresenter + { + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + DependencyObject parent = VisualTreeHelper.GetParent(this); + while (parent != null && !(parent is PanoramaItem)) + parent = VisualTreeHelper.GetParent(parent); + + var panoramaItem = parent as PanoramaItem; + if (panoramaItem == null) + throw new Exception("No parent PanoramaItem found for carousel page"); + + var element = (FrameworkElement)VisualTreeHelper.GetChild(panoramaItem, 0); + element.SizeChanged += (s, e) => + { + if (element.ActualWidth > 0 && element.ActualHeight > 0) + { + var carouselItem = (Page)DataContext; + ((CarouselPage)carouselItem.RealParent).ContainerArea = new Rectangle(0, 0, element.ActualWidth, element.ActualHeight); + } + }; + } + } + + public class CarouselPageRenderer : Panorama, IVisualElementRenderer + { + static readonly System.Windows.DataTemplate PageTemplate; + + static readonly BindableProperty PageContainerProperty = BindableProperty.CreateAttached("PageContainer", typeof(PanoramaItem), typeof(CarouselPageRenderer), null); + + CarouselPage _page; + BackgroundTracker<Control> _tracker; + + static CarouselPageRenderer() + { + PageTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["CarouselPage"]; + } + + public CarouselPageRenderer() + { + SetBinding(TitleProperty, new System.Windows.Data.Binding("Title")); + } + + public UIElement ContainerElement + { + get { return this; } + } + + public VisualElement Element + { + get { return _page; } + } + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + + public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + return new SizeRequest(new Size(widthConstraint, heightConstraint)); + } + + public void SetElement(VisualElement element) + { + CarouselPage oldElement = _page; + _page = (CarouselPage)element; + _tracker = new BackgroundTracker<Control>(BackgroundProperty) { Model = _page, Element = this }; + + DataContext = _page; + + SelectionChanged += OnSelectionChanged; + + OnPagesChanged(_page, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + + _page.PagesChanged += OnPagesChanged; + _page.PropertyChanged += OnPropertyChanged; + + Loaded += (sender, args) => _page.SendAppearing(); + Unloaded += (sender, args) => _page.SendDisappearing(); + + OnElementChanged(new VisualElementChangedEventArgs(oldElement, element)); + } + + protected virtual void OnElementChanged(VisualElementChangedEventArgs e) + { + EventHandler<VisualElementChangedEventArgs> changed = ElementChanged; + if (changed != null) + changed(this, e); + } + + static PanoramaItem GetPageContainer(BindableObject bindable) + { + return (PanoramaItem)bindable.GetValue(PageContainerProperty); + } + + void InsertItem(object item, int index, bool newItem) + { + DependencyObject pageContent = PageTemplate.LoadContent(); + + var pageItem = (Page)item; + PanoramaItem container = GetPageContainer(pageItem); + if (container == null) + { + container = new PanoramaItem { DataContext = item, Content = pageContent }; + + SetPageContainer(pageItem, container); + } + + Items.Insert(index, container); + } + + void OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + e.Apply(InsertItem, RemoveItem, Reset); + } + + void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "CurrentPage") + { + ContentPage current = _page.CurrentPage; + if (current == null) + return; + + SetValue(SelectedItemProperty, GetPageContainer(current)); + OnItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + + void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + var panoramaItem = (PanoramaItem)SelectedItem; + if (panoramaItem == null) + _page.CurrentPage = null; + else + _page.CurrentPage = (ContentPage)panoramaItem.DataContext; + } + + void RemoveItem(object item, int index) + { + Items.RemoveAt(index); + } + + void Reset() + { + Items.Clear(); + + var i = 0; + foreach (Page pageItem in _page.Children) + InsertItem(pageItem, i++, true); + + ContentPage current = _page.CurrentPage; + if (current != null) + SetValue(SelectedItemProperty, GetPageContainer(current)); + } + + static void SetPageContainer(BindableObject bindable, PanoramaItem container) + { + bindable.SetValue(PageContainerProperty, container); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/CarouselViewRenderer.cs b/Xamarin.Forms.Platform.WP8/CarouselViewRenderer.cs new file mode 100644 index 00000000..6050d07d --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/CarouselViewRenderer.cs @@ -0,0 +1,13 @@ +using Microsoft.Phone.Controls; +using SLButton = System.Windows.Controls.Button; +using SLBinding = System.Windows.Data.Binding; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class CarouselViewRenderer : ViewRenderer<CarouselView, LongListSelector> + { + protected override void OnElementChanged(ElementChangedEventArgs<CarouselView> e) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/CellControl.cs b/Xamarin.Forms.Platform.WP8/CellControl.cs new file mode 100644 index 00000000..743f4ba7 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/CellControl.cs @@ -0,0 +1,101 @@ +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class CellControl : ContentControl + { + public static readonly DependencyProperty CellProperty = DependencyProperty.Register("Cell", typeof(object), typeof(CellControl), + new PropertyMetadata((o, e) => ((CellControl)o).SetSource((Cell)e.OldValue, (Cell)e.NewValue))); + + public static readonly DependencyProperty ShowContextActionsProperty = DependencyProperty.Register("ShowContextActions", typeof(bool), typeof(CellControl), new PropertyMetadata(true)); + + readonly PropertyChangedEventHandler _propertyChangedHandler; + + public CellControl() + { + Unloaded += (sender, args) => + { + var cell = DataContext as Cell; + if (cell != null) + cell.SendDisappearing(); + }; + + _propertyChangedHandler = OnCellPropertyChanged; + } + + public Cell Cell + { + get { return (Cell)GetValue(CellProperty); } + set { SetValue(CellProperty, value); } + } + + public bool ShowContextActions + { + get { return (bool)GetValue(ShowContextActionsProperty); } + set { SetValue(ShowContextActionsProperty, value); } + } + + System.Windows.DataTemplate GetTemplate(Cell cell) + { + var renderer = Registrar.Registered.GetHandler<ICellRenderer>(cell.GetType()); + return renderer.GetTemplate(cell); + } + + void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "HasContextActions") + SetupContextMenu(); + } + + void SetSource(Cell oldCell, Cell newCell) + { + if (oldCell != null) + { + oldCell.PropertyChanged -= _propertyChangedHandler; + oldCell.SendDisappearing(); + } + + DependencyObject content = null; + if (newCell != null) + { + newCell.SendAppearing(); + + if (oldCell == null || oldCell.GetType() != newCell.GetType()) + ContentTemplate = GetTemplate(newCell); + + Content = newCell; + + SetupContextMenu(); + + newCell.PropertyChanged += _propertyChangedHandler; + } + else + Content = null; + } + + void SetupContextMenu() + { + if (Content == null || !ShowContextActions) + return; + + if (!Cell.HasContextActions) + { + if (VisualTreeHelper.GetChildrenCount(this) > 0) + ContextMenuService.SetContextMenu(VisualTreeHelper.GetChild(this, 0), null); + + return; + } + + ApplyTemplate(); + + ContextMenu menu = new CustomContextMenu(); + menu.SetBinding(ItemsControl.ItemsSourceProperty, new System.Windows.Data.Binding("ContextActions")); + + ContextMenuService.SetContextMenu(VisualTreeHelper.GetChild(this, 0), menu); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/CellTemplateSelector.cs b/Xamarin.Forms.Platform.WP8/CellTemplateSelector.cs new file mode 100644 index 00000000..09d939fd --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/CellTemplateSelector.cs @@ -0,0 +1,60 @@ +using System; +using System.Globalization; +using System.Windows; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class HeightConverter : System.Windows.Data.IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var val = (double)value; + return val > 0 ? val : double.NaN; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + [Obsolete("Deprecated in favor of CellControl")] + public class CellTemplateSelector : DataTemplateSelector + { + public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(object), typeof(CellTemplateSelector), + new PropertyMetadata((o, e) => ((CellTemplateSelector)o).SetSource(e.OldValue, e.NewValue))); + + public CellTemplateSelector() + { + Loaded += (sender, args) => SetBinding(SourceProperty, new System.Windows.Data.Binding()); + Unloaded += (sender, args) => + { + var cell = DataContext as Cell; + if (cell != null) + cell.SendDisappearing(); + }; + } + + public override System.Windows.DataTemplate SelectTemplate(object item, DependencyObject container) + { + var cell = item as Cell; + if (cell == null) + return null; + + var renderer = Registrar.Registered.GetHandler<ICellRenderer>(cell.GetType()); + return renderer.GetTemplate(cell); + } + + void SetSource(object oldSource, object newSource) + { + var oldCell = oldSource as Cell; + var newCell = newSource as Cell; + + if (oldCell != null) + oldCell.SendDisappearing(); + + if (newCell != null) + newCell.SendAppearing(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/CollapseWhenEmptyConverter.cs b/Xamarin.Forms.Platform.WP8/CollapseWhenEmptyConverter.cs new file mode 100644 index 00000000..d7dc6de6 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/CollapseWhenEmptyConverter.cs @@ -0,0 +1,28 @@ +using System; +using System.Globalization; +using System.Windows; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class CollapseWhenEmptyConverter : System.Windows.Data.IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var length = 0; + + var s = value as string; + if (s != null) + length = s.Length; + + if (value is int) + length = (int)value; + + return length > 0 ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ConvertExtensions.cs b/Xamarin.Forms.Platform.WP8/ConvertExtensions.cs new file mode 100644 index 00000000..ed96ecdf --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ConvertExtensions.cs @@ -0,0 +1,17 @@ +using System.Windows.Media; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal static class ConvertExtensions + { + public static Brush ToBrush(this Color color) + { + return new SolidColorBrush(color.ToMediaColor()); + } + + public static System.Windows.Media.Color ToMediaColor(this Color color) + { + return System.Windows.Media.Color.FromArgb((byte)(color.A * 255), (byte)(color.R * 255), (byte)(color.G * 255), (byte)(color.B * 255)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Converters/CaseConverter.cs b/Xamarin.Forms.Platform.WP8/Converters/CaseConverter.cs new file mode 100644 index 00000000..b972fc57 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Converters/CaseConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class CaseConverter : System.Windows.Data.IValueConverter + { + public bool ConvertToUpper { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return null; + + var v = (string)value; + return ConvertToUpper ? v.ToUpper() : v.ToLower(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Converters/ColorConverter.cs b/Xamarin.Forms.Platform.WP8/Converters/ColorConverter.cs new file mode 100644 index 00000000..1e495ecf --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Converters/ColorConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; +using System.Windows.Media; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class ColorConverter : System.Windows.Data.IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var color = (Color)value; + var defaultColorKey = (string)parameter; + + Brush defaultBrush = defaultColorKey != null ? (Brush)System.Windows.Application.Current.Resources[defaultColorKey] : new SolidColorBrush(Colors.Transparent); + + return color == Color.Default ? defaultBrush : color.ToBrush(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Converters/HorizontalTextAlignmentConverter.cs b/Xamarin.Forms.Platform.WP8/Converters/HorizontalTextAlignmentConverter.cs new file mode 100644 index 00000000..7923b1d3 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Converters/HorizontalTextAlignmentConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class HorizontalTextAlignmentConverter : System.Windows.Data.IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var textAlign = (TextAlignment)value; + + return textAlign.ToNativeTextAlignment(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Converters/ImageConverter.cs b/Xamarin.Forms.Platform.WP8/Converters/ImageConverter.cs new file mode 100644 index 00000000..2e8bdef6 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Converters/ImageConverter.cs @@ -0,0 +1,28 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class ImageConverter : System.Windows.Data.IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var source = (ImageSource)value; + IImageSourceHandler handler; + + if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null) + { + Task<System.Windows.Media.ImageSource> task = handler.LoadImageAsync(source); + return new AsyncValue<System.Windows.Media.ImageSource>(task, null); + } + + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Converters/KeyboardConverter.cs b/Xamarin.Forms.Platform.WP8/Converters/KeyboardConverter.cs new file mode 100644 index 00000000..cd082b29 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Converters/KeyboardConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class KeyboardConverter : System.Windows.Data.IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var keyboard = (Keyboard)value; + return keyboard.ToInputScope(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/CustomContextMenu.cs b/Xamarin.Forms.Platform.WP8/CustomContextMenu.cs new file mode 100644 index 00000000..4946b2b2 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/CustomContextMenu.cs @@ -0,0 +1,27 @@ +using System.Windows; +using System.Windows.Controls; +using Microsoft.Phone.Controls; +using WMenuItem = Microsoft.Phone.Controls.MenuItem; +using WApplication = System.Windows.Application; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public sealed class CustomContextMenu : ContextMenu + { + protected override DependencyObject GetContainerForItemOverride() + { + var item = new WMenuItem(); + item.SetBinding(HeaderedItemsControl.HeaderProperty, new System.Windows.Data.Binding("Text") { Converter = (System.Windows.Data.IValueConverter)WApplication.Current.Resources["LowerConverter"] }); + + item.Click += (sender, args) => + { + IsOpen = false; + + var menuItem = item.DataContext as MenuItem; + if (menuItem != null) + menuItem.Activate(); + }; + return item; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/DataTemplateSelector.cs b/Xamarin.Forms.Platform.WP8/DataTemplateSelector.cs new file mode 100644 index 00000000..6a9acca6 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/DataTemplateSelector.cs @@ -0,0 +1,17 @@ +using System.Windows; +using System.Windows.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public abstract class DataTemplateSelector : ContentControl + { + public abstract System.Windows.DataTemplate SelectTemplate(object item, DependencyObject container); + + protected override void OnContentChanged(object oldContent, object newContent) + { + base.OnContentChanged(oldContent, newContent); + + ContentTemplate = SelectTemplate(newContent, this); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/DatePickerRenderer.cs b/Xamarin.Forms.Platform.WP8/DatePickerRenderer.cs new file mode 100644 index 00000000..9553171b --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/DatePickerRenderer.cs @@ -0,0 +1,59 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class DatePickerRenderer : ViewRenderer<DatePicker, Microsoft.Phone.Controls.DatePicker> + { + protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e) + { + base.OnElementChanged(e); + + var datePicker = new Microsoft.Phone.Controls.DatePicker { Value = Element.Date }; + datePicker.ValueChanged += DatePickerOnValueChanged; + SetNativeControl(datePicker); + + UpdateFormatString(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "Date") + Control.Value = Element.Date; + else if (e.PropertyName == DatePicker.FormatProperty.PropertyName) + UpdateFormatString(); + base.OnElementPropertyChanged(sender, e); + } + + internal override void OnModelFocusChangeRequested(object sender, VisualElement.FocusRequestArgs args) + { + Microsoft.Phone.Controls.DatePicker control = Control; + if (control == null) + return; + + if (args.Focus) + { + typeof(DateTimePickerBase).InvokeMember("OpenPickerPage", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, control, null); + args.Result = true; + } + else + { + UnfocusControl(control); + args.Result = true; + } + } + + void DatePickerOnValueChanged(object sender, DateTimeValueChangedEventArgs dateTimeValueChangedEventArgs) + { + if (Control.Value.HasValue) + ((IElementController)Element).SetValueFromRenderer(DatePicker.DateProperty, Control.Value.Value); + } + + void UpdateFormatString() + { + Control.ValueStringFormat = "{0:" + Element.Format + "}"; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Deserializer.cs b/Xamarin.Forms.Platform.WP8/Deserializer.cs new file mode 100644 index 00000000..a0c2de00 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Deserializer.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.IsolatedStorage; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using System.Xml; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal class Deserializer : IDeserializer + { + const string PropertyStoreFile = "PropertyStore.forms"; + + public Task<IDictionary<string, object>> DeserializePropertiesAsync() + { + // Deserialize property dictionary to local storage + // Make sure to use Internal + return Task.Run(() => + { + using(IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) + using(IsolatedStorageFileStream stream = store.OpenFile(PropertyStoreFile, System.IO.FileMode.OpenOrCreate)) + using(XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max)) + { + if (stream.Length == 0) + return null; + + try + { + var dcs = new DataContractSerializer(typeof(Dictionary<string, object>)); + return (IDictionary<string, object>)dcs.ReadObject(reader); + } + catch (Exception e) + { + Debug.WriteLine("Could not deserialize properties: " + e.Message); + } + } + + return null; + }); + } + + public Task SerializePropertiesAsync(IDictionary<string, object> properties) + { + properties = new Dictionary<string, object>(properties); + // Serialize property dictionary to local storage + // Make sure to use Internal + return Task.Run(() => + { + var success = false; + using(IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) + using(IsolatedStorageFileStream stream = store.OpenFile(PropertyStoreFile + ".tmp", System.IO.FileMode.OpenOrCreate)) + using(XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream)) + { + try + { + var dcs = new DataContractSerializer(typeof(Dictionary<string, object>)); + dcs.WriteObject(writer, properties); + writer.Flush(); + success = true; + } + catch (Exception e) + { + Debug.WriteLine("Could not serialize properties: " + e.Message); + } + } + + if (!success) + return; + using(IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) + { + try + { + if (store.FileExists(PropertyStoreFile)) + store.DeleteFile(PropertyStoreFile); + store.MoveFile(PropertyStoreFile + ".tmp", PropertyStoreFile); + } + catch (Exception e) + { + Debug.WriteLine("Could not move new serialized property file over old: " + e.Message); + } + } + }); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/EditorRenderer.cs b/Xamarin.Forms.Platform.WP8/EditorRenderer.cs new file mode 100644 index 00000000..500bcfae --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/EditorRenderer.cs @@ -0,0 +1,108 @@ +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class EditorRenderer : ViewRenderer<Editor, TextBox> + { + bool _fontApplied; + + protected override void OnElementChanged(ElementChangedEventArgs<Editor> e) + { + base.OnElementChanged(e); + + var textBox = new TextBox { VerticalScrollBarVisibility = ScrollBarVisibility.Visible, TextWrapping = TextWrapping.Wrap, AcceptsReturn = true }; + + SetNativeControl(textBox); + + UpdateText(); + UpdateInputScope(); + UpdateTextColor(); + + Control.LostFocus += (sender, args) => Element.SendCompleted(); + + textBox.TextChanged += TextBoxOnTextChanged; + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Editor.TextProperty.PropertyName) + UpdateText(); + else if (e.PropertyName == InputView.KeyboardProperty.PropertyName) + UpdateInputScope(); + else if (e.PropertyName == Editor.TextColorProperty.PropertyName) + UpdateTextColor(); + else if (e.PropertyName == Editor.FontAttributesProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Editor.FontFamilyProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Editor.FontSizeProperty.PropertyName) + UpdateFont(); + } + + protected override void UpdateBackgroundColor() + { + Control.Background = Element.BackgroundColor == Color.Default ? (Brush)System.Windows.Application.Current.Resources["PhoneTextBoxBrush"] : Element.BackgroundColor.ToBrush(); + } + + void TextBoxOnTextChanged(object sender, System.Windows.Controls.TextChangedEventArgs textChangedEventArgs) + { + ((IElementController)Element).SetValueFromRenderer(Editor.TextProperty, Control.Text); + } + + void UpdateFont() + { + if (Control == null) + return; + + Editor editor = Element; + + bool editorIsDefault = editor.FontFamily == null && editor.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(Editor), true) && editor.FontAttributes == FontAttributes.None; + if (editor == null || (editorIsDefault && !_fontApplied)) + return; + + if (editorIsDefault) + { + Control.ClearValue(System.Windows.Controls.Control.FontStyleProperty); + Control.ClearValue(System.Windows.Controls.Control.FontSizeProperty); + Control.ClearValue(System.Windows.Controls.Control.FontFamilyProperty); + Control.ClearValue(System.Windows.Controls.Control.FontWeightProperty); + Control.ClearValue(System.Windows.Controls.Control.FontStretchProperty); + } + else + Control.ApplyFont(editor); + + _fontApplied = true; + } + + void UpdateInputScope() + { + Control.InputScope = Element.Keyboard.ToInputScope(); + } + + void UpdateText() + { + string newText = Element.Text ?? ""; + + if (Control.Text == newText) + return; + + Control.Text = newText; + Control.SelectionStart = Control.Text.Length; + } + + void UpdateTextColor() + { + Color textColor = Element.TextColor; + + if (textColor.IsDefault || !Element.IsEnabled) + Control.ClearValue(System.Windows.Controls.Control.ForegroundProperty); + else + Control.Foreground = textColor.ToBrush(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ElementChangedEventArgs.cs b/Xamarin.Forms.Platform.WP8/ElementChangedEventArgs.cs new file mode 100644 index 00000000..903acb18 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ElementChangedEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class VisualElementChangedEventArgs : ElementChangedEventArgs<VisualElement> + { + public VisualElementChangedEventArgs(VisualElement oldElement, VisualElement newElement) : base(oldElement, newElement) + { + } + } + + public class ElementChangedEventArgs<TElement> : EventArgs where TElement : Element + { + public ElementChangedEventArgs(TElement oldElement, TElement newElement) + { + OldElement = oldElement; + NewElement = newElement; + } + + public TElement NewElement { get; private set; } + + public TElement OldElement { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/EntryRenderer.cs b/Xamarin.Forms.Platform.WP8/EntryRenderer.cs new file mode 100644 index 00000000..f96ca312 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/EntryRenderer.cs @@ -0,0 +1,324 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using static System.String; +using WControl = System.Windows.Controls.Control; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal static class KeyboardExtensions + { + public static InputScope ToInputScope(this Keyboard self) + { + var result = new InputScope(); + var name = new InputScopeName(); + if (self == Keyboard.Default) + name.NameValue = InputScopeNameValue.Default; + else if (self == Keyboard.Chat) + name.NameValue = InputScopeNameValue.Chat; + else if (self == Keyboard.Email) + name.NameValue = InputScopeNameValue.EmailNameOrAddress; + else if (self == Keyboard.Numeric) + name.NameValue = InputScopeNameValue.Number; + else if (self == Keyboard.Telephone) + name.NameValue = InputScopeNameValue.TelephoneNumber; + else if (self == Keyboard.Text) + name.NameValue = InputScopeNameValue.Text; + else if (self == Keyboard.Url) + name.NameValue = InputScopeNameValue.Url; + else if (self is CustomKeyboard) + { + var custom = (CustomKeyboard)self; + bool capitalizedSentenceEnabled = (custom.Flags & KeyboardFlags.CapitalizeSentence) == KeyboardFlags.CapitalizeSentence; + bool spellcheckEnabled = (custom.Flags & KeyboardFlags.Spellcheck) == KeyboardFlags.Spellcheck; + bool suggestionsEnabled = (custom.Flags & KeyboardFlags.Suggestions) == KeyboardFlags.Suggestions; + + if (!capitalizedSentenceEnabled && !spellcheckEnabled && !suggestionsEnabled) + name.NameValue = InputScopeNameValue.Default; + if (!capitalizedSentenceEnabled && !spellcheckEnabled && suggestionsEnabled) + name.NameValue = InputScopeNameValue.Search; + if (!capitalizedSentenceEnabled && spellcheckEnabled && !suggestionsEnabled) + { + Debug.WriteLine("Keyboard: Suggestions cannot be disabled in Windows Phone if spellcheck is enabled"); + name.NameValue = InputScopeNameValue.Search; + } + if (!capitalizedSentenceEnabled && spellcheckEnabled && suggestionsEnabled) + name.NameValue = InputScopeNameValue.Search; + if (capitalizedSentenceEnabled && !spellcheckEnabled && !suggestionsEnabled) + { + Debug.WriteLine("Keyboard: Suggestions cannot be disabled in Windows Phone if auto Capitalization is enabled"); + name.NameValue = InputScopeNameValue.Chat; + } + if (capitalizedSentenceEnabled && !spellcheckEnabled && suggestionsEnabled) + name.NameValue = InputScopeNameValue.Chat; + if (capitalizedSentenceEnabled && spellcheckEnabled && !suggestionsEnabled) + { + Debug.WriteLine("Keyboard: Suggestions cannot be disabled in Windows Phone if spellcheck is enabled"); + name.NameValue = InputScopeNameValue.Text; + } + if (capitalizedSentenceEnabled && spellcheckEnabled && suggestionsEnabled) + name.NameValue = InputScopeNameValue.Text; + } + else + { + // Should never happens + name.NameValue = InputScopeNameValue.Default; + } + result.Names.Add(name); + return result; + } + } + + public class EntryRenderer : ViewRenderer<Entry, FormsPhoneTextBox> + { + bool _fontApplied; + bool _ignoreTextChange; + Brush _placeholderDefaultBrush; + + public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + if (Children.Count == 0) + return new SizeRequest(); + + var constraint = new System.Windows.Size(widthConstraint, heightConstraint); + + FormsPhoneTextBox child = Control; + + double oldWidth = child.Width; + double oldHeight = child.Height; + + child.Height = double.NaN; + child.Width = double.NaN; + + child.Measure(constraint); + var result = new Size(Math.Ceiling(child.DesiredSize.Width), Math.Ceiling(child.DesiredSize.Height)); + + child.Width = oldWidth; + child.Height = oldHeight; + + return new SizeRequest(result); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Entry> e) + { + base.OnElementChanged(e); + + var textBox = new FormsPhoneTextBox(); + + SetNativeControl(textBox); + + UpdateInputScope(); + UpdateIsPassword(); + UpdateText(); + UpdatePlaceholder(); + UpdateColor(); + UpdateFont(); + UpdateAlignment(); + UpdatePlaceholderColor(); + UpdateIsEnabled(); + + Control.LostFocus += OnTextBoxUnfocused; + Control.TextChanged += TextBoxOnTextChanged; + Control.KeyUp += TextBoxOnKeyUp; + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Entry.TextProperty.PropertyName) + UpdateText(); + else if (e.PropertyName == Entry.PlaceholderProperty.PropertyName) + UpdatePlaceholder(); + else if (e.PropertyName == Entry.IsPasswordProperty.PropertyName) + UpdateIsPassword(); + else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + UpdateIsEnabled(); + else if (e.PropertyName == Entry.TextColorProperty.PropertyName) + UpdateColor(); + else if (e.PropertyName == InputView.KeyboardProperty.PropertyName) + UpdateInputScope(); + else if (e.PropertyName == Entry.FontAttributesProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Entry.FontFamilyProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Entry.FontSizeProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Entry.HorizontalTextAlignmentProperty.PropertyName) + UpdateAlignment(); + else if (e.PropertyName == Entry.PlaceholderColorProperty.PropertyName) + UpdatePlaceholderColor(); + } + + protected override void UpdateBackgroundColor() + { + Control.Background = Element.BackgroundColor.IsDefault ? (Brush)System.Windows.Application.Current.Resources["PhoneTextBoxBrush"] : Element.BackgroundColor.ToBrush(); + } + + internal override void OnModelFocusChangeRequested(object sender, VisualElement.FocusRequestArgs args) + { + if (args.Focus) + args.Result = Control.Focus(); + else + { + UnfocusControl(Control); + args.Result = true; + } + } + + void OnTextBoxUnfocused(object sender, RoutedEventArgs e) + { + if (Element.TextColor.IsDefault) + return; + + if (!IsNullOrEmpty(Element.Text)) + Control.Foreground = Element.TextColor.ToBrush(); + } + + void TextBoxOnKeyUp(object sender, KeyEventArgs keyEventArgs) + { + if (keyEventArgs.Key == Key.Enter) + Element.SendCompleted(); + } + + void TextBoxOnTextChanged(object sender, System.Windows.Controls.TextChangedEventArgs textChangedEventArgs) + { + // Signal to the UpdateText method that the change to TextProperty doesn't need to update the control + // This prevents the cursor position from getting lost + _ignoreTextChange = true; + ((IElementController)Element).SetValueFromRenderer(Entry.TextProperty, Control.Text); + + // If an Entry.TextChanged handler modified the value of the Entry's text, the values could now be + // out-of-sync; re-sync them and force the TextBox cursor to the end of the text + string entryText = Element.Text; + if (Control.Text != entryText) + { + Control.Text = entryText; + Control.SelectionStart = Control.Text.Length; + } + + _ignoreTextChange = false; + } + + void UpdateAlignment() + { + if (Control == null) + return; + + Control.TextAlignment = Element.HorizontalTextAlignment.ToNativeTextAlignment(); + } + + void UpdateColor() + { + if (Control == null) + return; + + Entry entry = Element; + if (entry != null) + { + if (!IsNullOrEmpty(entry.Text)) + { + if (!entry.TextColor.IsDefault) + Control.Foreground = entry.TextColor.ToBrush(); + else + Control.Foreground = (Brush)WControl.ForegroundProperty.GetMetadata(typeof(FormsPhoneTextBox)).DefaultValue; + + // Force the PhoneTextBox control to do some internal bookkeeping + // so the colors change immediately and remain changed when the control gets focus + Control.OnApplyTemplate(); + } + } + else + Control.Foreground = (Brush)WControl.ForegroundProperty.GetMetadata(typeof(FormsPhoneTextBox)).DefaultValue; + } + + void UpdateFont() + { + if (Control == null) + return; + + Entry entry = Element; + + if (entry == null) + return; + + bool entryIsDefault = entry.FontFamily == null && entry.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(Entry), true) && entry.FontAttributes == FontAttributes.None; + + if (entryIsDefault && !_fontApplied) + return; + + if (entryIsDefault) + { + Control.ClearValue(WControl.FontStyleProperty); + Control.ClearValue(WControl.FontSizeProperty); + Control.ClearValue(WControl.FontFamilyProperty); + Control.ClearValue(WControl.FontWeightProperty); + Control.ClearValue(WControl.FontStretchProperty); + } + else + Control.ApplyFont(entry); + + _fontApplied = true; + } + + void UpdateInputScope() + { + Control.InputScope = Element.Keyboard.ToInputScope(); + } + + void UpdateIsEnabled() + { + Control.IsEnabled = Element.IsEnabled; + } + + void UpdateIsPassword() + { + Control.IsPassword = Element.IsPassword; + } + + void UpdatePlaceholder() + { + Control.Hint = Element.Placeholder ?? ""; + } + + void UpdatePlaceholderColor() + { + Color placeholderColor = Element.PlaceholderColor; + + if (placeholderColor.IsDefault) + { + if (_placeholderDefaultBrush == null) + return; + + // Use the cached default brush + Control.PlaceholderForegroundBrush = _placeholderDefaultBrush; + return; + } + + if (_placeholderDefaultBrush == null) + { + // Cache the default brush in case we need to set the color back to default + _placeholderDefaultBrush = Control.PlaceholderForegroundBrush; + } + + Control.PlaceholderForegroundBrush = placeholderColor.ToBrush(); + } + + void UpdateText() + { + // If the text property has changed because TextBoxOnTextChanged called SetValueFromRenderer, + // we don't want to re-update the text and reset the cursor position + if (_ignoreTextChange) + return; + + if (Control.Text == Element.Text) + return; + + Control.Text = Element.Text ?? ""; + Control.Select(Control.Text.Length, 0); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ExportCellAttribute.cs b/Xamarin.Forms.Platform.WP8/ExportCellAttribute.cs new file mode 100644 index 00000000..96aec6cb --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ExportCellAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class ExportCellAttribute : HandlerAttribute + { + public ExportCellAttribute(Type handler, Type target) : base(handler, target) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ExportImageSourceHandlerAttribute.cs b/Xamarin.Forms.Platform.WP8/ExportImageSourceHandlerAttribute.cs new file mode 100644 index 00000000..4c5c01fb --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ExportImageSourceHandlerAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class ExportImageSourceHandlerAttribute : HandlerAttribute + { + public ExportImageSourceHandlerAttribute(Type handler, Type target) : base(handler, target) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ExportRendererAttribute.cs b/Xamarin.Forms.Platform.WP8/ExportRendererAttribute.cs new file mode 100644 index 00000000..bf18653b --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ExportRendererAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class ExportRendererAttribute : HandlerAttribute + { + public ExportRendererAttribute(Type handler, Type target) : base(handler, target) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Extensions.cs b/Xamarin.Forms.Platform.WP8/Extensions.cs new file mode 100644 index 00000000..844c75ab --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Extensions.cs @@ -0,0 +1,30 @@ +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal static class Extensions + { + public static DeviceOrientation ToDeviceOrientation(this PageOrientation pageOrientation) + { + switch (pageOrientation) + { + case PageOrientation.None: + return DeviceOrientation.Other; + case PageOrientation.Portrait: + return DeviceOrientation.Portrait; + case PageOrientation.Landscape: + return DeviceOrientation.Landscape; + case PageOrientation.PortraitUp: + return DeviceOrientation.PortraitUp; + case PageOrientation.PortraitDown: + return DeviceOrientation.PortraitDown; + case PageOrientation.LandscapeRight: + return DeviceOrientation.LandscapeRight; + case PageOrientation.LandscapeLeft: + return DeviceOrientation.LandscapeLeft; + default: + return DeviceOrientation.Other; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/FontExtensions.cs b/Xamarin.Forms.Platform.WP8/FontExtensions.cs new file mode 100644 index 00000000..186c6719 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/FontExtensions.cs @@ -0,0 +1,162 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public static class FontExtensions + { + public static void ApplyFont(this Control self, Font font) + { + if (font.UseNamedSize) + { + switch (font.NamedSize) + { + case NamedSize.Micro: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeSmall"] - 3; + break; + case NamedSize.Small: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeSmall"]; + break; + case NamedSize.Medium: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeMedium"]; + // use medium instead of normal as this is the default for non-labels + break; + case NamedSize.Large: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeLarge"]; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + else + self.FontSize = font.FontSize; + + if (!string.IsNullOrEmpty(font.FontFamily)) + self.FontFamily = new FontFamily(font.FontFamily); + else + self.FontFamily = (FontFamily)System.Windows.Application.Current.Resources["PhoneFontFamilySemiBold"]; + + if (font.FontAttributes.HasFlag(FontAttributes.Italic)) + self.FontStyle = FontStyles.Italic; + else + self.FontStyle = FontStyles.Normal; + + if (font.FontAttributes.HasFlag(FontAttributes.Bold)) + self.FontWeight = FontWeights.Bold; + else + self.FontWeight = FontWeights.Normal; + } + + public static void ApplyFont(this TextBlock self, Font font) + { + if (font.UseNamedSize) + { + switch (font.NamedSize) + { + case NamedSize.Micro: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeSmall"] - 3; + break; + case NamedSize.Small: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeSmall"]; + break; + case NamedSize.Medium: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeNormal"]; + // use normal instead of medium as this is the default + break; + case NamedSize.Large: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeLarge"]; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + else + self.FontSize = font.FontSize; + + if (!string.IsNullOrEmpty(font.FontFamily)) + self.FontFamily = new FontFamily(font.FontFamily); + else + self.FontFamily = (FontFamily)System.Windows.Application.Current.Resources["PhoneFontFamilyNormal"]; + + if (font.FontAttributes.HasFlag(FontAttributes.Italic)) + self.FontStyle = FontStyles.Italic; + else + self.FontStyle = FontStyles.Normal; + + if (font.FontAttributes.HasFlag(FontAttributes.Bold)) + self.FontWeight = FontWeights.Bold; + else + self.FontWeight = FontWeights.Normal; + } + + public static void ApplyFont(this TextElement self, Font font) + { + if (font.UseNamedSize) + { + switch (font.NamedSize) + { + case NamedSize.Micro: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeSmall"] - 3; + break; + case NamedSize.Small: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeSmall"]; + break; + case NamedSize.Medium: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeNormal"]; + // use normal instead of medium as this is the default + break; + case NamedSize.Large: + self.FontSize = (double)System.Windows.Application.Current.Resources["PhoneFontSizeLarge"]; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + else + self.FontSize = font.FontSize; + + if (!string.IsNullOrEmpty(font.FontFamily)) + self.FontFamily = new FontFamily(font.FontFamily); + else + self.FontFamily = (FontFamily)System.Windows.Application.Current.Resources["PhoneFontFamilyNormal"]; + + if (font.FontAttributes.HasFlag(FontAttributes.Italic)) + self.FontStyle = FontStyles.Italic; + else + self.FontStyle = FontStyles.Normal; + + if (font.FontAttributes.HasFlag(FontAttributes.Bold)) + self.FontWeight = FontWeights.Bold; + else + self.FontWeight = FontWeights.Normal; + } + + internal static void ApplyFont(this Control self, IFontElement element) + { + self.FontSize = element.FontSize; + + if (!string.IsNullOrEmpty(element.FontFamily)) + self.FontFamily = new FontFamily(element.FontFamily); + else + self.FontFamily = (FontFamily)System.Windows.Application.Current.Resources["PhoneFontFamilySemiBold"]; + + if (element.FontAttributes.HasFlag(FontAttributes.Italic)) + self.FontStyle = FontStyles.Italic; + else + self.FontStyle = FontStyles.Normal; + + if (element.FontAttributes.HasFlag(FontAttributes.Bold)) + self.FontWeight = FontWeights.Bold; + else + self.FontWeight = FontWeights.Normal; + } + + internal static bool IsDefault(this IFontElement self) + { + return self.FontFamily == null && self.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(Label), true) && self.FontAttributes == FontAttributes.None; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Forms.cs b/Xamarin.Forms.Platform.WP8/Forms.cs new file mode 100644 index 00000000..b4435d3a --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Forms.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; +using Microsoft.Phone.Controls; +using Xamarin.Forms.Platform.WinPhone; + +using Expression = System.Linq.Expressions.Expression; + + +namespace Xamarin.Forms +{ + public static class Forms + { + static bool s_isInitialized; + + public static UIElement ConvertPageToUIElement(this Page page, PhoneApplicationPage applicationPage) + { + var application = new DefaultApplication(); + application.MainPage = page; + var result = new Platform.WinPhone.Platform(applicationPage); + result.SetPage(page); + return result.GetCanvas(); + } + + public static void Init() + { + if (s_isInitialized) + return; + + // Needed to prevent stripping of System.Windows.Interactivity + // which is current only referenced in the XAML DataTemplates + var eventTrigger = new System.Windows.Interactivity.EventTrigger(); + + string assemblyName = Assembly.GetExecutingAssembly().GetName().Name; + + System.Windows.Application.Current.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary + { + Source = new Uri(string.Format("/{0};component/WPResources.xaml", assemblyName), UriKind.Relative) + }); + + var accent = System.Windows.Application.Current.Resources["PhoneAccentBrush"] as SolidColorBrush; + System.Windows.Media.Color color = accent.Color; + Color.Accent = Color.FromRgba(color.R, color.G, color.B, color.A); + + Log.Listeners.Add(new DelegateLogListener((c, m) => Console.WriteLine("[{0}] {1}", m, c))); + + Device.OS = TargetPlatform.WinPhone; + Device.PlatformServices = new WP8PlatformServices(); + Device.Info = new WP8DeviceInfo(); + + Registrar.RegisterAll(new[] { typeof(ExportRendererAttribute), typeof(ExportCellAttribute), typeof(ExportImageSourceHandlerAttribute) }); + + Ticker.Default = new WinPhoneTicker(); + + Device.Idiom = TargetIdiom.Phone; + + ExpressionSearch.Default = new WinPhoneExpressionSearch(); + + s_isInitialized = true; + } + + class DefaultApplication : Application + { + } + + internal class WP8DeviceInfo : DeviceInfo + { + internal const string BWPorientationChangedName = "Xamarin.WP8.OrientationChanged"; + readonly double _scalingFactor; + + public WP8DeviceInfo() + { + MessagingCenter.Subscribe(this, BWPorientationChangedName, (FormsApplicationPage page, DeviceOrientation orientation) => { CurrentOrientation = orientation; }); + + Content content = System.Windows.Application.Current.Host.Content; + + // Scaling Factor for Windows Phone 8 is relative to WVGA: https://msdn.microsoft.com/en-us/library/windows/apps/jj206974(v=vs.105).aspx + _scalingFactor = content.ScaleFactor / 100d; + + PixelScreenSize = new Size(content.ActualWidth * _scalingFactor, content.ActualHeight * _scalingFactor); + ScaledScreenSize = new Size(content.ActualWidth, content.ActualHeight); + } + + public override Size PixelScreenSize { get; } + + public override Size ScaledScreenSize { get; } + + public override double ScalingFactor + { + get { return _scalingFactor; } + } + + protected override void Dispose(bool disposing) + { + MessagingCenter.Unsubscribe<FormsApplicationPage, DeviceOrientation>(this, BWPorientationChangedName); + base.Dispose(disposing); + } + } + + sealed class WinPhoneExpressionSearch : IExpressionSearch + { + List<object> _results; + Type _targeType; + + public List<T> FindObjects<T>(Expression expression) where T : class + { + _results = new List<object>(); + _targeType = typeof(T); + + Visit(expression); + + return _results.Select(o => o as T).ToList(); + } + + void Visit(Expression expression) + { + if (expression == null) + return; + + switch (expression.NodeType) + { + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.ArrayLength: + case ExpressionType.Quote: + case ExpressionType.TypeAs: + case ExpressionType.UnaryPlus: + Visit(((UnaryExpression)expression).Operand); + break; + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.Power: + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.Or: + case ExpressionType.OrElse: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.Coalesce: + case ExpressionType.ArrayIndex: + case ExpressionType.RightShift: + case ExpressionType.LeftShift: + case ExpressionType.ExclusiveOr: + var binary = (BinaryExpression)expression; + Visit(binary.Left); + Visit(binary.Right); + Visit(binary.Conversion); + break; + case ExpressionType.TypeIs: + Visit(((TypeBinaryExpression)expression).Expression); + break; + case ExpressionType.Conditional: + var conditional = (ConditionalExpression)expression; + Visit(conditional.Test); + Visit(conditional.IfTrue); + Visit(conditional.IfFalse); + break; + case ExpressionType.MemberAccess: + VisitMemberAccess((MemberExpression)expression); + break; + case ExpressionType.Call: + var methodCall = (MethodCallExpression)expression; + Visit(methodCall.Object); + VisitList(methodCall.Arguments, Visit); + break; + case ExpressionType.Lambda: + Visit(((LambdaExpression)expression).Body); + break; + case ExpressionType.New: + VisitList(((NewExpression)expression).Arguments, Visit); + break; + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + VisitList(((NewArrayExpression)expression).Expressions, Visit); + break; + case ExpressionType.Invoke: + var invocation = (InvocationExpression)expression; + VisitList(invocation.Arguments, Visit); + Visit(invocation.Expression); + break; + case ExpressionType.MemberInit: + var init = (MemberInitExpression)expression; + VisitList(init.NewExpression.Arguments, Visit); + VisitList(init.Bindings, VisitBinding); + break; + case ExpressionType.ListInit: + var init1 = (ListInitExpression)expression; + VisitList(init1.NewExpression.Arguments, Visit); + VisitList(init1.Initializers, initializer => VisitList(initializer.Arguments, Visit)); + break; + case ExpressionType.Constant: + break; + default: + throw new ArgumentException(string.Format("Unhandled expression type: '{0}'", expression.NodeType)); + } + } + + void VisitBinding(MemberBinding binding) + { + switch (binding.BindingType) + { + case MemberBindingType.Assignment: + Visit(((MemberAssignment)binding).Expression); + break; + case MemberBindingType.MemberBinding: + VisitList(((MemberMemberBinding)binding).Bindings, VisitBinding); + break; + case MemberBindingType.ListBinding: + VisitList(((MemberListBinding)binding).Initializers, initializer => VisitList(initializer.Arguments, Visit)); + break; + default: + throw new ArgumentException(string.Format("Unhandled binding type '{0}'", binding.BindingType)); + } + } + + static void VisitList<TList>(IEnumerable<TList> list, Action<TList> visitor) + { + foreach (TList element in list) + visitor(element); + } + + void VisitMemberAccess(MemberExpression member) + { + if (member.Expression is ConstantExpression && member.Member is FieldInfo) + { + object container = ((ConstantExpression)member.Expression).Value; + object value = ((FieldInfo)member.Member).GetValue(container); + + if (_targeType.IsInstanceOfType(value)) + _results.Add(value); + } + Visit(member.Expression); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/FormsApplicationPage.cs b/Xamarin.Forms.Platform.WP8/FormsApplicationPage.cs new file mode 100644 index 00000000..ac6decb6 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/FormsApplicationPage.cs @@ -0,0 +1,86 @@ +using System.ComponentModel; +using Microsoft.Phone.Controls; +using Microsoft.Phone.Shell; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class FormsApplicationPage : PhoneApplicationPage + { + Application _application; + Platform _platform; + + protected FormsApplicationPage() + { + PhoneApplicationService.Current.Launching += OnLaunching; + PhoneApplicationService.Current.Activated += OnActivated; + PhoneApplicationService.Current.Deactivated += OnDeactivated; + PhoneApplicationService.Current.Closing += OnClosing; + + MessagingCenter.Send(this, Forms.WP8DeviceInfo.BWPorientationChangedName, Orientation.ToDeviceOrientation()); + OrientationChanged += OnOrientationChanged; + //DeserializePropertyStore (); + } + + protected void LoadApplication(Application application) + { + Application.Current = application; + application.PropertyChanged += ApplicationOnPropertyChanged; + _application = application; + + // Hack around the fact that OnLaunching will haev already happened by this point, sad but needed. + application.SendStart(); + + SetMainPage(); + } + + void ApplicationOnPropertyChanged(object sender, PropertyChangedEventArgs args) + { + if (args.PropertyName == "MainPage") + SetMainPage(); + } + + void OnActivated(object sender, ActivatedEventArgs e) + { + // TODO : figure out consistency of get this to fire + // Check whether tombstoned (terminated, but OS retains information about navigation state and state dictionarys) or dormant + _application.SendResume(); + } + + // when app gets tombstoned, user press back past first page + void OnClosing(object sender, ClosingEventArgs e) + { + // populate isolated storage. + //SerializePropertyStore (); + _application.SendSleepAsync().Wait(); + } + + void OnDeactivated(object sender, DeactivatedEventArgs e) + { + // populate state dictionaries, properties + //SerializePropertyStore (); + _application.SendSleepAsync().Wait(); + } + + void OnLaunching(object sender, LaunchingEventArgs e) + { + // TODO : not currently firing, is fired before MainPage ctor is called + _application.SendStart(); + } + + void OnOrientationChanged(object sender, OrientationChangedEventArgs e) + { + MessagingCenter.Send(this, Forms.WP8DeviceInfo.BWPorientationChangedName, e.Orientation.ToDeviceOrientation()); + } + + void SetMainPage() + { + if (_platform == null) + _platform = new Platform(this); + + _platform.SetPage(_application.MainPage); + + if (!ReferenceEquals(Content, _platform)) + Content = _platform.GetCanvas(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/FormsListPicker.cs b/Xamarin.Forms.Platform.WP8/FormsListPicker.cs new file mode 100644 index 00000000..573f639b --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/FormsListPicker.cs @@ -0,0 +1,25 @@ +using System; +using System.Windows; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class FormsListPicker : ListPicker + { + internal static readonly DependencyProperty ListPickerModeChangedProperty = DependencyProperty.Register("ListPickerMode", typeof(ListPickerMode), typeof(FormsListPicker), + new PropertyMetadata(ModeChanged)); + + protected virtual void OnListPickerModeChanged(DependencyPropertyChangedEventArgs args) + { + ListPickerModeChanged?.Invoke(this, args); + } + + internal event EventHandler<DependencyPropertyChangedEventArgs> ListPickerModeChanged; + + static void ModeChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + var listPicker = dependencyObject as FormsListPicker; + listPicker?.OnListPickerModeChanged(dependencyPropertyChangedEventArgs); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/FormsPhoneTextBox.cs b/Xamarin.Forms.Platform.WP8/FormsPhoneTextBox.cs new file mode 100644 index 00000000..342646cd --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/FormsPhoneTextBox.cs @@ -0,0 +1,236 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + /// <summary> + /// An intermediate class for injecting bindings for things the default + /// textbox doesn't allow us to bind/modify + /// </summary> + public class FormsPhoneTextBox : PhoneTextBox + { + const char ObfuscationCharacter = '●'; + + public static readonly DependencyProperty PlaceholderForegroundBrushProperty = DependencyProperty.Register("PlaceholderForegroundBrush", typeof(Brush), typeof(FormsPhoneTextBox), + new PropertyMetadata(default(Brush))); + + public static readonly DependencyProperty IsPasswordProperty = DependencyProperty.Register("IsPassword", typeof(bool), typeof(FormsPhoneTextBox), + new PropertyMetadata(default(bool), OnIsPasswordChanged)); + + public new static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(FormsPhoneTextBox), new PropertyMetadata("", TextPropertyChanged)); + + protected internal static readonly DependencyProperty DisabledTextProperty = DependencyProperty.Register("DisabledText", typeof(string), typeof(FormsPhoneTextBox), new PropertyMetadata("")); + + static InputScope s_passwordInputScope; + InputScope _cachedInputScope; + CancellationTokenSource _cts; + bool _internalChangeFlag; + + public FormsPhoneTextBox() + { + TextChanged += OnTextChanged; + SelectionChanged += OnSelectionChanged; + } + + public bool IsPassword + { + get { return (bool)GetValue(IsPasswordProperty); } + set { SetValue(IsPasswordProperty, value); } + } + + public Brush PlaceholderForegroundBrush + { + get { return (Brush)GetValue(PlaceholderForegroundBrushProperty); } + set { SetValue(PlaceholderForegroundBrushProperty, value); } + } + + public new string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + protected internal string DisabledText + { + get { return (string)GetValue(DisabledTextProperty); } + set { SetValue(DisabledTextProperty, value); } + } + + static InputScope PasswordInputScope + { + get + { + if (s_passwordInputScope != null) + return s_passwordInputScope; + + s_passwordInputScope = new InputScope(); + var name = new InputScopeName { NameValue = InputScopeNameValue.Default }; + s_passwordInputScope.Names.Add(name); + + return s_passwordInputScope; + } + } + + void DelayObfuscation() + { + int lengthDifference = base.Text.Length - Text.Length; + + string updatedRealText = DetermineTextFromPassword(Text, base.Text); + + if (Text == updatedRealText) + { + // Nothing to do + return; + } + + Text = updatedRealText; + + // Cancel any pending delayed obfuscation + _cts?.Cancel(); + _cts = null; + + string newText; + + if (lengthDifference != 1) + { + // Either More than one character got added in this text change (e.g., a paste operation) + // Or characters were removed. Either way, we don't need to do the delayed obfuscation dance + newText = Obfuscate(); + } + else + { + // Only one character was added; we need to leave it visible for a brief time period + // Obfuscate all but the last character for now + newText = Obfuscate(true); + + // Leave the last character visible until a new character is added + // or sufficient time has passed + if (_cts == null) + _cts = new CancellationTokenSource(); + + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromSeconds(0.5), _cts.Token); + _cts.Token.ThrowIfCancellationRequested(); + Dispatcher.BeginInvoke(() => + { + base.Text = Obfuscate(); + SelectionStart = base.Text.Length; + }); + }, _cts.Token); + } + + if (base.Text == newText) + return; + + base.Text = newText; + SelectionStart = base.Text.Length; + } + + static string DetermineTextFromPassword(string realText, string passwordText) + { + int firstObfuscationChar = passwordText.IndexOf(ObfuscationCharacter); + + if (firstObfuscationChar > 0) + { + // The user is typing faster than we can process, and the text is coming in at the beginning + // of the textbox instead of the end + passwordText = passwordText.Substring(firstObfuscationChar, passwordText.Length - firstObfuscationChar) + passwordText.Substring(0, firstObfuscationChar); + } + + if (realText.Length == passwordText.Length) + return realText; + + if (passwordText.Length == 0) + return ""; + + if (passwordText.Length < realText.Length) + return realText.Substring(0, passwordText.Length); + + int lengthDifference = passwordText.Length - realText.Length; + + return realText + passwordText.Substring(passwordText.Length - lengthDifference, lengthDifference); + } + + string Obfuscate(bool leaveLastVisible = false) + { + if (leaveLastVisible && Text.Length == 1) + return Text; + + if (leaveLastVisible && Text.Length > 1) + return new string(ObfuscationCharacter, Text.Length - 1) + Text.Substring(Text.Length - 1, 1); + + return new string(ObfuscationCharacter, Text.Length); + } + + static void OnIsPasswordChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + var textBox = (FormsPhoneTextBox)dependencyObject; + textBox.UpdateInputScope(); + textBox.SyncBaseText(); + } + + void OnSelectionChanged(object sender, RoutedEventArgs routedEventArgs) + { + if (!IsPassword) + return; + + // Prevent the user from selecting any text in the password box by forcing all selection + // to zero-length at the end of the text + // This simulates the "do not allow clipboard copy" behavior the PasswordBox control has + if (SelectionLength > 0 || SelectionStart < Text.Length) + { + SelectionLength = 0; + SelectionStart = Text.Length; + } + } + + void OnTextChanged(object sender, System.Windows.Controls.TextChangedEventArgs textChangedEventArgs) + { + if (IsPassword) + DelayObfuscation(); + else if (base.Text != Text) + { + // Not in password mode, so we just need to make the "real" Text match + // what's in the textbox; the internalChange flag keeps the TextProperty + // synchronization from happening + _internalChangeFlag = true; + Text = base.Text; + _internalChangeFlag = false; + } + } + + void SyncBaseText() + { + if (_internalChangeFlag) + return; + + base.Text = IsPassword ? Obfuscate() : Text; + DisabledText = base.Text; + + SelectionStart = base.Text.Length; + } + + static void TextPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + var textBox = (FormsPhoneTextBox)dependencyObject; + textBox.SyncBaseText(); + } + + void UpdateInputScope() + { + if (IsPassword) + { + _cachedInputScope = InputScope; + InputScope = PasswordInputScope; // We don't want suggestions turned on if we're in password mode + } + else + InputScope = _cachedInputScope; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/FrameRenderer.cs b/Xamarin.Forms.Platform.WP8/FrameRenderer.cs new file mode 100644 index 00000000..00472161 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/FrameRenderer.cs @@ -0,0 +1,57 @@ +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class FrameRenderer : ViewRenderer<Frame, Border> + { + public FrameRenderer() + { + AutoPackage = false; + } + + protected override void OnElementChanged(ElementChangedEventArgs<Frame> e) + { + base.OnElementChanged(e); + + SetNativeControl(new Border()); + + PackChild(); + UpdateBorder(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == "Content") + PackChild(); + else if (e.PropertyName == Frame.OutlineColorProperty.PropertyName || e.PropertyName == Frame.HasShadowProperty.PropertyName) + UpdateBorder(); + } + + void PackChild() + { + if (Element.Content == null) + return; + + Platform.SetRenderer(Element.Content, Platform.CreateRenderer(Element.Content)); + + UIElement element = Platform.GetRenderer(Element.Content).ContainerElement; + Control.Child = element; + } + + void UpdateBorder() + { + Control.CornerRadius = new CornerRadius(5); + if (Element.OutlineColor != Color.Default) + { + Control.BorderBrush = Element.OutlineColor.ToBrush(); + Control.BorderThickness = new System.Windows.Thickness(1); + } + else + Control.BorderBrush = new Color(0, 0, 0, 0).ToBrush(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/FrameworkElementExtensions.cs b/Xamarin.Forms.Platform.WP8/FrameworkElementExtensions.cs new file mode 100644 index 00000000..f8c77cef --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/FrameworkElementExtensions.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal static class FrameworkElementExtensions + { + static readonly Lazy<ConcurrentDictionary<Type, DependencyProperty>> ForegroundProperties = + new Lazy<ConcurrentDictionary<Type, DependencyProperty>>(() => new ConcurrentDictionary<Type, DependencyProperty>()); + + public static Brush GetForeground(this FrameworkElement element) + { + if (element == null) + throw new ArgumentNullException("element"); + + return (Brush)element.GetValue(GetForegroundProperty(element)); + } + + public static System.Windows.Data.Binding GetForegroundBinding(this FrameworkElement element) + { + System.Windows.Data.BindingExpression expr = element.GetBindingExpression(GetForegroundProperty(element)); + if (expr == null) + return null; + + return expr.ParentBinding; + } + + public static void SetForeground(this FrameworkElement element, Brush foregroundBrush) + { + if (element == null) + throw new ArgumentNullException("element"); + + element.SetValue(GetForegroundProperty(element), foregroundBrush); + } + + public static void SetForeground(this FrameworkElement element, System.Windows.Data.Binding binding) + { + if (element == null) + throw new ArgumentNullException("element"); + + element.SetBinding(GetForegroundProperty(element), binding); + } + + static DependencyProperty GetForegroundProperty(FrameworkElement element) + { + if (element is Control) + return Control.ForegroundProperty; + if (element is TextBlock) + return TextBlock.ForegroundProperty; + + Type type = element.GetType(); + + DependencyProperty foregroundProperty; + if (!ForegroundProperties.Value.TryGetValue(type, out foregroundProperty)) + { + FieldInfo field = type.GetFields(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(f => f.Name == "ForegroundProperty"); + if (field == null) + throw new ArgumentException("type is not a Foregroundable type"); + + var property = (DependencyProperty)field.GetValue(null); + ForegroundProperties.Value.TryAdd(type, property); + + return property; + } + + return foregroundProperty; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ICellRenderer.cs b/Xamarin.Forms.Platform.WP8/ICellRenderer.cs new file mode 100644 index 00000000..350b72d6 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ICellRenderer.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms.Platform.WinPhone +{ + public interface ICellRenderer : IRegisterable + { + System.Windows.DataTemplate GetTemplate(Cell cell); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/IVisualElementRenderer.cs b/Xamarin.Forms.Platform.WP8/IVisualElementRenderer.cs new file mode 100644 index 00000000..c4e097cb --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/IVisualElementRenderer.cs @@ -0,0 +1,18 @@ +using System; +using System.Windows; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public interface IVisualElementRenderer : IRegisterable + { + UIElement ContainerElement { get; } + + VisualElement Element { get; } + + event EventHandler<VisualElementChangedEventArgs> ElementChanged; + + SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint); + + void SetElement(VisualElement element); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ImageRenderer.cs b/Xamarin.Forms.Platform.WP8/ImageRenderer.cs new file mode 100644 index 00000000..8be9c15d --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ImageRenderer.cs @@ -0,0 +1,166 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal static class ImageExtensions + { + public static Stretch ToStretch(this Aspect aspect) + { + switch (aspect) + { + case Aspect.Fill: + return Stretch.Fill; + case Aspect.AspectFill: + return Stretch.UniformToFill; + default: + case Aspect.AspectFit: + return Stretch.Uniform; + } + } + } + + public class ImageRenderer : ViewRenderer<Image, System.Windows.Controls.Image> + { + public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + // Someone reported a NRE happening in this method which can only be explained by Control being null + // which only happens at the very beginning of the view lifecycle. Honest I have no idea how this might + // happen because it really shouldn't measure at that point. Add check anyway and live in fear... + if (Control?.Source == null) + return new SizeRequest(); + + var result = new Size { Width = ((BitmapImage)Control.Source).PixelWidth, Height = ((BitmapImage)Control.Source).PixelHeight }; + + return new SizeRequest(result); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Image> e) + { + base.OnElementChanged(e); + + if (e.NewElement != null) + { + if (Control == null) + { + var image = new System.Windows.Controls.Image(); + SetNativeControl(image); + } + + SetSource(Control); + SetAspect(Control); + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Image.SourceProperty.PropertyName) + SetSource(Control); + else if (e.PropertyName == Image.AspectProperty.PropertyName) + SetAspect(Control); + } + + void SetAspect(System.Windows.Controls.Image image) + { + Aspect aspect = Element.Aspect; + + image.Stretch = aspect.ToStretch(); + } + + async void SetSource(System.Windows.Controls.Image image) + { + ((IElementController)Element).SetValueFromRenderer(Image.IsLoadingPropertyKey, true); + + ImageSource source = Element.Source; + IImageSourceHandler handler; + if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null) + { + System.Windows.Media.ImageSource imagesource; + try + { + imagesource = await handler.LoadImageAsync(source); + } + catch (TaskCanceledException) + { + imagesource = null; + } + image.Source = imagesource; + // if you dont at least measure the thing once it wont load the image + // then the whole thing falls over. + image.Measure(new System.Windows.Size(100, 100)); + ((IVisualElementController)Element).NativeSizeChanged(); + } + else + image.Source = null; + + ((IElementController)Element).SetValueFromRenderer(Image.IsLoadingPropertyKey, false); + } + } + + public interface IImageSourceHandler : IRegisterable + { + Task<System.Windows.Media.ImageSource> LoadImageAsync(ImageSource imagesoure, CancellationToken cancelationToken = default(CancellationToken)); + } + + public sealed class FileImageSourceHandler : IImageSourceHandler + { + public Task<System.Windows.Media.ImageSource> LoadImageAsync(ImageSource imagesoure, CancellationToken cancelationToken = new CancellationToken()) + { + System.Windows.Media.ImageSource image = null; + var filesource = imagesoure as FileImageSource; + if (filesource != null) + { + string file = filesource.File; + image = new BitmapImage(new Uri("/" + file, UriKind.Relative)); + } + return Task.FromResult(image); + } + } + + public sealed class StreamImagesourceHandler : IImageSourceHandler + { + public async Task<System.Windows.Media.ImageSource> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = new CancellationToken()) + { + BitmapImage bitmapimage = null; + + var streamsource = imagesource as StreamImageSource; + if (streamsource != null && streamsource.Stream != null) + { + using(Stream stream = await streamsource.GetStreamAsync(cancelationToken)) + { + bitmapimage = new BitmapImage(); + bitmapimage.SetSource(stream); + } + } + return (System.Windows.Media.ImageSource)bitmapimage; + } + } + + public sealed class ImageLoaderSourceHandler : IImageSourceHandler + { + public async Task<System.Windows.Media.ImageSource> LoadImageAsync(ImageSource imagesoure, CancellationToken cancelationToken = new CancellationToken()) + { + BitmapImage bitmapimage = null; + var imageLoader = imagesoure as UriImageSource; + if (imageLoader != null && imageLoader.Uri != null) + { + using(Stream streamimage = await imageLoader.GetStreamAsync(cancelationToken)) + { + if (streamimage != null && streamimage.CanRead) + { + bitmapimage = new BitmapImage(); + bitmapimage.SetSource(streamimage); + } + } + } + return bitmapimage; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/LabelRenderer.cs b/Xamarin.Forms.Platform.WP8/LabelRenderer.cs new file mode 100644 index 00000000..86c68adf --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/LabelRenderer.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public static class FormattedStringExtensions + { + public static IEnumerable<Inline> ToInlines(this FormattedString formattedString) + { + foreach (Span span in formattedString.Spans) + yield return span.ToRun(); + } + + public static Run ToRun(this Span span) + { + var run = new Run { Text = span.Text }; + + if (span.ForegroundColor != Color.Default) + run.Foreground = span.ForegroundColor.ToBrush(); + + if (!span.IsDefault()) + run.ApplyFont(span.Font); + + return run; + } + } + + public class LabelRenderer : ViewRenderer<Label, TextBlock> + { + bool _fontApplied; + + protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) + { + if (Element == null) + return finalSize; + double childHeight = Math.Max(0, Math.Min(Element.Height, Control.DesiredSize.Height)); + var rect = new Rect(); + + switch (Element.VerticalTextAlignment) + { + case TextAlignment.Start: + break; + default: + case TextAlignment.Center: + rect.Y = (int)((finalSize.Height - childHeight) / 2); + break; + case TextAlignment.End: + rect.Y = finalSize.Height - childHeight; + break; + } + rect.Height = childHeight; + rect.Width = finalSize.Width; + Control.Arrange(rect); + return finalSize; + } + + protected override void OnElementChanged(ElementChangedEventArgs<Label> e) + { + base.OnElementChanged(e); + + var textBlock = new TextBlock { Foreground = (Brush)System.Windows.Application.Current.Resources["PhoneForegroundBrush"] }; + UpdateText(textBlock); + UpdateColor(textBlock); + UpdateAlign(textBlock); + UpdateFont(textBlock); + UpdateLineBreakMode(textBlock); + SetNativeControl(textBlock); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Label.TextProperty.PropertyName || e.PropertyName == Label.FormattedTextProperty.PropertyName) + UpdateText(Control); + else if (e.PropertyName == Label.TextColorProperty.PropertyName) + UpdateColor(Control); + else if (e.PropertyName == Label.HorizontalTextAlignmentProperty.PropertyName || e.PropertyName == Label.VerticalTextAlignmentProperty.PropertyName) + UpdateAlign(Control); + else if (e.PropertyName == Label.FontProperty.PropertyName) + UpdateFont(Control); + else if (e.PropertyName == Label.LineBreakModeProperty.PropertyName) + UpdateLineBreakMode(Control); + base.OnElementPropertyChanged(sender, e); + } + + void UpdateAlign(TextBlock textBlock) + { + if (textBlock == null) + return; + + Label label = Element; + if (label == null) + return; + + textBlock.TextAlignment = label.HorizontalTextAlignment.ToNativeTextAlignment(); + textBlock.VerticalAlignment = label.VerticalTextAlignment.ToNativeVerticalAlignment(); + } + + void UpdateColor(TextBlock textBlock) + { + if (textBlock == null) + return; + + Label label = Element; + if (label != null && label.TextColor != Color.Default) + textBlock.Foreground = label.TextColor.ToBrush(); + else + textBlock.Foreground = (Brush)System.Windows.Application.Current.Resources["PhoneForegroundBrush"]; + } + + void UpdateFont(TextBlock textBlock) + { + if (textBlock == null) + return; + + Label label = Element; + if (label == null || (label.IsDefault() && !_fontApplied)) + return; + + Font fontToApply = label.IsDefault() ? Font.SystemFontOfSize(NamedSize.Medium) : label.Font; + + textBlock.ApplyFont(fontToApply); + _fontApplied = true; + } + + void UpdateLineBreakMode(TextBlock textBlock) + { + if (textBlock == null) + return; + + switch (Element.LineBreakMode) + { + case LineBreakMode.NoWrap: + textBlock.TextTrimming = TextTrimming.None; + textBlock.TextWrapping = TextWrapping.NoWrap; + break; + case LineBreakMode.WordWrap: + textBlock.TextTrimming = TextTrimming.None; + textBlock.TextWrapping = TextWrapping.Wrap; + break; + case LineBreakMode.CharacterWrap: + textBlock.TextTrimming = TextTrimming.WordEllipsis; + textBlock.TextWrapping = TextWrapping.Wrap; + break; + case LineBreakMode.HeadTruncation: + textBlock.TextTrimming = TextTrimming.WordEllipsis; + textBlock.TextWrapping = TextWrapping.NoWrap; + break; + case LineBreakMode.TailTruncation: + textBlock.TextTrimming = TextTrimming.WordEllipsis; + textBlock.TextWrapping = TextWrapping.NoWrap; + break; + case LineBreakMode.MiddleTruncation: + textBlock.TextTrimming = TextTrimming.WordEllipsis; + textBlock.TextWrapping = TextWrapping.NoWrap; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + void UpdateText(TextBlock textBlock) + { + if (textBlock == null) + return; + + Label label = Element; + if (label != null) + { + if (label.FormattedText == null) + textBlock.Text = label.Text; + else + { + FormattedString formattedText = label.FormattedText ?? label.Text; + + textBlock.Inlines.Clear(); + foreach (Inline inline in formattedText.ToInlines()) + textBlock.Inlines.Add(inline); + } + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/LayoutExtensions.cs b/Xamarin.Forms.Platform.WP8/LayoutExtensions.cs new file mode 100644 index 00000000..1fb1f247 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/LayoutExtensions.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Windows; +using WSize = System.Windows.Size; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public delegate SizeRequest? GetDesiredSizeDelegate(NativeViewWrapperRenderer renderer, double widthConstraint, double heightConstraint); + + public delegate WSize? ArrangeOverrideDelegate(NativeViewWrapperRenderer renderer, WSize finalSize); + + public delegate WSize? MeasureOverrideDelegate(NativeViewWrapperRenderer renderer, WSize availableSize); + + public static class LayoutExtensions + { + public static void Add(this IList<View> children, FrameworkElement view, GetDesiredSizeDelegate getDesiredSizeDelegate = null, ArrangeOverrideDelegate arrangeOverrideDelegate = null, + MeasureOverrideDelegate measureOverrideDelegate = null) + { + children.Add(view.ToView(getDesiredSizeDelegate, arrangeOverrideDelegate, measureOverrideDelegate)); + } + + public static View ToView(this FrameworkElement view, GetDesiredSizeDelegate getDesiredSizeDelegate = null, ArrangeOverrideDelegate arrangeOverrideDelegate = null, + MeasureOverrideDelegate measureOverrideDelegate = null) + { + return new NativeViewWrapper(view, getDesiredSizeDelegate, arrangeOverrideDelegate, measureOverrideDelegate); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ListViewRenderer.cs b/Xamarin.Forms.Platform.WP8/ListViewRenderer.cs new file mode 100644 index 00000000..362c993c --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ListViewRenderer.cs @@ -0,0 +1,717 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; +using Microsoft.Phone.Controls; +using GestureEventArgs = System.Windows.Input.GestureEventArgs; +using SLButton = System.Windows.Controls.Button; +using SLBinding = System.Windows.Data.Binding; + +namespace Xamarin.Forms.Platform.WinPhone +{ + // Fixes a weird crash, don't ask. + internal class FixedLongListSelector : LongListSelector + { + bool _isInPullToRefresh; + + System.Windows.Point _lastPosition; + double _pullToRefreshStatus; + + public FixedLongListSelector() + { + Loaded += OnLoaded; + } + + public bool IsInPullToRefresh + { + get { return _isInPullToRefresh; } + private set + { + if (_isInPullToRefresh == value) + return; + _isInPullToRefresh = value; + + if (_isInPullToRefresh) + { + EventHandler handler = PullToRefreshStarted; + if (handler != null) + handler(this, EventArgs.Empty); + } + else + { + EventHandler handler = PullToRefreshStatus >= 1 ? PullToRefreshCompleted : PullToRefreshCanceled; + if (handler != null) + handler(this, EventArgs.Empty); + _pullToRefreshStatus = 0; + } + } + } + + public double PullToRefreshStatus + { + get { return _pullToRefreshStatus; } + set + { + if (_pullToRefreshStatus == value) + return; + _pullToRefreshStatus = value; + EventHandler handler = PullToRefreshStatusUpdated; + if (handler != null) + handler(this, EventArgs.Empty); + } + } + + public ViewportControl ViewportControl { get; private set; } + + bool ViewportAtTop + { + get { return ViewportControl.Viewport.Top == 0; } + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (ViewportControl != null) + { + ViewportControl.ViewportChanged -= OnViewportChanged; + ViewportControl.ManipulationStateChanged -= OnManipulationStateChanged; + } + + ViewportControl = (ViewportControl)VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(this, 0), 0), 0); + ViewportControl.ViewportChanged += OnViewportChanged; + ViewportControl.ManipulationStateChanged += OnManipulationStateChanged; + } + + public event EventHandler PullToRefreshCanceled; + + public event EventHandler PullToRefreshCompleted; + + public event EventHandler PullToRefreshStarted; + + public event EventHandler PullToRefreshStatusUpdated; + + protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) + { + try + { + return base.MeasureOverride(availableSize); + } + catch (ArgumentException) + { + return base.MeasureOverride(availableSize); + } + } + + void OnFrameReported(object sender, TouchFrameEventArgs e) + { + TouchPoint touchPoint; + try + { + touchPoint = e.GetPrimaryTouchPoint(this); + } + catch (Exception) + { + return; + } + + if (touchPoint == null || touchPoint.Action != TouchAction.Move) + return; + + System.Windows.Point position = touchPoint.Position; + + if (IsInPullToRefresh) + { + double delta = position.Y - _lastPosition.Y; + PullToRefreshStatus += delta / 150.0; + } + + _lastPosition = position; + } + + void OnLoaded(object sender, RoutedEventArgs routedEventArgs) + { + Loaded -= OnLoaded; + Unloaded += OnUnloaded; + + Touch.FrameReported += OnFrameReported; + } + + void OnManipulationStateChanged(object o, ManipulationStateChangedEventArgs args) + { + switch (ViewportControl.ManipulationState) + { + case ManipulationState.Idle: + // thing is rested + IsInPullToRefresh = false; + break; + case ManipulationState.Manipulating: + // user interaction + IsInPullToRefresh = ViewportAtTop; + break; + case ManipulationState.Animating: + // user let go + IsInPullToRefresh = false; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) + { + Loaded += OnLoaded; + Unloaded -= OnUnloaded; + + Touch.FrameReported -= OnFrameReported; + } + + void OnViewportChanged(object o, ViewportChangedEventArgs args) + { + if (ViewportControl.ManipulationState == ManipulationState.Manipulating) + IsInPullToRefresh = ViewportAtTop; + } + } + + public class ListViewRenderer : ViewRenderer<ListView, LongListSelector> + { + public static readonly DependencyProperty HighlightWhenSelectedProperty = DependencyProperty.RegisterAttached("HighlightWhenSelected", typeof(bool), typeof(ListViewRenderer), + new PropertyMetadata(false)); + + readonly List<Tuple<FrameworkElement, SLBinding, Brush>> _previousHighlights = new List<Tuple<FrameworkElement, SLBinding, Brush>>(); + + Animatable _animatable; + object _fromNative; + bool _itemNeedsSelecting; + FixedLongListSelector _listBox; + System.Windows.Controls.ProgressBar _progressBar; + + ViewportControl _viewport; + + public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + SizeRequest result = base.GetDesiredSize(widthConstraint, heightConstraint); + result.Minimum = new Size(40, 40); + return result; + } + + public static bool GetHighlightWhenSelected(DependencyObject dependencyObject) + { + return (bool)dependencyObject.GetValue(HighlightWhenSelectedProperty); + } + + public static void SetHighlightWhenSelected(DependencyObject dependencyObject, bool value) + { + dependencyObject.SetValue(HighlightWhenSelectedProperty, value); + } + + protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) + { + System.Windows.Size result = base.ArrangeOverride(finalSize); + + _progressBar.Measure(finalSize); + _progressBar.Arrange(new Rect(0, 0, finalSize.Width, _progressBar.DesiredSize.Height)); + + return result; + } + + protected override void OnElementChanged(ElementChangedEventArgs<ListView> e) + { + base.OnElementChanged(e); + + Element.ScrollToRequested += OnScrollToRequested; + + if (Element.SelectedItem != null) + _itemNeedsSelecting = true; + + _listBox = new FixedLongListSelector + { + DataContext = Element, + ItemsSource = Element.TemplatedItems, + ItemTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["CellTemplate"], + GroupHeaderTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["ListViewHeader"], + ListHeaderTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["View"], + ListFooterTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["View"] + }; + _listBox.SetBinding(LongListSelector.IsGroupingEnabledProperty, new SLBinding("IsGroupingEnabled")); + + _listBox.SelectionChanged += OnNativeSelectionChanged; + _listBox.Tap += OnNativeItemTapped; + _listBox.ItemRealized += OnItemRealized; + + _listBox.PullToRefreshStarted += OnPullToRefreshStarted; + _listBox.PullToRefreshCompleted += OnPullToRefreshCompleted; + _listBox.PullToRefreshCanceled += OnPullToRefreshCanceled; + _listBox.PullToRefreshStatusUpdated += OnPullToRefreshStatusUpdated; + + SetNativeControl(_listBox); + + _progressBar = new System.Windows.Controls.ProgressBar { Maximum = 1, Visibility = Visibility.Collapsed }; + Children.Add(_progressBar); + + UpdateHeader(); + UpdateFooter(); + UpdateJumpList(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == ListView.SelectedItemProperty.PropertyName) + OnItemSelected(Element.SelectedItem); + else if (e.PropertyName == "HeaderElement") + UpdateHeader(); + else if (e.PropertyName == "FooterElement") + UpdateFooter(); + else if ((e.PropertyName == ListView.IsRefreshingProperty.PropertyName) || (e.PropertyName == ListView.IsPullToRefreshEnabledProperty.PropertyName) || (e.PropertyName == "CanRefresh")) + UpdateIsRefreshing(); + else if (e.PropertyName == "GroupShortNameBinding") + UpdateJumpList(); + } + + protected override void UpdateNativeWidget() + { + base.UpdateNativeWidget(); + + if (_progressBar != null) + _progressBar.Width = Element.Width; + } + + Cell FindCell(GestureEventArgs e, out FrameworkElement element) + { + Cell cell = null; + element = e.OriginalSource as FrameworkElement; + if (element != null) + cell = element.DataContext as Cell; + + if (cell == null) + { + System.Windows.Point pos = e.GetPosition(_listBox); + IEnumerable<UIElement> elements = VisualTreeHelper.FindElementsInHostCoordinates(pos, _listBox); + foreach (FrameworkElement frameworkElement in elements.OfType<FrameworkElement>()) + { + if ((cell = frameworkElement.DataContext as Cell) != null) + { + element = frameworkElement; + break; + } + } + } + + return cell; + } + + static IEnumerable<T> FindDescendants<T>(DependencyObject dobj) where T : DependencyObject + { + int count = VisualTreeHelper.GetChildrenCount(dobj); + for (var i = 0; i < count; i++) + { + DependencyObject element = VisualTreeHelper.GetChild(dobj, i); + if (element is T) + yield return (T)element; + + foreach (T descendant in FindDescendants<T>(element)) + yield return descendant; + } + } + + FrameworkElement FindElement(Cell cell) + { + foreach (CellControl selector in FindDescendants<CellControl>(_listBox)) + { + if (ReferenceEquals(cell, selector.DataContext)) + return selector; + } + + return null; + } + + IEnumerable<FrameworkElement> FindHighlight(FrameworkElement element) + { + FrameworkElement parent = element; + while (true) + { + element = parent; + if (element is CellControl) + break; + + parent = VisualTreeHelper.GetParent(element) as FrameworkElement; + if (parent == null) + { + parent = element; + break; + } + } + + return FindHighlightCore(parent); + } + + IEnumerable<FrameworkElement> FindHighlightCore(DependencyObject element) + { + int children = VisualTreeHelper.GetChildrenCount(element); + for (var i = 0; i < children; i++) + { + DependencyObject child = VisualTreeHelper.GetChild(element, i); + + var label = child as LabelRenderer; + var childElement = child as FrameworkElement; + if (childElement != null && (GetHighlightWhenSelected(childElement) || label != null)) + { + if (label != null) + yield return label.Control; + else + yield return childElement; + } + + foreach (FrameworkElement recursedElement in FindHighlightCore(childElement)) + yield return recursedElement; + } + } + + double GetHeight(Dictionary<System.Windows.DataTemplate, FrameworkElement> reusables, System.Windows.DataTemplate template, object bindingContext) + { + double width = Control.ActualWidth; + + FrameworkElement content; + if (!reusables.TryGetValue(template, out content)) + { + content = (FrameworkElement)template.LoadContent(); + + // Windows Phone refuses to properly bind things on a first pass or even a second pass unless it has different content + // so we'll force it to cycle here the first time for each template. + content.DataContext = bindingContext; + content.Measure(new System.Windows.Size(width, double.PositiveInfinity)); + content.DataContext = null; + content.Measure(new System.Windows.Size(width, double.PositiveInfinity)); + + var control = content as Control; + if (control != null) + { + // Since we're not adding to the visual tree, we need to inherit the font to measure correctly. + control.FontFamily = Control.FontFamily; + } + + reusables[template] = content; + } + + content.DataContext = bindingContext; + content.Measure(new System.Windows.Size(width, double.PositiveInfinity)); + return content.DesiredSize.Height; + } + + void OnItemRealized(object sender, ItemRealizationEventArgs e) + { + if (!_itemNeedsSelecting) + return; + + var cell = e.Container.DataContext as Cell; + if (cell == null || !Equals(cell.BindingContext, Element.SelectedItem)) + return; + + _itemNeedsSelecting = false; + OnItemSelected(Element.SelectedItem); + } + + void OnItemSelected(object selectedItem) + { + if (_fromNative != null && Equals(selectedItem, _fromNative)) + { + _fromNative = null; + return; + } + + RestorePreviousSelectedVisual(); + + if (selectedItem == null) + { + _listBox.SelectedItem = selectedItem; + return; + } + + IEnumerable<CellControl> items = FindDescendants<CellControl>(_listBox); + + CellControl item = items.FirstOrDefault(i => + { + var cell = (Cell)i.DataContext; + return Equals(cell.BindingContext, selectedItem); + }); + + if (item == null) + { + _itemNeedsSelecting = true; + return; + } + + SetSelectedVisual(item); + } + + void OnNativeItemTapped(object sender, GestureEventArgs e) + { + var cell = (Cell)Control.SelectedItem; + if (cell == null) + return; + + Cell parentCell = null; + + if (Element.IsGroupingEnabled) + { + TemplatedItemsList<ItemsView<Cell>, Cell> til = TemplatedItemsList<ItemsView<Cell>, Cell>.GetGroup(cell); + parentCell = til.HeaderContent; + } + + _fromNative = cell.BindingContext; + + if (Element.IsGroupingEnabled) + { + Element.NotifyRowTapped(TemplatedItemsList<ItemsView<Cell>, Cell>.GetIndex(parentCell), TemplatedItemsList<ItemsView<Cell>, Cell>.GetIndex(cell)); + } + else + Element.NotifyRowTapped(TemplatedItemsList<ItemsView<Cell>, Cell>.GetIndex(cell)); + } + + void OnNativeSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (e.AddedItems.Count == 0) + return; + + var cell = (Cell)e.AddedItems[0]; + Cell parentCell = null; + + if (cell == null) + { + RestorePreviousSelectedVisual(); + return; + } + + RestorePreviousSelectedVisual(); + FrameworkElement element = FindElement(cell); + if (element != null) + SetSelectedVisual(element); + } + + void OnPullToRefreshCanceled(object sender, EventArgs args) + { + if (Element.IsPullToRefreshEnabled && ((IListViewController)Element).RefreshAllowed) + _progressBar.Visibility = Visibility.Collapsed; + } + + void OnPullToRefreshCompleted(object sender, EventArgs args) + { + if (Element.IsPullToRefreshEnabled && ((IListViewController)Element).RefreshAllowed) + { + _progressBar.IsIndeterminate = true; + ((IListViewController)Element).SendRefreshing(); + } + } + + void OnPullToRefreshStarted(object sender, EventArgs args) + { + if (Element.IsPullToRefreshEnabled && ((IListViewController)Element).RefreshAllowed) + { + _progressBar.Visibility = Visibility.Visible; + _progressBar.IsIndeterminate = false; + _progressBar.Value = Math.Max(0, Math.Min(1, _listBox.PullToRefreshStatus)); + } + } + + void OnPullToRefreshStatusUpdated(object sender, EventArgs eventArgs) + { + if (Element.IsPullToRefreshEnabled && ((IListViewController)Element).RefreshAllowed) + _progressBar.Value = Math.Max(0, Math.Min(1, _listBox.PullToRefreshStatus)); + } + + void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e) + { + if (_animatable == null && e.ShouldAnimate) + _animatable = new Animatable(); + + if (_viewport == null) + { + // Making sure we're actually loaded + if (VisualTreeHelper.GetChildrenCount(_listBox) == 0) + { + RoutedEventHandler handler = null; + handler = (o, args) => + { + Control.Loaded -= handler; + OnScrollToRequested(sender, e); + }; + Control.Loaded += handler; + + return; + } + _viewport = (ViewportControl)VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(_listBox, 0), 0), 0); + if (_viewport.Viewport.Bottom == 0) + { + EventHandler<ViewportChangedEventArgs> viewportChanged = null; + viewportChanged = (o, args) => + { + if (_viewport.Viewport.Bottom == 0) + return; + + _viewport.ViewportChanged -= viewportChanged; + OnScrollToRequested(sender, e); + }; + _viewport.ViewportChanged += viewportChanged; + return; + } + } + + double y = 0; + double targetHeight = 0; + double targetHeaderHeight = 0; + + var templateReusables = new Dictionary<System.Windows.DataTemplate, FrameworkElement>(); + + var found = false; + + if (Element.IsGroupingEnabled) + { + for (var g = 0; g < Element.TemplatedItems.Count; g++) + { + if (found) + break; + + TemplatedItemsList<ItemsView<Cell>, Cell> til = Element.TemplatedItems.GetGroup(g); + + double headerHeight = GetHeight(templateReusables, Control.GroupHeaderTemplate, til); + y += headerHeight; + + for (var i = 0; i < til.Count; i++) + { + Cell cell = til[i]; + + double contentHeight = GetHeight(templateReusables, Control.ItemTemplate, cell); + + if ((ReferenceEquals(til.BindingContext, e.Group) || e.Group == null) && ReferenceEquals(cell.BindingContext, e.Item)) + { + targetHeaderHeight = headerHeight; + targetHeight = contentHeight; + found = true; + break; + } + + y += contentHeight; + } + } + } + else + { + for (var i = 0; i < Element.TemplatedItems.Count; i++) + { + Cell cell = Element.TemplatedItems[i]; + + double height = GetHeight(templateReusables, Control.ItemTemplate, cell); + + if (ReferenceEquals(cell.BindingContext, e.Item)) + { + found = true; + targetHeight = height; + break; + } + + y += height; + } + } + + if (!found) + return; + + ScrollToPosition position = e.Position; + if (position == ScrollToPosition.MakeVisible) + { + if (y >= _viewport.Viewport.Top && y <= _viewport.Viewport.Bottom) + return; + if (y > _viewport.Viewport.Bottom) + position = ScrollToPosition.End; + else + position = ScrollToPosition.Start; + } + + if (position == ScrollToPosition.Start && Element.IsGroupingEnabled) + y = y - targetHeaderHeight; + else if (position == ScrollToPosition.Center) + y = y - (_viewport.ActualHeight / 2 + targetHeight / 2); + else if (position == ScrollToPosition.End) + y = y - _viewport.ActualHeight + targetHeight; + + double startY = _viewport.Viewport.Y; + double distance = y - startY; + + if (e.ShouldAnimate) + { + var animation = new Animation(v => { _viewport.SetViewportOrigin(new System.Windows.Point(0, startY + distance * v)); }); + + animation.Commit(_animatable, "ScrollTo", length: 500, easing: Easing.CubicInOut); + } + else + _viewport.SetViewportOrigin(new System.Windows.Point(0, y)); + } + + void RestorePreviousSelectedVisual() + { + foreach (Tuple<FrameworkElement, SLBinding, Brush> highlight in _previousHighlights) + { + if (highlight.Item2 != null) + highlight.Item1.SetForeground(highlight.Item2); + else + highlight.Item1.SetForeground(highlight.Item3); + } + + _previousHighlights.Clear(); + } + + void SetSelectedVisual(FrameworkElement element) + { + IEnumerable<FrameworkElement> highlightMes = FindHighlight(element); + foreach (FrameworkElement toHighlight in highlightMes) + { + Brush brush = null; + SLBinding binding = toHighlight.GetForegroundBinding(); + if (binding == null) + brush = toHighlight.GetForeground(); + + _previousHighlights.Add(new Tuple<FrameworkElement, SLBinding, Brush>(toHighlight, binding, brush)); + toHighlight.SetForeground((Brush)System.Windows.Application.Current.Resources["PhoneAccentBrush"]); + } + } + + void UpdateFooter() + { + Control.ListFooter = ((IListViewController)Element).FooterElement; + } + + void UpdateHeader() + { + Control.ListHeader = ((IListViewController)Element).HeaderElement; + } + + void UpdateIsRefreshing() + { + if (Element.IsRefreshing) + { + _progressBar.Visibility = Visibility.Visible; + _progressBar.IsIndeterminate = true; + } + else + { + _progressBar.IsIndeterminate = false; + _progressBar.Visibility = _listBox.IsInPullToRefresh && Element.IsPullToRefreshEnabled && ((IListViewController)Element).RefreshAllowed ? Visibility.Visible : Visibility.Collapsed; + } + } + + void UpdateJumpList() + { + if (_listBox.IsGroupingEnabled && Element.GroupShortNameBinding == null) + _listBox.JumpListStyle = null; + else + _listBox.JumpListStyle = (System.Windows.Style)System.Windows.Application.Current.Resources["HeaderJumpStyle"]; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/MD5.cs b/Xamarin.Forms.Platform.WP8/MD5.cs new file mode 100644 index 00000000..2c0fc206 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/MD5.cs @@ -0,0 +1,12 @@ +using System.Security.Cryptography; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal abstract class MD5 : HashAlgorithm + { + public MD5() + { + HashSizeValue = 128; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/MD5CryptoServiceProvider.cs b/Xamarin.Forms.Platform.WP8/MD5CryptoServiceProvider.cs new file mode 100644 index 00000000..aa5eba7f --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/MD5CryptoServiceProvider.cs @@ -0,0 +1,444 @@ +using System; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal sealed class MD5CryptoServiceProvider : MD5 + { + const int BlockSizeBytes = 64; + + static readonly uint[] K = + { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, + 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, + 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, + 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, + 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + uint[] _buff; + ulong _count; + uint[] _h; + byte[] _processingBuffer; // Used to start data when passed less than a block worth. + int _processingBufferCount; // Counts how much data we have stored that still needs processed. + + public MD5CryptoServiceProvider() + { + _h = new uint[4]; + _buff = new uint[16]; + _processingBuffer = new byte[BlockSizeBytes]; + + Initialize(); + } + + public override void Initialize() + { + _count = 0; + _processingBufferCount = 0; + + _h[0] = 0x67452301; + _h[1] = 0xefcdab89; + _h[2] = 0x98badcfe; + _h[3] = 0x10325476; + } + + protected override void Dispose(bool disposing) + { + if (_processingBuffer != null) + { + Array.Clear(_processingBuffer, 0, _processingBuffer.Length); + _processingBuffer = null; + } + if (_h != null) + { + Array.Clear(_h, 0, _h.Length); + _h = null; + } + if (_buff != null) + { + Array.Clear(_buff, 0, _buff.Length); + _buff = null; + } + } + + protected override void HashCore(byte[] rgb, int ibStart, int cbSize) + { + int i; + State = 1; + + if (_processingBufferCount != 0) + { + if (cbSize < BlockSizeBytes - _processingBufferCount) + { + Buffer.BlockCopy(rgb, ibStart, _processingBuffer, _processingBufferCount, cbSize); + _processingBufferCount += cbSize; + return; + } + i = BlockSizeBytes - _processingBufferCount; + Buffer.BlockCopy(rgb, ibStart, _processingBuffer, _processingBufferCount, i); + ProcessBlock(_processingBuffer, 0); + _processingBufferCount = 0; + ibStart += i; + cbSize -= i; + } + + for (i = 0; i < cbSize - cbSize % BlockSizeBytes; i += BlockSizeBytes) + ProcessBlock(rgb, ibStart + i); + + if (cbSize % BlockSizeBytes != 0) + { + Buffer.BlockCopy(rgb, cbSize - cbSize % BlockSizeBytes + ibStart, _processingBuffer, 0, cbSize % BlockSizeBytes); + _processingBufferCount = cbSize % BlockSizeBytes; + } + } + + protected override byte[] HashFinal() + { + var hash = new byte[16]; + int i, j; + + ProcessFinalBlock(_processingBuffer, 0, _processingBufferCount); + + for (i = 0; i < 4; i++) + { + for (j = 0; j < 4; j++) + hash[i * 4 + j] = (byte)(_h[i] >> j * 8); + } + + return hash; + } + + internal void AddLength(ulong length, byte[] buffer, int position) + { + buffer[position++] = (byte)length; + buffer[position++] = (byte)(length >> 8); + buffer[position++] = (byte)(length >> 16); + buffer[position++] = (byte)(length >> 24); + buffer[position++] = (byte)(length >> 32); + buffer[position++] = (byte)(length >> 40); + buffer[position++] = (byte)(length >> 48); + buffer[position] = (byte)(length >> 56); + } + + void ProcessBlock(byte[] inputBuffer, int inputOffset) + { + uint a, b, c, d; + int i; + + _count += BlockSizeBytes; + + for (i = 0; i < 16; i++) + { + _buff[i] = inputBuffer[inputOffset + 4 * i] | ((uint)inputBuffer[inputOffset + 4 * i + 1] << 8) | ((uint)inputBuffer[inputOffset + 4 * i + 2] << 16) | + ((uint)inputBuffer[inputOffset + 4 * i + 3] << 24); + } + + a = _h[0]; + b = _h[1]; + c = _h[2]; + d = _h[3]; + + // This function was unrolled because it seems to be doubling our performance with current compiler/VM. + // Possibly roll up if this changes. + + // ---- Round 1 -------- + + a += (((c ^ d) & b) ^ d) + K[0] + _buff[0]; + a = (a << 7) | (a >> 25); + a += b; + + d += (((b ^ c) & a) ^ c) + K[1] + _buff[1]; + d = (d << 12) | (d >> 20); + d += a; + + c += (((a ^ b) & d) ^ b) + K[2] + _buff[2]; + c = (c << 17) | (c >> 15); + c += d; + + b += (((d ^ a) & c) ^ a) + K[3] + _buff[3]; + b = (b << 22) | (b >> 10); + b += c; + + a += (((c ^ d) & b) ^ d) + K[4] + _buff[4]; + a = (a << 7) | (a >> 25); + a += b; + + d += (((b ^ c) & a) ^ c) + K[5] + _buff[5]; + d = (d << 12) | (d >> 20); + d += a; + + c += (((a ^ b) & d) ^ b) + K[6] + _buff[6]; + c = (c << 17) | (c >> 15); + c += d; + + b += (((d ^ a) & c) ^ a) + K[7] + _buff[7]; + b = (b << 22) | (b >> 10); + b += c; + + a += (((c ^ d) & b) ^ d) + K[8] + _buff[8]; + a = (a << 7) | (a >> 25); + a += b; + + d += (((b ^ c) & a) ^ c) + K[9] + _buff[9]; + d = (d << 12) | (d >> 20); + d += a; + + c += (((a ^ b) & d) ^ b) + K[10] + _buff[10]; + c = (c << 17) | (c >> 15); + c += d; + + b += (((d ^ a) & c) ^ a) + K[11] + _buff[11]; + b = (b << 22) | (b >> 10); + b += c; + + a += (((c ^ d) & b) ^ d) + K[12] + _buff[12]; + a = (a << 7) | (a >> 25); + a += b; + + d += (((b ^ c) & a) ^ c) + K[13] + _buff[13]; + d = (d << 12) | (d >> 20); + d += a; + + c += (((a ^ b) & d) ^ b) + K[14] + _buff[14]; + c = (c << 17) | (c >> 15); + c += d; + + b += (((d ^ a) & c) ^ a) + K[15] + _buff[15]; + b = (b << 22) | (b >> 10); + b += c; + + // ---- Round 2 -------- + + a += (((b ^ c) & d) ^ c) + K[16] + _buff[1]; + a = (a << 5) | (a >> 27); + a += b; + + d += (((a ^ b) & c) ^ b) + K[17] + _buff[6]; + d = (d << 9) | (d >> 23); + d += a; + + c += (((d ^ a) & b) ^ a) + K[18] + _buff[11]; + c = (c << 14) | (c >> 18); + c += d; + + b += (((c ^ d) & a) ^ d) + K[19] + _buff[0]; + b = (b << 20) | (b >> 12); + b += c; + + a += (((b ^ c) & d) ^ c) + K[20] + _buff[5]; + a = (a << 5) | (a >> 27); + a += b; + + d += (((a ^ b) & c) ^ b) + K[21] + _buff[10]; + d = (d << 9) | (d >> 23); + d += a; + + c += (((d ^ a) & b) ^ a) + K[22] + _buff[15]; + c = (c << 14) | (c >> 18); + c += d; + + b += (((c ^ d) & a) ^ d) + K[23] + _buff[4]; + b = (b << 20) | (b >> 12); + b += c; + + a += (((b ^ c) & d) ^ c) + K[24] + _buff[9]; + a = (a << 5) | (a >> 27); + a += b; + + d += (((a ^ b) & c) ^ b) + K[25] + _buff[14]; + d = (d << 9) | (d >> 23); + d += a; + + c += (((d ^ a) & b) ^ a) + K[26] + _buff[3]; + c = (c << 14) | (c >> 18); + c += d; + + b += (((c ^ d) & a) ^ d) + K[27] + _buff[8]; + b = (b << 20) | (b >> 12); + b += c; + + a += (((b ^ c) & d) ^ c) + K[28] + _buff[13]; + a = (a << 5) | (a >> 27); + a += b; + + d += (((a ^ b) & c) ^ b) + K[29] + _buff[2]; + d = (d << 9) | (d >> 23); + d += a; + + c += (((d ^ a) & b) ^ a) + K[30] + _buff[7]; + c = (c << 14) | (c >> 18); + c += d; + + b += (((c ^ d) & a) ^ d) + K[31] + _buff[12]; + b = (b << 20) | (b >> 12); + b += c; + + // ---- Round 3 -------- + + a += (b ^ c ^ d) + K[32] + _buff[5]; + a = (a << 4) | (a >> 28); + a += b; + + d += (a ^ b ^ c) + K[33] + _buff[8]; + d = (d << 11) | (d >> 21); + d += a; + + c += (d ^ a ^ b) + K[34] + _buff[11]; + c = (c << 16) | (c >> 16); + c += d; + + b += (c ^ d ^ a) + K[35] + _buff[14]; + b = (b << 23) | (b >> 9); + b += c; + + a += (b ^ c ^ d) + K[36] + _buff[1]; + a = (a << 4) | (a >> 28); + a += b; + + d += (a ^ b ^ c) + K[37] + _buff[4]; + d = (d << 11) | (d >> 21); + d += a; + + c += (d ^ a ^ b) + K[38] + _buff[7]; + c = (c << 16) | (c >> 16); + c += d; + + b += (c ^ d ^ a) + K[39] + _buff[10]; + b = (b << 23) | (b >> 9); + b += c; + + a += (b ^ c ^ d) + K[40] + _buff[13]; + a = (a << 4) | (a >> 28); + a += b; + + d += (a ^ b ^ c) + K[41] + _buff[0]; + d = (d << 11) | (d >> 21); + d += a; + + c += (d ^ a ^ b) + K[42] + _buff[3]; + c = (c << 16) | (c >> 16); + c += d; + + b += (c ^ d ^ a) + K[43] + _buff[6]; + b = (b << 23) | (b >> 9); + b += c; + + a += (b ^ c ^ d) + K[44] + _buff[9]; + a = (a << 4) | (a >> 28); + a += b; + + d += (a ^ b ^ c) + K[45] + _buff[12]; + d = (d << 11) | (d >> 21); + d += a; + + c += (d ^ a ^ b) + K[46] + _buff[15]; + c = (c << 16) | (c >> 16); + c += d; + + b += (c ^ d ^ a) + K[47] + _buff[2]; + b = (b << 23) | (b >> 9); + b += c; + + // ---- Round 4 -------- + + a += ((~d | b) ^ c) + K[48] + _buff[0]; + a = (a << 6) | (a >> 26); + a += b; + + d += ((~c | a) ^ b) + K[49] + _buff[7]; + d = (d << 10) | (d >> 22); + d += a; + + c += ((~b | d) ^ a) + K[50] + _buff[14]; + c = (c << 15) | (c >> 17); + c += d; + + b += ((~a | c) ^ d) + K[51] + _buff[5]; + b = (b << 21) | (b >> 11); + b += c; + + a += ((~d | b) ^ c) + K[52] + _buff[12]; + a = (a << 6) | (a >> 26); + a += b; + + d += ((~c | a) ^ b) + K[53] + _buff[3]; + d = (d << 10) | (d >> 22); + d += a; + + c += ((~b | d) ^ a) + K[54] + _buff[10]; + c = (c << 15) | (c >> 17); + c += d; + + b += ((~a | c) ^ d) + K[55] + _buff[1]; + b = (b << 21) | (b >> 11); + b += c; + + a += ((~d | b) ^ c) + K[56] + _buff[8]; + a = (a << 6) | (a >> 26); + a += b; + + d += ((~c | a) ^ b) + K[57] + _buff[15]; + d = (d << 10) | (d >> 22); + d += a; + + c += ((~b | d) ^ a) + K[58] + _buff[6]; + c = (c << 15) | (c >> 17); + c += d; + + b += ((~a | c) ^ d) + K[59] + _buff[13]; + b = (b << 21) | (b >> 11); + b += c; + + a += ((~d | b) ^ c) + K[60] + _buff[4]; + a = (a << 6) | (a >> 26); + a += b; + + d += ((~c | a) ^ b) + K[61] + _buff[11]; + d = (d << 10) | (d >> 22); + d += a; + + c += ((~b | d) ^ a) + K[62] + _buff[2]; + c = (c << 15) | (c >> 17); + c += d; + + b += ((~a | c) ^ d) + K[63] + _buff[9]; + b = (b << 21) | (b >> 11); + b += c; + + _h[0] += a; + _h[1] += b; + _h[2] += c; + _h[3] += d; + } + + void ProcessFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) + { + ulong total = _count + (ulong)inputCount; + var paddingSize = (int)(56 - total % BlockSizeBytes); + + if (paddingSize < 1) + paddingSize += BlockSizeBytes; + + var fooBuffer = new byte[inputCount + paddingSize + 8]; + + for (var i = 0; i < inputCount; i++) + fooBuffer[i] = inputBuffer[i + inputOffset]; + + fooBuffer[inputCount] = 0x80; + for (int i = inputCount + 1; i < inputCount + paddingSize; i++) + fooBuffer[i] = 0x00; + + // I deal in bytes. The algorithm deals in bits. + ulong size = total << 3; + AddLength(size, fooBuffer, inputCount + paddingSize); + ProcessBlock(fooBuffer, 0); + + if (inputCount + paddingSize + 8 == 128) + ProcessBlock(fooBuffer, 64); + } + + ~MD5CryptoServiceProvider() + { + Dispose(false); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/MasterDetailRenderer.cs b/Xamarin.Forms.Platform.WP8/MasterDetailRenderer.cs new file mode 100644 index 00000000..43796d92 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/MasterDetailRenderer.cs @@ -0,0 +1,183 @@ +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class MasterDetailRenderer : VisualElementRenderer<MasterDetailPage, FrameworkElement> + { + readonly SlideTransition _inTransition = new SlideTransition { Mode = SlideTransitionMode.SlideUpFadeIn }; + readonly SlideTransition _outTransition = new SlideTransition { Mode = SlideTransitionMode.SlideDownFadeOut }; + readonly Border _popup = new Border(); + IVisualElementRenderer _detailRenderer; + IVisualElementRenderer _masterRenderer; + + ITransition _toggleTransition; + + public MasterDetailRenderer() + { + AutoPackage = false; + } + + public bool Visible { get; private set; } + + protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) + { + UpdateSizes(finalSize.Width, finalSize.Height); + return base.ArrangeOverride(finalSize); + } + + protected override void OnElementChanged(ElementChangedEventArgs<MasterDetailPage> e) + { + base.OnElementChanged(e); + + if (e.OldElement != null) + e.OldElement.BackButtonPressed -= HandleBackButtonPressed; + + if (e.NewElement != null) + e.NewElement.BackButtonPressed += HandleBackButtonPressed; + + LoadDetail(); + LoadMaster(); + + UpdateSizes(ActualWidth, ActualHeight); + + Loaded += (sender, args) => + { + if (Element.IsPresented) + Toggle(); + Element.SendAppearing(); + }; + Unloaded += (sender, args) => + { + Element.SendDisappearing(); + if (Visible) + { + var platform = (Platform)Element.Platform; + Canvas container = platform.GetCanvas(); + + container.Children.Remove(_popup); + } + }; + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == "Detail") + { + LoadDetail(); + UpdateSizes(ActualWidth, ActualHeight); + } + else if (e.PropertyName == "Master") + { + LoadMaster(); + UpdateSizes(ActualWidth, ActualHeight); + } + else if (e.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName) + { + if (Visible == Element.IsPresented) + return; + Toggle(); + } + } + + internal void Toggle() + { + var platform = Element.Platform as Platform; + Canvas container = platform.GetCanvas(); + + if (_toggleTransition != null) + return; + + if (Visible) + { + _toggleTransition = _outTransition.GetTransition(_popup); + _toggleTransition.Begin(); + _toggleTransition.Completed += (sender, args) => + { + _toggleTransition.Stop(); + container.Children.Remove(_popup); + _toggleTransition = null; + }; + } + else + { + _popup.Child = _masterRenderer.ContainerElement; + container.Children.Add(_popup); + + _toggleTransition = _inTransition.GetTransition(_popup); + _toggleTransition.Begin(); + + _toggleTransition.Completed += (sender, args) => + { + _toggleTransition.Stop(); + _toggleTransition = null; + }; + } + + Visible = !Visible; + + ((IElementController)Element).SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, Visible); + } + + void HandleBackButtonPressed(object sender, BackButtonPressedEventArgs e) + { + if (Visible) + { + Toggle(); + e.Handled = true; + } + } + + void LoadDetail() + { + if (_detailRenderer != null) + Children.Remove(_detailRenderer.ContainerElement); + + Page detail = Element.Detail; + if (Platform.GetRenderer(detail) == null) + Platform.SetRenderer(detail, Platform.CreateRenderer(detail)); + + _detailRenderer = Platform.GetRenderer(detail); + + Children.Clear(); + if (_detailRenderer != null) + Children.Add(_detailRenderer.ContainerElement); + } + + void LoadMaster() + { + if (_masterRenderer != null && _popup != null) + _popup.Child = null; + + Page master = Element.Master; + if (Platform.GetRenderer(master) == null) + Platform.SetRenderer(master, Platform.CreateRenderer(master)); + + _masterRenderer = Platform.GetRenderer(master); + var control = _masterRenderer as Panel; + if (control != null && master.BackgroundColor == Color.Default) + control.Background = Color.Black.ToBrush(); + } + + void UpdateSizes(double width, double height) + { + if (width <= 0 || height <= 0) + return; + + var platform = Element.Platform as Platform; + Size screenSize = platform.Size; + Element.MasterBounds = new Rectangle(0, 0, screenSize.Width - 20, screenSize.Height - 20); + Element.DetailBounds = new Rectangle(0, 0, width, height); + + _popup.Width = width - 20; + _popup.Height = height - 20; + + Canvas.SetLeft(_popup, 10); + Canvas.SetTop(_popup, 10); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/NativeViewWrapper.cs b/Xamarin.Forms.Platform.WP8/NativeViewWrapper.cs new file mode 100644 index 00000000..a28d0c9a --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/NativeViewWrapper.cs @@ -0,0 +1,24 @@ +using System.Windows; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class NativeViewWrapper : View + { + public NativeViewWrapper(FrameworkElement nativeElement, GetDesiredSizeDelegate getDesiredSizeDelegate = null, ArrangeOverrideDelegate arrangeOverrideDelegate = null, + MeasureOverrideDelegate measureOverrideDelegate = null) + { + GetDesiredSizeDelegate = getDesiredSizeDelegate; + ArrangeOverrideDelegate = arrangeOverrideDelegate; + MeasureOverrideDelegate = measureOverrideDelegate; + NativeElement = nativeElement; + } + + public ArrangeOverrideDelegate ArrangeOverrideDelegate { get; set; } + + public GetDesiredSizeDelegate GetDesiredSizeDelegate { get; } + + public MeasureOverrideDelegate MeasureOverrideDelegate { get; set; } + + public FrameworkElement NativeElement { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/NativeViewWrapperRenderer.cs b/Xamarin.Forms.Platform.WP8/NativeViewWrapperRenderer.cs new file mode 100644 index 00000000..e28dec94 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/NativeViewWrapperRenderer.cs @@ -0,0 +1,62 @@ +using System.Windows; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class NativeViewWrapperRenderer : ViewRenderer<NativeViewWrapper, FrameworkElement> + { + public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + if (Element?.GetDesiredSizeDelegate == null) + return base.GetDesiredSize(widthConstraint, heightConstraint); + + // The user has specified a different implementation of GetDesiredSize + SizeRequest? result = Element.GetDesiredSizeDelegate(this, widthConstraint, heightConstraint); + + // If the delegate returns a SizeRequest, we use it; + // if it returns null, fall back to the default implementation + return result ?? base.GetDesiredSize(widthConstraint, heightConstraint); + } + + protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) + { + if (Element?.ArrangeOverrideDelegate == null) + return base.ArrangeOverride(finalSize); + + // The user has specified a different implementation of ArrangeOverride + System.Windows.Size? result = Element.ArrangeOverrideDelegate(this, finalSize); + + // If the delegate returns a Size, we use it; + // if it returns null, fall back to the default implementation + return result ?? base.ArrangeOverride(finalSize); + } + + protected System.Windows.Size MeasureOverride() + { + return MeasureOverride(new System.Windows.Size()); + } + + protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) + { + if (Element?.MeasureOverrideDelegate == null) + return base.MeasureOverride(availableSize); + + // The user has specified a different implementation of MeasureOverride + System.Windows.Size? result = Element.MeasureOverrideDelegate(this, availableSize); + + // If the delegate returns a Size, we use it; + // if it returns null, fall back to the default implementation + return result ?? base.MeasureOverride(availableSize); + } + + protected override void OnElementChanged(ElementChangedEventArgs<NativeViewWrapper> e) + { + base.OnElementChanged(e); + + if (e.OldElement == null) + { + SetNativeControl(Element.NativeElement); + Control.LayoutUpdated += (sender, args) => { Element?.InvalidateMeasure(InvalidationTrigger.MeasureChanged); }; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/NavigationMenuRenderer.cs b/Xamarin.Forms.Platform.WP8/NavigationMenuRenderer.cs new file mode 100644 index 00000000..c5a983d3 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/NavigationMenuRenderer.cs @@ -0,0 +1,85 @@ +using System; +using System.ComponentModel; +using System.Windows.Media.Imaging; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal class NavigationMenuRenderer : ViewRenderer<NavigationMenu, System.Windows.Controls.Grid> + { + const int Spacing = 12; + + protected override void OnElementChanged(ElementChangedEventArgs<NavigationMenu> e) + { + base.OnElementChanged(e); + + var grid = new System.Windows.Controls.Grid(); + grid.ColumnDefinitions.Add(new System.Windows.Controls.ColumnDefinition { Width = System.Windows.GridLength.Auto }); + grid.ColumnDefinitions.Add(new System.Windows.Controls.ColumnDefinition { Width = System.Windows.GridLength.Auto }); + + UpdateItems(grid); + SetNativeControl(grid); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + switch (e.PropertyName) + { + case "Targets": + UpdateItems(Control); + break; + } + } + + TileSize GetSize() + { + return RenderSize.Width >= 210 * 2 + Spacing ? TileSize.Medium : TileSize.Default; + } + + void UpdateItems(System.Windows.Controls.Grid grid) + { + grid.Children.Clear(); + + grid.RowDefinitions.Clear(); + + var x = 0; + var y = 0; + foreach (Page target in Element.Targets) + { + if (x > 1) + { + x = 0; + y++; + } + + if (x == 0) + grid.RowDefinitions.Add(new System.Windows.Controls.RowDefinition()); + + var hubTile = new HubTile { Title = target.Title, Source = new BitmapImage(new Uri(target.Icon, UriKind.Relative)), Margin = new System.Windows.Thickness(0, 0, Spacing, Spacing) }; + + if (target.BackgroundColor != Color.Default) + hubTile.Background = target.BackgroundColor.ToBrush(); + + Page tmp = target; + hubTile.Tap += (sender, args) => Element.SendTargetSelected(tmp); + + hubTile.SetValue(System.Windows.Controls.Grid.RowProperty, y); + hubTile.SetValue(System.Windows.Controls.Grid.ColumnProperty, x); + hubTile.Size = GetSize(); + + var weakRef = new WeakReference(hubTile); + SizeChanged += (sender, args) => + { + if (weakRef.IsAlive) + ((HubTile)weakRef.Target).Size = GetSize(); + ((IVisualElementController)Element).NativeSizeChanged(); + }; + + x++; + grid.Children.Add(hubTile); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.WP8/NavigationPageRenderer.cs new file mode 100644 index 00000000..6f290107 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/NavigationPageRenderer.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class NavigationPageRenderer : VisualElementRenderer<NavigationPage, FrameworkElement> + { + Page _currentRoot; + bool _isRemoving; + + public NavigationPageRenderer() + { + AutoPackage = false; + } + + protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e) + { + base.OnElementChanged(e); + + Debug.WriteLine("Warning, Windows Phone backend does not support NavigationPage, falling back to global navigation."); + + Action init = () => + { + Element.PushRequested += PageOnPushed; + Element.PopRequested += PageOnPopped; + Element.PopToRootRequested += PageOnPoppedToRoot; + Element.RemovePageRequested += RemovePageRequested; + Element.InsertPageBeforeRequested += ElementOnInsertPageBeforeRequested; + Element.PropertyChanged += OnElementPropertyChanged; + + var platform = (Platform)Element.Platform; + Element.ContainerArea = new Rectangle(new Point(0, 0), platform.Size); + + platform.SizeChanged += (sender, args) => Element.ContainerArea = new Rectangle(new Point(0, 0), platform.Size); + + List<Page> stack = GetStack(); + if (stack.Count > 0) + UpdateRootPage(stack); + else + return; + + Device.BeginInvokeOnMainThread(() => + { + for (var i = 0; i < stack.Count; i++) + PageOnPushed(this, new NavigationRequestedEventArgs(stack[i], false, i != 0)); + }); + }; + + if (Element.Platform == null) + Element.PlatformSet += (sender, args) => init(); + else + init(); + + Loaded += (sender, args) => Element.SendAppearing(); + Unloaded += OnUnloaded; + } + + void ElementOnInsertPageBeforeRequested(object sender, NavigationRequestedEventArgs eventArgs) + { + if (Element.Platform == null) + return; + var platform = Element.Platform as Platform; + if (platform != null) + ((INavigation)platform).InsertPageBefore(eventArgs.Page, eventArgs.BeforePage); + + List<Page> stack = GetStack(); + stack.Insert(stack.IndexOf(eventArgs.BeforePage), eventArgs.Page); + + UpdateRootPage(stack); + } + + List<Page> GetStack() + { + int count = Element.InternalChildren.Count; + var stack = new List<Page>(count); + for (var i = 0; i < count; i++) + stack.Add((Page)Element.InternalChildren[i]); + + return stack; + } + + void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName != "Parent" || Element.RealParent != null) + return; + + var platform = Element.Platform as Platform; + + if (platform == null) + return; + + for (var i = 0; i < Element.LogicalChildren.Count; i++) + { + var page = Element.LogicalChildren[i] as Page; + if (page != null) + platform.RemovePage(page, false); + } + } + + void OnUnloaded(object sender, RoutedEventArgs args) + { + Element.SendDisappearing(); + } + + void PageOnPopped(object sender, NavigationRequestedEventArgs eventArg) + { + if (Element.Platform == null) + return; + var platform = Element.Platform as Platform; + if (platform != null) + eventArg.Task = platform.Pop(Element, eventArg.Animated).ContinueWith((t, o) => true, null); + } + + void PageOnPoppedToRoot(object sender, NavigationRequestedEventArgs eventArgs) + { + if (Element.Platform == null) + return; + var platform = Element.Platform as Platform; + if (platform != null) + eventArgs.Task = platform.PopToRoot(Element, eventArgs.Animated).ContinueWith((t, o) => true, null); + } + + void PageOnPushed(object sender, NavigationRequestedEventArgs e) + { + if (Element.Platform == null) + return; + var platform = Element.Platform as Platform; + if (platform != null) + { + if (e.Page == Element.StackCopy.LastOrDefault()) + e.Page.IgnoresContainerArea = true; + e.Task = platform.PushCore(e.Page, Element, e.Animated, e.Realize).ContinueWith((t, o) => true, null); + } + } + + void RemovePageRequested(object sender, NavigationRequestedEventArgs eventArgs) + { + if (Element.Platform == null) + return; + var platform = Element.Platform as Platform; + if (platform != null) + ((INavigation)platform).RemovePage(eventArgs.Page); + + List<Page> stack = GetStack(); + stack.Remove(eventArgs.Page); + _isRemoving = true; + UpdateRootPage(stack); + _isRemoving = false; + } + + void UpdateRootPage(IReadOnlyList<Page> stack) + { + Page first = stack.FirstOrDefault(); + if (first == _currentRoot) + return; + + if (Children.Count > 0) + { + var renderer = Children[0] as IVisualElementRenderer; + if (renderer != null) + { + Children.RemoveAt(0); + + var page = renderer.Element as Page; + if (page != null) + page.IgnoresContainerArea = false; + + if (!stack.Contains(renderer.Element)) + Platform.SetRenderer(renderer.Element, null); + } + } + + _currentRoot = first; + + if (first == null) + return; + + first.IgnoresContainerArea = true; + + IVisualElementRenderer firstRenderer = Platform.GetRenderer(first); + if (firstRenderer == null) + { + firstRenderer = Platform.CreateRenderer(first); + Platform.SetRenderer(first, firstRenderer); + } + var uiElement = (UIElement)firstRenderer; + var platform = Element.Platform as Platform; + Canvas canvas = platform?.GetCanvas(); + + //We could be swapping the visible page, + //so let's make sure we remove it + if (canvas.Children.Contains(uiElement)) + canvas.Children.Remove(uiElement); + Children.Add(uiElement); + + // we removed the previous root page, and the new root page is the one being presented + // at this time there's only 1 page now on the stack (the navigationpage with root) + // we need to update the platform to set this root page as the visible again + bool updateRoot = Element.CurrentPage == first && _isRemoving; + if (updateRoot) + platform.SetCurrent(Element, false); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/PageRenderer.cs b/Xamarin.Forms.Platform.WP8/PageRenderer.cs new file mode 100644 index 00000000..6ba89ab5 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/PageRenderer.cs @@ -0,0 +1,18 @@ +using System.Windows.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class PageRenderer : VisualElementRenderer<Page, Panel> + { + protected override void OnElementChanged(ElementChangedEventArgs<Page> e) + { + // Set prior to calling base + Tracker = new BackgroundTracker<Panel>(BackgroundProperty) { Model = Element, Element = this }; + + base.OnElementChanged(e); + + Loaded += (sender, args) => Element.SendAppearing(); + Unloaded += (sender, args) => Element.SendDisappearing(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/PageToRendererConverter.cs b/Xamarin.Forms.Platform.WP8/PageToRendererConverter.cs new file mode 100644 index 00000000..2c809c58 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/PageToRendererConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class PageToRendererConverter : System.Windows.Data.IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var page = value as Page; + if (page == null) + return null; + + IVisualElementRenderer renderer = Platform.CreateRenderer(page); + Platform.SetRenderer(page, renderer); + return renderer; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/PickerRenderer.cs b/Xamarin.Forms.Platform.WP8/PickerRenderer.cs new file mode 100644 index 00000000..f9cd2d81 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/PickerRenderer.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class PickerRenderer : ViewRenderer<Picker, FrameworkElement> + { + bool _isChanging; + + FormsListPicker _listPicker; + + protected override void OnElementChanged(ElementChangedEventArgs<Picker> e) + { + _listPicker = new FormsListPicker(); + + UpdateAlignment(); + UpdateIsEnabled(); + + base.OnElementChanged(e); + + if (e.OldElement != null) + ((ObservableList<string>)Element.Items).CollectionChanged -= ItemsCollectionChanged; + + ((ObservableList<string>)Element.Items).CollectionChanged += ItemsCollectionChanged; + + _listPicker.ItemTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["PickerItemTemplate"]; + _listPicker.FullModeItemTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["PickerFullItemTemplate"]; + _listPicker.ExpansionMode = ExpansionMode.FullScreenOnly; + _listPicker.Items.Add(new ItemViewModel(" ") { MaxHeight = 0 }); + + _listPicker.ListPickerModeChanged += ListPickerModeChanged; + + var grid = new System.Windows.Controls.Grid { Children = { _listPicker }, MaxWidth = Device.Info.PixelScreenSize.Width }; + SetNativeControl(grid); + + UpdatePicker(); + _listPicker.SelectionChanged += PickerSelectionChanged; + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + if (e.PropertyName == Picker.TitleProperty.PropertyName) + _listPicker.FullModeHeader = Element.Title; + + if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + UpdateIsEnabled(); + + if (e.PropertyName == Picker.SelectedIndexProperty.PropertyName) + { + if (Element.SelectedIndex >= 0 && Element.SelectedIndex < Element.Items.Count) + _listPicker.SelectedIndex = Element.SelectedIndex + 1; + } + + if (e.PropertyName == View.HorizontalOptionsProperty.PropertyName) + UpdateAlignment(); + } + + protected override void OnGotFocus(object sender, RoutedEventArgs args) + { + // Do nothing. ListPickerModeChanged is handling the IsFocusProperty setter + // Required because FrameworkElement.GotFocus and FrameworkElement.LostFocus () are fired by ListPicker.Open () + } + + protected override void OnLostFocus(object sender, RoutedEventArgs args) + { + // Do nothing. ListPickerModeChanged is handling the IsFocusProperty setter + // Required because FrameworkElement.GotFocus and FrameworkElement.LostFocus () are fired by ListPicker.Open () + } + + protected override void UpdateNativeWidget() + { + base.UpdateNativeWidget(); + UpdateIsEnabled(); + } + + internal override void OnModelFocusChangeRequested(object sender, VisualElement.FocusRequestArgs args) + { + if (Control == null) + return; + + if (args.Focus) + args.Result = OpenPickerPage(); + else + { + args.Result = ClosePickerPage(); + UnfocusControl(_listPicker); + } + } + + bool ClosePickerPage() + { + FieldInfo pickerPageField = typeof(ListPicker).GetField("_listPickerPage", BindingFlags.NonPublic | BindingFlags.Instance); + var pickerPage = pickerPageField.GetValue(Control) as ListPickerPage; + typeof(ListPickerPage).InvokeMember("ClosePickerPage", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, pickerPage, null); + + return true; + } + + void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + UpdateItems(); + } + + void ListPickerModeChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (e.OldValue == null || e.NewValue == null) + return; + + var oldVal = (ListPickerMode)e.OldValue; + var newVal = (ListPickerMode)e.NewValue; + + if (oldVal == ListPickerMode.Normal && newVal == ListPickerMode.Full) + { + // Picker Page is now showing + ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true); + } + else if (oldVal == ListPickerMode.Full && newVal == ListPickerMode.Normal) + { + // PickerPage is now dismissed + ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false); + } + } + + bool OpenPickerPage() + { + bool result = _listPicker.Open(); + + if (result) + return true; + + return false; + } + + void PickerSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (_isChanging) + return; + + var picker = (ListPicker)sender; + + // initializing picker + if (picker.SelectedIndex == -1) + return; + + int elementSelectedIndex = picker.SelectedIndex - 1; + ((IElementController)Element).SetValueFromRenderer(Picker.SelectedIndexProperty, elementSelectedIndex); + } + + void UpdateAlignment() + { + if (Element.HorizontalOptions.Alignment != LayoutAlignment.Fill) + _listPicker.HorizontalAlignment = HorizontalAlignment.Left; + } + + void UpdateIsEnabled() + { + if (_listPicker != null) + _listPicker.IsEnabled = Element.IsEnabled; + } + + void UpdateItems() + { + // supress notification of non-user generated events (e.g. adding\syncing list values) + _isChanging = true; + FormsListPicker picker = _listPicker; + // add/remove slots from control to match element + while (picker.Items.Count < Element.Items.Count + 1) + picker.Items.Add(new ItemViewModel(string.Empty)); + + while (picker.Items.Count > Element.Items.Count + 1) + picker.Items.RemoveAt(picker.Items.Count - 1); + + // update all control values to match element values + for (var i = 0; i < Element.Items.Count; i++) + { + var item = (ItemViewModel)picker.Items[i + 1]; + if (item.Data == Element.Items[i]) + continue; + + item.Data = Element.Items[i]; + } + + picker.SelectedIndex = Element.SelectedIndex + 1; + + _isChanging = false; + } + + void UpdatePicker() + { + _listPicker.FullModeHeader = Element.Title; + UpdateItems(); + _listPicker.SelectedIndex = Element.SelectedIndex + 1; + } + + class ItemViewModel : INotifyPropertyChanged + { + string _data; + int _maxHeight; + float _opacity; + + public ItemViewModel(string item) + { + _opacity = 1; + _data = item; + _maxHeight = int.MaxValue; + } + + public string Data + { + get { return _data; } + set + { + if (value == _data) + return; + + _data = value; + PropertyChanged(this, new PropertyChangedEventArgs("Data")); + } + } + + public int MaxHeight + { + get { return _maxHeight; } + set + { + if (value == _maxHeight) + return; + + _maxHeight = value; + PropertyChanged(this, new PropertyChangedEventArgs("MaxHeight")); + } + } + + public float Opacity + { + get { return _opacity; } + set + { + if (value == _opacity) + return; + + _opacity = value; + PropertyChanged(this, new PropertyChangedEventArgs("Opacity")); + } + } + + public event PropertyChangedEventHandler PropertyChanged = delegate { }; + } + } +}
\ No newline at end of file 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 diff --git a/Xamarin.Forms.Platform.WP8/PlatformEffect.cs b/Xamarin.Forms.Platform.WP8/PlatformEffect.cs new file mode 100644 index 00000000..3ee0f416 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/PlatformEffect.cs @@ -0,0 +1,8 @@ +using System.Windows; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public abstract class PlatformEffect : PlatformEffect<FrameworkElement, FrameworkElement> + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ProgressBarRenderer.cs b/Xamarin.Forms.Platform.WP8/ProgressBarRenderer.cs new file mode 100644 index 00000000..b027d674 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ProgressBarRenderer.cs @@ -0,0 +1,35 @@ +using System.ComponentModel; +using System.Windows; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class ProgressBarRenderer : ViewRenderer<ProgressBar, System.Windows.Controls.ProgressBar> + { + protected override void OnElementChanged(ElementChangedEventArgs<ProgressBar> e) + { + base.OnElementChanged(e); + + var progressBar = new System.Windows.Controls.ProgressBar { Minimum = 0, Maximum = 1, Value = Element.Progress }; + progressBar.ValueChanged += ProgressBarOnValueChanged; + + SetNativeControl(progressBar); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + switch (e.PropertyName) + { + case "Progress": + Control.Value = Element.Progress; + break; + } + } + + void ProgressBarOnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> routedPropertyChangedEventArgs) + { + Element?.InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.WP8/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..f1be5df1 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Properties/AssemblyInfo.cs @@ -0,0 +1,78 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xamarin.Forms; +using Xamarin.Forms.Platform.WinPhone; +using TableView = Xamarin.Forms.TableView; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("Xamarin.Forms.Platform.WP8")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: Guid("50e5d817-4ac1-46c0-8e20-260fb6ad16af")] +[assembly: NeutralResourcesLanguage("en-US")] + +// View subclasses + +[assembly: ExportRenderer(typeof(BoxView), typeof(BoxViewRenderer))] +[assembly: ExportRenderer(typeof(Entry), typeof(EntryRenderer))] +[assembly: ExportRenderer(typeof(Editor), typeof(EditorRenderer))] +[assembly: ExportRenderer(typeof(Label), typeof(LabelRenderer))] +[assembly: ExportRenderer(typeof(Image), typeof(ImageRenderer))] +[assembly: ExportRenderer(typeof(Button), typeof(ButtonRenderer))] +[assembly: ExportRenderer(typeof(Slider), typeof(SliderRenderer))] +[assembly: ExportRenderer(typeof(WebView), typeof(WebViewRenderer))] +[assembly: ExportRenderer(typeof(SearchBar), typeof(SearchBarRenderer))] +[assembly: ExportRenderer(typeof(Switch), typeof(SwitchRenderer))] +[assembly: ExportRenderer(typeof(DatePicker), typeof(DatePickerRenderer))] +[assembly: ExportRenderer(typeof(TimePicker), typeof(TimePickerRenderer))] +[assembly: ExportRenderer(typeof(Picker), typeof(PickerRenderer))] +[assembly: ExportRenderer(typeof(Stepper), typeof(StepperRenderer))] +[assembly: ExportRenderer(typeof(ProgressBar), typeof(ProgressBarRenderer))] +[assembly: ExportRenderer(typeof(ScrollView), typeof(ScrollViewRenderer))] +[assembly: ExportRenderer(typeof(ActivityIndicator), typeof(ActivityIndicatorRenderer))] +[assembly: ExportRenderer(typeof(Frame), typeof(FrameRenderer))] +[assembly: ExportRenderer(typeof(NavigationMenu), typeof(NavigationMenuRenderer))] +[assembly: ExportRenderer(typeof(ListView), typeof(ListViewRenderer))] +[assembly: ExportRenderer(typeof(CarouselView), typeof(CarouselViewRenderer))] +[assembly: ExportRenderer(typeof(TableView), typeof(TableViewRenderer))] +[assembly: ExportRenderer(typeof(NativeViewWrapper), typeof(NativeViewWrapperRenderer))] + +// Form and subclasses + +[assembly: ExportRenderer(typeof(TabbedPage), typeof(TabbedPageRenderer))] +[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer))] +[assembly: ExportRenderer(typeof(CarouselPage), typeof(CarouselPageRenderer))] +[assembly: ExportRenderer(typeof(Page), typeof(PageRenderer))] +[assembly: ExportRenderer(typeof(MasterDetailPage), typeof(MasterDetailRenderer))] + +//ImageSources + +[assembly: ExportImageSourceHandler(typeof(FileImageSource), typeof(FileImageSourceHandler))] +[assembly: ExportImageSourceHandler(typeof(StreamImageSource), typeof(StreamImagesourceHandler))] +[assembly: ExportImageSourceHandler(typeof(UriImageSource), typeof(ImageLoaderSourceHandler))] + +// Cells + +[assembly: ExportCell(typeof(Cell), typeof(TextCellRenderer))] +[assembly: ExportCell(typeof(ImageCell), typeof(ImageCellRenderer))] +[assembly: ExportCell(typeof(EntryCell), typeof(EntryCellRenderer))] +[assembly: ExportCell(typeof(SwitchCell), typeof(SwitchCellRenderer))] +[assembly: ExportCell(typeof(ViewCell), typeof(ViewCellRenderer))] +[assembly: Dependency(typeof(Deserializer))] +[assembly: Dependency(typeof(ResourcesProvider))] +[assembly: InternalsVisibleTo("Xamarin.Forms.Core.WP8.UnitTests")]
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/RendererFactory.cs b/Xamarin.Forms.Platform.WP8/RendererFactory.cs new file mode 100644 index 00000000..33896df3 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/RendererFactory.cs @@ -0,0 +1,13 @@ +using System; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public static class RendererFactory + { + [Obsolete("Use Platform.CreateRenderer")] + public static IVisualElementRenderer GetRenderer(VisualElement view) + { + return Platform.CreateRenderer(view); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ResourcesProvider.cs b/Xamarin.Forms.Platform.WP8/ResourcesProvider.cs new file mode 100644 index 00000000..3eaf2a1f --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ResourcesProvider.cs @@ -0,0 +1,59 @@ +using System.Windows.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + internal class ResourcesProvider : ISystemResourcesProvider + { + ResourceDictionary _dictionary; + + public IResourceDictionary GetSystemResources() + { + _dictionary = new ResourceDictionary(); + + UpdateStyles(); + + return _dictionary; + } + + Style GetListItemDetailTextStyle() + { + var result = new Style(typeof(Label)); + + result.Setters.Add(new Setter { Property = Label.FontSizeProperty, Value = 32 }); + + return result; + } + + Style GetListItemTextStyle() + { + var result = new Style(typeof(Label)); + + result.Setters.Add(new Setter { Property = Label.FontSizeProperty, Value = 48 }); + + return result; + } + + Style GetStyle(System.Windows.Style style, TextBlock hackbox) + { + hackbox.Style = style; + + var result = new Style(typeof(Label)); + result.Setters.Add(new Setter { Property = Label.FontFamilyProperty, Value = hackbox.FontFamily }); + + result.Setters.Add(new Setter { Property = Label.FontSizeProperty, Value = hackbox.FontSize }); + + return result; + } + + void UpdateStyles() + { + var textBlock = new TextBlock(); + _dictionary[Device.Styles.TitleStyleKey] = GetStyle((System.Windows.Style)System.Windows.Application.Current.Resources["PhoneTextTitle1Style"], textBlock); + _dictionary[Device.Styles.SubtitleStyleKey] = GetStyle((System.Windows.Style)System.Windows.Application.Current.Resources["PhoneTextTitle2Style"], textBlock); + _dictionary[Device.Styles.BodyStyleKey] = GetStyle((System.Windows.Style)System.Windows.Application.Current.Resources["PhoneTextNormalStyle"], textBlock); + _dictionary[Device.Styles.CaptionStyleKey] = GetStyle((System.Windows.Style)System.Windows.Application.Current.Resources["PhoneTextSmallStyle"], textBlock); + _dictionary[Device.Styles.ListItemTextStyleKey] = GetListItemTextStyle(); + _dictionary[Device.Styles.ListItemDetailTextStyleKey] = GetListItemDetailTextStyle(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ScrollViewRenderer.cs b/Xamarin.Forms.Platform.WP8/ScrollViewRenderer.cs new file mode 100644 index 00000000..6e19d8c4 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ScrollViewRenderer.cs @@ -0,0 +1,194 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class ScrollViewRenderer : ViewRenderer<ScrollView, ScrollViewer> + { + Animatable _animatable; + + public ScrollViewRenderer() + { + AutoPackage = false; + } + + protected IScrollViewController Controller + { + get { return Element; } + } + + public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + SizeRequest result = base.GetDesiredSize(widthConstraint, heightConstraint); + result.Minimum = new Size(40, 40); + return result; + } + + protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) + { + if (Element == null) + return finalSize; + + Element.IsInNativeLayout = true; + + if (Control != null) + { + Control.Measure(finalSize); + Control.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); + } + + Element.IsInNativeLayout = false; + + return finalSize; + } + + protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) + { + if (Element == null) + return new System.Windows.Size(0, 0); + + double width = Math.Max(0, Element.Width); + double height = Math.Max(0, Element.Height); + return new System.Windows.Size(width, height); + } + + protected override void OnElementChanged(ElementChangedEventArgs<ScrollView> e) + { + base.OnElementChanged(e); + + if (e.OldElement != null) + ((IScrollViewController)e.OldElement).ScrollToRequested -= OnScrollToRequested; + + if (e.NewElement != null) + { + if (Control == null) + { + SetNativeControl(new ScrollViewer { ManipulationMode = ManipulationMode.Control }); + Control.LayoutUpdated += (sender, args) => { UpdateScrollPosition(); }; + } + ((IScrollViewController)e.NewElement).ScrollToRequested += OnScrollToRequested; + } + + SizeChanged += (sender, args) => + { + Control.Width = ActualWidth; + Control.Height = ActualHeight; + }; + + UpdateOrientation(); + + LoadContent(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == "Content") + LoadContent(); + else if (e.PropertyName == Layout.PaddingProperty.PropertyName) + UpdateMargins(); + else if (e.PropertyName == ScrollView.OrientationProperty.PropertyName) + UpdateOrientation(); + } + + static double GetDistance(double start, double position, double v) + { + return start + (position - start) * v; + } + + void LoadContent() + { + var lastContent = Control.Content as FrameworkElement; + if (lastContent != null) + lastContent.Margin = new System.Windows.Thickness(); //undo any damage we may have done to this renderer + + View view = Element.Content; + + if (view != null) + Platform.SetRenderer(view, Platform.CreateRenderer(view)); + + Control.Content = view != null ? Platform.GetRenderer(view) : null; + + UpdateMargins(); + } + + void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e) + { + if (_animatable == null && e.ShouldAnimate) + _animatable = new Animatable(); + + ScrollToPosition position = e.Position; + double x = e.ScrollX; + double y = e.ScrollY; + + if (e.Mode == ScrollToMode.Element) + { + Point itemPosition = Controller.GetScrollPositionForElement(e.Element as VisualElement, e.Position); + x = itemPosition.X; + y = itemPosition.Y; + } + + if (Control.VerticalOffset == y && Control.HorizontalOffset == x) + return; + + if (e.ShouldAnimate) + { + var animation = new Animation(v => { UpdateScrollOffset(GetDistance(Control.ViewportWidth, x, v), GetDistance(Control.ViewportHeight, y, v)); }); + + animation.Commit(_animatable, "ScrollTo", length: 500, easing: Easing.CubicInOut, finished: (v, d) => + { + UpdateScrollOffset(x, y); + Controller.SendScrollFinished(); + }); + } + else + { + UpdateScrollOffset(x, y); + Controller.SendScrollFinished(); + } + } + + void UpdateMargins() + { + var element = Control.Content as FrameworkElement; + if (element == null) + return; + + if (Element.Orientation == ScrollOrientation.Horizontal) + { + // need to add left/right margins + element.Margin = new System.Windows.Thickness(Element.Padding.Left, 0, Element.Padding.Right, 0); + } + else + { + // need to add top/bottom margins + element.Margin = new System.Windows.Thickness(0, Element.Padding.Top, 0, Element.Padding.Bottom); + } + } + + void UpdateOrientation() + { + if (Element.Orientation == ScrollOrientation.Horizontal) + Control.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; + else + Control.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled; + } + + void UpdateScrollOffset(double x, double y) + { + if (Element.Orientation == ScrollOrientation.Horizontal) + Control.ScrollToHorizontalOffset(x); + else + Control.ScrollToVerticalOffset(y); + } + + void UpdateScrollPosition() + { + if (Element != null) + Controller.SetScrolledPosition(Control.HorizontalOffset, Control.VerticalOffset); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/SearchBarRenderer.cs b/Xamarin.Forms.Platform.WP8/SearchBarRenderer.cs new file mode 100644 index 00000000..b27851a8 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/SearchBarRenderer.cs @@ -0,0 +1,159 @@ +using System.ComponentModel; +using System.Windows.Input; +using System.Windows.Media; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class SearchBarRenderer : ViewRenderer<SearchBar, FormsPhoneTextBox> + { + const string DefaultPlaceholder = "Search"; + Brush _defaultPlaceholderColorBrush; + + Brush _defaultTextColorBrush; + + bool _fontApplied; + + protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e) + { + base.OnElementChanged(e); + + var scope = new InputScope(); + var name = new InputScopeName(); + name.NameValue = InputScopeNameValue.Search; + scope.Names.Add(name); + + var phoneTextBox = new FormsPhoneTextBox { InputScope = scope }; + + phoneTextBox.KeyUp += PhoneTextBoxOnKeyUp; + + phoneTextBox.TextChanged += PhoneTextBoxOnTextChanged; + + SetNativeControl(phoneTextBox); + + UpdateText(); + UpdatePlaceholder(); + UpdateAlignment(); + UpdateFont(); + UpdatePlaceholderColor(); + UpdateTextColor(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == SearchBar.TextProperty.PropertyName) + UpdateText(); + else if (e.PropertyName == SearchBar.PlaceholderProperty.PropertyName) + UpdatePlaceholder(); + else if (e.PropertyName == SearchBar.FontAttributesProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == SearchBar.FontFamilyProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == SearchBar.FontSizeProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == SearchBar.HorizontalTextAlignmentProperty.PropertyName) + UpdateAlignment(); + else if (e.PropertyName == SearchBar.PlaceholderColorProperty.PropertyName) + UpdatePlaceholderColor(); + else if (e.PropertyName == SearchBar.TextColorProperty.PropertyName) + UpdateTextColor(); + } + + protected override void UpdateBackgroundColor() + { + Control.Background = Element.BackgroundColor == Color.Default ? (Brush)System.Windows.Application.Current.Resources["PhoneTextBoxBrush"] : Element.BackgroundColor.ToBrush(); + } + + void PhoneTextBoxOnKeyUp(object sender, KeyEventArgs keyEventArgs) + { + if (keyEventArgs.Key == Key.Enter) + Element.OnSearchButtonPressed(); + } + + void PhoneTextBoxOnTextChanged(object sender, System.Windows.Controls.TextChangedEventArgs textChangedEventArgs) + { + ((IElementController)Element).SetValueFromRenderer(SearchBar.TextProperty, Control.Text); + } + + void UpdateAlignment() + { + Control.TextAlignment = Element.HorizontalTextAlignment.ToNativeTextAlignment(); + } + + void UpdateFont() + { + if (Control == null) + return; + + SearchBar searchbar = Element; + + if (searchbar == null) + return; + + bool searchbarIsDefault = searchbar.FontFamily == null && searchbar.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(SearchBar), true) && searchbar.FontAttributes == FontAttributes.None; + + if (searchbarIsDefault && !_fontApplied) + return; + + if (searchbarIsDefault) + { + Control.ClearValue(System.Windows.Controls.Control.FontStyleProperty); + Control.ClearValue(System.Windows.Controls.Control.FontSizeProperty); + Control.ClearValue(System.Windows.Controls.Control.FontFamilyProperty); + Control.ClearValue(System.Windows.Controls.Control.FontWeightProperty); + Control.ClearValue(System.Windows.Controls.Control.FontStretchProperty); + } + else + Control.ApplyFont(searchbar); + + _fontApplied = true; + } + + void UpdatePlaceholder() + { + Control.Hint = Element.Placeholder ?? DefaultPlaceholder; + } + + void UpdatePlaceholderColor() + { + Color placeholderColor = Element.PlaceholderColor; + + if (placeholderColor.IsDefault) + { + if (_defaultPlaceholderColorBrush == null) + return; + + Control.PlaceholderForegroundBrush = _defaultPlaceholderColorBrush; + } + + if (_defaultPlaceholderColorBrush == null) + _defaultPlaceholderColorBrush = Control.PlaceholderForegroundBrush; + + Control.PlaceholderForegroundBrush = placeholderColor.ToBrush(); + } + + void UpdateText() + { + Control.Text = Element.Text ?? ""; + } + + void UpdateTextColor() + { + Color textColor = Element.TextColor; + + if (textColor.IsDefault) + { + if (_defaultTextColorBrush == null) + return; + + Control.Foreground = _defaultTextColorBrush; + } + + if (_defaultTextColorBrush == null) + _defaultTextColorBrush = Control.Foreground; + + Control.Foreground = textColor.ToBrush(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ServiceReferences.ClientConfig b/Xamarin.Forms.Platform.WP8/ServiceReferences.ClientConfig new file mode 100644 index 00000000..26e884eb --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ServiceReferences.ClientConfig @@ -0,0 +1,26 @@ +<configuration> + <system.serviceModel> + <bindings> + <basicHttpBinding> + <binding name="BasicHttpBinding_IGeocodeService" maxBufferSize="2147483647" + maxReceivedMessageSize="2147483647"> + <security mode="None" /> + </binding> + </basicHttpBinding> + <customBinding> + <binding name="CustomBinding_IGeocodeService"> + <binaryMessageEncoding /> + <httpTransport maxReceivedMessageSize="2147483647" maxBufferSize="2147483647" /> + </binding> + </customBinding> + </bindings> + <client> + <endpoint address="http://dev.virtualearth.net/webservices/v1/geocodeservice/GeocodeService.svc" + binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IGeocodeService" + contract="GeoService.IGeocodeService" name="BasicHttpBinding_IGeocodeService" /> + <endpoint address="http://dev.virtualearth.net/webservices/v1/geocodeservice/GeocodeService.svc/binaryHttp" + binding="customBinding" bindingConfiguration="CustomBinding_IGeocodeService" + contract="GeoService.IGeocodeService" name="CustomBinding_IGeocodeService" /> + </client> + </system.serviceModel> +</configuration> diff --git a/Xamarin.Forms.Platform.WP8/SliderRenderer.cs b/Xamarin.Forms.Platform.WP8/SliderRenderer.cs new file mode 100644 index 00000000..40cd767d --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/SliderRenderer.cs @@ -0,0 +1,43 @@ +using System.ComponentModel; +using System.Windows; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class SliderRenderer : ViewRenderer<Slider, System.Windows.Controls.Slider> + { + protected override void OnElementChanged(ElementChangedEventArgs<Slider> e) + { + base.OnElementChanged(e); + + var wSlider = new System.Windows.Controls.Slider { Minimum = Element.Minimum, Maximum = Element.Maximum, Value = Element.Value }; + + SetNativeControl(wSlider); + + wSlider.ValueChanged += HandleValueChanged; + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + switch (e.PropertyName) + { + case "Minimum": + Control.Minimum = Element.Minimum; + break; + case "Maximum": + Control.Maximum = Element.Maximum; + break; + case "Value": + if (Control.Value != Element.Value) + Control.Value = Element.Value; + break; + } + } + + void HandleValueChanged(object sender, RoutedPropertyChangedEventArgs<double> routedPropertyChangedEventArgs) + { + ((IElementController)Element).SetValueFromRenderer(Slider.ValueProperty, Control.Value); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/StepperRenderer.cs b/Xamarin.Forms.Platform.WP8/StepperRenderer.cs new file mode 100644 index 00000000..b459305f --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/StepperRenderer.cs @@ -0,0 +1,68 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using WButton = System.Windows.Controls.Button; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class StepperRenderer : ViewRenderer<Stepper, Border> + { + readonly StackPanel _panel = new StackPanel(); + WButton _downButton; + WButton _upButton; + + protected override void OnElementChanged(ElementChangedEventArgs<Stepper> e) + { + base.OnElementChanged(e); + + var border = new Border(); + border.Child = _panel; + _panel.HorizontalAlignment = HorizontalAlignment.Right; + _panel.Orientation = Orientation.Horizontal; + + _upButton = new WButton { Content = "+", Width = 100 }; + _downButton = new WButton { Content = "-", Width = 100 }; + + _panel.Children.Add(_downButton); + _panel.Children.Add(_upButton); + + SetNativeControl(border); + + _upButton.Click += UpButtonOnClick; + _downButton.Click += DownButtonOnClick; + + UpdateButtons(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + switch (e.PropertyName) + { + case "Minimum": + case "Maximum": + case "Value": + UpdateButtons(); + break; + } + } + + void DownButtonOnClick(object sender, RoutedEventArgs routedEventArgs) + { + ((IElementController)Element).SetValueFromRenderer(Stepper.ValueProperty, Math.Max(Element.Minimum, Element.Value - Element.Increment)); + } + + void UpButtonOnClick(object sender, RoutedEventArgs routedEventArgs) + { + ((IElementController)Element).SetValueFromRenderer(Stepper.ValueProperty, Math.Min(Element.Maximum, Element.Value + Element.Increment)); + } + + void UpdateButtons() + { + _upButton.IsEnabled = Element.Value < Element.Maximum; + _downButton.IsEnabled = Element.Value > Element.Minimum; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/SwitchRenderer.cs b/Xamarin.Forms.Platform.WP8/SwitchRenderer.cs new file mode 100644 index 00000000..e64fe227 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/SwitchRenderer.cs @@ -0,0 +1,50 @@ +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Phone.Controls.Primitives; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class SwitchRenderer : ViewRenderer<Switch, Border> + { + readonly ToggleSwitchButton _toggleSwitch = new ToggleSwitchButton(); + + protected override void OnElementChanged(ElementChangedEventArgs<Switch> e) + { + base.OnElementChanged(e); + + var container = new Border { Child = _toggleSwitch }; + _toggleSwitch.IsChecked = Element.IsToggled; + _toggleSwitch.Checked += (sender, args) => ((IElementController)Element).SetValueFromRenderer(Switch.IsToggledProperty, true); + _toggleSwitch.Unchecked += (sender, args) => ((IElementController)Element).SetValueFromRenderer(Switch.IsToggledProperty, false); + _toggleSwitch.HorizontalAlignment = HorizontalAlignment.Right; + + SetNativeControl(container); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Switch.IsToggledProperty.PropertyName) + { + if (_toggleSwitch.IsChecked != Element.IsToggled) + _toggleSwitch.IsChecked = Element.IsToggled; + } + else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + UpdateSwitchIsEnabled(); + } + + protected override void UpdateNativeWidget() + { + base.UpdateNativeWidget(); + + UpdateSwitchIsEnabled(); + } + + void UpdateSwitchIsEnabled() + { + _toggleSwitch.IsEnabled = Element.IsEnabled; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/TabbedPageRenderer.cs b/Xamarin.Forms.Platform.WP8/TabbedPageRenderer.cs new file mode 100644 index 00000000..9192fc6f --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/TabbedPageRenderer.cs @@ -0,0 +1,105 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class TabbedPagePresenter : System.Windows.Controls.ContentPresenter + { + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + DependencyObject parent = VisualTreeHelper.GetParent(this); + while (parent != null && !(parent is PivotItem)) + parent = VisualTreeHelper.GetParent(parent); + + var pivotItem = parent as PivotItem; + if (pivotItem == null) + throw new Exception("No parent PivotItem found for tab"); + + pivotItem.SizeChanged += (s, e) => + { + if (pivotItem.ActualWidth > 0 && pivotItem.ActualHeight > 0) + { + var tab = (Page)DataContext; + ((TabbedPage)tab.RealParent).ContainerArea = new Rectangle(0, 0, pivotItem.ActualWidth, pivotItem.ActualHeight); + } + }; + } + } + + public class TabbedPageRenderer : Pivot, IVisualElementRenderer + { + TabbedPage _page; + BackgroundTracker<Control> _tracker; + + public TabbedPageRenderer() + { + SetBinding(TitleProperty, new System.Windows.Data.Binding("Title")); + SetBinding(ItemsSourceProperty, new System.Windows.Data.Binding("Children")); + HeaderTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["TabbedPageHeader"]; + ItemTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["TabbedPage"]; + + SelectionChanged += OnSelectionChanged; + } + + public UIElement ContainerElement + { + get { return this; } + } + + public VisualElement Element + { + get { return _page; } + } + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + + public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + return new SizeRequest(new Size(widthConstraint, heightConstraint)); + } + + public void SetElement(VisualElement element) + { + TabbedPage oldElement = _page; + _page = (TabbedPage)element; + _tracker = new BackgroundTracker<Control>(BackgroundProperty) { Model = _page, Element = this }; + + DataContext = element; + + _page.PropertyChanged += OnPropertyChanged; + + Loaded += (sender, args) => _page.SendAppearing(); + Unloaded += (sender, args) => _page.SendDisappearing(); + + OnElementChanged(new VisualElementChangedEventArgs(_page, element)); + } + + protected virtual void OnElementChanged(VisualElementChangedEventArgs e) + { + EventHandler<VisualElementChangedEventArgs> changed = ElementChanged; + if (changed != null) + changed(this, e); + } + + void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "CurrentPage") + { + Page current = _page.CurrentPage; + if (current != null) + SelectedItem = current; + } + } + + void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + _page.CurrentPage = (Page)SelectedItem; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/TableView.xaml b/Xamarin.Forms.Platform.WP8/TableView.xaml new file mode 100644 index 00000000..89c9cddb --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/TableView.xaml @@ -0,0 +1,31 @@ +<Grid x:Class="Xamarin.Forms.Platform.WinPhone.TableView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" + > + + <Grid.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ResourceDictionary Source="WPResources.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Grid.Resources> + + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition /> + </Grid.RowDefinitions> + + <TextBlock Grid.Row="0" Text="{Binding Title,Converter={StaticResource UpperConverter}}" Style="{StaticResource PhoneTextSmallTitleStyle}" /> + <ListBox Grid.Row="1" ItemsSource="{Binding}" ItemTemplate="{StaticResource TableGroup}"> + <ListBox.ItemContainerStyle> + <Style TargetType="ListBoxItem"> + <Setter Property="HorizontalContentAlignment" Value="Stretch" /> + </Style> + </ListBox.ItemContainerStyle> + </ListBox> +</Grid> + diff --git a/Xamarin.Forms.Platform.WP8/TableView.xaml.cs b/Xamarin.Forms.Platform.WP8/TableView.xaml.cs new file mode 100644 index 00000000..dc3d1dcc --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/TableView.xaml.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms.Platform.WinPhone +{ + public partial class TableView : System.Windows.Controls.Grid + { + public TableView() + { + InitializeComponent(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/TableViewRenderer.cs b/Xamarin.Forms.Platform.WP8/TableViewRenderer.cs new file mode 100644 index 00000000..84f205eb --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/TableViewRenderer.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using Microsoft.Phone.Shell; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class TableViewRenderer : ViewRenderer<Xamarin.Forms.TableView, TableView> + { + TableView _view; + + public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + SizeRequest result = base.GetDesiredSize(widthConstraint, heightConstraint); + result.Minimum = new Size(40, 40); + return result; + } + + protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.TableView> e) + { + base.OnElementChanged(e); + + Element.ModelChanged += OnModelChanged; + + _view = new TableView { DataContext = Element.Root }; + _view.Tap += OnTapTable; + _view.Hold += OnLongPressTable; + SetNativeControl(_view); + } + + bool FindIndices(GestureEventArgs e, out int sectionIndex, out int cellIndex) + { + sectionIndex = 0; + cellIndex = 0; + + TableSection section = null; + Cell cell = null; + + System.Windows.Point pos = e.GetPosition(System.Windows.Application.Current.RootVisual); + if (Device.Info.CurrentOrientation.IsLandscape()) + { + double x = pos.Y; + double y = System.Windows.Application.Current.RootVisual.RenderSize.Width - pos.X + (SystemTray.IsVisible ? 72 : 0); + pos = new System.Windows.Point(x, y); + } + IEnumerable<UIElement> elements = VisualTreeHelper.FindElementsInHostCoordinates(pos, System.Windows.Application.Current.RootVisual); + foreach (FrameworkElement element in elements.OfType<FrameworkElement>()) + { + if (cell == null) + cell = element.DataContext as Cell; + else if (section == null) + section = element.DataContext as TableSection; + else + break; + } + + if (cell == null || section == null) + return false; + + sectionIndex = Element.Root.IndexOf(section); + cellIndex = section.IndexOf(cell); + return true; + } + + void OnLongPressTable(object sender, GestureEventArgs e) + { + int sectionIndex, cellIndex; + if (!FindIndices(e, out sectionIndex, out cellIndex)) + return; + + Element.Model.RowLongPressed(sectionIndex, cellIndex); + } + + void OnModelChanged(object sender, EventArgs eventArgs) + { + _view.DataContext = Element.Root; + } + + void OnTapTable(object sender, GestureEventArgs e) + { + int sectionIndex, cellIndex; + if (!FindIndices(e, out sectionIndex, out cellIndex)) + return; + + Element.Model.RowSelected(sectionIndex, cellIndex); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/TextAlignmentToHorizontalAlignmentConverter.cs b/Xamarin.Forms.Platform.WP8/TextAlignmentToHorizontalAlignmentConverter.cs new file mode 100644 index 00000000..7f8aeef6 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/TextAlignmentToHorizontalAlignmentConverter.cs @@ -0,0 +1,43 @@ +using System; +using System.Globalization; +using System.Windows; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public sealed class TextAlignmentToHorizontalAlignmentConverter : System.Windows.Data.IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var alignment = (System.Windows.TextAlignment)value; + + switch (alignment) + { + case System.Windows.TextAlignment.Center: + return HorizontalAlignment.Center; + case System.Windows.TextAlignment.Left: + return HorizontalAlignment.Left; + case System.Windows.TextAlignment.Right: + return HorizontalAlignment.Right; + default: + return HorizontalAlignment.Left; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + var alignment = (HorizontalAlignment)value; + + switch (alignment) + { + case HorizontalAlignment.Left: + return System.Windows.TextAlignment.Left; + case HorizontalAlignment.Center: + return System.Windows.TextAlignment.Center; + case HorizontalAlignment.Right: + return System.Windows.TextAlignment.Right; + default: + return System.Windows.TextAlignment.Left; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/TextCellRenderer.cs b/Xamarin.Forms.Platform.WP8/TextCellRenderer.cs new file mode 100644 index 00000000..b3c2453d --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/TextCellRenderer.cs @@ -0,0 +1,88 @@ +using System; +using System.Windows.Input; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class TextCellRenderer : ICellRenderer + { + public virtual System.Windows.DataTemplate GetTemplate(Cell cell) + { + if (cell.RealParent is ListView) + { + if (TemplatedItemsList<ItemsView<Cell>, Cell>.GetIsGroupHeader(cell)) + return (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["ListViewHeaderTextCell"]; + + return (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["ListViewTextCell"]; + } + + return (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["TextCell"]; + } + } + + public class EntryCellRendererCompleted : ICommand + { + public bool CanExecute(object parameter) + { + return true; + } + + public event EventHandler CanExecuteChanged; + + public void Execute(object parameter) + { + var entryCell = (EntryCell)parameter; + entryCell.SendCompleted(); + } + } + + public class EntryCellPhoneTextBox : PhoneTextBox + { + public event EventHandler KeyboardReturnPressed; + + protected override void OnKeyUp(KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + EventHandler handler = KeyboardReturnPressed; + if (handler != null) + handler(this, EventArgs.Empty); + } + base.OnKeyUp(e); + } + } + + public class EntryCellRenderer : ICellRenderer + { + public virtual System.Windows.DataTemplate GetTemplate(Cell cell) + { + return (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["EntryCell"]; + } + } + + public class ViewCellRenderer : ICellRenderer + { + public virtual System.Windows.DataTemplate GetTemplate(Cell cell) + { + return (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["ViewCell"]; + } + } + + public class SwitchCellRenderer : ICellRenderer + { + public virtual System.Windows.DataTemplate GetTemplate(Cell cell) + { + return (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["SwitchCell"]; + } + } + + public class ImageCellRenderer : ICellRenderer + { + public virtual System.Windows.DataTemplate GetTemplate(Cell cell) + { + if (cell.RealParent is ListView) + return (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["ListImageCell"]; + return (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["ImageCell"]; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/TimePickerRenderer.cs b/Xamarin.Forms.Platform.WP8/TimePickerRenderer.cs new file mode 100644 index 00000000..70841d7a --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/TimePickerRenderer.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class TimePickerRenderer : ViewRenderer<TimePicker, Microsoft.Phone.Controls.TimePicker> + { + protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e) + { + base.OnElementChanged(e); + + var timePicker = new Microsoft.Phone.Controls.TimePicker { Value = DateTime.Today.Add(Element.Time) }; + timePicker.ValueChanged += TimePickerOnValueChanged; + + SetNativeControl(timePicker); + UpdateFormatString(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == "Time") + Control.Value = DateTime.Today.Add(Element.Time); + else if (e.PropertyName == TimePicker.FormatProperty.PropertyName) + UpdateFormatString(); + } + + internal override void OnModelFocusChangeRequested(object sender, VisualElement.FocusRequestArgs args) + { + Microsoft.Phone.Controls.TimePicker control = Control; + if (control == null) + return; + + if (args.Focus) + { + typeof(DateTimePickerBase).InvokeMember("OpenPickerPage", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, control, null); + args.Result = true; + } + else + { + UnfocusControl(control); + args.Result = true; + } + } + + void TimePickerOnValueChanged(object sender, DateTimeValueChangedEventArgs dateTimeValueChangedEventArgs) + { + if (Control.Value != null) + ((IElementController)Element).SetValueFromRenderer(TimePicker.TimeProperty, Control.Value.Value - DateTime.Today); + } + + void UpdateFormatString() + { + Control.ValueStringFormat = "{0:" + Element.Format + "}"; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Cancel.png b/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Cancel.png Binary files differnew file mode 100644 index 00000000..4dd724f0 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Cancel.png diff --git a/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Check.png b/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Check.png Binary files differnew file mode 100644 index 00000000..7a074666 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Check.png diff --git a/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Delete.png b/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Delete.png Binary files differnew file mode 100644 index 00000000..95bb16da --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Delete.png diff --git a/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Select.png b/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Select.png Binary files differnew file mode 100644 index 00000000..995deaaa --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Toolkit.Content/ApplicationBar.Select.png diff --git a/Xamarin.Forms.Platform.WP8/ViewExtensions.cs b/Xamarin.Forms.Platform.WP8/ViewExtensions.cs new file mode 100644 index 00000000..1d46b3b2 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ViewExtensions.cs @@ -0,0 +1,19 @@ +using System; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public static class ViewExtensions + { + [Obsolete("Use Platform.GetRenderer")] + public static IVisualElementRenderer GetRenderer(this VisualElement self) + { + return Platform.GetRenderer(self); + } + + [Obsolete("Use Platform.SetRenderer")] + public static void SetRenderer(this VisualElement self, IVisualElementRenderer renderer) + { + Platform.SetRenderer(self, renderer); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ViewRenderer.cs b/Xamarin.Forms.Platform.WP8/ViewRenderer.cs new file mode 100644 index 00000000..3f0d72c5 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ViewRenderer.cs @@ -0,0 +1,47 @@ +using System.ComponentModel; +using System.Windows; +using System.Windows.Media; +using WSize = System.Windows.Size; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class ViewRenderer<TElement, TNativeElement> : VisualElementRenderer<TElement, TNativeElement> where TElement : View where TNativeElement : FrameworkElement + { + } + + public class ViewRenderer : ViewRenderer<View, FrameworkElement> + { + protected override void OnElementChanged(ElementChangedEventArgs<View> e) + { + base.OnElementChanged(e); + SizeChanged += (sender, args) => UpdateClipToBounds(); + + UpdateBackgroundColor(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName) + UpdateClipToBounds(); + } + + protected override void UpdateNativeWidget() + { + base.UpdateNativeWidget(); + UpdateClipToBounds(); + } + + void UpdateClipToBounds() + { + var layout = Element as Layout; + if (layout != null) + { + Clip = null; + if (layout.IsClippedToBounds) + Clip = new RectangleGeometry { Rect = new Rect(0, 0, ActualWidth, ActualHeight) }; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/ViewToRendererConverter.cs b/Xamarin.Forms.Platform.WP8/ViewToRendererConverter.cs new file mode 100644 index 00000000..49b232a1 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/ViewToRendererConverter.cs @@ -0,0 +1,77 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class ViewToRendererConverter : System.Windows.Data.IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var view = value as View; + if (view == null) + return null; + + return new WrapperControl(view); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + class WrapperControl : ContentControl + { + readonly View _view; + + public WrapperControl(View view) + { + _view = view; + _view.MeasureInvalidated += (sender, args) => InvalidateMeasure(); + + IVisualElementRenderer visualElementRenderer = Platform.CreateRenderer(view); + Platform.SetRenderer(view, visualElementRenderer); + Content = visualElementRenderer.ContainerElement; + + // make sure we re-measure once the template is applied + var frameworkElement = visualElementRenderer.ContainerElement as FrameworkElement; + if (frameworkElement != null) + { + frameworkElement.Loaded += (sender, args) => + { + _view.InvalidateMeasure(InvalidationTrigger.MeasureChanged); + InvalidateMeasure(); + }; + } + } + + protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) + { + _view.IsInNativeLayout = true; + Layout.LayoutChildIntoBoundingRegion(_view, new Rectangle(0, 0, finalSize.Width, finalSize.Height)); + _view.IsInNativeLayout = false; + + var content = Content as FrameworkElement; + content?.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); + return finalSize; + } + + protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) + { + var content = Content as FrameworkElement; + content?.Measure(availableSize); + Size request = _view.Measure(availableSize.Width, availableSize.Height, MeasureFlags.IncludeMargins).Request; + + System.Windows.Size result; + if (_view.HorizontalOptions.Alignment == LayoutAlignment.Fill && !double.IsInfinity(availableSize.Width) && availableSize.Width != 0) + result = new System.Windows.Size(availableSize.Width, request.Height); + else + result = new System.Windows.Size(request.Width, request.Height); + + _view.Layout(new Rectangle(0, 0, result.Width, result.Width)); + return result; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/VisualElementPackager.cs b/Xamarin.Forms.Platform.WP8/VisualElementPackager.cs new file mode 100644 index 00000000..65f589fc --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/VisualElementPackager.cs @@ -0,0 +1,88 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class VisualElementPackager + { + readonly Panel _panel; + readonly IVisualElementRenderer _renderer; + bool _loaded; + + public VisualElementPackager(IVisualElementRenderer renderer) + { + if (renderer == null) + throw new ArgumentNullException("renderer"); + + _panel = renderer.ContainerElement as Panel; + if (_panel == null) + throw new ArgumentException("Renderer's container element must be a Panel or Panel subclass"); + + _renderer = renderer; + } + + public void Load() + { + if (_loaded) + return; + + _loaded = true; + _renderer.Element.ChildAdded += HandleChildAdded; + _renderer.Element.ChildRemoved += HandleChildRemoved; + _renderer.Element.ChildrenReordered += HandleChildrenReordered; + + foreach (Element child in _renderer.Element.LogicalChildren) + HandleChildAdded(_renderer.Element, new ElementEventArgs(child)); + } + + void EnsureZIndex() + { + for (var index = 0; index < _renderer.Element.LogicalChildren.Count; index++) + { + var child = (VisualElement)_renderer.Element.LogicalChildren[index]; + IVisualElementRenderer r = Platform.GetRenderer(child); + if (r == null) + continue; + // Even though this attached property is defined on Canvas, it actually works on all Panels + // Why? Microsoft. + Canvas.SetZIndex(r.ContainerElement, index + 1); + } + } + + void HandleChildAdded(object sender, ElementEventArgs e) + { + var view = e.Element as VisualElement; + + if (view == null) + return; + + IVisualElementRenderer renderer; + Platform.SetRenderer(view, renderer = Platform.CreateRenderer(view)); + + _panel.Children.Add(renderer.ContainerElement); + + EnsureZIndex(); + } + + void HandleChildRemoved(object sender, ElementEventArgs e) + { + var view = e.Element as VisualElement; + + if (view == null) + return; + + var renderer = Platform.GetRenderer(view) as UIElement; + + if (renderer != null) + _panel.Children.Remove(renderer); + + EnsureZIndex(); + } + + void HandleChildrenReordered(object sender, EventArgs e) + { + EnsureZIndex(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/VisualElementRenderer.cs b/Xamarin.Forms.Platform.WP8/VisualElementRenderer.cs new file mode 100644 index 00000000..9ec274c5 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/VisualElementRenderer.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class VisualElementRenderer<TElement, TNativeElement> : Panel, IVisualElementRenderer, IEffectControlProvider where TElement : VisualElement where TNativeElement : FrameworkElement + { + readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers = new List<EventHandler<VisualElementChangedEventArgs>>(); + + Brush _initialBrush; + + VisualElementTracker _tracker; + + public TNativeElement Control { get; private set; } + + public TElement Element { get; private set; } + + protected bool AutoPackage { get; set; } = true; + + protected bool AutoTrack { get; set; } = true; + + protected VisualElementTracker Tracker + { + get { return _tracker; } + set + { + if (_tracker == value) + return; + + if (_tracker != null) + { + _tracker.Dispose(); + _tracker.Updated -= HandleTrackerUpdated; + } + + _tracker = value; + + if (_tracker != null) + _tracker.Updated += HandleTrackerUpdated; + } + } + + VisualElementPackager Packager { get; set; } + + void IEffectControlProvider.RegisterEffect(Effect effect) + { + var platformEffect = effect as PlatformEffect; + if (platformEffect != null) + OnRegisterEffect(platformEffect); + } + + public UIElement ContainerElement + { + get { return this; } + } + + VisualElement IVisualElementRenderer.Element + { + get { return Element; } + } + + event EventHandler<VisualElementChangedEventArgs> IVisualElementRenderer.ElementChanged + { + add { _elementChangedHandlers.Add(value); } + remove { _elementChangedHandlers.Remove(value); } + } + + public virtual SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + if (Children.Count == 0) + return new SizeRequest(); + + var constraint = new System.Windows.Size(widthConstraint, heightConstraint); + var child = (FrameworkElement)Children[0]; + + child.Measure(constraint); + var result = new Size(Math.Ceiling(child.DesiredSize.Width), Math.Ceiling(child.DesiredSize.Height)); + + return new SizeRequest(result); + } + + public void SetElement(VisualElement element) + { + TElement oldElement = Element; + Element = (TElement)element; + + if (oldElement != null) + { + oldElement.PropertyChanged -= OnElementPropertyChanged; + oldElement.FocusChangeRequested -= OnModelFocusChangeRequested; + } + + Element.PropertyChanged += OnElementPropertyChanged; + Element.FocusChangeRequested += OnModelFocusChangeRequested; + + if (AutoPackage && Packager == null) + Packager = new VisualElementPackager(this); + + if (AutoTrack && Tracker == null) + { + Tracker = new VisualElementTracker<TElement, FrameworkElement> { Model = Element, Element = this }; + } + + // Disabled until reason for crashes with unhandled exceptions is discovered + // Without this some layouts may end up with improper sizes, however their children + // will position correctly + //Loaded += (sender, args) => { + if (Packager != null) + Packager.Load(); + //}; + + OnElementChanged(new ElementChangedEventArgs<TElement>(oldElement, Element)); + + var controller = (IElementController)oldElement; + if (controller != null && controller.EffectControlProvider == this) + controller.EffectControlProvider = null; + + controller = element; + if (controller != null) + controller.EffectControlProvider = this; + } + + public event EventHandler<ElementChangedEventArgs<TElement>> ElementChanged; + + protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) + { + if (Element == null || finalSize.Width * finalSize.Height == 0) + return finalSize; + + Element.IsInNativeLayout = true; + + if (Control != null) + { + Control.Measure(finalSize); + Control.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); + } + + for (var i = 0; i < Element.LogicalChildren.Count; i++) + { + var child = Element.LogicalChildren[i] as VisualElement; + if (child == null) + continue; + IVisualElementRenderer renderer = child.GetRenderer(); + if (renderer == null) + continue; + Rectangle bounds = child.Bounds; + + renderer.ContainerElement.Arrange(new Rect(bounds.X, bounds.Y, Math.Max(0, bounds.Width), Math.Max(0, bounds.Height))); + } + + Element.IsInNativeLayout = false; + + return finalSize; + } + + protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) + { + if (Element == null || availableSize.Width * availableSize.Height == 0) + return new System.Windows.Size(0, 0); + + Element.IsInNativeLayout = true; + + for (var i = 0; i < Element.LogicalChildren.Count; i++) + { + var child = Element.LogicalChildren[i] as VisualElement; + if (child == null) + continue; + IVisualElementRenderer renderer = Platform.GetRenderer(child); + if (renderer == null) + continue; + + try + { + renderer.ContainerElement.Measure(availableSize); + } + catch (NullReferenceException) + { + if (!IsExpectedTabbedPageMeasurementException(renderer)) + throw; + } + } + + double width = Math.Max(0, Element.Width); + double height = Math.Max(0, Element.Height); + var result = new System.Windows.Size(width, height); + + if (Control != null) + { + double w = Element.Width; + double h = Element.Height; + if (w == -1) + w = availableSize.Width; + if (h == -1) + h = availableSize.Height; + w = Math.Max(0, w); + h = Math.Max(0, h); + Control.Measure(new System.Windows.Size(w, h)); + } + + Element.IsInNativeLayout = false; + + return result; + } + + protected virtual void OnElementChanged(ElementChangedEventArgs<TElement> e) + { + var args = new VisualElementChangedEventArgs(e.OldElement, e.NewElement); + for (var i = 0; i < _elementChangedHandlers.Count; i++) + _elementChangedHandlers[i](this, args); + + EventHandler<ElementChangedEventArgs<TElement>> changed = ElementChanged; + if (changed != null) + changed(this, e); + } + + protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + UpdateEnabled(); + else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackgroundColor(); + } + + protected virtual void OnGotFocus(object sender, RoutedEventArgs args) + { + ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true); + } + + protected virtual void OnLostFocus(object sender, RoutedEventArgs args) + { + ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false); + } + + protected virtual void OnRegisterEffect(PlatformEffect effect) + { + effect.Container = this; + effect.Control = Control; + } + + protected void SetNativeControl(TNativeElement element) + { + Control = element; + + Children.Add(element); + Element.IsNativeStateConsistent = false; + element.Loaded += (sender, args) => Element.IsNativeStateConsistent = true; + + element.GotFocus += OnGotFocus; + element.LostFocus += OnLostFocus; + + _tracker.Child = element; + + UpdateBackgroundColor(); + } + + protected virtual void UpdateBackgroundColor() + { + var control = Control as Control; + if (_initialBrush == null) + _initialBrush = control == null ? Background : control.Background; + if (control != null) + control.Background = Element.BackgroundColor != Color.Default ? Element.BackgroundColor.ToBrush() : _initialBrush; + else + Background = Element.BackgroundColor != Color.Default ? Element.BackgroundColor.ToBrush() : _initialBrush; + } + + protected virtual void UpdateNativeWidget() + { + UpdateEnabled(); + } + + internal virtual void OnModelFocusChangeRequested(object sender, VisualElement.FocusRequestArgs args) + { + var control = Control as Control; + if (control == null) + return; + + if (args.Focus) + args.Result = control.Focus(); + else + { + UnfocusControl(control); + args.Result = true; + } + } + + internal void UnfocusControl(Control control) + { + if (control == null || !control.IsEnabled) + return; + control.IsEnabled = false; + control.IsEnabled = true; + } + + void HandleTrackerUpdated(object sender, EventArgs e) + { + UpdateNativeWidget(); + } + + static bool IsExpectedTabbedPageMeasurementException(IVisualElementRenderer renderer) + { + // The TabbedPageRenderer's underlying Pivot control throws a NRE if the tabbed page + // does not have any toolbar items and it's measured during an animated transition + // from a page which does have toolbar items + + // The NRE happens before TabbedPageRenderer's MeasureOverride is even called, + // so unfortunately we have to handle it here + + var tpr = renderer.ContainerElement as TabbedPageRenderer; + + var tp = tpr?.Element as TabbedPage; + + return tp?.ToolbarItems.Count == 0; + } + + void UpdateEnabled() + { + if (Control is Control) + (Control as Control).IsEnabled = Element.IsEnabled; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/VisualElementTracker.cs b/Xamarin.Forms.Platform.WP8/VisualElementTracker.cs new file mode 100644 index 00000000..8211ec63 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/VisualElementTracker.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public abstract class VisualElementTracker : IDisposable + { + public abstract FrameworkElement Child { get; set; } + + public abstract void Dispose(); + + public event EventHandler Updated; + + protected void OnUpdated() + { + if (Updated != null) + Updated(this, EventArgs.Empty); + } + } + + public class VisualElementTracker<TModel, TElement> : VisualElementTracker where TModel : VisualElement where TElement : FrameworkElement + { + FrameworkElement _child; + bool _disposed; + TElement _element; + + bool _invalidateArrangeNeeded; + bool _isPanning; + bool _isPinching; + + TModel _model; + bool _touchFrameReportedEventSet; + int _touchPoints = 1; + + public override FrameworkElement Child + { + get { return _child; } + set + { + if (_child == value) + return; + _child = value; + UpdateNativeControl(); + } + } + + public TElement Element + { + get { return _element; } + set + { + if (_element == value) + return; + + if (_element != null) + { + _element.Tap -= ElementOnTap; + _element.DoubleTap -= ElementOnDoubleTap; + _element.ManipulationDelta -= OnManipulationDelta; + _element.ManipulationCompleted -= OnManipulationCompleted; + } + + _element = value; + + if (_element != null) + { + _element.Tap += ElementOnTap; + _element.DoubleTap += ElementOnDoubleTap; + _element.ManipulationDelta += OnManipulationDelta; + _element.ManipulationCompleted += OnManipulationCompleted; + } + + UpdateNativeControl(); + } + } + + public TModel Model + { + get { return _model; } + set + { + if (_model == value) + return; + + if (_model != null) + { + _model.BatchCommitted -= HandleRedrawNeeded; + _model.PropertyChanged -= HandlePropertyChanged; + } + + _model = value; + + if (_model != null) + { + _model.BatchCommitted += HandleRedrawNeeded; + _model.PropertyChanged += HandlePropertyChanged; + } + + UpdateNativeControl(); + } + } + + public override void Dispose() + { + if (_disposed) + return; + _disposed = true; + + if (_element != null) + { + _element.Tap -= ElementOnTap; + _element.DoubleTap -= ElementOnDoubleTap; + _element.ManipulationDelta -= OnManipulationDelta; + _element.ManipulationCompleted -= OnManipulationCompleted; + } + + if (_model != null) + { + _model.BatchCommitted -= HandleRedrawNeeded; + _model.PropertyChanged -= HandlePropertyChanged; + } + + Child = null; + Model = null; + Element = null; + } + + protected virtual void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (Model.Batched) + { + if (e.PropertyName == VisualElement.XProperty.PropertyName || e.PropertyName == VisualElement.YProperty.PropertyName || e.PropertyName == VisualElement.WidthProperty.PropertyName || + e.PropertyName == VisualElement.HeightProperty.PropertyName) + _invalidateArrangeNeeded = true; + return; + } + + if (e.PropertyName == VisualElement.XProperty.PropertyName || e.PropertyName == VisualElement.YProperty.PropertyName || e.PropertyName == VisualElement.WidthProperty.PropertyName || + e.PropertyName == VisualElement.HeightProperty.PropertyName) + MaybeInvalidate(); + else if (e.PropertyName == VisualElement.AnchorXProperty.PropertyName || e.PropertyName == VisualElement.AnchorYProperty.PropertyName) + UpdateScaleAndRotation(Model, Element); + else if (e.PropertyName == VisualElement.ScaleProperty.PropertyName) + UpdateScaleAndRotation(Model, Element); + else if (e.PropertyName == VisualElement.TranslationXProperty.PropertyName || e.PropertyName == VisualElement.TranslationYProperty.PropertyName || + e.PropertyName == VisualElement.RotationProperty.PropertyName || e.PropertyName == VisualElement.RotationXProperty.PropertyName || e.PropertyName == VisualElement.RotationYProperty.PropertyName) + UpdateRotation(Model, Element); + else if (e.PropertyName == VisualElement.IsVisibleProperty.PropertyName) + UpdateVisibility(Model, Element); + else if (e.PropertyName == VisualElement.OpacityProperty.PropertyName) + UpdateOpacity(Model, Element); + else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName) + UpdateInputTransparent(Model, Element); + } + + protected virtual void UpdateNativeControl() + { + if (Model == null || Element == null) + return; + + UpdateOpacity(_model, _element); + UpdateScaleAndRotation(_model, _element); + UpdateInputTransparent(_model, _element); + + if (_invalidateArrangeNeeded) + MaybeInvalidate(); + _invalidateArrangeNeeded = false; + + UpdateTouchFrameReportedEvent(_model); + + OnUpdated(); + } + + void ElementOnDoubleTap(object sender, GestureEventArgs gestureEventArgs) + { + var view = Model as View; + if (view == null) + return; + + foreach (TapGestureRecognizer gestureRecognizer in + view.GestureRecognizers.OfType<TapGestureRecognizer>().Where(g => g.NumberOfTapsRequired == 2)) + { + gestureRecognizer.SendTapped(view); + gestureEventArgs.Handled = true; + } + } + + void ElementOnTap(object sender, GestureEventArgs gestureEventArgs) + { + var view = Model as View; + if (view == null) + return; + + foreach (TapGestureRecognizer gestureRecognizer in + view.GestureRecognizers.OfType<TapGestureRecognizer>().Where(g => g.NumberOfTapsRequired == 1)) + { + gestureRecognizer.SendTapped(view); + gestureEventArgs.Handled = true; + } + } + + void HandlePan(ManipulationDeltaEventArgs e, View view) + { + foreach (PanGestureRecognizer recognizer in + view.GestureRecognizers.GetGesturesFor<PanGestureRecognizer>().Where(g => g.TouchPoints == _touchPoints)) + { + if (!_isPanning) + ((IPanGestureController)recognizer).SendPanStarted(view, Application.Current.PanGestureId); + + double totalX = 0; + double totalY = 0; + + // Translation and CumulativeManipulation will be 0 if we have more than one touch point because it thinks we're pinching, + // so we'll just go ahead and use the center point of the pinch gesture to figure out how much we're panning. + if (_touchPoints > 1 && e.PinchManipulation != null) + { + totalX = e.PinchManipulation.Current.Center.X - e.PinchManipulation.Original.Center.X; + totalY = e.PinchManipulation.Current.Center.Y - e.PinchManipulation.Original.Center.Y; + } + else + { + totalX = e.DeltaManipulation.Translation.X + e.CumulativeManipulation.Translation.X; + totalY = e.DeltaManipulation.Translation.Y + e.CumulativeManipulation.Translation.Y; + } + + ((IPanGestureController)recognizer).SendPan(view, totalX, totalY, Application.Current.PanGestureId); + _isPanning = true; + } + } + + void HandlePinch(ManipulationDeltaEventArgs e, View view) + { + if (e.PinchManipulation == null) + return; + + IEnumerable<PinchGestureRecognizer> pinchGestures = view.GestureRecognizers.GetGesturesFor<PinchGestureRecognizer>(); + System.Windows.Point translationPoint = e.ManipulationContainer.TransformToVisual(Element).Transform(e.PinchManipulation.Current.Center); + var scaleOriginPoint = new Point(translationPoint.X / view.Width, translationPoint.Y / view.Height); + foreach (var recognizer in pinchGestures) + { + if (!_isPinching) + ((IPinchGestureController)recognizer).SendPinchStarted(view, scaleOriginPoint); + ((IPinchGestureController)recognizer).SendPinch(view, e.PinchManipulation.DeltaScale, scaleOriginPoint); + } + _isPinching = true; + } + + void HandleRedrawNeeded(object sender, EventArgs e) + { + UpdateNativeControl(); + } + + void MaybeInvalidate() + { + if (Model.IsInNativeLayout) + return; + var parent = (FrameworkElement)Element.Parent; + parent?.InvalidateMeasure(); + Element.InvalidateMeasure(); + } + + void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs e) + { + var view = Model as View; + if (view == null) + return; + + IEnumerable pinchGestures = view.GestureRecognizers.GetGesturesFor<PinchGestureRecognizer>(); + foreach (var recognizer in pinchGestures) + ((IPinchGestureController)recognizer).SendPinchEnded(view); + _isPinching = false; + + IEnumerable<PanGestureRecognizer> panGestures = view.GestureRecognizers.GetGesturesFor<PanGestureRecognizer>().Where(g => g.TouchPoints == _touchPoints); + foreach (PanGestureRecognizer recognizer in panGestures) + ((IPanGestureController)recognizer).SendPanCompleted(view, Application.Current.PanGestureId); + Application.Current.PanGestureId++; + _isPanning = false; + } + + void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e) + { + var view = Model as View; + if (view == null) + return; + + HandlePinch(e, view); + + HandlePan(e, view); + } + + void Touch_FrameReported(object sender, TouchFrameEventArgs e) + { + _touchPoints = e.GetTouchPoints(Child).Count; + } + + static void UpdateInputTransparent(VisualElement view, FrameworkElement frameworkElement) + { + frameworkElement.IsHitTestVisible = !view.InputTransparent; + } + + static void UpdateOpacity(VisualElement view, FrameworkElement frameworkElement) + { + frameworkElement.Opacity = view.Opacity; + } + + static void UpdateRotation(VisualElement view, FrameworkElement frameworkElement) + { + double anchorX = view.AnchorX; + double anchorY = view.AnchorY; + double rotationX = view.RotationX; + double rotationY = view.RotationY; + double rotation = view.Rotation; + double translationX = view.TranslationX; + double translationY = view.TranslationY; + double scale = view.Scale; + + frameworkElement.Projection = new PlaneProjection + { + CenterOfRotationX = anchorX, + CenterOfRotationY = anchorY, + GlobalOffsetX = translationX / scale, + GlobalOffsetY = translationY / scale, + RotationX = -rotationX, + RotationY = -rotationY, + RotationZ = -rotation + }; + } + + static void UpdateScaleAndRotation(VisualElement view, FrameworkElement frameworkElement) + { + double anchorX = view.AnchorX; + double anchorY = view.AnchorY; + double scale = view.Scale; + frameworkElement.RenderTransformOrigin = new System.Windows.Point(anchorX, anchorY); + frameworkElement.RenderTransform = new ScaleTransform { ScaleX = scale, ScaleY = scale }; + + UpdateRotation(view, frameworkElement); + } + + void UpdateTouchFrameReportedEvent(VisualElement model) + { + if (_touchFrameReportedEventSet) + return; + + Touch.FrameReported -= Touch_FrameReported; + _touchFrameReportedEventSet = false; + + var view = model as View; + if (view == null) + return; + + if (!view.GestureRecognizers.GetGesturesFor<PanGestureRecognizer>().Any(g => g.TouchPoints > 1)) + return; + + Touch.FrameReported += Touch_FrameReported; + _touchFrameReportedEventSet = true; + } + + static void UpdateVisibility(VisualElement view, FrameworkElement frameworkElement) + { + frameworkElement.Visibility = view.IsVisible ? Visibility.Visible : Visibility.Collapsed; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/WP8PlatformServices.cs b/Xamarin.Forms.Platform.WP8/WP8PlatformServices.cs new file mode 100644 index 00000000..c20071f4 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/WP8PlatformServices.cs @@ -0,0 +1,232 @@ +using System; +using System.IO; +using System.IO.IsolatedStorage; +using System.Net; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; +using Windows.System; +using Xamarin.Forms.Platform.WinPhone; + +namespace Xamarin.Forms +{ + internal class WP8PlatformServices : IPlatformServices + { + static readonly MD5CryptoServiceProvider Checksum = new MD5CryptoServiceProvider(); + + public void BeginInvokeOnMainThread(Action action) + { + Deployment.Current.Dispatcher.BeginInvoke(action); + } + + public ITimer CreateTimer(Action<object> callback) + { + return new _Timer(new Timer(o => callback(o))); + } + + public ITimer CreateTimer(Action<object> callback, object state, int dueTime, int period) + { + return new _Timer(new Timer(o => callback(o), state, dueTime, period)); + } + + public ITimer CreateTimer(Action<object> callback, object state, long dueTime, long period) + { + return new _Timer(new Timer(o => callback(o), state, dueTime, period)); + } + + public ITimer CreateTimer(Action<object> callback, object state, TimeSpan dueTime, TimeSpan period) + { + return new _Timer(new Timer(o => callback(o), state, dueTime, period)); + } + + public ITimer CreateTimer(Action<object> callback, object state, uint dueTime, uint period) + { + return new _Timer(new Timer(o => callback(o), state, dueTime, period)); + } + + public Assembly[] GetAssemblies() + { + return AppDomain.CurrentDomain.GetAssemblies(); + } + + public string GetMD5Hash(string input) + { + byte[] bytes = Checksum.ComputeHash(Encoding.UTF8.GetBytes(input)); + var ret = new char[32]; + for (var i = 0; i < 16; i++) + { + ret[i * 2] = (char)Hex(bytes[i] >> 4); + ret[i * 2 + 1] = (char)Hex(bytes[i] & 0xf); + } + return new string(ret); + } + + public double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes) + { + switch (size) + { + case NamedSize.Default: + if (typeof(Label).IsAssignableFrom(targetElementType)) + return (double)System.Windows.Application.Current.Resources["PhoneFontSizeNormal"]; + return (double)System.Windows.Application.Current.Resources["PhoneFontSizeMedium"]; + case NamedSize.Micro: + return (double)System.Windows.Application.Current.Resources["PhoneFontSizeSmall"] - 3; + case NamedSize.Small: + return (double)System.Windows.Application.Current.Resources["PhoneFontSizeSmall"]; + case NamedSize.Medium: + if (useOldSizes) + goto case NamedSize.Default; + return (double)System.Windows.Application.Current.Resources["PhoneFontSizeMedium"]; + case NamedSize.Large: + return (double)System.Windows.Application.Current.Resources["PhoneFontSizeLarge"]; + default: + throw new ArgumentOutOfRangeException("size"); + } + } + + public Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource<Stream>(); + + try + { + HttpWebRequest request = WebRequest.CreateHttp(uri); + request.AllowReadStreamBuffering = true; + request.BeginGetResponse(ar => + { + if (cancellationToken.IsCancellationRequested) + { + tcs.SetCanceled(); + return; + } + + try + { + Stream stream = request.EndGetResponse(ar).GetResponseStream(); + tcs.TrySetResult(stream); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + }, null); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + + return tcs.Task; + } + + public IIsolatedStorageFile GetUserStoreForApplication() + { + return new _IsolatedStorageFile(IsolatedStorageFile.GetUserStoreForApplication()); + } + + public bool IsInvokeRequired + { + get { return !Deployment.Current.Dispatcher.CheckAccess(); } + } + + public void OpenUriAction(Uri uri) + { + Launcher.LaunchUriAsync(uri); + } + + public void StartTimer(TimeSpan interval, Func<bool> callback) + { + var timer = new DispatcherTimer { Interval = interval }; + timer.Start(); + timer.Tick += (sender, args) => + { + bool result = callback(); + if (!result) + timer.Stop(); + }; + } + + static int Hex(int v) + { + if (v < 10) + return '0' + v; + return 'a' + v - 10; + } + + public class _Timer : ITimer + { + readonly Timer _timer; + + public _Timer(Timer timer) + { + _timer = timer; + } + + public void Change(int dueTime, int period) + { + _timer.Change(dueTime, period); + } + + public void Change(long dueTime, long period) + { + _timer.Change(dueTime, period); + } + + public void Change(TimeSpan dueTime, TimeSpan period) + { + _timer.Change(dueTime, period); + } + + public void Change(uint dueTime, uint period) + { + _timer.Change(dueTime, period); + } + } + + public class _IsolatedStorageFile : IIsolatedStorageFile + { + readonly IsolatedStorageFile _isolatedStorageFile; + + public _IsolatedStorageFile(IsolatedStorageFile isolatedStorageFile) + { + _isolatedStorageFile = isolatedStorageFile; + } + + public Task CreateDirectoryAsync(string path) + { + _isolatedStorageFile.CreateDirectory(path); + return Task.FromResult(true); + } + + public Task<bool> GetDirectoryExistsAsync(string path) + { + return Task.FromResult(_isolatedStorageFile.DirectoryExists(path)); + } + + public Task<bool> GetFileExistsAsync(string path) + { + return Task.FromResult(_isolatedStorageFile.FileExists(path)); + } + + public Task<DateTimeOffset> GetLastWriteTimeAsync(string path) + { + return Task.FromResult(_isolatedStorageFile.GetLastWriteTime(path)); + } + + public Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access) + { + Stream stream = _isolatedStorageFile.OpenFile(path, (System.IO.FileMode)mode, (System.IO.FileAccess)access); + return Task.FromResult(stream); + } + + public Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share) + { + Stream stream = _isolatedStorageFile.OpenFile(path, (System.IO.FileMode)mode, (System.IO.FileAccess)access, (System.IO.FileShare)share); + return Task.FromResult(stream); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/WPResources.xaml b/Xamarin.Forms.Platform.WP8/WPResources.xaml new file mode 100644 index 00000000..6a234e60 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/WPResources.xaml @@ -0,0 +1,542 @@ +<?xml version="1.0" encoding="utf-8" ?> +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" + xmlns:forms="clr-namespace:Xamarin.Forms.Platform.WinPhone" + xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" + xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"> + + <forms:CaseConverter x:Key="UpperConverter" ConvertToUpper="True" /> + <forms:CaseConverter x:Key="LowerConverter" ConvertToUpper="False" /> + <forms:ColorConverter x:Key="ColorConverter" /> + <forms:HeightConverter x:Key="HeightConverter" /> + <forms:HorizontalTextAlignmentConverter x:Key="HorizontalTextAlignmentConverter" /> + <forms:KeyboardConverter x:Key="KeyboardConverter"/> + <forms:ImageConverter x:Key="ImageConverter" /> + <forms:ViewToRendererConverter x:Key="ViewToRenderer" /> + <forms:PageToRendererConverter x:Key="PageToRenderer" /> + <forms:CollapseWhenEmptyConverter x:Key="CollapseWhenEmpty" /> + <forms:EntryCellRendererCompleted x:Key="EntryCellRendererCompleted" /> + + <Style x:Key="PhoneTextSmallTitleStyle" BasedOn="{StaticResource PhoneTextNormalStyle}" TargetType="TextBlock"> + <Style.Setters> + <Setter Property="FontWeight" Value="Bold" /> + </Style.Setters> + </Style> + + <DataTemplate x:Key="CellTemplate"> + <Border Background="Transparent"> + <forms:CellControl Cell="{Binding}" HorizontalContentAlignment="Stretch" /> + </Border> + </DataTemplate> + + <DataTemplate x:Key="TableGroup"> + <StackPanel Margin="0,20,0,0"> + <TextBlock Text="{Binding Title,Converter={StaticResource LowerConverter}}" Style="{StaticResource PhoneTextGroupHeaderStyle}" /> + <ItemsControl ItemsSource="{Binding}" ItemTemplate="{StaticResource CellTemplate}" /> + </StackPanel> + </DataTemplate> + + <DataTemplate x:Key="ViewCell"> + <ContentPresenter Height="{Binding RenderHeight, Converter={StaticResource HeightConverter}}" Content="{Binding View,Converter={StaticResource ViewToRenderer}}" /> + </DataTemplate> + + <DataTemplate x:Key="View"> + <ContentPresenter Content="{Binding Path=.,Converter={StaticResource ViewToRenderer}}" /> + </DataTemplate> + + <Style x:Key="HeaderJumpStyle" TargetType="phone:LongListSelector"> + <Setter Property="GridCellSize" Value="113,113" /> + <Setter Property="LayoutMode" Value="Grid" /> + <Setter Property="ItemTemplate"> + <Setter.Value> + <DataTemplate> + <Border Background="{StaticResource PhoneAccentBrush}" Margin="6"> + <TextBlock Text="{Binding ShortName, Converter={StaticResource LowerConverter}}" Foreground="{StaticResource PhoneForegroundBrush}" FontSize="48" Padding="6" FontFamily="{StaticResource PhoneFontFamilySemiLight}" HorizontalAlignment="Left" VerticalAlignment="Bottom" /> + </Border> + </DataTemplate> + </Setter.Value> + </Setter> + </Style> + + <DataTemplate x:Key="ListViewHeader"> + <forms:CellControl Cell="{Binding HeaderContent}" HorizontalContentAlignment="Left" ShowContextActions="False" /> + </DataTemplate> + + <DataTemplate x:Key="ListViewHeaderTextCell"> + <Border Background="{StaticResource PhoneAccentBrush}" Height="{Binding RenderHeight, Converter={StaticResource HeightConverter}}" Margin="5" MinWidth="62" MinHeight="62"> + <StackPanel Margin="6"> + <StackPanel.Resources> + <Style TargetType="TextBlock"> + <Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiLight}" /> + </Style> + </StackPanel.Resources> + + <TextBlock FontSize="48" + Text="{Binding Text, Converter={StaticResource LowerConverter}}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + Foreground="{Binding TextColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneForegroundBrush}" /> + + <TextBlock FontSize="32" + Text="{Binding Detail}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + Foreground="{Binding DetailColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneAccentBrush}" /> + </StackPanel> + </Border> + </DataTemplate> + + <DataTemplate x:Key="TextCell"> + <Button Name="button" Margin="0,10,0,0" Height="{Binding RenderHeight, Converter={StaticResource HeightConverter}}" IsEnabled="{Binding IsEnabled}"> + <Button.Template> + <ControlTemplate> + <Grid Background="{Binding Background,RelativeSource={RelativeSource TemplatedParent}}"> + <ContentPresenter /> + </Grid> + </ControlTemplate> + </Button.Template> + <Button.Content> + <StackPanel> + <TextBlock + Style="{StaticResource PhoneTextExtraLargeStyle}" + Text="{Binding Text,Converter={StaticResource LowerConverter}}" + Foreground="{Binding TextColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneForegroundBrush}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + toolkit:TiltEffect.IsTiltEnabled="True" + /> + + <TextBlock + Style="{StaticResource PhoneTextAccentStyle}" + Text="{Binding Detail}" + Foreground="{Binding DetailColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneAccentBrush}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + toolkit:TiltEffect.IsTiltEnabled="True" + /> + </StackPanel> + </Button.Content> + </Button> + </DataTemplate> + + <DataTemplate x:Key="ImageCell"> + <Button Name="button" Margin="0,10,0,0" Height="{Binding RenderHeight, Converter={StaticResource HeightConverter}}" IsEnabled="{Binding IsEnabled}"> + <Button.Template> + <ControlTemplate> + <Grid Background="{Binding Background,RelativeSource={RelativeSource TemplatedParent}}"> + <ContentPresenter /> + </Grid> + </ControlTemplate> + </Button.Template> + <Button.Content> + <Grid Margin="10, 0, 0, 0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="80"/> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + + <Image + Grid.Column="0" + DataContext="{Binding ImageSource, Converter={StaticResource ImageConverter}}" + Source="{Binding Value}" + VerticalAlignment="Center" + /> + + <StackPanel Grid.Column="1"> + <TextBlock + Style="{StaticResource PhoneTextExtraLargeStyle}" + Text="{Binding Text,Converter={StaticResource LowerConverter}}" + Foreground="{Binding TextColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneForegroundBrush}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + toolkit:TiltEffect.IsTiltEnabled="True" + /> + + <TextBlock + Style="{StaticResource PhoneTextAccentStyle}" + Text="{Binding Detail}" + Foreground="{Binding DetailColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneAccentBrush}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + toolkit:TiltEffect.IsTiltEnabled="True" + /> + </StackPanel> + </Grid> + </Button.Content> + </Button> + </DataTemplate> + + <DataTemplate x:Key="ListImageCell"> + <Button Margin="0,10,0,0" Height="{Binding RenderHeight, Converter={StaticResource HeightConverter}}" IsEnabled="{Binding IsEnabled}"> + <Button.Template> + <ControlTemplate> + <Grid Background="{Binding Background,RelativeSource={RelativeSource TemplatedParent}}"> + <ContentPresenter /> + </Grid> + </ControlTemplate> + </Button.Template> + <Button.Content> + <Grid Margin="10, 0, 0, 0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="80"/> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + + <Image + Grid.Column="0" + DataContext="{Binding ImageSource, Converter={StaticResource ImageConverter}}" + Source="{Binding Value}" + VerticalAlignment="Center" + /> + + <StackPanel Grid.Column="1"> + <TextBlock + Style="{StaticResource PhoneTextLargeStyle}" + Text="{Binding Text}" + Foreground="{Binding TextColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneForegroundBrush}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + toolkit:TiltEffect.IsTiltEnabled="True" + forms:ListViewRenderer.HighlightWhenSelected="True" + /> + + <TextBlock + Style="{StaticResource PhoneTextAccentStyle}" + Text="{Binding Detail}" + Foreground="{Binding DetailColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneAccentBrush}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + toolkit:TiltEffect.IsTiltEnabled="True" + forms:ListViewRenderer.HighlightWhenSelected="True" + /> + </StackPanel> + </Grid> + </Button.Content> + </Button> + </DataTemplate> + + <DataTemplate x:Key="ListViewTextCell"> + <Button Margin="0,10,0,0" Height="{Binding RenderHeight, Converter={StaticResource HeightConverter}}" IsEnabled="{Binding IsEnabled}"> + <Button.Template> + <ControlTemplate> + <Grid Background="{Binding Background,RelativeSource={RelativeSource TemplatedParent}}"> + <ContentPresenter /> + </Grid> + </ControlTemplate> + </Button.Template> + <Button.Content> + <StackPanel> + <TextBlock + Style="{StaticResource PhoneTextLargeStyle}" + Text="{Binding Text}" + Foreground="{Binding TextColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneForegroundBrush}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + toolkit:TiltEffect.IsTiltEnabled="True" + forms:ListViewRenderer.HighlightWhenSelected="True" + /> + + <TextBlock + Style="{StaticResource PhoneTextAccentStyle}" + Text="{Binding Detail}" + Foreground="{Binding DetailColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneAccentBrush}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + toolkit:TiltEffect.IsTiltEnabled="True" + forms:ListViewRenderer.HighlightWhenSelected="True" + /> + </StackPanel> + </Button.Content> + </Button> + </DataTemplate> + + <DataTemplate x:Key="EntryCell"> + <Grid HorizontalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*"/> + </Grid.ColumnDefinitions> + + <Grid.Children> + <TextBlock forms:ListViewRenderer.HighlightWhenSelected="True" Style="{StaticResource PhoneTextLargeStyle}" + Text="{Binding Label}" + Foreground="{Binding LabelColor,Converter={StaticResource ColorConverter},ConverterParameter=PhoneForegroundBrush}" + Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}" + VerticalAlignment="Center" + Grid.Column="0" + /> + <forms:EntryCellPhoneTextBox + IsEnabled="{Binding IsEnabled}" + Hint="{Binding Placeholder}" + Text="{Binding Text, Mode=TwoWay}" + InputScope="{Binding Keyboard, Converter={StaticResource KeyboardConverter}}" + VerticalAlignment="Center" + TextAlignment="{Binding HorizontalTextAlignment, Converter={StaticResource HorizontalTextAlignmentConverter}}" + Grid.Column="1" + > + <i:Interaction.Triggers> + <i:EventTrigger EventName="KeyboardReturnPressed"> + <i:InvokeCommandAction Command="{StaticResource EntryCellRendererCompleted}" CommandParameter="{Binding}" /> + </i:EventTrigger> + </i:Interaction.Triggers> + </forms:EntryCellPhoneTextBox> + </Grid.Children> + </Grid> + </DataTemplate> + + <DataTemplate x:Key="SwitchCell"> + <toolkit:ToggleSwitch Content="{Binding Text}" Height="{Binding RenderHeight, Converter={StaticResource HeightConverter}}" + IsChecked="{Binding On, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}" FontFamily="{StaticResource PhoneFontFamilyNormal}" /> + </DataTemplate> + + <DataTemplate x:Key="TabbedPage"> + <forms:TabbedPagePresenter Content="{Binding Converter={StaticResource PageToRenderer}}" /> + </DataTemplate> + + <DataTemplate x:Key="TabbedPageHeader"> + <TextBlock Text="{Binding Title, Converter={StaticResource LowerConverter}}" /> + </DataTemplate> + + <DataTemplate x:Key="CarouselPage"> + <forms:CarouselPagePresenter Content="{Binding Converter={StaticResource PageToRenderer}}" /> + </DataTemplate> + + <DataTemplate x:Key="PickerItemTemplate"> + <StackPanel> + <TextBlock Text="{Binding Data}" Opacity="{Binding Opacity}"/> + </StackPanel> + </DataTemplate> + + <DataTemplate x:Key="PickerFullItemTemplate"> + <StackPanel MaxHeight="{Binding MaxHeight}"> + <TextBlock Text="{Binding Data}" FontSize="43" /> + </StackPanel> + </DataTemplate> + + <!-- Overriding the PhoneTextBox Style so we can handle Placeholder alignment --> + <forms:TextAlignmentToHorizontalAlignmentConverter x:Key="AlignmentConverter" /> + <Style TargetType="forms:FormsPhoneTextBox"> + <Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}" /> + <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMediumLarge}" /> + <Setter Property="Background" Value="{StaticResource PhoneTextBoxBrush}" /> + <Setter Property="Foreground" Value="{StaticResource PhoneTextBoxForegroundBrush}" /> + <Setter Property="BorderBrush" Value="{StaticResource PhoneTextBoxBrush}" /> + <Setter Property="SelectionBackground" Value="{StaticResource PhoneAccentBrush}" /> + <Setter Property="SelectionForeground" Value="{StaticResource PhoneTextBoxSelectionForegroundBrush}" /> + <Setter Property="BorderThickness" Value="{StaticResource PhoneBorderThickness}" /> + <Setter Property="Padding" Value="{StaticResource PhoneBorderThickness}" /> + <Setter Property="PlaceholderForegroundBrush" Value="{StaticResource PhoneTextBoxReadOnlyBrush}" /> + + <Setter Property="Template"> + <Setter.Value> + + <ControlTemplate TargetType="forms:FormsPhoneTextBox"> + + <Grid Background="Transparent" x:Name="RootGrid"> + <VisualStateManager.VisualStateGroups> + <VisualStateGroup x:Name="CommonStates"> + <VisualState x:Name="Normal" /> + <VisualState x:Name="Disabled"> + <Storyboard> + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" + Storyboard.TargetName="HintBorder"> + <DiscreteObjectKeyFrame KeyTime="0"> + <DiscreteObjectKeyFrame.Value> + <Visibility>Collapsed</Visibility> + </DiscreteObjectKeyFrame.Value> + </DiscreteObjectKeyFrame> + </ObjectAnimationUsingKeyFrames> + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" + Storyboard.TargetName="TextBorder"> + <DiscreteObjectKeyFrame KeyTime="0"> + <DiscreteObjectKeyFrame.Value> + <Visibility>Visible</Visibility> + </DiscreteObjectKeyFrame.Value> + </DiscreteObjectKeyFrame> + </ObjectAnimationUsingKeyFrames> + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" + Storyboard.TargetName="TextBorder"> + <DiscreteObjectKeyFrame KeyTime="0" Value="Transparent" /> + </ObjectAnimationUsingKeyFrames> + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" + Storyboard.TargetName="TextBorder"> + <DiscreteObjectKeyFrame KeyTime="0" Value="0" /> + </ObjectAnimationUsingKeyFrames> + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="HorizontalAlignment" + Storyboard.TargetName="Text"> + <DiscreteObjectKeyFrame KeyTime="0"> + <DiscreteObjectKeyFrame.Value> + <HorizontalAlignment>Stretch</HorizontalAlignment> + </DiscreteObjectKeyFrame.Value> + </DiscreteObjectKeyFrame> + </ObjectAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + </VisualStateGroup> + <VisualStateGroup x:Name="FocusStates"> + <VisualState x:Name="Focused"> + <Storyboard> + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" + Storyboard.TargetName="HintBorder"> + <DiscreteObjectKeyFrame KeyTime="0" + Value="{StaticResource PhoneTextBoxEditBackgroundBrush}" /> + </ObjectAnimationUsingKeyFrames> + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" + Storyboard.TargetName="HintBorder"> + <DiscreteObjectKeyFrame KeyTime="0" + Value="{StaticResource PhoneTextBoxEditBorderBrush}" /> + </ObjectAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + <VisualState x:Name="Unfocused" /> + </VisualStateGroup> + <VisualStateGroup x:Name="LengthIndicatorStates"> + <VisualState x:Name="LengthIndicatorVisible"> + <Storyboard> + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" + Storyboard.TargetName="LengthIndicator"> + <DiscreteObjectKeyFrame KeyTime="0:0:0"> + <DiscreteObjectKeyFrame.Value> + <Visibility>Visible</Visibility> + </DiscreteObjectKeyFrame.Value> + </DiscreteObjectKeyFrame> + </ObjectAnimationUsingKeyFrames> + + <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" + Storyboard.TargetProperty="Margin"> + <DiscreteObjectKeyFrame KeyTime="0:0:0" + Value="0, 0, 0, 27" /> + </ObjectAnimationUsingKeyFrames> + + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" + Storyboard.TargetName="LengthIndicator"> + <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="0.6" /> + </ObjectAnimationUsingKeyFrames> + + <DoubleAnimation Storyboard.TargetName="LengthIndicator" + Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)" + To="32" + Duration="0:0:0.350"> + <DoubleAnimation.EasingFunction> + <ExponentialEase Exponent="6" /> + </DoubleAnimation.EasingFunction> + </DoubleAnimation> + </Storyboard> + </VisualState> + <VisualState x:Name="LengthIndicatorHidden"> + <Storyboard> + <DoubleAnimation Storyboard.TargetName="LengthIndicator" + Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)" + To="0" + Duration="0:0:0.350"> + <DoubleAnimation.EasingFunction> + <ExponentialEase Exponent="6" /> + </DoubleAnimation.EasingFunction> + </DoubleAnimation> + + <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" + Storyboard.TargetProperty="Margin"> + <DiscreteObjectKeyFrame KeyTime="0:0:0" + Value="0, 0, 0, 0" /> + </ObjectAnimationUsingKeyFrames> + + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" + Storyboard.TargetName="LengthIndicator"> + <DiscreteObjectKeyFrame KeyTime="0:0:0.350" Value="0" /> + </ObjectAnimationUsingKeyFrames> + + <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" + Storyboard.TargetName="LengthIndicator"> + <DiscreteObjectKeyFrame KeyTime="0:0:0.350"> + <DiscreteObjectKeyFrame.Value> + <Visibility>Collapsed</Visibility> + </DiscreteObjectKeyFrame.Value> + </DiscreteObjectKeyFrame> + </ObjectAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + </VisualStateGroup> + </VisualStateManager.VisualStateGroups> + + <Border x:Name="LengthIndicatorBorder"> + <TextBlock Foreground="{StaticResource PhoneContrastBackgroundBrush}" + HorizontalAlignment="Right" + TextAlignment="Right" + VerticalAlignment="Bottom" + Margin="{StaticResource PhoneMargin}" + FontSize="{StaticResource PhoneFontSizeNormal}" + Opacity="0" + x:Name="LengthIndicator"> + <TextBlock.RenderTransform> + <TranslateTransform /> + </TextBlock.RenderTransform> + </TextBlock> + </Border> + + <Border x:Name="HintBorder" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + Background="{TemplateBinding Background}" + Margin="{StaticResource PhoneTouchTargetOverhang}"> + <Grid> + <ContentControl x:Name="HintContent" + Style="{TemplateBinding HintStyle}" + Content="{TemplateBinding Hint}" + Foreground="{TemplateBinding PlaceholderForegroundBrush}" + Background="Transparent" + HorizontalAlignment="{Binding TextAlignment, + RelativeSource={RelativeSource Mode=TemplatedParent}, + Converter={StaticResource AlignmentConverter}}" + VerticalAlignment="Center" + Margin="3,0,3,0" + Visibility="{TemplateBinding ActualHintVisibility}" /> + <ContentControl x:Name="ContentElement" + BorderThickness="0" + HorizontalContentAlignment="Stretch" + Margin="{StaticResource PhoneTextBoxInnerMargin}" + Padding="{TemplateBinding Padding}" + VerticalContentAlignment="Stretch" /> + </Grid> + </Border> + <Border x:Name="TextBorder" + BorderBrush="{StaticResource PhoneDisabledBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + Background="Transparent" + Margin="{StaticResource PhoneTouchTargetOverhang}" + Visibility="Collapsed"> + <TextBox x:Name="Text" + Foreground="{StaticResource PhoneDisabledBrush}" + FontWeight="{TemplateBinding FontWeight}" + FontStyle="{TemplateBinding FontStyle}" + FontSize="{TemplateBinding FontSize}" + FontFamily="{TemplateBinding FontFamily}" + SelectionForeground="{TemplateBinding SelectionForeground}" + SelectionBackground="{TemplateBinding SelectionBackground}" + TextAlignment="{TemplateBinding TextAlignment}" + TextWrapping="{TemplateBinding TextWrapping}" + Text="{TemplateBinding DisabledText}" + + HorizontalAlignment="Left" /> + </Border> + <Border x:Name="ActionIconBorder" + Width="84" + Height="72" + Background="Transparent" + HorizontalAlignment="Right" + VerticalAlignment="Bottom"> + <Image x:Name="ActionIcon" + Width="26" + Height="26" + Source="{TemplateBinding ActionIcon}" /> + </Border> + + <TextBlock x:Name="MeasurementTextBlock" + Margin="8" + IsHitTestVisible="False" + Opacity="0" + FontFamily="{TemplateBinding FontFamily}" + FontSize="{TemplateBinding FontSize}" + FontStretch="{TemplateBinding FontStretch}" + TextAlignment="{TemplateBinding TextAlignment}" + FontWeight="{TemplateBinding FontWeight}" + FontStyle="{TemplateBinding FontStyle}" + TextWrapping="{TemplateBinding TextWrapping}" + Text="{TemplateBinding Text}" /> + </Grid> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + +</ResourceDictionary> diff --git a/Xamarin.Forms.Platform.WP8/WebViewRenderer.cs b/Xamarin.Forms.Platform.WP8/WebViewRenderer.cs new file mode 100644 index 00000000..4b3151b0 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/WebViewRenderer.cs @@ -0,0 +1,166 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Navigation; +using Microsoft.Phone.Controls; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class WebViewRenderer : ViewRenderer<WebView, WebBrowser>, IWebViewRenderer + { + WebNavigationEvent _eventState; + bool _updating; + + public async void LoadHtml(string html, string baseUrl) + { + string fileName = string.Format("formslocal_{0}.html", DateTime.Now.Ticks); + ; + await SaveToIsoStore(fileName, html); + Control.Navigate(new Uri(fileName, UriKind.Relative)); + } + + public void LoadUrl(string url) + { + Control.Source = new Uri(url, UriKind.RelativeOrAbsolute); + } + + protected override void OnElementChanged(ElementChangedEventArgs<WebView> e) + { + base.OnElementChanged(e); + + if (Control == null) + { + var webBrowser = new WebBrowser(); + webBrowser.IsScriptEnabled = true; + webBrowser.Navigated += WebBrowserOnNavigated; + webBrowser.Navigating += WebBrowserOnNavigating; + webBrowser.NavigationFailed += WebBrowserOnNavigationFailed; + SetNativeControl(webBrowser); + } + + if (e.OldElement != null) + { + e.OldElement.EvalRequested -= OnEvalRequested; + e.OldElement.GoBackRequested -= OnGoBackRequested; + e.OldElement.GoForwardRequested -= OnGoForwardRequested; + Control.DataContext = null; + } + + if (e.NewElement != null) + { + e.NewElement.EvalRequested += OnEvalRequested; + e.NewElement.GoBackRequested += OnGoBackRequested; + e.NewElement.GoForwardRequested += OnGoForwardRequested; + Control.DataContext = e.NewElement; + } + + Load(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + switch (e.PropertyName) + { + case "Source": + if (!_updating) + Load(); + break; + } + } + + void Load() + { + if (Element.Source != null) + Element.Source.Load(this); + + UpdateCanGoBackForward(); + } + + void OnEvalRequested(object sender, EventArg<string> eventArg) + { + Control.Dispatcher.BeginInvoke(() => Control.InvokeScript("eval", eventArg.Data)); + } + + void OnGoBackRequested(object sender, EventArgs eventArgs) + { + if (Control.CanGoBack) + { + _eventState = WebNavigationEvent.Back; + Control.GoBack(); + } + + UpdateCanGoBackForward(); + } + + void OnGoForwardRequested(object sender, EventArgs eventArgs) + { + if (Control.CanGoForward) + { + _eventState = WebNavigationEvent.Forward; + Control.GoForward(); + } + UpdateCanGoBackForward(); + } + + async Task SaveToIsoStore(string fileName, string html) + { + IIsolatedStorageFile store = Device.PlatformServices.GetUserStoreForApplication(); + using(Stream file = await store.OpenFileAsync(fileName, FileMode.CreateNew, FileAccess.Write).ConfigureAwait(false)) + { + byte[] bytes = Encoding.UTF8.GetBytes(html); + await file.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + } + } + + void SendNavigated(UrlWebViewSource source, WebNavigationEvent evnt, WebNavigationResult result) + { + _updating = true; + ((IElementController)Element).SetValueFromRenderer(WebView.SourceProperty, source); + _updating = false; + + Element.SendNavigated(new WebNavigatedEventArgs(evnt, source, source.Url, result)); + + UpdateCanGoBackForward(); + _eventState = WebNavigationEvent.NewPage; + } + + // Nasty hack because we cant bind this because OneWayToSource isn't a thing in WP8, yay + void UpdateCanGoBackForward() + { + Element.CanGoBack = Control.CanGoBack; + Element.CanGoForward = Control.CanGoForward; + } + + void WebBrowserOnNavigated(object sender, System.Windows.Navigation.NavigationEventArgs navigationEventArgs) + { + string url = navigationEventArgs.Uri.IsAbsoluteUri ? navigationEventArgs.Uri.AbsoluteUri : navigationEventArgs.Uri.OriginalString; + SendNavigated(new UrlWebViewSource { Url = url }, _eventState, WebNavigationResult.Success); + + UpdateCanGoBackForward(); + } + + void WebBrowserOnNavigating(object sender, NavigatingEventArgs navigatingEventArgs) + { + string url = navigatingEventArgs.Uri.IsAbsoluteUri ? navigatingEventArgs.Uri.AbsoluteUri : navigatingEventArgs.Uri.OriginalString; + var args = new WebNavigatingEventArgs(_eventState, new UrlWebViewSource { Url = url }, url); + + Element.SendNavigating(args); + + navigatingEventArgs.Cancel = args.Cancel; + + // reset in this case because this is the last event we will get + if (args.Cancel) + _eventState = WebNavigationEvent.NewPage; + } + + void WebBrowserOnNavigationFailed(object sender, NavigationFailedEventArgs navigationFailedEventArgs) + { + string url = navigationFailedEventArgs.Uri.IsAbsoluteUri ? navigationFailedEventArgs.Uri.AbsoluteUri : navigationFailedEventArgs.Uri.OriginalString; + SendNavigated(new UrlWebViewSource { Url = url }, _eventState, WebNavigationResult.Failure); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/WinPhoneTicker.cs b/Xamarin.Forms.Platform.WP8/WinPhoneTicker.cs new file mode 100644 index 00000000..94b17246 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/WinPhoneTicker.cs @@ -0,0 +1,26 @@ +using System; +using System.Windows.Threading; + +namespace Xamarin.Forms +{ + internal class WinPhoneTicker : Ticker + { + readonly DispatcherTimer _timer; + + public WinPhoneTicker() + { + _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(15) }; + _timer.Tick += (sender, args) => SendSignals(); + } + + protected override void DisableTimer() + { + _timer.Stop(); + } + + protected override void EnableTimer() + { + _timer.Start(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/Xamarin.Forms.Platform.WP8.csproj b/Xamarin.Forms.Platform.WP8/Xamarin.Forms.Platform.WP8.csproj new file mode 100644 index 00000000..29ebeae0 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/Xamarin.Forms.Platform.WP8.csproj @@ -0,0 +1,245 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>10.0.20506</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{517B6AE0-792B-4665-9376-5CA33E539181}</ProjectGuid> + <ProjectTypeGuids>{C089C8C0-30E0-4E22-80C0-CE093F111A43};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Xamarin.Forms.Platform.WinPhone</RootNamespace> + <AssemblyName>Xamarin.Forms.Platform.WP8</AssemblyName> + <TargetFrameworkIdentifier>WindowsPhone</TargetFrameworkIdentifier> + <TargetFrameworkVersion>v8.0</TargetFrameworkVersion> + <SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion> + <SilverlightApplication>false</SilverlightApplication> + <ValidateXaml>true</ValidateXaml> + <MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion> + <ThrowErrorsInValidation>true</ThrowErrorsInValidation> + <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> + <RestorePackages>true</RestorePackages> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>Bin\Debug</OutputPath> + <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants> + <NoStdLib>true</NoStdLib> + <NoConfig>true</NoConfig> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>Bin\Release</OutputPath> + <DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants> + <NoStdLib>true</NoStdLib> + <NoConfig>true</NoConfig> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>Bin\x86\Debug</OutputPath> + <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants> + <NoStdLib>true</NoStdLib> + <NoConfig>true</NoConfig> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>Bin\x86\Release</OutputPath> + <DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants> + <NoStdLib>true</NoStdLib> + <NoConfig>true</NoConfig> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|ARM' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>Bin\ARM\Debug</OutputPath> + <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants> + <NoStdLib>true</NoStdLib> + <NoConfig>true</NoConfig> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|ARM' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>Bin\ARM\Release</OutputPath> + <DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants> + <NoStdLib>true</NoStdLib> + <NoConfig>true</NoConfig> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Turkey|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <OutputPath>bin\Turkey\</OutputPath> + <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants> + <NoStdLib>true</NoStdLib> + <DebugType>full</DebugType> + <PlatformTarget>AnyCPU</PlatformTarget> + <ErrorReport>prompt</ErrorReport> + <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Turkey|x86'"> + <DebugSymbols>true</DebugSymbols> + <OutputPath>bin\x86\Turkey\</OutputPath> + <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants> + <NoStdLib>true</NoStdLib> + <DebugType>full</DebugType> + <PlatformTarget>AnyCPU</PlatformTarget> + <ErrorReport>prompt</ErrorReport> + <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Turkey|ARM'"> + <DebugSymbols>true</DebugSymbols> + <OutputPath>bin\ARM\Turkey\</OutputPath> + <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants> + <NoStdLib>true</NoStdLib> + <DebugType>full</DebugType> + <PlatformTarget>AnyCPU</PlatformTarget> + <ErrorReport>prompt</ErrorReport> + <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + <ItemGroup> + <Compile Include="..\Xamarin.Forms.Core\Properties\GlobalAssemblyInfo.cs"> + <Link>Properties\GlobalAssemblyInfo.cs</Link> + </Compile> + <Compile Include="ActivityIndicatorRenderer.cs" /> + <Compile Include="AlignmentExtensions.cs" /> + <Compile Include="Animatable.cs" /> + <Compile Include="AsyncValue.cs" /> + <Compile Include="CellControl.cs" /> + <Compile Include="CollapseWhenEmptyConverter.cs" /> + <Compile Include="Deserializer.cs" /> + <Compile Include="CustomContextMenu.cs" /> + <Compile Include="ElementChangedEventArgs.cs" /> + <Compile Include="ExportCellAttribute.cs" /> + <Compile Include="ExportImageSourceHandlerAttribute.cs" /> + <Compile Include="ExportRendererAttribute.cs" /> + <Compile Include="Extensions.cs" /> + <Compile Include="FontExtensions.cs" /> + <Compile Include="Forms.cs" /> + <Compile Include="FormsApplicationPage.cs" /> + <Compile Include="FormsListPicker.cs" /> + <Compile Include="FormsPhoneTextBox.cs" /> + <Compile Include="FrameworkElementExtensions.cs" /> + <Compile Include="LayoutExtensions.cs" /> + <Compile Include="CarouselViewRenderer.cs" /> + <Compile Include="MD5.cs" /> + <Compile Include="MD5CryptoServiceProvider.cs" /> + <Compile Include="NativeViewWrapper.cs" /> + <Compile Include="NativeViewWrapperRenderer.cs" /> + <Compile Include="PageToRendererConverter.cs" /> + <Compile Include="PlatformEffect.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="BoxViewRenderer.cs" /> + <Compile Include="ButtonRenderer.cs" /> + <Compile Include="CarouselPageRenderer.cs" /> + <Compile Include="Converters\CaseConverter.cs" /> + <Compile Include="Converters\ColorConverter.cs" /> + <Compile Include="Converters\ImageConverter.cs" /> + <Compile Include="Converters\KeyboardConverter.cs" /> + <Compile Include="Converters\HorizontalTextAlignmentConverter.cs" /> + <Compile Include="CellTemplateSelector.cs" /> + <Compile Include="ConvertExtensions.cs" /> + <Compile Include="DataTemplateSelector.cs" /> + <Compile Include="DatePickerRenderer.cs" /> + <Compile Include="EditorRenderer.cs" /> + <Compile Include="EntryRenderer.cs" /> + <Compile Include="FrameRenderer.cs" /> + <Compile Include="ICellRenderer.cs" /> + <Compile Include="ImageRenderer.cs" /> + <Compile Include="IVisualElementRenderer.cs" /> + <Compile Include="LabelRenderer.cs" /> + <Compile Include="ListViewRenderer.cs" /> + <Compile Include="MasterDetailRenderer.cs" /> + <Compile Include="NavigationMenuRenderer.cs" /> + <Compile Include="NavigationPageRenderer.cs" /> + <Compile Include="PageRenderer.cs" /> + <Compile Include="Platform.cs" /> + <Compile Include="ProgressBarRenderer.cs" /> + <Compile Include="RendererFactory.cs" /> + <Compile Include="ResourcesProvider.cs" /> + <Compile Include="ScrollViewRenderer.cs" /> + <Compile Include="SearchBarRenderer.cs" /> + <Compile Include="SliderRenderer.cs" /> + <Compile Include="StepperRenderer.cs" /> + <Compile Include="SwitchRenderer.cs" /> + <Compile Include="TabbedPageRenderer.cs" /> + <Compile Include="TableView.xaml.cs"> + <DependentUpon>TableView.xaml</DependentUpon> + </Compile> + <Compile Include="TableViewRenderer.cs" /> + <Compile Include="TextAlignmentToHorizontalAlignmentConverter.cs" /> + <Compile Include="TextCellRenderer.cs" /> + <Compile Include="TimePickerRenderer.cs" /> + <Compile Include="ViewExtensions.cs" /> + <Compile Include="VisualElementPackager.cs" /> + <Compile Include="ViewRenderer.cs" /> + <Compile Include="ViewToRendererConverter.cs" /> + <Compile Include="VisualElementRenderer.cs" /> + <Compile Include="VisualElementTracker.cs" /> + <Compile Include="WebViewRenderer.cs" /> + <Compile Include="PickerRenderer.cs" /> + <Compile Include="WinPhoneTicker.cs" /> + <Compile Include="WP8PlatformServices.cs" /> + </ItemGroup> + <ItemGroup> + <Reference Include="Microsoft.Phone.Controls, Version=8.0.0.0, Culture=neutral, PublicKeyToken=24eec0d8c86cda1e, processorArchitecture=MSIL" /> + <Reference Include="Microsoft.Phone.Controls.Toolkit"> + <HintPath>..\packages\WPtoolkit.4.2013.08.16\lib\wp8\Microsoft.Phone.Controls.Toolkit.dll</HintPath> + </Reference> + <Reference Include="System.Windows.Interactivity, Version=3.9.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + <Content Include="ServiceReferences.ClientConfig" /> + </ItemGroup> + <ItemGroup> + <Content Include="Toolkit.Content\ApplicationBar.Cancel.png" /> + <Content Include="Toolkit.Content\ApplicationBar.Check.png" /> + <Content Include="Toolkit.Content\ApplicationBar.Delete.png" /> + <Content Include="Toolkit.Content\ApplicationBar.Select.png" /> + </ItemGroup> + <ItemGroup> + <Page Include="TableView.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="WPResources.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj"> + <Project>{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}</Project> + <Name>Xamarin.Forms.Core</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildExtensionsPath)\Microsoft\$(TargetFrameworkIdentifier)\$(TargetFrameworkVersion)\Microsoft.$(TargetFrameworkIdentifier).$(TargetFrameworkVersion).Overrides.targets" /> + <Import Project="$(MSBuildExtensionsPath)\Microsoft\$(TargetFrameworkIdentifier)\$(TargetFrameworkVersion)\Microsoft.$(TargetFrameworkIdentifier).CSharp.targets" /> + <ProjectExtensions /> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.WP8/packages.config b/Xamarin.Forms.Platform.WP8/packages.config new file mode 100644 index 00000000..d534d6e7 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="WPtoolkit" version="4.2013.08.16" targetFramework="wp80" /> +</packages> |