diff options
Diffstat (limited to 'Xamarin.Forms.Core')
349 files changed, 27299 insertions, 0 deletions
diff --git a/Xamarin.Forms.Core/AbsoluteLayout.cs b/Xamarin.Forms.Core/AbsoluteLayout.cs new file mode 100644 index 00000000..51b5ca12 --- /dev/null +++ b/Xamarin.Forms.Core/AbsoluteLayout.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; + +namespace Xamarin.Forms +{ + public class AbsoluteLayout : Layout<View> + { + public static readonly BindableProperty LayoutFlagsProperty = BindableProperty.CreateAttached("LayoutFlags", typeof(AbsoluteLayoutFlags), typeof(AbsoluteLayout), AbsoluteLayoutFlags.None); + + public static readonly BindableProperty LayoutBoundsProperty = BindableProperty.CreateAttached("LayoutBounds", typeof(Rectangle), typeof(AbsoluteLayout), new Rectangle(0, 0, AutoSize, AutoSize)); + + readonly AbsoluteElementCollection _children; + + public AbsoluteLayout() + { + _children = new AbsoluteElementCollection(InternalChildren, this); + } + + public static double AutoSize + { + get { return -1; } + } + + public new IAbsoluteList<View> Children + { + get { return _children; } + } + + [TypeConverter(typeof(BoundsTypeConverter))] + public static Rectangle GetLayoutBounds(BindableObject bindable) + { + return (Rectangle)bindable.GetValue(LayoutBoundsProperty); + } + + public static AbsoluteLayoutFlags GetLayoutFlags(BindableObject bindable) + { + return (AbsoluteLayoutFlags)bindable.GetValue(LayoutFlagsProperty); + } + + public static void SetLayoutBounds(BindableObject bindable, Rectangle bounds) + { + bindable.SetValue(LayoutBoundsProperty, bounds); + } + + public static void SetLayoutFlags(BindableObject bindable, AbsoluteLayoutFlags flags) + { + bindable.SetValue(LayoutFlagsProperty, flags); + } + + protected override void LayoutChildren(double x, double y, double width, double height) + { + foreach (View child in LogicalChildren) + { + Rectangle rect = ComputeLayoutForRegion(child, new Size(width, height)); + rect.X += x; + rect.Y += y; + + LayoutChildIntoBoundingRegion(child, rect); + } + } + + protected override void OnChildAdded(Element child) + { + base.OnChildAdded(child); + child.PropertyChanged += ChildOnPropertyChanged; + } + + protected override void OnChildRemoved(Element child) + { + child.PropertyChanged -= ChildOnPropertyChanged; + base.OnChildRemoved(child); + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + var bestFitSize = new Size(); + var minimum = new Size(); + foreach (View child in LogicalChildren) + { + SizeRequest desiredSize = ComputeBoundingRegionDesiredSize(child); + + bestFitSize.Width = Math.Max(bestFitSize.Width, desiredSize.Request.Width); + bestFitSize.Height = Math.Max(bestFitSize.Height, desiredSize.Request.Height); + minimum.Width = Math.Max(minimum.Width, desiredSize.Minimum.Width); + minimum.Height = Math.Max(minimum.Height, desiredSize.Minimum.Height); + } + + return new SizeRequest(bestFitSize, minimum); + } + + internal override void ComputeConstraintForView(View view) + { + AbsoluteLayoutFlags layoutFlags = GetLayoutFlags(view); + + if ((layoutFlags & AbsoluteLayoutFlags.SizeProportional) == AbsoluteLayoutFlags.SizeProportional) + { + view.ComputedConstraint = Constraint; + return; + } + + var result = LayoutConstraint.None; + Rectangle layoutBounds = GetLayoutBounds(view); + if ((layoutFlags & AbsoluteLayoutFlags.HeightProportional) != 0) + { + bool widthLocked = layoutBounds.Width != AutoSize; + result = Constraint & LayoutConstraint.VerticallyFixed; + if (widthLocked) + result |= LayoutConstraint.HorizontallyFixed; + } + else if ((layoutFlags & AbsoluteLayoutFlags.WidthProportional) != 0) + { + bool heightLocked = layoutBounds.Height != AutoSize; + result = Constraint & LayoutConstraint.HorizontallyFixed; + if (heightLocked) + result |= LayoutConstraint.VerticallyFixed; + } + else + { + if (layoutBounds.Width != AutoSize) + result |= LayoutConstraint.HorizontallyFixed; + if (layoutBounds.Height != AutoSize) + result |= LayoutConstraint.VerticallyFixed; + } + + view.ComputedConstraint = result; + } + + void ChildOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == LayoutFlagsProperty.PropertyName || e.PropertyName == LayoutBoundsProperty.PropertyName) + { + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + UpdateChildrenLayout(); + } + } + + static SizeRequest ComputeBoundingRegionDesiredSize(View view) + { + var width = 0.0; + var height = 0.0; + + var sizeRequest = new Lazy<SizeRequest>(() => view.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins)); + + Rectangle bounds = GetLayoutBounds(view); + AbsoluteLayoutFlags absFlags = GetLayoutFlags(view); + bool widthIsProportional = (absFlags & AbsoluteLayoutFlags.WidthProportional) != 0; + bool heightIsProportional = (absFlags & AbsoluteLayoutFlags.HeightProportional) != 0; + bool xIsProportional = (absFlags & AbsoluteLayoutFlags.XProportional) != 0; + bool yIsProportional = (absFlags & AbsoluteLayoutFlags.YProportional) != 0; + + // add in required x values + if (!xIsProportional) + { + width += bounds.X; + } + + if (!yIsProportional) + { + height += bounds.Y; + } + + double minWidth = width; + double minHeight = height; + + if (!widthIsProportional && bounds.Width != AutoSize) + { + // fixed size + width += bounds.Width; + minWidth += bounds.Width; + } + else if (!widthIsProportional) + { + // auto size + width += sizeRequest.Value.Request.Width; + minWidth += sizeRequest.Value.Minimum.Width; + } + else + { + // proportional size + width += sizeRequest.Value.Request.Width / Math.Max(0.25, bounds.Width); + //minWidth += 0; + } + + if (!heightIsProportional && bounds.Height != AutoSize) + { + // fixed size + height += bounds.Height; + minHeight += bounds.Height; + } + else if (!heightIsProportional) + { + // auto size + height += sizeRequest.Value.Request.Height; + minHeight += sizeRequest.Value.Minimum.Height; + } + else + { + // proportional size + height += sizeRequest.Value.Request.Height / Math.Max(0.25, bounds.Height); + //minHeight += 0; + } + + return new SizeRequest(new Size(width, height), new Size(minWidth, minHeight)); + } + + static Rectangle ComputeLayoutForRegion(View view, Size region) + { + var result = new Rectangle(); + + SizeRequest sizeRequest; + Rectangle bounds = GetLayoutBounds(view); + AbsoluteLayoutFlags absFlags = GetLayoutFlags(view); + bool widthIsProportional = (absFlags & AbsoluteLayoutFlags.WidthProportional) != 0; + bool heightIsProportional = (absFlags & AbsoluteLayoutFlags.HeightProportional) != 0; + bool xIsProportional = (absFlags & AbsoluteLayoutFlags.XProportional) != 0; + bool yIsProportional = (absFlags & AbsoluteLayoutFlags.YProportional) != 0; + + if (widthIsProportional) + { + result.Width = Math.Round(region.Width * bounds.Width); + } + else if (bounds.Width != AutoSize) + { + result.Width = bounds.Width; + } + + if (heightIsProportional) + { + result.Height = Math.Round(region.Height * bounds.Height); + } + else if (bounds.Height != AutoSize) + { + result.Height = bounds.Height; + } + + if (!widthIsProportional && bounds.Width == AutoSize) + { + if (!heightIsProportional && bounds.Width == AutoSize) + { + // Width and Height are auto + sizeRequest = view.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins); + result.Width = sizeRequest.Request.Width; + result.Height = sizeRequest.Request.Height; + } + else + { + // Only width is auto + sizeRequest = view.Measure(region.Width, result.Height, MeasureFlags.IncludeMargins); + result.Width = sizeRequest.Request.Width; + } + } + else if (!heightIsProportional && bounds.Height == AutoSize) + { + // Only height is auto + sizeRequest = view.Measure(result.Width, region.Height, MeasureFlags.IncludeMargins); + result.Height = sizeRequest.Request.Height; + } + + if (xIsProportional) + { + result.X = Math.Round((region.Width - result.Width) * bounds.X); + } + else + { + result.X = bounds.X; + } + + if (yIsProportional) + { + result.Y = Math.Round((region.Height - result.Height) * bounds.Y); + } + else + { + result.Y = bounds.Y; + } + + return result; + } + + public interface IAbsoluteList<T> : IList<T> where T : View + { + void Add(View view, Rectangle bounds, AbsoluteLayoutFlags flags = AbsoluteLayoutFlags.None); + + void Add(View view, Point position); + } + + class AbsoluteElementCollection : ElementCollection<View>, IAbsoluteList<View> + { + public AbsoluteElementCollection(ObservableCollection<Element> inner, AbsoluteLayout parent) : base(inner) + { + Parent = parent; + } + + internal AbsoluteLayout Parent { get; set; } + + public void Add(View view, Rectangle bounds, AbsoluteLayoutFlags flags = AbsoluteLayoutFlags.None) + { + SetLayoutBounds(view, bounds); + SetLayoutFlags(view, flags); + Add(view); + } + + public void Add(View view, Point position) + { + SetLayoutBounds(view, new Rectangle(position.X, position.Y, AutoSize, AutoSize)); + Add(view); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/AbsoluteLayoutFlags.cs b/Xamarin.Forms.Core/AbsoluteLayoutFlags.cs new file mode 100644 index 00000000..dcc81c10 --- /dev/null +++ b/Xamarin.Forms.Core/AbsoluteLayoutFlags.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + [Flags] + public enum AbsoluteLayoutFlags + { + None = 0, + XProportional = 1 << 0, + YProportional = 1 << 1, + WidthProportional = 1 << 2, + HeightProportional = 1 << 3, + PositionProportional = 1 | 1 << 1, + SizeProportional = 1 << 2 | 1 << 3, + All = ~0 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ActionSheetArguments.cs b/Xamarin.Forms.Core/ActionSheetArguments.cs new file mode 100644 index 00000000..3417ed61 --- /dev/null +++ b/Xamarin.Forms.Core/ActionSheetArguments.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + internal class ActionSheetArguments + { + public ActionSheetArguments(string title, string cancel, string destruction, IEnumerable<string> buttons) + { + Title = title; + Cancel = cancel; + Destruction = destruction; + Buttons = buttons; + Result = new TaskCompletionSource<string>(); + } + + /// <summary> + /// Gets titles of any buttons on the action sheet that aren't <see cref="Cancel" /> or <see cref="Destruction" />. Can + /// be <c>null</c>. + /// </summary> + public IEnumerable<string> Buttons { get; private set; } + + /// <summary> + /// Gets the text for a cancel button. Can be null. + /// </summary> + public string Cancel { get; private set; } + + /// <summary> + /// Gets the text for a destructive button. Can be null. + /// </summary> + public string Destruction { get; private set; } + + public TaskCompletionSource<string> Result { get; } + + /// <summary> + /// Gets the title for the action sheet. Can be null. + /// </summary> + public string Title { get; private set; } + + public void SetResult(string result) + { + Result.TrySetResult(result); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ActivityIndicator.cs b/Xamarin.Forms.Core/ActivityIndicator.cs new file mode 100644 index 00000000..3689609a --- /dev/null +++ b/Xamarin.Forms.Core/ActivityIndicator.cs @@ -0,0 +1,24 @@ +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_ActivityIndicatorRenderer))] + public class ActivityIndicator : View + { + public static readonly BindableProperty IsRunningProperty = BindableProperty.Create("IsRunning", typeof(bool), typeof(ActivityIndicator), default(bool)); + + public static readonly BindableProperty ColorProperty = BindableProperty.Create("Color", typeof(Color), typeof(ActivityIndicator), Color.Default); + + public Color Color + { + get { return (Color)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + public bool IsRunning + { + get { return (bool)GetValue(IsRunningProperty); } + set { SetValue(IsRunningProperty, value); } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/AlertArguments.cs b/Xamarin.Forms.Core/AlertArguments.cs new file mode 100644 index 00000000..87224cb4 --- /dev/null +++ b/Xamarin.Forms.Core/AlertArguments.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + internal class AlertArguments + { + public AlertArguments(string title, string message, string accept, string cancel) + { + Title = title; + Message = message; + Accept = accept; + Cancel = cancel; + Result = new TaskCompletionSource<bool>(); + } + + /// <summary> + /// Gets the text for the accept button. Can be null. + /// </summary> + public string Accept { get; private set; } + + /// <summary> + /// Gets the text of the cancel button. + /// </summary> + public string Cancel { get; private set; } + + /// <summary> + /// Gets the message for the alert. Can be null. + /// </summary> + public string Message { get; private set; } + + public TaskCompletionSource<bool> Result { get; } + + /// <summary> + /// Gets the title for the alert. Can be null. + /// </summary> + public string Title { get; private set; } + + public void SetResult(bool result) + { + Result.TrySetResult(result); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/AnimatableKey.cs b/Xamarin.Forms.Core/AnimatableKey.cs new file mode 100644 index 00000000..2a73ef6d --- /dev/null +++ b/Xamarin.Forms.Core/AnimatableKey.cs @@ -0,0 +1,82 @@ +using System; + +namespace Xamarin.Forms +{ + internal class AnimatableKey + { + public AnimatableKey(IAnimatable animatable, string handle) + { + if (animatable == null) + { + throw new ArgumentNullException(nameof(animatable)); + } + + if (string.IsNullOrEmpty(handle)) + { + throw new ArgumentException("Argument is null or empty", nameof(handle)); + } + + Animatable = new WeakReference<IAnimatable>(animatable); + Handle = handle; + } + + public WeakReference<IAnimatable> Animatable { get; } + + public string Handle { get; } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != GetType()) + { + return false; + } + return Equals((AnimatableKey)obj); + } + + public override int GetHashCode() + { + unchecked + { + IAnimatable target; + if (!Animatable.TryGetTarget(out target)) + { + return Handle?.GetHashCode() ?? 0; + } + + return ((target?.GetHashCode() ?? 0) * 397) ^ (Handle?.GetHashCode() ?? 0); + } + } + + protected bool Equals(AnimatableKey other) + { + if (!string.Equals(Handle, other.Handle)) + { + return false; + } + + IAnimatable thisAnimatable; + + if (!Animatable.TryGetTarget(out thisAnimatable)) + { + return false; + } + + IAnimatable thatAnimatable; + + if (!other.Animatable.TryGetTarget(out thatAnimatable)) + { + return false; + } + + return Equals(thisAnimatable, thatAnimatable); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Animation.cs b/Xamarin.Forms.Core/Animation.cs new file mode 100644 index 00000000..03cded1e --- /dev/null +++ b/Xamarin.Forms.Core/Animation.cs @@ -0,0 +1,138 @@ +// +// Tweener.cs +// +// Author: +// Jason Smith <jason.smith@xamarin.com> +// +// Copyright (c) 2012 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.Collections; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + public class Animation : IEnumerable + { + readonly List<Animation> _children; + readonly Easing _easing; + readonly Action _finished; + readonly Action<double> _step; + double _beginAt; + double _finishAt; + bool _finishedTriggered; + + public Animation() + { + _children = new List<Animation>(); + _easing = Easing.Linear; + _step = f => { }; + } + + public Animation(Action<double> callback, double start = 0.0f, double end = 1.0f, Easing easing = null, Action finished = null) + { + _children = new List<Animation>(); + _easing = easing ?? Easing.Linear; + _finished = finished; + + Func<double, double> transform = AnimationExtensions.Interpolate(start, end); + _step = f => callback(transform(f)); + } + + public IEnumerator GetEnumerator() + { + return _children.GetEnumerator(); + } + + public void Add(double beginAt, double finishAt, Animation animation) + { + if (beginAt < 0 || beginAt > 1) + throw new ArgumentOutOfRangeException("beginAt"); + + if (finishAt < 0 || finishAt > 1) + throw new ArgumentOutOfRangeException("finishAt"); + + if (finishAt <= beginAt) + throw new ArgumentException("finishAt must be greater than beginAt"); + + animation._beginAt = beginAt; + animation._finishAt = finishAt; + _children.Add(animation); + } + + public void Commit(IAnimatable owner, string name, uint rate = 16, uint length = 250, Easing easing = null, Action<double, bool> finished = null, Func<bool> repeat = null) + { + owner.Animate(name, this, rate, length, easing, finished, repeat); + } + + public Action<double> GetCallback() + { + Action<double> result = f => + { + _step(_easing.Ease(f)); + foreach (Animation animation in _children) + { + if (animation._finishedTriggered) + continue; + + double val = Math.Max(0.0f, Math.Min(1.0f, (f - animation._beginAt) / (animation._finishAt - animation._beginAt))); + + if (val <= 0.0f) // not ready to process yet + continue; + + Action<double> callback = animation.GetCallback(); + callback(val); + + if (val >= 1.0f) + { + animation._finishedTriggered = true; + if (animation._finished != null) + animation._finished(); + } + } + }; + return result; + } + + public Animation Insert(double beginAt, double finishAt, Animation animation) + { + Add(beginAt, finishAt, animation); + return this; + } + + public Animation WithConcurrent(Animation animation, double beginAt = 0.0f, double finishAt = 1.0f) + { + animation._beginAt = beginAt; + animation._finishAt = finishAt; + _children.Add(animation); + return this; + } + + public Animation WithConcurrent(Action<double> callback, double start = 0.0f, double end = 1.0f, Easing easing = null, double beginAt = 0.0f, double finishAt = 1.0f) + { + var child = new Animation(callback, start, end, easing); + child._beginAt = beginAt; + child._finishAt = finishAt; + _children.Add(child); + return this; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/AnimationExtensions.cs b/Xamarin.Forms.Core/AnimationExtensions.cs new file mode 100644 index 00000000..efc3e405 --- /dev/null +++ b/Xamarin.Forms.Core/AnimationExtensions.cs @@ -0,0 +1,259 @@ +// +// Tweener.cs +// +// Author: +// Jason Smith <jason.smith@xamarin.com> +// +// Copyright (c) 2012 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.Collections.Generic; + +namespace Xamarin.Forms +{ + public static class AnimationExtensions + { + static readonly Dictionary<AnimatableKey, Info> s_animations; + static readonly Dictionary<AnimatableKey, int> s_kinetics; + + static AnimationExtensions() + { + s_animations = new Dictionary<AnimatableKey, Info>(); + s_kinetics = new Dictionary<AnimatableKey, int>(); + } + + public static bool AbortAnimation(this IAnimatable self, string handle) + { + CheckAccess(); + + var key = new AnimatableKey(self, handle); + + return AbortAnimation(key) && AbortKinetic(key); + } + + public static void Animate(this IAnimatable self, string name, Animation animation, uint rate = 16, uint length = 250, Easing easing = null, Action<double, bool> finished = null, + Func<bool> repeat = null) + { + self.Animate(name, animation.GetCallback(), rate, length, easing, finished, repeat); + } + + public static void Animate(this IAnimatable self, string name, Action<double> callback, double start, double end, uint rate = 16, uint length = 250, Easing easing = null, + Action<double, bool> finished = null, Func<bool> repeat = null) + { + self.Animate(name, Interpolate(start, end), callback, rate, length, easing, finished, repeat); + } + + public static void Animate(this IAnimatable self, string name, Action<double> callback, uint rate = 16, uint length = 250, Easing easing = null, Action<double, bool> finished = null, + Func<bool> repeat = null) + { + self.Animate(name, x => x, callback, rate, length, easing, finished, repeat); + } + + public static void Animate<T>(this IAnimatable self, string name, Func<double, T> transform, Action<T> callback, uint rate = 16, uint length = 250, Easing easing = null, + Action<T, bool> finished = null, Func<bool> repeat = null) + { + if (transform == null) + throw new ArgumentNullException(nameof(transform)); + if (callback == null) + throw new ArgumentNullException(nameof(callback)); + if (self == null) + throw new ArgumentNullException(nameof(self)); + + CheckAccess(); + + var key = new AnimatableKey(self, name); + + AbortAnimation(key); + + Action<double> step = f => callback(transform(f)); + Action<double, bool> final = null; + if (finished != null) + final = (f, b) => finished(transform(f), b); + + var info = new Info { Rate = rate, Length = length, Easing = easing ?? Easing.Linear }; + + var tweener = new Tweener(info.Length); + tweener.Handle = key; + tweener.ValueUpdated += HandleTweenerUpdated; + tweener.Finished += HandleTweenerFinished; + + info.Tweener = tweener; + info.Callback = step; + info.Finished = final; + info.Repeat = repeat; + info.Owner = new WeakReference<IAnimatable>(self); + + s_animations[key] = info; + + info.Callback(0.0f); + tweener.Start(); + } + + public static void AnimateKinetic(this IAnimatable self, string name, Func<double, double, bool> callback, double velocity, double drag, Action finished = null) + { + CheckAccess(); + + var key = new AnimatableKey(self, name); + + AbortKinetic(key); + + double sign = velocity / Math.Abs(velocity); + velocity = Math.Abs(velocity); + + int tick = Ticker.Default.Insert(step => + { + long ms = step; + + velocity -= drag * ms; + velocity = Math.Max(0, velocity); + + var result = false; + if (velocity > 0) + { + result = callback(sign * velocity * ms, velocity); + } + + if (!result) + { + finished?.Invoke(); + s_kinetics.Remove(key); + } + return result; + }); + + s_kinetics[key] = tick; + } + + public static bool AnimationIsRunning(this IAnimatable self, string handle) + { + CheckAccess(); + + var key = new AnimatableKey(self, handle); + + return s_animations.ContainsKey(key); + } + + public static Func<double, double> Interpolate(double start, double end = 1.0f, double reverseVal = 0.0f, bool reverse = false) + { + double target = reverse ? reverseVal : end; + return x => start + (target - start) * x; + } + + static bool AbortAnimation(AnimatableKey key) + { + if (!s_animations.ContainsKey(key)) + { + return false; + } + + Info info = s_animations[key]; + info.Tweener.ValueUpdated -= HandleTweenerUpdated; + info.Tweener.Finished -= HandleTweenerFinished; + info.Tweener.Stop(); + info.Finished?.Invoke(1.0f, true); + + return s_animations.Remove(key); + } + + static bool AbortKinetic(AnimatableKey key) + { + if (!s_kinetics.ContainsKey(key)) + { + return false; + } + + Ticker.Default.Remove(s_kinetics[key]); + return s_kinetics.Remove(key); + } + + static void CheckAccess() + { + if (Device.IsInvokeRequired) + { + throw new InvalidOperationException("Animation operations must be invoked on the UI thread"); + } + } + + static void HandleTweenerFinished(object o, EventArgs args) + { + var tweener = o as Tweener; + Info info; + if (tweener != null && s_animations.TryGetValue(tweener.Handle, out info)) + { + var repeat = false; + if (info.Repeat != null) + repeat = info.Repeat(); + + IAnimatable owner; + if (info.Owner.TryGetTarget(out owner)) + owner.BatchBegin(); + info.Callback(tweener.Value); + + if (!repeat) + { + s_animations.Remove(tweener.Handle); + tweener.ValueUpdated -= HandleTweenerUpdated; + tweener.Finished -= HandleTweenerFinished; + } + + info.Finished?.Invoke(tweener.Value, false); + + if (info.Owner.TryGetTarget(out owner)) + owner.BatchCommit(); + + if (repeat) + { + tweener.Start(); + } + } + } + + static void HandleTweenerUpdated(object o, EventArgs args) + { + var tweener = o as Tweener; + Info info; + IAnimatable owner; + + if (tweener != null && s_animations.TryGetValue(tweener.Handle, out info) && info.Owner.TryGetTarget(out owner)) + { + owner.BatchBegin(); + info.Callback(info.Easing.Ease(tweener.Value)); + owner.BatchCommit(); + } + } + + class Info + { + public Action<double> Callback; + public Action<double, bool> Finished; + public Func<bool> Repeat; + public Tweener Tweener; + + public Easing Easing { get; set; } + + public uint Length { get; set; } + + public WeakReference<IAnimatable> Owner { get; set; } + + public uint Rate { get; set; } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Application.cs b/Xamarin.Forms.Core/Application.cs new file mode 100644 index 00000000..1dfe258b --- /dev/null +++ b/Xamarin.Forms.Core/Application.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + public class Application : Element, IResourcesProvider, IApplicationController + { + static Application s_current; + readonly Task<IDictionary<string, object>> _propertiesTask; + + bool _isSaving; + + ReadOnlyCollection<Element> _logicalChildren; + + Page _mainPage; + + ResourceDictionary _resources; + bool _saveAgain; + + protected Application() + { + var f = false; + if (f) + Loader.Load(); + NavigationProxy = new NavigationImpl(this); + Current = this; + _propertiesTask = GetPropertiesAsync(); + + SystemResources = DependencyService.Get<ISystemResourcesProvider>().GetSystemResources(); + SystemResources.ValuesChanged += OnParentResourcesChanged; + } + + public static Application Current + { + get { return s_current; } + internal set + { + if (s_current == value) + return; + if (value == null) + s_current = null; //Allow to reset current for unittesting + s_current = value; + } + } + + public Page MainPage + { + get { return _mainPage; } + set + { + if (value == null) + throw new ArgumentNullException("value"); + + if (_mainPage == value) + return; + + OnPropertyChanging(); + if (_mainPage != null) + { + InternalChildren.Remove(_mainPage); + _mainPage.Parent = null; + } + + _mainPage = value; + + if (_mainPage != null) + { + _mainPage.Parent = this; + _mainPage.NavigationProxy.Inner = NavigationProxy; + InternalChildren.Add(_mainPage); + } + OnPropertyChanged(); + } + } + + public IDictionary<string, object> Properties + { + get { return _propertiesTask.Result; } + } + + internal override ReadOnlyCollection<Element> LogicalChildren + { + get { return _logicalChildren ?? (_logicalChildren = new ReadOnlyCollection<Element>(InternalChildren)); } + } + + internal NavigationProxy NavigationProxy { get; } + + internal int PanGestureId { get; set; } + + internal IResourceDictionary SystemResources { get; } + + ObservableCollection<Element> InternalChildren { get; } = new ObservableCollection<Element>(); + + public ResourceDictionary Resources + { + get { return _resources; } + set + { + if (_resources == value) + return; + OnPropertyChanging(); + if (_resources != null) + ((IResourceDictionary)_resources).ValuesChanged -= OnResourcesChanged; + _resources = value; + OnResourcesChanged(value); + if (_resources != null) + ((IResourceDictionary)_resources).ValuesChanged += OnResourcesChanged; + OnPropertyChanged(); + } + } + + public event EventHandler<ModalPoppedEventArgs> ModalPopped; + + public event EventHandler<ModalPoppingEventArgs> ModalPopping; + + public event EventHandler<ModalPushedEventArgs> ModalPushed; + + public event EventHandler<ModalPushingEventArgs> ModalPushing; + + public async Task SavePropertiesAsync() + { + if (Device.IsInvokeRequired) + Device.BeginInvokeOnMainThread(async () => await SetPropertiesAsync()); + else + await SetPropertiesAsync(); + } + + protected override void OnParentSet() + { + throw new InvalidOperationException("Setting a Parent on Application is invalid."); + } + + protected virtual void OnResume() + { + } + + protected virtual void OnSleep() + { + } + + protected virtual void OnStart() + { + } + + internal static void ClearCurrent() + { + s_current = null; + } + + internal static bool IsApplicationOrNull(Element element) + { + return element == null || element is Application; + } + + internal override void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values) + { + if (Resources == null || Resources.Count == 0) + { + base.OnParentResourcesChanged(values); + return; + } + + var innerKeys = new HashSet<string>(); + var changedResources = new List<KeyValuePair<string, object>>(); + foreach (KeyValuePair<string, object> c in Resources) + innerKeys.Add(c.Key); + foreach (KeyValuePair<string, object> value in values) + { + if (innerKeys.Add(value.Key)) + changedResources.Add(value); + } + OnResourcesChanged(changedResources); + } + + internal event EventHandler PopCanceled; + + internal void SendResume() + { + s_current = this; + OnResume(); + } + + internal Task SendSleepAsync() + { + OnSleep(); + return SavePropertiesAsync(); + } + + internal void SendStart() + { + OnStart(); + } + + async Task<IDictionary<string, object>> GetPropertiesAsync() + { + var deserializer = DependencyService.Get<IDeserializer>(); + if (deserializer == null) + { + Log.Warning("Startup", "No IDeserialzier was found registered"); + return new Dictionary<string, object>(4); + } + + IDictionary<string, object> properties = await deserializer.DeserializePropertiesAsync().ConfigureAwait(false); + if (properties == null) + properties = new Dictionary<string, object>(4); + + return properties; + } + + void OnModalPopped(Page modalPage) + { + EventHandler<ModalPoppedEventArgs> handler = ModalPopped; + if (handler != null) + handler(this, new ModalPoppedEventArgs(modalPage)); + } + + bool OnModalPopping(Page modalPage) + { + EventHandler<ModalPoppingEventArgs> handler = ModalPopping; + var args = new ModalPoppingEventArgs(modalPage); + if (handler != null) + handler(this, args); + return args.Cancel; + } + + void OnModalPushed(Page modalPage) + { + EventHandler<ModalPushedEventArgs> handler = ModalPushed; + if (handler != null) + handler(this, new ModalPushedEventArgs(modalPage)); + } + + void OnModalPushing(Page modalPage) + { + EventHandler<ModalPushingEventArgs> handler = ModalPushing; + if (handler != null) + handler(this, new ModalPushingEventArgs(modalPage)); + } + + void OnPopCanceled() + { + EventHandler handler = PopCanceled; + if (handler != null) + handler(this, EventArgs.Empty); + } + + async Task SetPropertiesAsync() + { + if (_isSaving) + { + _saveAgain = true; + return; + } + _isSaving = true; + await DependencyService.Get<IDeserializer>().SerializePropertiesAsync(Properties); + if (_saveAgain) + await DependencyService.Get<IDeserializer>().SerializePropertiesAsync(Properties); + _isSaving = _saveAgain = false; + } + + class NavigationImpl : NavigationProxy + { + readonly Application _owner; + + public NavigationImpl(Application owner) + { + _owner = owner; + } + + protected override async Task<Page> OnPopModal(bool animated) + { + Page modal = ModalStack[ModalStack.Count - 1]; + if (_owner.OnModalPopping(modal)) + { + _owner.OnPopCanceled(); + return null; + } + Page result = await base.OnPopModal(animated); + result.Parent = null; + _owner.OnModalPopped(result); + return result; + } + + protected override async Task OnPushModal(Page modal, bool animated) + { + _owner.OnModalPushing(modal); + + modal.Parent = _owner; + + if (modal.NavigationProxy.ModalStack.Count == 0) + { + modal.NavigationProxy.Inner = this; + await base.OnPushModal(modal, animated); + } + else + { + await base.OnPushModal(modal, animated); + modal.NavigationProxy.Inner = this; + } + + _owner.OnModalPushed(modal); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Aspect.cs b/Xamarin.Forms.Core/Aspect.cs new file mode 100644 index 00000000..6a0085ca --- /dev/null +++ b/Xamarin.Forms.Core/Aspect.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public enum Aspect + { + AspectFit, + AspectFill, + Fill + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BackButtonPressedEventArgs.cs b/Xamarin.Forms.Core/BackButtonPressedEventArgs.cs new file mode 100644 index 00000000..2cc9a185 --- /dev/null +++ b/Xamarin.Forms.Core/BackButtonPressedEventArgs.cs @@ -0,0 +1,9 @@ +using System; + +namespace Xamarin.Forms +{ + public class BackButtonPressedEventArgs : EventArgs + { + public bool Handled { get; set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BaseMenuItem.cs b/Xamarin.Forms.Core/BaseMenuItem.cs new file mode 100644 index 00000000..58831aa5 --- /dev/null +++ b/Xamarin.Forms.Core/BaseMenuItem.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + public abstract class BaseMenuItem : Element + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BindableObject.cs b/Xamarin.Forms.Core/BindableObject.cs new file mode 100644 index 00000000..683d390a --- /dev/null +++ b/Xamarin.Forms.Core/BindableObject.cs @@ -0,0 +1,647 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms +{ + public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler + { + public static readonly BindableProperty BindingContextProperty = BindableProperty.Create("BindingContext", typeof(object), typeof(BindableObject), default(object), BindingMode.OneWay, null, + BindingContextPropertyBindingPropertyChanged, null, null, BindingContextPropertyBindingChanging); + + readonly List<BindablePropertyContext> _properties = new List<BindablePropertyContext>(4); + + bool _applying; + + object _inheritedContext; + + public object BindingContext + { + get { return _inheritedContext ?? GetValue(BindingContextProperty); } + set { SetValue(BindingContextProperty, value); } + } + + void IDynamicResourceHandler.SetDynamicResource(BindableProperty property, string key) + { + SetDynamicResource(property, key, false); + } + + public event PropertyChangedEventHandler PropertyChanged; + + public event EventHandler BindingContextChanged; + + public void ClearValue(BindableProperty property) + { + ClearValue(property, true); + } + + public void ClearValue(BindablePropertyKey propertyKey) + { + if (propertyKey == null) + throw new ArgumentNullException("propertyKey"); + + ClearValue(propertyKey.BindableProperty, false); + } + + public object GetValue(BindableProperty property) + { + if (property == null) + throw new ArgumentNullException("property"); + + BindablePropertyContext context = property.DefaultValueCreator != null ? GetOrCreateContext(property) : GetContext(property); + + if (context == null) + return property.DefaultValue; + + return context.Value; + } + + public event PropertyChangingEventHandler PropertyChanging; + + public void RemoveBinding(BindableProperty property) + { + if (property == null) + throw new ArgumentNullException("property"); + + BindablePropertyContext context = GetContext(property); + if (context == null || context.Binding == null) + return; + + RemoveBinding(property, context); + } + + public void SetBinding(BindableProperty targetProperty, BindingBase binding) + { + SetBinding(targetProperty, binding, false); + } + + public void SetValue(BindableProperty property, object value) + { + SetValue(property, value, false, true); + } + + public void SetValue(BindablePropertyKey propertyKey, object value) + { + if (propertyKey == null) + throw new ArgumentNullException("propertyKey"); + + SetValue(propertyKey.BindableProperty, value, false, false); + } + + protected internal static void SetInheritedBindingContext(BindableObject bindable, object value) + { + BindablePropertyContext bpContext = bindable.GetContext(BindingContextProperty); + if (bpContext != null && ((bpContext.Attributes & BindableContextAttributes.IsManuallySet) != 0)) + return; + + object oldContext = bindable._inheritedContext; + if (bpContext != null && oldContext == null) + oldContext = bpContext.Value; + + if (bpContext != null && bpContext.Binding != null) + { + bpContext.Binding.Context = value; + bindable._inheritedContext = null; + } + else + { + if (ReferenceEquals(oldContext, value)) + return; + + bindable._inheritedContext = value; + } + + bindable.ApplyBindings(oldContext); + bindable.OnBindingContextChanged(); + } + + protected void ApplyBindings(object oldContext = null) + { + ApplyBindings(oldContext, false); + } + + protected virtual void OnBindingContextChanged() + { + EventHandler change = BindingContextChanged; + if (change != null) + change(this, EventArgs.Empty); + } + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChangedEventHandler handler = PropertyChanged; + if (handler != null) + handler(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void OnPropertyChanging([CallerMemberName] string propertyName = null) + { + PropertyChangingEventHandler changing = PropertyChanging; + if (changing != null) + changing(this, new PropertyChangingEventArgs(propertyName)); + } + + protected void UnapplyBindings() + { + foreach (BindablePropertyContext context in _properties) + { + if (context.Binding == null) + continue; + + context.Binding.Unapply(); + } + } + + internal bool GetIsBound(BindableProperty targetProperty) + { + if (targetProperty == null) + throw new ArgumentNullException("targetProperty"); + + BindablePropertyContext bpcontext = GetContext(targetProperty); + return bpcontext != null && bpcontext.Binding != null; + } + + internal object[] GetValues(BindableProperty property0, BindableProperty property1) + { + var values = new object[2]; + + for (var i = 0; i < _properties.Count; i++) + { + BindablePropertyContext context = _properties[i]; + + if (ReferenceEquals(context.Property, property0)) + { + values[0] = context.Value; + property0 = null; + } + else if (ReferenceEquals(context.Property, property1)) + { + values[1] = context.Value; + property1 = null; + } + + if (property0 == null && property1 == null) + return values; + } + + if (!ReferenceEquals(property0, null)) + values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value; + if (!ReferenceEquals(property1, null)) + values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value; + + return values; + } + + internal object[] GetValues(BindableProperty property0, BindableProperty property1, BindableProperty property2) + { + var values = new object[3]; + + for (var i = 0; i < _properties.Count; i++) + { + BindablePropertyContext context = _properties[i]; + + if (ReferenceEquals(context.Property, property0)) + { + values[0] = context.Value; + property0 = null; + } + else if (ReferenceEquals(context.Property, property1)) + { + values[1] = context.Value; + property1 = null; + } + else if (ReferenceEquals(context.Property, property2)) + { + values[2] = context.Value; + property2 = null; + } + + if (property0 == null && property1 == null && property2 == null) + return values; + } + + if (!ReferenceEquals(property0, null)) + values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value; + if (!ReferenceEquals(property1, null)) + values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value; + if (!ReferenceEquals(property2, null)) + values[2] = property2.DefaultValueCreator == null ? property2.DefaultValue : CreateAndAddContext(property2).Value; + + return values; + } + + internal virtual void OnRemoveDynamicResource(BindableProperty property) + { + } + + internal virtual void OnSetDynamicResource(BindableProperty property, string key) + { + } + + internal void RemoveDynamicResource(BindableProperty property) + { + if (property == null) + throw new ArgumentNullException("property"); + + OnRemoveDynamicResource(property); + BindablePropertyContext context = GetOrCreateContext(property); + context.Attributes &= ~BindableContextAttributes.IsDynamicResource; + } + + internal void SetBinding(BindableProperty targetProperty, BindingBase binding, bool fromStyle) + { + if (targetProperty == null) + throw new ArgumentNullException("targetProperty"); + if (binding == null) + throw new ArgumentNullException("binding"); + + BindablePropertyContext context = null; + if (fromStyle && (context = GetContext(targetProperty)) != null && (context.Attributes & BindableContextAttributes.IsDefaultValue) == 0 && + (context.Attributes & BindableContextAttributes.IsSetFromStyle) == 0) + return; + + context = context ?? GetOrCreateContext(targetProperty); + if (fromStyle) + context.Attributes |= BindableContextAttributes.IsSetFromStyle; + else + context.Attributes &= ~BindableContextAttributes.IsSetFromStyle; + + if (context.Binding != null) + context.Binding.Unapply(); + + BindingBase oldBinding = context.Binding; + context.Binding = binding; + + if (targetProperty.BindingChanging != null) + targetProperty.BindingChanging(this, oldBinding, binding); + + binding.Apply(BindingContext, this, targetProperty); + } + + internal void SetDynamicResource(BindableProperty property, string key) + { + SetDynamicResource(property, key, false); + } + + internal void SetDynamicResource(BindableProperty property, string key, bool fromStyle) + { + if (property == null) + throw new ArgumentNullException("property"); + if (string.IsNullOrEmpty(key)) + throw new ArgumentNullException("key"); + + BindablePropertyContext context = null; + if (fromStyle && (context = GetContext(property)) != null && (context.Attributes & BindableContextAttributes.IsDefaultValue) == 0 && + (context.Attributes & BindableContextAttributes.IsSetFromStyle) == 0) + return; + + context = context ?? GetOrCreateContext(property); + + context.Attributes |= BindableContextAttributes.IsDynamicResource; + if (fromStyle) + context.Attributes |= BindableContextAttributes.IsSetFromStyle; + else + context.Attributes &= ~BindableContextAttributes.IsSetFromStyle; + + OnSetDynamicResource(property, key); + } + + internal void SetValue(BindableProperty property, object value, bool fromStyle) + { + SetValue(property, value, fromStyle, true); + } + + internal void SetValueCore(BindablePropertyKey propertyKey, object value, SetValueFlags attributes = SetValueFlags.None) + { + SetValueCore(propertyKey.BindableProperty, value, attributes, SetValuePrivateFlags.None); + } + + internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes = SetValueFlags.None) + { + SetValueCore(property, value, attributes, SetValuePrivateFlags.Default); + } + + internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes) + { + bool checkAccess = (privateAttributes & SetValuePrivateFlags.CheckAccess) != 0; + bool manuallySet = (privateAttributes & SetValuePrivateFlags.ManuallySet) != 0; + bool silent = (privateAttributes & SetValuePrivateFlags.Silent) != 0; + bool fromStyle = (privateAttributes & SetValuePrivateFlags.FromStyle) != 0; + bool converted = (privateAttributes & SetValuePrivateFlags.Converted) != 0; + + if (property == null) + throw new ArgumentNullException("property"); + if (checkAccess && property.IsReadOnly) + { + Debug.WriteLine("Can not set the BindableProperty \"{0}\" because it is readonly.", property.PropertyName); + return; + } + + if (!converted && !property.TryConvert(ref value)) + { + Log.Warning("SetValue", "Can not convert {0} to type '{1}'", value, property.ReturnType); + return; + } + + if (property.ValidateValue != null && !property.ValidateValue(this, value)) + throw new ArgumentException("Value was an invalid value for " + property.PropertyName, "value"); + + if (property.CoerceValue != null) + value = property.CoerceValue(this, value); + + BindablePropertyContext context = GetOrCreateContext(property); + if (manuallySet) + context.Attributes |= BindableContextAttributes.IsManuallySet; + else + context.Attributes &= ~BindableContextAttributes.IsManuallySet; + + if (fromStyle) + context.Attributes |= BindableContextAttributes.IsSetFromStyle; + // else ommitted on purpose + + bool currentlyApplying = _applying; + + if ((context.Attributes & BindableContextAttributes.IsBeingSet) != 0) + { + Queue<SetValueArgs> delayQueue = context.DelayedSetters; + if (delayQueue == null) + context.DelayedSetters = delayQueue = new Queue<SetValueArgs>(); + + delayQueue.Enqueue(new SetValueArgs(property, context, value, currentlyApplying, attributes)); + } + else + { + context.Attributes |= BindableContextAttributes.IsBeingSet; + SetValueActual(property, context, value, currentlyApplying, attributes, silent); + + Queue<SetValueArgs> delayQueue = context.DelayedSetters; + if (delayQueue != null) + { + while (delayQueue.Count > 0) + { + SetValueArgs s = delayQueue.Dequeue(); + SetValueActual(s.Property, s.Context, s.Value, s.CurrentlyApplying, s.Attributes); + } + + context.DelayedSetters = null; + } + + context.Attributes &= ~BindableContextAttributes.IsBeingSet; + } + } + + void ApplyBindings(object oldContext, bool skipBindingContext) + { + foreach (BindablePropertyContext context in _properties.ToArray()) + { + BindingBase binding = context.Binding; + if (binding == null) + continue; + + if (skipBindingContext && context.Property == BindingContextProperty) + continue; + + binding.Unapply(); + binding.Apply(BindingContext, this, context.Property); + } + } + + static void BindingContextPropertyBindingChanging(BindableObject bindable, BindingBase oldBindingBase, BindingBase newBindingBase) + { + object context = bindable._inheritedContext; + var oldBinding = oldBindingBase as Binding; + var newBinding = newBindingBase as Binding; + + if (context == null && oldBinding != null) + context = oldBinding.Context; + if (context != null && newBinding != null) + newBinding.Context = context; + } + + static void BindingContextPropertyBindingPropertyChanged(BindableObject bindable, object oldvalue, object newvalue) + { + object oldInheritedContext = bindable._inheritedContext; + bindable._inheritedContext = null; + bindable.ApplyBindings(oldInheritedContext ?? oldvalue, true); + bindable.OnBindingContextChanged(); + } + + void ClearValue(BindableProperty property, bool checkaccess) + { + if (property == null) + throw new ArgumentNullException("property"); + + if (checkaccess && property.IsReadOnly) + throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName)); + + BindablePropertyContext bpcontext = GetContext(property); + if (bpcontext == null) + return; + + object original = bpcontext.Value; + + object newValue = property.GetDefaultValue(this); + + bool same = Equals(original, newValue); + if (!same) + { + if (property.PropertyChanging != null) + property.PropertyChanging(this, original, newValue); + + OnPropertyChanging(property.PropertyName); + } + + bpcontext.Attributes &= ~BindableContextAttributes.IsManuallySet; + bpcontext.Value = newValue; + bpcontext.Attributes |= BindableContextAttributes.IsDefaultValue; + + if (!same) + { + OnPropertyChanged(property.PropertyName); + if (property.PropertyChanged != null) + property.PropertyChanged(this, original, newValue); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + BindablePropertyContext CreateAndAddContext(BindableProperty property) + { + var context = new BindablePropertyContext { Property = property, Value = property.DefaultValueCreator != null ? property.DefaultValueCreator(this) : property.DefaultValue }; + + if (property.DefaultValueCreator != null) + context.Attributes = BindableContextAttributes.IsDefaultValue; + + _properties.Add(context); + return context; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + BindablePropertyContext GetContext(BindableProperty property) + { + List<BindablePropertyContext> properties = _properties; + + for (var i = 0; i < properties.Count; i++) + { + BindablePropertyContext context = properties[i]; + if (ReferenceEquals(context.Property, property)) + return context; + } + + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + BindablePropertyContext GetOrCreateContext(BindableProperty property) + { + BindablePropertyContext context = GetContext(property); + if (context == null) + { + context = CreateAndAddContext(property); + } + + return context; + } + + void RemoveBinding(BindableProperty property, BindablePropertyContext context) + { + context.Binding.Unapply(); + + if (property.BindingChanging != null) + property.BindingChanging(this, context.Binding, null); + + context.Binding = null; + } + + void SetValue(BindableProperty property, object value, bool fromStyle, bool checkAccess) + { + if (property == null) + throw new ArgumentNullException("property"); + + if (checkAccess && property.IsReadOnly) + throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName)); + + BindablePropertyContext context = null; + if (fromStyle && (context = GetContext(property)) != null && (context.Attributes & BindableContextAttributes.IsDefaultValue) == 0 && + (context.Attributes & BindableContextAttributes.IsSetFromStyle) == 0) + return; + + SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearDynamicResource, + (fromStyle ? SetValuePrivateFlags.FromStyle : SetValuePrivateFlags.ManuallySet) | (checkAccess ? SetValuePrivateFlags.CheckAccess : 0)); + } + + void SetValueActual(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes, bool silent = false) + { + object original = context.Value; + bool raiseOnEqual = (attributes & SetValueFlags.RaiseOnEqual) != 0; + bool clearDynamicResources = (attributes & SetValueFlags.ClearDynamicResource) != 0; + bool clearOneWayBindings = (attributes & SetValueFlags.ClearOneWayBindings) != 0; + bool clearTwoWayBindings = (attributes & SetValueFlags.ClearTwoWayBindings) != 0; + + bool same = Equals(value, original); + if (!silent && (!same || raiseOnEqual)) + { + if (property.PropertyChanging != null) + property.PropertyChanging(this, original, value); + + OnPropertyChanging(property.PropertyName); + } + + if (!same || raiseOnEqual) + { + context.Value = value; + } + + context.Attributes &= ~BindableContextAttributes.IsDefaultValue; + + if ((context.Attributes & BindableContextAttributes.IsDynamicResource) != 0 && clearDynamicResources) + RemoveDynamicResource(property); + + BindingBase binding = context.Binding; + if (binding != null) + { + if (clearOneWayBindings && binding.GetRealizedMode(property) == BindingMode.OneWay || clearTwoWayBindings && binding.GetRealizedMode(property) == BindingMode.TwoWay) + { + RemoveBinding(property, context); + binding = null; + } + } + + if (!silent && (!same || raiseOnEqual)) + { + if (binding != null && !currentlyApplying) + { + _applying = true; + binding.Apply(true); + _applying = false; + } + + OnPropertyChanged(property.PropertyName); + + if (property.PropertyChanged != null) + property.PropertyChanged(this, original, value); + } + } + + [Flags] + enum BindableContextAttributes + { + IsManuallySet = 1 << 0, + IsBeingSet = 1 << 1, + IsDynamicResource = 1 << 2, + IsSetFromStyle = 1 << 3, + IsDefaultValue = 1 << 4 + } + + class BindablePropertyContext + { + public BindableContextAttributes Attributes; + public BindingBase Binding; + public Queue<SetValueArgs> DelayedSetters; + public BindableProperty Property; + public object Value; + } + + [Flags] + internal enum SetValueFlags + { + None = 0, + ClearOneWayBindings = 1 << 0, + ClearTwoWayBindings = 1 << 1, + ClearDynamicResource = 1 << 2, + RaiseOnEqual = 1 << 3 + } + + [Flags] + internal enum SetValuePrivateFlags + { + None = 0, + CheckAccess = 1 << 0, + Silent = 1 << 1, + ManuallySet = 1 << 2, + FromStyle = 1 << 3, + Converted = 1 << 4, + Default = CheckAccess + } + + class SetValueArgs + { + public readonly SetValueFlags Attributes; + public readonly BindablePropertyContext Context; + public readonly bool CurrentlyApplying; + public readonly BindableProperty Property; + public readonly object Value; + + public SetValueArgs(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes) + { + Property = property; + Context = context; + Value = value; + CurrentlyApplying = currentlyApplying; + Attributes = attributes; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BindableObjectExtensions.cs b/Xamarin.Forms.Core/BindableObjectExtensions.cs new file mode 100644 index 00000000..2eab2380 --- /dev/null +++ b/Xamarin.Forms.Core/BindableObjectExtensions.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq.Expressions; + +namespace Xamarin.Forms +{ + public static class BindableObjectExtensions + { + public static void SetBinding(this BindableObject self, BindableProperty targetProperty, string path, BindingMode mode = BindingMode.Default, IValueConverter converter = null, + string stringFormat = null) + { + if (self == null) + throw new ArgumentNullException("self"); + if (targetProperty == null) + throw new ArgumentNullException("targetProperty"); + + var binding = new Binding(path, mode, converter, stringFormat: stringFormat); + self.SetBinding(targetProperty, binding); + } + + public static void SetBinding<TSource>(this BindableObject self, BindableProperty targetProperty, Expression<Func<TSource, object>> sourceProperty, BindingMode mode = BindingMode.Default, + IValueConverter converter = null, string stringFormat = null) + { + if (self == null) + throw new ArgumentNullException("self"); + if (targetProperty == null) + throw new ArgumentNullException("targetProperty"); + if (sourceProperty == null) + throw new ArgumentNullException("sourceProperty"); + + Binding binding = Binding.Create(sourceProperty, mode, converter, stringFormat: stringFormat); + self.SetBinding(targetProperty, binding); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BindableProperty.cs b/Xamarin.Forms.Core/BindableProperty.cs new file mode 100644 index 00000000..f95d38ab --- /dev/null +++ b/Xamarin.Forms.Core/BindableProperty.cs @@ -0,0 +1,331 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq.Expressions; +using System.Reflection; + +namespace Xamarin.Forms +{ + [DebuggerDisplay("{PropertyName}")] + [TypeConverter(typeof(BindablePropertyConverter))] + public sealed class BindableProperty + { + public delegate void BindingPropertyChangedDelegate(BindableObject bindable, object oldValue, object newValue); + + public delegate void BindingPropertyChangedDelegate<in TPropertyType>(BindableObject bindable, TPropertyType oldValue, TPropertyType newValue); + + public delegate void BindingPropertyChangingDelegate(BindableObject bindable, object oldValue, object newValue); + + public delegate void BindingPropertyChangingDelegate<in TPropertyType>(BindableObject bindable, TPropertyType oldValue, TPropertyType newValue); + + public delegate object CoerceValueDelegate(BindableObject bindable, object value); + + public delegate TPropertyType CoerceValueDelegate<TPropertyType>(BindableObject bindable, TPropertyType value); + + public delegate object CreateDefaultValueDelegate(BindableObject bindable); + + public delegate TPropertyType CreateDefaultValueDelegate<in TDeclarer, out TPropertyType>(TDeclarer bindable); + + public delegate bool ValidateValueDelegate(BindableObject bindable, object value); + + public delegate bool ValidateValueDelegate<in TPropertyType>(BindableObject bindable, TPropertyType value); + + // more or less the encoding of this, without the need to reflect + // http://msdn.microsoft.com/en-us/library/y5b434w4.aspx + static readonly Dictionary<Type, Type[]> SimpleConvertTypes = new Dictionary<Type, Type[]> + { + { typeof(sbyte), new[] { typeof(string), typeof(short), typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) } }, + { typeof(byte), new[] { typeof(string), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } }, + { typeof(short), new[] { typeof(string), typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) } }, + { typeof(ushort), new[] { typeof(string), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } }, + { typeof(int), new[] { typeof(string), typeof(long), typeof(float), typeof(double), typeof(decimal) } }, + { typeof(uint), new[] { typeof(string), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } }, + { typeof(long), new[] { typeof(string), typeof(float), typeof(double), typeof(decimal) } }, + { typeof(char), new[] { typeof(string), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } }, + { typeof(float), new[] { typeof(string), typeof(double) } }, + { typeof(ulong), new[] { typeof(string), typeof(float), typeof(double), typeof(decimal) } } + }; + + BindableProperty(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWay, + ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null, + CoerceValueDelegate coerceValue = null, BindablePropertyBindingChanging bindingChanging = null, bool isReadOnly = false, CreateDefaultValueDelegate defaultValueCreator = null) + { + if (propertyName == null) + throw new ArgumentNullException("propertyName"); + if (ReferenceEquals(returnType, null)) + throw new ArgumentNullException("returnType"); + if (ReferenceEquals(declaringType, null)) + throw new ArgumentNullException("declaringType"); + + // don't use Enum.IsDefined as its redonkulously expensive for what it does + if (defaultBindingMode != BindingMode.Default && defaultBindingMode != BindingMode.OneWay && defaultBindingMode != BindingMode.OneWayToSource && defaultBindingMode != BindingMode.TwoWay) + throw new ArgumentException("Not a valid type of BindingMode", "defaultBindingMode"); + if (defaultValue == null && Nullable.GetUnderlyingType(returnType) == null && returnType.GetTypeInfo().IsValueType) + throw new ArgumentException("Not a valid default value", "defaultValue"); + if (defaultValue != null && !returnType.IsInstanceOfType(defaultValue)) + throw new ArgumentException("Default value did not match return type", "defaultValue"); + if (defaultBindingMode == BindingMode.Default) + defaultBindingMode = BindingMode.OneWay; + + PropertyName = propertyName; + ReturnType = returnType; + ReturnTypeInfo = returnType.GetTypeInfo(); + DeclaringType = declaringType; + DefaultValue = defaultValue; + DefaultBindingMode = defaultBindingMode; + PropertyChanged = propertyChanged; + PropertyChanging = propertyChanging; + ValidateValue = validateValue; + CoerceValue = coerceValue; + BindingChanging = bindingChanging; + IsReadOnly = isReadOnly; + DefaultValueCreator = defaultValueCreator; + } + + public Type DeclaringType { get; private set; } + + public BindingMode DefaultBindingMode { get; private set; } + + public object DefaultValue { get; } + + public bool IsReadOnly { get; private set; } + + public string PropertyName { get; } + + public Type ReturnType { get; } + + internal BindablePropertyBindingChanging BindingChanging { get; private set; } + + internal CoerceValueDelegate CoerceValue { get; private set; } + + internal CreateDefaultValueDelegate DefaultValueCreator { get; } + + internal BindingPropertyChangedDelegate PropertyChanged { get; private set; } + + internal BindingPropertyChangingDelegate PropertyChanging { get; private set; } + + internal TypeInfo ReturnTypeInfo { get; } + + internal ValidateValueDelegate ValidateValue { get; private set; } + + [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")] + public static BindableProperty Create<TDeclarer, TPropertyType>(Expression<Func<TDeclarer, TPropertyType>> getter, TPropertyType defaultValue, BindingMode defaultBindingMode = BindingMode.OneWay, + ValidateValueDelegate<TPropertyType> validateValue = null, BindingPropertyChangedDelegate<TPropertyType> propertyChanged = null, + BindingPropertyChangingDelegate<TPropertyType> propertyChanging = null, CoerceValueDelegate<TPropertyType> coerceValue = null, + CreateDefaultValueDelegate<TDeclarer, TPropertyType> defaultValueCreator = null) where TDeclarer : BindableObject + { + return Create(getter, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, defaultValueCreator: defaultValueCreator); + } + + public static BindableProperty Create(string propertyName, Type returnType, Type declaringType, object defaultValue = null, BindingMode defaultBindingMode = BindingMode.OneWay, + ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null, + CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null) + { + return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, + defaultValueCreator: defaultValueCreator); + } + + [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")] + public static BindableProperty CreateAttached<TDeclarer, TPropertyType>(Expression<Func<BindableObject, TPropertyType>> staticgetter, TPropertyType defaultValue, + BindingMode defaultBindingMode = BindingMode.OneWay, ValidateValueDelegate<TPropertyType> validateValue = null, BindingPropertyChangedDelegate<TPropertyType> propertyChanged = null, + BindingPropertyChangingDelegate<TPropertyType> propertyChanging = null, CoerceValueDelegate<TPropertyType> coerceValue = null, + CreateDefaultValueDelegate<BindableObject, TPropertyType> defaultValueCreator = null) + { + return CreateAttached<TDeclarer, TPropertyType>(staticgetter, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, + defaultValueCreator: defaultValueCreator); + } + + public static BindableProperty CreateAttached(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWay, + ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null, + CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null) + { + return CreateAttached(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, false, defaultValueCreator); + } + + [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")] + public static BindablePropertyKey CreateAttachedReadOnly<TDeclarer, TPropertyType>(Expression<Func<BindableObject, TPropertyType>> staticgetter, TPropertyType defaultValue, + BindingMode defaultBindingMode = BindingMode.OneWayToSource, ValidateValueDelegate<TPropertyType> validateValue = null, + BindingPropertyChangedDelegate<TPropertyType> propertyChanged = null, BindingPropertyChangingDelegate<TPropertyType> propertyChanging = null, + CoerceValueDelegate<TPropertyType> coerceValue = null, CreateDefaultValueDelegate<BindableObject, TPropertyType> defaultValueCreator = null) + + { + return + new BindablePropertyKey(CreateAttached<TDeclarer, TPropertyType>(staticgetter, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, true, + defaultValueCreator)); + } + + public static BindablePropertyKey CreateAttachedReadOnly(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWayToSource, + ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null, + CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null) + { + return + new BindablePropertyKey(CreateAttached(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, true, + defaultValueCreator)); + } + + [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")] + public static BindablePropertyKey CreateReadOnly<TDeclarer, TPropertyType>(Expression<Func<TDeclarer, TPropertyType>> getter, TPropertyType defaultValue, + BindingMode defaultBindingMode = BindingMode.OneWayToSource, ValidateValueDelegate<TPropertyType> validateValue = null, + BindingPropertyChangedDelegate<TPropertyType> propertyChanged = null, BindingPropertyChangingDelegate<TPropertyType> propertyChanging = null, + CoerceValueDelegate<TPropertyType> coerceValue = null, CreateDefaultValueDelegate<TDeclarer, TPropertyType> defaultValueCreator = null) where TDeclarer : BindableObject + { + return new BindablePropertyKey(Create(getter, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, true, defaultValueCreator)); + } + + public static BindablePropertyKey CreateReadOnly(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWayToSource, + ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null, + CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null) + { + return + new BindablePropertyKey(new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, + isReadOnly: true, defaultValueCreator: defaultValueCreator)); + } + + [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")] + internal static BindableProperty Create<TDeclarer, TPropertyType>(Expression<Func<TDeclarer, TPropertyType>> getter, TPropertyType defaultValue, BindingMode defaultBindingMode, + ValidateValueDelegate<TPropertyType> validateValue, BindingPropertyChangedDelegate<TPropertyType> propertyChanged, BindingPropertyChangingDelegate<TPropertyType> propertyChanging, + CoerceValueDelegate<TPropertyType> coerceValue, BindablePropertyBindingChanging bindingChanging, bool isReadOnly = false, + CreateDefaultValueDelegate<TDeclarer, TPropertyType> defaultValueCreator = null) where TDeclarer : BindableObject + { + if (getter == null) + throw new ArgumentNullException("getter"); + + Expression expr = getter.Body; + + var unary = expr as UnaryExpression; + if (unary != null) + expr = unary.Operand; + + var member = expr as MemberExpression; + if (member == null) + throw new ArgumentException("getter must be a MemberExpression", "getter"); + + var property = (PropertyInfo)member.Member; + + ValidateValueDelegate untypedValidateValue = null; + BindingPropertyChangedDelegate untypedBindingPropertyChanged = null; + BindingPropertyChangingDelegate untypedBindingPropertyChanging = null; + CoerceValueDelegate untypedCoerceValue = null; + CreateDefaultValueDelegate untypedDefaultValueCreator = null; + if (validateValue != null) + untypedValidateValue = (bindable, value) => validateValue(bindable, (TPropertyType)value); + if (propertyChanged != null) + untypedBindingPropertyChanged = (bindable, oldValue, newValue) => propertyChanged(bindable, (TPropertyType)oldValue, (TPropertyType)newValue); + if (propertyChanging != null) + untypedBindingPropertyChanging = (bindable, oldValue, newValue) => propertyChanging(bindable, (TPropertyType)oldValue, (TPropertyType)newValue); + if (coerceValue != null) + untypedCoerceValue = (bindable, value) => coerceValue(bindable, (TPropertyType)value); + if (defaultValueCreator != null) + untypedDefaultValueCreator = o => defaultValueCreator((TDeclarer)o); + + return new BindableProperty(property.Name, property.PropertyType, typeof(TDeclarer), defaultValue, defaultBindingMode, untypedValidateValue, untypedBindingPropertyChanged, + untypedBindingPropertyChanging, untypedCoerceValue, bindingChanging, isReadOnly, untypedDefaultValueCreator); + } + + internal static BindableProperty Create(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode, ValidateValueDelegate validateValue, + BindingPropertyChangedDelegate propertyChanged, BindingPropertyChangingDelegate propertyChanging, CoerceValueDelegate coerceValue, BindablePropertyBindingChanging bindingChanging, + CreateDefaultValueDelegate defaultValueCreator = null) + { + return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, bindingChanging, + defaultValueCreator: defaultValueCreator); + } + + [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")] + internal static BindableProperty CreateAttached<TDeclarer, TPropertyType>(Expression<Func<BindableObject, TPropertyType>> staticgetter, TPropertyType defaultValue, BindingMode defaultBindingMode, + ValidateValueDelegate<TPropertyType> validateValue, BindingPropertyChangedDelegate<TPropertyType> propertyChanged, BindingPropertyChangingDelegate<TPropertyType> propertyChanging, + CoerceValueDelegate<TPropertyType> coerceValue, BindablePropertyBindingChanging bindingChanging, bool isReadOnly = false, + CreateDefaultValueDelegate<BindableObject, TPropertyType> defaultValueCreator = null) + { + if (staticgetter == null) + throw new ArgumentNullException("staticgetter"); + + Expression expr = staticgetter.Body; + + var unary = expr as UnaryExpression; + if (unary != null) + expr = unary.Operand; + + var methodcall = expr as MethodCallExpression; + if (methodcall == null) + throw new ArgumentException("staticgetter must be a MethodCallExpression", "staticgetter"); + + MethodInfo method = methodcall.Method; + if (!method.Name.StartsWith("Get", StringComparison.Ordinal)) + throw new ArgumentException("staticgetter name must start with Get", "staticgetter"); + + string propertyname = method.Name.Substring(3); + + ValidateValueDelegate untypedValidateValue = null; + BindingPropertyChangedDelegate untypedBindingPropertyChanged = null; + BindingPropertyChangingDelegate untypedBindingPropertyChanging = null; + CoerceValueDelegate untypedCoerceValue = null; + CreateDefaultValueDelegate untypedDefaultValueCreator = null; + if (validateValue != null) + untypedValidateValue = (bindable, value) => validateValue(bindable, (TPropertyType)value); + if (propertyChanged != null) + untypedBindingPropertyChanged = (bindable, oldValue, newValue) => propertyChanged(bindable, (TPropertyType)oldValue, (TPropertyType)newValue); + if (propertyChanging != null) + untypedBindingPropertyChanging = (bindable, oldValue, newValue) => propertyChanging(bindable, (TPropertyType)oldValue, (TPropertyType)newValue); + if (coerceValue != null) + untypedCoerceValue = (bindable, value) => coerceValue(bindable, (TPropertyType)value); + if (defaultValueCreator != null) + untypedDefaultValueCreator = o => defaultValueCreator(o); + + return new BindableProperty(propertyname, method.ReturnType, typeof(TDeclarer), defaultValue, defaultBindingMode, untypedValidateValue, untypedBindingPropertyChanged, untypedBindingPropertyChanging, + untypedCoerceValue, bindingChanging, isReadOnly, untypedDefaultValueCreator); + } + + internal static BindableProperty CreateAttached(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode, ValidateValueDelegate validateValue, + BindingPropertyChangedDelegate propertyChanged, BindingPropertyChangingDelegate propertyChanging, CoerceValueDelegate coerceValue, BindablePropertyBindingChanging bindingChanging, + bool isReadOnly, CreateDefaultValueDelegate defaultValueCreator = null) + { + return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, bindingChanging, isReadOnly, + defaultValueCreator); + } + + internal object GetDefaultValue(BindableObject bindable) + { + if (DefaultValueCreator != null) + return DefaultValueCreator(bindable); + + return DefaultValue; + } + + internal bool TryConvert(ref object value) + { + if (value == null) + { + return !ReturnTypeInfo.IsValueType || ReturnTypeInfo.IsGenericType && ReturnTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + Type valueType = value.GetType(); + Type type = ReturnType; + + // Dont support arbitrary IConvertible by limiting which types can use this + Type[] convertableTo; + if (SimpleConvertTypes.TryGetValue(valueType, out convertableTo) && Array.IndexOf(convertableTo, type) != -1) + { + value = Convert.ChangeType(value, type); + } + else if (!ReturnTypeInfo.IsAssignableFrom(valueType.GetTypeInfo())) + { + // Is there an implicit cast operator ? + MethodInfo cast = type.GetRuntimeMethod("op_Implicit", new[] { valueType }); + if (cast != null && cast.ReturnType != type) + cast = null; + if (cast == null) + cast = valueType.GetRuntimeMethod("op_Implicit", new[] { valueType }); + if (cast != null && cast.ReturnType != type) + cast = null; + if (cast == null) + return false; + + value = cast.Invoke(null, new[] { value }); + } + + return true; + } + + internal delegate void BindablePropertyBindingChanging(BindableObject bindable, BindingBase oldValue, BindingBase newValue); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BindablePropertyConverter.cs b/Xamarin.Forms.Core/BindablePropertyConverter.cs new file mode 100644 index 00000000..af3fd5b6 --- /dev/null +++ b/Xamarin.Forms.Core/BindablePropertyConverter.cs @@ -0,0 +1,105 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Xml; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + public sealed class BindablePropertyConverter : TypeConverter, IExtendedTypeConverter + { + object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider) + { + return ((IExtendedTypeConverter)this).ConvertFromInvariantString(value as string, serviceProvider); + } + + object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + if (serviceProvider == null) + return null; + var parentValuesProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideParentValues; + var typeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver; + if (typeResolver == null) + return null; + IXmlLineInfo lineinfo = null; + var xmlLineInfoProvider = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider; + if (xmlLineInfoProvider != null) + lineinfo = xmlLineInfoProvider.XmlLineInfo; + string[] parts = value.Split('.'); + Type type = null; + if (parts.Length == 1) + { + if (parentValuesProvider == null) + { + string msg = string.Format("Can't resolve {0}", parts[0]); + throw new XamlParseException(msg, lineinfo); + } + object parent = parentValuesProvider.ParentObjects.Skip(1).FirstOrDefault(); + if (parentValuesProvider.TargetObject is Setter) + { + var style = parent as Style; + var triggerBase = parent as TriggerBase; + if (style != null) + type = style.TargetType; + else if (triggerBase != null) + type = triggerBase.TargetType; + } + else if (parentValuesProvider.TargetObject is Trigger) + type = (parentValuesProvider.TargetObject as Trigger).TargetType; + + if (type == null) + { + string msg = string.Format("Can't resolve {0}", parts[0]); + throw new XamlParseException(msg, lineinfo); + } + + return ConvertFrom(type, parts[0], lineinfo); + } + if (parts.Length == 2) + { + if (!typeResolver.TryResolve(parts[0], out type)) + { + string msg = string.Format("Can't resolve {0}", parts[0]); + throw new XamlParseException(msg, lineinfo); + } + return ConvertFrom(type, parts[1], lineinfo); + } + string emsg = string.Format("Can't resolve {0}. Syntax is [[ns:]Type.]PropertyName.", value); + throw new XamlParseException(emsg, lineinfo); + } + + public override object ConvertFromInvariantString(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + if (value.Contains(":")) + { + Log.Warning(null, "Can't resolve properties with xml namespace prefix."); + return null; + } + string[] parts = value.Split('.'); + if (parts.Length != 2) + { + Log.Warning(null, "Can't resolve {0}. Accepted syntax is Type.PropertyName.", value); + return null; + } + Type type = Type.GetType("Xamarin.Forms." + parts[0]); + return ConvertFrom(type, parts[1], null); + } + + BindableProperty ConvertFrom(Type type, string propertyName, IXmlLineInfo lineinfo) + { + string name = propertyName + "Property"; + FieldInfo bpinfo = type.GetField(fi => fi.Name == name && fi.IsStatic && fi.IsPublic && fi.FieldType == typeof(BindableProperty)); + if (bpinfo == null) + throw new XamlParseException(string.Format("Can't resolve {0} on {1}", name, type.Name), lineinfo); + var bp = bpinfo.GetValue(null) as BindableProperty; + if (bp.PropertyName != propertyName) + throw new XamlParseException(string.Format("The PropertyName of {0}.{1} is not {2}", type.Name, name, propertyName), lineinfo); + return bp; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BindablePropertyKey.cs b/Xamarin.Forms.Core/BindablePropertyKey.cs new file mode 100644 index 00000000..a4b9f4a4 --- /dev/null +++ b/Xamarin.Forms.Core/BindablePropertyKey.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + public sealed class BindablePropertyKey + { + internal BindablePropertyKey(BindableProperty property) + { + if (property == null) + throw new ArgumentNullException("property"); + + BindableProperty = property; + } + + public BindableProperty BindableProperty { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Binding.cs b/Xamarin.Forms.Core/Binding.cs new file mode 100644 index 00000000..5fa1dd65 --- /dev/null +++ b/Xamarin.Forms.Core/Binding.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace Xamarin.Forms +{ + public sealed class Binding : BindingBase + { + internal const string SelfPath = "."; + IValueConverter _converter; + object _converterParameter; + + BindingExpression _expression; + string _path; + object _source; + + public Binding() + { + } + + public Binding(string path, BindingMode mode = BindingMode.Default, IValueConverter converter = null, object converterParameter = null, string stringFormat = null, object source = null) + { + if (path == null) + throw new ArgumentNullException("path"); + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("path can not be an empty string", "path"); + + Path = path; + Converter = converter; + ConverterParameter = converterParameter; + Mode = mode; + StringFormat = stringFormat; + Source = source; + } + + public IValueConverter Converter + { + get { return _converter; } + set + { + ThrowIfApplied(); + + _converter = value; + } + } + + public object ConverterParameter + { + get { return _converterParameter; } + set + { + ThrowIfApplied(); + + _converterParameter = value; + } + } + + public string Path + { + get { return _path; } + set + { + ThrowIfApplied(); + + _path = value; + _expression = new BindingExpression(this, !string.IsNullOrWhiteSpace(value) ? value : SelfPath); + } + } + + public object Source + { + get { return _source; } + set + { + ThrowIfApplied(); + _source = value; + } + } + + public static Binding Create<TSource>(Expression<Func<TSource, object>> propertyGetter, BindingMode mode = BindingMode.Default, IValueConverter converter = null, object converterParameter = null, + string stringFormat = null) + { + if (propertyGetter == null) + throw new ArgumentNullException("propertyGetter"); + + string path = GetBindingPath(propertyGetter); + return new Binding(path, mode, converter, converterParameter, stringFormat); + } + + internal override void Apply(bool fromTarget) + { + base.Apply(fromTarget); + + if (_expression == null) + _expression = new BindingExpression(this, SelfPath); + + _expression.Apply(fromTarget); + } + + internal override void Apply(object newContext, BindableObject bindObj, BindableProperty targetProperty) + { + object src = _source; + base.Apply(src ?? newContext, bindObj, targetProperty); + + object bindingContext = src ?? Context ?? newContext; + if (_expression == null && bindingContext != null) + _expression = new BindingExpression(this, SelfPath); + + _expression.Apply(bindingContext, bindObj, targetProperty); + } + + internal override BindingBase Clone() + { + return new Binding(Path, Mode) { Converter = Converter, ConverterParameter = ConverterParameter, StringFormat = StringFormat, Source = Source }; + } + + internal override object GetSourceValue(object value, Type targetPropertyType) + { + if (Converter != null) + value = Converter.Convert(value, targetPropertyType, ConverterParameter, CultureInfo.CurrentUICulture); + + return base.GetSourceValue(value, targetPropertyType); + } + + internal override object GetTargetValue(object value, Type sourcePropertyType) + { + if (Converter != null) + value = Converter.ConvertBack(value, sourcePropertyType, ConverterParameter, CultureInfo.CurrentUICulture); + + return base.GetTargetValue(value, sourcePropertyType); + } + + internal override void Unapply() + { + base.Unapply(); + + if (_expression != null) + _expression.Unapply(); + } + + static string GetBindingPath<TSource>(Expression<Func<TSource, object>> propertyGetter) + { + Expression expr = propertyGetter.Body; + + var unary = expr as UnaryExpression; + if (unary != null) + expr = unary.Operand; + + var builder = new StringBuilder(); + + var indexed = false; + + var member = expr as MemberExpression; + if (member == null) + { + var methodCall = expr as MethodCallExpression; + if (methodCall != null) + { + if (methodCall.Arguments.Count == 0) + throw new ArgumentException("Method calls are not allowed in binding expression"); + + var arguments = new List<string>(methodCall.Arguments.Count); + foreach (Expression arg in methodCall.Arguments) + { + if (arg.NodeType != ExpressionType.Constant) + throw new ArgumentException("Only constants can be used as indexer arguments"); + + object value = ((ConstantExpression)arg).Value; + arguments.Add(value != null ? value.ToString() : "null"); + } + + Type declarerType = methodCall.Method.DeclaringType; + DefaultMemberAttribute defaultMember = declarerType.GetTypeInfo().GetCustomAttributes(typeof(DefaultMemberAttribute), true).OfType<DefaultMemberAttribute>().FirstOrDefault(); + string indexerName = defaultMember != null ? defaultMember.MemberName : "Item"; + + MethodInfo getterInfo = + declarerType.GetProperties().Where(pi => pi.Name == indexerName && pi.CanRead && pi.GetMethod.IsPublic && !pi.GetMethod.IsStatic).Select(pi => pi.GetMethod).FirstOrDefault(); + if (getterInfo != null) + { + if (getterInfo == methodCall.Method) + { + indexed = true; + builder.Append("["); + + var first = true; + foreach (string argument in arguments) + { + if (!first) + builder.Append(","); + + builder.Append(argument); + first = false; + } + + builder.Append("]"); + + member = methodCall.Object as MemberExpression; + } + else + throw new ArgumentException("Method calls are not allowed in binding expressions"); + } + else + throw new ArgumentException("Public indexer not found"); + } + else + throw new ArgumentException("Invalid expression type"); + } + + while (member != null) + { + var property = (PropertyInfo)member.Member; + if (builder.Length != 0) + { + if (!indexed) + builder.Insert(0, "."); + else + indexed = false; + } + + builder.Insert(0, property.Name); + + // member = member.Expression as MemberExpression ?? (member.Expression as UnaryExpression)?.Operand as MemberExpression; + member = member.Expression as MemberExpression ?? (member.Expression is UnaryExpression ? (member.Expression as UnaryExpression).Operand as MemberExpression : null); + } + + return builder.ToString(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BindingBase.cs b/Xamarin.Forms.Core/BindingBase.cs new file mode 100644 index 00000000..0810cbcf --- /dev/null +++ b/Xamarin.Forms.Core/BindingBase.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections; +using System.Runtime.CompilerServices; + +namespace Xamarin.Forms +{ + public abstract class BindingBase + { + static readonly ConditionalWeakTable<IEnumerable, CollectionSynchronizationContext> SynchronizedCollections = new ConditionalWeakTable<IEnumerable, CollectionSynchronizationContext>(); + + BindingMode _mode = BindingMode.Default; + string _stringFormat; + + internal BindingBase() + { + } + + public BindingMode Mode + { + get { return _mode; } + set + { + if (value != BindingMode.Default && value != BindingMode.OneWay && value != BindingMode.OneWayToSource && value != BindingMode.TwoWay) + throw new ArgumentException("mode is not a valid BindingMode", "mode"); + + ThrowIfApplied(); + + _mode = value; + } + } + + public string StringFormat + { + get { return _stringFormat; } + set + { + ThrowIfApplied(); + + _stringFormat = value; + } + } + + internal bool AllowChaining { get; set; } + + internal object Context { get; set; } + + internal bool IsApplied { get; private set; } + + public static void DisableCollectionSynchronization(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + + SynchronizedCollections.Remove(collection); + } + + public static void EnableCollectionSynchronization(IEnumerable collection, object context, CollectionSynchronizationCallback callback) + { + if (collection == null) + throw new ArgumentNullException("collection"); + if (callback == null) + throw new ArgumentNullException("callback"); + + SynchronizedCollections.Add(collection, new CollectionSynchronizationContext(context, callback)); + } + + protected void ThrowIfApplied() + { + if (IsApplied) + throw new InvalidOperationException("Can not change a binding while it's applied"); + } + + internal virtual void Apply(bool fromTarget) + { + IsApplied = true; + } + + internal virtual void Apply(object context, BindableObject bindObj, BindableProperty targetProperty) + { + IsApplied = true; + } + + internal abstract BindingBase Clone(); + + internal virtual object GetSourceValue(object value, Type targetPropertyType) + { + if (StringFormat != null) + return string.Format(StringFormat, value); + + return value; + } + + internal virtual object GetTargetValue(object value, Type sourcePropertyType) + { + return value; + } + + internal static bool TryGetSynchronizedCollection(IEnumerable collection, out CollectionSynchronizationContext synchronizationContext) + { + if (collection == null) + throw new ArgumentNullException("collection"); + + return SynchronizedCollections.TryGetValue(collection, out synchronizationContext); + } + + internal virtual void Unapply() + { + IsApplied = false; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BindingBaseExtensions.cs b/Xamarin.Forms.Core/BindingBaseExtensions.cs new file mode 100644 index 00000000..a52c5cb1 --- /dev/null +++ b/Xamarin.Forms.Core/BindingBaseExtensions.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + internal static class BindingBaseExtensions + { + internal static BindingMode GetRealizedMode(this BindingBase self, BindableProperty property) + { + if (self == null) + throw new ArgumentNullException("self"); + if (property == null) + throw new ArgumentNullException("property"); + + return self.Mode != BindingMode.Default ? self.Mode : property.DefaultBindingMode; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BindingExpression.cs b/Xamarin.Forms.Core/BindingExpression.cs new file mode 100644 index 00000000..505fc584 --- /dev/null +++ b/Xamarin.Forms.Core/BindingExpression.cs @@ -0,0 +1,506 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Reflection; + +namespace Xamarin.Forms +{ + internal class BindingExpression + { + internal const string PropertyNotFoundErrorMessage = "'{0}' property not found on '{1}', target property: '{2}.{3}'"; + + readonly List<BindingExpressionPart> _parts = new List<BindingExpressionPart>(); + + BindableProperty _targetProperty; + WeakReference<object> _weakSource; + WeakReference<BindableObject> _weakTarget; + + internal BindingExpression(BindingBase binding, string path) + { + if (binding == null) + throw new ArgumentNullException("binding"); + if (path == null) + throw new ArgumentNullException("path"); + + Binding = binding; + Path = path; + + ParsePath(); + } + + internal BindingBase Binding { get; } + + internal string Path { get; } + + /// <summary> + /// Applies the binding expression to a previously set source and target. + /// </summary> + internal void Apply(bool fromTarget = false) + { + if (_weakSource == null || _weakTarget == null) + return; + + BindableObject target; + if (!_weakTarget.TryGetTarget(out target)) + { + Unapply(); + return; + } + + object source; + if (_weakSource.TryGetTarget(out source) && _targetProperty != null) + ApplyCore(source, target, _targetProperty, fromTarget); + } + + /// <summary> + /// Applies the binding expression to a new source or target. + /// </summary> + internal void Apply(object sourceObject, BindableObject target, BindableProperty property) + { + _targetProperty = property; + + BindableObject prevTarget; + if (_weakTarget != null && _weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, target)) + throw new InvalidOperationException("Binding instances can not be reused"); + + object previousSource; + if (_weakSource != null && _weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, sourceObject)) + throw new InvalidOperationException("Binding instances can not be reused"); + + _weakSource = new WeakReference<object>(sourceObject); + _weakTarget = new WeakReference<BindableObject>(target); + + ApplyCore(sourceObject, target, property); + } + + internal void Unapply() + { + object sourceObject; + if (_weakSource != null && _weakSource.TryGetTarget(out sourceObject)) + { + for (var i = 0; i < _parts.Count - 1; i++) + { + BindingExpressionPart part = _parts[i]; + + if (!part.IsSelf) + { + part.TryGetValue(sourceObject, out sourceObject); + } + + var inpc = sourceObject as INotifyPropertyChanged; + if (inpc != null) + inpc.PropertyChanged -= part.ChangeHandler; + } + } + + _weakSource = null; + _weakTarget = null; + } + + /// <summary> + /// Applies the binding expression to a previously set source or target. + /// </summary> + void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false) + { + BindingMode mode = Binding.GetRealizedMode(_targetProperty); + if (mode == BindingMode.OneWay && fromTarget) + return; + + bool needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay; + bool needsSetter = !needsGetter && ((mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource); + + object current = sourceObject; + object previous = null; + BindingExpressionPart part = null; + + for (var i = 0; i < _parts.Count; i++) + { + part = _parts[i]; + bool isLast = i + 1 == _parts.Count; + + if (!part.IsSelf && current != null) + { + // Allow the object instance itself to provide its own TypeInfo + var reflectable = current as IReflectableType; + TypeInfo currentType = reflectable != null ? reflectable.GetTypeInfo() : current.GetType().GetTypeInfo(); + if (part.LastGetter == null || !part.LastGetter.DeclaringType.GetTypeInfo().IsAssignableFrom(currentType)) + SetupPart(currentType, part); + + if (!isLast) + part.TryGetValue(current, out current); + } + + if (!part.IsSelf && current != null) + { + if ((needsGetter && part.LastGetter == null) || (needsSetter && part.NextPart == null && part.LastSetter == null)) + { + Log.Warning("Binding", PropertyNotFoundErrorMessage, part.Content, current, target.GetType(), property.PropertyName); + break; + } + } + + if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) + { + var inpc = current as INotifyPropertyChanged; + if (inpc != null && !ReferenceEquals(current, previous)) + { + // If we're reapplying, we don't want to double subscribe + inpc.PropertyChanged -= part.ChangeHandler; + inpc.PropertyChanged += part.ChangeHandler; + } + } + + previous = current; + } + + Debug.Assert(part != null, "There should always be at least the self part in the expression."); + + if (needsGetter) + { + object value = property.DefaultValue; + if (part.TryGetValue(current, out value) || part.IsSelf) + { + value = Binding.GetSourceValue(value, property.ReturnType); + } + else + value = property.DefaultValue; + + if (!TryConvert(part, ref value, property.ReturnType, true)) + { + Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, property.ReturnType); + return; + } + + target.SetValueCore(property, value, BindableObject.SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted); + } + else if (needsSetter && part.LastSetter != null && current != null) + { + object value = Binding.GetTargetValue(target.GetValue(property), part.SetterType); + + if (!TryConvert(part, ref value, part.SetterType, false)) + { + Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, part.SetterType); + return; + } + + object[] args; + if (part.IsIndexer) + { + args = new object[part.Arguments.Length + 1]; + part.Arguments.CopyTo(args, 0); + args[args.Length - 1] = value; + } + else if (part.IsBindablePropertySetter) + { + args = new[] { part.BindablePropertyField, value }; + } + else + { + args = new[] { value }; + } + + part.LastSetter.Invoke(current, args); + } + } + + IEnumerable<BindingExpressionPart> GetPart(string part) + { + part = part.Trim(); + if (part == string.Empty) + throw new FormatException("Path contains an empty part"); + + BindingExpressionPart indexer = null; + + int lbIndex = part.IndexOf('['); + if (lbIndex != -1) + { + int rbIndex = part.LastIndexOf(']'); + if (rbIndex == -1) + throw new FormatException("Indexer did not contain closing bracket"); + + int argLength = rbIndex - lbIndex - 1; + if (argLength == 0) + throw new FormatException("Indexer did not contain arguments"); + + string argString = part.Substring(lbIndex + 1, argLength); + indexer = new BindingExpressionPart(this, argString, true); + + part = part.Substring(0, lbIndex); + part = part.Trim(); + } + + if (part.Length > 0) + yield return new BindingExpressionPart(this, part); + if (indexer != null) + yield return indexer; + } + + void ParsePath() + { + string p = Path.Trim(); + + var last = new BindingExpressionPart(this, "."); + _parts.Add(last); + + if (p[0] == '.') + { + if (p.Length == 1) + return; + + p = p.Substring(1); + } + + string[] pathParts = p.Split('.'); + for (var i = 0; i < pathParts.Length; i++) + { + foreach (BindingExpressionPart part in GetPart(pathParts[i])) + { + last.NextPart = part; + _parts.Add(part); + last = part; + } + } + } + + void SetupPart(TypeInfo sourceType, BindingExpressionPart part) + { + part.Arguments = null; + part.LastGetter = null; + part.LastSetter = null; + + PropertyInfo property = null; + if (part.IsIndexer) + { + if (sourceType.IsArray) + { + int index; + if (!int.TryParse(part.Content, out index)) + Log.Warning("Binding", "{0} could not be parsed as an index for a {1}", part.Content, sourceType); + else + part.Arguments = new object[] { index }; + + part.LastGetter = sourceType.GetDeclaredMethod("Get"); + part.LastSetter = sourceType.GetDeclaredMethod("Set"); + part.SetterType = sourceType.GetElementType(); + } + + DefaultMemberAttribute defaultMember = sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true).OfType<DefaultMemberAttribute>().FirstOrDefault(); + string indexerName = defaultMember != null ? defaultMember.MemberName : "Item"; + + part.IndexerName = indexerName; + + property = sourceType.GetDeclaredProperty(indexerName); + if (property == null) + property = sourceType.BaseType.GetProperty(indexerName); + + if (property != null) + { + ParameterInfo parameter = property.GetIndexParameters().FirstOrDefault(); + if (parameter != null) + { + try + { + object arg = Convert.ChangeType(part.Content, parameter.ParameterType, CultureInfo.InvariantCulture); + part.Arguments = new[] { arg }; + } + catch (FormatException) + { + } + catch (InvalidCastException) + { + } + catch (OverflowException) + { + } + } + } + } + else + { + property = sourceType.GetDeclaredProperty(part.Content); + if (property == null) + property = sourceType.BaseType.GetProperty(part.Content); + } + + if (property != null) + { + if (property.CanRead && property.GetMethod.IsPublic && !property.GetMethod.IsStatic) + part.LastGetter = property.GetMethod; + if (property.CanWrite && property.SetMethod.IsPublic && !property.SetMethod.IsStatic) + { + part.LastSetter = property.SetMethod; + part.SetterType = part.LastSetter.GetParameters().Last().ParameterType; + + if (Binding.AllowChaining) + { + FieldInfo bindablePropertyField = sourceType.GetDeclaredField(part.Content + "Property"); + if (bindablePropertyField != null && bindablePropertyField.FieldType == typeof(BindableProperty) && sourceType.ImplementedInterfaces.Contains(typeof(IElementController))) + { + MethodInfo setValueMethod = null; + foreach (MethodInfo m in sourceType.AsType().GetRuntimeMethods()) + { + if (m.Name.EndsWith("IElementController.SetValueFromRenderer")) + { + ParameterInfo[] parameters = m.GetParameters(); + if (parameters.Length == 2 && parameters[0].ParameterType == typeof(BindableProperty)) + { + setValueMethod = m; + break; + } + } + } + if (setValueMethod != null) + { + part.LastSetter = setValueMethod; + part.IsBindablePropertySetter = true; + part.BindablePropertyField = bindablePropertyField.GetValue(null); + } + } + } + } + } + } + + bool TryConvert(BindingExpressionPart part, ref object value, Type convertTo, bool toTarget) + { + if (value == null) + return true; + if ((toTarget && _targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value))) + return true; + + object original = value; + try + { + value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture); + return true; + } + catch (InvalidCastException) + { + value = original; + return false; + } + catch (FormatException) + { + value = original; + return false; + } + catch (OverflowException) + { + value = original; + return false; + } + } + + class BindingPair + { + public BindingPair(BindingExpressionPart part, object source, bool isLast) + { + Part = part; + Source = source; + IsLast = isLast; + } + + public bool IsLast { get; set; } + + public BindingExpressionPart Part { get; private set; } + + public object Source { get; private set; } + } + + class BindingExpressionPart + { + readonly BindingExpression _expression; + + public readonly PropertyChangedEventHandler ChangeHandler; + + public BindingExpressionPart(BindingExpression expression, string content, bool isIndexer = false) + { + _expression = expression; + IsSelf = content == Forms.Binding.SelfPath; + Content = content; + IsIndexer = isIndexer; + + ChangeHandler = PropertyChanged; + } + + public object[] Arguments { get; set; } + + public object BindablePropertyField { get; set; } + + public string Content { get; } + + public string IndexerName { get; set; } + + public bool IsBindablePropertySetter { get; set; } + + public bool IsIndexer { get; } + + public bool IsSelf { get; } + + public MethodInfo LastGetter { get; set; } + + public MethodInfo LastSetter { get; set; } + + public BindingExpressionPart NextPart { get; set; } + + public Type SetterType { get; set; } + + public void PropertyChanged(object sender, PropertyChangedEventArgs args) + { + BindingExpressionPart part = NextPart ?? this; + + string name = args.PropertyName; + + if (!string.IsNullOrEmpty(name)) + { + if (part.IsIndexer) + { + if (name.Contains("[")) + { + if (name != string.Format("{0}[{1}]", part.IndexerName, part.Content)) + return; + } + else if (name != part.IndexerName) + return; + } + else if (name != part.Content) + { + return; + } + } + + Device.BeginInvokeOnMainThread(() => _expression.Apply()); + } + + public bool TryGetValue(object source, out object value) + { + value = source; + + if (LastGetter != null && value != null) + { + if (IsIndexer) + { + try + { + value = LastGetter.Invoke(value, Arguments); + } + catch (TargetInvocationException ex) + { + if (!(ex.InnerException is KeyNotFoundException)) + throw; + value = null; + } + return true; + } + value = LastGetter.Invoke(value, Arguments); + return true; + } + + return false; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BindingMode.cs b/Xamarin.Forms.Core/BindingMode.cs new file mode 100644 index 00000000..89396acb --- /dev/null +++ b/Xamarin.Forms.Core/BindingMode.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public enum BindingMode + { + Default, + TwoWay, + OneWay, + OneWayToSource + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BindingTypeConverter.cs b/Xamarin.Forms.Core/BindingTypeConverter.cs new file mode 100644 index 00000000..07dda896 --- /dev/null +++ b/Xamarin.Forms.Core/BindingTypeConverter.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public sealed class BindingTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + return new Binding(value); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BoundsConstraint.cs b/Xamarin.Forms.Core/BoundsConstraint.cs new file mode 100644 index 00000000..43be86eb --- /dev/null +++ b/Xamarin.Forms.Core/BoundsConstraint.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Xamarin.Forms +{ + public class BoundsConstraint + { + Func<Rectangle> _measureFunc; + + BoundsConstraint() + { + } + + internal IEnumerable<View> RelativeTo { get; set; } + + public static BoundsConstraint FromExpression(Expression<Func<Rectangle>> expression, IEnumerable<View> parents = null) + { + Func<Rectangle> compiled = expression.Compile(); + var result = new BoundsConstraint + { + _measureFunc = compiled, + RelativeTo = parents ?? ExpressionSearch.Default.FindObjects<View>(expression).ToArray() // make sure we have our own copy + }; + + return result; + } + + internal Rectangle Compute() + { + return _measureFunc(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BoundsTypeConverter.cs b/Xamarin.Forms.Core/BoundsTypeConverter.cs new file mode 100644 index 00000000..549b7b63 --- /dev/null +++ b/Xamarin.Forms.Core/BoundsTypeConverter.cs @@ -0,0 +1,42 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms +{ + public class BoundsTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + double x = -1, y = -1, w = -1, h = -1; + string[] xywh = value.Split(','); + bool hasX, hasY, hasW, hasH; + + hasX = (xywh.Length == 2 || xywh.Length == 4) && double.TryParse(xywh[0], NumberStyles.Number, CultureInfo.InvariantCulture, out x); + hasY = (xywh.Length == 2 || xywh.Length == 4) && double.TryParse(xywh[1], NumberStyles.Number, CultureInfo.InvariantCulture, out y); + hasW = xywh.Length == 4 && double.TryParse(xywh[2], NumberStyles.Number, CultureInfo.InvariantCulture, out w); + hasH = xywh.Length == 4 && double.TryParse(xywh[3], NumberStyles.Number, CultureInfo.InvariantCulture, out h); + + if (!hasW && xywh.Length == 4 && string.Compare("AutoSize", xywh[2].Trim(), StringComparison.OrdinalIgnoreCase) == 0) + { + hasW = true; + w = AbsoluteLayout.AutoSize; + } + + if (!hasH && xywh.Length == 4 && string.Compare("AutoSize", xywh[3].Trim(), StringComparison.OrdinalIgnoreCase) == 0) + { + hasH = true; + h = AbsoluteLayout.AutoSize; + } + + if (hasX && hasY && xywh.Length == 2) + return new Rectangle(x, y, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize); + if (hasX && hasY && hasW && hasH && xywh.Length == 4) + return new Rectangle(x, y, w, h); + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Rectangle))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/BoxView.cs b/Xamarin.Forms.Core/BoxView.cs new file mode 100644 index 00000000..79fef390 --- /dev/null +++ b/Xamarin.Forms.Core/BoxView.cs @@ -0,0 +1,23 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_BoxViewRenderer))] + public class BoxView : View + { + public static readonly BindableProperty ColorProperty = BindableProperty.Create("Color", typeof(Color), typeof(BoxView), Color.Default); + + public Color Color + { + get { return (Color)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + return new SizeRequest(new Size(40, 40)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Button.cs b/Xamarin.Forms.Core/Button.cs new file mode 100644 index 00000000..4662105f --- /dev/null +++ b/Xamarin.Forms.Core/Button.cs @@ -0,0 +1,251 @@ +using System; +using System.Windows.Input; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_ButtonRenderer))] + public class Button : View, IFontElement, IButtonController + { + public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(Button), null, propertyChanged: (bo, o, n) => ((Button)bo).OnCommandChanged()); + + public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(Button), null, + propertyChanged: (bindable, oldvalue, newvalue) => ((Button)bindable).CommandCanExecuteChanged(bindable, EventArgs.Empty)); + + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Button), null, + propertyChanged: (bindable, oldVal, newVal) => ((Button)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged)); + + public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(Button), Color.Default); + + public static readonly BindableProperty FontProperty = BindableProperty.Create("Font", typeof(Font), typeof(Button), default(Font), propertyChanged: FontStructPropertyChanged); + + public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create("FontFamily", typeof(string), typeof(Button), default(string), propertyChanged: SpecificFontPropertyChanged); + + public static readonly BindableProperty FontSizeProperty = BindableProperty.Create("FontSize", typeof(double), typeof(Button), -1.0, propertyChanged: SpecificFontPropertyChanged, + defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (Button)bindable)); + + public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create("FontAttributes", typeof(FontAttributes), typeof(Button), FontAttributes.None, + propertyChanged: SpecificFontPropertyChanged); + + public static readonly BindableProperty BorderWidthProperty = BindableProperty.Create("BorderWidth", typeof(double), typeof(Button), 0d); + + public static readonly BindableProperty BorderColorProperty = BindableProperty.Create("BorderColor", typeof(Color), typeof(Button), Color.Default); + + public static readonly BindableProperty BorderRadiusProperty = BindableProperty.Create("BorderRadius", typeof(int), typeof(Button), 5); + + public static readonly BindableProperty ImageProperty = BindableProperty.Create("Image", typeof(FileImageSource), typeof(Button), default(FileImageSource), + propertyChanging: (bindable, oldvalue, newvalue) => ((Button)bindable).OnSourcePropertyChanging((ImageSource)oldvalue, (ImageSource)newvalue), + propertyChanged: (bindable, oldvalue, newvalue) => ((Button)bindable).OnSourcePropertyChanged((ImageSource)oldvalue, (ImageSource)newvalue)); + + bool _cancelEvents; + + public Color BorderColor + { + get { return (Color)GetValue(BorderColorProperty); } + set { SetValue(BorderColorProperty, value); } + } + + public int BorderRadius + { + get { return (int)GetValue(BorderRadiusProperty); } + set { SetValue(BorderRadiusProperty, value); } + } + + public double BorderWidth + { + get { return (double)GetValue(BorderWidthProperty); } + set { SetValue(BorderWidthProperty, value); } + } + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public object CommandParameter + { + get { return GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public Font Font + { + get { return (Font)GetValue(FontProperty); } + set { SetValue(FontProperty, value); } + } + + public FileImageSource Image + { + get { return (FileImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public Color TextColor + { + get { return (Color)GetValue(TextColorProperty); } + set { SetValue(TextColorProperty, value); } + } + + bool IsEnabledCore + { + set { SetValueCore(IsEnabledProperty, value); } + } + + void IButtonController.SendClicked() + { + ICommand cmd = Command; + if (cmd != null) + cmd.Execute(CommandParameter); + + EventHandler handler = Clicked; + if (handler != null) + handler(this, EventArgs.Empty); + } + + public FontAttributes FontAttributes + { + get { return (FontAttributes)GetValue(FontAttributesProperty); } + set { SetValue(FontAttributesProperty, value); } + } + + public string FontFamily + { + get { return (string)GetValue(FontFamilyProperty); } + set { SetValue(FontFamilyProperty, value); } + } + + [TypeConverter(typeof(FontSizeConverter))] + public double FontSize + { + get { return (double)GetValue(FontSizeProperty); } + set { SetValue(FontSizeProperty, value); } + } + + public event EventHandler Clicked; + + protected override void OnBindingContextChanged() + { + FileImageSource image = Image; + if (image != null) + SetInheritedBindingContext(image, BindingContext); + + base.OnBindingContextChanged(); + } + + protected override void OnPropertyChanging(string propertyName = null) + { + if (propertyName == CommandProperty.PropertyName) + { + ICommand cmd = Command; + if (cmd != null) + cmd.CanExecuteChanged -= CommandCanExecuteChanged; + } + + base.OnPropertyChanging(propertyName); + } + + void CommandCanExecuteChanged(object sender, EventArgs eventArgs) + { + ICommand cmd = Command; + if (cmd != null) + IsEnabledCore = cmd.CanExecute(CommandParameter); + } + + static void FontStructPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + var button = (Button)bindable; + + if (button._cancelEvents) + return; + + button.InvalidateMeasure(InvalidationTrigger.MeasureChanged); + + button._cancelEvents = true; + + if (button.Font == Font.Default) + { + button.FontFamily = null; + button.FontSize = Device.GetNamedSize(NamedSize.Default, button); + button.FontAttributes = FontAttributes.None; + } + else + { + button.FontFamily = button.Font.FontFamily; + if (button.Font.UseNamedSize) + { + button.FontSize = Device.GetNamedSize(button.Font.NamedSize, button.GetType(), true); + } + else + { + button.FontSize = button.Font.FontSize; + } + button.FontAttributes = button.Font.FontAttributes; + } + + button._cancelEvents = false; + } + + void OnCommandChanged() + { + if (Command != null) + { + Command.CanExecuteChanged += CommandCanExecuteChanged; + CommandCanExecuteChanged(this, EventArgs.Empty); + } + else + IsEnabledCore = true; + } + + void OnSourceChanged(object sender, EventArgs eventArgs) + { + OnPropertyChanged(ImageProperty.PropertyName); + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + void OnSourcePropertyChanged(ImageSource oldvalue, ImageSource newvalue) + { + if (newvalue != null) + { + newvalue.SourceChanged += OnSourceChanged; + SetInheritedBindingContext(newvalue, BindingContext); + } + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + void OnSourcePropertyChanging(ImageSource oldvalue, ImageSource newvalue) + { + if (oldvalue != null) + oldvalue.SourceChanged -= OnSourceChanged; + } + + static void SpecificFontPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + var button = (Button)bindable; + + if (button._cancelEvents) + return; + + button.InvalidateMeasure(InvalidationTrigger.MeasureChanged); + + button._cancelEvents = true; + + if (button.FontFamily != null) + { + button.Font = Font.OfSize(button.FontFamily, button.FontSize).WithAttributes(button.FontAttributes); + } + else + { + button.Font = Font.SystemFontOfSize(button.FontSize, button.FontAttributes); + } + + button._cancelEvents = false; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/CarouselPage.cs b/Xamarin.Forms.Core/CarouselPage.cs new file mode 100644 index 00000000..a6f769e5 --- /dev/null +++ b/Xamarin.Forms.Core/CarouselPage.cs @@ -0,0 +1,17 @@ +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_CarouselPageRenderer))] + public class CarouselPage : MultiPage<ContentPage> + { + protected override ContentPage CreateDefault(object item) + { + var page = new ContentPage(); + if (item != null) + page.Title = item.ToString(); + + return page; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/CarouselView.cs b/Xamarin.Forms.Core/CarouselView.cs new file mode 100644 index 00000000..62e9393e --- /dev/null +++ b/Xamarin.Forms.Core/CarouselView.cs @@ -0,0 +1,86 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_CarouselViewRenderer))] + public class CarouselView : ItemsView, ICarouselViewController + { + public static readonly BindableProperty PositionProperty = BindableProperty.Create(nameof(Position), typeof(int), typeof(CarouselView), 0, BindingMode.TwoWay); + + public static readonly BindableProperty ItemProperty = BindableProperty.Create(nameof(Item), typeof(object), typeof(CarouselView), 0, BindingMode.TwoWay); + + object _lastItem; + + int _lastPosition; + + public CarouselView() + { + _lastPosition = 0; + _lastItem = null; + VerticalOptions = LayoutOptions.FillAndExpand; + HorizontalOptions = LayoutOptions.FillAndExpand; + } + + public int Item + { + get { return (int)GetValue(ItemProperty); } + } + + public int Position + { + get { return (int)GetValue(PositionProperty); } + set { SetValue(PositionProperty, value); } + } + + void ICarouselViewController.SendPositionAppearing(int position) + { + ItemAppearing?.Invoke(this, new ItemVisibilityEventArgs(GetItem(position))); + } + + void ICarouselViewController.SendPositionDisappearing(int position) + { + ItemDisappearing?.Invoke(this, new ItemVisibilityEventArgs(GetItem(position))); + } + + void ICarouselViewController.SendSelectedItemChanged(object item) + { + if (item.Equals(_lastItem)) + return; + + ItemSelected?.Invoke(this, new SelectedItemChangedEventArgs(item)); + _lastItem = item; + } + + void ICarouselViewController.SendSelectedPositionChanged(int position) + { + if (_lastPosition == position) + return; + + _lastPosition = position; + PositionSelected?.Invoke(this, new SelectedPositionChangedEventArgs(position)); + } + + public event EventHandler<SelectedItemChangedEventArgs> ItemSelected; + + public event EventHandler<SelectedPositionChangedEventArgs> PositionSelected; + + protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) + { + var minimumSize = new Size(40, 40); + return new SizeRequest(minimumSize, minimumSize); + } + + // non-public bc unable to implement on iOS + internal event EventHandler<ItemVisibilityEventArgs> ItemAppearing; + + internal event EventHandler<ItemVisibilityEventArgs> ItemDisappearing; + + object GetItem(int position) + { + var controller = (IItemViewController)this; + object item = controller.GetItem(position); + return item; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/CastingEnumerator.cs b/Xamarin.Forms.Core/CastingEnumerator.cs new file mode 100644 index 00000000..62a823b1 --- /dev/null +++ b/Xamarin.Forms.Core/CastingEnumerator.cs @@ -0,0 +1,46 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + internal class CastingEnumerator<T, TFrom> : IEnumerator<T> where T : class where TFrom : class + { + readonly IEnumerator<TFrom> _enumerator; + + bool _disposed; + + public CastingEnumerator(IEnumerator<TFrom> enumerator) + { + _enumerator = enumerator; + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + + _enumerator.Dispose(); + } + + object IEnumerator.Current + { + get { return Current; } + } + + public bool MoveNext() + { + return _enumerator.MoveNext(); + } + + public void Reset() + { + _enumerator.Reset(); + } + + public T Current + { + get { return _enumerator.Current as T; } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Cells/Cell.cs b/Xamarin.Forms.Core/Cells/Cell.cs new file mode 100644 index 00000000..3b16d06a --- /dev/null +++ b/Xamarin.Forms.Core/Cells/Cell.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + public abstract class Cell : Element + { + public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create("IsEnabled", typeof(bool), typeof(Cell), true, propertyChanged: OnIsEnabledPropertyChanged); + + ObservableCollection<MenuItem> _contextActions; + + double _height = -1; + + bool _nextCallToForceUpdateSizeQueued; + + public IList<MenuItem> ContextActions + { + get + { + if (_contextActions == null) + { + _contextActions = new ObservableCollection<MenuItem>(); + _contextActions.CollectionChanged += OnContextActionsChanged; + } + + return _contextActions; + } + } + + public bool HasContextActions + { + get { return _contextActions != null && _contextActions.Count > 0 && IsEnabled; } + } + + public double Height + { + get { return _height; } + set + { + if (_height == value) + return; + + OnPropertyChanging("Height"); + OnPropertyChanging("RenderHeight"); + _height = value; + OnPropertyChanged("Height"); + OnPropertyChanged("RenderHeight"); + } + } + + public bool IsEnabled + { + get { return (bool)GetValue(IsEnabledProperty); } + set { SetValue(IsEnabledProperty, value); } + } + + public double RenderHeight + { + get + { + var table = RealParent as TableView; + if (table != null) + return table.HasUnevenRows && Height > 0 ? Height : table.RowHeight; + + var list = RealParent as ListView; + if (list != null) + return list.HasUnevenRows && Height > 0 ? Height : list.RowHeight; + + return 40; + } + } + + public event EventHandler Appearing; + + public event EventHandler Disappearing; + + public void ForceUpdateSize() + { + if (_nextCallToForceUpdateSizeQueued) + return; + + if ((Parent as ListView)?.HasUnevenRows == true) + { + _nextCallToForceUpdateSizeQueued = true; + OnForceUpdateSizeRequested(); + } + } + + public event EventHandler Tapped; + + protected internal virtual void OnTapped() + { + if (Tapped != null) + Tapped(this, EventArgs.Empty); + } + + protected virtual void OnAppearing() + { + EventHandler handler = Appearing; + if (handler != null) + handler(this, EventArgs.Empty); + } + + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + + if (HasContextActions) + { + for (var i = 0; i < _contextActions.Count; i++) + SetInheritedBindingContext(_contextActions[i], BindingContext); + } + } + + protected virtual void OnDisappearing() + { + EventHandler handler = Disappearing; + if (handler != null) + handler(this, EventArgs.Empty); + } + + protected override void OnParentSet() + { + if (RealParent != null) + { + RealParent.PropertyChanged += OnParentPropertyChanged; + RealParent.PropertyChanging += OnParentPropertyChanging; + } + + base.OnParentSet(); + } + + protected override void OnPropertyChanging(string propertyName = null) + { + if (propertyName == "Parent") + { + if (RealParent != null) + { + RealParent.PropertyChanged -= OnParentPropertyChanged; + RealParent.PropertyChanging -= OnParentPropertyChanging; + } + } + + base.OnPropertyChanging(propertyName); + } + + internal event EventHandler ForceUpdateSizeRequested; + + internal void SendAppearing() + { + OnAppearing(); + + var container = RealParent as IListViewController; + if (container != null) + container.SendCellAppearing(this); + } + + internal void SendDisappearing() + { + OnDisappearing(); + + var container = RealParent as IListViewController; + if (container != null) + container.SendCellDisappearing(this); + } + + void OnContextActionsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + for (var i = 0; i < _contextActions.Count; i++) + SetInheritedBindingContext(_contextActions[i], BindingContext); + + OnPropertyChanged("HasContextActions"); + } + + async void OnForceUpdateSizeRequested() + { + // don't run more than once per 16 milliseconds + await Task.Delay(TimeSpan.FromMilliseconds(16)); + EventHandler handler = ForceUpdateSizeRequested; + if (handler != null) + handler(this, null); + + _nextCallToForceUpdateSizeQueued = false; + } + + static void OnIsEnabledPropertyChanged(BindableObject bindable, object oldvalue, object newvalue) + { + (bindable as Cell).OnPropertyChanged("HasContextActions"); + } + + void OnParentPropertyChanged(object sender, PropertyChangedEventArgs e) + { + // Technically we might be raising this even if it didn't change, but I'm taking the bet that + // its uncommon enough that we don't want to take the penalty of N GetValue calls to verify. + if (e.PropertyName == "RowHeight") + OnPropertyChanged("RenderHeight"); + } + + void OnParentPropertyChanging(object sender, PropertyChangingEventArgs e) + { + if (e.PropertyName == "RowHeight") + OnPropertyChanging("RenderHeight"); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Cells/EntryCell.cs b/Xamarin.Forms.Core/Cells/EntryCell.cs new file mode 100644 index 00000000..d74e365e --- /dev/null +++ b/Xamarin.Forms.Core/Cells/EntryCell.cs @@ -0,0 +1,80 @@ +using System; + +namespace Xamarin.Forms +{ + public class EntryCell : Cell + { + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(EntryCell), null, BindingMode.TwoWay); + + public static readonly BindableProperty LabelProperty = BindableProperty.Create("Label", typeof(string), typeof(EntryCell), null); + + public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(EntryCell), null); + + public static readonly BindableProperty LabelColorProperty = BindableProperty.Create("LabelColor", typeof(Color), typeof(EntryCell), Color.Default); + + public static readonly BindableProperty KeyboardProperty = BindableProperty.Create("Keyboard", typeof(Keyboard), typeof(EntryCell), Keyboard.Default); + + public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create("HorizontalTextAlignment", typeof(TextAlignment), typeof(EntryCell), TextAlignment.Start, + propertyChanged: OnHorizontalTextAlignmentPropertyChanged); + + [Obsolete("XAlignProperty is obsolete. Please use HorizontalTextAlignmentProperty instead.")] public static readonly BindableProperty XAlignProperty = HorizontalTextAlignmentProperty; + + public TextAlignment HorizontalTextAlignment + { + get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); } + set { SetValue(HorizontalTextAlignmentProperty, value); } + } + + public Keyboard Keyboard + { + get { return (Keyboard)GetValue(KeyboardProperty); } + set { SetValue(KeyboardProperty, value); } + } + + public string Label + { + get { return (string)GetValue(LabelProperty); } + set { SetValue(LabelProperty, value); } + } + + public Color LabelColor + { + get { return (Color)GetValue(LabelColorProperty); } + set { SetValue(LabelColorProperty, value); } + } + + public string Placeholder + { + get { return (string)GetValue(PlaceholderProperty); } + set { SetValue(PlaceholderProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + [Obsolete("XAlign is obsolete. Please use HorizontalTextAlignment instead.")] + public TextAlignment XAlign + { + get { return (TextAlignment)GetValue(XAlignProperty); } + set { SetValue(XAlignProperty, value); } + } + + public event EventHandler Completed; + + internal void SendCompleted() + { + EventHandler handler = Completed; + if (handler != null) + handler(this, EventArgs.Empty); + } + + static void OnHorizontalTextAlignmentPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + var label = (EntryCell)bindable; + label.OnPropertyChanged(nameof(XAlign)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Cells/INativeElementView.cs b/Xamarin.Forms.Core/Cells/INativeElementView.cs new file mode 100644 index 00000000..2d015282 --- /dev/null +++ b/Xamarin.Forms.Core/Cells/INativeElementView.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms +{ + public interface INativeElementView + { + Element Element { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Cells/ImageCell.cs b/Xamarin.Forms.Core/Cells/ImageCell.cs new file mode 100644 index 00000000..6d5ba714 --- /dev/null +++ b/Xamarin.Forms.Core/Cells/ImageCell.cs @@ -0,0 +1,56 @@ +using System; + +namespace Xamarin.Forms +{ + public class ImageCell : TextCell + { + public static readonly BindableProperty ImageSourceProperty = BindableProperty.Create("ImageSource", typeof(ImageSource), typeof(ImageCell), null, + propertyChanging: (bindable, oldvalue, newvalue) => ((ImageCell)bindable).OnSourcePropertyChanging((ImageSource)oldvalue, (ImageSource)newvalue), + propertyChanged: (bindable, oldvalue, newvalue) => ((ImageCell)bindable).OnSourcePropertyChanged((ImageSource)oldvalue, (ImageSource)newvalue)); + + public ImageCell() + { + Disappearing += (sender, e) => + { + if (ImageSource == null) + return; + ImageSource.Cancel(); + }; + } + + [TypeConverter(typeof(ImageSourceConverter))] + public ImageSource ImageSource + { + get { return (ImageSource)GetValue(ImageSourceProperty); } + set { SetValue(ImageSourceProperty, value); } + } + + protected override void OnBindingContextChanged() + { + if (ImageSource != null) + SetInheritedBindingContext(ImageSource, BindingContext); + + base.OnBindingContextChanged(); + } + + void OnSourceChanged(object sender, EventArgs eventArgs) + { + OnPropertyChanged(ImageSourceProperty.PropertyName); + } + + void OnSourcePropertyChanged(ImageSource oldvalue, ImageSource newvalue) + { + if (newvalue != null) + { + newvalue.SourceChanged += OnSourceChanged; + SetInheritedBindingContext(newvalue, BindingContext); + } + } + + void OnSourcePropertyChanging(ImageSource oldvalue, ImageSource newvalue) + { + if (oldvalue != null) + oldvalue.SourceChanged -= OnSourceChanged; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Cells/SwitchCell.cs b/Xamarin.Forms.Core/Cells/SwitchCell.cs new file mode 100644 index 00000000..adab7f45 --- /dev/null +++ b/Xamarin.Forms.Core/Cells/SwitchCell.cs @@ -0,0 +1,31 @@ +using System; + +namespace Xamarin.Forms +{ + public class SwitchCell : Cell + { + public static readonly BindableProperty OnProperty = BindableProperty.Create("On", typeof(bool), typeof(SwitchCell), false, propertyChanged: (obj, oldValue, newValue) => + { + var switchCell = (SwitchCell)obj; + EventHandler<ToggledEventArgs> handler = switchCell.OnChanged; + if (handler != null) + handler(obj, new ToggledEventArgs((bool)newValue)); + }, defaultBindingMode: BindingMode.TwoWay); + + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(SwitchCell), default(string)); + + public bool On + { + get { return (bool)GetValue(OnProperty); } + set { SetValue(OnProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public event EventHandler<ToggledEventArgs> OnChanged; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Cells/TextCell.cs b/Xamarin.Forms.Core/Cells/TextCell.cs new file mode 100644 index 00000000..01da6447 --- /dev/null +++ b/Xamarin.Forms.Core/Cells/TextCell.cs @@ -0,0 +1,94 @@ +using System; +using System.Windows.Input; + +namespace Xamarin.Forms +{ + public class TextCell : Cell + { + public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(TextCell), default(ICommand), + propertyChanging: (bindable, oldvalue, newvalue) => + { + var textCell = (TextCell)bindable; + var oldcommand = (ICommand)oldvalue; + if (oldcommand != null) + oldcommand.CanExecuteChanged -= textCell.OnCommandCanExecuteChanged; + }, propertyChanged: (bindable, oldvalue, newvalue) => + { + var textCell = (TextCell)bindable; + var newcommand = (ICommand)newvalue; + if (newcommand != null) + { + textCell.IsEnabled = newcommand.CanExecute(textCell.CommandParameter); + newcommand.CanExecuteChanged += textCell.OnCommandCanExecuteChanged; + } + }); + + public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(TextCell), default(object), + propertyChanged: (bindable, oldvalue, newvalue) => + { + var textCell = (TextCell)bindable; + if (textCell.Command != null) + { + textCell.IsEnabled = textCell.Command.CanExecute(newvalue); + } + }); + + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(TextCell), default(string)); + + public static readonly BindableProperty DetailProperty = BindableProperty.Create("Detail", typeof(string), typeof(TextCell), default(string)); + + public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(TextCell), Color.Default); + + public static readonly BindableProperty DetailColorProperty = BindableProperty.Create("DetailColor", typeof(Color), typeof(TextCell), Color.Default); + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public object CommandParameter + { + get { return GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public string Detail + { + get { return (string)GetValue(DetailProperty); } + set { SetValue(DetailProperty, value); } + } + + public Color DetailColor + { + get { return (Color)GetValue(DetailColorProperty); } + set { SetValue(DetailColorProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public Color TextColor + { + get { return (Color)GetValue(TextColorProperty); } + set { SetValue(TextColorProperty, value); } + } + + protected internal override void OnTapped() + { + base.OnTapped(); + + ICommand cmd = Command; + if (cmd != null) + cmd.Execute(CommandParameter); + } + + void OnCommandCanExecuteChanged(object sender, EventArgs eventArgs) + { + IsEnabled = Command.CanExecute(CommandParameter); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Cells/ViewCell.cs b/Xamarin.Forms.Core/Cells/ViewCell.cs new file mode 100644 index 00000000..334822f6 --- /dev/null +++ b/Xamarin.Forms.Core/Cells/ViewCell.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Xamarin.Forms +{ + [ContentProperty("View")] + public class ViewCell : Cell + { + ReadOnlyCollection<Element> _logicalChildren; + + View _view; + + public View View + { + get { return _view; } + set + { + if (_view == value) + return; + + OnPropertyChanging(); + + if (_view != null) + { + OnChildRemoved(_view); + _view.ComputedConstraint = LayoutConstraint.None; + } + + _view = value; + + if (_view != null) + { + _view.ComputedConstraint = LayoutConstraint.Fixed; + OnChildAdded(_view); + _logicalChildren = new ReadOnlyCollection<Element>(new List<Element>(new[] { View })); + } + else + { + _logicalChildren = null; + } + + OnPropertyChanged(); + } + } + + internal override ReadOnlyCollection<Element> LogicalChildren => _logicalChildren ?? base.LogicalChildren; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ChatKeyboard.cs b/Xamarin.Forms.Core/ChatKeyboard.cs new file mode 100644 index 00000000..26a403a0 --- /dev/null +++ b/Xamarin.Forms.Core/ChatKeyboard.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + internal sealed class ChatKeyboard : Keyboard + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ChildCollectionChangedEventArgs.cs b/Xamarin.Forms.Core/ChildCollectionChangedEventArgs.cs new file mode 100644 index 00000000..3c5e946a --- /dev/null +++ b/Xamarin.Forms.Core/ChildCollectionChangedEventArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Specialized; + +namespace Xamarin.Forms +{ + internal class ChildCollectionChangedEventArgs : EventArgs + { + public ChildCollectionChangedEventArgs(NotifyCollectionChangedEventArgs args) + { + Args = args; + } + + public NotifyCollectionChangedEventArgs Args { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/CollectionSynchronizationCallback.cs b/Xamarin.Forms.Core/CollectionSynchronizationCallback.cs new file mode 100644 index 00000000..e186e016 --- /dev/null +++ b/Xamarin.Forms.Core/CollectionSynchronizationCallback.cs @@ -0,0 +1,7 @@ +using System; +using System.Collections; + +namespace Xamarin.Forms +{ + public delegate void CollectionSynchronizationCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess); +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/CollectionSynchronizationContext.cs b/Xamarin.Forms.Core/CollectionSynchronizationContext.cs new file mode 100644 index 00000000..a0144260 --- /dev/null +++ b/Xamarin.Forms.Core/CollectionSynchronizationContext.cs @@ -0,0 +1,22 @@ +using System; + +namespace Xamarin.Forms +{ + internal sealed class CollectionSynchronizationContext + { + internal CollectionSynchronizationContext(object context, CollectionSynchronizationCallback callback) + { + ContextReference = new WeakReference(context); + Callback = callback; + } + + internal CollectionSynchronizationCallback Callback { get; private set; } + + internal object Context + { + get { return ContextReference != null ? ContextReference.Target : null; } + } + + internal WeakReference ContextReference { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Color.cs b/Xamarin.Forms.Core/Color.cs new file mode 100644 index 00000000..e9f2987d --- /dev/null +++ b/Xamarin.Forms.Core/Color.cs @@ -0,0 +1,375 @@ +using System; +using System.Diagnostics; +using System.Globalization; + +namespace Xamarin.Forms +{ + [DebuggerDisplay("R={R}, G={G}, B={B}, A={A}, Hue={Hue}, Saturation={Saturation}, Luminosity={Luminosity}")] + [TypeConverter(typeof(ColorTypeConverter))] + public struct Color + { + readonly Mode _mode; + + enum Mode + { + Default, + Rgb, + Hsl + } + + public static Color Default + { + get { return new Color(-1d, -1d, -1d, -1d, Mode.Default); } + } + + internal bool IsDefault + { + get { return _mode == Mode.Default; } + } + + public static Color Accent { get; internal set; } + + readonly float _a; + + public double A + { + get { return _a; } + } + + readonly float _r; + + public double R + { + get { return _r; } + } + + readonly float _g; + + public double G + { + get { return _g; } + } + + readonly float _b; + + public double B + { + get { return _b; } + } + + readonly float _hue; + + public double Hue + { + get { return _hue; } + } + + readonly float _saturation; + + public double Saturation + { + get { return _saturation; } + } + + readonly float _luminosity; + + public double Luminosity + { + get { return _luminosity; } + } + + public Color(double r, double g, double b, double a) : this(r, g, b, a, Mode.Rgb) + { + } + + Color(double w, double x, double y, double z, Mode mode) + { + _mode = mode; + switch (mode) + { + default: + case Mode.Default: + _r = _g = _b = _a = -1; + _hue = _saturation = _luminosity = -1; + break; + case Mode.Rgb: + _r = (float)w.Clamp(0, 1); + _g = (float)x.Clamp(0, 1); + _b = (float)y.Clamp(0, 1); + _a = (float)z.Clamp(0, 1); + ConvertToHsl(_r, _g, _b, mode, out _hue, out _saturation, out _luminosity); + break; + case Mode.Hsl: + _hue = (float)w.Clamp(0, 1); + _saturation = (float)x.Clamp(0, 1); + _luminosity = (float)y.Clamp(0, 1); + _a = (float)z.Clamp(0, 1); + ConvertToRgb(_hue, _saturation, _luminosity, mode, out _r, out _g, out _b); + break; + } + } + + public Color(double r, double g, double b) : this(r, g, b, 1) + { + } + + public Color(double value) : this(value, value, value, 1) + { + } + + public Color MultiplyAlpha(double alpha) + { + switch (_mode) + { + default: + case Mode.Default: + throw new InvalidOperationException("Invalid on Color.Default"); + case Mode.Rgb: + return new Color(_r, _g, _b, _a * alpha, Mode.Rgb); + case Mode.Hsl: + return new Color(_hue, _saturation, _luminosity, _a * alpha, Mode.Hsl); + } + } + + public Color AddLuminosity(double delta) + { + if (_mode == Mode.Default) + throw new InvalidOperationException("Invalid on Color.Default"); + + return new Color(_hue, _saturation, _luminosity + delta, _a, Mode.Hsl); + } + + public Color WithHue(double hue) + { + if (_mode == Mode.Default) + throw new InvalidOperationException("Invalid on Color.Default"); + return new Color(hue, _saturation, _luminosity, _a, Mode.Hsl); + } + + public Color WithSaturation(double saturation) + { + if (_mode == Mode.Default) + throw new InvalidOperationException("Invalid on Color.Default"); + return new Color(_hue, saturation, _luminosity, _a, Mode.Hsl); + } + + public Color WithLuminosity(double luminosity) + { + if (_mode == Mode.Default) + throw new InvalidOperationException("Invalid on Color.Default"); + return new Color(_hue, _saturation, luminosity, _a, Mode.Hsl); + } + + static void ConvertToRgb(float hue, float saturation, float luminosity, Mode mode, out float r, out float g, out float b) + { + if (mode != Mode.Hsl) + throw new InvalidOperationException(); + + if (luminosity == 0) + { + r = g = b = 0; + return; + } + + if (saturation == 0) + { + r = g = b = luminosity; + return; + } + float temp2 = luminosity <= 0.5f ? luminosity * (1.0f + saturation) : luminosity + saturation - luminosity * saturation; + float temp1 = 2.0f * luminosity - temp2; + + var t3 = new[] { hue + 1.0f / 3.0f, hue, hue - 1.0f / 3.0f }; + var clr = new float[] { 0, 0, 0 }; + for (var i = 0; i < 3; i++) + { + if (t3[i] < 0) + t3[i] += 1.0f; + if (t3[i] > 1) + t3[i] -= 1.0f; + if (6.0 * t3[i] < 1.0) + clr[i] = temp1 + (temp2 - temp1) * t3[i] * 6.0f; + else if (2.0 * t3[i] < 1.0) + clr[i] = temp2; + else if (3.0 * t3[i] < 2.0) + clr[i] = temp1 + (temp2 - temp1) * (2.0f / 3.0f - t3[i]) * 6.0f; + else + clr[i] = temp1; + } + + r = clr[0]; + g = clr[1]; + b = clr[2]; + } + + static void ConvertToHsl(float r, float g, float b, Mode mode, out float h, out float s, out float l) + { + float v = Math.Max(r, g); + v = Math.Max(v, b); + + float m = Math.Min(r, g); + m = Math.Min(m, b); + + l = (m + v) / 2.0f; + if (l <= 0.0) + { + h = s = l = 0; + return; + } + float vm = v - m; + s = vm; + + if (s > 0.0) + { + s /= l <= 0.5f ? v + m : 2.0f - v - m; + } + else + { + h = 0; + s = 0; + return; + } + + float r2 = (v - r) / vm; + float g2 = (v - g) / vm; + float b2 = (v - b) / vm; + + if (r == v) + { + h = g == m ? 5.0f + b2 : 1.0f - g2; + } + else if (g == v) + { + h = b == m ? 1.0f + r2 : 3.0f - b2; + } + else + { + h = r == m ? 3.0f + g2 : 5.0f - r2; + } + h /= 6.0f; + } + + public static bool operator ==(Color color1, Color color2) + { + return EqualsInner(color1, color2); + } + + public static bool operator !=(Color color1, Color color2) + { + return !EqualsInner(color1, color2); + } + + public override int GetHashCode() + { + unchecked + { + int hashcode = _r.GetHashCode(); + hashcode = (hashcode * 397) ^ _g.GetHashCode(); + hashcode = (hashcode * 397) ^ _b.GetHashCode(); + hashcode = (hashcode * 397) ^ _a.GetHashCode(); + return hashcode; + } + } + + public override bool Equals(object obj) + { + if (obj is Color) + { + return EqualsInner(this, (Color)obj); + } + return base.Equals(obj); + } + + static bool EqualsInner(Color color1, Color color2) + { + if (color1._mode == Mode.Default && color2._mode == Mode.Default) + return true; + if (color1._mode == Mode.Default || color2._mode == Mode.Default) + return false; + if (color1._mode == Mode.Hsl && color2._mode == Mode.Hsl) + return color1._hue == color2._hue && color1._saturation == color2._saturation && color1._luminosity == color2._luminosity && color1._a == color2._a; + return color1._r == color2._r && color1._g == color2._g && color1._b == color2._b && color1._a == color2._a; + } + + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "[Color: A={0}, R={1}, G={2}, B={3}, Hue={4}, Saturation={5}, Luminosity={6}]", A, R, G, B, Hue, Saturation, Luminosity); + } + + public static Color FromHex(string hex) + { + hex = hex.Replace("#", ""); + switch (hex.Length) + { + case 3: //#rgb => ffrrggbb + hex = string.Format("ff{0}{1}{2}{3}{4}{5}", hex[0], hex[0], hex[1], hex[1], hex[2], hex[2]); + break; + case 4: //#argb => aarrggbb + hex = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}", hex[0], hex[0], hex[1], hex[1], hex[2], hex[2], hex[3], hex[3]); + break; + case 6: //#rrggbb => ffrrggbb + hex = string.Format("ff{0}", hex); + break; + } + return FromUint(Convert.ToUInt32(hex.Replace("#", ""), 16)); + } + + public static Color FromUint(uint argb) + { + return FromRgba((byte)((argb & 0x00ff0000) >> 0x10), (byte)((argb & 0x0000ff00) >> 0x8), (byte)(argb & 0x000000ff), (byte)((argb & 0xff000000) >> 0x18)); + } + + public static Color FromRgba(int r, int g, int b, int a) + { + double red = (double)r / 255; + double green = (double)g / 255; + double blue = (double)b / 255; + double alpha = (double)a / 255; + return new Color(red, green, blue, alpha, Mode.Rgb); + } + + public static Color FromRgb(int r, int g, int b) + { + return FromRgba(r, g, b, 255); + } + + public static Color FromRgba(double r, double g, double b, double a) + { + return new Color(r, g, b, a); + } + + public static Color FromRgb(double r, double g, double b) + { + return new Color(r, g, b, 1d, Mode.Rgb); + } + + public static Color FromHsla(double h, double s, double l, double a = 1d) + { + return new Color(h, s, l, a, Mode.Hsl); + } + + #region Color Definitions + + public static readonly Color Transparent = FromRgba(0, 0, 0, 0); + public static readonly Color Aqua = FromRgb(0, 255, 255); + public static readonly Color Black = FromRgb(0, 0, 0); + public static readonly Color Blue = FromRgb(0, 0, 255); + public static readonly Color Fuchsia = FromRgb(255, 0, 255); + [Obsolete("Fuschia is obsolete as of version 1.3, please use the correct spelling of Fuchsia")] public static readonly Color Fuschia = FromRgb(255, 0, 255); + public static readonly Color Gray = FromRgb(128, 128, 128); + public static readonly Color Green = FromRgb(0, 128, 0); + public static readonly Color Lime = FromRgb(0, 255, 0); + public static readonly Color Maroon = FromRgb(128, 0, 0); + public static readonly Color Navy = FromRgb(0, 0, 128); + public static readonly Color Olive = FromRgb(128, 128, 0); + public static readonly Color Purple = FromRgb(128, 0, 128); + public static readonly Color Pink = FromRgb(255, 102, 255); + public static readonly Color Red = FromRgb(255, 0, 0); + public static readonly Color Silver = FromRgb(192, 192, 192); + public static readonly Color Teal = FromRgb(0, 128, 128); + public static readonly Color White = FromRgb(255, 255, 255); + public static readonly Color Yellow = FromRgb(255, 255, 0); + + #endregion + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ColorTypeConverter.cs b/Xamarin.Forms.Core/ColorTypeConverter.cs new file mode 100644 index 00000000..547adf3b --- /dev/null +++ b/Xamarin.Forms.Core/ColorTypeConverter.cs @@ -0,0 +1,72 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Xamarin.Forms +{ + public class ColorTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + if (value.Trim().StartsWith("#", StringComparison.Ordinal)) + return Color.FromHex(value); + string[] parts = value.Split('.'); + if (parts.Length == 1 || (parts.Length == 2 && parts[0] == "Color")) + { + string color = parts[parts.Length - 1]; + switch (color) + { + case "Default": + return Color.Default; + case "Transparent": + return Color.Transparent; + case "Aqua": + return Color.Aqua; + case "Black": + return Color.Black; + case "Blue": + return Color.Blue; + case "Fuchsia": + return Color.Fuchsia; + case "Gray": + return Color.Gray; + case "Green": + return Color.Green; + case "Lime": + return Color.Lime; + case "Maroon": + return Color.Maroon; + case "Navy": + return Color.Navy; + case "Olive": + return Color.Olive; + case "Purple": + return Color.Purple; + case "Pink": + return Color.Pink; + case "Red": + return Color.Red; + case "Silver": + return Color.Silver; + case "Teal": + return Color.Teal; + case "White": + return Color.White; + case "Yellow": + return Color.Yellow; + } + FieldInfo field = typeof(Color).GetFields().FirstOrDefault(fi => fi.IsStatic && fi.Name == color); + if (field != null) + return (Color)field.GetValue(null); + PropertyInfo property = typeof(Color).GetProperties().FirstOrDefault(pi => pi.Name == color && pi.CanRead && pi.GetMethod.IsStatic); + if (property != null) + return (Color)property.GetValue(null, null); + } + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Color))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ColumnDefinition.cs b/Xamarin.Forms.Core/ColumnDefinition.cs new file mode 100644 index 00000000..995f6c73 --- /dev/null +++ b/Xamarin.Forms.Core/ColumnDefinition.cs @@ -0,0 +1,34 @@ +using System; + +namespace Xamarin.Forms +{ + public sealed class ColumnDefinition : BindableObject, IDefinition + { + public static readonly BindableProperty WidthProperty = BindableProperty.Create("Width", typeof(GridLength), typeof(ColumnDefinition), new GridLength(1, GridUnitType.Star), + propertyChanged: (bindable, oldValue, newValue) => ((ColumnDefinition)bindable).OnSizeChanged()); + + public ColumnDefinition() + { + MinimumWidth = -1; + } + + public GridLength Width + { + get { return (GridLength)GetValue(WidthProperty); } + set { SetValue(WidthProperty, value); } + } + + internal double ActualWidth { get; set; } + + internal double MinimumWidth { get; set; } + + public event EventHandler SizeChanged; + + void OnSizeChanged() + { + EventHandler eh = SizeChanged; + if (eh != null) + eh(this, EventArgs.Empty); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ColumnDefinitionCollection.cs b/Xamarin.Forms.Core/ColumnDefinitionCollection.cs new file mode 100644 index 00000000..0ee0358b --- /dev/null +++ b/Xamarin.Forms.Core/ColumnDefinitionCollection.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + public sealed class ColumnDefinitionCollection : DefinitionCollection<ColumnDefinition> + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Command.cs b/Xamarin.Forms.Core/Command.cs new file mode 100644 index 00000000..73ae1b08 --- /dev/null +++ b/Xamarin.Forms.Core/Command.cs @@ -0,0 +1,80 @@ +using System; +using System.Windows.Input; + +namespace Xamarin.Forms +{ + public sealed class Command<T> : Command + { + public Command(Action<T> execute) : base(o => execute((T)o)) + { + if (execute == null) + throw new ArgumentNullException("execute"); + } + + public Command(Action<T> execute, Func<T, bool> canExecute) : base(o => execute((T)o), o => canExecute((T)o)) + { + if (execute == null) + throw new ArgumentNullException("execute"); + if (canExecute == null) + throw new ArgumentNullException("canExecute"); + } + } + + public class Command : ICommand + { + readonly Func<object, bool> _canExecute; + readonly Action<object> _execute; + + public Command(Action<object> execute) + { + if (execute == null) + throw new ArgumentNullException("execute"); + + _execute = execute; + } + + public Command(Action execute) : this(o => execute()) + { + if (execute == null) + throw new ArgumentNullException("execute"); + } + + public Command(Action<object> execute, Func<object, bool> canExecute) : this(execute) + { + if (canExecute == null) + throw new ArgumentNullException("canExecute"); + + _canExecute = canExecute; + } + + public Command(Action execute, Func<bool> canExecute) : this(o => execute(), o => canExecute()) + { + if (execute == null) + throw new ArgumentNullException("execute"); + if (canExecute == null) + throw new ArgumentNullException("canExecute"); + } + + public bool CanExecute(object parameter) + { + if (_canExecute != null) + return _canExecute(parameter); + + return true; + } + + public event EventHandler CanExecuteChanged; + + public void Execute(object parameter) + { + _execute(parameter); + } + + public void ChangeCanExecute() + { + EventHandler changed = CanExecuteChanged; + if (changed != null) + changed(this, EventArgs.Empty); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ConcurrentDictionary.cs b/Xamarin.Forms.Core/ConcurrentDictionary.cs new file mode 100644 index 00000000..a229c6fe --- /dev/null +++ b/Xamarin.Forms.Core/ConcurrentDictionary.cs @@ -0,0 +1,426 @@ +// ConcurrentDictionary.cs +// +// Copyright (c) 2009 Jérémie "Garuma" Laval +// +// 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.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Xamarin.Forms +{ + internal class ConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IEnumerable + { + readonly IEqualityComparer<TKey> _comparer; + + SplitOrderedList<TKey, KeyValuePair<TKey, TValue>> _internalDictionary; + + public ConcurrentDictionary() : this(EqualityComparer<TKey>.Default) + { + } + + public ConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, EqualityComparer<TKey>.Default) + { + } + + public ConcurrentDictionary(IEqualityComparer<TKey> comparer) + { + _comparer = comparer; + _internalDictionary = new SplitOrderedList<TKey, KeyValuePair<TKey, TValue>>(comparer); + } + + public ConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) : this(comparer) + { + foreach (KeyValuePair<TKey, TValue> pair in collection) + Add(pair.Key, pair.Value); + } + + // Parameters unused + public ConcurrentDictionary(int concurrencyLevel, int capacity) : this(EqualityComparer<TKey>.Default) + { + } + + public ConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) : this(collection, comparer) + { + } + + // Parameters unused + public ConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer) : this(comparer) + { + } + + public bool IsEmpty + { + get { return Count == 0; } + } + + void ICollection.CopyTo(Array array, int startIndex) + { + var arr = array as KeyValuePair<TKey, TValue>[]; + if (arr == null) + return; + + CopyTo(arr, startIndex, Count); + } + + bool ICollection.IsSynchronized + { + get { return true; } + } + + object ICollection.SyncRoot + { + get { return this; } + } + + void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> pair) + { + Add(pair.Key, pair.Value); + } + + public void Clear() + { + // Pronk + _internalDictionary = new SplitOrderedList<TKey, KeyValuePair<TKey, TValue>>(_comparer); + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> pair) + { + return ContainsKey(pair.Key); + } + + void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int startIndex) + { + CopyTo(array, startIndex); + } + + public int Count + { + get { return _internalDictionary.Count; } + } + + bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly + { + get { return false; } + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> pair) + { + return Remove(pair.Key); + } + + void IDictionary.Add(object key, object value) + { + if (!(key is TKey) || !(value is TValue)) + throw new ArgumentException("key or value aren't of correct type"); + + Add((TKey)key, (TValue)value); + } + + bool IDictionary.Contains(object key) + { + if (!(key is TKey)) + return false; + + return ContainsKey((TKey)key); + } + + IDictionaryEnumerator IDictionary.GetEnumerator() + { + return new ConcurrentDictionaryEnumerator(GetEnumeratorInternal()); + } + + bool IDictionary.IsFixedSize + { + get { return false; } + } + + bool IDictionary.IsReadOnly + { + get { return false; } + } + + object IDictionary.this[object key] + { + get + { + if (!(key is TKey)) + throw new ArgumentException("key isn't of correct type", "key"); + + return this[(TKey)key]; + } + set + { + if (!(key is TKey) || !(value is TValue)) + throw new ArgumentException("key or value aren't of correct type"); + + this[(TKey)key] = (TValue)value; + } + } + + ICollection IDictionary.Keys + { + get { return (ICollection)Keys; } + } + + void IDictionary.Remove(object key) + { + if (!(key is TKey)) + return; + + Remove((TKey)key); + } + + ICollection IDictionary.Values + { + get { return (ICollection)Values; } + } + + void IDictionary<TKey, TValue>.Add(TKey key, TValue value) + { + Add(key, value); + } + + public bool ContainsKey(TKey key) + { + CheckKey(key); + KeyValuePair<TKey, TValue> dummy; + return _internalDictionary.Find(Hash(key), key, out dummy); + } + + public TValue this[TKey key] + { + get { return GetValue(key); } + set { AddOrUpdate(key, value, value); } + } + + public ICollection<TKey> Keys + { + get { return GetPart(kvp => kvp.Key); } + } + + bool IDictionary<TKey, TValue>.Remove(TKey key) + { + return Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + CheckKey(key); + KeyValuePair<TKey, TValue> pair; + bool result = _internalDictionary.Find(Hash(key), key, out pair); + value = pair.Value; + + return result; + } + + public ICollection<TValue> Values + { + get { return GetPart(kvp => kvp.Value); } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumeratorInternal(); + } + + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + return GetEnumeratorInternal(); + } + + public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory) + { + CheckKey(key); + if (addValueFactory == null) + throw new ArgumentNullException("addValueFactory"); + if (updateValueFactory == null) + throw new ArgumentNullException("updateValueFactory"); + return _internalDictionary.InsertOrUpdate(Hash(key), key, () => Make(key, addValueFactory(key)), e => Make(key, updateValueFactory(key, e.Value))).Value; + } + + public TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory) + { + return AddOrUpdate(key, _ => addValue, updateValueFactory); + } + + public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) + { + CheckKey(key); + return _internalDictionary.InsertOrGet(Hash(key), key, Make(key, default(TValue)), () => Make(key, valueFactory(key))).Value; + } + + public TValue GetOrAdd(TKey key, TValue value) + { + CheckKey(key); + return _internalDictionary.InsertOrGet(Hash(key), key, Make(key, value), null).Value; + } + + public KeyValuePair<TKey, TValue>[] ToArray() + { + // This is most certainly not optimum but there is + // not a lot of possibilities + + return new List<KeyValuePair<TKey, TValue>>(this).ToArray(); + } + + public bool TryAdd(TKey key, TValue value) + { + CheckKey(key); + return _internalDictionary.Insert(Hash(key), key, Make(key, value)); + } + + public bool TryRemove(TKey key, out TValue value) + { + CheckKey(key); + KeyValuePair<TKey, TValue> data; + bool result = _internalDictionary.Delete(Hash(key), key, out data); + value = data.Value; + return result; + } + + public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) + { + CheckKey(key); + return _internalDictionary.CompareExchange(Hash(key), key, Make(key, newValue), e => e.Value.Equals(comparisonValue)); + } + + void Add(TKey key, TValue value) + { + while (!TryAdd(key, value)) + ; + } + + TValue AddOrUpdate(TKey key, TValue addValue, TValue updateValue) + { + CheckKey(key); + return _internalDictionary.InsertOrUpdate(Hash(key), key, Make(key, addValue), Make(key, updateValue)).Value; + } + + void CheckKey(TKey key) + { + if (key == null) + throw new ArgumentNullException("key"); + } + + void CopyTo(KeyValuePair<TKey, TValue>[] array, int startIndex) + { + CopyTo(array, startIndex, Count); + } + + void CopyTo(KeyValuePair<TKey, TValue>[] array, int startIndex, int num) + { + foreach (KeyValuePair<TKey, TValue> kvp in this) + { + array[startIndex++] = kvp; + + if (--num <= 0) + return; + } + } + + IEnumerator<KeyValuePair<TKey, TValue>> GetEnumeratorInternal() + { + return _internalDictionary.GetEnumerator(); + } + + ICollection<T> GetPart<T>(Func<KeyValuePair<TKey, TValue>, T> extractor) + { + var temp = new List<T>(); + + foreach (KeyValuePair<TKey, TValue> kvp in this) + temp.Add(extractor(kvp)); + + return new ReadOnlyCollection<T>(temp); + } + + TValue GetValue(TKey key) + { + TValue temp; + if (!TryGetValue(key, out temp)) + throw new KeyNotFoundException(key.ToString()); + return temp; + } + + uint Hash(TKey key) + { + return (uint)_comparer.GetHashCode(key); + } + + static KeyValuePair<T, V> Make<T, V>(T key, V value) + { + return new KeyValuePair<T, V>(key, value); + } + + bool Remove(TKey key) + { + TValue dummy; + + return TryRemove(key, out dummy); + } + + class ConcurrentDictionaryEnumerator : IDictionaryEnumerator + { + readonly IEnumerator<KeyValuePair<TKey, TValue>> _internalEnum; + + public ConcurrentDictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> internalEnum) + { + _internalEnum = internalEnum; + } + + public DictionaryEntry Entry + { + get + { + KeyValuePair<TKey, TValue> current = _internalEnum.Current; + return new DictionaryEntry(current.Key, current.Value); + } + } + + public object Key + { + get { return _internalEnum.Current.Key; } + } + + public object Value + { + get { return _internalEnum.Current.Value; } + } + + public object Current + { + get { return Entry; } + } + + public bool MoveNext() + { + return _internalEnum.MoveNext(); + } + + public void Reset() + { + _internalEnum.Reset(); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Constraint.cs b/Xamarin.Forms.Core/Constraint.cs new file mode 100644 index 00000000..bd219f0a --- /dev/null +++ b/Xamarin.Forms.Core/Constraint.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Xamarin.Forms +{ + [TypeConverter(typeof(ConstraintTypeConverter))] + public sealed class Constraint + { + Func<RelativeLayout, double> _measureFunc; + + Constraint() + { + } + + internal IEnumerable<View> RelativeTo { get; set; } + + public static Constraint Constant(double size) + { + var result = new Constraint { _measureFunc = parent => size }; + + return result; + } + + public static Constraint FromExpression(Expression<Func<double>> expression) + { + Func<double> compiled = expression.Compile(); + var result = new Constraint + { + _measureFunc = layout => compiled(), + RelativeTo = ExpressionSearch.Default.FindObjects<View>(expression).ToArray() // make sure we have our own copy + }; + + return result; + } + + public static Constraint RelativeToParent(Func<RelativeLayout, double> measure) + { + var result = new Constraint { _measureFunc = measure }; + + return result; + } + + public static Constraint RelativeToView(View view, Func<RelativeLayout, View, double> measure) + { + var result = new Constraint { _measureFunc = layout => measure(layout, view), RelativeTo = new[] { view } }; + + return result; + } + + internal double Compute(RelativeLayout parent) + { + return _measureFunc(parent); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ConstraintExpression.cs b/Xamarin.Forms.Core/ConstraintExpression.cs new file mode 100644 index 00000000..b2ca4b8f --- /dev/null +++ b/Xamarin.Forms.Core/ConstraintExpression.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using System.Reflection; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + public class ConstraintExpression : IMarkupExtension + { + public ConstraintExpression() + { + Factor = 1.0; + } + + public double Constant { get; set; } + + public string ElementName { get; set; } + + public double Factor { get; set; } + + public string Property { get; set; } + + public ConstraintType Type { get; set; } + + public object ProvideValue(IServiceProvider serviceProvider) + { + MethodInfo minfo; + switch (Type) + { + default: + case ConstraintType.RelativeToParent: + if (string.IsNullOrEmpty(Property)) + return null; + minfo = typeof(View).GetProperties().First(pi => pi.Name == Property && pi.CanRead && pi.GetMethod.IsPublic).GetMethod; + return Constraint.RelativeToParent(p => (double)minfo.Invoke(p, new object[] { }) * Factor + Constant); + case ConstraintType.Constant: + return Constraint.Constant(Constant); + case ConstraintType.RelativeToView: + if (string.IsNullOrEmpty(Property)) + return null; + if (string.IsNullOrEmpty(ElementName)) + return null; + minfo = typeof(View).GetProperties().First(pi => pi.Name == Property && pi.CanRead && pi.GetMethod.IsPublic).GetMethod; + var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; + if (valueProvider == null || !(valueProvider.TargetObject is INameScope)) + return null; + var view = ((INameScope)valueProvider.TargetObject).FindByName<View>(ElementName); + return Constraint.RelativeToView(view, delegate(RelativeLayout p, View v) { return (double)minfo.Invoke(v, new object[] { }) * Factor + Constant; }); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ConstraintType.cs b/Xamarin.Forms.Core/ConstraintType.cs new file mode 100644 index 00000000..5ee8bc9a --- /dev/null +++ b/Xamarin.Forms.Core/ConstraintType.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public enum ConstraintType + { + RelativeToParent, + RelativeToView, + Constant + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ConstraintTypeConverter.cs b/Xamarin.Forms.Core/ConstraintTypeConverter.cs new file mode 100644 index 00000000..8cc45229 --- /dev/null +++ b/Xamarin.Forms.Core/ConstraintTypeConverter.cs @@ -0,0 +1,17 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms +{ + public class ConstraintTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + double size; + if (value != null && double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out size)) + return Constraint.Constant(size); + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Color))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ContentPage.cs b/Xamarin.Forms.Core/ContentPage.cs new file mode 100644 index 00000000..01957c39 --- /dev/null +++ b/Xamarin.Forms.Core/ContentPage.cs @@ -0,0 +1,26 @@ +namespace Xamarin.Forms +{ + [ContentProperty("Content")] + public class ContentPage : TemplatedPage + { + public static readonly BindableProperty ContentProperty = BindableProperty.Create(nameof(Content), typeof(View), typeof(ContentPage), null, propertyChanged: TemplateUtilities.OnContentChanged); + + public View Content + { + get { return (View)GetValue(ContentProperty); } + set { SetValue(ContentProperty, value); } + } + + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + + View content = Content; + ControlTemplate controlTemplate = ControlTemplate; + if (content != null && controlTemplate != null) + { + SetInheritedBindingContext(content, BindingContext); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ContentPresenter.cs b/Xamarin.Forms.Core/ContentPresenter.cs new file mode 100644 index 00000000..a99a048b --- /dev/null +++ b/Xamarin.Forms.Core/ContentPresenter.cs @@ -0,0 +1,91 @@ +using System; + +namespace Xamarin.Forms +{ + public class ContentPresenter : Layout + { + public static BindableProperty ContentProperty = BindableProperty.Create("Content", typeof(View), typeof(ContentPresenter), null, propertyChanged: OnContentChanged); + + public ContentPresenter() + { + SetBinding(ContentProperty, new TemplateBinding("Content")); + } + + public View Content + { + get { return (View)GetValue(ContentProperty); } + set { SetValue(ContentProperty, value); } + } + + protected override void LayoutChildren(double x, double y, double width, double height) + { + for (var i = 0; i < LogicalChildren.Count; i++) + { + Element element = LogicalChildren[i]; + var child = element as View; + if (child != null) + LayoutChildIntoBoundingRegion(child, new Rectangle(x, y, width, height)); + } + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + double widthRequest = WidthRequest; + double heightRequest = HeightRequest; + var childRequest = new SizeRequest(); + if ((widthRequest == -1 || heightRequest == -1) && Content != null) + { + childRequest = Content.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins); + } + + return new SizeRequest + { + Request = new Size { Width = widthRequest != -1 ? widthRequest : childRequest.Request.Width, Height = heightRequest != -1 ? heightRequest : childRequest.Request.Height }, + Minimum = childRequest.Minimum + }; + } + + internal virtual void Clear() + { + Content = null; + } + + internal override void ComputeConstraintForView(View view) + { + bool isFixedHorizontally = (Constraint & LayoutConstraint.HorizontallyFixed) != 0; + bool isFixedVertically = (Constraint & LayoutConstraint.VerticallyFixed) != 0; + + var result = LayoutConstraint.None; + if (isFixedVertically && view.VerticalOptions.Alignment == LayoutAlignment.Fill) + result |= LayoutConstraint.VerticallyFixed; + if (isFixedHorizontally && view.HorizontalOptions.Alignment == LayoutAlignment.Fill) + result |= LayoutConstraint.HorizontallyFixed; + view.ComputedConstraint = result; + } + + internal override void SetChildInheritedBindingContext(Element child, object context) + { + // We never want to use the standard inheritance mechanism, we will get this set by our parent + } + + static async void OnContentChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (ContentPresenter)bindable; + + var oldView = (View)oldValue; + var newView = (View)newValue; + if (oldView != null) + { + self.InternalChildren.Remove(oldView); + oldView.ParentOverride = null; + } + + if (newView != null) + { + self.InternalChildren.Add(newView); + newView.ParentOverride = await TemplateUtilities.FindTemplatedParentAsync((Element)bindable); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ContentPropertyAttribute.cs b/Xamarin.Forms.Core/ContentPropertyAttribute.cs new file mode 100644 index 00000000..7aa60744 --- /dev/null +++ b/Xamarin.Forms.Core/ContentPropertyAttribute.cs @@ -0,0 +1,26 @@ +// +// ContentPropertyAttribute.cs +// +// Author: +// Stephane Delcroix <stephane@delcroix.org> +// +// Copyright (c) 2013 S. Delcroix +// + +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Class)] + public sealed class ContentPropertyAttribute : Attribute + { + internal static string[] ContentPropertyTypes = { "Xamarin.Forms.ContentPropertyAttribute", "System.Windows.Markup.ContentPropertyAttribute" }; + + public ContentPropertyAttribute(string name) + { + Name = name; + } + + public string Name { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ContentView.cs b/Xamarin.Forms.Core/ContentView.cs new file mode 100644 index 00000000..a30688f9 --- /dev/null +++ b/Xamarin.Forms.Core/ContentView.cs @@ -0,0 +1,26 @@ +namespace Xamarin.Forms +{ + [ContentProperty("Content")] + public class ContentView : TemplatedView + { + public static readonly BindableProperty ContentProperty = BindableProperty.Create(nameof(Content), typeof(View), typeof(ContentView), null, propertyChanged: TemplateUtilities.OnContentChanged); + + public View Content + { + get { return (View)GetValue(ContentProperty); } + set { SetValue(ContentProperty, value); } + } + + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + + View content = Content; + ControlTemplate controlTemplate = ControlTemplate; + if (content != null && controlTemplate != null) + { + SetInheritedBindingContext(content, BindingContext); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ControlTemplate.cs b/Xamarin.Forms.Core/ControlTemplate.cs new file mode 100644 index 00000000..1e198e97 --- /dev/null +++ b/Xamarin.Forms.Core/ControlTemplate.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Forms +{ + public class ControlTemplate : ElementTemplate + { + public ControlTemplate() + { + } + + public ControlTemplate(Type type) : base(type) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/CustomKeyboard.cs b/Xamarin.Forms.Core/CustomKeyboard.cs new file mode 100644 index 00000000..422198cc --- /dev/null +++ b/Xamarin.Forms.Core/CustomKeyboard.cs @@ -0,0 +1,12 @@ +namespace Xamarin.Forms +{ + internal sealed class CustomKeyboard : Keyboard + { + internal CustomKeyboard(KeyboardFlags flags) + { + Flags = flags; + } + + internal KeyboardFlags Flags { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DataTemplate.cs b/Xamarin.Forms.Core/DataTemplate.cs new file mode 100644 index 00000000..676718a0 --- /dev/null +++ b/Xamarin.Forms.Core/DataTemplate.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + public class DataTemplate : ElementTemplate + { + public DataTemplate() + { + } + + public DataTemplate(Type type) : base(type) + { + } + + public DataTemplate(Func<object> loadTemplate) : base(loadTemplate) + { + } + + public IDictionary<BindableProperty, BindingBase> Bindings { get; } = new Dictionary<BindableProperty, BindingBase>(); + + public IDictionary<BindableProperty, object> Values { get; } = new Dictionary<BindableProperty, object>(); + + public void SetBinding(BindableProperty property, BindingBase binding) + { + if (property == null) + throw new ArgumentNullException("property"); + if (binding == null) + throw new ArgumentNullException("binding"); + + Values.Remove(property); + Bindings[property] = binding; + } + + public void SetValue(BindableProperty property, object value) + { + if (property == null) + throw new ArgumentNullException("property"); + + Bindings.Remove(property); + Values[property] = value; + } + + internal override void SetupContent(object item) + { + ApplyBindings(item); + ApplyValues(item); + } + + void ApplyBindings(object item) + { + if (Bindings == null) + return; + + var bindable = item as BindableObject; + if (bindable == null) + return; + + foreach (KeyValuePair<BindableProperty, BindingBase> kvp in Bindings) + { + if (Values.ContainsKey(kvp.Key)) + throw new InvalidOperationException("Binding and Value found for " + kvp.Key.PropertyName); + + bindable.SetBinding(kvp.Key, kvp.Value.Clone()); + } + } + + void ApplyValues(object item) + { + if (Values == null) + return; + + var bindable = item as BindableObject; + if (bindable == null) + return; + foreach (KeyValuePair<BindableProperty, object> kvp in Values) + bindable.SetValue(kvp.Key, kvp.Value); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DataTemplateExtensions.cs b/Xamarin.Forms.Core/DataTemplateExtensions.cs new file mode 100644 index 00000000..ffa0ffd0 --- /dev/null +++ b/Xamarin.Forms.Core/DataTemplateExtensions.cs @@ -0,0 +1,15 @@ +namespace Xamarin.Forms +{ + internal static class DataTemplateExtensions + { + public static object CreateContent(this DataTemplate self, object item, BindableObject container) + { + var selector = self as DataTemplateSelector; + if (selector != null) + { + self = selector.SelectTemplate(item, container); + } + return self.CreateContent(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DataTemplateSelector.cs b/Xamarin.Forms.Core/DataTemplateSelector.cs new file mode 100644 index 00000000..8ffa4781 --- /dev/null +++ b/Xamarin.Forms.Core/DataTemplateSelector.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + public abstract class DataTemplateSelector : DataTemplate + { + public DataTemplate SelectTemplate(object item, BindableObject container) + { + DataTemplate result = OnSelectTemplate(item, container); + if (result is DataTemplateSelector) + throw new NotSupportedException("DataTemplateSelector.OnSelectTemplate must not return another DataTemplateSelector"); + return result; + } + + protected abstract DataTemplate OnSelectTemplate(object item, BindableObject container); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DateChangedEventArgs.cs b/Xamarin.Forms.Core/DateChangedEventArgs.cs new file mode 100644 index 00000000..8fbc803d --- /dev/null +++ b/Xamarin.Forms.Core/DateChangedEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + public class DateChangedEventArgs : EventArgs + { + public DateChangedEventArgs(DateTime oldDate, DateTime newDate) + { + OldDate = oldDate; + NewDate = newDate; + } + + public DateTime NewDate { get; private set; } + + public DateTime OldDate { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DatePicker.cs b/Xamarin.Forms.Core/DatePicker.cs new file mode 100644 index 00000000..15f1c198 --- /dev/null +++ b/Xamarin.Forms.Core/DatePicker.cs @@ -0,0 +1,99 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_DatePickerRenderer))] + public class DatePicker : View + { + public static readonly BindableProperty FormatProperty = BindableProperty.Create("Format", typeof(string), typeof(DatePicker), "d"); + + public static readonly BindableProperty DateProperty = BindableProperty.Create("Date", typeof(DateTime), typeof(DatePicker), DateTime.Today, BindingMode.TwoWay, coerceValue: CoerceDate, + propertyChanged: DatePropertyChanged); + + public static readonly BindableProperty MinimumDateProperty = BindableProperty.Create("MinimumDate", typeof(DateTime), typeof(DatePicker), new DateTime(1900, 1, 1), + validateValue: ValidateMinimumDate, coerceValue: CoerceMinimumDate); + + public static readonly BindableProperty MaximumDateProperty = BindableProperty.Create("MaximumDate", typeof(DateTime), typeof(DatePicker), new DateTime(2100, 12, 31), + validateValue: ValidateMaximumDate, coerceValue: CoerceMaximumDate); + + public DateTime Date + { + get { return (DateTime)GetValue(DateProperty); } + set { SetValue(DateProperty, value); } + } + + public string Format + { + get { return (string)GetValue(FormatProperty); } + set { SetValue(FormatProperty, value); } + } + + public DateTime MaximumDate + { + get { return (DateTime)GetValue(MaximumDateProperty); } + set { SetValue(MaximumDateProperty, value); } + } + + public DateTime MinimumDate + { + get { return (DateTime)GetValue(MinimumDateProperty); } + set { SetValue(MinimumDateProperty, value); } + } + + public event EventHandler<DateChangedEventArgs> DateSelected; + + static object CoerceDate(BindableObject bindable, object value) + { + var picker = (DatePicker)bindable; + DateTime dateValue = ((DateTime)value).Date; + + if (dateValue > picker.MaximumDate) + dateValue = picker.MaximumDate; + + if (dateValue < picker.MinimumDate) + dateValue = picker.MinimumDate; + + return dateValue; + } + + static object CoerceMaximumDate(BindableObject bindable, object value) + { + DateTime dateValue = ((DateTime)value).Date; + var picker = (DatePicker)bindable; + if (picker.Date > dateValue) + picker.Date = dateValue; + + return dateValue; + } + + static object CoerceMinimumDate(BindableObject bindable, object value) + { + DateTime dateValue = ((DateTime)value).Date; + var picker = (DatePicker)bindable; + if (picker.Date < dateValue) + picker.Date = dateValue; + + return dateValue; + } + + static void DatePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + var datePicker = (DatePicker)bindable; + EventHandler<DateChangedEventArgs> selected = datePicker.DateSelected; + + if (selected != null) + selected(datePicker, new DateChangedEventArgs((DateTime)oldValue, (DateTime)newValue)); + } + + static bool ValidateMaximumDate(BindableObject bindable, object value) + { + return (DateTime)value >= ((DatePicker)bindable).MinimumDate; + } + + static bool ValidateMinimumDate(BindableObject bindable, object value) + { + return (DateTime)value <= ((DatePicker)bindable).MaximumDate; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DefinitionCollection.cs b/Xamarin.Forms.Core/DefinitionCollection.cs new file mode 100644 index 00000000..bf0e4d06 --- /dev/null +++ b/Xamarin.Forms.Core/DefinitionCollection.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + public class DefinitionCollection<T> : IList<T>, ICollection<T> where T : IDefinition + { + readonly List<T> _internalList = new List<T>(); + + internal DefinitionCollection() + { + } + + public void Add(T item) + { + _internalList.Add(item); + item.SizeChanged += OnItemSizeChanged; + OnItemSizeChanged(this, EventArgs.Empty); + } + + public void Clear() + { + foreach (T item in _internalList) + item.SizeChanged -= OnItemSizeChanged; + _internalList.Clear(); + OnItemSizeChanged(this, EventArgs.Empty); + } + + public bool Contains(T item) + { + return _internalList.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _internalList.CopyTo(array, arrayIndex); + } + + public int Count + { + get { return _internalList.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public bool Remove(T item) + { + item.SizeChanged -= OnItemSizeChanged; + bool success = _internalList.Remove(item); + if (success) + OnItemSizeChanged(this, EventArgs.Empty); + return success; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _internalList.GetEnumerator(); + } + + public IEnumerator<T> GetEnumerator() + { + return _internalList.GetEnumerator(); + } + + public int IndexOf(T item) + { + return _internalList.IndexOf(item); + } + + public void Insert(int index, T item) + { + _internalList.Insert(index, item); + item.SizeChanged += OnItemSizeChanged; + OnItemSizeChanged(this, EventArgs.Empty); + } + + public T this[int index] + { + get { return _internalList[index]; } + set + { + _internalList[index] = value; + value.SizeChanged += OnItemSizeChanged; + OnItemSizeChanged(this, EventArgs.Empty); + } + } + + public void RemoveAt(int index) + { + T item = _internalList[index]; + _internalList.RemoveAt(index); + item.SizeChanged -= OnItemSizeChanged; + OnItemSizeChanged(this, EventArgs.Empty); + } + + public event EventHandler ItemSizeChanged; + + void OnItemSizeChanged(object sender, EventArgs e) + { + EventHandler eh = ItemSizeChanged; + if (eh != null) + eh(this, EventArgs.Empty); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DelegateLogListener.cs b/Xamarin.Forms.Core/DelegateLogListener.cs new file mode 100644 index 00000000..20a0ab76 --- /dev/null +++ b/Xamarin.Forms.Core/DelegateLogListener.cs @@ -0,0 +1,22 @@ +using System; + +namespace Xamarin.Forms +{ + internal class DelegateLogListener : LogListener + { + readonly Action<string, string> _log; + + public DelegateLogListener(Action<string, string> log) + { + if (log == null) + throw new ArgumentNullException("log"); + + _log = log; + } + + public override void Warning(string category, string message) + { + _log(category, message); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DependencyAttribute.cs b/Xamarin.Forms.Core/DependencyAttribute.cs new file mode 100644 index 00000000..e0ea22a1 --- /dev/null +++ b/Xamarin.Forms.Core/DependencyAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class DependencyAttribute : Attribute + { + public DependencyAttribute(Type implementorType) + { + Implementor = implementorType; + } + + internal Type Implementor { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DependencyFetchTarget.cs b/Xamarin.Forms.Core/DependencyFetchTarget.cs new file mode 100644 index 00000000..2433c4f4 --- /dev/null +++ b/Xamarin.Forms.Core/DependencyFetchTarget.cs @@ -0,0 +1,8 @@ +namespace Xamarin.Forms +{ + public enum DependencyFetchTarget + { + GlobalInstance, + NewInstance + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DependencyService.cs b/Xamarin.Forms.Core/DependencyService.cs new file mode 100644 index 00000000..d3c43998 --- /dev/null +++ b/Xamarin.Forms.Core/DependencyService.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Xamarin.Forms +{ + public static class DependencyService + { + static bool s_initialized; + + static readonly List<Type> DependencyTypes = new List<Type>(); + static readonly Dictionary<Type, DependencyData> DependencyImplementations = new Dictionary<Type, DependencyData>(); + + public static T Get<T>(DependencyFetchTarget fetchTarget = DependencyFetchTarget.GlobalInstance) where T : class + { + if (!s_initialized) + Initialize(); + + Type targetType = typeof(T); + + if (!DependencyImplementations.ContainsKey(targetType)) + { + Type implementor = FindImplementor(targetType); + DependencyImplementations[targetType] = implementor != null ? new DependencyData { ImplementorType = implementor } : null; + } + + DependencyData dependencyImplementation = DependencyImplementations[targetType]; + if (dependencyImplementation == null) + return null; + + if (fetchTarget == DependencyFetchTarget.GlobalInstance) + { + if (dependencyImplementation.GlobalInstance == null) + { + dependencyImplementation.GlobalInstance = Activator.CreateInstance(dependencyImplementation.ImplementorType); + } + return (T)dependencyImplementation.GlobalInstance; + } + return (T)Activator.CreateInstance(dependencyImplementation.ImplementorType); + } + + public static void Register<T>() where T : class + { + Type type = typeof(T); + if (!DependencyTypes.Contains(type)) + DependencyTypes.Add(type); + } + + public static void Register<T, TImpl>() where T : class where TImpl : class, T + { + Type targetType = typeof(T); + Type implementorType = typeof(TImpl); + if (!DependencyTypes.Contains(targetType)) + DependencyTypes.Add(targetType); + + DependencyImplementations[targetType] = new DependencyData { ImplementorType = implementorType }; + } + + static Type FindImplementor(Type target) + { + return DependencyTypes.FirstOrDefault(t => target.IsAssignableFrom(t)); + } + + static void Initialize() + { + Assembly[] assemblies = Device.GetAssemblies(); + if (Registrar.ExtraAssemblies != null) + { + assemblies = assemblies.Union(Registrar.ExtraAssemblies).ToArray(); + } + + Type targetAttrType = typeof(DependencyAttribute); + + // Don't use LINQ for performance reasons + // Naive implementation can easily take over a second to run + foreach (Assembly assembly in assemblies) + { + Attribute[] attributes = assembly.GetCustomAttributes(targetAttrType).ToArray(); + if (attributes.Length == 0) + continue; + + foreach (DependencyAttribute attribute in attributes) + { + if (!DependencyTypes.Contains(attribute.Implementor)) + { + DependencyTypes.Add(attribute.Implementor); + } + } + } + + s_initialized = true; + } + + class DependencyData + { + public object GlobalInstance { get; set; } + + public Type ImplementorType { get; set; } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Device.cs b/Xamarin.Forms.Core/Device.cs new file mode 100644 index 00000000..db0a2747 --- /dev/null +++ b/Xamarin.Forms.Core/Device.cs @@ -0,0 +1,159 @@ +using System; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + public static class Device + { + internal static DeviceInfo info; + + static IPlatformServices s_platformServices; + + public static TargetIdiom Idiom { get; internal set; } + + public static TargetPlatform OS { get; internal set; } + + internal static DeviceInfo Info + { + get + { + if (info == null) + throw new InvalidOperationException("You MUST call Xamarin.Forms.Init(); prior to using it."); + return info; + } + set { info = value; } + } + + internal static bool IsInvokeRequired + { + get { return PlatformServices.IsInvokeRequired; } + } + + internal static IPlatformServices PlatformServices + { + get + { + if (s_platformServices == null) + throw new InvalidOperationException("You MUST call Xamarin.Forms.Init(); prior to using it."); + return s_platformServices; + } + set { s_platformServices = value; } + } + + public static void BeginInvokeOnMainThread(Action action) + { + PlatformServices.BeginInvokeOnMainThread(action); + } + + public static double GetNamedSize(NamedSize size, Element targetElement) + { + return GetNamedSize(size, targetElement.GetType()); + } + + public static double GetNamedSize(NamedSize size, Type targetElementType) + { + return GetNamedSize(size, targetElementType, false); + } + + public static void OnPlatform(Action iOS = null, Action Android = null, Action WinPhone = null, Action Default = null) + { + switch (OS) + { + case TargetPlatform.iOS: + if (iOS != null) + iOS(); + else if (Default != null) + Default(); + break; + case TargetPlatform.Android: + if (Android != null) + Android(); + else if (Default != null) + Default(); + break; + case TargetPlatform.Windows: + case TargetPlatform.WinPhone: + if (WinPhone != null) + WinPhone(); + else if (Default != null) + Default(); + break; + case TargetPlatform.Other: + if (Default != null) + Default(); + break; + } + } + + public static T OnPlatform<T>(T iOS, T Android, T WinPhone) + { + switch (OS) + { + case TargetPlatform.iOS: + return iOS; + case TargetPlatform.Android: + return Android; + case TargetPlatform.Windows: + case TargetPlatform.WinPhone: + return WinPhone; + } + + return iOS; + } + + public static void OpenUri(Uri uri) + { + PlatformServices.OpenUriAction(uri); + } + + public static void StartTimer(TimeSpan interval, Func<bool> callback) + { + PlatformServices.StartTimer(interval, callback); + } + + internal static Assembly[] GetAssemblies() + { + return PlatformServices.GetAssemblies(); + } + + internal static double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes) + { + return PlatformServices.GetNamedSize(size, targetElementType, useOldSizes); + } + + internal static Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken) + { + return PlatformServices.GetStreamAsync(uri, cancellationToken); + } + + public static class Styles + { + public static readonly string TitleStyleKey = "TitleStyle"; + + public static readonly string SubtitleStyleKey = "SubtitleStyle"; + + public static readonly string BodyStyleKey = "BodyStyle"; + + public static readonly string ListItemTextStyleKey = "ListItemTextStyle"; + + public static readonly string ListItemDetailTextStyleKey = "ListItemDetailTextStyle"; + + public static readonly string CaptionStyleKey = "CaptionStyle"; + + public static readonly Style TitleStyle = new Style(typeof(Label)) { BaseResourceKey = TitleStyleKey }; + + public static readonly Style SubtitleStyle = new Style(typeof(Label)) { BaseResourceKey = SubtitleStyleKey }; + + public static readonly Style BodyStyle = new Style(typeof(Label)) { BaseResourceKey = BodyStyleKey }; + + public static readonly Style ListItemTextStyle = new Style(typeof(Label)) { BaseResourceKey = ListItemTextStyleKey }; + + public static readonly Style ListItemDetailTextStyle = new Style(typeof(Label)) { BaseResourceKey = ListItemDetailTextStyleKey }; + + public static readonly Style CaptionStyle = new Style(typeof(Label)) { BaseResourceKey = CaptionStyleKey }; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DeviceInfo.cs b/Xamarin.Forms.Core/DeviceInfo.cs new file mode 100644 index 00000000..dc83075a --- /dev/null +++ b/Xamarin.Forms.Core/DeviceInfo.cs @@ -0,0 +1,51 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Xamarin.Forms +{ + internal abstract class DeviceInfo : INotifyPropertyChanged, IDisposable + { + DeviceOrientation _currentOrientation; + bool _disposed; + + public DeviceOrientation CurrentOrientation + { + get { return _currentOrientation; } + internal set + { + if (Equals(_currentOrientation, value)) + return; + _currentOrientation = value; + OnPropertyChanged(); + } + } + + public abstract Size PixelScreenSize { get; } + + public abstract Size ScaledScreenSize { get; } + + public abstract double ScalingFactor { get; } + + public void Dispose() + { + Dispose(true); + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + _disposed = true; + } + + protected virtual 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.Core/DeviceOrientation.cs b/Xamarin.Forms.Core/DeviceOrientation.cs new file mode 100644 index 00000000..53a03f2d --- /dev/null +++ b/Xamarin.Forms.Core/DeviceOrientation.cs @@ -0,0 +1,13 @@ +namespace Xamarin.Forms +{ + internal enum DeviceOrientation + { + Portrait, + Landscape, + PortraitUp, + PortraitDown, + LandscapeLeft, + LandscapeRight, + Other + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/DeviceOrientationExtensions.cs b/Xamarin.Forms.Core/DeviceOrientationExtensions.cs new file mode 100644 index 00000000..8dbaaa82 --- /dev/null +++ b/Xamarin.Forms.Core/DeviceOrientationExtensions.cs @@ -0,0 +1,15 @@ +namespace Xamarin.Forms +{ + internal static class DeviceOrientationExtensions + { + public static bool IsLandscape(this DeviceOrientation orientation) + { + return orientation == DeviceOrientation.Landscape || orientation == DeviceOrientation.LandscapeLeft || orientation == DeviceOrientation.LandscapeRight; + } + + public static bool IsPortrait(this DeviceOrientation orientation) + { + return orientation == DeviceOrientation.Portrait || orientation == DeviceOrientation.PortraitDown || orientation == DeviceOrientation.PortraitUp; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Easing.cs b/Xamarin.Forms.Core/Easing.cs new file mode 100644 index 00000000..8d64e255 --- /dev/null +++ b/Xamarin.Forms.Core/Easing.cs @@ -0,0 +1,98 @@ +// +// Tweener.cs +// +// Author: +// Jason Smith <jason.smith@xamarin.com> +// +// Copyright (c) 2012 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; + +namespace Xamarin.Forms +{ + public class Easing + { + public static readonly Easing Linear = new Easing(x => x); + + public static readonly Easing SinOut = new Easing(x => Math.Sin(x * Math.PI * 0.5f)); + public static readonly Easing SinIn = new Easing(x => 1.0f - Math.Cos(x * Math.PI * 0.5f)); + public static readonly Easing SinInOut = new Easing(x => -Math.Cos(Math.PI * x) / 2.0f + 0.5f); + + public static readonly Easing CubicIn = new Easing(x => x * x * x); + public static readonly Easing CubicOut = new Easing(x => Math.Pow(x - 1.0f, 3.0f) + 1.0f); + + public static readonly Easing CubicInOut = new Easing(x => x < 0.5f ? Math.Pow(x * 2.0f, 3.0f) / 2.0f : (Math.Pow((x - 1) * 2.0f, 3.0f) + 2.0f) / 2.0f); + + public static readonly Easing BounceOut; + public static readonly Easing BounceIn; + + public static readonly Easing SpringIn = new Easing(x => x * x * ((1.70158f + 1) * x - 1.70158f)); + public static readonly Easing SpringOut = new Easing(x => (x - 1) * (x - 1) * ((1.70158f + 1) * (x - 1) + 1.70158f) + 1); + + readonly Func<double, double> _easingFunc; + + static Easing() + { + BounceOut = new Easing(p => + { + if (p < 1 / 2.75f) + { + return 7.5625f * p * p; + } + if (p < 2 / 2.75f) + { + p -= 1.5f / 2.75f; + + return 7.5625f * p * p + .75f; + } + if (p < 2.5f / 2.75f) + { + p -= 2.25f / 2.75f; + + return 7.5625f * p * p + .9375f; + } + p -= 2.625f / 2.75f; + + return 7.5625f * p * p + .984375f; + }); + + BounceIn = new Easing(p => 1.0f - BounceOut.Ease(1 - p)); + } + + public Easing(Func<double, double> easingFunc) + { + if (easingFunc == null) + throw new ArgumentNullException("easingFunc"); + + _easingFunc = easingFunc; + } + + public double Ease(double v) + { + return _easingFunc(v); + } + + public static implicit operator Easing(Func<double, double> func) + { + return new Easing(func); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Editor.cs b/Xamarin.Forms.Core/Editor.cs new file mode 100644 index 00000000..949c0865 --- /dev/null +++ b/Xamarin.Forms.Core/Editor.cs @@ -0,0 +1,67 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_EditorRenderer))] + public class Editor : InputView, IFontElement + { + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Editor), null, BindingMode.TwoWay, propertyChanged: (bindable, oldValue, newValue) => + { + var editor = (Editor)bindable; + if (editor.TextChanged != null) + editor.TextChanged(editor, new TextChangedEventArgs((string)oldValue, (string)newValue)); + }); + + public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create("FontFamily", typeof(string), typeof(Editor), default(string)); + + public static readonly BindableProperty FontSizeProperty = BindableProperty.Create("FontSize", typeof(double), typeof(Editor), -1.0, + defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (Editor)bindable)); + + public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create("FontAttributes", typeof(FontAttributes), typeof(Editor), FontAttributes.None); + + public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(Editor), Color.Default); + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public Color TextColor + { + get { return (Color)GetValue(TextColorProperty); } + set { SetValue(TextColorProperty, value); } + } + + public FontAttributes FontAttributes + { + get { return (FontAttributes)GetValue(FontAttributesProperty); } + set { SetValue(FontAttributesProperty, value); } + } + + public string FontFamily + { + get { return (string)GetValue(FontFamilyProperty); } + set { SetValue(FontFamilyProperty, value); } + } + + [TypeConverter(typeof(FontSizeConverter))] + public double FontSize + { + get { return (double)GetValue(FontSizeProperty); } + set { SetValue(FontSizeProperty, value); } + } + + public event EventHandler Completed; + + public event EventHandler<TextChangedEventArgs> TextChanged; + + internal void SendCompleted() + { + EventHandler handler = Completed; + if (handler != null) + handler(this, EventArgs.Empty); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Effect.cs b/Xamarin.Forms.Core/Effect.cs new file mode 100644 index 00000000..9e269118 --- /dev/null +++ b/Xamarin.Forms.Core/Effect.cs @@ -0,0 +1,70 @@ +using System; +using System.ComponentModel; + +namespace Xamarin.Forms +{ + public abstract class Effect + { + internal Effect() + { + } + + public Element Element { get; internal set; } + + public bool IsAttached { get; private set; } + + public string ResolveId { get; internal set; } + + #region Statics + + public static Effect Resolve(string name) + { + Type effectType; + Effect result = null; + if (Registrar.Effects.TryGetValue(name, out effectType)) + { + result = (Effect)Activator.CreateInstance(effectType); + } + + if (result == null) + result = new NullEffect(); + result.ResolveId = name; + return result; + } + + #endregion + + // Received after Control/Container/Element made valid + protected abstract void OnAttached(); + + // Received after Control/Container made invalid + protected abstract void OnDetached(); + + internal virtual void ClearEffect() + { + if (IsAttached) + SendDetached(); + Element = null; + } + + internal virtual void SendAttached() + { + if (IsAttached) + return; + OnAttached(); + IsAttached = true; + } + + internal virtual void SendDetached() + { + if (!IsAttached) + return; + OnDetached(); + IsAttached = false; + } + + internal virtual void SendOnElementPropertyChanged(PropertyChangedEventArgs args) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Element.cs b/Xamarin.Forms.Core/Element.cs new file mode 100644 index 00000000..a85bb3fb --- /dev/null +++ b/Xamarin.Forms.Core/Element.cs @@ -0,0 +1,584 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Xml; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms +{ + public abstract class Element : BindableObject, IElement, INameScope, IElementController + { + internal static readonly ReadOnlyCollection<Element> EmptyChildren = new ReadOnlyCollection<Element>(new Element[0]); + + public static readonly BindableProperty ClassIdProperty = BindableProperty.Create("ClassId", typeof(string), typeof(View), null); + + string _automationId; + + List<Action<object, ResourcesChangedEventArgs>> _changeHandlers; + + List<KeyValuePair<string, BindableProperty>> _dynamicResources; + + IEffectControlProvider _effectControlProvider; + + TrackableCollection<Effect> _effects; + + Guid? _id; + + Element _parentOverride; + + IPlatform _platform; + + string _styleId; + + public string AutomationId + { + get { return _automationId; } + set + { + if (_automationId != null) + throw new InvalidOperationException("AutomationId may only be set one time"); + _automationId = value; + } + } + + public string ClassId + { + get { return (string)GetValue(ClassIdProperty); } + set { SetValue(ClassIdProperty, value); } + } + + public IList<Effect> Effects + { + get + { + if (_effects == null) + { + _effects = new TrackableCollection<Effect>(); + _effects.CollectionChanged += EffectsOnCollectionChanged; + _effects.Clearing += EffectsOnClearing; + } + return _effects; + } + } + + public Guid Id + { + get + { + if (!_id.HasValue) + _id = Guid.NewGuid(); + return _id.Value; + } + } + + [Obsolete("Use Parent")] + public VisualElement ParentView + { + get + { + Element parent = Parent; + while (parent != null) + { + var parentView = parent as VisualElement; + if (parentView != null) + return parentView; + parent = parent.RealParent; + } + return null; + } + } + + public string StyleId + { + get { return _styleId; } + set + { + if (_styleId == value) + return; + + OnPropertyChanging(); + _styleId = value; + OnPropertyChanged(); + } + } + + internal virtual ReadOnlyCollection<Element> LogicalChildren + { + get { return EmptyChildren; } + } + + internal bool Owned { get; set; } + + internal Element ParentOverride + { + get { return _parentOverride; } + set + { + if (_parentOverride == value) + return; + + bool emitChange = Parent != value; + + if (emitChange) + OnPropertyChanging(nameof(Parent)); + + _parentOverride = value; + + if (emitChange) + OnPropertyChanged(nameof(Parent)); + } + } + + internal IPlatform Platform + { + get + { + if (_platform == null && RealParent != null) + return RealParent.Platform; + return _platform; + } + set + { + if (_platform == value) + return; + _platform = value; + if (PlatformSet != null) + PlatformSet(this, EventArgs.Empty); + foreach (Element descendant in Descendants()) + { + descendant._platform = _platform; + if (descendant.PlatformSet != null) + descendant.PlatformSet(this, EventArgs.Empty); + } + } + } + + // you're not my real dad + internal Element RealParent { get; private set; } + + List<KeyValuePair<string, BindableProperty>> DynamicResources + { + get { return _dynamicResources ?? (_dynamicResources = new List<KeyValuePair<string, BindableProperty>>(4)); } + } + + void IElement.AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged) + { + _changeHandlers = _changeHandlers ?? new List<Action<object, ResourcesChangedEventArgs>>(2); + _changeHandlers.Add(onchanged); + } + + public Element Parent + { + get { return _parentOverride ?? RealParent; } + set + { + if (RealParent == value) + return; + + OnPropertyChanging(); + + if (RealParent != null) + ((IElement)RealParent).RemoveResourcesChangedListener(OnParentResourcesChanged); + RealParent = value; + if (RealParent != null) + { + OnParentResourcesChanged(RealParent.GetMergedResources()); + ((IElement)RealParent).AddResourcesChangedListener(OnParentResourcesChanged); + } + + object context = value != null ? value.BindingContext : null; + if (value != null) + { + value.SetChildInheritedBindingContext(this, context); + } + else + { + SetInheritedBindingContext(this, null); + } + + OnParentSet(); + + if (RealParent != null) + { + IPlatform platform = RealParent.Platform; + if (platform != null) + Platform = platform; + } + + OnPropertyChanged(); + } + } + + void IElement.RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged) + { + if (_changeHandlers == null) + return; + _changeHandlers.Remove(onchanged); + } + + IEffectControlProvider IElementController.EffectControlProvider + { + get { return _effectControlProvider; } + set + { + if (_effectControlProvider == value) + return; + if (_effectControlProvider != null && _effects != null) + { + foreach (Effect effect in _effects) + effect?.SendDetached(); + } + _effectControlProvider = value; + if (_effectControlProvider != null && _effects != null) + { + foreach (Effect effect in _effects) + { + if (effect != null) + AttachEffect(effect); + } + } + } + } + + void IElementController.SetValueFromRenderer(BindableProperty property, object value) + { + SetValueCore(property, value); + } + + void IElementController.SetValueFromRenderer(BindablePropertyKey property, object value) + { + SetValueCore(property, value); + } + + object INameScope.FindByName(string name) + { + INameScope namescope = GetNameScope(); + if (namescope == null) + throw new InvalidOperationException("this element is not in a namescope"); + return namescope.FindByName(name); + } + + void INameScope.RegisterName(string name, object scopedElement) + { + INameScope namescope = GetNameScope(); + if (namescope == null) + throw new InvalidOperationException("this element is not in a namescope"); + namescope.RegisterName(name, scopedElement); + } + + void INameScope.RegisterName(string name, object scopedElement, IXmlLineInfo xmlLineInfo) + { + INameScope namescope = GetNameScope(); + if (namescope == null) + throw new InvalidOperationException("this element is not in a namescope"); + namescope.RegisterName(name, scopedElement, xmlLineInfo); + } + + void INameScope.UnregisterName(string name) + { + INameScope namescope = GetNameScope(); + if (namescope == null) + throw new InvalidOperationException("this element is not in a namescope"); + namescope.UnregisterName(name); + } + + public event EventHandler<ElementEventArgs> ChildAdded; + + public event EventHandler<ElementEventArgs> ChildRemoved; + + public event EventHandler<ElementEventArgs> DescendantAdded; + + public event EventHandler<ElementEventArgs> DescendantRemoved; + + public new void RemoveDynamicResource(BindableProperty property) + { + base.RemoveDynamicResource(property); + } + + public new void SetDynamicResource(BindableProperty property, string key) + { + base.SetDynamicResource(property, key); + } + + protected override void OnBindingContextChanged() + { + var gotBindingContext = false; + object bc = null; + + for (var index = 0; index < LogicalChildren.Count; index++) + { + Element child = LogicalChildren[index]; + + if (!gotBindingContext) + { + bc = BindingContext; + gotBindingContext = true; + } + + SetChildInheritedBindingContext(child, bc); + } + + base.OnBindingContextChanged(); + } + + protected virtual void OnChildAdded(Element child) + { + child.Parent = this; + if (Platform != null) + child.Platform = Platform; + + child.ApplyBindings(); + + if (ChildAdded != null) + ChildAdded(this, new ElementEventArgs(child)); + + OnDescendantAdded(child); + foreach (Element element in child.Descendants()) + OnDescendantAdded(element); + } + + protected virtual void OnChildRemoved(Element child) + { + child.Parent = null; + + if (ChildRemoved != null) + ChildRemoved(child, new ElementEventArgs(child)); + + OnDescendantRemoved(child); + foreach (Element element in child.Descendants()) + OnDescendantRemoved(element); + } + + protected virtual void OnParentSet() + { + ParentSet?.Invoke(this, EventArgs.Empty); + } + + protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + base.OnPropertyChanged(propertyName); + + if (_effects == null || _effects.Count == 0) + return; + + var args = new PropertyChangedEventArgs(propertyName); + foreach (Effect effect in _effects) + { + effect?.SendOnElementPropertyChanged(args); + } + } + + internal IEnumerable<Element> Descendants() + { + var queue = new Queue<Element>(16); + queue.Enqueue(this); + + while (queue.Count > 0) + { + ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildren; + for (var i = 0; i < children.Count; i++) + { + Element child = children[i]; + yield return child; + queue.Enqueue(child); + } + } + } + + internal void OnParentResourcesChanged(object sender, ResourcesChangedEventArgs e) + { + OnParentResourcesChanged(e.Values); + } + + internal virtual void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values) + { + OnResourcesChanged(values); + } + + internal override void OnRemoveDynamicResource(BindableProperty property) + { + DynamicResources.RemoveAll(kvp => kvp.Value == property); + if (DynamicResources.Count == 0) + _dynamicResources = null; + base.OnRemoveDynamicResource(property); + } + + internal void OnResourcesChanged(object sender, ResourcesChangedEventArgs e) + { + OnResourcesChanged(e.Values); + } + + internal void OnResourcesChanged(IEnumerable<KeyValuePair<string, object>> values) + { + if (values == null) + return; + if (_changeHandlers != null) + foreach (Action<object, ResourcesChangedEventArgs> handler in _changeHandlers) + handler(this, new ResourcesChangedEventArgs(values)); + if (_dynamicResources == null) + return; + foreach (KeyValuePair<string, object> value in values) + { + List<BindableProperty> changedResources = null; + foreach (KeyValuePair<string, BindableProperty> dynR in DynamicResources) + { + if (dynR.Key != value.Key) + continue; + changedResources = changedResources ?? new List<BindableProperty>(); + changedResources.Add(dynR.Value); + } + if (changedResources == null) + continue; + foreach (BindableProperty changedResource in changedResources) + OnResourceChanged(changedResource, value.Value); + } + } + + internal override void OnSetDynamicResource(BindableProperty property, string key) + { + base.OnSetDynamicResource(property, key); + DynamicResources.Add(new KeyValuePair<string, BindableProperty>(key, property)); + object value; + if (this.TryGetResource(key, out value)) + OnResourceChanged(property, value); + } + + internal event EventHandler ParentSet; + + internal event EventHandler PlatformSet; + + internal virtual void SetChildInheritedBindingContext(Element child, object context) + { + SetInheritedBindingContext(child, context); + } + + internal IEnumerable<Element> VisibleDescendants() + { + var queue = new Queue<Element>(16); + queue.Enqueue(this); + + while (queue.Count > 0) + { + ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildren; + for (var i = 0; i < children.Count; i++) + { + var child = children[i] as VisualElement; + if (child == null || !child.IsVisible) + continue; + yield return child; + queue.Enqueue(child); + } + } + } + + void AttachEffect(Effect effect) + { + if (_effectControlProvider == null) + return; + if (effect.IsAttached) + throw new InvalidOperationException("Cannot attach Effect to multiple sources"); + + Effect effectToRegister = effect; + if (effect is RoutingEffect) + effectToRegister = ((RoutingEffect)effect).Inner; + _effectControlProvider.RegisterEffect(effectToRegister); + effectToRegister.Element = this; + effect.SendAttached(); + } + + void EffectsOnClearing(object sender, EventArgs eventArgs) + { + foreach (Effect effect in _effects) + { + effect.ClearEffect(); + } + } + + void EffectsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (Effect effect in e.NewItems) + { + AttachEffect(effect); + } + break; + case NotifyCollectionChangedAction.Move: + break; + case NotifyCollectionChangedAction.Remove: + foreach (Effect effect in e.OldItems) + { + effect.ClearEffect(); + } + break; + case NotifyCollectionChangedAction.Replace: + foreach (Effect effect in e.NewItems) + { + AttachEffect(effect); + } + foreach (Effect effect in e.OldItems) + { + effect.ClearEffect(); + } + break; + case NotifyCollectionChangedAction.Reset: + if (e.NewItems != null) + { + foreach (Effect effect in e.NewItems) + { + AttachEffect(effect); + } + } + if (e.OldItems != null) + { + foreach (Effect effect in e.OldItems) + { + effect.ClearEffect(); + } + } + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + INameScope GetNameScope() + { + INameScope namescope = NameScope.GetNameScope(this); + Element p = RealParent; + while (namescope == null && p != null) + { + namescope = NameScope.GetNameScope(p); + p = p.RealParent; + } + return namescope; + } + + void OnDescendantAdded(Element child) + { + if (DescendantAdded != null) + DescendantAdded(this, new ElementEventArgs(child)); + + if (RealParent != null) + RealParent.OnDescendantAdded(child); + } + + void OnDescendantRemoved(Element child) + { + if (DescendantRemoved != null) + DescendantRemoved(this, new ElementEventArgs(child)); + + if (RealParent != null) + RealParent.OnDescendantRemoved(child); + } + + void OnResourceChanged(BindableProperty property, object value) + { + SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearTwoWayBindings); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ElementCollection.cs b/Xamarin.Forms.Core/ElementCollection.cs new file mode 100644 index 00000000..37520ab6 --- /dev/null +++ b/Xamarin.Forms.Core/ElementCollection.cs @@ -0,0 +1,11 @@ +using System.Collections.ObjectModel; + +namespace Xamarin.Forms +{ + internal class ElementCollection<T> : ObservableWrapper<Element, T> where T : Element + { + public ElementCollection(ObservableCollection<Element> list) : base(list) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ElementEventArgs.cs b/Xamarin.Forms.Core/ElementEventArgs.cs new file mode 100644 index 00000000..34af4e0e --- /dev/null +++ b/Xamarin.Forms.Core/ElementEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + public class ElementEventArgs : EventArgs + { + public ElementEventArgs(Element element) + { + if (element == null) + throw new ArgumentNullException("element"); + + Element = element; + } + + public Element Element { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ElementTemplate.cs b/Xamarin.Forms.Core/ElementTemplate.cs new file mode 100644 index 00000000..016dee7e --- /dev/null +++ b/Xamarin.Forms.Core/ElementTemplate.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms +{ +#pragma warning disable 612 + public class ElementTemplate : IElement, IDataTemplate +#pragma warning restore 612 + { + List<Action<object, ResourcesChangedEventArgs>> _changeHandlers; + Element _parent; + + internal ElementTemplate() + { + } + + internal ElementTemplate(Type type) : this() + { + if (type == null) + throw new ArgumentNullException("type"); + + LoadTemplate = () => Activator.CreateInstance(type); + } + + internal ElementTemplate(Func<object> loadTemplate) : this() + { + if (loadTemplate == null) + throw new ArgumentNullException("loadTemplate"); + + LoadTemplate = loadTemplate; + } + + Func<object> LoadTemplate { get; set; } +#pragma warning disable 0612 + Func<object> IDataTemplate.LoadTemplate + { +#pragma warning restore 0612 + get { return LoadTemplate; } + set { LoadTemplate = value; } + } + + void IElement.AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged) + { + _changeHandlers = _changeHandlers ?? new List<Action<object, ResourcesChangedEventArgs>>(1); + _changeHandlers.Add(onchanged); + } + + Element IElement.Parent + { + get { return _parent; } + set + { + if (_parent == value) + return; + if (_parent != null) + ((IElement)_parent).RemoveResourcesChangedListener(OnResourcesChanged); + _parent = value; + if (_parent != null) + ((IElement)_parent).AddResourcesChangedListener(OnResourcesChanged); + } + } + + void IElement.RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged) + { + if (_changeHandlers == null) + return; + _changeHandlers.Remove(onchanged); + } + + public object CreateContent() + { + if (LoadTemplate == null) + throw new InvalidOperationException("LoadTemplate should not be null"); + if (this is DataTemplateSelector) + throw new InvalidOperationException("Cannot call CreateContent directly on a DataTemplateSelector"); + + object item = LoadTemplate(); + SetupContent(item); + + return item; + } + + internal virtual void SetupContent(object item) + { + } + + void OnResourcesChanged(object sender, ResourcesChangedEventArgs e) + { + if (_changeHandlers == null) + return; + foreach (Action<object, ResourcesChangedEventArgs> handler in _changeHandlers) + handler(this, e); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/EmailKeyboard.cs b/Xamarin.Forms.Core/EmailKeyboard.cs new file mode 100644 index 00000000..1911fd3c --- /dev/null +++ b/Xamarin.Forms.Core/EmailKeyboard.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + internal sealed class EmailKeyboard : Keyboard + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Entry.cs b/Xamarin.Forms.Core/Entry.cs new file mode 100644 index 00000000..ef10e963 --- /dev/null +++ b/Xamarin.Forms.Core/Entry.cs @@ -0,0 +1,99 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_EntryRenderer))] + public class Entry : InputView, IFontElement + { + public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(Entry), default(string)); + + public static readonly BindableProperty IsPasswordProperty = BindableProperty.Create("IsPassword", typeof(bool), typeof(Entry), default(bool)); + + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Entry), null, BindingMode.TwoWay, propertyChanged: OnTextChanged); + + public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(Entry), Color.Default); + + public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create("HorizontalTextAlignment", typeof(TextAlignment), typeof(Entry), TextAlignment.Start); + + public static readonly BindableProperty PlaceholderColorProperty = BindableProperty.Create("PlaceholderColor", typeof(Color), typeof(Entry), Color.Default); + + public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create("FontFamily", typeof(string), typeof(Entry), default(string)); + + public static readonly BindableProperty FontSizeProperty = BindableProperty.Create("FontSize", typeof(double), typeof(Entry), -1.0, + defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (Entry)bindable)); + + public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create("FontAttributes", typeof(FontAttributes), typeof(Entry), FontAttributes.None); + + public TextAlignment HorizontalTextAlignment + { + get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); } + set { SetValue(HorizontalTextAlignmentProperty, value); } + } + + public bool IsPassword + { + get { return (bool)GetValue(IsPasswordProperty); } + set { SetValue(IsPasswordProperty, value); } + } + + public string Placeholder + { + get { return (string)GetValue(PlaceholderProperty); } + set { SetValue(PlaceholderProperty, value); } + } + + public Color PlaceholderColor + { + get { return (Color)GetValue(PlaceholderColorProperty); } + set { SetValue(PlaceholderColorProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public Color TextColor + { + get { return (Color)GetValue(TextColorProperty); } + set { SetValue(TextColorProperty, value); } + } + + public FontAttributes FontAttributes + { + get { return (FontAttributes)GetValue(FontAttributesProperty); } + set { SetValue(FontAttributesProperty, value); } + } + + public string FontFamily + { + get { return (string)GetValue(FontFamilyProperty); } + set { SetValue(FontFamilyProperty, value); } + } + + [TypeConverter(typeof(FontSizeConverter))] + public double FontSize + { + get { return (double)GetValue(FontSizeProperty); } + set { SetValue(FontSizeProperty, value); } + } + + public event EventHandler Completed; + + public event EventHandler<TextChangedEventArgs> TextChanged; + + internal void SendCompleted() + { + Completed?.Invoke(this, EventArgs.Empty); + } + + static void OnTextChanged(BindableObject bindable, object oldValue, object newValue) + { + var entry = (Entry)bindable; + + entry.TextChanged?.Invoke(entry, new TextChangedEventArgs((string)oldValue, (string)newValue)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/EnumerableExtensions.cs b/Xamarin.Forms.Core/EnumerableExtensions.cs new file mode 100644 index 00000000..066e7e91 --- /dev/null +++ b/Xamarin.Forms.Core/EnumerableExtensions.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + internal static class EnumerableExtensions + { + public static IEnumerable<T> GetGesturesFor<T>(this IEnumerable<IGestureRecognizer> gestures, Func<T, bool> predicate = null) where T : GestureRecognizer + { + if (gestures == null) + yield break; + + if (predicate == null) + predicate = x => true; + + foreach (IGestureRecognizer item in gestures) + { + var gesture = item as T; + if (gesture != null && predicate(gesture)) + { + yield return gesture; + } + } + } + + internal static IEnumerable<T> Append<T>(this IEnumerable<T> enumerable, T item) + { + foreach (T x in enumerable) + yield return x; + + yield return item; + } + + internal static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action) + { + foreach (T item in enumeration) + { + action(item); + } + } + + internal static int IndexOf<T>(this IEnumerable<T> enumerable, T item) + { + if (enumerable == null) + throw new ArgumentNullException("enumerable"); + + var i = 0; + foreach (T element in enumerable) + { + if (Equals(element, item)) + return i; + + i++; + } + + return -1; + } + + internal static int IndexOf<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate) + { + var i = 0; + foreach (T element in enumerable) + { + if (predicate(element)) + return i; + + i++; + } + + return -1; + } + + internal static IEnumerable<T> Prepend<T>(this IEnumerable<T> enumerable, T item) + { + yield return item; + + foreach (T x in enumerable) + yield return x; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/EventArg.cs b/Xamarin.Forms.Core/EventArg.cs new file mode 100644 index 00000000..9b9ea0a1 --- /dev/null +++ b/Xamarin.Forms.Core/EventArg.cs @@ -0,0 +1,18 @@ +using System; + +namespace Xamarin.Forms +{ + internal class EventArg<T> : EventArgs + { + // Property variable + + // Constructor + public EventArg(T data) + { + Data = data; + } + + // Property for EventArgs argument + public T Data { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ExportEffectAttribute.cs b/Xamarin.Forms.Core/ExportEffectAttribute.cs new file mode 100644 index 00000000..35869570 --- /dev/null +++ b/Xamarin.Forms.Core/ExportEffectAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class ExportEffectAttribute : Attribute + { + public ExportEffectAttribute(Type effectType, string uniqueName) + { + if (uniqueName.Contains(".")) + throw new ArgumentException("uniqueName must not contain a ."); + Type = effectType; + Id = uniqueName; + } + + internal string Id { get; private set; } + + internal Type Type { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ExpressionSearch.cs b/Xamarin.Forms.Core/ExpressionSearch.cs new file mode 100644 index 00000000..fbbe80a8 --- /dev/null +++ b/Xamarin.Forms.Core/ExpressionSearch.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms +{ + internal abstract class ExpressionSearch + { + internal static IExpressionSearch Default { get; set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/FileAccess.cs b/Xamarin.Forms.Core/FileAccess.cs new file mode 100644 index 00000000..3636b733 --- /dev/null +++ b/Xamarin.Forms.Core/FileAccess.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + internal enum FileAccess + { + Read = 0x00000001, + Write = 0x00000002, + ReadWrite = Read | Write + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/FileImageSource.cs b/Xamarin.Forms.Core/FileImageSource.cs new file mode 100644 index 00000000..9e1d1e73 --- /dev/null +++ b/Xamarin.Forms.Core/FileImageSource.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + [TypeConverter(typeof(FileImageSourceConverter))] + public sealed class FileImageSource : ImageSource + { + public static readonly BindableProperty FileProperty = BindableProperty.Create("File", typeof(string), typeof(FileImageSource), default(string)); + + public string File + { + get { return (string)GetValue(FileProperty); } + set { SetValue(FileProperty, value); } + } + + public override Task<bool> Cancel() + { + return Task.FromResult(false); + } + + public static implicit operator FileImageSource(string file) + { + return (FileImageSource)FromFile(file); + } + + public static implicit operator string(FileImageSource file) + { + return file != null ? file.File : null; + } + + protected override void OnPropertyChanged(string propertyName = null) + { + if (propertyName == FileProperty.PropertyName) + OnSourceChanged(); + base.OnPropertyChanged(propertyName); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/FileImageSourceConverter.cs b/Xamarin.Forms.Core/FileImageSourceConverter.cs new file mode 100644 index 00000000..25a0e22e --- /dev/null +++ b/Xamarin.Forms.Core/FileImageSourceConverter.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Forms +{ + public sealed class FileImageSourceConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + return (FileImageSource)ImageSource.FromFile(value); + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(FileImageSource))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/FileMode.cs b/Xamarin.Forms.Core/FileMode.cs new file mode 100644 index 00000000..31369872 --- /dev/null +++ b/Xamarin.Forms.Core/FileMode.cs @@ -0,0 +1,12 @@ +namespace Xamarin.Forms +{ + internal enum FileMode + { + CreateNew = 1, + Create = 2, + Open = 3, + OpenOrCreate = 4, + Truncate = 5, + Append = 6 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/FileShare.cs b/Xamarin.Forms.Core/FileShare.cs new file mode 100644 index 00000000..bf9fc717 --- /dev/null +++ b/Xamarin.Forms.Core/FileShare.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Forms +{ + [Flags] + internal enum FileShare + { + None = 0, + Read = 1, + Write = 2, + ReadWrite = 3, + Delete = 4, + Inheritable = 16 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/FocusEventArgs.cs b/Xamarin.Forms.Core/FocusEventArgs.cs new file mode 100644 index 00000000..304fd823 --- /dev/null +++ b/Xamarin.Forms.Core/FocusEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Xamarin.Forms +{ + public class FocusEventArgs : EventArgs + { + public FocusEventArgs(VisualElement visualElement, bool isFocused) + { + if (visualElement == null) + throw new ArgumentNullException("visualElement"); + + VisualElement = visualElement; + IsFocused = isFocused; + } + + public bool IsFocused { get; private set; } + + public VisualElement VisualElement { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Font.cs b/Xamarin.Forms.Core/Font.cs new file mode 100644 index 00000000..bb6da4f4 --- /dev/null +++ b/Xamarin.Forms.Core/Font.cs @@ -0,0 +1,145 @@ +using System; + +namespace Xamarin.Forms +{ + [TypeConverter(typeof(FontTypeConverter))] + public struct Font + { + public string FontFamily { get; private set; } + + public double FontSize { get; private set; } + + public NamedSize NamedSize { get; private set; } + + public FontAttributes FontAttributes { get; private set; } + + public bool IsDefault + { + get { return FontFamily == null && FontSize == 0 && NamedSize == NamedSize.Default && FontAttributes == FontAttributes.None; } + } + + public bool UseNamedSize + { + get { return FontSize <= 0; } + } + + public static Font Default + { + get { return default(Font); } + } + + public Font WithSize(double size) + { + return new Font { FontFamily = FontFamily, FontSize = size, NamedSize = 0, FontAttributes = FontAttributes }; + } + + public Font WithSize(NamedSize size) + { + if (size <= 0) + throw new ArgumentOutOfRangeException("size"); + + return new Font { FontFamily = FontFamily, FontSize = 0, NamedSize = size, FontAttributes = FontAttributes }; + } + + public Font WithAttributes(FontAttributes fontAttributes) + { + return new Font { FontFamily = FontFamily, FontSize = FontSize, NamedSize = NamedSize, FontAttributes = fontAttributes }; + } + + public static Font OfSize(string name, double size) + { + var result = new Font { FontFamily = name, FontSize = size }; + return result; + } + + public static Font OfSize(string name, NamedSize size) + { + var result = new Font { FontFamily = name, NamedSize = size }; + return result; + } + + public static Font SystemFontOfSize(double size) + { + var result = new Font { FontSize = size }; + return result; + } + + public static Font SystemFontOfSize(NamedSize size) + { + var result = new Font { NamedSize = size }; + return result; + } + + public static Font SystemFontOfSize(double size, FontAttributes attributes) + { + var result = new Font { FontSize = size, FontAttributes = attributes }; + return result; + } + + public static Font SystemFontOfSize(NamedSize size, FontAttributes attributes) + { + var result = new Font { NamedSize = size, FontAttributes = attributes }; + return result; + } + + [Obsolete("BoldSystemFontOfSize is obsolete, please use SystemFontOfSize (double, FontAttributes)")] + public static Font BoldSystemFontOfSize(double size) + { + var result = new Font { FontSize = size, FontAttributes = FontAttributes.Bold }; + return result; + } + + [Obsolete("BoldSystemFontOfSize is obsolete, please use SystemFontOfSize (NamedSize, FontAttributes)")] + public static Font BoldSystemFontOfSize(NamedSize size) + { + var result = new Font { NamedSize = size, FontAttributes = FontAttributes.Bold }; + return result; + } + + bool Equals(Font other) + { + return string.Equals(FontFamily, other.FontFamily) && FontSize.Equals(other.FontSize) && NamedSize == other.NamedSize && FontAttributes == other.FontAttributes; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (obj.GetType() != GetType()) + { + return false; + } + return Equals((Font)obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = FontFamily != null ? FontFamily.GetHashCode() : 0; + hashCode = (hashCode * 397) ^ FontSize.GetHashCode(); + hashCode = (hashCode * 397) ^ NamedSize.GetHashCode(); + hashCode = (hashCode * 397) ^ FontAttributes.GetHashCode(); + + return hashCode; + } + } + + public static bool operator ==(Font left, Font right) + { + return left.Equals(right); + } + + public static bool operator !=(Font left, Font right) + { + return !left.Equals(right); + } + + public override string ToString() + { + return string.Format("FontFamily: {0}, FontSize: {1}, NamedSize: {2}, FontAttributes: {3}", FontFamily, FontSize, NamedSize, FontAttributes); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/FontAttributes.cs b/Xamarin.Forms.Core/FontAttributes.cs new file mode 100644 index 00000000..872629c5 --- /dev/null +++ b/Xamarin.Forms.Core/FontAttributes.cs @@ -0,0 +1,12 @@ +using System; + +namespace Xamarin.Forms +{ + [Flags] + public enum FontAttributes + { + None = 0, + Bold = 1 << 0, + Italic = 1 << 1 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/FontSizeConverter.cs b/Xamarin.Forms.Core/FontSizeConverter.cs new file mode 100644 index 00000000..0d0a8865 --- /dev/null +++ b/Xamarin.Forms.Core/FontSizeConverter.cs @@ -0,0 +1,48 @@ +using System; +using System.Globalization; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + public class FontSizeConverter : TypeConverter, IExtendedTypeConverter + { + [Obsolete("use ConvertFromInvariantString (string, IServiceProvider)")] + object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider) + { + return ((IExtendedTypeConverter)this).ConvertFromInvariantString(value as string, serviceProvider); + } + + object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider) + { + if (value != null) + { + double size; + if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out size)) + return size; + NamedSize namedSize; + if (Enum.TryParse(value, out namedSize)) + { + Type type; + var valueTargetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; + type = valueTargetProvider != null ? valueTargetProvider.TargetObject.GetType() : typeof(Label); + return Device.GetNamedSize(namedSize, type, false); + } + } + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(double))); + } + + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + double size; + if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out size)) + return size; + NamedSize namedSize; + if (Enum.TryParse(value, out namedSize)) + return Device.GetNamedSize(namedSize, typeof(Label), false); + } + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(double))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/FontTypeConverter.cs b/Xamarin.Forms.Core/FontTypeConverter.cs new file mode 100644 index 00000000..464cf4ab --- /dev/null +++ b/Xamarin.Forms.Core/FontTypeConverter.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Xamarin.Forms +{ + public sealed class FontTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + // string should be formatted as "[name],[attributes],[size]" there may be multiple attributes, e.g. "Georgia, Bold, Italic, 42" + if (value != null) + { + // trim because mono implements Enum.Parse incorrectly and fails to trim correctly. + List<string> parts = value.Split(',').Select(s => s.Trim()).ToList(); + + string name = null; + var bold = false; + var italic = false; + double size = -1; + NamedSize namedSize = 0; + + // check if last is a size + string last = parts.Last(); + + double trySize; + NamedSize tryNamedSize; + if (double.TryParse(last, NumberStyles.Number, CultureInfo.InvariantCulture, out trySize)) + { + size = trySize; + parts.RemoveAt(parts.Count - 1); + } + else if (Enum.TryParse(last, out tryNamedSize)) + { + namedSize = tryNamedSize; + parts.RemoveAt(parts.Count - 1); + } + + // check if first is a name + foreach (string part in parts) + { + FontAttributes tryAttibute; + if (Enum.TryParse(part, out tryAttibute)) + { + // they did not provide a font name + if (tryAttibute == FontAttributes.Bold) + bold = true; + else if (tryAttibute == FontAttributes.Italic) + italic = true; + } + else + { + // they may have provided a font name + if (name != null) + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Font))); + + name = part; + } + } + + FontAttributes attributes = 0; + if (bold) + attributes = attributes | FontAttributes.Bold; + if (italic) + attributes = attributes | FontAttributes.Italic; + if (size == -1 && namedSize == 0) + namedSize = NamedSize.Medium; + + if (name != null) + { + if (size == -1) + { + return Font.OfSize(name, namedSize).WithAttributes(attributes); + } + return Font.OfSize(name, size).WithAttributes(attributes); + } + if (size == -1) + { + return Font.SystemFontOfSize(namedSize, attributes); + } + return Font.SystemFontOfSize(size, attributes); + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Font))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/FormattedString.cs b/Xamarin.Forms.Core/FormattedString.cs new file mode 100644 index 00000000..14839ab3 --- /dev/null +++ b/Xamarin.Forms.Core/FormattedString.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Xamarin.Forms +{ + [ContentProperty("Spans")] + public class FormattedString : INotifyPropertyChanged + { + readonly SpanCollection _spans = new SpanCollection(); + + public FormattedString() + { + _spans.CollectionChanged += OnCollectionChanged; + } + + public IList<Span> Spans + { + get { return _spans; } + } + + public event PropertyChangedEventHandler PropertyChanged; + + public static explicit operator string(FormattedString formatted) + { + return formatted.ToString(); + } + + public static implicit operator FormattedString(string text) + { + return new FormattedString { Spans = { new Span { Text = text } } }; + } + + public override string ToString() + { + return string.Concat(Spans.Select(span => span.Text)); + } + + void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + { + foreach (object item in e.OldItems) + { + var bo = item as Span; + if (bo != null) + bo.PropertyChanged -= OnItemPropertyChanged; + } + } + + if (e.NewItems != null) + { + foreach (object item in e.NewItems) + { + var bo = item as Span; + if (bo != null) + bo.PropertyChanged += OnItemPropertyChanged; + } + } + + OnPropertyChanged("Spans"); + } + + void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) + { + OnPropertyChanged("Spans"); + } + + void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChangedEventHandler handler = PropertyChanged; + if (handler != null) + handler(this, new PropertyChangedEventArgs(propertyName)); + } + + class SpanCollection : ObservableCollection<Span> + { + protected override void InsertItem(int index, Span item) + { + if (item == null) + throw new ArgumentNullException("item"); + + base.InsertItem(index, item); + } + + protected override void SetItem(int index, Span item) + { + if (item == null) + throw new ArgumentNullException("item"); + + base.SetItem(index, item); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Frame.cs b/Xamarin.Forms.Core/Frame.cs new file mode 100644 index 00000000..01f409e4 --- /dev/null +++ b/Xamarin.Forms.Core/Frame.cs @@ -0,0 +1,30 @@ +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [ContentProperty("Content")] + [RenderWith(typeof(_FrameRenderer))] + public class Frame : ContentView + { + public static readonly BindableProperty OutlineColorProperty = BindableProperty.Create("OutlineColor", typeof(Color), typeof(Frame), Color.Default); + + public static readonly BindableProperty HasShadowProperty = BindableProperty.Create("HasShadow", typeof(bool), typeof(Frame), true); + + public Frame() + { + Padding = new Size(20, 20); + } + + public bool HasShadow + { + get { return (bool)GetValue(HasShadowProperty); } + set { SetValue(HasShadowProperty, value); } + } + + public Color OutlineColor + { + get { return (Color)GetValue(OutlineColorProperty); } + set { SetValue(OutlineColorProperty, value); } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/GestureRecognizer.cs b/Xamarin.Forms.Core/GestureRecognizer.cs new file mode 100644 index 00000000..f68c996d --- /dev/null +++ b/Xamarin.Forms.Core/GestureRecognizer.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public class GestureRecognizer : Element, IGestureRecognizer + { + internal GestureRecognizer() + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/GestureState.cs b/Xamarin.Forms.Core/GestureState.cs new file mode 100644 index 00000000..a6c110f2 --- /dev/null +++ b/Xamarin.Forms.Core/GestureState.cs @@ -0,0 +1,12 @@ +namespace Xamarin.Forms +{ + public enum GestureState + { + Began, + Update, + Ended, + Failed, + Cancelled, + Possible + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/GestureStatus.cs b/Xamarin.Forms.Core/GestureStatus.cs new file mode 100644 index 00000000..22f24460 --- /dev/null +++ b/Xamarin.Forms.Core/GestureStatus.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public enum GestureStatus + { + Started = 0, + Running = 1, + Completed = 2, + Canceled = 3 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Grid.cs b/Xamarin.Forms.Core/Grid.cs new file mode 100644 index 00000000..442146c2 --- /dev/null +++ b/Xamarin.Forms.Core/Grid.cs @@ -0,0 +1,359 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; + +namespace Xamarin.Forms +{ + public partial class Grid : Layout<View> + { + public static readonly BindableProperty RowProperty = BindableProperty.CreateAttached("Row", typeof(int), typeof(Grid), default(int), validateValue: (bindable, value) => (int)value >= 0); + + public static readonly BindableProperty RowSpanProperty = BindableProperty.CreateAttached("RowSpan", typeof(int), typeof(Grid), 1, validateValue: (bindable, value) => (int)value >= 1); + + public static readonly BindableProperty ColumnProperty = BindableProperty.CreateAttached("Column", typeof(int), typeof(Grid), default(int), validateValue: (bindable, value) => (int)value >= 0); + + public static readonly BindableProperty ColumnSpanProperty = BindableProperty.CreateAttached("ColumnSpan", typeof(int), typeof(Grid), 1, validateValue: (bindable, value) => (int)value >= 1); + + public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create("RowSpacing", typeof(double), typeof(Grid), 6d, + propertyChanged: (bindable, oldValue, newValue) => ((Grid)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged)); + + public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create("ColumnSpacing", typeof(double), typeof(Grid), 6d, + propertyChanged: (bindable, oldValue, newValue) => ((Grid)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged)); + + public static readonly BindableProperty ColumnDefinitionsProperty = BindableProperty.Create("ColumnDefinitions", typeof(ColumnDefinitionCollection), typeof(Grid), null, + validateValue: (bindable, value) => value != null, propertyChanged: (bindable, oldvalue, newvalue) => + { + if (oldvalue != null) + ((ColumnDefinitionCollection)oldvalue).ItemSizeChanged -= ((Grid)bindable).OnDefinitionChanged; + if (newvalue != null) + ((ColumnDefinitionCollection)newvalue).ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged; + }, defaultValueCreator: bindable => + { + var colDef = new ColumnDefinitionCollection(); + colDef.ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged; + return colDef; + }); + + public static readonly BindableProperty RowDefinitionsProperty = BindableProperty.Create("RowDefinitions", typeof(RowDefinitionCollection), typeof(Grid), null, + validateValue: (bindable, value) => value != null, propertyChanged: (bindable, oldvalue, newvalue) => + { + if (oldvalue != null) + ((RowDefinitionCollection)oldvalue).ItemSizeChanged -= ((Grid)bindable).OnDefinitionChanged; + if (newvalue != null) + ((RowDefinitionCollection)newvalue).ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged; + }, defaultValueCreator: bindable => + { + var rowDef = new RowDefinitionCollection(); + rowDef.ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged; + return rowDef; + }); + + readonly GridElementCollection _children; + + public Grid() + { + _children = new GridElementCollection(InternalChildren, this) { Parent = this }; + } + + public new IGridList<View> Children + { + get { return _children; } + } + + public ColumnDefinitionCollection ColumnDefinitions + { + get { return (ColumnDefinitionCollection)GetValue(ColumnDefinitionsProperty); } + set { SetValue(ColumnDefinitionsProperty, value); } + } + + public double ColumnSpacing + { + get { return (double)GetValue(ColumnSpacingProperty); } + set { SetValue(ColumnSpacingProperty, value); } + } + + public RowDefinitionCollection RowDefinitions + { + get { return (RowDefinitionCollection)GetValue(RowDefinitionsProperty); } + set { SetValue(RowDefinitionsProperty, value); } + } + + public double RowSpacing + { + get { return (double)GetValue(RowSpacingProperty); } + set { SetValue(RowSpacingProperty, value); } + } + + public static int GetColumn(BindableObject bindable) + { + return (int)bindable.GetValue(ColumnProperty); + } + + public static int GetColumnSpan(BindableObject bindable) + { + return (int)bindable.GetValue(ColumnSpanProperty); + } + + public static int GetRow(BindableObject bindable) + { + return (int)bindable.GetValue(RowProperty); + } + + public static int GetRowSpan(BindableObject bindable) + { + return (int)bindable.GetValue(RowSpanProperty); + } + + public static void SetColumn(BindableObject bindable, int value) + { + bindable.SetValue(ColumnProperty, value); + } + + public static void SetColumnSpan(BindableObject bindable, int value) + { + bindable.SetValue(ColumnSpanProperty, value); + } + + public static void SetRow(BindableObject bindable, int value) + { + bindable.SetValue(RowProperty, value); + } + + public static void SetRowSpan(BindableObject bindable, int value) + { + bindable.SetValue(RowSpanProperty, value); + } + + protected override void OnAdded(View view) + { + base.OnAdded(view); + view.PropertyChanged += OnItemPropertyChanged; + } + + protected override void OnBindingContextChanged() + { + UpdateInheritedBindingContexts(); + base.OnBindingContextChanged(); + } + + protected override void OnRemoved(View view) + { + base.OnRemoved(view); + view.PropertyChanged -= OnItemPropertyChanged; + } + + internal override void ComputeConstraintForView(View view) + { + LayoutOptions vOptions = view.VerticalOptions; + LayoutOptions hOptions = view.HorizontalOptions; + + var result = LayoutConstraint.None; + + if (_rows == null || _columns == null) + EnsureRowsColumnsInitialized(); + + if (vOptions.Alignment == LayoutAlignment.Fill) + { + int row = GetRow(view); + int rowSpan = GetRowSpan(view); + List<RowDefinition> rowDefinitions = _rows; + + var canFix = true; + + for (int i = row; i < row + rowSpan && i < rowDefinitions.Count; i++) + { + GridLength height = rowDefinitions[i].Height; + if (height.IsAuto) + { + canFix = false; + break; + } + if ((Constraint & LayoutConstraint.VerticallyFixed) == 0 && height.IsStar) + { + canFix = false; + break; + } + } + + if (canFix) + result |= LayoutConstraint.VerticallyFixed; + } + + if (hOptions.Alignment == LayoutAlignment.Fill) + { + int col = GetColumn(view); + int colSpan = GetColumnSpan(view); + List<ColumnDefinition> columnDefinitions = _columns; + + var canFix = true; + + for (int i = col; i < col + colSpan && i < columnDefinitions.Count; i++) + { + GridLength width = columnDefinitions[i].Width; + if (width.IsAuto) + { + canFix = false; + break; + } + if ((Constraint & LayoutConstraint.HorizontallyFixed) == 0 && width.IsStar) + { + canFix = false; + break; + } + } + + if (canFix) + result |= LayoutConstraint.HorizontallyFixed; + } + + view.ComputedConstraint = result; + } + + internal override void InvalidateMeasure(InvalidationTrigger trigger) + { + base.InvalidateMeasure(trigger); + _columns = null; + _rows = null; + } + + void OnDefinitionChanged(object sender, EventArgs args) + { + ComputeConstrainsForChildren(); + UpdateInheritedBindingContexts(); + InvalidateLayout(); + } + + void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == ColumnProperty.PropertyName || e.PropertyName == ColumnSpanProperty.PropertyName || e.PropertyName == RowProperty.PropertyName || + e.PropertyName == RowSpanProperty.PropertyName) + { + var child = sender as View; + if (child != null) + { + ComputeConstraintForView(child); + } + + InvalidateLayout(); + } + } + + void UpdateInheritedBindingContexts() + { + object bindingContext = BindingContext; + RowDefinitionCollection rowDefs = RowDefinitions; + if (rowDefs != null) + { + for (var i = 0; i < rowDefs.Count; i++) + { + RowDefinition rowdef = rowDefs[i]; + SetInheritedBindingContext(rowdef, bindingContext); + } + } + + ColumnDefinitionCollection colDefs = ColumnDefinitions; + if (colDefs != null) + { + for (var i = 0; i < colDefs.Count; i++) + { + ColumnDefinition coldef = colDefs[i]; + SetInheritedBindingContext(coldef, bindingContext); + } + } + } + + public interface IGridList<T> : IList<T> where T : View + { + void Add(View view, int left, int top); + void Add(View view, int left, int right, int top, int bottom); + void AddHorizontal(IEnumerable<View> views); + void AddHorizontal(View view); + void AddVertical(IEnumerable<View> views); + void AddVertical(View view); + } + + class GridElementCollection : ElementCollection<View>, IGridList<View> + { + public GridElementCollection(ObservableCollection<Element> inner, Grid parent) : base(inner) + { + Parent = parent; + } + + internal Grid Parent { get; set; } + + public void Add(View view, int left, int top) + { + if (left < 0) + throw new ArgumentOutOfRangeException("left"); + if (top < 0) + throw new ArgumentOutOfRangeException("top"); + Add(view, left, left + 1, top, top + 1); + } + + public void Add(View view, int left, int right, int top, int bottom) + { + if (left < 0) + throw new ArgumentOutOfRangeException("left"); + if (top < 0) + throw new ArgumentOutOfRangeException("top"); + if (left >= right) + throw new ArgumentOutOfRangeException("right"); + if (top >= bottom) + throw new ArgumentOutOfRangeException("bottom"); + if (view == null) + throw new ArgumentNullException("view"); + + SetRow(view, top); + SetRowSpan(view, bottom - top); + SetColumn(view, left); + SetColumnSpan(view, right - left); + + Add(view); + } + + public void AddHorizontal(IEnumerable<View> views) + { + if (views == null) + throw new ArgumentNullException("views"); + + views.ForEach(AddHorizontal); + } + + public void AddHorizontal(View view) + { + if (view == null) + throw new ArgumentNullException("view"); + + int lastRow = this.Any() ? this.Max(w => GetRow(w) + GetRowSpan(w) - 1) : -1; + lastRow = Math.Max(lastRow, Parent.RowDefinitions.Count - 1); + int lastCol = this.Any() ? this.Max(w => GetColumn(w) + GetColumnSpan(w) - 1) : -1; + lastCol = Math.Max(lastCol, Parent.ColumnDefinitions.Count - 1); + + Add(view, lastCol + 1, lastCol + 2, 0, Math.Max(1, lastRow)); + } + + public void AddVertical(IEnumerable<View> views) + { + if (views == null) + throw new ArgumentNullException("views"); + + views.ForEach(AddVertical); + } + + public void AddVertical(View view) + { + if (view == null) + throw new ArgumentNullException("view"); + + int lastRow = this.Any() ? this.Max(w => GetRow(w) + GetRowSpan(w) - 1) : -1; + lastRow = Math.Max(lastRow, Parent.RowDefinitions.Count - 1); + int lastCol = this.Any() ? this.Max(w => GetColumn(w) + GetColumnSpan(w) - 1) : -1; + lastCol = Math.Max(lastCol, Parent.ColumnDefinitions.Count - 1); + + Add(view, 0, Math.Max(1, lastCol), lastRow + 1, lastRow + 2); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/GridCalc.cs b/Xamarin.Forms.Core/GridCalc.cs new file mode 100644 index 00000000..778e188e --- /dev/null +++ b/Xamarin.Forms.Core/GridCalc.cs @@ -0,0 +1,698 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Forms +{ + public partial class Grid + { + List<ColumnDefinition> _columns; + List<RowDefinition> _rows; + + protected override void LayoutChildren(double x, double y, double width, double height) + { + if (!InternalChildren.Any()) + return; + + MeasureGrid(width, height); + + // Make copies so if InvalidateMeasure is called during layout we dont crash when these get nulled + List<ColumnDefinition> columnsCopy = _columns; + List<RowDefinition> rowsCopy = _rows; + + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (View)InternalChildren[index]; + if (!child.IsVisible) + continue; + int r = GetRow(child); + int c = GetColumn(child); + int rs = GetRowSpan(child); + int cs = GetColumnSpan(child); + + double posx = x + c * ColumnSpacing; + for (var i = 0; i < c; i++) + posx += columnsCopy[i].ActualWidth; + double posy = y + r * RowSpacing; + for (var i = 0; i < r; i++) + posy += rowsCopy[i].ActualHeight; + + double w = columnsCopy[c].ActualWidth; + for (var i = 1; i < cs; i++) + w += ColumnSpacing + columnsCopy[c + i].ActualWidth; + double h = rowsCopy[r].ActualHeight; + for (var i = 1; i < rs; i++) + h += RowSpacing + rowsCopy[r + i].ActualHeight; + + // in the future we can might maybe optimize by passing the already calculated size request + LayoutChildIntoBoundingRegion(child, new Rectangle(posx, posy, w, h)); + } + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + if (!InternalChildren.Any()) + return new SizeRequest(new Size(0, 0)); + + MeasureGrid(widthConstraint, heightConstraint, true); + + double columnWidthSum = 0; + double nonStarColumnWidthSum = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition c = _columns[index]; + columnWidthSum += c.ActualWidth; + if (!c.Width.IsStar) + nonStarColumnWidthSum += c.ActualWidth; + } + double rowHeightSum = 0; + double nonStarRowHeightSum = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition r = _rows[index]; + rowHeightSum += r.ActualHeight; + if (!r.Height.IsStar) + nonStarRowHeightSum += r.ActualHeight; + } + + var request = new Size(columnWidthSum + (_columns.Count - 1) * ColumnSpacing, rowHeightSum + (_rows.Count - 1) * RowSpacing); + var minimum = new Size(nonStarColumnWidthSum + (_columns.Count - 1) * ColumnSpacing, nonStarRowHeightSum + (_rows.Count - 1) * RowSpacing); + + var result = new SizeRequest(request, minimum); + return result; + } + + void AssignAbsoluteCells() + { + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsAbsolute) + row.ActualHeight = row.Height.Value; + } + + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsAbsolute) + col.ActualWidth = col.Width.Value; + } + } + + void CalculateAutoCells(double width, double height) + { + // this require multiple passes. First process the 1-span, then 2, 3, ... + // And this needs to be run twice, just in case a lower-span column can be determined by a larger span + for (var iteration = 0; iteration < 2; iteration++) + { + for (var rowspan = 1; rowspan <= _rows.Count; rowspan++) + { + for (var i = 0; i < _rows.Count; i++) + { + RowDefinition row = _rows[i]; + if (!row.Height.IsAuto) + continue; + if (row.ActualHeight >= 0) // if Actual is already set (by a smaller span), skip till pass 3 + continue; + + double actualHeight = row.ActualHeight; + double minimumHeight = row.MinimumHeight; + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (View)InternalChildren[index]; + if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1) + continue; + double assignedWidth = GetAssignedColumnWidth(child); + double assignedHeight = GetAssignedRowHeight(child); + double widthRequest = assignedWidth + GetUnassignedWidth(width); + double heightRequest = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + GetUnassignedHeight(height); + + SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins); + actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1)); + minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1)); + } + if (actualHeight >= 0) + row.ActualHeight = actualHeight; + if (minimumHeight >= 0) + row.MinimumHeight = minimumHeight; + } + } + + for (var colspan = 1; colspan <= _columns.Count; colspan++) + { + for (var i = 0; i < _columns.Count; i++) + { + ColumnDefinition col = _columns[i]; + if (!col.Width.IsAuto) + continue; + if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip + continue; + + double actualWidth = col.ActualWidth; + double minimumWidth = col.MinimumWidth; + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (View)InternalChildren[index]; + if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1) + continue; + double assignedWidth = GetAssignedColumnWidth(child); + double assignedHeight = GetAssignedRowHeight(child); + double widthRequest = double.IsPositiveInfinity(width) ? double.PositiveInfinity : assignedWidth + GetUnassignedWidth(width); + double heightRequest = assignedHeight + GetUnassignedHeight(height); + + SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins); + actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing); + minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing); + } + if (actualWidth >= 0) + col.ActualWidth = actualWidth; + if (minimumWidth >= 0) + col.MinimumWidth = actualWidth; + } + } + } + } + + void CalculateStarCells(double width, double height, double totalStarsWidth, double totalStarsHeight) + { + double starColWidth = GetUnassignedWidth(width) / totalStarsWidth; + double starRowHeight = GetUnassignedHeight(height) / totalStarsHeight; + + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + col.ActualWidth = col.Width.Value * starColWidth; + } + + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + row.ActualHeight = row.Height.Value * starRowHeight; + } + } + + void ContractColumnsIfNeeded(double width, Func<ColumnDefinition, bool> predicate) + { + double columnWidthSum = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition c = _columns[index]; + columnWidthSum += c.ActualWidth; + } + + double rowHeightSum = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition r = _rows[index]; + rowHeightSum += r.ActualHeight; + } + + var request = new Size(columnWidthSum + (_columns.Count - 1) * ColumnSpacing, rowHeightSum + (_rows.Count - 1) * RowSpacing); + if (request.Width > width) + { + double contractionSpace = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition c = _columns[index]; + if (predicate(c)) + contractionSpace += c.ActualWidth - c.MinimumWidth; + } + if (contractionSpace > 0) + { + // contract as much as we can but no more + double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Width - width, 0)); + double contractFactor = contractionNeeded / contractionSpace; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (!predicate(col)) + continue; + double availableSpace = col.ActualWidth - col.MinimumWidth; + double contraction = availableSpace * contractFactor; + col.ActualWidth -= contraction; + contractionNeeded -= contraction; + } + } + } + } + + void ContractRowsIfNeeded(double height, Func<RowDefinition, bool> predicate) + { + double columnSum = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition c = _columns[index]; + columnSum += Math.Max(0, c.ActualWidth); + } + double rowSum = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition r = _rows[index]; + rowSum += Math.Max(0, r.ActualHeight); + } + + var request = new Size(columnSum + (_columns.Count - 1) * ColumnSpacing, rowSum + (_rows.Count - 1) * RowSpacing); + if (request.Height <= height) + return; + double contractionSpace = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition r = _rows[index]; + if (predicate(r)) + contractionSpace += r.ActualHeight - r.MinimumHeight; + } + if (!(contractionSpace > 0)) + return; + // contract as much as we can but no more + double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Height - height, 0)); + double contractFactor = contractionNeeded / contractionSpace; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (!predicate(row)) + continue; + double availableSpace = row.ActualHeight - row.MinimumHeight; + double contraction = availableSpace * contractFactor; + row.ActualHeight -= contraction; + contractionNeeded -= contraction; + } + } + + void EnsureRowsColumnsInitialized() + { + _columns = ColumnDefinitions == null ? new List<ColumnDefinition>() : ColumnDefinitions.ToList(); + _rows = RowDefinitions == null ? new List<RowDefinition>() : RowDefinitions.ToList(); + + int lastRow = -1; + for (var index = 0; index < InternalChildren.Count; index++) + { + Element w = InternalChildren[index]; + lastRow = Math.Max(lastRow, GetRow(w) + GetRowSpan(w) - 1); + } + lastRow = Math.Max(lastRow, RowDefinitions.Count - 1); + + int lastCol = -1; + for (var index = 0; index < InternalChildren.Count; index++) + { + Element w = InternalChildren[index]; + lastCol = Math.Max(lastCol, GetColumn(w) + GetColumnSpan(w) - 1); + } + lastCol = Math.Max(lastCol, ColumnDefinitions.Count - 1); + + while (_columns.Count <= lastCol) + _columns.Add(new ColumnDefinition()); + while (_rows.Count <= lastRow) + _rows.Add(new RowDefinition()); + + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + col.ActualWidth = -1; + } + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + row.ActualHeight = -1; + } + } + + void ExpandLastAutoColumnIfNeeded(double width, bool expandToRequest) + { + for (var index = 0; index < InternalChildren.Count; index++) + { + Element element = InternalChildren[index]; + var child = (View)element; + if (!child.IsVisible) + continue; + + ColumnDefinition col = GetLastAutoColumn(child); + if (col == null) + continue; + + double assignedWidth = GetAssignedColumnWidth(child); + double w = double.IsPositiveInfinity(width) ? double.PositiveInfinity : assignedWidth + GetUnassignedWidth(width); + SizeRequest sizeRequest = child.Measure(w, GetAssignedRowHeight(child), MeasureFlags.IncludeMargins); + double requiredWidth = expandToRequest ? sizeRequest.Request.Width : sizeRequest.Minimum.Width; + double deltaWidth = requiredWidth - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing; + if (deltaWidth > 0) + { + col.ActualWidth += deltaWidth; + } + } + } + + void ExpandLastAutoRowIfNeeded(double height, bool expandToRequest) + { + for (var index = 0; index < InternalChildren.Count; index++) + { + Element element = InternalChildren[index]; + var child = (View)element; + if (!child.IsVisible) + continue; + + RowDefinition row = GetLastAutoRow(child); + if (row == null) + continue; + + double assignedHeight = GetAssignedRowHeight(child); + double h = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + GetUnassignedHeight(height); + SizeRequest sizeRequest = child.Measure(GetAssignedColumnWidth(child), h, MeasureFlags.IncludeMargins); + double requiredHeight = expandToRequest ? sizeRequest.Request.Height : sizeRequest.Minimum.Height; + double deltaHeight = requiredHeight - assignedHeight - (GetRowSpan(child) - 1) * RowSpacing; + if (deltaHeight > 0) + { + row.ActualHeight += deltaHeight; + } + } + } + + void MeasureAndContractStarredColumns(double width, double height, double totalStarsWidth) + { + double starColWidth; + starColWidth = MeasuredStarredColumns(); + + if (!double.IsPositiveInfinity(width) && double.IsPositiveInfinity(height)) + { + // re-zero columns so GetUnassignedWidth returns correctly + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + col.ActualWidth = 0; + } + + starColWidth = Math.Max(starColWidth, GetUnassignedWidth(width) / totalStarsWidth); + } + + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + col.ActualWidth = col.Width.Value * starColWidth; + } + + ContractColumnsIfNeeded(width, c => c.Width.IsStar); + } + + void MeasureAndContractStarredRows(double width, double height, double totalStarsHeight) + { + double starRowHeight; + starRowHeight = MeasureStarredRows(); + + if (!double.IsPositiveInfinity(height) && double.IsPositiveInfinity(width)) + { + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + row.ActualHeight = 0; + } + + starRowHeight = Math.Max(starRowHeight, GetUnassignedHeight(height) / totalStarsHeight); + } + + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + row.ActualHeight = row.Height.Value * starRowHeight; + } + + ContractRowsIfNeeded(height, r => r.Height.IsStar); + } + + double MeasuredStarredColumns() + { + double starColWidth; + for (var iteration = 0; iteration < 2; iteration++) + { + for (var colspan = 1; colspan <= _columns.Count; colspan++) + { + for (var i = 0; i < _columns.Count; i++) + { + ColumnDefinition col = _columns[i]; + if (!col.Width.IsStar) + continue; + if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip + continue; + + double actualWidth = col.ActualWidth; + double minimumWidth = col.MinimumWidth; + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (View)InternalChildren[index]; + if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1) + continue; + double assignedWidth = GetAssignedColumnWidth(child); + + SizeRequest sizeRequest = child.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins); + actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing); + minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing); + } + if (actualWidth >= 0) + col.ActualWidth = actualWidth; + + if (minimumWidth >= 0) + col.MinimumWidth = minimumWidth; + } + } + } + + //Measure the stars + starColWidth = 1; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (!col.Width.IsStar) + continue; + starColWidth = Math.Max(starColWidth, col.ActualWidth / col.Width.Value); + } + + return starColWidth; + } + + void MeasureGrid(double width, double height, bool requestSize = false) + { + EnsureRowsColumnsInitialized(); + + AssignAbsoluteCells(); + + CalculateAutoCells(width, height); + + if (!requestSize) + { + ContractColumnsIfNeeded(width, c => c.Width.IsAuto); + ContractRowsIfNeeded(height, r => r.Height.IsAuto); + } + + double totalStarsHeight = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + totalStarsHeight += row.Height.Value; + } + + double totalStarsWidth = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + totalStarsWidth += col.Width.Value; + } + + if (requestSize) + { + MeasureAndContractStarredColumns(width, height, totalStarsWidth); + MeasureAndContractStarredRows(width, height, totalStarsHeight); + } + else + { + CalculateStarCells(width, height, totalStarsWidth, totalStarsHeight); + } + + ZeroUnassignedCells(); + + ExpandLastAutoRowIfNeeded(height, requestSize); + ExpandLastAutoColumnIfNeeded(width, requestSize); + } + + double MeasureStarredRows() + { + double starRowHeight; + for (var iteration = 0; iteration < 2; iteration++) + { + for (var rowspan = 1; rowspan <= _rows.Count; rowspan++) + { + for (var i = 0; i < _rows.Count; i++) + { + RowDefinition row = _rows[i]; + if (!row.Height.IsStar) + continue; + if (row.ActualHeight >= 0) // if Actual is already set (by a smaller span), skip till pass 3 + continue; + + double actualHeight = row.ActualHeight; + double minimumHeight = row.MinimumHeight; + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (View)InternalChildren[index]; + if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1) + continue; + double assignedHeight = GetAssignedRowHeight(child); + double assignedWidth = GetAssignedColumnWidth(child); + + SizeRequest sizeRequest = child.Measure(assignedWidth, double.PositiveInfinity, MeasureFlags.IncludeMargins); + actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1)); + minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1)); + } + if (actualHeight >= 0) + row.ActualHeight = actualHeight; + + if (minimumHeight >= 0) + row.MinimumHeight = minimumHeight; + } + } + } + + // 3. Star columns: + + //Measure the stars + starRowHeight = 1; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (!row.Height.IsStar) + continue; + starRowHeight = Math.Max(starRowHeight, row.ActualHeight / row.Height.Value); + } + + return starRowHeight; + } + + void ZeroUnassignedCells() + { + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.ActualWidth < 0) + col.ActualWidth = 0; + } + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.ActualHeight < 0) + row.ActualHeight = 0; + } + } + + #region Helpers + + static bool IsInColumn(BindableObject child, int column) + { + int childColumn = GetColumn(child); + int span = GetColumnSpan(child); + return childColumn <= column && column < childColumn + span; + } + + static bool IsInRow(BindableObject child, int row) + { + int childRow = GetRow(child); + int span = GetRowSpan(child); + return childRow <= row && row < childRow + span; + } + + int NumberOfUnsetColumnWidth(BindableObject child) + { + var n = 0; + int index = GetColumn(child); + int span = GetColumnSpan(child); + for (int i = index; i < index + span; i++) + if (_columns[i].ActualWidth <= 0) + n++; + return n; + } + + int NumberOfUnsetRowHeight(BindableObject child) + { + var n = 0; + int index = GetRow(child); + int span = GetRowSpan(child); + for (int i = index; i < index + span; i++) + if (_rows[i].ActualHeight <= 0) + n++; + return n; + } + + double GetAssignedColumnWidth(BindableObject child) + { + var actual = 0d; + int index = GetColumn(child); + int span = GetColumnSpan(child); + for (int i = index; i < index + span; i++) + if (_columns[i].ActualWidth >= 0) + actual += _columns[i].ActualWidth; + return actual; + } + + double GetAssignedRowHeight(BindableObject child) + { + var actual = 0d; + int index = GetRow(child); + int span = GetRowSpan(child); + for (int i = index; i < index + span; i++) + if (_rows[i].ActualHeight >= 0) + actual += _rows[i].ActualHeight; + return actual; + } + + ColumnDefinition GetLastAutoColumn(BindableObject child) + { + int index = GetColumn(child); + int span = GetColumnSpan(child); + for (int i = index + span - 1; i >= index; i--) + if (_columns[i].Width.IsAuto) + return _columns[i]; + return null; + } + + RowDefinition GetLastAutoRow(BindableObject child) + { + int index = GetRow(child); + int span = GetRowSpan(child); + for (int i = index + span - 1; i >= index; i--) + if (_rows[i].Height.IsAuto) + return _rows[i]; + return null; + } + + double GetUnassignedHeight(double heightRequest) + { + double assigned = (_rows.Count - 1) * RowSpacing; + for (var i = 0; i < _rows.Count; i++) + { + double actual = _rows[i].ActualHeight; + if (actual >= 0) + assigned += actual; + } + return heightRequest - assigned; + } + + double GetUnassignedWidth(double widthRequest) + { + double assigned = (_columns.Count - 1) * ColumnSpacing; + for (var i = 0; i < _columns.Count; i++) + { + double actual = _columns[i].ActualWidth; + if (actual >= 0) + assigned += actual; + } + return widthRequest - assigned; + } + + #endregion + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/GridLength.cs b/Xamarin.Forms.Core/GridLength.cs new file mode 100644 index 00000000..5d569d08 --- /dev/null +++ b/Xamarin.Forms.Core/GridLength.cs @@ -0,0 +1,74 @@ +using System; +using System.Diagnostics; + +namespace Xamarin.Forms +{ + [TypeConverter(typeof(GridLengthTypeConverter))] + [DebuggerDisplay("{Value}.{GridUnitType}")] + public struct GridLength + { + public static GridLength Auto + { + get { return new GridLength(1, GridUnitType.Auto); } + } + + public double Value { get; } + + public GridUnitType GridUnitType { get; } + + public bool IsAbsolute + { + get { return GridUnitType == GridUnitType.Absolute; } + } + + public bool IsAuto + { + get { return GridUnitType == GridUnitType.Auto; } + } + + public bool IsStar + { + get { return GridUnitType == GridUnitType.Star; } + } + + public GridLength(double value) : this(value, GridUnitType.Absolute) + { + } + + public GridLength(double value, GridUnitType type) + { + if (value < 0 || double.IsNaN(value)) + throw new ArgumentException("value is less than 0 or is not a number", "value"); + if ((int)type < (int)GridUnitType.Absolute || (int)type > (int)GridUnitType.Auto) + throw new ArgumentException("type is not a valid GridUnitType", "type"); + + Value = value; + GridUnitType = type; + } + + public override bool Equals(object obj) + { + return obj != null && obj is GridLength && Equals((GridLength)obj); + } + + bool Equals(GridLength other) + { + return GridUnitType == other.GridUnitType && Math.Abs(Value - other.Value) < double.Epsilon; + } + + public override int GetHashCode() + { + return GridUnitType.GetHashCode() * 397 ^ Value.GetHashCode(); + } + + public static implicit operator GridLength(double absoluteValue) + { + return new GridLength(absoluteValue); + } + + public override string ToString() + { + return string.Format("{0}.{1}", Value, GridUnitType); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/GridLengthTypeConverter.cs b/Xamarin.Forms.Core/GridLengthTypeConverter.cs new file mode 100644 index 00000000..7c58b417 --- /dev/null +++ b/Xamarin.Forms.Core/GridLengthTypeConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms +{ + public class GridLengthTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value == null) + return null; + + double length; + value = value.Trim(); + if (string.Compare(value, "auto", StringComparison.OrdinalIgnoreCase) == 0) + return GridLength.Auto; + if (string.Compare(value, "*", StringComparison.OrdinalIgnoreCase) == 0) + return new GridLength(1, GridUnitType.Star); + if (value.EndsWith("*", StringComparison.Ordinal) && double.TryParse(value.Substring(0, value.Length - 1), NumberStyles.Number, CultureInfo.InvariantCulture, out length)) + return new GridLength(length, GridUnitType.Star); + if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out length)) + return new GridLength(length); + + throw new FormatException(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/GridUnitType.cs b/Xamarin.Forms.Core/GridUnitType.cs new file mode 100644 index 00000000..372c1804 --- /dev/null +++ b/Xamarin.Forms.Core/GridUnitType.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public enum GridUnitType + { + Absolute, + Star, + Auto + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/HandlerAttribute.cs b/Xamarin.Forms.Core/HandlerAttribute.cs new file mode 100644 index 00000000..b9dc9ecb --- /dev/null +++ b/Xamarin.Forms.Core/HandlerAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public abstract class HandlerAttribute : Attribute + { + protected HandlerAttribute(Type handler, Type target) + { + TargetType = target; + HandlerType = handler; + } + + internal Type HandlerType { get; private set; } + + internal Type TargetType { get; private set; } + + public virtual bool ShouldRegister() + { + return true; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/HtmlWebViewSource.cs b/Xamarin.Forms.Core/HtmlWebViewSource.cs new file mode 100644 index 00000000..157e5054 --- /dev/null +++ b/Xamarin.Forms.Core/HtmlWebViewSource.cs @@ -0,0 +1,28 @@ +namespace Xamarin.Forms +{ + public class HtmlWebViewSource : WebViewSource + { + public static readonly BindableProperty HtmlProperty = BindableProperty.Create("Html", typeof(string), typeof(HtmlWebViewSource), default(string), + propertyChanged: (bindable, oldvalue, newvalue) => ((HtmlWebViewSource)bindable).OnSourceChanged()); + + public static readonly BindableProperty BaseUrlProperty = BindableProperty.Create("BaseUrl", typeof(string), typeof(HtmlWebViewSource), default(string), + propertyChanged: (bindable, oldvalue, newvalue) => ((HtmlWebViewSource)bindable).OnSourceChanged()); + + public string BaseUrl + { + get { return (string)GetValue(BaseUrlProperty); } + set { SetValue(BaseUrlProperty, value); } + } + + public string Html + { + get { return (string)GetValue(HtmlProperty); } + set { SetValue(HtmlProperty, value); } + } + + internal override void Load(IWebViewRenderer renderer) + { + renderer.LoadHtml(Html, BaseUrl); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IAnimatable.cs b/Xamarin.Forms.Core/IAnimatable.cs new file mode 100644 index 00000000..2e57c2e5 --- /dev/null +++ b/Xamarin.Forms.Core/IAnimatable.cs @@ -0,0 +1,34 @@ +// +// IAnimatable.cs +// +// Author: +// Jason Smith <jason.smith@xamarin.com> +// +// Copyright (c) 2012 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. + +namespace Xamarin.Forms +{ + public interface IAnimatable + { + void BatchBegin(); + void BatchCommit(); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IApplicationController.cs b/Xamarin.Forms.Core/IApplicationController.cs new file mode 100644 index 00000000..201032f9 --- /dev/null +++ b/Xamarin.Forms.Core/IApplicationController.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + public interface IApplicationController + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IButtonController.cs b/Xamarin.Forms.Core/IButtonController.cs new file mode 100644 index 00000000..2fa1737f --- /dev/null +++ b/Xamarin.Forms.Core/IButtonController.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms +{ + internal interface IButtonController : IViewController + { + void SendClicked(); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ICarouselViewController.cs b/Xamarin.Forms.Core/ICarouselViewController.cs new file mode 100644 index 00000000..f59bc3d3 --- /dev/null +++ b/Xamarin.Forms.Core/ICarouselViewController.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public interface ICarouselViewController : IItemViewController + { + void SendPositionAppearing(int position); + void SendPositionDisappearing(int position); + void SendSelectedItemChanged(object item); + void SendSelectedPositionChanged(int position); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IControlTemplated.cs b/Xamarin.Forms.Core/IControlTemplated.cs new file mode 100644 index 00000000..f5798c2e --- /dev/null +++ b/Xamarin.Forms.Core/IControlTemplated.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + internal interface IControlTemplated + { + ControlTemplate ControlTemplate { get; set; } + + IList<Element> InternalChildren { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IDefinition.cs b/Xamarin.Forms.Core/IDefinition.cs new file mode 100644 index 00000000..6971d611 --- /dev/null +++ b/Xamarin.Forms.Core/IDefinition.cs @@ -0,0 +1,9 @@ +using System; + +namespace Xamarin.Forms +{ + public interface IDefinition + { + event EventHandler SizeChanged; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IDeserializer.cs b/Xamarin.Forms.Core/IDeserializer.cs new file mode 100644 index 00000000..60478e1b --- /dev/null +++ b/Xamarin.Forms.Core/IDeserializer.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + internal interface IDeserializer + { + Task<IDictionary<string, object>> DeserializePropertiesAsync(); + Task SerializePropertiesAsync(IDictionary<string, object> properties); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IEffectControlProvider.cs b/Xamarin.Forms.Core/IEffectControlProvider.cs new file mode 100644 index 00000000..f1882f5f --- /dev/null +++ b/Xamarin.Forms.Core/IEffectControlProvider.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms +{ + public interface IEffectControlProvider + { + void RegisterEffect(Effect effect); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IElement.cs b/Xamarin.Forms.Core/IElement.cs new file mode 100644 index 00000000..ee302b5f --- /dev/null +++ b/Xamarin.Forms.Core/IElement.cs @@ -0,0 +1,13 @@ +using System; + +namespace Xamarin.Forms +{ + internal interface IElement + { + Element Parent { get; set; } + + //Use these 2 instead of an event to avoid cloning way too much multicastdelegates on mono + void AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged); + void RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IElementController.cs b/Xamarin.Forms.Core/IElementController.cs new file mode 100644 index 00000000..4e1ab10d --- /dev/null +++ b/Xamarin.Forms.Core/IElementController.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public interface IElementController + { + IEffectControlProvider EffectControlProvider { get; set; } + + void SetValueFromRenderer(BindableProperty property, object value); + void SetValueFromRenderer(BindablePropertyKey propertyKey, object value); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IExpressionSearch.cs b/Xamarin.Forms.Core/IExpressionSearch.cs new file mode 100644 index 00000000..e5d4c26f --- /dev/null +++ b/Xamarin.Forms.Core/IExpressionSearch.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Xamarin.Forms +{ + internal interface IExpressionSearch + { + List<T> FindObjects<T>(Expression expression) where T : class; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IExtendedTypeConverter.cs b/Xamarin.Forms.Core/IExtendedTypeConverter.cs new file mode 100644 index 00000000..e3b16b52 --- /dev/null +++ b/Xamarin.Forms.Core/IExtendedTypeConverter.cs @@ -0,0 +1,13 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms +{ + public interface IExtendedTypeConverter + { + [Obsolete("use ConvertFromInvariantString (string, IServiceProvider)")] + object ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider); + + object ConvertFromInvariantString(string value, IServiceProvider serviceProvider); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IFontElement.cs b/Xamarin.Forms.Core/IFontElement.cs new file mode 100644 index 00000000..f1f3a36f --- /dev/null +++ b/Xamarin.Forms.Core/IFontElement.cs @@ -0,0 +1,11 @@ +namespace Xamarin.Forms +{ + internal interface IFontElement + { + FontAttributes FontAttributes { get; } + + string FontFamily { get; } + + double FontSize { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IGestureRecognizer.cs b/Xamarin.Forms.Core/IGestureRecognizer.cs new file mode 100644 index 00000000..4e361c0a --- /dev/null +++ b/Xamarin.Forms.Core/IGestureRecognizer.cs @@ -0,0 +1,8 @@ +using System.ComponentModel; + +namespace Xamarin.Forms +{ + public interface IGestureRecognizer : INotifyPropertyChanged + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IIsolatedStorageFile.cs b/Xamarin.Forms.Core/IIsolatedStorageFile.cs new file mode 100644 index 00000000..50899023 --- /dev/null +++ b/Xamarin.Forms.Core/IIsolatedStorageFile.cs @@ -0,0 +1,18 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + internal interface IIsolatedStorageFile + { + Task CreateDirectoryAsync(string path); + Task<bool> GetDirectoryExistsAsync(string path); + Task<bool> GetFileExistsAsync(string path); + + Task<DateTimeOffset> GetLastWriteTimeAsync(string path); + + Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access); + Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IItemViewController.cs b/Xamarin.Forms.Core/IItemViewController.cs new file mode 100644 index 00000000..79f711e9 --- /dev/null +++ b/Xamarin.Forms.Core/IItemViewController.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public interface IItemViewController + { + void BindView(View view, object item); + View CreateView(object itemType); + object GetItem(int index); + object GetItemType(object item); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IItemsView.cs b/Xamarin.Forms.Core/IItemsView.cs new file mode 100644 index 00000000..7f968769 --- /dev/null +++ b/Xamarin.Forms.Core/IItemsView.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + internal interface IItemsView<T> where T : BindableObject + { + T CreateDefault(object item); + void SetupContent(T content, int index); + void UnhookContent(T content); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ILayout.cs b/Xamarin.Forms.Core/ILayout.cs new file mode 100644 index 00000000..781111a8 --- /dev/null +++ b/Xamarin.Forms.Core/ILayout.cs @@ -0,0 +1,9 @@ +using System; + +namespace Xamarin.Forms +{ + public interface ILayout + { + event EventHandler LayoutChanged; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ILayoutController.cs b/Xamarin.Forms.Core/ILayoutController.cs new file mode 100644 index 00000000..56bff7e0 --- /dev/null +++ b/Xamarin.Forms.Core/ILayoutController.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + public interface ILayoutController + { + IReadOnlyList<Element> Children { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IListViewController.cs b/Xamarin.Forms.Core/IListViewController.cs new file mode 100644 index 00000000..078f13b7 --- /dev/null +++ b/Xamarin.Forms.Core/IListViewController.cs @@ -0,0 +1,15 @@ +namespace Xamarin.Forms +{ + internal interface IListViewController : IViewController + { + Element FooterElement { get; } + + Element HeaderElement { get; } + + bool RefreshAllowed { get; } + + void SendCellAppearing(Cell cell); + void SendCellDisappearing(Cell cell); + void SendRefreshing(); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IMarkupExtension.cs b/Xamarin.Forms.Core/IMarkupExtension.cs new file mode 100644 index 00000000..24a435f9 --- /dev/null +++ b/Xamarin.Forms.Core/IMarkupExtension.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms.Xaml +{ + public interface IMarkupExtension<out T> : IMarkupExtension + { + new T ProvideValue(IServiceProvider serviceProvider); + } + + public interface IMarkupExtension + { + object ProvideValue(IServiceProvider serviceProvider); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/INavigation.cs b/Xamarin.Forms.Core/INavigation.cs new file mode 100644 index 00000000..6ae5959d --- /dev/null +++ b/Xamarin.Forms.Core/INavigation.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + public interface INavigation + { + IReadOnlyList<Page> ModalStack { get; } + + IReadOnlyList<Page> NavigationStack { get; } + + void InsertPageBefore(Page page, Page before); + Task<Page> PopAsync(); + Task<Page> PopAsync(bool animated); + Task<Page> PopModalAsync(); + Task<Page> PopModalAsync(bool animated); + Task PopToRootAsync(); + Task PopToRootAsync(bool animated); + + Task PushAsync(Page page); + + Task PushAsync(Page page, bool animated); + Task PushModalAsync(Page page); + Task PushModalAsync(Page page, bool animated); + + void RemovePage(Page page); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IOpenGlViewController.cs b/Xamarin.Forms.Core/IOpenGlViewController.cs new file mode 100644 index 00000000..d89c5fef --- /dev/null +++ b/Xamarin.Forms.Core/IOpenGlViewController.cs @@ -0,0 +1,9 @@ +using System; + +namespace Xamarin.Forms +{ + public interface IOpenGlViewController : IViewController + { + event EventHandler DisplayRequested; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IPageContainer.cs b/Xamarin.Forms.Core/IPageContainer.cs new file mode 100644 index 00000000..9582e5e2 --- /dev/null +++ b/Xamarin.Forms.Core/IPageContainer.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms +{ + public interface IPageContainer<out T> where T : Page + { + T CurrentPage { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IPanGestureController.cs b/Xamarin.Forms.Core/IPanGestureController.cs new file mode 100644 index 00000000..96283092 --- /dev/null +++ b/Xamarin.Forms.Core/IPanGestureController.cs @@ -0,0 +1,13 @@ +namespace Xamarin.Forms +{ + internal interface IPanGestureController + { + void SendPan(Element sender, double totalX, double totalY, int gestureId); + + void SendPanCanceled(Element sender, int gestureId); + + void SendPanCompleted(Element sender, int gestureId); + + void SendPanStarted(Element sender, int gestureId); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IPinchGestureController.cs b/Xamarin.Forms.Core/IPinchGestureController.cs new file mode 100644 index 00000000..9848fa74 --- /dev/null +++ b/Xamarin.Forms.Core/IPinchGestureController.cs @@ -0,0 +1,15 @@ +namespace Xamarin.Forms +{ + internal interface IPinchGestureController + { + bool IsPinching { get; set; } + + void SendPinch(Element sender, double scale, Point currentScalePoint); + + void SendPinchCanceled(Element sender); + + void SendPinchEnded(Element sender); + + void SendPinchStarted(Element sender, Point intialScalePoint); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IPlatform.cs b/Xamarin.Forms.Core/IPlatform.cs new file mode 100644 index 00000000..507abcfe --- /dev/null +++ b/Xamarin.Forms.Core/IPlatform.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms +{ + internal interface IPlatform + { + SizeRequest GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IPlatformServices.cs b/Xamarin.Forms.Core/IPlatformServices.cs new file mode 100644 index 00000000..e4a3ce0b --- /dev/null +++ b/Xamarin.Forms.Core/IPlatformServices.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + internal interface IPlatformServices + { + bool IsInvokeRequired { get; } + + void BeginInvokeOnMainThread(Action action); + + //this will go once Timer is included in Pcl profiles + ITimer CreateTimer(Action<object> callback); + ITimer CreateTimer(Action<object> callback, object state, int dueTime, int period); + ITimer CreateTimer(Action<object> callback, object state, long dueTime, long period); + ITimer CreateTimer(Action<object> callback, object state, TimeSpan dueTime, TimeSpan period); + ITimer CreateTimer(Action<object> callback, object state, uint dueTime, uint period); + + Assembly[] GetAssemblies(); + + string GetMD5Hash(string input); + + double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes); + + Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken); + + IIsolatedStorageFile GetUserStoreForApplication(); + + void OpenUriAction(Uri uri); + + void StartTimer(TimeSpan interval, Func<bool> callback); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IProvideParentValues.cs b/Xamarin.Forms.Core/IProvideParentValues.cs new file mode 100644 index 00000000..c07c606f --- /dev/null +++ b/Xamarin.Forms.Core/IProvideParentValues.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms.Xaml +{ + internal interface IProvideParentValues : IProvideValueTarget + { + IEnumerable<object> ParentObjects { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IProvideValueTarget.cs b/Xamarin.Forms.Core/IProvideValueTarget.cs new file mode 100644 index 00000000..b8324b5e --- /dev/null +++ b/Xamarin.Forms.Core/IProvideValueTarget.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms.Xaml +{ + public interface IProvideValueTarget + { + object TargetObject { get; } + + object TargetProperty { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IRegisterable.cs b/Xamarin.Forms.Core/IRegisterable.cs new file mode 100644 index 00000000..995ebbce --- /dev/null +++ b/Xamarin.Forms.Core/IRegisterable.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + public interface IRegisterable + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IResourceDictionary.cs b/Xamarin.Forms.Core/IResourceDictionary.cs new file mode 100644 index 00000000..5fccb9d9 --- /dev/null +++ b/Xamarin.Forms.Core/IResourceDictionary.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + internal interface IResourceDictionary : IEnumerable<KeyValuePair<string, object>> + { + bool TryGetValue(string key, out object value); + + event EventHandler<ResourcesChangedEventArgs> ValuesChanged; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IResourcesProvider.cs b/Xamarin.Forms.Core/IResourcesProvider.cs new file mode 100644 index 00000000..e06ef743 --- /dev/null +++ b/Xamarin.Forms.Core/IResourcesProvider.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms +{ + internal interface IResourcesProvider + { + ResourceDictionary Resources { get; set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IRootObjectProvider.cs b/Xamarin.Forms.Core/IRootObjectProvider.cs new file mode 100644 index 00000000..883033a5 --- /dev/null +++ b/Xamarin.Forms.Core/IRootObjectProvider.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms.Xaml +{ + public interface IRootObjectProvider + { + object RootObject { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IScrollViewController.cs b/Xamarin.Forms.Core/IScrollViewController.cs new file mode 100644 index 00000000..c06ddc09 --- /dev/null +++ b/Xamarin.Forms.Core/IScrollViewController.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Forms +{ + public interface IScrollViewController : ILayoutController + { + Point GetScrollPositionForElement(VisualElement item, ScrollToPosition position); + + event EventHandler<ScrollToRequestedEventArgs> ScrollToRequested; + + void SendScrollFinished(); + + void SetScrolledPosition(double x, double y); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IStyle.cs b/Xamarin.Forms.Core/IStyle.cs new file mode 100644 index 00000000..cdb998dc --- /dev/null +++ b/Xamarin.Forms.Core/IStyle.cs @@ -0,0 +1,12 @@ +using System; + +namespace Xamarin.Forms +{ + internal interface IStyle + { + Type TargetType { get; } + + void Apply(BindableObject bindable); + void UnApply(BindableObject bindable); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ISystemResourcesProvider.cs b/Xamarin.Forms.Core/ISystemResourcesProvider.cs new file mode 100644 index 00000000..9f0e5772 --- /dev/null +++ b/Xamarin.Forms.Core/ISystemResourcesProvider.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms +{ + internal interface ISystemResourcesProvider + { + IResourceDictionary GetSystemResources(); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ITimer.cs b/Xamarin.Forms.Core/ITimer.cs new file mode 100644 index 00000000..ba867dba --- /dev/null +++ b/Xamarin.Forms.Core/ITimer.cs @@ -0,0 +1,13 @@ +using System; + +namespace Xamarin.Forms +{ + //this will go once Timer is included in Pcl profiles + internal interface ITimer + { + void Change(int dueTime, int period); + void Change(long dueTime, long period); + void Change(TimeSpan dueTime, TimeSpan period); + void Change(uint dueTime, uint period); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IValueConverter.cs b/Xamarin.Forms.Core/IValueConverter.cs new file mode 100644 index 00000000..8702ec3d --- /dev/null +++ b/Xamarin.Forms.Core/IValueConverter.cs @@ -0,0 +1,11 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms +{ + public interface IValueConverter + { + object Convert(object value, Type targetType, object parameter, CultureInfo culture); + object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IValueConverterProvider.cs b/Xamarin.Forms.Core/IValueConverterProvider.cs new file mode 100644 index 00000000..1221ce74 --- /dev/null +++ b/Xamarin.Forms.Core/IValueConverterProvider.cs @@ -0,0 +1,10 @@ +using System; +using System.Reflection; + +namespace Xamarin.Forms.Xaml +{ + internal interface IValueConverterProvider + { + object Convert(object value, Type toType, Func<MemberInfo> minfoRetriever, IServiceProvider serviceProvider); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IValueProvider.cs b/Xamarin.Forms.Core/IValueProvider.cs new file mode 100644 index 00000000..19d3bf93 --- /dev/null +++ b/Xamarin.Forms.Core/IValueProvider.cs @@ -0,0 +1,9 @@ +using System; + +namespace Xamarin.Forms.Xaml +{ + public interface IValueProvider + { + object ProvideValue(IServiceProvider serviceProvider); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IViewContainer.cs b/Xamarin.Forms.Core/IViewContainer.cs new file mode 100644 index 00000000..9b2e0570 --- /dev/null +++ b/Xamarin.Forms.Core/IViewContainer.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + public interface IViewContainer<T> where T : VisualElement + { + IList<T> Children { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IViewController.cs b/Xamarin.Forms.Core/IViewController.cs new file mode 100644 index 00000000..1d956387 --- /dev/null +++ b/Xamarin.Forms.Core/IViewController.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + public interface IViewController : IVisualElementController + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IVisualElementController.cs b/Xamarin.Forms.Core/IVisualElementController.cs new file mode 100644 index 00000000..03be293a --- /dev/null +++ b/Xamarin.Forms.Core/IVisualElementController.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms +{ + public interface IVisualElementController : IElementController + { + void NativeSizeChanged(); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IWebViewRenderer.cs b/Xamarin.Forms.Core/IWebViewRenderer.cs new file mode 100644 index 00000000..0b1b3db5 --- /dev/null +++ b/Xamarin.Forms.Core/IWebViewRenderer.cs @@ -0,0 +1,8 @@ +namespace Xamarin.Forms +{ + internal interface IWebViewRenderer + { + void LoadHtml(string html, string baseUrl); + void LoadUrl(string url); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IXamlTypeResolver.cs b/Xamarin.Forms.Core/IXamlTypeResolver.cs new file mode 100644 index 00000000..1708e0b1 --- /dev/null +++ b/Xamarin.Forms.Core/IXamlTypeResolver.cs @@ -0,0 +1,10 @@ +using System; + +namespace Xamarin.Forms.Xaml +{ + public interface IXamlTypeResolver + { + Type Resolve(string qualifiedTypeName, IServiceProvider serviceProvider = null); + bool TryResolve(string qualifiedTypeName, out Type type); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/IXmlLineInfoProvider.cs b/Xamarin.Forms.Core/IXmlLineInfoProvider.cs new file mode 100644 index 00000000..348f4d4d --- /dev/null +++ b/Xamarin.Forms.Core/IXmlLineInfoProvider.cs @@ -0,0 +1,9 @@ +using System.Xml; + +namespace Xamarin.Forms.Xaml +{ + public interface IXmlLineInfoProvider + { + IXmlLineInfo XmlLineInfo { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Image.cs b/Xamarin.Forms.Core/Image.cs new file mode 100644 index 00000000..9e329331 --- /dev/null +++ b/Xamarin.Forms.Core/Image.cs @@ -0,0 +1,145 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_ImageRenderer))] + public class Image : View + { + public static readonly BindableProperty SourceProperty = BindableProperty.Create("Source", typeof(ImageSource), typeof(Image), default(ImageSource), propertyChanging: OnSourcePropertyChanging, + propertyChanged: OnSourcePropertyChanged); + + public static readonly BindableProperty AspectProperty = BindableProperty.Create("Aspect", typeof(Aspect), typeof(Image), Aspect.AspectFit); + + public static readonly BindableProperty IsOpaqueProperty = BindableProperty.Create("IsOpaque", typeof(bool), typeof(Image), false); + + internal static readonly BindablePropertyKey IsLoadingPropertyKey = BindableProperty.CreateReadOnly("IsLoading", typeof(bool), typeof(Image), default(bool)); + + public static readonly BindableProperty IsLoadingProperty = IsLoadingPropertyKey.BindableProperty; + + public Aspect Aspect + { + get { return (Aspect)GetValue(AspectProperty); } + set { SetValue(AspectProperty, value); } + } + + public bool IsLoading + { + get { return (bool)GetValue(IsLoadingProperty); } + } + + public bool IsOpaque + { + get { return (bool)GetValue(IsOpaqueProperty); } + set { SetValue(IsOpaqueProperty, value); } + } + + [TypeConverter(typeof(ImageSourceConverter))] + public ImageSource Source + { + get { return (ImageSource)GetValue(SourceProperty); } + set { SetValue(SourceProperty, value); } + } + + protected override void OnBindingContextChanged() + { + if (Source != null) + SetInheritedBindingContext(Source, BindingContext); + + base.OnBindingContextChanged(); + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + SizeRequest desiredSize = base.OnSizeRequest(double.PositiveInfinity, double.PositiveInfinity); + + double desiredAspect = desiredSize.Request.Width / desiredSize.Request.Height; + double constraintAspect = widthConstraint / heightConstraint; + + double desiredWidth = desiredSize.Request.Width; + double desiredHeight = desiredSize.Request.Height; + + if (desiredWidth == 0 || desiredHeight == 0) + return new SizeRequest(new Size(0, 0)); + + double width = desiredWidth; + double height = desiredHeight; + if (constraintAspect > desiredAspect) + { + // constraint area is proportionally wider than image + switch (Aspect) + { + case Aspect.AspectFit: + case Aspect.AspectFill: + height = Math.Min(desiredHeight, heightConstraint); + width = desiredWidth * (height / desiredHeight); + break; + case Aspect.Fill: + width = Math.Min(desiredWidth, widthConstraint); + height = desiredHeight * (width / desiredWidth); + break; + } + } + else if (constraintAspect < desiredAspect) + { + // constraint area is proportionally taller than image + switch (Aspect) + { + case Aspect.AspectFit: + case Aspect.AspectFill: + width = Math.Min(desiredWidth, widthConstraint); + height = desiredHeight * (width / desiredWidth); + break; + case Aspect.Fill: + height = Math.Min(desiredHeight, heightConstraint); + width = desiredWidth * (height / desiredHeight); + break; + } + } + else + { + // constraint area is same aspect as image + width = Math.Min(desiredWidth, widthConstraint); + height = desiredHeight * (width / desiredWidth); + } + + return new SizeRequest(new Size(width, height)); + } + + void OnSourceChanged(object sender, EventArgs eventArgs) + { + OnPropertyChanged(SourceProperty.PropertyName); + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + static void OnSourcePropertyChanged(BindableObject bindable, object oldvalue, object newvalue) + { + ((Image)bindable).OnSourcePropertyChanged((ImageSource)oldvalue, (ImageSource)newvalue); + } + + void OnSourcePropertyChanged(ImageSource oldvalue, ImageSource newvalue) + { + if (newvalue != null) + { + newvalue.SourceChanged += OnSourceChanged; + SetInheritedBindingContext(newvalue, BindingContext); + } + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + static void OnSourcePropertyChanging(BindableObject bindable, object oldvalue, object newvalue) + { + ((Image)bindable).OnSourcePropertyChanging((ImageSource)oldvalue, (ImageSource)newvalue); + } + + async void OnSourcePropertyChanging(ImageSource oldvalue, ImageSource newvalue) + { + if (oldvalue == null) + return; + + oldvalue.SourceChanged -= OnSourceChanged; + await oldvalue.Cancel(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ImageSource.cs b/Xamarin.Forms.Core/ImageSource.cs new file mode 100644 index 00000000..71e75952 --- /dev/null +++ b/Xamarin.Forms.Core/ImageSource.cs @@ -0,0 +1,142 @@ +using System; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + [TypeConverter(typeof(ImageSourceConverter))] + public abstract class ImageSource : Element + { + readonly object _synchandle = new object(); + CancellationTokenSource _cancellationTokenSource; + + TaskCompletionSource<bool> _completionSource; + + internal ImageSource() + { + } + + protected CancellationTokenSource CancellationTokenSource + { + get { return _cancellationTokenSource; } + private set + { + if (_cancellationTokenSource == value) + return; + if (_cancellationTokenSource != null) + _cancellationTokenSource.Cancel(); + _cancellationTokenSource = value; + } + } + + bool IsLoading + { + get { return _cancellationTokenSource != null; } + } + + public virtual Task<bool> Cancel() + { + if (!IsLoading) + return Task.FromResult(false); + + var tcs = new TaskCompletionSource<bool>(); + TaskCompletionSource<bool> original = Interlocked.CompareExchange(ref _completionSource, tcs, null); + if (original == null) + { + _cancellationTokenSource.Cancel(); + } + else + tcs = original; + + return tcs.Task; + } + + public static ImageSource FromFile(string file) + { + return new FileImageSource { File = file }; + } + + public static ImageSource FromResource(string resource, Type resolvingType) + { + return FromResource(resource, resolvingType.GetTypeInfo().Assembly); + } + + public static ImageSource FromResource(string resource, Assembly sourceAssembly = null) + { + if (sourceAssembly == null) + { + MethodInfo callingAssemblyMethod = typeof(Assembly).GetTypeInfo().GetDeclaredMethod("GetCallingAssembly"); + if (callingAssemblyMethod != null) + { + sourceAssembly = (Assembly)callingAssemblyMethod.Invoke(null, new object[0]); + } + else + { + Log.Warning("Warning", "Can not find CallingAssembly, pass resolvingType to FromResource to ensure proper resolution"); + return null; + } + } + + return FromStream(() => sourceAssembly.GetManifestResourceStream(resource)); + } + + public static ImageSource FromStream(Func<Stream> stream) + { + return new StreamImageSource { Stream = token => Task.Run(stream, token) }; + } + + public static ImageSource FromUri(Uri uri) + { + if (!uri.IsAbsoluteUri) + throw new ArgumentException("uri is relative"); + return new UriImageSource { Uri = uri }; + } + + public static implicit operator ImageSource(string source) + { + Uri uri; + return Uri.TryCreate(source, UriKind.Absolute, out uri) && uri.Scheme != "file" ? FromUri(uri) : FromFile(source); + } + + public static implicit operator ImageSource(Uri uri) + { + if (!uri.IsAbsoluteUri) + throw new ArgumentException("uri is relative"); + return FromUri(uri); + } + + protected void OnLoadingCompleted(bool cancelled) + { + if (!IsLoading || _completionSource == null) + return; + + TaskCompletionSource<bool> tcs = Interlocked.Exchange(ref _completionSource, null); + if (tcs != null) + tcs.SetResult(cancelled); + + lock(_synchandle) + { + CancellationTokenSource = null; + } + } + + protected void OnLoadingStarted() + { + lock(_synchandle) + { + CancellationTokenSource = new CancellationTokenSource(); + } + } + + protected void OnSourceChanged() + { + EventHandler eh = SourceChanged; + if (eh != null) + eh(this, EventArgs.Empty); + } + + internal event EventHandler SourceChanged; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ImageSourceConverter.cs b/Xamarin.Forms.Core/ImageSourceConverter.cs new file mode 100644 index 00000000..8ca829f7 --- /dev/null +++ b/Xamarin.Forms.Core/ImageSourceConverter.cs @@ -0,0 +1,18 @@ +using System; + +namespace Xamarin.Forms +{ + public sealed class ImageSourceConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + Uri uri; + return Uri.TryCreate(value, UriKind.Absolute, out uri) && uri.Scheme != "file" ? ImageSource.FromUri(uri) : ImageSource.FromFile(value); + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(ImageSource))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/InputView.cs b/Xamarin.Forms.Core/InputView.cs new file mode 100644 index 00000000..58b75c42 --- /dev/null +++ b/Xamarin.Forms.Core/InputView.cs @@ -0,0 +1,18 @@ +namespace Xamarin.Forms +{ + public class InputView : View + { + public static readonly BindableProperty KeyboardProperty = BindableProperty.Create("Keyboard", typeof(Keyboard), typeof(InputView), Keyboard.Default, + coerceValue: (o, v) => (Keyboard)v ?? Keyboard.Default); + + internal InputView() + { + } + + public Keyboard Keyboard + { + get { return (Keyboard)GetValue(KeyboardProperty); } + set { SetValue(KeyboardProperty, value); } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs b/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs new file mode 100644 index 00000000..6aff5147 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Xamarin.Forms +{ + internal class AttachedCollection<T> : ObservableCollection<T>, ICollection<T>, IAttachedObject where T : BindableObject, IAttachedObject + { + readonly List<WeakReference> _associatedObjects = new List<WeakReference>(); + + public AttachedCollection() + { + } + + public AttachedCollection(IEnumerable<T> collection) : base(collection) + { + } + + public AttachedCollection(IList<T> list) : base(list) + { + } + + public void AttachTo(BindableObject bindable) + { + if (bindable == null) + throw new ArgumentNullException("bindable"); + OnAttachedTo(bindable); + } + + public void DetachFrom(BindableObject bindable) + { + OnDetachingFrom(bindable); + } + + protected override void ClearItems() + { + foreach (WeakReference weakbindable in _associatedObjects) + { + foreach (T item in this) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item.DetachFrom(bindable); + } + } + base.ClearItems(); + } + + protected override void InsertItem(int index, T item) + { + base.InsertItem(index, item); + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item.AttachTo(bindable); + } + } + + protected virtual void OnAttachedTo(BindableObject bindable) + { + lock(_associatedObjects) + { + _associatedObjects.Add(new WeakReference(bindable)); + } + foreach (T item in this) + item.AttachTo(bindable); + } + + protected virtual void OnDetachingFrom(BindableObject bindable) + { + foreach (T item in this) + item.DetachFrom(bindable); + lock(_associatedObjects) + { + for (var i = 0; i < _associatedObjects.Count; i++) + { + object target = _associatedObjects[i].Target; + + if (target == null || target == bindable) + { + _associatedObjects.RemoveAt(i); + i--; + } + } + } + } + + protected override void RemoveItem(int index) + { + T item = this[index]; + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item.DetachFrom(bindable); + } + + base.RemoveItem(index); + } + + protected override void SetItem(int index, T item) + { + T old = this[index]; + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + old.DetachFrom(bindable); + } + + base.SetItem(index, item); + + foreach (WeakReference weakbindable in _associatedObjects) + { + var bindable = weakbindable.Target as BindableObject; + if (bindable == null) + continue; + item.AttachTo(bindable); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/Behavior.cs b/Xamarin.Forms.Core/Interactivity/Behavior.cs new file mode 100644 index 00000000..f8689041 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/Behavior.cs @@ -0,0 +1,65 @@ +using System; + +namespace Xamarin.Forms +{ + public abstract class Behavior : BindableObject, IAttachedObject + { + internal Behavior(Type associatedType) + { + if (associatedType == null) + throw new ArgumentNullException("associatedType"); + AssociatedType = associatedType; + } + + protected Type AssociatedType { get; } + + void IAttachedObject.AttachTo(BindableObject bindable) + { + if (bindable == null) + throw new ArgumentNullException("bindable"); + if (!AssociatedType.IsInstanceOfType(bindable)) + throw new InvalidOperationException("bindable not an instance of AssociatedType"); + OnAttachedTo(bindable); + } + + void IAttachedObject.DetachFrom(BindableObject bindable) + { + OnDetachingFrom(bindable); + } + + protected virtual void OnAttachedTo(BindableObject bindable) + { + } + + protected virtual void OnDetachingFrom(BindableObject bindable) + { + } + } + + public abstract class Behavior<T> : Behavior where T : BindableObject + { + protected Behavior() : base(typeof(T)) + { + } + + protected override void OnAttachedTo(BindableObject bindable) + { + base.OnAttachedTo(bindable); + OnAttachedTo((T)bindable); + } + + protected virtual void OnAttachedTo(T bindable) + { + } + + protected override void OnDetachingFrom(BindableObject bindable) + { + OnDetachingFrom((T)bindable); + base.OnDetachingFrom(bindable); + } + + protected virtual void OnDetachingFrom(T bindable) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/BindingCondition.cs b/Xamarin.Forms.Core/Interactivity/BindingCondition.cs new file mode 100644 index 00000000..88b7cf36 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/BindingCondition.cs @@ -0,0 +1,100 @@ +using System; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + public sealed class BindingCondition : Condition, IValueProvider + { + readonly BindableProperty _boundProperty; + + BindingBase _binding; + object _triggerValue; + + public BindingCondition() + { + _boundProperty = BindableProperty.CreateAttached("Bound", typeof(object), typeof(DataTrigger), null, propertyChanged: OnBoundPropertyChanged); + } + + public BindingBase Binding + { + get { return _binding; } + set + { + if (_binding == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Binding once the Trigger has been applied."); + _binding = value; + } + } + + public object Value + { + get { return _triggerValue; } + set + { + if (_triggerValue == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Trigger has been applied."); + _triggerValue = value; + } + } + + internal IServiceProvider ServiceProvider { get; set; } + + internal IValueConverterProvider ValueConverter { get; set; } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + ValueConverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider; + ServiceProvider = serviceProvider; + + return this; + } + + internal override bool GetState(BindableObject bindable) + { + object newValue = bindable.GetValue(_boundProperty); + return EqualsToValue(newValue); + } + + internal override void SetUp(BindableObject bindable) + { + if (Binding != null) + bindable.SetBinding(_boundProperty, Binding.Clone()); + } + + internal override void TearDown(BindableObject bindable) + { + bindable.RemoveBinding(_boundProperty); + bindable.ClearValue(_boundProperty); + } + + bool EqualsToValue(object other) + { + if ((other == Value) || (other != null && other.Equals(Value))) + return true; + + object converted = null; + if (ValueConverter != null) + converted = ValueConverter.Convert(Value, other != null ? other.GetType() : typeof(object), null, ServiceProvider); + else + return false; + + return (other == converted) || (other != null && other.Equals(converted)); + } + + void OnBoundPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + bool oldState = EqualsToValue(oldValue); + bool newState = EqualsToValue(newValue); + + if (newState == oldState) + return; + + if (ConditionChanged != null) + ConditionChanged(bindable, oldState, newState); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/Condition.cs b/Xamarin.Forms.Core/Interactivity/Condition.cs new file mode 100644 index 00000000..aad921cf --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/Condition.cs @@ -0,0 +1,51 @@ +using System; + +namespace Xamarin.Forms +{ + public abstract class Condition + { + Action<BindableObject, bool, bool> _conditionChanged; + + bool _isSealed; + + internal Condition() + { + } + + internal Action<BindableObject, bool, bool> ConditionChanged + { + get { return _conditionChanged; } + set + { + if (_conditionChanged == value) + return; + if (_conditionChanged != null) + throw new InvalidOperationException("The same condition instance can not be reused"); + _conditionChanged = value; + } + } + + internal bool IsSealed + { + get { return _isSealed; } + set + { + if (_isSealed == value) + return; + if (!value) + throw new InvalidOperationException("What is sealed can not be unsealed."); + _isSealed = value; + OnSealed(); + } + } + + internal abstract bool GetState(BindableObject bindable); + + internal virtual void OnSealed() + { + } + + internal abstract void SetUp(BindableObject bindable); + internal abstract void TearDown(BindableObject bindable); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/DataTrigger.cs b/Xamarin.Forms.Core/Interactivity/DataTrigger.cs new file mode 100644 index 00000000..e27ec134 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/DataTrigger.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + [ContentProperty("Setters")] + public sealed class DataTrigger : TriggerBase, IValueProvider + { + public DataTrigger([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) : base(new BindingCondition(), targetType) + { + } + + public BindingBase Binding + { + get { return ((BindingCondition)Condition).Binding; } + set + { + if (((BindingCondition)Condition).Binding == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Binding once the Trigger has been applied."); + OnPropertyChanging(); + ((BindingCondition)Condition).Binding = value; + OnPropertyChanged(); + } + } + + public new IList<Setter> Setters + { + get { return base.Setters; } + } + + public object Value + { + get { return ((BindingCondition)Condition).Value; } + set + { + if (((BindingCondition)Condition).Value == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Trigger has been applied."); + OnPropertyChanging(); + ((BindingCondition)Condition).Value = value; + OnPropertyChanged(); + } + } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider; + (Condition as BindingCondition).ValueConverter = valueconverter; + + return this; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/EventTrigger.cs b/Xamarin.Forms.Core/Interactivity/EventTrigger.cs new file mode 100644 index 00000000..52e221a0 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/EventTrigger.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Xamarin.Forms +{ + [ContentProperty("Actions")] + public sealed class EventTrigger : TriggerBase + { + static readonly MethodInfo s_handlerinfo = typeof(EventTrigger).GetRuntimeMethods().Single(mi => mi.Name == "OnEventTriggered" && mi.IsPublic == false); + readonly List<BindableObject> _associatedObjects = new List<BindableObject>(); + + EventInfo _eventinfo; + + string _eventname; + Delegate _handlerdelegate; + + public EventTrigger() : base(typeof(BindableObject)) + { + Actions = new SealedList<TriggerAction>(); + } + + public IList<TriggerAction> Actions { get; } + + public string Event + { + get { return _eventname; } + set + { + if (_eventname == value) + return; + if (IsSealed) + throw new InvalidOperationException("Event cannot be changed once the Trigger has been applied"); + OnPropertyChanging(); + _eventname = value; + OnPropertyChanged(); + } + } + + internal override void OnAttachedTo(BindableObject bindable) + { + base.OnAttachedTo(bindable); + if (!string.IsNullOrEmpty(Event)) + AttachHandlerTo(bindable); + _associatedObjects.Add(bindable); + } + + internal override void OnDetachingFrom(BindableObject bindable) + { + _associatedObjects.Remove(bindable); + DetachHandlerFrom(bindable); + base.OnDetachingFrom(bindable); + } + + internal override void OnSeal() + { + base.OnSeal(); + ((SealedList<TriggerAction>)Actions).IsReadOnly = true; + } + + void AttachHandlerTo(BindableObject bindable) + { + try + { + _eventinfo = bindable.GetType().GetRuntimeEvent(Event); + _handlerdelegate = s_handlerinfo.CreateDelegate(_eventinfo.EventHandlerType, this); + } + catch (Exception) + { + Log.Warning("EventTrigger", "Can not attach EventTrigger to {0}.{1}. Check if the handler exists and if the signature is right.", bindable.GetType(), Event); + } + if (_eventinfo != null && _handlerdelegate != null) + _eventinfo.AddEventHandler(bindable, _handlerdelegate); + } + + void DetachHandlerFrom(BindableObject bindable) + { + if (_eventinfo != null && _handlerdelegate != null) + _eventinfo.RemoveEventHandler(bindable, _handlerdelegate); + } + + [Preserve] + void OnEventTriggered(object sender, EventArgs e) + { + var bindable = (BindableObject)sender; + foreach (TriggerAction action in Actions) + action.DoInvoke(bindable); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/IAttachedObject.cs b/Xamarin.Forms.Core/Interactivity/IAttachedObject.cs new file mode 100644 index 00000000..09748873 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/IAttachedObject.cs @@ -0,0 +1,8 @@ +namespace Xamarin.Forms +{ + internal interface IAttachedObject + { + void AttachTo(BindableObject bindable); + void DetachFrom(BindableObject bindable); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/MultiCondition.cs b/Xamarin.Forms.Core/Interactivity/MultiCondition.cs new file mode 100644 index 00000000..23ca41c5 --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/MultiCondition.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + internal sealed class MultiCondition : Condition + { + readonly BindableProperty _aggregatedStateProperty; + + public MultiCondition() + { + _aggregatedStateProperty = BindableProperty.CreateAttached("AggregatedState", typeof(bool), typeof(DataTrigger), false, propertyChanged: OnAggregatedStatePropertyChanged); + Conditions = new TriggerBase.SealedList<Condition>(); + } + + public IList<Condition> Conditions { get; } + + internal override bool GetState(BindableObject bindable) + { + return (bool)bindable.GetValue(_aggregatedStateProperty); + } + + internal override void OnSealed() + { + ((TriggerBase.SealedList<Condition>)Conditions).IsReadOnly = true; + foreach (Condition condition in Conditions) + condition.ConditionChanged = OnConditionChanged; + } + + internal override void SetUp(BindableObject bindable) + { + foreach (Condition condition in Conditions) + condition.SetUp(bindable); + } + + internal override void TearDown(BindableObject bindable) + { + foreach (Condition condition in Conditions) + condition.TearDown(bindable); + } + + void OnAggregatedStatePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if ((bool)oldValue == (bool)newValue) + return; + + if (ConditionChanged != null) + ConditionChanged(bindable, (bool)oldValue, (bool)newValue); + } + + void OnConditionChanged(BindableObject bindable, bool oldValue, bool newValue) + { + var oldState = (bool)bindable.GetValue(_aggregatedStateProperty); + var newState = true; + foreach (Condition condition in Conditions) + { + if (!condition.GetState(bindable)) + { + newState = false; + break; + } + } + if (newState != oldState) + bindable.SetValue(_aggregatedStateProperty, newState); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/MultiTrigger.cs b/Xamarin.Forms.Core/Interactivity/MultiTrigger.cs new file mode 100644 index 00000000..3c85467c --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/MultiTrigger.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + [ContentProperty("Setters")] + public sealed class MultiTrigger : TriggerBase + { + public MultiTrigger([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) : base(new MultiCondition(), targetType) + { + } + + public IList<Condition> Conditions + { + get { return ((MultiCondition)Condition).Conditions; } + } + + public new IList<Setter> Setters + { + get { return base.Setters; } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/PropertyCondition.cs b/Xamarin.Forms.Core/Interactivity/PropertyCondition.cs new file mode 100644 index 00000000..be37d48f --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/PropertyCondition.cs @@ -0,0 +1,100 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + public sealed class PropertyCondition : Condition, IValueProvider + { + readonly BindableProperty _stateProperty; + + BindableProperty _property; + object _triggerValue; + + public PropertyCondition() + { + _stateProperty = BindableProperty.CreateAttached("State", typeof(bool), typeof(DataTrigger), false, propertyChanged: OnStatePropertyChanged); + } + + public BindableProperty Property + { + get { return _property; } + set + { + if (_property == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Property once the Trigger has been applied."); + _property = value; + } + } + + public object Value + { + get { return _triggerValue; } + set + { + if (_triggerValue == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Trigger has been applied."); + _triggerValue = value; + } + } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider; + Func<MemberInfo> minforetriever = () => Property.DeclaringType.GetRuntimeProperty(Property.PropertyName); + + object value = valueconverter.Convert(Value, Property.ReturnType, minforetriever, serviceProvider); + Value = value; + return this; + } + + internal override bool GetState(BindableObject bindable) + { + return (bool)bindable.GetValue(_stateProperty); + } + + internal override void SetUp(BindableObject bindable) + { + object newvalue = bindable.GetValue(Property); + + bool newState = (newvalue == Value) || (newvalue != null && newvalue.Equals(Value)); + bindable.SetValue(_stateProperty, newState); + bindable.PropertyChanged += OnAttachedObjectPropertyChanged; + } + + internal override void TearDown(BindableObject bindable) + { + bindable.ClearValue(_stateProperty); + bindable.PropertyChanged -= OnAttachedObjectPropertyChanged; + } + + void OnAttachedObjectPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var bindable = (BindableObject)sender; + var oldState = (bool)bindable.GetValue(_stateProperty); + + if (Property == null) + return; + if (e.PropertyName != Property.PropertyName) + return; + object newvalue = bindable.GetValue(Property); + bool newstate = (newvalue == Value) || (newvalue != null && newvalue.Equals(Value)); + if (oldState != newstate) + bindable.SetValue(_stateProperty, newstate); + } + + void OnStatePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if ((bool)oldValue == (bool)newValue) + return; + + if (ConditionChanged != null) + ConditionChanged(bindable, (bool)oldValue, (bool)newValue); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/Trigger.cs b/Xamarin.Forms.Core/Interactivity/Trigger.cs new file mode 100644 index 00000000..ea3dc5ae --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/Trigger.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + [ContentProperty("Setters")] + public sealed class Trigger : TriggerBase, IValueProvider + { + public Trigger([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) : base(new PropertyCondition(), targetType) + { + } + + public BindableProperty Property + { + get { return ((PropertyCondition)Condition).Property; } + set + { + if (((PropertyCondition)Condition).Property == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Property once the Trigger has been applied."); + OnPropertyChanging(); + ((PropertyCondition)Condition).Property = value; + OnPropertyChanged(); + } + } + + public new IList<Setter> Setters + { + get { return base.Setters; } + } + + public object Value + { + get { return ((PropertyCondition)Condition).Value; } + set + { + if (((PropertyCondition)Condition).Value == value) + return; + if (IsSealed) + throw new InvalidOperationException("Can not change Value once the Trigger has been applied."); + OnPropertyChanging(); + ((PropertyCondition)Condition).Value = value; + OnPropertyChanged(); + } + } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider; + Func<MemberInfo> minforetriever = () => Property.DeclaringType.GetRuntimeProperty(Property.PropertyName); + + object value = valueconverter.Convert(Value, Property.ReturnType, minforetriever, serviceProvider); + Value = value; + return this; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/TriggerAction.cs b/Xamarin.Forms.Core/Interactivity/TriggerAction.cs new file mode 100644 index 00000000..bb9dc08f --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/TriggerAction.cs @@ -0,0 +1,37 @@ +using System; + +namespace Xamarin.Forms +{ + public abstract class TriggerAction + { + internal TriggerAction(Type associatedType) + { + if (associatedType == null) + throw new ArgumentNullException("associatedType"); + AssociatedType = associatedType; + } + + protected Type AssociatedType { get; private set; } + + protected abstract void Invoke(object sender); + + internal virtual void DoInvoke(object sender) + { + Invoke(sender); + } + } + + public abstract class TriggerAction<T> : TriggerAction where T : BindableObject + { + protected TriggerAction() : base(typeof(T)) + { + } + + protected override void Invoke(object sender) + { + Invoke((T)sender); + } + + protected abstract void Invoke(T sender); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Interactivity/TriggerBase.cs b/Xamarin.Forms.Core/Interactivity/TriggerBase.cs new file mode 100644 index 00000000..9418c7ae --- /dev/null +++ b/Xamarin.Forms.Core/Interactivity/TriggerBase.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + public abstract class TriggerBase : BindableObject, IAttachedObject + { + bool _isSealed; + + internal TriggerBase(Type targetType) + { + if (targetType == null) + throw new ArgumentNullException("targetType"); + TargetType = targetType; + + EnterActions = new SealedList<TriggerAction>(); + ExitActions = new SealedList<TriggerAction>(); + } + + internal TriggerBase(Condition condition, Type targetType) : this(targetType) + { + Setters = new SealedList<Setter>(); + Condition = condition; + Condition.ConditionChanged = OnConditionChanged; + } + + public IList<TriggerAction> EnterActions { get; } + + public IList<TriggerAction> ExitActions { get; } + + public bool IsSealed + { + get { return _isSealed; } + private set + { + if (_isSealed == value) + return; + if (!value) + throw new InvalidOperationException("What is sealed can not be unsealed."); + _isSealed = value; + OnSeal(); + } + } + + public Type TargetType { get; } + + internal Condition Condition { get; } + + //Setters and Condition are used by Trigger, DataTrigger and MultiTrigger + internal IList<Setter> Setters { get; } + + void IAttachedObject.AttachTo(BindableObject bindable) + { + IsSealed = true; + + if (bindable == null) + throw new ArgumentNullException("bindable"); + if (!TargetType.IsInstanceOfType(bindable)) + throw new InvalidOperationException("bindable not an instance of AssociatedType"); + OnAttachedTo(bindable); + } + + void IAttachedObject.DetachFrom(BindableObject bindable) + { + if (bindable == null) + throw new ArgumentNullException("bindable"); + OnDetachingFrom(bindable); + } + + internal virtual void OnAttachedTo(BindableObject bindable) + { + if (Condition != null) + Condition.SetUp(bindable); + } + + internal virtual void OnDetachingFrom(BindableObject bindable) + { + if (Condition != null) + Condition.TearDown(bindable); + } + + internal virtual void OnSeal() + { + ((SealedList<TriggerAction>)EnterActions).IsReadOnly = true; + ((SealedList<TriggerAction>)ExitActions).IsReadOnly = true; + if (Setters != null) + ((SealedList<Setter>)Setters).IsReadOnly = true; + if (Condition != null) + Condition.IsSealed = true; + } + + void OnConditionChanged(BindableObject bindable, bool oldValue, bool newValue) + { + if (newValue) + { + foreach (TriggerAction action in EnterActions) + action.DoInvoke(bindable); + foreach (Setter setter in Setters) + setter.Apply(bindable); + } + else + { + foreach (Setter setter in Setters) + setter.UnApply(bindable); + foreach (TriggerAction action in ExitActions) + action.DoInvoke(bindable); + } + } + + internal class SealedList<T> : IList<T> + { + readonly IList<T> _actual; + + bool _isReadOnly; + + public SealedList() + { + _actual = new List<T>(); + } + + public void Add(T item) + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + _actual.Add(item); + } + + public void Clear() + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + _actual.Clear(); + } + + public bool Contains(T item) + { + return _actual.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _actual.CopyTo(array, arrayIndex); + } + + public int Count + { + get { return _actual.Count; } + } + + public bool IsReadOnly + { + get { return _isReadOnly; } + set + { + if (_isReadOnly == value) + return; + if (!value) + throw new InvalidOperationException("Can't change this back to non readonly"); + _isReadOnly = value; + } + } + + public bool Remove(T item) + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + return _actual.Remove(item); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_actual).GetEnumerator(); + } + + public IEnumerator<T> GetEnumerator() + { + return _actual.GetEnumerator(); + } + + public int IndexOf(T item) + { + return _actual.IndexOf(item); + } + + public void Insert(int index, T item) + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + _actual.Insert(index, item); + } + + public T this[int index] + { + get { return _actual[index]; } + set + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + _actual[index] = value; + } + } + + public void RemoveAt(int index) + { + if (IsReadOnly) + throw new InvalidOperationException("This list is ReadOnly"); + _actual.RemoveAt(index); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Internals/DynamicResource.cs b/Xamarin.Forms.Core/Internals/DynamicResource.cs new file mode 100644 index 00000000..3724daec --- /dev/null +++ b/Xamarin.Forms.Core/Internals/DynamicResource.cs @@ -0,0 +1,12 @@ +namespace Xamarin.Forms.Internals +{ + public class DynamicResource + { + public DynamicResource(string key) + { + Key = key; + } + + public string Key { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Internals/IDataTemplate.cs b/Xamarin.Forms.Core/Internals/IDataTemplate.cs new file mode 100644 index 00000000..d6947ae8 --- /dev/null +++ b/Xamarin.Forms.Core/Internals/IDataTemplate.cs @@ -0,0 +1,10 @@ +using System; + +namespace Xamarin.Forms.Internals +{ + [Obsolete] + public interface IDataTemplate + { + Func<object> LoadTemplate { get; set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Internals/IDynamicResourceHandler.cs b/Xamarin.Forms.Core/Internals/IDynamicResourceHandler.cs new file mode 100644 index 00000000..1885d8ab --- /dev/null +++ b/Xamarin.Forms.Core/Internals/IDynamicResourceHandler.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms.Internals +{ + public interface IDynamicResourceHandler + { + void SetDynamicResource(BindableProperty property, string key); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Internals/INameScope.cs b/Xamarin.Forms.Core/Internals/INameScope.cs new file mode 100644 index 00000000..ca520605 --- /dev/null +++ b/Xamarin.Forms.Core/Internals/INameScope.cs @@ -0,0 +1,12 @@ +using System.Xml; + +namespace Xamarin.Forms.Internals +{ + public interface INameScope + { + object FindByName(string name); + void RegisterName(string name, object scopedElement); + void RegisterName(string name, object scopedElement, IXmlLineInfo xmlLineInfo); + void UnregisterName(string name); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Internals/NameScope.cs b/Xamarin.Forms.Core/Internals/NameScope.cs new file mode 100644 index 00000000..b29534b6 --- /dev/null +++ b/Xamarin.Forms.Core/Internals/NameScope.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Internals +{ + public class NameScope : INameScope + { + public static readonly BindableProperty NameScopeProperty = BindableProperty.CreateAttached("NameScope", typeof(INameScope), typeof(NameScope), default(INameScope)); + + readonly Dictionary<string, object> _names = new Dictionary<string, object>(); + + object INameScope.FindByName(string name) + { + if (_names.ContainsKey(name)) + return _names[name]; + return null; + } + + void INameScope.RegisterName(string name, object scopedElement) + { + if (_names.ContainsKey(name)) + throw new ArgumentException("An element with the same key already exists in NameScope", "name"); + + _names[name] = scopedElement; + } + + void INameScope.RegisterName(string name, object scopedElement, IXmlLineInfo xmlLineInfo) + { + try + { + ((INameScope)this).RegisterName(name, scopedElement); + } + catch (ArgumentException) + { + throw new XamlParseException(string.Format("An element with the name \"{0}\" already exists in this NameScope", name), xmlLineInfo); + } + } + + void INameScope.UnregisterName(string name) + { + _names.Remove(name); + } + + public static INameScope GetNameScope(BindableObject bindable) + { + return (INameScope)bindable.GetValue(NameScopeProperty); + } + + public static void SetNameScope(BindableObject bindable, INameScope value) + { + bindable.SetValue(NameScopeProperty, value); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/InvalidNavigationException.cs b/Xamarin.Forms.Core/InvalidNavigationException.cs new file mode 100644 index 00000000..fce5b314 --- /dev/null +++ b/Xamarin.Forms.Core/InvalidNavigationException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Xamarin.Forms +{ + internal class InvalidNavigationException : Exception + { + public InvalidNavigationException(string message) : base(message) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/InvalidationEventArgs.cs b/Xamarin.Forms.Core/InvalidationEventArgs.cs new file mode 100644 index 00000000..d88e2519 --- /dev/null +++ b/Xamarin.Forms.Core/InvalidationEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms +{ + internal class InvalidationEventArgs : EventArgs + { + public InvalidationEventArgs(InvalidationTrigger trigger) + { + Trigger = trigger; + } + + public InvalidationTrigger Trigger { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/InvalidationTrigger.cs b/Xamarin.Forms.Core/InvalidationTrigger.cs new file mode 100644 index 00000000..e7db534e --- /dev/null +++ b/Xamarin.Forms.Core/InvalidationTrigger.cs @@ -0,0 +1,16 @@ +using System; + +namespace Xamarin.Forms +{ + [Flags] + internal enum InvalidationTrigger + { + Undefined = 0, + MeasureChanged = 1 << 0, + HorizontalOptionsChanged = 1 << 1, + VerticalOptionsChanged = 1 << 2, + SizeRequestChanged = 1 << 3, + RendererReady = 1 << 4, + MarginChanged = 1 << 5 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ItemTappedEventArgs.cs b/Xamarin.Forms.Core/ItemTappedEventArgs.cs new file mode 100644 index 00000000..9fde19bd --- /dev/null +++ b/Xamarin.Forms.Core/ItemTappedEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + public class ItemTappedEventArgs : EventArgs + { + public ItemTappedEventArgs(object group, object item) + { + Group = group; + Item = item; + } + + public object Group { get; private set; } + + public object Item { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ItemVisibilityEventArgs.cs b/Xamarin.Forms.Core/ItemVisibilityEventArgs.cs new file mode 100644 index 00000000..48166c13 --- /dev/null +++ b/Xamarin.Forms.Core/ItemVisibilityEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms +{ + public sealed class ItemVisibilityEventArgs : EventArgs + { + public ItemVisibilityEventArgs(object item) + { + Item = item; + } + + public object Item { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ItemsView.cs b/Xamarin.Forms.Core/ItemsView.cs new file mode 100644 index 00000000..51c105bd --- /dev/null +++ b/Xamarin.Forms.Core/ItemsView.cs @@ -0,0 +1,92 @@ +using System.Collections; + +namespace Xamarin.Forms +{ + public abstract class ItemsView<TVisual> : View, IItemsView<TVisual> where TVisual : BindableObject + { + /* + public static readonly BindableProperty InfiniteScrollingProperty = + BindableProperty.Create<ItemsView, bool> (lv => lv.InfiniteScrolling, false); + + public bool InfiniteScrolling + { + get { return (bool) GetValue (InfiniteScrollingProperty); } + set { SetValue (InfiniteScrollingProperty, value); } + }*/ + + public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(ItemsView<TVisual>), null, propertyChanged: OnItemsSourceChanged); + + public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(ItemsView<TVisual>), null, validateValue: ValidateItemTemplate); + + internal ItemsView() + { + TemplatedItems = new TemplatedItemsList<ItemsView<TVisual>, TVisual>(this, ItemsSourceProperty, ItemTemplateProperty); + } + + public IEnumerable ItemsSource + { + get { return (IEnumerable)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + public DataTemplate ItemTemplate + { + get { return (DataTemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + + /*public void UpdateNonNotifyingList() + { + this.templatedItems.ForceUpdate(); + }*/ + + internal ListProxy ListProxy + { + get { return TemplatedItems.ListProxy; } + } + + internal TemplatedItemsList<ItemsView<TVisual>, TVisual> TemplatedItems { get; } + + TVisual IItemsView<TVisual>.CreateDefault(object item) + { + return CreateDefault(item); + } + + void IItemsView<TVisual>.SetupContent(TVisual content, int index) + { + SetupContent(content, index); + } + + void IItemsView<TVisual>.UnhookContent(TVisual content) + { + UnhookContent(content); + } + + protected abstract TVisual CreateDefault(object item); + + protected virtual void SetupContent(TVisual content, int index) + { + } + + protected virtual void UnhookContent(TVisual content) + { + } + + static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue) + { + var element = newValue as Element; + if (element == null) + return; + element.Parent = (Element)bindable; + } + + static bool ValidateItemTemplate(BindableObject b, object v) + { + var lv = b as ListView; + if (lv == null) + return true; + + return !(lv.CachingStrategy == ListViewCachingStrategy.RetainElement && lv.ItemTemplate is DataTemplateSelector); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ItemsViewSimple.cs b/Xamarin.Forms.Core/ItemsViewSimple.cs new file mode 100644 index 00000000..fd631838 --- /dev/null +++ b/Xamarin.Forms.Core/ItemsViewSimple.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Xamarin.Forms +{ + public abstract class ItemsView : View, IItemViewController + { + public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(ItemsView), Enumerable.Empty<object>()); + + public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(ItemsView)); + + ItemSource _itemSource; + + internal ItemsView() + { + } + + public int Count => _itemSource.Count; + + public IEnumerable ItemsSource + { + get { return (IEnumerable)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + public DataTemplate ItemTemplate + { + get { return (DataTemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + + void IItemViewController.BindView(View view, object item) + { + view.BindingContext = item; + } + + View IItemViewController.CreateView(object type) + { + var dataTemplate = (DataTemplate)type; + object content = dataTemplate.CreateContent(); + var view = (View)content; + view.Parent = this; + return view; + } + + object IItemViewController.GetItem(int index) => _itemSource[index]; + + object IItemViewController.GetItemType(object item) + { + DataTemplate dataTemplate = ItemTemplate; + var dataTemplateSelector = dataTemplate as DataTemplateSelector; + if (dataTemplateSelector != null) + dataTemplate = dataTemplateSelector.SelectTemplate(item, this); + + if (item == null) + throw new ArgumentException($"No DataTemplate resolved for item: {item}."); + + return dataTemplate; + } + + protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + if (propertyName == nameof(ItemsSource)) + { + // abstract enumerable, IList, IList<T>, and IReadOnlyList<T> + _itemSource = new ItemSource(ItemsSource); + + // subscribe to collection changed events + var dynamicItemSource = _itemSource as INotifyCollectionChanged; + if (dynamicItemSource != null) + { + new WeakNotifyCollectionChanged(dynamicItemSource, OnCollectionChange); + } + } + + base.OnPropertyChanged(propertyName); + } + + internal event NotifyCollectionChangedEventHandler CollectionChanged; + + void OnCollectionChange(object sender, NotifyCollectionChangedEventArgs e) + { + CollectionChanged?.Invoke(sender, e); + } + + sealed class WeakNotifyCollectionChanged + { + readonly INotifyCollectionChanged _source; + // prevent the itemSource from keeping the itemsView alive + readonly WeakReference<NotifyCollectionChangedEventHandler> _weakTarget; + + public WeakNotifyCollectionChanged(INotifyCollectionChanged source, NotifyCollectionChangedEventHandler target) + { + _weakTarget = new WeakReference<NotifyCollectionChangedEventHandler>(target); + _source = source; + _source.CollectionChanged += OnCollectionChanged; + } + + public void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + NotifyCollectionChangedEventHandler weakTarget; + if (!_weakTarget.TryGetTarget(out weakTarget)) + { + _source.CollectionChanged -= OnCollectionChanged; + return; + } + + weakTarget(sender, e); + } + } + + sealed class ItemSource : IEnumerable<object>, INotifyCollectionChanged + { + IndexableCollection _indexable; + + internal ItemSource(IEnumerable enumerable) + { + _indexable = new IndexableCollection(enumerable); + var dynamicItemSource = enumerable as INotifyCollectionChanged; + if (dynamicItemSource != null) + dynamicItemSource.CollectionChanged += OnCollectionChanged; + } + + public int Count => _indexable.Count; + + public IEnumerable Enumerable => _indexable.Enumerable; + + public object this[int index] + { + get + { + // madness ported from listProxy + CollectionSynchronizationContext syncContext = SyncContext; + if (syncContext != null) + { + object value = null; + syncContext.Callback(Enumerable, SyncContext.Context, () => value = _indexable[index], false); + + return value; + } + + return _indexable[index]; + } + } + + CollectionSynchronizationContext SyncContext + { + get + { + CollectionSynchronizationContext syncContext; + BindingBase.TryGetSynchronizedCollection(Enumerable, out syncContext); + return syncContext; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator<object> IEnumerable<object>.GetEnumerator() + { + return GetEnumerator(); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public int IndexOf(object item) + { + // madness ported from listProxy + CollectionSynchronizationContext syncContext = SyncContext; + if (syncContext != null) + { + int value = -1; + syncContext.Callback(Enumerable, SyncContext.Context, () => value = _indexable.IndexOf(item), false); + + return value; + } + + return _indexable.IndexOf(item); + } + + void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + Action onCollectionChanged = () => + { + if (CollectionChanged != null) + CollectionChanged(this, e); + }; + + // madness ported from listProxy + CollectionSynchronizationContext syncContext = SyncContext; + if (syncContext != null) + { + syncContext.Callback(Enumerable, syncContext.Context, () => Device.BeginInvokeOnMainThread(onCollectionChanged), false); + } + + else if (Device.IsInvokeRequired) + Device.BeginInvokeOnMainThread(onCollectionChanged); + + else + onCollectionChanged(); + } + + internal struct Enumerator : IEnumerator<object> + { + readonly ItemSource _itemSource; + int _index; + + internal Enumerator(ItemSource itemSource) : this() + { + _itemSource = itemSource; + } + + public bool MoveNext() + { + if (_index == _itemSource.Count) + return false; + + Current = _itemSource[_index++]; + return true; + } + + public object Current { get; private set; } + + public void Reset() + { + Current = null; + _index = 0; + } + + public void Dispose() + { + } + } + + struct IndexableCollection : IEnumerable<object> + { + internal IndexableCollection(IEnumerable list) + { + Enumerable = list; + + if (list is IList) + return; + + if (list is IList<object>) + return; + + if (list is IReadOnlyList<object>) + return; + + Enumerable = list.Cast<object>().ToArray(); + } + + internal IEnumerable Enumerable { get; } + + internal int Count + { + get + { + var list = Enumerable as IList; + if (list != null) + return list.Count; + + var listOf = Enumerable as IList<object>; + if (listOf != null) + return listOf.Count; + + var readOnlyList = (IReadOnlyList<object>)Enumerable; + return readOnlyList.Count; + } + } + + internal object this[int index] + { + get + { + var list = Enumerable as IList; + if (list != null) + return list[index]; + + var listOf = Enumerable as IList<object>; + if (listOf != null) + return listOf[index]; + + var readOnlyList = (IReadOnlyList<object>)Enumerable; + return readOnlyList[index]; + } + } + + internal int IndexOf(object item) + { + var list = Enumerable as IList; + if (list != null) + return list.IndexOf(item); + + var listOf = Enumerable as IList<object>; + if (listOf != null) + return listOf.IndexOf(item); + + var readOnlyList = (IReadOnlyList<object>)Enumerable; + return readOnlyList.IndexOf(item); + } + + public IEnumerator<object> GetEnumerator() + { + var list = Enumerable as IList; + if (list != null) + return list.Cast<object>().GetEnumerator(); + + var listOf = Enumerable as IList<object>; + if (listOf != null) + return listOf.GetEnumerator(); + + var readOnlyList = (IReadOnlyList<object>)Enumerable; + return readOnlyList.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Keyboard.cs b/Xamarin.Forms.Core/Keyboard.cs new file mode 100644 index 00000000..56d5ca8c --- /dev/null +++ b/Xamarin.Forms.Core/Keyboard.cs @@ -0,0 +1,64 @@ +namespace Xamarin.Forms +{ + [TypeConverter(typeof(KeyboardTypeConverter))] + public class Keyboard + { + static Keyboard s_def; + + static Keyboard s_email; + + static Keyboard s_text; + + static Keyboard s_url; + + static Keyboard s_numeric; + + static Keyboard s_telephone; + + static Keyboard s_chat; + + internal Keyboard() + { + } + + public static Keyboard Chat + { + get { return s_chat ?? (s_chat = new ChatKeyboard()); } + } + + public static Keyboard Default + { + get { return s_def ?? (s_def = new Keyboard()); } + } + + public static Keyboard Email + { + get { return s_email ?? (s_email = new EmailKeyboard()); } + } + + public static Keyboard Numeric + { + get { return s_numeric ?? (s_numeric = new NumericKeyboard()); } + } + + public static Keyboard Telephone + { + get { return s_telephone ?? (s_telephone = new TelephoneKeyboard()); } + } + + public static Keyboard Text + { + get { return s_text ?? (s_text = new TextKeyboard()); } + } + + public static Keyboard Url + { + get { return s_url ?? (s_url = new UrlKeyboard()); } + } + + public static Keyboard Create(KeyboardFlags flags) + { + return new CustomKeyboard(flags); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/KeyboardFlags.cs b/Xamarin.Forms.Core/KeyboardFlags.cs new file mode 100644 index 00000000..5ac2f1cc --- /dev/null +++ b/Xamarin.Forms.Core/KeyboardFlags.cs @@ -0,0 +1,13 @@ +using System; + +namespace Xamarin.Forms +{ + [Flags] + public enum KeyboardFlags + { + CapitalizeSentence = 1, + Spellcheck = 1 << 1, + Suggestions = 1 << 2, + All = ~0 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/KeyboardTypeConverter.cs b/Xamarin.Forms.Core/KeyboardTypeConverter.cs new file mode 100644 index 00000000..4a93010b --- /dev/null +++ b/Xamarin.Forms.Core/KeyboardTypeConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Xamarin.Forms +{ + public class KeyboardTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + string[] parts = value.Split('.'); + if (parts.Length == 1 || (parts.Length == 2 && parts[0] == "Keyboard")) + { + string keyboard = parts[parts.Length - 1]; + FieldInfo field = typeof(Keyboard).GetFields().FirstOrDefault(fi => fi.IsStatic && fi.Name == keyboard); + if (field != null) + return (Keyboard)field.GetValue(null); + PropertyInfo property = typeof(Keyboard).GetProperties().FirstOrDefault(pi => pi.Name == keyboard && pi.CanRead && pi.GetMethod.IsStatic); + if (property != null) + return (Keyboard)property.GetValue(null, null); + } + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Keyboard))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Label.cs b/Xamarin.Forms.Core/Label.cs new file mode 100644 index 00000000..fed021f8 --- /dev/null +++ b/Xamarin.Forms.Core/Label.cs @@ -0,0 +1,270 @@ +using System; +using System.ComponentModel; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [ContentProperty("Text")] + [RenderWith(typeof(_LabelRenderer))] + public class Label : View, IFontElement + { + public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create("HorizontalTextAlignment", typeof(TextAlignment), typeof(Label), TextAlignment.Start, + propertyChanged: OnHorizontalTextAlignmentPropertyChanged); + + [Obsolete("XAlignProperty is obsolete. Please use HorizontalTextAlignmentProperty instead.")] public static readonly BindableProperty XAlignProperty = HorizontalTextAlignmentProperty; + + public static readonly BindableProperty VerticalTextAlignmentProperty = BindableProperty.Create("VerticalTextAlignment", typeof(TextAlignment), typeof(Label), TextAlignment.Start, + propertyChanged: OnVerticalTextAlignmentPropertyChanged); + + [Obsolete("YAlignProperty is obsolete. Please use VerticalTextAlignmentProperty instead.")] public static readonly BindableProperty YAlignProperty = VerticalTextAlignmentProperty; + + public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(Label), Color.Default); + + public static readonly BindableProperty FontProperty = BindableProperty.Create("Font", typeof(Font), typeof(Label), default(Font), propertyChanged: FontStructPropertyChanged); + + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Label), default(string), propertyChanged: OnTextPropertyChanged); + + public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create("FontFamily", typeof(string), typeof(Label), default(string), propertyChanged: OnFontFamilyChanged); + + public static readonly BindableProperty FontSizeProperty = BindableProperty.Create("FontSize", typeof(double), typeof(Label), -1.0, propertyChanged: OnFontSizeChanged, + defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (Label)bindable)); + + public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create("FontAttributes", typeof(FontAttributes), typeof(Label), FontAttributes.None, + propertyChanged: OnFontAttributesChanged); + + public static readonly BindableProperty FormattedTextProperty = BindableProperty.Create("FormattedText", typeof(FormattedString), typeof(Label), default(FormattedString), + propertyChanging: (bindable, oldvalue, newvalue) => + { + if (oldvalue != null) + ((FormattedString)oldvalue).PropertyChanged -= ((Label)bindable).OnFormattedTextChanged; + }, propertyChanged: (bindable, oldvalue, newvalue) => + { + if (newvalue != null) + ((FormattedString)newvalue).PropertyChanged += ((Label)bindable).OnFormattedTextChanged; + ((Label)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged); + if (newvalue != null) + ((Label)bindable).Text = null; + }); + + public static readonly BindableProperty LineBreakModeProperty = BindableProperty.Create("LineBreakMode", typeof(LineBreakMode), typeof(Label), LineBreakMode.WordWrap, + propertyChanged: (bindable, oldvalue, newvalue) => ((Label)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged)); + + bool _cancelEvents; + + [Obsolete("Please use the Font attributes which are on the class itself. Obsoleted in v1.3.0")] + public Font Font + { + get { return (Font)GetValue(FontProperty); } + set { SetValue(FontProperty, value); } + } + + public FormattedString FormattedText + { + get { return (FormattedString)GetValue(FormattedTextProperty); } + set { SetValue(FormattedTextProperty, value); } + } + + public TextAlignment HorizontalTextAlignment + { + get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); } + set { SetValue(HorizontalTextAlignmentProperty, value); } + } + + public LineBreakMode LineBreakMode + { + get { return (LineBreakMode)GetValue(LineBreakModeProperty); } + set { SetValue(LineBreakModeProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public Color TextColor + { + get { return (Color)GetValue(TextColorProperty); } + set { SetValue(TextColorProperty, value); } + } + + public TextAlignment VerticalTextAlignment + { + get { return (TextAlignment)GetValue(VerticalTextAlignmentProperty); } + set { SetValue(VerticalTextAlignmentProperty, value); } + } + + [Obsolete("XAlign is obsolete. Please use HorizontalTextAlignment instead.")] + public TextAlignment XAlign + { + get { return (TextAlignment)GetValue(XAlignProperty); } + set { SetValue(XAlignProperty, value); } + } + + [Obsolete("YAlign is obsolete. Please use VerticalTextAlignment instead.")] + public TextAlignment YAlign + { + get { return (TextAlignment)GetValue(YAlignProperty); } + set { SetValue(YAlignProperty, value); } + } + + public FontAttributes FontAttributes + { + get { return (FontAttributes)GetValue(FontAttributesProperty); } + set { SetValue(FontAttributesProperty, value); } + } + + public string FontFamily + { + get { return (string)GetValue(FontFamilyProperty); } + set { SetValue(FontFamilyProperty, value); } + } + + [TypeConverter(typeof(FontSizeConverter))] + public double FontSize + { + get { return (double)GetValue(FontSizeProperty); } + set { SetValue(FontSizeProperty, value); } + } + + static void FontStructPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + var label = (Label)bindable; + if (label._cancelEvents) + return; + + label._cancelEvents = true; + + var font = (Font)newValue; + if (font == Font.Default) + { + label.FontFamily = null; + label.FontSize = Device.GetNamedSize(NamedSize.Default, label); + label.FontAttributes = FontAttributes.None; + } + else + { + label.FontFamily = font.FontFamily; + if (font.UseNamedSize) + { + label.FontSize = Device.GetNamedSize(font.NamedSize, label.GetType(), true); + } + else + { + label.FontSize = font.FontSize; + } + label.FontAttributes = font.FontAttributes; + } + + label._cancelEvents = false; + + label.InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + static void OnFontAttributesChanged(BindableObject bindable, object oldValue, object newValue) + { + var label = (Label)bindable; + + if (label._cancelEvents) + return; + + label._cancelEvents = true; + + var attributes = (FontAttributes)newValue; + + object[] values = label.GetValues(FontFamilyProperty, FontSizeProperty); + var family = (string)values[0]; + if (family != null) + { + label.Font = Font.OfSize(family, (double)values[1]).WithAttributes(attributes); + } + else + { + label.Font = Font.SystemFontOfSize((double)values[1], attributes); + } + + label._cancelEvents = false; + + label.InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + static void OnFontFamilyChanged(BindableObject bindable, object oldValue, object newValue) + { + var label = (Label)bindable; + if (label._cancelEvents) + return; + + label._cancelEvents = true; + + object[] values = label.GetValues(FontSizeProperty, FontAttributesProperty); + + var family = (string)newValue; + if (family != null) + { + label.Font = Font.OfSize(family, (double)values[0]).WithAttributes((FontAttributes)values[1]); + } + else + { + label.Font = Font.SystemFontOfSize((double)values[0], (FontAttributes)values[1]); + } + + label._cancelEvents = false; + label.InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + static void OnFontSizeChanged(BindableObject bindable, object oldValue, object newValue) + { + var label = (Label)bindable; + if (label._cancelEvents) + return; + + label._cancelEvents = true; + + object[] values = label.GetValues(FontFamilyProperty, FontAttributesProperty); + + var size = (double)newValue; + var family = (string)values[0]; + if (family != null) + { + label.Font = Font.OfSize(family, size).WithAttributes((FontAttributes)values[1]); + } + else + { + label.Font = Font.SystemFontOfSize(size, (FontAttributes)values[1]); + } + + label._cancelEvents = false; + + label.InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + void OnFormattedTextChanged(object sender, PropertyChangedEventArgs e) + { + OnPropertyChanged("FormattedText"); + } + + static void OnHorizontalTextAlignmentPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + var label = (Label)bindable; + label.OnPropertyChanged(nameof(XAlign)); + } + + static void OnTextPropertyChanged(BindableObject bindable, object oldvalue, object newvalue) + { + var label = (Label)bindable; + LineBreakMode breakMode = label.LineBreakMode; + bool isVerticallyFixed = (label.Constraint & LayoutConstraint.VerticallyFixed) != 0; + bool isSingleLine = !(breakMode == LineBreakMode.CharacterWrap || breakMode == LineBreakMode.WordWrap); + if (!isVerticallyFixed || !isSingleLine) + ((Label)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged); + if (newvalue != null) + ((Label)bindable).FormattedText = null; + } + + static void OnVerticalTextAlignmentPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + var label = (Label)bindable; + label.OnPropertyChanged(nameof(YAlign)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Layout.cs b/Xamarin.Forms.Core/Layout.cs new file mode 100644 index 00000000..c611777c --- /dev/null +++ b/Xamarin.Forms.Core/Layout.cs @@ -0,0 +1,433 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace Xamarin.Forms +{ + [ContentProperty("Children")] + public abstract class Layout<T> : Layout, IViewContainer<T> where T : View + { + readonly ElementCollection<T> _children; + + protected Layout() + { + _children = new ElementCollection<T>(InternalChildren); + } + + public IList<T> Children + { + get { return _children; } + } + + protected virtual void OnAdded(T view) + { + } + + protected override void OnChildAdded(Element child) + { + base.OnChildAdded(child); + + var typedChild = child as T; + if (typedChild != null) + OnAdded(typedChild); + } + + protected override void OnChildRemoved(Element child) + { + base.OnChildRemoved(child); + + var typedChild = child as T; + if (typedChild != null) + OnRemoved(typedChild); + } + + protected virtual void OnRemoved(T view) + { + } + } + + public abstract class Layout : View, ILayout, ILayoutController + { + public static readonly BindableProperty IsClippedToBoundsProperty = BindableProperty.Create("IsClippedToBounds", typeof(bool), typeof(Layout), false); + + public static readonly BindableProperty PaddingProperty = BindableProperty.Create("Padding", typeof(Thickness), typeof(Layout), default(Thickness), propertyChanged: (bindable, old, newValue) => + { + var layout = (Layout)bindable; + layout.UpdateChildrenLayout(); + }); + + static IList<KeyValuePair<Layout, int>> s_resolutionList = new List<KeyValuePair<Layout, int>>(); + static bool s_relayoutInProgress; + bool _allocatedFlag; + + bool _hasDoneLayout; + Size _lastLayoutSize = new Size(-1, -1); + + ReadOnlyCollection<Element> _logicalChildren; + + protected Layout() + { + InternalChildren.CollectionChanged += InternalChildrenOnCollectionChanged; + } + + public bool IsClippedToBounds + { + get { return (bool)GetValue(IsClippedToBoundsProperty); } + set { SetValue(IsClippedToBoundsProperty, value); } + } + + public Thickness Padding + { + get { return (Thickness)GetValue(PaddingProperty); } + set { SetValue(PaddingProperty, value); } + } + + internal ObservableCollection<Element> InternalChildren { get; } = new ObservableCollection<Element>(); + + internal override ReadOnlyCollection<Element> LogicalChildren + { + get { return _logicalChildren ?? (_logicalChildren = new ReadOnlyCollection<Element>(InternalChildren)); } + } + + public event EventHandler LayoutChanged; + + IReadOnlyList<Element> ILayoutController.Children + { + get { return InternalChildren; } + } + + public void ForceLayout() + { + SizeAllocated(Width, Height); + } + + [Obsolete("Use Measure")] + public sealed override SizeRequest GetSizeRequest(double widthConstraint, double heightConstraint) + { + SizeRequest size = base.GetSizeRequest(widthConstraint - Padding.HorizontalThickness, heightConstraint - Padding.VerticalThickness); + return new SizeRequest(new Size(size.Request.Width + Padding.HorizontalThickness, size.Request.Height + Padding.VerticalThickness), + new Size(size.Minimum.Width + Padding.HorizontalThickness, size.Minimum.Height + Padding.VerticalThickness)); + } + + public static void LayoutChildIntoBoundingRegion(VisualElement child, Rectangle region) + { + var view = child as View; + if (view == null) + { + child.Layout(region); + return; + } + + LayoutOptions horizontalOptions = view.HorizontalOptions; + if (horizontalOptions.Alignment != LayoutAlignment.Fill) + { + SizeRequest request = child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins); + double diff = Math.Max(0, region.Width - request.Request.Width); + region.X += (int)(diff * horizontalOptions.Alignment.ToDouble()); + region.Width -= diff; + } + + LayoutOptions verticalOptions = view.VerticalOptions; + if (verticalOptions.Alignment != LayoutAlignment.Fill) + { + SizeRequest request = child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins); + double diff = Math.Max(0, region.Height - request.Request.Height); + region.Y += (int)(diff * verticalOptions.Alignment.ToDouble()); + region.Height -= diff; + } + + Thickness margin = view.Margin; + region.X += margin.Left; + region.Width -= margin.HorizontalThickness; + region.Y += margin.Top; + region.Height -= margin.VerticalThickness; + + child.Layout(region); + } + + public void LowerChild(View view) + { + if (!InternalChildren.Contains(view) || InternalChildren.First() == view) + return; + + InternalChildren.Move(InternalChildren.IndexOf(view), 0); + OnChildrenReordered(); + } + + public void RaiseChild(View view) + { + if (!InternalChildren.Contains(view) || InternalChildren.Last() == view) + return; + + InternalChildren.Move(InternalChildren.IndexOf(view), InternalChildren.Count - 1); + OnChildrenReordered(); + } + + protected virtual void InvalidateLayout() + { + _hasDoneLayout = false; + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + if (!_hasDoneLayout) + ForceLayout(); + } + + protected abstract void LayoutChildren(double x, double y, double width, double height); + + protected void OnChildMeasureInvalidated(object sender, EventArgs e) + { + InvalidationTrigger trigger = (e as InvalidationEventArgs)?.Trigger ?? InvalidationTrigger.Undefined; + OnChildMeasureInvalidated((VisualElement)sender, trigger); + OnChildMeasureInvalidated(); + } + + protected virtual void OnChildMeasureInvalidated() + { + } + + protected override void OnSizeAllocated(double width, double height) + { + _allocatedFlag = true; + base.OnSizeAllocated(width, height); + UpdateChildrenLayout(); + } + + protected virtual bool ShouldInvalidateOnChildAdded(View child) + { + return true; + } + + protected virtual bool ShouldInvalidateOnChildRemoved(View child) + { + return true; + } + + protected void UpdateChildrenLayout() + { + _hasDoneLayout = true; + + if (!ShouldLayoutChildren()) + return; + + var oldBounds = new Rectangle[LogicalChildren.Count]; + for (var index = 0; index < oldBounds.Length; index++) + { + var c = (VisualElement)LogicalChildren[index]; + oldBounds[index] = c.Bounds; + } + + double width = Width; + double height = Height; + + double x = Padding.Left; + double y = Padding.Top; + double w = Math.Max(0, width - Padding.HorizontalThickness); + double h = Math.Max(0, height - Padding.VerticalThickness); + + LayoutChildren(x, y, w, h); + + for (var i = 0; i < oldBounds.Length; i++) + { + Rectangle oldBound = oldBounds[i]; + Rectangle newBound = ((VisualElement)LogicalChildren[i]).Bounds; + if (oldBound != newBound) + { + EventHandler handler = LayoutChanged; + if (handler != null) + handler(this, EventArgs.Empty); + return; + } + } + + _lastLayoutSize = new Size(width, height); + } + + internal static void LayoutChildIntoBoundingRegion(View child, Rectangle region, SizeRequest childSizeRequest) + { + if (region.Size != childSizeRequest.Request) + { + bool canUseAlreadyDoneRequest = region.Width >= childSizeRequest.Request.Width && region.Height >= childSizeRequest.Request.Height; + + if (child.HorizontalOptions.Alignment != LayoutAlignment.Fill) + { + SizeRequest request = canUseAlreadyDoneRequest ? childSizeRequest : child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins); + double diff = Math.Max(0, region.Width - request.Request.Width); + region.X += (int)(diff * child.HorizontalOptions.Alignment.ToDouble()); + region.Width -= diff; + } + + if (child.VerticalOptions.Alignment != LayoutAlignment.Fill) + { + SizeRequest request = canUseAlreadyDoneRequest ? childSizeRequest : child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins); + double diff = Math.Max(0, region.Height - request.Request.Height); + region.Y += (int)(diff * child.VerticalOptions.Alignment.ToDouble()); + region.Height -= diff; + } + } + + Thickness margin = child.Margin; + region.X += margin.Left; + region.Width -= margin.HorizontalThickness; + region.Y += margin.Top; + region.Height -= margin.VerticalThickness; + + child.Layout(region); + } + + internal virtual void OnChildMeasureInvalidated(VisualElement child, InvalidationTrigger trigger) + { + ReadOnlyCollection<Element> children = LogicalChildren; + int count = children.Count; + for (var index = 0; index < count; index++) + { + var v = LogicalChildren[index] as VisualElement; + if (v != null && v.IsVisible && (!v.IsPlatformEnabled || !v.IsNativeStateConsistent)) + return; + } + + var view = child as View; + if (view != null) + { + // we can ignore the request if we are either fully constrained or when the size request changes and we were already fully constrainted + if ((trigger == InvalidationTrigger.MeasureChanged && view.Constraint == LayoutConstraint.Fixed) || + (trigger == InvalidationTrigger.SizeRequestChanged && view.ComputedConstraint == LayoutConstraint.Fixed)) + { + return; + } + if (trigger == InvalidationTrigger.HorizontalOptionsChanged || trigger == InvalidationTrigger.VerticalOptionsChanged) + { + ComputeConstraintForView(view); + } + } + + _allocatedFlag = false; + if (trigger == InvalidationTrigger.RendererReady) + { + InvalidateMeasure(InvalidationTrigger.RendererReady); + } + else + { + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + s_resolutionList.Add(new KeyValuePair<Layout, int>(this, GetElementDepth(this))); + if (!s_relayoutInProgress) + { + s_relayoutInProgress = true; + Device.BeginInvokeOnMainThread(() => + { + // if thread safety mattered we would need to lock this and compareexchange above + IList<KeyValuePair<Layout, int>> copy = s_resolutionList; + s_resolutionList = new List<KeyValuePair<Layout, int>>(); + s_relayoutInProgress = false; + + foreach (KeyValuePair<Layout, int> kvp in copy.OrderBy(kvp => kvp.Value)) + { + Layout layout = kvp.Key; + double width = layout.Width, height = layout.Height; + if (!layout._allocatedFlag && width >= 0 && height >= 0) + { + layout.SizeAllocated(width, height); + } + } + }); + } + } + + internal override void OnIsVisibleChanged(bool oldValue, bool newValue) + { + base.OnIsVisibleChanged(oldValue, newValue); + if (newValue) + { + if (_lastLayoutSize != new Size(Width, Height)) + { + UpdateChildrenLayout(); + } + } + } + + static int GetElementDepth(Element view) + { + var result = 0; + while (view.Parent != null) + { + result++; + view = view.Parent; + } + return result; + } + + void InternalChildrenOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Move) + { + return; + } + + if (e.OldItems != null) + { + foreach (object item in e.OldItems) + { + var v = item as View; + if (v == null) + continue; + + OnInternalRemoved(v); + } + } + + if (e.NewItems != null) + { + foreach (object item in e.NewItems) + { + var v = item as View; + if (v == null) + continue; + + if (item == this) + throw new InvalidOperationException("Can not add self to own child collection."); + + OnInternalAdded(v); + } + } + } + + void OnInternalAdded(View view) + { + OnChildAdded(view); + if (ShouldInvalidateOnChildAdded(view)) + InvalidateLayout(); + + view.MeasureInvalidated += OnChildMeasureInvalidated; + } + + void OnInternalRemoved(View view) + { + view.MeasureInvalidated -= OnChildMeasureInvalidated; + + OnChildRemoved(view); + if (ShouldInvalidateOnChildRemoved(view)) + InvalidateLayout(); + } + + bool ShouldLayoutChildren() + { + if (!LogicalChildren.Any() || Width <= 0 || Height <= 0 || !IsVisible || !IsNativeStateConsistent || DisableLayout) + return false; + + foreach (Element element in VisibleDescendants()) + { + var visual = element as VisualElement; + if (visual == null || !visual.IsVisible) + continue; + + if (!visual.IsPlatformEnabled || !visual.IsNativeStateConsistent) + { + return false; + } + } + return true; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/LayoutAlignment.cs b/Xamarin.Forms.Core/LayoutAlignment.cs new file mode 100644 index 00000000..04b03e95 --- /dev/null +++ b/Xamarin.Forms.Core/LayoutAlignment.cs @@ -0,0 +1,13 @@ +using System; + +namespace Xamarin.Forms +{ + [Flags] + public enum LayoutAlignment + { + Start = 0, + Center = 1, + End = 2, + Fill = 3 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/LayoutAlignmentExtensions.cs b/Xamarin.Forms.Core/LayoutAlignmentExtensions.cs new file mode 100644 index 00000000..a7efcf39 --- /dev/null +++ b/Xamarin.Forms.Core/LayoutAlignmentExtensions.cs @@ -0,0 +1,21 @@ +using System; + +namespace Xamarin.Forms +{ + internal static class LayoutAlignmentExtensions + { + public static double ToDouble(this LayoutAlignment align) + { + switch (align) + { + case LayoutAlignment.Start: + return 0; + case LayoutAlignment.Center: + return 0.5; + case LayoutAlignment.End: + return 1; + } + throw new ArgumentOutOfRangeException("align"); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/LayoutConstraint.cs b/Xamarin.Forms.Core/LayoutConstraint.cs new file mode 100644 index 00000000..a9ddc8ec --- /dev/null +++ b/Xamarin.Forms.Core/LayoutConstraint.cs @@ -0,0 +1,13 @@ +using System; + +namespace Xamarin.Forms +{ + [Flags] + internal enum LayoutConstraint + { + None = 0, + HorizontallyFixed = 1 << 0, + VerticallyFixed = 1 << 1, + Fixed = HorizontallyFixed | VerticallyFixed + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/LayoutExpandFlag.cs b/Xamarin.Forms.Core/LayoutExpandFlag.cs new file mode 100644 index 00000000..613d0b4a --- /dev/null +++ b/Xamarin.Forms.Core/LayoutExpandFlag.cs @@ -0,0 +1,10 @@ +using System; + +namespace Xamarin.Forms +{ + [Flags] + internal enum LayoutExpandFlag + { + Expand = 4 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/LayoutOptions.cs b/Xamarin.Forms.Core/LayoutOptions.cs new file mode 100644 index 00000000..a3a900b0 --- /dev/null +++ b/Xamarin.Forms.Core/LayoutOptions.cs @@ -0,0 +1,39 @@ +using System; + +namespace Xamarin.Forms +{ + [TypeConverter(typeof(LayoutOptionsConverter))] + public struct LayoutOptions + { + int _flags; + + public static readonly LayoutOptions Start = new LayoutOptions(LayoutAlignment.Start, false); + public static readonly LayoutOptions Center = new LayoutOptions(LayoutAlignment.Center, false); + public static readonly LayoutOptions End = new LayoutOptions(LayoutAlignment.End, false); + public static readonly LayoutOptions Fill = new LayoutOptions(LayoutAlignment.Fill, false); + public static readonly LayoutOptions StartAndExpand = new LayoutOptions(LayoutAlignment.Start, true); + public static readonly LayoutOptions CenterAndExpand = new LayoutOptions(LayoutAlignment.Center, true); + public static readonly LayoutOptions EndAndExpand = new LayoutOptions(LayoutAlignment.End, true); + public static readonly LayoutOptions FillAndExpand = new LayoutOptions(LayoutAlignment.Fill, true); + + public LayoutOptions(LayoutAlignment alignment, bool expands) + { + var a = (int)alignment; + if (a < 0 || a > 3) + throw new ArgumentOutOfRangeException(); + _flags = (int)alignment | (expands ? (int)LayoutExpandFlag.Expand : 0); + } + + public LayoutAlignment Alignment + { + get { return (LayoutAlignment)(_flags & 3); } + set { _flags = (_flags & ~3) | (int)value; } + } + + public bool Expands + { + get { return (_flags & (int)LayoutExpandFlag.Expand) != 0; } + set { _flags = (_flags & 3) | (value ? (int)LayoutExpandFlag.Expand : 0); } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/LayoutOptionsConverter.cs b/Xamarin.Forms.Core/LayoutOptionsConverter.cs new file mode 100644 index 00000000..746e56ce --- /dev/null +++ b/Xamarin.Forms.Core/LayoutOptionsConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Xamarin.Forms +{ + public sealed class LayoutOptionsConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + string[] parts = value.Split('.'); + if (parts.Length > 2 || (parts.Length == 2 && parts[0] != "LayoutOptions")) + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(LayoutOptions))); + value = parts[parts.Length - 1]; + FieldInfo field = typeof(LayoutOptions).GetFields().FirstOrDefault(fi => fi.IsStatic && fi.Name == value); + if (field != null) + return (LayoutOptions)field.GetValue(null); + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(LayoutOptions))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/LineBreakMode.cs b/Xamarin.Forms.Core/LineBreakMode.cs new file mode 100644 index 00000000..c7a36bb8 --- /dev/null +++ b/Xamarin.Forms.Core/LineBreakMode.cs @@ -0,0 +1,12 @@ +namespace Xamarin.Forms +{ + public enum LineBreakMode + { + NoWrap, + WordWrap, + CharacterWrap, + HeadTruncation, + TailTruncation, + MiddleTruncation + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ListProxy.cs b/Xamarin.Forms.Core/ListProxy.cs new file mode 100644 index 00000000..229dcb6b --- /dev/null +++ b/Xamarin.Forms.Core/ListProxy.cs @@ -0,0 +1,488 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; + +namespace Xamarin.Forms +{ + internal sealed class ListProxy : IReadOnlyList<object>, IList, INotifyCollectionChanged + { + readonly ICollection _collection; + readonly IList _list; + readonly int _windowSize; + + IEnumerator _enumerator; + int _enumeratorIndex; + + bool _finished; + HashSet<int> _indexesCounted; + + Dictionary<int, object> _items; + int _version; + + int _windowIndex; + + internal ListProxy(IEnumerable enumerable, int windowSize = int.MaxValue) + { + _windowSize = windowSize; + + ProxiedEnumerable = enumerable; + _collection = enumerable as ICollection; + + if (_collection == null && enumerable is IReadOnlyCollection<object>) + _collection = new ReadOnlyListAdapter((IReadOnlyCollection<object>)enumerable); + + _list = enumerable as IList; + if (_list == null && enumerable is IReadOnlyList<object>) + _list = new ReadOnlyListAdapter((IReadOnlyList<object>)enumerable); + + var changed = enumerable as INotifyCollectionChanged; + if (changed != null) + new WeakNotifyProxy(this, changed); + } + + public IEnumerable ProxiedEnumerable { get; } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator<object> GetEnumerator() + { + return new ProxyEnumerator(this); + } + + /// <summary> + /// Gets whether or not the current window contains the <paramref name="item" />. + /// </summary> + /// <param name="item">The item to search for.</param> + /// <returns><c>true</c> if the item was found in a list or the current window, <c>false</c> otherwise.</returns> + public bool Contains(object item) + { + if (_list != null) + return _list.Contains(item); + + EnsureWindowCreated(); + + if (_items != null) + return _items.Values.Contains(item); + + return false; + } + + /// <summary> + /// Gets the index for the <paramref name="item" /> if in a list or the current window. + /// </summary> + /// <param name="item">The item to search for.</param> + /// <returns>The index of the item if in a list or the current window, -1 otherwise.</returns> + public int IndexOf(object item) + { + if (_list != null) + return _list.IndexOf(item); + + EnsureWindowCreated(); + + if (_items != null) + { + foreach (KeyValuePair<int, object> kvp in _items) + { + if (Equals(kvp.Value, item)) + return kvp.Key; + } + } + + return -1; + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public int Count + { + get + { + if (_collection != null) + return _collection.Count; + + EnsureWindowCreated(); + + if (_indexesCounted != null) + return _indexesCounted.Count; + + return 0; + } + } + + public object this[int index] + { + get + { + object value; + if (!TryGetValue(index, out value)) + throw new ArgumentOutOfRangeException("index"); + + return value; + } + } + + public void Clear() + { + _version++; + _finished = false; + _windowIndex = 0; + _enumeratorIndex = 0; + + if (_enumerator != null) + { + var dispose = _enumerator as IDisposable; + if (dispose != null) + dispose.Dispose(); + + _enumerator = null; + } + + if (_items != null) + _items.Clear(); + if (_indexesCounted != null) + _indexesCounted.Clear(); + + OnCountChanged(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + public event EventHandler CountChanged; + + void ClearRange(int index, int clearCount) + { + if (_items == null) + return; + + for (int i = index; i < index + clearCount; i++) + _items.Remove(i); + } + + bool CountIndex(int index) + { + if (_collection != null) + return false; + + // A collection is used in case TryGetValue is called out of order. + if (_indexesCounted == null) + _indexesCounted = new HashSet<int>(); + + if (_indexesCounted.Contains(index)) + return false; + + _indexesCounted.Add(index); + return true; + } + + void EnsureWindowCreated() + { + if (_items != null && _items.Count > 0) + return; + + object value; + TryGetValue(0, out value); + } + + void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + Action action; + if (_list == null) + { + action = Clear; + } + else + { + action = () => + { + _version++; + OnCollectionChanged(e); + }; + } + + CollectionSynchronizationContext sync; + if (BindingBase.TryGetSynchronizedCollection(ProxiedEnumerable, out sync)) + { + sync.Callback(ProxiedEnumerable, sync.Context, () => + { + e = e.WithCount(Count); + Device.BeginInvokeOnMainThread(action); + }, false); + } + else + { + e = e.WithCount(Count); + if (Device.IsInvokeRequired) + Device.BeginInvokeOnMainThread(action); + else + action(); + } + } + + void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + NotifyCollectionChangedEventHandler changed = CollectionChanged; + if (changed != null) + changed(this, e); + } + + void OnCountChanged() + { + EventHandler changed = CountChanged; + if (changed != null) + changed(this, EventArgs.Empty); + } + + bool TryGetValue(int index, out object value) + { + value = null; + + CollectionSynchronizationContext syncContext; + BindingBase.TryGetSynchronizedCollection(ProxiedEnumerable, out syncContext); + + if (_list != null) + { + object indexedValue = null; + var inRange = false; + Action getFromList = () => + { + if (index >= _list.Count) + return; + + indexedValue = _list[index]; + inRange = true; + }; + + if (syncContext != null) + syncContext.Callback(ProxiedEnumerable, syncContext.Context, getFromList, false); + else + getFromList(); + + value = indexedValue; + return inRange; + } + + if (_collection != null && index >= _collection.Count) + return false; + if (_items != null) + { + bool found = _items.TryGetValue(index, out value); + if (found || _finished) + return found; + } + + if (index >= _windowIndex + _windowSize) + { + int newIndex = index - _windowSize / 2; + ClearRange(_windowIndex, newIndex - _windowIndex); + _windowIndex = newIndex; + } + else if (index < _windowIndex) + { + int clearIndex = _windowIndex; + int clearSize = _windowSize; + if (clearIndex <= index + clearSize) + { + int diff = index + clearSize - clearIndex; + clearIndex += diff + 1; + clearSize -= diff; + } + + ClearRange(clearIndex, clearSize); + _windowIndex = 0; + + var dispose = _enumerator as IDisposable; + if (dispose != null) + dispose.Dispose(); + + _enumerator = null; + _enumeratorIndex = 0; + } + + if (_enumerator == null) + _enumerator = ProxiedEnumerable.GetEnumerator(); + if (_items == null) + _items = new Dictionary<int, object>(); + + var countChanged = false; + int end = _windowIndex + _windowSize; + + for (; _enumeratorIndex < end; _enumeratorIndex++) + { + var moved = false; + Action move = () => + { + try + { + moved = _enumerator.MoveNext(); + } + catch (InvalidOperationException ioex) + { + throw new InvalidOperationException("You must call UpdateNonNotifyingList() after updating a list that does not implement INotifyCollectionChanged", ioex); + } + + if (!moved) + { + var dispose = _enumerator as IDisposable; + if (dispose != null) + dispose.Dispose(); + + _enumerator = null; + _enumeratorIndex = 0; + _finished = true; + } + }; + + if (syncContext == null) + move(); + else + syncContext.Callback(ProxiedEnumerable, syncContext.Context, move, false); + + if (!moved) + break; + + if (CountIndex(_enumeratorIndex)) + countChanged = true; + + if (_enumeratorIndex >= _windowIndex) + _items.Add(_enumeratorIndex, _enumerator.Current); + } + + if (countChanged) + OnCountChanged(); + + return _items.TryGetValue(index, out value); + } + + class WeakNotifyProxy + { + readonly WeakReference<INotifyCollectionChanged> _weakCollection; + readonly WeakReference<ListProxy> _weakProxy; + + public WeakNotifyProxy(ListProxy proxy, INotifyCollectionChanged incc) + { + incc.CollectionChanged += OnCollectionChanged; + + _weakProxy = new WeakReference<ListProxy>(proxy); + _weakCollection = new WeakReference<INotifyCollectionChanged>(incc); + } + + void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + ListProxy proxy; + if (!_weakProxy.TryGetTarget(out proxy)) + { + INotifyCollectionChanged collection; + if (_weakCollection.TryGetTarget(out collection)) + collection.CollectionChanged -= OnCollectionChanged; + + return; + } + + proxy.OnCollectionChanged(sender, e); + } + } + + class ProxyEnumerator : IEnumerator<object> + { + readonly ListProxy _proxy; + readonly int _version; + + int _index; + + public ProxyEnumerator(ListProxy proxy) + { + _proxy = proxy; + _version = proxy._version; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + if (_proxy._version != _version) + throw new InvalidOperationException(); + + object value; + bool next = _proxy.TryGetValue(_index++, out value); + if (next) + Current = value; + + return next; + } + + public void Reset() + { + _index = 0; + Current = null; + } + + public object Current { get; private set; } + } + + #region IList + + object IList.this[int index] + { + get { return this[index]; } + set { throw new NotSupportedException(); } + } + + bool IList.IsReadOnly + { + get { return true; } + } + + bool IList.IsFixedSize + { + get { return false; } + } + + bool ICollection.IsSynchronized + { + get { return false; } + } + + object ICollection.SyncRoot + { + get { throw new NotSupportedException(); } + } + + void ICollection.CopyTo(Array array, int index) + { + throw new NotSupportedException(); + } + + int IList.Add(object item) + { + throw new NotSupportedException(); + } + + void IList.Remove(object item) + { + throw new NotSupportedException(); + } + + void IList.Insert(int index, object item) + { + throw new NotSupportedException(); + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException(); + } + + void IList.Clear() + { + throw new NotSupportedException(); + } + + #endregion + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ListView.cs b/Xamarin.Forms.Core/ListView.cs new file mode 100644 index 00000000..3d29033a --- /dev/null +++ b/Xamarin.Forms.Core/ListView.cs @@ -0,0 +1,540 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.Windows.Input; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_ListViewRenderer))] + public class ListView : ItemsView<Cell>, IListViewController + + { + public static readonly BindableProperty IsPullToRefreshEnabledProperty = BindableProperty.Create("IsPullToRefreshEnabled", typeof(bool), typeof(ListView), false); + + public static readonly BindableProperty IsRefreshingProperty = BindableProperty.Create("IsRefreshing", typeof(bool), typeof(ListView), false, BindingMode.TwoWay); + + public static readonly BindableProperty RefreshCommandProperty = BindableProperty.Create("RefreshCommand", typeof(ICommand), typeof(ListView), null, propertyChanged: OnRefreshCommandChanged); + + public static readonly BindableProperty HeaderProperty = BindableProperty.Create("Header", typeof(object), typeof(ListView), null, propertyChanged: OnHeaderChanged); + + public static readonly BindableProperty HeaderTemplateProperty = BindableProperty.Create("HeaderTemplate", typeof(DataTemplate), typeof(ListView), null, propertyChanged: OnHeaderTemplateChanged, + validateValue: ValidateHeaderFooterTemplate); + + public static readonly BindableProperty FooterProperty = BindableProperty.Create("Footer", typeof(object), typeof(ListView), null, propertyChanged: OnFooterChanged); + + public static readonly BindableProperty FooterTemplateProperty = BindableProperty.Create("FooterTemplate", typeof(DataTemplate), typeof(ListView), null, propertyChanged: OnFooterTemplateChanged, + validateValue: ValidateHeaderFooterTemplate); + + public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem", typeof(object), typeof(ListView), null, BindingMode.OneWayToSource, + propertyChanged: OnSelectedItemChanged); + + public static readonly BindableProperty HasUnevenRowsProperty = BindableProperty.Create("HasUnevenRows", typeof(bool), typeof(ListView), false); + + public static readonly BindableProperty RowHeightProperty = BindableProperty.Create("RowHeight", typeof(int), typeof(ListView), -1); + + public static readonly BindableProperty GroupHeaderTemplateProperty = BindableProperty.Create("GroupHeaderTemplate", typeof(DataTemplate), typeof(ListView), null, + propertyChanged: OnGroupHeaderTemplateChanged); + + public static readonly BindableProperty IsGroupingEnabledProperty = BindableProperty.Create("IsGroupingEnabled", typeof(bool), typeof(ListView), false); + + public static readonly BindableProperty SeparatorVisibilityProperty = BindableProperty.Create("SeparatorVisibility", typeof(SeparatorVisibility), typeof(ListView), SeparatorVisibility.Default); + + public static readonly BindableProperty SeparatorColorProperty = BindableProperty.Create("SeparatorColor", typeof(Color), typeof(ListView), Color.Default); + + BindingBase _groupDisplayBinding; + + BindingBase _groupShortNameBinding; + Element _headerElement; + Element _footerElement; + + ScrollToRequestedEventArgs _pendingScroll; + int _previousGroupSelected = -1; + int _previousRowSelected = -1; + + /// <summary> + /// Controls whether anything happens in BeginRefresh(), is set based on RefreshCommand.CanExecute + /// </summary> + bool _refreshAllowed = true; + + public ListView() + { + TakePerformanceHit = false; + + VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand; + + TemplatedItems.IsGroupingEnabledProperty = IsGroupingEnabledProperty; + TemplatedItems.GroupHeaderTemplateProperty = GroupHeaderTemplateProperty; + } + + public ListView([Parameter("CachingStrategy")] ListViewCachingStrategy cachingStrategy) : this() + { + if (Device.OS == TargetPlatform.Android || Device.OS == TargetPlatform.iOS) + CachingStrategy = cachingStrategy; + } + + public object Footer + { + get { return GetValue(FooterProperty); } + set { SetValue(FooterProperty, value); } + } + + public DataTemplate FooterTemplate + { + get { return (DataTemplate)GetValue(FooterTemplateProperty); } + set { SetValue(FooterTemplateProperty, value); } + } + + public BindingBase GroupDisplayBinding + { + get { return _groupDisplayBinding; } + set + { + if (_groupDisplayBinding == value) + return; + + OnPropertyChanging(); + BindingBase oldValue = value; + _groupDisplayBinding = value; + OnGroupDisplayBindingChanged(this, oldValue, _groupDisplayBinding); + TemplatedItems.GroupDisplayBinding = value; + OnPropertyChanged(); + } + } + + public DataTemplate GroupHeaderTemplate + { + get { return (DataTemplate)GetValue(GroupHeaderTemplateProperty); } + set { SetValue(GroupHeaderTemplateProperty, value); } + } + + public BindingBase GroupShortNameBinding + { + get { return _groupShortNameBinding; } + set + { + if (_groupShortNameBinding == value) + return; + + OnPropertyChanging(); + _groupShortNameBinding = value; + TemplatedItems.GroupShortNameBinding = value; + OnPropertyChanged(); + } + } + + public bool HasUnevenRows + { + get { return (bool)GetValue(HasUnevenRowsProperty); } + set { SetValue(HasUnevenRowsProperty, value); } + } + + public object Header + { + get { return GetValue(HeaderProperty); } + set { SetValue(HeaderProperty, value); } + } + + public DataTemplate HeaderTemplate + { + get { return (DataTemplate)GetValue(HeaderTemplateProperty); } + set { SetValue(HeaderTemplateProperty, value); } + } + + public bool IsGroupingEnabled + { + get { return (bool)GetValue(IsGroupingEnabledProperty); } + set { SetValue(IsGroupingEnabledProperty, value); } + } + + public bool IsPullToRefreshEnabled + { + get { return (bool)GetValue(IsPullToRefreshEnabledProperty); } + set { SetValue(IsPullToRefreshEnabledProperty, value); } + } + + public bool IsRefreshing + { + get { return (bool)GetValue(IsRefreshingProperty); } + set { SetValue(IsRefreshingProperty, value); } + } + + public ICommand RefreshCommand + { + get { return (ICommand)GetValue(RefreshCommandProperty); } + set { SetValue(RefreshCommandProperty, value); } + } + + public int RowHeight + { + get { return (int)GetValue(RowHeightProperty); } + set { SetValue(RowHeightProperty, value); } + } + + public object SelectedItem + { + get { return GetValue(SelectedItemProperty); } + set { SetValue(SelectedItemProperty, value); } + } + + public Color SeparatorColor + { + get { return (Color)GetValue(SeparatorColorProperty); } + set { SetValue(SeparatorColorProperty, value); } + } + + public SeparatorVisibility SeparatorVisibility + { + get { return (SeparatorVisibility)GetValue(SeparatorVisibilityProperty); } + set { SetValue(SeparatorVisibilityProperty, value); } + } + + internal ListViewCachingStrategy CachingStrategy { get; private set; } + + internal bool TakePerformanceHit { get; set; } + + bool RefreshAllowed + { + set + { + if (_refreshAllowed == value) + return; + + _refreshAllowed = value; + OnPropertyChanged(); + } + get { return _refreshAllowed; } + } + + Element IListViewController.FooterElement + { + get { return _footerElement; } + } + + Element IListViewController.HeaderElement + { + get { return _headerElement; } + } + + bool IListViewController.RefreshAllowed + { + get { return RefreshAllowed; } + } + + void IListViewController.SendCellAppearing(Cell cell) + { + EventHandler<ItemVisibilityEventArgs> handler = ItemAppearing; + if (handler != null) + handler(this, new ItemVisibilityEventArgs(cell.BindingContext)); + } + + void IListViewController.SendCellDisappearing(Cell cell) + { + EventHandler<ItemVisibilityEventArgs> handler = ItemDisappearing; + if (handler != null) + handler(this, new ItemVisibilityEventArgs(cell.BindingContext)); + } + + void IListViewController.SendRefreshing() + { + BeginRefresh(); + } + + public void BeginRefresh() + { + if (!RefreshAllowed) + return; + + SetValueCore(IsRefreshingProperty, true); + OnRefreshing(EventArgs.Empty); + + ICommand command = RefreshCommand; + if (command != null) + command.Execute(null); + } + + public void EndRefresh() + { + SetValueCore(IsRefreshingProperty, false); + } + + public event EventHandler<ItemVisibilityEventArgs> ItemAppearing; + + public event EventHandler<ItemVisibilityEventArgs> ItemDisappearing; + + public event EventHandler<SelectedItemChangedEventArgs> ItemSelected; + + public event EventHandler<ItemTappedEventArgs> ItemTapped; + + public event EventHandler Refreshing; + + public void ScrollTo(object item, ScrollToPosition position, bool animated) + { + if (!Enum.IsDefined(typeof(ScrollToPosition), position)) + throw new ArgumentException("position is not a valid ScrollToPosition", "position"); + + var args = new ScrollToRequestedEventArgs(item, position, animated); + if (IsPlatformEnabled) + OnScrollToRequested(args); + else + _pendingScroll = args; + } + + public void ScrollTo(object item, object group, ScrollToPosition position, bool animated) + { + if (!IsGroupingEnabled) + throw new InvalidOperationException("Grouping is not enabled"); + if (!Enum.IsDefined(typeof(ScrollToPosition), position)) + throw new ArgumentException("position is not a valid ScrollToPosition", "position"); + + var args = new ScrollToRequestedEventArgs(item, group, position, animated); + if (IsPlatformEnabled) + OnScrollToRequested(args); + else + _pendingScroll = args; + } + + protected override Cell CreateDefault(object item) + { + string text = null; + if (item != null) + text = item.ToString(); + + return new TextCell { Text = text }; + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + var minimumSize = new Size(40, 40); + Size request; + + double width = Math.Min(Device.Info.ScaledScreenSize.Width, Device.Info.ScaledScreenSize.Height); + + var list = ItemsSource as IList; + if (list != null && HasUnevenRows == false && RowHeight > 0 && !IsGroupingEnabled) + { + // we can calculate this + request = new Size(width, list.Count * RowHeight); + } + else + { + // probably not worth it + request = new Size(width, Math.Max(Device.Info.ScaledScreenSize.Width, Device.Info.ScaledScreenSize.Height)); + } + + return new SizeRequest(request, minimumSize); + } + + protected override void SetupContent(Cell content, int index) + { + base.SetupContent(content, index); + content.Parent = this; + } + + protected override void UnhookContent(Cell content) + { + base.UnhookContent(content); + content.Parent = null; + } + + internal Cell CreateDefaultCell(object item) + { + return CreateDefault(item); + } + + internal void NotifyRowTapped(int groupIndex, int inGroupIndex, Cell cell = null) + { + TemplatedItemsList<ItemsView<Cell>, Cell> group = TemplatedItems.GetGroup(groupIndex); + + bool changed = _previousGroupSelected != groupIndex || _previousRowSelected != inGroupIndex; + + _previousRowSelected = inGroupIndex; + _previousGroupSelected = groupIndex; + if (cell == null) + { + cell = group[inGroupIndex]; + } + + // Set SelectedItem before any events so we don't override any changes they may have made. + SetValueCore(SelectedItemProperty, cell.BindingContext, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearDynamicResource | (changed ? SetValueFlags.RaiseOnEqual : 0)); + + cell.OnTapped(); + + ItemTapped?.Invoke(this, new ItemTappedEventArgs(group, cell.BindingContext)); + } + + internal void NotifyRowTapped(int index, Cell cell = null) + { + if (IsGroupingEnabled) + { + int leftOver; + int groupIndex = TemplatedItems.GetGroupIndexFromGlobal(index, out leftOver); + + NotifyRowTapped(groupIndex, leftOver - 1, cell); + } + else + NotifyRowTapped(0, index, cell); + } + + internal override void OnIsPlatformEnabledChanged() + { + base.OnIsPlatformEnabledChanged(); + + if (IsPlatformEnabled && _pendingScroll != null) + { + OnScrollToRequested(_pendingScroll); + _pendingScroll = null; + } + } + + internal event EventHandler<ScrollToRequestedEventArgs> ScrollToRequested; + + void OnCommandCanExecuteChanged(object sender, EventArgs eventArgs) + { + RefreshAllowed = RefreshCommand.CanExecute(null); + } + + static void OnFooterChanged(BindableObject bindable, object oldValue, object newValue) + { + var lv = (ListView)bindable; + lv.OnHeaderOrFooterChanged(ref lv._footerElement, "FooterElement", newValue, lv.FooterTemplate, false); + } + + static void OnFooterTemplateChanged(BindableObject bindable, object oldValue, object newValue) + { + var lv = (ListView)bindable; + lv.OnHeaderOrFooterChanged(ref lv._footerElement, "FooterElement", lv.Footer, (DataTemplate)newValue, true); + } + + static void OnGroupDisplayBindingChanged(BindableObject bindable, BindingBase oldValue, BindingBase newValue) + { + var lv = (ListView)bindable; + if (newValue != null && lv.GroupHeaderTemplate != null) + { + lv.GroupHeaderTemplate = null; + Log.Warning("ListView", "GroupHeaderTemplate and GroupDisplayBinding can not be set at the same time, setting GroupHeaderTemplate to null"); + } + } + + static void OnGroupHeaderTemplateChanged(BindableObject bindable, object oldvalue, object newValue) + { + var lv = (ListView)bindable; + if (newValue != null && lv.GroupDisplayBinding != null) + { + lv.GroupDisplayBinding = null; + Debug.WriteLine("GroupHeaderTemplate and GroupDisplayBinding can not be set at the same time, setting GroupDisplayBinding to null"); + } + } + + static void OnHeaderChanged(BindableObject bindable, object oldValue, object newValue) + { + var lv = (ListView)bindable; + lv.OnHeaderOrFooterChanged(ref lv._headerElement, "HeaderElement", newValue, lv.HeaderTemplate, false); + } + + void OnHeaderOrFooterChanged(ref Element storage, string property, object dataObject, DataTemplate template, bool templateChanged) + { + if (dataObject == null) + { + if (!templateChanged) + { + OnPropertyChanging(property); + storage = null; + OnPropertyChanged(property); + } + + return; + } + + if (template == null) + { + var view = dataObject as Element; + if (view == null || view is Page) + view = new Label { Text = dataObject.ToString() }; + + view.Parent = this; + OnPropertyChanging(property); + storage = view; + OnPropertyChanged(property); + } + else if (storage == null || templateChanged) + { + OnPropertyChanging(property); + storage = template.CreateContent() as Element; + if (storage != null) + { + storage.BindingContext = dataObject; + storage.Parent = this; + } + OnPropertyChanged(property); + } + else + { + storage.BindingContext = dataObject; + } + } + + static void OnHeaderTemplateChanged(BindableObject bindable, object oldValue, object newValue) + { + var lv = (ListView)bindable; + lv.OnHeaderOrFooterChanged(ref lv._headerElement, "HeaderElement", lv.Header, (DataTemplate)newValue, true); + } + + static void OnRefreshCommandChanged(BindableObject bindable, object oldValue, object newValue) + { + var lv = (ListView)bindable; + var oldCommand = (ICommand)oldValue; + var command = (ICommand)newValue; + + lv.OnRefreshCommandChanged(oldCommand, command); + } + + void OnRefreshCommandChanged(ICommand oldCommand, ICommand newCommand) + { + if (oldCommand != null) + { + oldCommand.CanExecuteChanged -= OnCommandCanExecuteChanged; + } + + if (newCommand != null) + { + newCommand.CanExecuteChanged += OnCommandCanExecuteChanged; + RefreshAllowed = newCommand.CanExecute(null); + } + else + { + RefreshAllowed = true; + } + } + + void OnRefreshing(EventArgs e) + { + EventHandler handler = Refreshing; + if (handler != null) + handler(this, e); + } + + void OnScrollToRequested(ScrollToRequestedEventArgs e) + { + EventHandler<ScrollToRequestedEventArgs> handler = ScrollToRequested; + if (handler != null) + handler(this, e); + } + + static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue) + { + var list = (ListView)bindable; + if (list.ItemSelected != null) + list.ItemSelected(list, new SelectedItemChangedEventArgs(newValue)); + } + + static bool ValidateHeaderFooterTemplate(BindableObject bindable, object value) + { + if (value == null) + return true; + var template = (DataTemplate)value; + return template.CreateContent() is View; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ListViewCachingStrategy.cs b/Xamarin.Forms.Core/ListViewCachingStrategy.cs new file mode 100644 index 00000000..7dd90196 --- /dev/null +++ b/Xamarin.Forms.Core/ListViewCachingStrategy.cs @@ -0,0 +1,8 @@ +namespace Xamarin.Forms +{ + public enum ListViewCachingStrategy + { + RetainElement = 0, + RecycleElement + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/LockingSemaphore.cs b/Xamarin.Forms.Core/LockingSemaphore.cs new file mode 100644 index 00000000..b9fd20a9 --- /dev/null +++ b/Xamarin.Forms.Core/LockingSemaphore.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + internal class LockingSemaphore + { + static readonly Task Completed = Task.FromResult(true); + readonly Queue<TaskCompletionSource<bool>> _waiters = new Queue<TaskCompletionSource<bool>>(); + int _currentCount; + + public LockingSemaphore(int initialCount) + { + if (initialCount < 0) + throw new ArgumentOutOfRangeException("initialCount"); + _currentCount = initialCount; + } + + public void Release() + { + TaskCompletionSource<bool> toRelease = null; + lock(_waiters) + { + if (_waiters.Count > 0) + toRelease = _waiters.Dequeue(); + else + ++_currentCount; + } + if (toRelease != null) + toRelease.TrySetResult(true); + } + + public Task WaitAsync(CancellationToken token) + { + lock(_waiters) + { + if (_currentCount > 0) + { + --_currentCount; + return Completed; + } + var waiter = new TaskCompletionSource<bool>(); + _waiters.Enqueue(waiter); + token.Register(() => waiter.TrySetCanceled()); + return waiter.Task; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Log.cs b/Xamarin.Forms.Core/Log.cs new file mode 100644 index 00000000..b8053e5d --- /dev/null +++ b/Xamarin.Forms.Core/Log.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + internal static class Log + { + static Log() + { + Listeners = new SynchronizedList<LogListener>(); + } + + public static IList<LogListener> Listeners { get; } + + public static void Warning(string category, string message) + { + foreach (LogListener listener in Listeners) + listener.Warning(category, message); + } + + public static void Warning(string category, string format, params object[] args) + { + Warning(category, string.Format(format, args)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/LogListener.cs b/Xamarin.Forms.Core/LogListener.cs new file mode 100644 index 00000000..78222565 --- /dev/null +++ b/Xamarin.Forms.Core/LogListener.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Forms +{ + internal abstract class LogListener + { + public abstract void Warning(string category, string message); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/MasterBehavior.cs b/Xamarin.Forms.Core/MasterBehavior.cs new file mode 100644 index 00000000..cd2dae2f --- /dev/null +++ b/Xamarin.Forms.Core/MasterBehavior.cs @@ -0,0 +1,11 @@ +namespace Xamarin.Forms +{ + public enum MasterBehavior + { + Default = 0, + SplitOnLandscape = 1, + Split = 2, + Popover = 3, + SplitOnPortrait = 4 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/MasterDetailPage.cs b/Xamarin.Forms.Core/MasterDetailPage.cs new file mode 100644 index 00000000..a0849aa4 --- /dev/null +++ b/Xamarin.Forms.Core/MasterDetailPage.cs @@ -0,0 +1,229 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_MasterDetailPageRenderer))] + public class MasterDetailPage : Page + { + public static readonly BindableProperty IsGestureEnabledProperty = BindableProperty.Create("IsGestureEnabled", typeof(bool), typeof(MasterDetailPage), true); + + public static readonly BindableProperty IsPresentedProperty = BindableProperty.Create("IsPresented", typeof(bool), typeof(MasterDetailPage), default(bool), + propertyChanged: OnIsPresentedPropertyChanged, propertyChanging: OnIsPresentedPropertyChanging); + + public static readonly BindableProperty MasterBehaviorProperty = BindableProperty.Create("MasterBehavior", typeof(MasterBehavior), typeof(MasterDetailPage), default(MasterBehavior), + propertyChanged: OnMasterBehaviorPropertyChanged); + + Page _detail; + + Rectangle _detailBounds; + + Page _master; + + Rectangle _masterBounds; + + public Page Detail + { + get { return _detail; } + set + { + if (_detail != null && value == null) + throw new ArgumentNullException("value", "Detail cannot be set to null once a value is set."); + + if (_detail == value) + return; + + if (value.RealParent != null) + throw new InvalidOperationException("Detail must not already have a parent."); + + OnPropertyChanging(); + if (_detail != null) + InternalChildren.Remove(_detail); + _detail = value; + InternalChildren.Add(_detail); + OnPropertyChanged(); + } + } + + public bool IsGestureEnabled + { + get { return (bool)GetValue(IsGestureEnabledProperty); } + set { SetValue(IsGestureEnabledProperty, value); } + } + + public bool IsPresented + { + get { return (bool)GetValue(IsPresentedProperty); } + set { SetValue(IsPresentedProperty, value); } + } + + public Page Master + { + get { return _master; } + set + { + if (_master != null && value == null) + throw new ArgumentNullException("value", "Master cannot be set to null once a value is set"); + + if (string.IsNullOrEmpty(value.Title)) + throw new InvalidOperationException("Title property must be set on Master page"); + + if (_master == value) + return; + + if (value.RealParent != null) + throw new InvalidOperationException("Master must not already have a parent."); + + OnPropertyChanging(); + if (_master != null) + InternalChildren.Remove(_master); + _master = value; + InternalChildren.Add(_master); + OnPropertyChanged(); + } + } + + public MasterBehavior MasterBehavior + { + get { return (MasterBehavior)GetValue(MasterBehaviorProperty); } + set { SetValue(MasterBehaviorProperty, value); } + } + + internal bool CanChangeIsPresented { get; set; } = true; + + internal Rectangle DetailBounds + { + get { return _detailBounds; } + set + { + _detailBounds = value; + if (_detail == null) + throw new InvalidOperationException("Detail must be set before using a MasterDetailPage"); + _detail.Layout(value); + } + } + + internal Rectangle MasterBounds + { + get { return _masterBounds; } + set + { + _masterBounds = value; + if (_master == null) + throw new InvalidOperationException("Master must be set before using a MasterDetailPage"); + _master.Layout(value); + } + } + + internal bool ShouldShowSplitMode + { + get + { + if (Device.Idiom == TargetIdiom.Phone) + return false; + + MasterBehavior behavior = MasterBehavior; + DeviceOrientation orientation = Device.Info.CurrentOrientation; + + bool isSplitOnLandscape = (behavior == MasterBehavior.SplitOnLandscape || behavior == MasterBehavior.Default) && orientation.IsLandscape(); + bool isSplitOnPortrait = behavior == MasterBehavior.SplitOnPortrait && orientation.IsPortrait(); + return behavior == MasterBehavior.Split || isSplitOnLandscape || isSplitOnPortrait; + } + } + + public event EventHandler IsPresentedChanged; + + public virtual bool ShouldShowToolbarButton() + { + if (Device.Idiom == TargetIdiom.Phone) + return true; + + MasterBehavior behavior = MasterBehavior; + DeviceOrientation orientation = Device.Info.CurrentOrientation; + + bool isSplitOnLandscape = (behavior == MasterBehavior.SplitOnLandscape || behavior == MasterBehavior.Default) && orientation.IsLandscape(); + bool isSplitOnPortrait = behavior == MasterBehavior.SplitOnPortrait && orientation.IsPortrait(); + return behavior != MasterBehavior.Split && !isSplitOnLandscape && !isSplitOnPortrait; + } + + protected override void LayoutChildren(double x, double y, double width, double height) + { + if (Master == null || Detail == null) + throw new InvalidOperationException("Master and Detail must be set before using a MasterDetailPage"); + _master.Layout(_masterBounds); + _detail.Layout(_detailBounds); + } + + protected override void OnAppearing() + { + CanChangeIsPresented = true; + UpdateMasterBehavior(this); + base.OnAppearing(); + } + + protected override bool OnBackButtonPressed() + { + if (IsPresented) + { + if (Master.SendBackButtonPressed()) + return true; + } + + EventHandler<BackButtonPressedEventArgs> handler = BackButtonPressed; + if (handler != null) + { + var args = new BackButtonPressedEventArgs(); + handler(this, args); + if (args.Handled) + return true; + } + + if (Detail.SendBackButtonPressed()) + { + return true; + } + + return base.OnBackButtonPressed(); + } + + protected override void OnParentSet() + { + if (RealParent != null && (Master == null || Detail == null)) + throw new InvalidOperationException("Master and Detail must be set before adding MasterDetailPage to a container"); + base.OnParentSet(); + } + + internal event EventHandler<BackButtonPressedEventArgs> BackButtonPressed; + + internal static void UpdateMasterBehavior(MasterDetailPage page) + { + if (page.ShouldShowSplitMode) + { + page.SetValueCore(IsPresentedProperty, true); + if (page.MasterBehavior != MasterBehavior.Default) + page.CanChangeIsPresented = false; + } + } + + static void OnIsPresentedPropertyChanged(BindableObject sender, object oldValue, object newValue) + { + var page = (MasterDetailPage)sender; + EventHandler handler = page.IsPresentedChanged; + if (handler != null) + handler(page, EventArgs.Empty); + } + + static void OnIsPresentedPropertyChanging(BindableObject sender, object oldValue, object newValue) + { + var page = (MasterDetailPage)sender; + if (!page.CanChangeIsPresented) + throw new InvalidOperationException(string.Format("Can't change IsPresented when setting {0}", page.MasterBehavior)); + } + + static void OnMasterBehaviorPropertyChanged(BindableObject sender, object oldValue, object newValue) + { + var page = (MasterDetailPage)sender; + UpdateMasterBehavior(page); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/MeasureFlags.cs b/Xamarin.Forms.Core/MeasureFlags.cs new file mode 100644 index 00000000..c2e5c80a --- /dev/null +++ b/Xamarin.Forms.Core/MeasureFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Xamarin.Forms +{ + [Flags] + public enum MeasureFlags + { + None = 0, + IncludeMargins = 1 << 0 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/MenuItem.cs b/Xamarin.Forms.Core/MenuItem.cs new file mode 100644 index 00000000..3e830445 --- /dev/null +++ b/Xamarin.Forms.Core/MenuItem.cs @@ -0,0 +1,117 @@ +using System; +using System.Windows.Input; + +namespace Xamarin.Forms +{ + public class MenuItem : BaseMenuItem + { + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(MenuItem), null); + + public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(MenuItem), null, + propertyChanging: (bo, o, n) => ((MenuItem)bo).OnCommandChanging(), propertyChanged: (bo, o, n) => ((MenuItem)bo).OnCommandChanged()); + + public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(MenuItem), null, + propertyChanged: (bo, o, n) => ((MenuItem)bo).OnCommandParameterChanged()); + + public static readonly BindableProperty IsDestructiveProperty = BindableProperty.Create("IsDestructive", typeof(bool), typeof(MenuItem), false); + + public static readonly BindableProperty IconProperty = BindableProperty.Create("Icon", typeof(FileImageSource), typeof(MenuItem), default(FileImageSource)); + + internal static readonly BindableProperty IsEnabledProperty = BindableProperty.Create("IsEnabled", typeof(bool), typeof(ToolbarItem), true); + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public object CommandParameter + { + get { return GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public FileImageSource Icon + { + get { return (FileImageSource)GetValue(IconProperty); } + set { SetValue(IconProperty, value); } + } + + public bool IsDestructive + { + get { return (bool)GetValue(IsDestructiveProperty); } + set { SetValue(IsDestructiveProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + internal bool IsEnabled + { + get { return (bool)GetValue(IsEnabledProperty); } + set { SetValue(IsEnabledProperty, value); } + } + + bool IsEnabledCore + { + set { SetValueCore(IsEnabledProperty, value); } + } + + public event EventHandler Clicked; + + protected virtual void OnClicked() + { + EventHandler handler = Clicked; + if (handler != null) + handler(this, EventArgs.Empty); + } + + internal void Activate() + { + if (Command != null) + { + if (IsEnabled) + Command.Execute(CommandParameter); + } + + OnClicked(); + } + + void OnCommandCanExecuteChanged(object sender, EventArgs eventArgs) + { + IsEnabledCore = Command.CanExecute(CommandParameter); + } + + void OnCommandChanged() + { + if (Command == null) + { + IsEnabledCore = true; + return; + } + + IsEnabledCore = Command.CanExecute(CommandParameter); + + Command.CanExecuteChanged += OnCommandCanExecuteChanged; + } + + void OnCommandChanging() + { + if (Command == null) + return; + + Command.CanExecuteChanged -= OnCommandCanExecuteChanged; + } + + void OnCommandParameterChanged() + { + if (Command == null) + return; + + IsEnabledCore = Command.CanExecute(CommandParameter); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/MergedStyle.cs b/Xamarin.Forms.Core/MergedStyle.cs new file mode 100644 index 00000000..9f9c68bf --- /dev/null +++ b/Xamarin.Forms.Core/MergedStyle.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Xamarin.Forms +{ + public partial class VisualElement + { + sealed class MergedStyle : IStyle + { + ////If the base type is one of these, stop registering dynamic resources further + ////The last one (typeof(Element)) is a safety guard as we might be creating VisualElement directly in internal code + static readonly IList<Type> s_stopAtTypes = new List<Type> { typeof(View), typeof(Layout<>), typeof(VisualElement), typeof(Element) }; + + readonly BindableProperty _classStyleProperty = BindableProperty.Create("ClassStyle", typeof(IList<Style>), typeof(VisualElement), default(IList<Style>), + propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable)._mergedStyle.OnClassStyleChanged()); + + readonly List<BindableProperty> _implicitStyles = new List<BindableProperty>(); + + IStyle _classStyle; + + IStyle _implicitStyle; + + IStyle _style; + + string _styleClass; + + public MergedStyle(Type targetType, BindableObject target) + { + Target = target; + TargetType = targetType; + RegisterImplicitStyles(); + Apply(Target); + } + + public IStyle Style + { + get { return _style; } + set { SetStyle(ImplicitStyle, ClassStyle, value); } + } + + public string StyleClass + { + get { return _styleClass; } + set + { + string val = string.IsNullOrWhiteSpace(value) ? null : value.Trim(); + if (_styleClass == val) + return; + + if (_styleClass != null) + Target.RemoveDynamicResource(_classStyleProperty); + + _styleClass = val; + + if (_styleClass != null) + Target.SetDynamicResource(_classStyleProperty, Forms.Style.StyleClassPrefix + _styleClass); + } + } + + public BindableObject Target { get; } + + IStyle ClassStyle + { + get { return _classStyle; } + set { SetStyle(ImplicitStyle, value, Style); } + } + + IStyle ImplicitStyle + { + get { return _implicitStyle; } + set { SetStyle(value, ClassStyle, Style); } + } + + public void Apply(BindableObject bindable) + { + ImplicitStyle?.Apply(bindable); + ClassStyle?.Apply(bindable); + Style?.Apply(bindable); + } + + public Type TargetType { get; } + + public void UnApply(BindableObject bindable) + { + Style?.UnApply(bindable); + ClassStyle?.UnApply(bindable); + ImplicitStyle?.UnApply(bindable); + } + + void OnClassStyleChanged() + { + var classStyles = Target.GetValue(_classStyleProperty) as IList<Style>; + if (classStyles == null) + ClassStyle = null; + else + { + ClassStyle = classStyles.FirstOrDefault(s => s.CanBeAppliedTo(TargetType)); + } + } + + void OnImplicitStyleChanged() + { + var first = true; + foreach (BindableProperty implicitStyleProperty in _implicitStyles) + { + var implicitStyle = (Style)Target.GetValue(implicitStyleProperty); + if (implicitStyle != null) + { + if (first || implicitStyle.ApplyToDerivedTypes) + { + ImplicitStyle = implicitStyle; + return; + } + } + first = false; + } + } + + void RegisterImplicitStyles() + { + Type type = TargetType; + while (true) + { + BindableProperty implicitStyleProperty = BindableProperty.Create("ImplicitStyle", typeof(Style), typeof(VisualElement), default(Style), + propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable)._mergedStyle.OnImplicitStyleChanged()); + Target.SetDynamicResource(implicitStyleProperty, type.FullName); + _implicitStyles.Add(implicitStyleProperty); + type = type.GetTypeInfo().BaseType; + if (s_stopAtTypes.Contains(type)) + return; + } + } + + void SetStyle(IStyle implicitStyle, IStyle classStyle, IStyle style) + { + bool shouldReApplyStyle = implicitStyle != ImplicitStyle || classStyle != ClassStyle || Style != style; + bool shouldReApplyClassStyle = implicitStyle != ImplicitStyle || classStyle != ClassStyle; + bool shouldReApplyImplicitStyle = implicitStyle != ImplicitStyle && (Style as Style == null || ((Style)Style).CanCascade); + + if (shouldReApplyStyle) + Style?.UnApply(Target); + if (shouldReApplyClassStyle) + ClassStyle?.UnApply(Target); + if (shouldReApplyImplicitStyle) + ImplicitStyle?.UnApply(Target); + + _implicitStyle = implicitStyle; + _classStyle = classStyle; + _style = style; + + if (shouldReApplyImplicitStyle) + ImplicitStyle?.Apply(Target); + if (shouldReApplyClassStyle) + ClassStyle?.Apply(Target); + if (shouldReApplyStyle) + Style?.Apply(Target); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/MessagingCenter.cs b/Xamarin.Forms.Core/MessagingCenter.cs new file mode 100644 index 00000000..973531ab --- /dev/null +++ b/Xamarin.Forms.Core/MessagingCenter.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Forms +{ + public static class MessagingCenter + { + static readonly Dictionary<Tuple<string, Type, Type>, List<Tuple<WeakReference, Action<object, object>>>> s_callbacks = + new Dictionary<Tuple<string, Type, Type>, List<Tuple<WeakReference, Action<object, object>>>>(); + + public static void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class + { + if (sender == null) + throw new ArgumentNullException("sender"); + InnerSend(message, typeof(TSender), typeof(TArgs), sender, args); + } + + public static void Send<TSender>(TSender sender, string message) where TSender : class + { + if (sender == null) + throw new ArgumentNullException("sender"); + InnerSend(message, typeof(TSender), null, sender, null); + } + + public static void Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source = null) where TSender : class + { + if (subscriber == null) + throw new ArgumentNullException("subscriber"); + if (callback == null) + throw new ArgumentNullException("callback"); + + Action<object, object> wrap = (sender, args) => + { + var send = (TSender)sender; + if (source == null || send == source) + callback((TSender)sender, (TArgs)args); + }; + + InnerSubscribe(subscriber, message, typeof(TSender), typeof(TArgs), wrap); + } + + public static void Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source = null) where TSender : class + { + if (subscriber == null) + throw new ArgumentNullException("subscriber"); + if (callback == null) + throw new ArgumentNullException("callback"); + + Action<object, object> wrap = (sender, args) => + { + var send = (TSender)sender; + if (source == null || send == source) + callback((TSender)sender); + }; + + InnerSubscribe(subscriber, message, typeof(TSender), null, wrap); + } + + public static void Unsubscribe<TSender, TArgs>(object subscriber, string message) where TSender : class + { + InnerUnsubscribe(message, typeof(TSender), typeof(TArgs), subscriber); + } + + public static void Unsubscribe<TSender>(object subscriber, string message) where TSender : class + { + InnerUnsubscribe(message, typeof(TSender), null, subscriber); + } + + internal static void ClearSubscribers() + { + s_callbacks.Clear(); + } + + static void InnerSend(string message, Type senderType, Type argType, object sender, object args) + { + if (message == null) + throw new ArgumentNullException("message"); + var key = new Tuple<string, Type, Type>(message, senderType, argType); + if (!s_callbacks.ContainsKey(key)) + return; + List<Tuple<WeakReference, Action<object, object>>> actions = s_callbacks[key]; + if (actions == null || !actions.Any()) + return; // should not be reachable + + // ok so this code looks a bit funky but here is the gist of the problem. It is possible that in the course + // of executing the callbacks for this message someone will subscribe/unsubscribe from the same message in + // the callback. This would invalidate the enumerator. To work around this we make a copy. However if you unsubscribe + // from a message you can fairly reasonably expect that you will therefor not receive a call. To fix this we then + // check that the item we are about to send the message to actually exists in the live list. + List<Tuple<WeakReference, Action<object, object>>> actionsCopy = actions.ToList(); + foreach (Tuple<WeakReference, Action<object, object>> action in actionsCopy) + { + if (action.Item1.IsAlive && actions.Contains(action)) + action.Item2(sender, args); + } + } + + static void InnerSubscribe(object subscriber, string message, Type senderType, Type argType, Action<object, object> callback) + { + if (message == null) + throw new ArgumentNullException("message"); + var key = new Tuple<string, Type, Type>(message, senderType, argType); + var value = new Tuple<WeakReference, Action<object, object>>(new WeakReference(subscriber), callback); + if (s_callbacks.ContainsKey(key)) + { + s_callbacks[key].Add(value); + } + else + { + var list = new List<Tuple<WeakReference, Action<object, object>>> { value }; + s_callbacks[key] = list; + } + } + + static void InnerUnsubscribe(string message, Type senderType, Type argType, object subscriber) + { + if (subscriber == null) + throw new ArgumentNullException("subscriber"); + if (message == null) + throw new ArgumentNullException("message"); + + var key = new Tuple<string, Type, Type>(message, senderType, argType); + if (!s_callbacks.ContainsKey(key)) + return; + s_callbacks[key].RemoveAll(tuple => !tuple.Item1.IsAlive || tuple.Item1.Target == subscriber); + if (!s_callbacks[key].Any()) + s_callbacks.Remove(key); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ModalEventArgs.cs b/Xamarin.Forms.Core/ModalEventArgs.cs new file mode 100644 index 00000000..483ea5ad --- /dev/null +++ b/Xamarin.Forms.Core/ModalEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms +{ + public abstract class ModalEventArgs : EventArgs + { + protected ModalEventArgs(Page modal) + { + Modal = modal; + } + + public Page Modal { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ModalPoppedEventArgs.cs b/Xamarin.Forms.Core/ModalPoppedEventArgs.cs new file mode 100644 index 00000000..3c17f009 --- /dev/null +++ b/Xamarin.Forms.Core/ModalPoppedEventArgs.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public class ModalPoppedEventArgs : ModalEventArgs + { + public ModalPoppedEventArgs(Page modal) : base(modal) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ModalPoppingEventArgs.cs b/Xamarin.Forms.Core/ModalPoppingEventArgs.cs new file mode 100644 index 00000000..57c7a657 --- /dev/null +++ b/Xamarin.Forms.Core/ModalPoppingEventArgs.cs @@ -0,0 +1,11 @@ +namespace Xamarin.Forms +{ + public class ModalPoppingEventArgs : ModalEventArgs + { + public ModalPoppingEventArgs(Page modal) : base(modal) + { + } + + public bool Cancel { get; set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ModalPushedEventArgs.cs b/Xamarin.Forms.Core/ModalPushedEventArgs.cs new file mode 100644 index 00000000..d09caf4d --- /dev/null +++ b/Xamarin.Forms.Core/ModalPushedEventArgs.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public class ModalPushedEventArgs : ModalEventArgs + { + public ModalPushedEventArgs(Page modal) : base(modal) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ModalPushingEventArgs.cs b/Xamarin.Forms.Core/ModalPushingEventArgs.cs new file mode 100644 index 00000000..12396c9e --- /dev/null +++ b/Xamarin.Forms.Core/ModalPushingEventArgs.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public class ModalPushingEventArgs : ModalEventArgs + { + public ModalPushingEventArgs(Page modal) : base(modal) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/MultiPage.cs b/Xamarin.Forms.Core/MultiPage.cs new file mode 100644 index 00000000..89fd7e9c --- /dev/null +++ b/Xamarin.Forms.Core/MultiPage.cs @@ -0,0 +1,359 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Xamarin.Forms +{ + [ContentProperty("Children")] + public abstract class MultiPage<T> : Page, IViewContainer<T>, IPageContainer<T>, IItemsView<T> where T : Page + { + public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(MultiPage<>), null); + + public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(MultiPage<>), null); + + public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem", typeof(object), typeof(MultiPage<>), null, BindingMode.TwoWay); + + internal static readonly BindableProperty IndexProperty = BindableProperty.Create("Index", typeof(int), typeof(Page), -1); + + readonly ElementCollection<T> _children; + readonly TemplatedItemsList<MultiPage<T>, T> _templatedItems; + + T _current; + + protected MultiPage() + { + _templatedItems = new TemplatedItemsList<MultiPage<T>, T>(this, ItemsSourceProperty, ItemTemplateProperty); + _templatedItems.CollectionChanged += OnTemplatedItemsChanged; + + _children = new ElementCollection<T>(InternalChildren); + InternalChildren.CollectionChanged += OnChildrenChanged; + } + + public IEnumerable ItemsSource + { + get { return (IEnumerable)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + public DataTemplate ItemTemplate + { + get { return (DataTemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + + public object SelectedItem + { + get { return GetValue(SelectedItemProperty); } + set { SetValue(SelectedItemProperty, value); } + } + + T IItemsView<T>.CreateDefault(object item) + { + return CreateDefault(item); + } + + void IItemsView<T>.SetupContent(T content, int index) + { + SetupContent(content, index); + } + + void IItemsView<T>.UnhookContent(T content) + { + UnhookContent(content); + } + + public T CurrentPage + { + get { return _current; } + set + { + if (_current == value) + return; + + OnPropertyChanging(); + _current = value; + OnPropertyChanged(); + OnCurrentPageChanged(); + } + } + + public IList<T> Children + { + get { return _children; } + } + + public event EventHandler CurrentPageChanged; + + public event NotifyCollectionChangedEventHandler PagesChanged; + + protected abstract T CreateDefault(object item); + + protected override bool OnBackButtonPressed() + { + if (CurrentPage != null) + { + bool handled = CurrentPage.SendBackButtonPressed(); + if (handled) + return true; + } + + return base.OnBackButtonPressed(); + } + + protected override void OnChildAdded(Element child) + { + base.OnChildAdded(child); + + ForceLayout(); + } + + protected virtual void OnCurrentPageChanged() + { + EventHandler changed = CurrentPageChanged; + if (changed != null) + changed(this, EventArgs.Empty); + } + + protected virtual void OnPagesChanged(NotifyCollectionChangedEventArgs e) + { + NotifyCollectionChangedEventHandler handler = PagesChanged; + if (handler != null) + handler(this, e); + } + + protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + if (propertyName == ItemsSourceProperty.PropertyName) + _children.IsReadOnly = ItemsSource != null; + else if (propertyName == SelectedItemProperty.PropertyName) + { + UpdateCurrentPage(); + } + else if (propertyName == "CurrentPage" && ItemsSource != null) + { + if (CurrentPage == null) + { + SelectedItem = null; + } + else + { + int index = _templatedItems.IndexOf(CurrentPage); + SelectedItem = index != -1 ? _templatedItems.ListProxy[index] : null; + } + } + + base.OnPropertyChanged(propertyName); + } + + protected virtual void SetupContent(T content, int index) + { + } + + protected virtual void UnhookContent(T content) + { + } + + internal static int GetIndex(T page) + { + if (page == null) + throw new ArgumentNullException("page"); + + return (int)page.GetValue(IndexProperty); + } + + internal T GetPageByIndex(int index) + { + foreach (T page in InternalChildren) + { + if (index == GetIndex(page)) + return page; + } + return null; + } + + internal static void SetIndex(Page page, int index) + { + if (page == null) + throw new ArgumentNullException("page"); + + page.SetValue(IndexProperty, index); + } + + void OnChildrenChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (Children.IsReadOnly) + return; + + var i = 0; + foreach (T page in Children) + SetIndex(page, i++); + + OnPagesChanged(e); + + if (CurrentPage == null || Children.IndexOf(CurrentPage) == -1) + CurrentPage = Children.FirstOrDefault(); + } + + void OnTemplatedItemsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (e.NewStartingIndex < 0) + goto case NotifyCollectionChangedAction.Reset; + + for (int i = e.NewStartingIndex; i < Children.Count; i++) + SetIndex((T)InternalChildren[i], i + e.NewItems.Count); + + for (var i = 0; i < e.NewItems.Count; i++) + { + var page = (T)e.NewItems[i]; + page.Owned = true; + int index = i + e.NewStartingIndex; + SetIndex(page, index); + InternalChildren.Insert(index, (T)e.NewItems[i]); + } + + break; + + case NotifyCollectionChangedAction.Remove: + if (e.OldStartingIndex < 0) + goto case NotifyCollectionChangedAction.Reset; + + int removeIndex = e.OldStartingIndex; + for (int i = removeIndex + e.OldItems.Count; i < Children.Count; i++) + SetIndex((T)InternalChildren[i], removeIndex++); + + for (var i = 0; i < e.OldItems.Count; i++) + { + Element element = InternalChildren[e.OldStartingIndex]; + InternalChildren.RemoveAt(e.OldStartingIndex); + element.Owned = false; + } + + break; + + case NotifyCollectionChangedAction.Move: + if (e.NewStartingIndex < 0 || e.OldStartingIndex < 0) + goto case NotifyCollectionChangedAction.Reset; + + if (e.NewStartingIndex == e.OldStartingIndex) + return; + + bool movingForward = e.OldStartingIndex < e.NewStartingIndex; + + if (movingForward) + { + int moveIndex = e.OldStartingIndex; + for (int i = moveIndex + e.OldItems.Count; i <= e.NewStartingIndex; i++) + SetIndex((T)InternalChildren[i], moveIndex++); + } + else + { + for (var i = 0; i < e.OldStartingIndex - e.NewStartingIndex; i++) + { + var page = (T)InternalChildren[i + e.NewStartingIndex]; + SetIndex(page, GetIndex(page) + e.OldItems.Count); + } + } + + for (var i = 0; i < e.OldItems.Count; i++) + InternalChildren.RemoveAt(e.OldStartingIndex); + + int insertIndex = e.NewStartingIndex; + if (movingForward) + insertIndex -= e.OldItems.Count - 1; + + for (var i = 0; i < e.OldItems.Count; i++) + { + var page = (T)e.OldItems[i]; + SetIndex(page, insertIndex + i); + InternalChildren.Insert(insertIndex + i, page); + } + + break; + + case NotifyCollectionChangedAction.Replace: + if (e.OldStartingIndex < 0) + goto case NotifyCollectionChangedAction.Reset; + + for (int i = e.OldStartingIndex; i - e.OldStartingIndex < e.OldItems.Count; i++) + { + Element element = InternalChildren[i]; + InternalChildren.RemoveAt(i); + element.Owned = false; + + T page = _templatedItems.GetOrCreateContent(i, e.NewItems[i - e.OldStartingIndex]); + page.Owned = true; + SetIndex(page, i); + InternalChildren.Insert(i, page); + } + + break; + + case NotifyCollectionChangedAction.Reset: + Reset(); + return; + } + + OnPagesChanged(e); + UpdateCurrentPage(); + } + + void Reset() + { + List<Element> snapshot = InternalChildren.ToList(); + + InternalChildren.Clear(); + + foreach (Element element in snapshot) + element.Owned = false; + + for (var i = 0; i < _templatedItems.Count; i++) + { + T page = _templatedItems.GetOrCreateContent(i, _templatedItems.ListProxy[i]); + page.Owned = true; + SetIndex(page, i); + InternalChildren.Add(page); + } + + var currentNeedsUpdate = true; + + BatchBegin(); + + if (ItemsSource != null) + { + object selected = SelectedItem; + if (selected == null || !ItemsSource.Cast<object>().Contains(selected)) + { + SelectedItem = ItemsSource.Cast<object>().FirstOrDefault(); + currentNeedsUpdate = false; + } + } + + if (currentNeedsUpdate) + UpdateCurrentPage(); + + OnPagesChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + + BatchCommit(); + } + + void UpdateCurrentPage() + { + if (ItemsSource != null) + { + int index = _templatedItems.ListProxy.IndexOf(SelectedItem); + if (index == -1) + CurrentPage = (T)InternalChildren.FirstOrDefault(); + else + CurrentPage = _templatedItems.GetOrCreateContent(index, SelectedItem); + } + else if (SelectedItem is T) + CurrentPage = (T)SelectedItem; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NameScopeExtensions.cs b/Xamarin.Forms.Core/NameScopeExtensions.cs new file mode 100644 index 00000000..b9acb460 --- /dev/null +++ b/Xamarin.Forms.Core/NameScopeExtensions.cs @@ -0,0 +1,17 @@ +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms +{ + public static class NameScopeExtensions + { + public static T FindByName<T>(this Element element, string name) + { + return ((INameScope)element).FindByName<T>(name); + } + + internal static T FindByName<T>(this INameScope namescope, string name) + { + return (T)namescope.FindByName(name); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NamedSize.cs b/Xamarin.Forms.Core/NamedSize.cs new file mode 100644 index 00000000..92f7a579 --- /dev/null +++ b/Xamarin.Forms.Core/NamedSize.cs @@ -0,0 +1,11 @@ +namespace Xamarin.Forms +{ + public enum NamedSize + { + Default = 0, + Micro = 1, + Small = 2, + Medium = 3, + Large = 4 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NavigationEventArgs.cs b/Xamarin.Forms.Core/NavigationEventArgs.cs new file mode 100644 index 00000000..0ccd4343 --- /dev/null +++ b/Xamarin.Forms.Core/NavigationEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + public class NavigationEventArgs : EventArgs + { + public NavigationEventArgs(Page page) + { + if (page == null) + throw new ArgumentNullException("page"); + + Page = page; + } + + public Page Page { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NavigationMenu.cs b/Xamarin.Forms.Core/NavigationMenu.cs new file mode 100644 index 00000000..2386dd29 --- /dev/null +++ b/Xamarin.Forms.Core/NavigationMenu.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + // Mark as internal until renderers are ready for release after 1.0 + [RenderWith(typeof(_NavigationMenuRenderer))] + internal class NavigationMenu : View + { + readonly List<Page> _targets = new List<Page>(); + + public IEnumerable<Page> Targets + { + get { return _targets; } + set + { + if (_targets.AsEnumerable().SequenceEqual(value)) + return; + + foreach (Page page in value) + { + VerifyTarget(page); + } + + OnPropertyChanging(); + _targets.Clear(); + _targets.AddRange(value); + OnPropertyChanged(); + } + } + + public void Add(Page target) + { + if (_targets.Contains(target)) + return; + VerifyTarget(target); + + OnPropertyChanging("Targets"); + _targets.Add(target); + OnPropertyChanged("Targets"); + } + + public void Remove(Page target) + { + if (_targets.Contains(target)) + { + OnPropertyChanging("Targets"); + if (_targets.Remove(target)) + OnPropertyChanged("Targets"); + } + } + + internal void SendTargetSelected(Page target) + { + TargetSelected(target); + } + + void TargetSelected(Page target) + { + Navigation.PushAsync(target); + } + + void VerifyTarget(Page target) + { + if (target.Icon == null || string.IsNullOrWhiteSpace(target.Icon.File)) + throw new Exception("Icon must be set for each page before adding them to a Navigation Menu"); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NavigationModel.cs b/Xamarin.Forms.Core/NavigationModel.cs new file mode 100644 index 00000000..4591d4a4 --- /dev/null +++ b/Xamarin.Forms.Core/NavigationModel.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Forms +{ + internal class NavigationModel + { + readonly List<Page> _modalStack = new List<Page>(); + readonly List<List<Page>> _navTree = new List<List<Page>>(); + + public Page CurrentPage + { + get + { + if (_navTree.Any()) + return _navTree.Last().Last(); + return null; + } + } + + public IEnumerable<Page> Modals + { + get { return _modalStack; } + } + + public IEnumerable<Page> Roots + { + get + { + foreach (List<Page> list in _navTree) + { + yield return list[0]; + } + } + } + + public IReadOnlyList<IReadOnlyList<Page>> Tree + { + get { return _navTree; } + } + + public void Clear() + { + _navTree.Clear(); + } + + public void InsertPageBefore(Page page, Page before) + { + List<Page> currentStack = _navTree.Last(); + int index = currentStack.IndexOf(before); + + if (index == -1) + throw new ArgumentException("before must be in the current navigation context"); + + currentStack.Insert(index, page); + } + + public Page Pop(Page ancestralNav) + { + ancestralNav = AncestorToRoot(ancestralNav); + foreach (List<Page> stack in _navTree) + { + if (stack.Contains(ancestralNav)) + { + if (stack.Count <= 1) + throw new InvalidNavigationException("Can not pop final item in stack"); + Page result = stack.Last(); + stack.Remove(result); + return result; + } + } + + throw new InvalidNavigationException("Popped from unpushed item?"); + } + + public Page PopModal() + { + if (_navTree.Count <= 1) + throw new InvalidNavigationException("Can't pop modal without any modals pushed"); + Page modal = _navTree.Last().First(); + _modalStack.Remove(modal); + _navTree.Remove(_navTree.Last()); + return modal; + } + + public Page PopTopPage() + { + Page itemToRemove; + if (_navTree.Count == 1) + { + if (_navTree[0].Count > 1) + { + itemToRemove = _navTree[0].Last(); + _navTree[0].Remove(itemToRemove); + return itemToRemove; + } + return null; + } + itemToRemove = _navTree.Last().Last(); + _navTree.Last().Remove(itemToRemove); + if (!_navTree.Last().Any()) + { + _navTree.RemoveAt(_navTree.Count - 1); + } + return itemToRemove; + } + + public void PopToRoot(Page ancestralNav) + { + ancestralNav = AncestorToRoot(ancestralNav); + foreach (List<Page> stack in _navTree) + { + if (stack.Contains(ancestralNav)) + { + if (stack.Count <= 1) + throw new InvalidNavigationException("Can not pop final item in stack"); + stack.RemoveRange(1, stack.Count - 1); + return; + } + } + + throw new InvalidNavigationException("Popped from unpushed item?"); + } + + public void Push(Page page, Page ancestralNav) + { + if (ancestralNav == null) + { + if (_navTree.Any()) + throw new InvalidNavigationException("Ancestor must be provided for all pushes except first"); + _navTree.Add(new List<Page> { page }); + return; + } + + ancestralNav = AncestorToRoot(ancestralNav); + + foreach (List<Page> stack in _navTree) + { + if (stack.Contains(ancestralNav)) + { + stack.Add(page); + return; + } + } + + throw new InvalidNavigationException("Invalid ancestor passed"); + } + + public void PushModal(Page page) + { + _navTree.Add(new List<Page> { page }); + _modalStack.Add(page); + } + + public bool RemovePage(Page page) + { + bool found; + List<Page> currentStack = _navTree.Last(); + var i = 0; + while (!(found = currentStack.Remove(page)) && i < _navTree.Count - 1) + { + currentStack = _navTree[i++]; + } + + return found; + } + + Page AncestorToRoot(Page ancestor) + { + Page result = ancestor; + while (!Application.IsApplicationOrNull(result.RealParent)) + result = (Page)result.RealParent; + return result; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NavigationPage.cs b/Xamarin.Forms.Core/NavigationPage.cs new file mode 100644 index 00000000..61545b11 --- /dev/null +++ b/Xamarin.Forms.Core/NavigationPage.cs @@ -0,0 +1,411 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_NavigationPageRenderer))] + public class NavigationPage : Page, IPageContainer<Page> + { + public static readonly BindableProperty BackButtonTitleProperty = BindableProperty.CreateAttached("BackButtonTitle", typeof(string), typeof(Page), null); + + public static readonly BindableProperty HasNavigationBarProperty = BindableProperty.CreateAttached("HasNavigationBar", typeof(bool), typeof(Page), true); + + public static readonly BindableProperty HasBackButtonProperty = BindableProperty.CreateAttached("HasBackButton", typeof(bool), typeof(NavigationPage), true); + + [Obsolete("Use BarBackgroundColorProperty and BarTextColorProperty to change NavigationPage bar color properties")] public static readonly BindableProperty TintProperty = + BindableProperty.Create("Tint", typeof(Color), typeof(NavigationPage), Color.Default); + + public static readonly BindableProperty BarBackgroundColorProperty = BindableProperty.Create("BarBackgroundColor", typeof(Color), typeof(NavigationPage), Color.Default); + + public static readonly BindableProperty BarTextColorProperty = BindableProperty.Create("BarTextColor", typeof(Color), typeof(NavigationPage), Color.Default); + + public static readonly BindableProperty TitleIconProperty = BindableProperty.CreateAttached("TitleIcon", typeof(FileImageSource), typeof(NavigationPage), default(FileImageSource)); + + static readonly BindablePropertyKey CurrentPagePropertyKey = BindableProperty.CreateReadOnly("CurrentPage", typeof(Page), typeof(NavigationPage), null); + public static readonly BindableProperty CurrentPageProperty = CurrentPagePropertyKey.BindableProperty; + + public NavigationPage() + { + Navigation = new NavigationImpl(this); + } + + public NavigationPage(Page root) : this() + { + PushPage(root); + } + + public Color BarBackgroundColor + { + get { return (Color)GetValue(BarBackgroundColorProperty); } + set { SetValue(BarBackgroundColorProperty, value); } + } + + public Color BarTextColor + { + get { return (Color)GetValue(BarTextColorProperty); } + set { SetValue(BarTextColorProperty, value); } + } + + [Obsolete("Use BarBackgroundColor and BarTextColor to change NavigationPage bar color properties")] + public Color Tint + { + get { return (Color)GetValue(TintProperty); } + set { SetValue(TintProperty, value); } + } + + internal Task CurrentNavigationTask { get; set; } + + internal Stack<Page> StackCopy + { + get + { + var result = new Stack<Page>(InternalChildren.Count); + foreach (Page page in InternalChildren) + result.Push(page); + return result; + } + } + + internal int StackDepth + { + get { return InternalChildren.Count; } + } + + public Page CurrentPage + { + get { return (Page)GetValue(CurrentPageProperty); } + private set { SetValue(CurrentPagePropertyKey, value); } + } + + public static string GetBackButtonTitle(BindableObject page) + { + return (string)page.GetValue(BackButtonTitleProperty); + } + + public static bool GetHasBackButton(Page page) + { + if (page == null) + throw new ArgumentNullException("page"); + return (bool)page.GetValue(HasBackButtonProperty); + } + + public static bool GetHasNavigationBar(BindableObject page) + { + return (bool)page.GetValue(HasNavigationBarProperty); + } + + public static FileImageSource GetTitleIcon(BindableObject bindable) + { + return (FileImageSource)bindable.GetValue(TitleIconProperty); + } + + public Task<Page> PopAsync() + { + return PopAsync(true); + } + + public async Task<Page> PopAsync(bool animated) + { + if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted) + { + var tcs = new TaskCompletionSource<bool>(); + Task oldTask = CurrentNavigationTask; + CurrentNavigationTask = tcs.Task; + await oldTask; + + Page page = await PopAsyncInner(animated); + tcs.SetResult(true); + return page; + } + + Task<Page> result = PopAsyncInner(animated); + CurrentNavigationTask = result; + return await result; + } + + public event EventHandler<NavigationEventArgs> Popped; + + public event EventHandler<NavigationEventArgs> PoppedToRoot; + + public Task PopToRootAsync() + { + return PopToRootAsync(true); + } + + public async Task PopToRootAsync(bool animated) + { + if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted) + { + var tcs = new TaskCompletionSource<bool>(); + Task oldTask = CurrentNavigationTask; + CurrentNavigationTask = tcs.Task; + await oldTask; + + await PopToRootAsyncInner(animated); + tcs.SetResult(true); + return; + } + + Task result = PopToRootAsyncInner(animated); + CurrentNavigationTask = result; + await result; + } + + public Task PushAsync(Page page) + { + return PushAsync(page, true); + } + + public async Task PushAsync(Page page, bool animated) + { + if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted) + { + var tcs = new TaskCompletionSource<bool>(); + Task oldTask = CurrentNavigationTask; + CurrentNavigationTask = tcs.Task; + await oldTask; + + await PushAsyncInner(page, animated); + tcs.SetResult(true); + return; + } + + CurrentNavigationTask = PushAsyncInner(page, animated); + await CurrentNavigationTask; + } + + public event EventHandler<NavigationEventArgs> Pushed; + + public static void SetBackButtonTitle(BindableObject page, string value) + { + page.SetValue(BackButtonTitleProperty, value); + } + + public static void SetHasBackButton(Page page, bool value) + { + if (page == null) + throw new ArgumentNullException("page"); + page.SetValue(HasBackButtonProperty, value); + } + + public static void SetHasNavigationBar(BindableObject page, bool value) + { + page.SetValue(HasNavigationBarProperty, value); + } + + public static void SetTitleIcon(BindableObject bindable, FileImageSource value) + { + bindable.SetValue(TitleIconProperty, value); + } + + protected override bool OnBackButtonPressed() + { + if (CurrentPage.SendBackButtonPressed()) + return true; + + if (StackDepth > 1) + { + SafePop(); + return true; + } + + return base.OnBackButtonPressed(); + } + + internal event EventHandler<NavigationRequestedEventArgs> InsertPageBeforeRequested; + + internal async Task<Page> PopAsyncInner(bool animated, bool fast = false) + { + if (StackDepth == 1) + { + return null; + } + + var page = (Page)InternalChildren.Last(); + + var args = new NavigationRequestedEventArgs(page, animated); + + var removed = true; + + EventHandler<NavigationRequestedEventArgs> requestPop = PopRequested; + if (requestPop != null) + { + requestPop(this, args); + + if (args.Task != null && !fast) + removed = await args.Task; + } + + if (!removed && !fast) + return CurrentPage; + + InternalChildren.Remove(page); + + CurrentPage = (Page)InternalChildren.Last(); + + if (Popped != null) + Popped(this, args); + + return page; + } + + internal event EventHandler<NavigationRequestedEventArgs> PopRequested; + + internal event EventHandler<NavigationRequestedEventArgs> PopToRootRequested; + + internal event EventHandler<NavigationRequestedEventArgs> PushRequested; + + internal event EventHandler<NavigationRequestedEventArgs> RemovePageRequested; + + void InsertPageBefore(Page page, Page before) + { + if (!InternalChildren.Contains(before)) + throw new ArgumentException("before must be a child of the NavigationPage", "before"); + + if (InternalChildren.Contains(page)) + throw new ArgumentException("Cannot insert page which is already in the navigation stack"); + + EventHandler<NavigationRequestedEventArgs> handler = InsertPageBeforeRequested; + if (handler != null) + handler(this, new NavigationRequestedEventArgs(page, before, false)); + + int index = InternalChildren.IndexOf(before); + InternalChildren.Insert(index, page); + + // Shouldn't be required? + if (Width > 0 && Height > 0) + ForceLayout(); + } + + async Task PopToRootAsyncInner(bool animated) + { + if (StackDepth == 1) + return; + + var root = (Page)InternalChildren.First(); + + InternalChildren.ToArray().Where(c => c != root).ForEach(c => InternalChildren.Remove(c)); + + CurrentPage = root; + + var args = new NavigationRequestedEventArgs(root, animated); + + EventHandler<NavigationRequestedEventArgs> requestPopToRoot = PopToRootRequested; + if (requestPopToRoot != null) + { + requestPopToRoot(this, args); + + if (args.Task != null) + await args.Task; + } + + if (PoppedToRoot != null) + PoppedToRoot(this, new NavigationEventArgs(root)); + } + + async Task PushAsyncInner(Page page, bool animated) + { + if (InternalChildren.Contains(page)) + return; + + PushPage(page); + + var args = new NavigationRequestedEventArgs(page, animated); + + EventHandler<NavigationRequestedEventArgs> requestPush = PushRequested; + if (requestPush != null) + { + requestPush(this, args); + + if (args.Task != null) + await args.Task; + } + + if (Pushed != null) + Pushed(this, args); + } + + void PushPage(Page page) + { + InternalChildren.Add(page); + + CurrentPage = page; + } + + void RemovePage(Page page) + { + if (page == CurrentPage && StackDepth <= 1) + throw new InvalidOperationException("Cannot remove root page when it is also the currently displayed page."); + if (page == CurrentPage) + { + Log.Warning("NavigationPage", "RemovePage called for CurrentPage object. This can result in undesired behavior, consider called PopAsync instead."); + PopAsync(); + return; + } + + if (!InternalChildren.Contains(page)) + throw new ArgumentException("Page to remove must be contained on this Navigation Page"); + + EventHandler<NavigationRequestedEventArgs> handler = RemovePageRequested; + if (handler != null) + handler(this, new NavigationRequestedEventArgs(page, true)); + + InternalChildren.Remove(page); + } + + void SafePop() + { + PopAsync(true).ContinueWith(t => + { + if (t.IsFaulted) + throw t.Exception; + }); + } + + class NavigationImpl : NavigationProxy + { + readonly Lazy<ReadOnlyCastingList<Page, Element>> _castingList; + + public NavigationImpl(NavigationPage owner) + { + Owner = owner; + _castingList = new Lazy<ReadOnlyCastingList<Page, Element>>(() => new ReadOnlyCastingList<Page, Element>(Owner.InternalChildren)); + } + + NavigationPage Owner { get; } + + protected override IReadOnlyList<Page> GetNavigationStack() + { + return _castingList.Value; + } + + protected override void OnInsertPageBefore(Page page, Page before) + { + Owner.InsertPageBefore(page, before); + } + + protected override Task<Page> OnPopAsync(bool animated) + { + return Owner.PopAsync(animated); + } + + protected override Task OnPopToRootAsync(bool animated) + { + return Owner.PopToRootAsync(animated); + } + + protected override Task OnPushAsync(Page root, bool animated) + { + return Owner.PushAsync(root, animated); + } + + protected override void OnRemovePage(Page page) + { + Owner.RemovePage(page); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NavigationProxy.cs b/Xamarin.Forms.Core/NavigationProxy.cs new file mode 100644 index 00000000..65e2fee9 --- /dev/null +++ b/Xamarin.Forms.Core/NavigationProxy.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + internal class NavigationProxy : INavigation + { + INavigation _inner; + Lazy<List<Page>> _modalStack = new Lazy<List<Page>>(() => new List<Page>()); + + Lazy<List<Page>> _pushStack = new Lazy<List<Page>>(() => new List<Page>()); + + public INavigation Inner + { + get { return _inner; } + set + { + if (_inner == value) + return; + _inner = value; + // reverse so that things go into the new stack in the same order + // null out to release memory that will likely never be needed again + + if (ReferenceEquals(_inner, null)) + { + _pushStack = new Lazy<List<Page>>(() => new List<Page>()); + _modalStack = new Lazy<List<Page>>(() => new List<Page>()); + } + else + { + if (_pushStack != null && _pushStack.IsValueCreated) + { + foreach (Page page in _pushStack.Value) + { + _inner.PushAsync(page); + } + } + + if (_modalStack != null && _modalStack.IsValueCreated) + { + foreach (Page page in _modalStack.Value) + { + _inner.PushModalAsync(page); + } + } + + _pushStack = null; + _modalStack = null; + } + } + } + + public void InsertPageBefore(Page page, Page before) + { + OnInsertPageBefore(page, before); + } + + public IReadOnlyList<Page> ModalStack + { + get { return GetModalStack(); } + } + + public IReadOnlyList<Page> NavigationStack + { + get { return GetNavigationStack(); } + } + + public Task<Page> PopAsync() + { + return OnPopAsync(true); + } + + public Task<Page> PopAsync(bool animated) + { + return OnPopAsync(animated); + } + + public Task<Page> PopModalAsync() + { + return OnPopModal(true); + } + + public Task<Page> PopModalAsync(bool animated) + { + return OnPopModal(animated); + } + + public Task PopToRootAsync() + { + return OnPopToRootAsync(true); + } + + public Task PopToRootAsync(bool animated) + { + return OnPopToRootAsync(animated); + } + + public Task PushAsync(Page root) + { + return PushAsync(root, true); + } + + public Task PushAsync(Page root, bool animated) + { + if (root.RealParent != null) + throw new InvalidOperationException("Page must not already have a parent."); + return OnPushAsync(root, animated); + } + + public Task PushModalAsync(Page modal) + { + return PushModalAsync(modal, true); + } + + public Task PushModalAsync(Page modal, bool animated) + { + if (modal.RealParent != null) + throw new InvalidOperationException("Page must not already have a parent."); + return OnPushModal(modal, animated); + } + + public void RemovePage(Page page) + { + OnRemovePage(page); + } + + protected virtual IReadOnlyList<Page> GetModalStack() + { + INavigation currentInner = Inner; + return currentInner == null ? _modalStack.Value : currentInner.ModalStack; + } + + protected virtual IReadOnlyList<Page> GetNavigationStack() + { + INavigation currentInner = Inner; + return currentInner == null ? _pushStack.Value : currentInner.NavigationStack; + } + + protected virtual void OnInsertPageBefore(Page page, Page before) + { + INavigation currentInner = Inner; + if (currentInner == null) + { + int index = _pushStack.Value.IndexOf(before); + if (index == -1) + throw new ArgumentException("before must be in the pushed stack of the current context"); + _pushStack.Value.Insert(index, page); + } + else + { + currentInner.InsertPageBefore(page, before); + } + } + + protected virtual Task<Page> OnPopAsync(bool animated) + { + INavigation inner = Inner; + return inner == null ? Task.FromResult(Pop()) : inner.PopAsync(animated); + } + + protected virtual Task<Page> OnPopModal(bool animated) + { + INavigation innerNav = Inner; + return innerNav == null ? Task.FromResult(PopModal()) : innerNav.PopModalAsync(animated); + } + + protected virtual Task OnPopToRootAsync(bool animated) + { + INavigation currentInner = Inner; + if (currentInner == null) + { + Page root = _pushStack.Value.Last(); + _pushStack.Value.Clear(); + _pushStack.Value.Add(root); + return Task.FromResult(root); + } + return currentInner.PopToRootAsync(animated); + } + + protected virtual Task OnPushAsync(Page page, bool animated) + { + INavigation currentInner = Inner; + if (currentInner == null) + { + _pushStack.Value.Add(page); + return Task.FromResult(page); + } + return currentInner.PushAsync(page, animated); + } + + protected virtual Task OnPushModal(Page modal, bool animated) + { + INavigation currentInner = Inner; + if (currentInner == null) + { + _modalStack.Value.Add(modal); + return Task.FromResult<object>(null); + } + return currentInner.PushModalAsync(modal, animated); + } + + protected virtual void OnRemovePage(Page page) + { + INavigation currentInner = Inner; + if (currentInner == null) + { + _pushStack.Value.Remove(page); + } + else + { + currentInner.RemovePage(page); + } + } + + Page Pop() + { + List<Page> list = _pushStack.Value; + Page result = list[list.Count - 1]; + list.RemoveAt(list.Count - 1); + return result; + } + + Page PopModal() + { + List<Page> list = _modalStack.Value; + Page result = list[list.Count - 1]; + list.RemoveAt(list.Count - 1); + return result; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NavigationRequestedEventArgs.cs b/Xamarin.Forms.Core/NavigationRequestedEventArgs.cs new file mode 100644 index 00000000..b21cffce --- /dev/null +++ b/Xamarin.Forms.Core/NavigationRequestedEventArgs.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + internal class NavigationRequestedEventArgs : NavigationEventArgs + { + public NavigationRequestedEventArgs(Page page, bool animated, bool realize = true) : base(page) + { + Animated = animated; + Realize = realize; + } + + public NavigationRequestedEventArgs(Page page, Page before, bool animated) : this(page, animated) + { + BeforePage = before; + } + + public bool Animated { get; set; } + + public Page BeforePage { get; set; } + + public bool Realize { get; set; } + + public Task<bool> Task { get; set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsEx.cs b/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsEx.cs new file mode 100644 index 00000000..bb6a2b84 --- /dev/null +++ b/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsEx.cs @@ -0,0 +1,65 @@ +using System.Collections; +using System.Collections.Specialized; + +namespace Xamarin.Forms +{ + internal class NotifyCollectionChangedEventArgsEx : NotifyCollectionChangedEventArgs + { + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action) : base(action) + { + Count = count; + } + + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, IList changedItems) : base(action, changedItems) + { + Count = count; + } + + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, IList newItems, IList oldItems) : base(action, newItems, oldItems) + { + Count = count; + } + + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex) : base(action, newItems, oldItems, startingIndex) + { + Count = count; + } + + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, IList changedItems, int startingIndex) : base(action, changedItems, startingIndex) + { + Count = count; + } + + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, IList changedItems, int index, int oldIndex) : base(action, changedItems, index, oldIndex) + { + Count = count; + } + + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, object changedItem) : base(action, changedItem) + { + Count = count; + } + + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, object changedItem, int index) : base(action, changedItem, index) + { + Count = count; + } + + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, object changedItem, int index, int oldIndex) : base(action, changedItem, index, oldIndex) + { + Count = count; + } + + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, object newItem, object oldItem) : base(action, newItem, oldItem) + { + Count = count; + } + + public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, object newItem, object oldItem, int index) : base(action, newItem, oldItem, index) + { + Count = count; + } + + public int Count { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsExtensions.cs b/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsExtensions.cs new file mode 100644 index 00000000..2ddf92c4 --- /dev/null +++ b/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsExtensions.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace Xamarin.Forms +{ + internal static class NotifyCollectionChangedEventArgsExtensions + { + public static void Apply<TFrom>(this NotifyCollectionChangedEventArgs self, IList<TFrom> from, IList<object> to) + { + self.Apply((o, i, b) => to.Insert(i, o), (o, i) => to.RemoveAt(i), () => + { + to.Clear(); + for (var i = 0; i < from.Count; i++) + to.Add(from[i]); + }); + } + + public static NotifyCollectionChangedAction Apply(this NotifyCollectionChangedEventArgs self, Action<object, int, bool> insert, Action<object, int> removeAt, Action reset) + { + if (self == null) + throw new ArgumentNullException("self"); + if (reset == null) + throw new ArgumentNullException("reset"); + if (insert == null) + throw new ArgumentNullException("insert"); + if (removeAt == null) + throw new ArgumentNullException("removeAt"); + + switch (self.Action) + { + case NotifyCollectionChangedAction.Add: + if (self.NewStartingIndex < 0) + goto case NotifyCollectionChangedAction.Reset; + + for (var i = 0; i < self.NewItems.Count; i++) + insert(self.NewItems[i], i + self.NewStartingIndex, true); + + break; + + case NotifyCollectionChangedAction.Move: + if (self.NewStartingIndex < 0 || self.OldStartingIndex < 0) + goto case NotifyCollectionChangedAction.Reset; + + for (var i = 0; i < self.OldItems.Count; i++) + removeAt(self.OldItems[i], self.OldStartingIndex); + + int insertIndex = self.NewStartingIndex; + if (self.OldStartingIndex < self.NewStartingIndex) + insertIndex -= self.OldItems.Count - 1; + + for (var i = 0; i < self.OldItems.Count; i++) + insert(self.OldItems[i], insertIndex + i, false); + + break; + + case NotifyCollectionChangedAction.Remove: + if (self.OldStartingIndex < 0) + goto case NotifyCollectionChangedAction.Reset; + + for (var i = 0; i < self.OldItems.Count; i++) + removeAt(self.OldItems[i], self.OldStartingIndex); + break; + + case NotifyCollectionChangedAction.Replace: + if (self.OldStartingIndex < 0) + goto case NotifyCollectionChangedAction.Reset; + + for (var i = 0; i < self.OldItems.Count; i++) + { + removeAt(self.OldItems[i], i + self.OldStartingIndex); + insert(self.OldItems[i], i + self.OldStartingIndex, true); + } + break; + + case NotifyCollectionChangedAction.Reset: + reset(); + return NotifyCollectionChangedAction.Reset; + } + + return self.Action; + } + + public static NotifyCollectionChangedEventArgsEx WithCount(this NotifyCollectionChangedEventArgs e, int count) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Add, e.NewItems, e.NewStartingIndex); + + case NotifyCollectionChangedAction.Remove: + return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Remove, e.OldItems, e.OldStartingIndex); + + case NotifyCollectionChangedAction.Move: + return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Move, e.OldItems, e.NewStartingIndex, e.OldStartingIndex); + + case NotifyCollectionChangedAction.Replace: + return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Replace, e.NewItems, e.OldItems, e.OldStartingIndex); + + default: + return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Reset); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NullEffect.cs b/Xamarin.Forms.Core/NullEffect.cs new file mode 100644 index 00000000..509405d7 --- /dev/null +++ b/Xamarin.Forms.Core/NullEffect.cs @@ -0,0 +1,13 @@ +namespace Xamarin.Forms +{ + internal class NullEffect : Effect + { + protected override void OnAttached() + { + } + + protected override void OnDetached() + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NumericExtensions.cs b/Xamarin.Forms.Core/NumericExtensions.cs new file mode 100644 index 00000000..6333c360 --- /dev/null +++ b/Xamarin.Forms.Core/NumericExtensions.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + internal static class NumericExtensions + { + public static double Clamp(this double self, double min, double max) + { + return Math.Min(max, Math.Max(self, min)); + } + + public static int Clamp(this int self, int min, int max) + { + return Math.Min(max, Math.Max(self, min)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/NumericKeyboard.cs b/Xamarin.Forms.Core/NumericKeyboard.cs new file mode 100644 index 00000000..08096bde --- /dev/null +++ b/Xamarin.Forms.Core/NumericKeyboard.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + internal sealed class NumericKeyboard : Keyboard + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ObservableList.cs b/Xamarin.Forms.Core/ObservableList.cs new file mode 100644 index 00000000..82a6c493 --- /dev/null +++ b/Xamarin.Forms.Core/ObservableList.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace Xamarin.Forms +{ + internal class ObservableList<T> : ObservableCollection<T> + { + // There's lots of special-casing optimizations that could be done here + // but right now this is only being used for tests. + + public void AddRange(IEnumerable<T> range) + { + if (range == null) + throw new ArgumentNullException("range"); + + List<T> items = range.ToList(); + int index = Items.Count; + foreach (T item in items) + Items.Add(item); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items, index)); + } + + public void InsertRange(int index, IEnumerable<T> range) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException("index"); + if (range == null) + throw new ArgumentNullException("range"); + + int originalIndex = index; + + List<T> items = range.ToList(); + foreach (T item in items) + Items.Insert(index++, item); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items, originalIndex)); + } + + public void Move(int oldIndex, int newIndex, int count) + { + if (oldIndex < 0 || oldIndex + count > Count) + throw new ArgumentOutOfRangeException("oldIndex"); + if (newIndex < 0 || newIndex + count > Count) + throw new ArgumentOutOfRangeException("newIndex"); + + var items = new List<T>(count); + for (var i = 0; i < count; i++) + { + T item = Items[oldIndex]; + items.Add(item); + Items.RemoveAt(oldIndex); + } + + int index = newIndex; + if (newIndex > oldIndex) + index -= items.Count - 1; + + for (var i = 0; i < items.Count; i++) + Items.Insert(index + i, items[i]); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, items, newIndex, oldIndex)); + } + + public void RemoveAt(int index, int count) + { + if (index < 0 || index + count > Count) + throw new ArgumentOutOfRangeException("index"); + + T[] items = Items.Skip(index).Take(count).ToArray(); + for (int i = index; i < count; i++) + Items.RemoveAt(i); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items, index)); + } + + public void RemoveRange(IEnumerable<T> range) + { + if (range == null) + throw new ArgumentNullException("range"); + + List<T> items = range.ToList(); + foreach (T item in items) + Items.Remove(item); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items)); + } + + public void ReplaceRange(int startIndex, IEnumerable<T> items) + { + if (items == null) + throw new ArgumentNullException("items"); + + T[] ritems = items.ToArray(); + + if (startIndex < 0 || startIndex + ritems.Length > Count) + throw new ArgumentOutOfRangeException("startIndex"); + + var oldItems = new T[ritems.Length]; + for (var i = 0; i < ritems.Length; i++) + { + oldItems[i] = Items[i + startIndex]; + Items[i + startIndex] = ritems[i]; + } + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, ritems, oldItems, startIndex)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ObservableWrapper.cs b/Xamarin.Forms.Core/ObservableWrapper.cs new file mode 100644 index 00000000..e8fc0c5b --- /dev/null +++ b/Xamarin.Forms.Core/ObservableWrapper.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace Xamarin.Forms +{ + internal class ObservableWrapper<TTrack, TRestrict> : IList<TRestrict>, INotifyCollectionChanged where TTrack : Element where TRestrict : TTrack + { + readonly ObservableCollection<TTrack> _list; + + public ObservableWrapper(ObservableCollection<TTrack> list) + { + if (list == null) + throw new ArgumentNullException("list"); + + _list = list; + + list.CollectionChanged += ListOnCollectionChanged; + } + + public void Add(TRestrict item) + { + if (item == null) + throw new ArgumentNullException("item"); + if (IsReadOnly) + throw new NotSupportedException("The collection is read-only."); + + if (_list.Contains(item)) + return; + + item.Owned = true; + _list.Add(item); + } + + public void Clear() + { + if (IsReadOnly) + throw new NotSupportedException("The collection is read-only."); + + foreach (TRestrict item in _list.OfType<TRestrict>().ToArray()) + { + _list.Remove(item); + item.Owned = false; + } + } + + public bool Contains(TRestrict item) + { + return item.Owned && _list.Contains(item); + } + + public void CopyTo(TRestrict[] array, int destIndex) + { + if (array.Length - destIndex < Count) + throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + foreach (TRestrict item in this) + { + array[destIndex] = item; + destIndex++; + } + } + + public int Count + { + get { return _list.Where(i => i.Owned).OfType<TRestrict>().Count(); } + } + + public bool IsReadOnly { get; internal set; } + + public bool Remove(TRestrict item) + { + if (item == null) + throw new ArgumentNullException("item"); + if (IsReadOnly) + throw new NotSupportedException("The collection is read-only."); + + if (!item.Owned) + return false; + + if (_list.Remove(item)) + { + item.Owned = false; + return true; + } + return false; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator<TRestrict> GetEnumerator() + { + return _list.Where(i => i.Owned).OfType<TRestrict>().GetEnumerator(); + } + + public int IndexOf(TRestrict value) + { + int innerIndex = _list.IndexOf(value); + if (innerIndex == -1) + return -1; + return ToOuterIndex(innerIndex); + } + + public void Insert(int index, TRestrict item) + { + if (item == null) + throw new ArgumentNullException("item"); + if (IsReadOnly) + throw new NotSupportedException("The collection is read-only."); + + item.Owned = true; + _list.Insert(ToInnerIndex(index), item); + } + + public TRestrict this[int index] + { + get { return (TRestrict)_list[ToInnerIndex(index)]; } + set + { + int innerIndex = ToInnerIndex(index); + if (value != null) + value.Owned = true; + TTrack old = _list[innerIndex]; + _list[innerIndex] = value; + + if (old != null) + old.Owned = false; + } + } + + public void RemoveAt(int index) + { + if (IsReadOnly) + throw new NotSupportedException("The collection is read-only"); + int innerIndex = ToInnerIndex(index); + TTrack item = _list[innerIndex]; + if (item.Owned) + { + _list.RemoveAt(innerIndex); + item.Owned = false; + } + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + void ListOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + NotifyCollectionChangedEventHandler handler = CollectionChanged; + if (handler == null) + return; + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (e.NewStartingIndex == -1 || e.NewItems.Count > 1) + goto case NotifyCollectionChangedAction.Reset; + + var newItem = e.NewItems[0] as TRestrict; + if (newItem == null || !newItem.Owned) + break; + + int outerIndex = ToOuterIndex(e.NewStartingIndex); + handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, e.NewItems, outerIndex)); + break; + case NotifyCollectionChangedAction.Move: + if (e.NewStartingIndex == -1 || e.OldStartingIndex == -1 || e.NewItems.Count > 1) + goto case NotifyCollectionChangedAction.Reset; + + var movedItem = e.NewItems[0] as TRestrict; + if (movedItem == null || !movedItem.Owned) + break; + + int outerOldIndex = ToOuterIndex(e.OldStartingIndex); + int outerNewIndex = ToOuterIndex(e.NewStartingIndex); + handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, e.NewItems, outerNewIndex, outerOldIndex)); + break; + case NotifyCollectionChangedAction.Remove: + if (e.OldStartingIndex == -1 || e.OldItems.Count > 1) + goto case NotifyCollectionChangedAction.Reset; + + var removedItem = e.OldItems[0] as TRestrict; + if (removedItem == null || !removedItem.Owned) + break; + + int outerRemovedIndex = ToOuterIndex(e.OldStartingIndex); + var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItem, outerRemovedIndex); + handler(this, args); + break; + case NotifyCollectionChangedAction.Replace: + if (e.NewStartingIndex == -1 || e.OldStartingIndex == -1 || e.NewItems.Count > 1) + goto case NotifyCollectionChangedAction.Reset; + + var newReplaceItem = e.NewItems[0] as TRestrict; + var oldReplaceItem = e.OldItems[0] as TRestrict; + + if ((newReplaceItem == null || !newReplaceItem.Owned) && (oldReplaceItem == null || !oldReplaceItem.Owned)) + { + break; + } + if (newReplaceItem == null || !newReplaceItem.Owned || oldReplaceItem == null || !oldReplaceItem.Owned) + { + goto case NotifyCollectionChangedAction.Reset; + } + + int index = ToOuterIndex(e.NewStartingIndex); + + var replaceArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newReplaceItem, oldReplaceItem, index); + handler(this, replaceArgs); + break; + case NotifyCollectionChangedAction.Reset: + handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + int ToInnerIndex(int outterIndex) + { + var outerIndex = 0; + int innerIndex; + for (innerIndex = 0; innerIndex < _list.Count; innerIndex++) + { + TTrack item = _list[innerIndex]; + if (item is TRestrict && item.Owned) + { + if (outerIndex == outterIndex) + return innerIndex; + outerIndex++; + } + } + + return innerIndex; + } + + int ToOuterIndex(int innerIndex) + { + var outerIndex = 0; + for (var index = 0; index < innerIndex; index++) + { + TTrack item = _list[index]; + if (item is TRestrict && item.Owned) + { + outerIndex++; + } + } + + return outerIndex; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/OnIdiom.cs b/Xamarin.Forms.Core/OnIdiom.cs new file mode 100644 index 00000000..9376d5b9 --- /dev/null +++ b/Xamarin.Forms.Core/OnIdiom.cs @@ -0,0 +1,21 @@ +namespace Xamarin.Forms +{ + public class OnIdiom<T> + { + public T Phone { get; set; } + + public T Tablet { get; set; } + + public static implicit operator T(OnIdiom<T> onIdiom) + { + switch (Device.Idiom) + { + default: + case TargetIdiom.Phone: + return onIdiom.Phone; + case TargetIdiom.Tablet: + return onIdiom.Tablet; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/OnPlatform.cs b/Xamarin.Forms.Core/OnPlatform.cs new file mode 100644 index 00000000..b66167fa --- /dev/null +++ b/Xamarin.Forms.Core/OnPlatform.cs @@ -0,0 +1,27 @@ +namespace Xamarin.Forms +{ + public class OnPlatform<T> + { + public T Android { get; set; } + + public T iOS { get; set; } + + public T WinPhone { get; set; } + + public static implicit operator T(OnPlatform<T> onPlatform) + { + switch (Device.OS) + { + case TargetPlatform.iOS: + return onPlatform.iOS; + case TargetPlatform.Android: + return onPlatform.Android; + case TargetPlatform.Windows: + case TargetPlatform.WinPhone: + return onPlatform.WinPhone; + } + + return onPlatform.iOS; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/OpenGLView.cs b/Xamarin.Forms.Core/OpenGLView.cs new file mode 100644 index 00000000..530a2f0d --- /dev/null +++ b/Xamarin.Forms.Core/OpenGLView.cs @@ -0,0 +1,38 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_OpenGLViewRenderer))] + public sealed class OpenGLView : View, IOpenGlViewController + { + #region Statics + + public static readonly BindableProperty HasRenderLoopProperty = BindableProperty.Create("HasRenderLoop", typeof(bool), typeof(OpenGLView), default(bool)); + + #endregion + + public bool HasRenderLoop + { + get { return (bool)GetValue(HasRenderLoopProperty); } + set { SetValue(HasRenderLoopProperty, value); } + } + + public Action<Rectangle> OnDisplay { get; set; } + + event EventHandler IOpenGlViewController.DisplayRequested + { + add { DisplayRequested += value; } + remove { DisplayRequested -= value; } + } + + public void Display() + { + EventHandler handler = DisplayRequested; + if (handler != null) + handler(this, EventArgs.Empty); + } + + event EventHandler DisplayRequested; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/OrderedDictionary.cs b/Xamarin.Forms.Core/OrderedDictionary.cs new file mode 100644 index 00000000..028979c5 --- /dev/null +++ b/Xamarin.Forms.Core/OrderedDictionary.cs @@ -0,0 +1,451 @@ +// +// OrderedDictionary.cs +// +// Author: +// Eric Maupin <me@ermau.com> +// +// Copyright (c) 2009 Eric Maupin (http://www.ermau.com) +// +// 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.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Xamarin.Forms; + +namespace Cadenza.Collections +{ + internal sealed class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IList<KeyValuePair<TKey, TValue>> + { + readonly Dictionary<TKey, TValue> _dict; + readonly List<TKey> _keyOrder; + readonly ICollection<KeyValuePair<TKey, TValue>> _kvpCollection; + readonly ReadOnlyCollection<TKey> _roKeys; + + readonly ReadOnlyValueCollection _roValues; + + public OrderedDictionary() : this(0) + { + } + + public OrderedDictionary(int capacity) : this(capacity, EqualityComparer<TKey>.Default) + { + } + + public OrderedDictionary(IEqualityComparer<TKey> equalityComparer) : this(0, equalityComparer) + { + } + + public OrderedDictionary(int capacity, IEqualityComparer<TKey> equalityComparer) + { + _dict = new Dictionary<TKey, TValue>(capacity, equalityComparer); + _kvpCollection = _dict; + _keyOrder = new List<TKey>(capacity); + _roKeys = new ReadOnlyCollection<TKey>(_keyOrder); + _roValues = new ReadOnlyValueCollection(this); + } + + public OrderedDictionary(ICollection<KeyValuePair<TKey, TValue>> dictionary) : this(dictionary, EqualityComparer<TKey>.Default) + { + } + + public OrderedDictionary(ICollection<KeyValuePair<TKey, TValue>> dictionary, IEqualityComparer<TKey> equalityComparer) : this(dictionary != null ? dictionary.Count : 0, equalityComparer) + { + if (dictionary == null) + throw new ArgumentNullException("dictionary"); + + foreach (KeyValuePair<TKey, TValue> kvp in dictionary) + Add(kvp.Key, kvp.Value); + } + + /// <summary> + /// Gets the equality comparer being used for + /// <typeparam name="TKey" /> + /// . + /// </summary> + public IEqualityComparer<TKey> Comparer + { + get { return _dict.Comparer; } + } + + /// <summary> + /// Gets the value at the specified index. + /// </summary> + /// <param name="index">The index to get the value at.</param> + /// <returns>The value at the specified index.</returns> + /// <exception cref="IndexOutOfRangeException"> + /// <paramref name="index" /> is less than 0 or greater than + /// <see cref="Count" />. + /// </exception> + public TValue this[int index] + { + get { return _dict[_keyOrder[index]]; } + } + + void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) + { + Add(item.Key, item.Value); + } + + /// <summary> + /// Clears the dictionary. + /// </summary> + public void Clear() + { + _dict.Clear(); + _keyOrder.Clear(); + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) + { + return _kvpCollection.Contains(item); + } + + void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array"); + if (Count > array.Length - arrayIndex) + throw new ArgumentException("Not enough space in array to copy"); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException("arrayIndex"); + + for (var i = 0; i < _keyOrder.Count; ++i) + { + TKey key = _keyOrder[i]; + array[arrayIndex++] = new KeyValuePair<TKey, TValue>(key, _dict[key]); + } + } + + /// <summary> + /// Gets the number of items in the dictionary. + /// </summary> + public int Count + { + get { return _dict.Count; } + } + + bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly + { + get { return false; } + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) + { + return _kvpCollection.Remove(item) && _keyOrder.Remove(item.Key); + } + + /// <summary> + /// Adds the <paramref name="key" /> and <paramref name="value" /> to the dictionary. + /// </summary> + /// <param name="key">The key to associate with the <paramref name="value" />.</param> + /// <param name="value">The value to add.</param> + /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception> + /// <exception cref="ArgumentException"><paramref name="key" /> already exists in the dictionary.</exception> + public void Add(TKey key, TValue value) + { + _dict.Add(key, value); + _keyOrder.Add(key); + } + + /// <summary> + /// Gets whether or not <paramref name="key" /> is in the dictionary. + /// </summary> + /// <param name="key">The key to look for.</param> + /// <returns><c>true</c> if the key was found, <c>false</c> if not.</returns> + /// <exception cref="ArgumentNullException">If <paramref name="key" /> is <c>null</c>.</exception> + public bool ContainsKey(TKey key) + { + return _dict.ContainsKey(key); + } + + /// <summary> + /// Gets or sets the value associated with <paramref name="key" />. + /// </summary> + /// <param name="key">The key to get or set the value for.</param> + /// <returns>The value associated with the key.</returns> + /// <exception cref="KeyNotFoundException"><paramref name="key" /> was not found attempting to get.</exception> + public TValue this[TKey key] + { + get { return _dict[key]; } + set + { + if (!_dict.ContainsKey(key)) + _keyOrder.Add(key); + + _dict[key] = value; + } + } + + /// <summary> + /// Gets a read only collection of keys in the dictionary. + /// </summary> + public ICollection<TKey> Keys + { + get { return _roKeys; } + } + + /// <summary> + /// Removes the key and associated value from the dictionary if found. + /// </summary> + /// <param name="key">The key to remove.</param> + /// <returns><c>true</c> if the key was found, <c>false</c> if not.</returns> + /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception> + public bool Remove(TKey key) + { + return _dict.Remove(key) && _keyOrder.Remove(key); + } + + /// <summary> + /// Attempts to get the <paramref name="value" /> for the <paramref name="key" />. + /// </summary> + /// <param name="key">The key to search for.</param> + /// <param name="value">The value, if found.</param> + /// <returns><c>true</c> if the key was found, <c>false</c> otherwise.</returns> + /// <exception cref="ArgumentNullException">If <paramref name="key" /> is <c>null</c>.</exception> + public bool TryGetValue(TKey key, out TValue value) + { + return _dict.TryGetValue(key, out value); + } + + /// <summary> + /// Gets a read only collection of values in the dictionary. + /// </summary> + public ICollection<TValue> Values + { + get { return _roValues; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + foreach (TKey key in _keyOrder) + yield return new KeyValuePair<TKey, TValue>(key, this[key]); + } + + int IList<KeyValuePair<TKey, TValue>>.IndexOf(KeyValuePair<TKey, TValue> item) + { + return _keyOrder.IndexOf(item.Key); + } + + void IList<KeyValuePair<TKey, TValue>>.Insert(int index, KeyValuePair<TKey, TValue> item) + { + Insert(index, item.Key, item.Value); + } + + KeyValuePair<TKey, TValue> IList<KeyValuePair<TKey, TValue>>.this[int index] + { + get { return new KeyValuePair<TKey, TValue>(_keyOrder[index], this[index]); } + set + { + _keyOrder[index] = value.Key; + _dict[value.Key] = value.Value; + } + } + + /// <summary> + /// Removes they key and associated value from the dictionary located at <paramref name="index" />. + /// </summary> + /// <param name="index">The index at which to remove an item.</param> + public void RemoveAt(int index) + { + TKey key = _keyOrder[index]; + Remove(key); + } + + /// <summary> + /// Gets whether or not <paramref name="value" /> is in the dictionary. + /// </summary> + /// <param name="value">The value to look for.</param> + /// <returns><c>true</c> if the value was found, <c>false</c> if not.</returns> + public bool ContainsValue(TValue value) + { + return _dict.ContainsValue(value); + } + + /// <summary> + /// Gets the index of <paramref name="key" />. + /// </summary> + /// <param name="key">The key to find the index of.</param> + /// <returns>-1 if the key was not found, the index otherwise.</returns> + /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception> + public int IndexOf(TKey key) + { + if (key == null) + throw new ArgumentNullException("key"); + + return _keyOrder.IndexOf(key); + } + + /// <summary> + /// Gets the index of <paramref name="key" /> starting with <paramref name="startIndex" />. + /// </summary> + /// <param name="key">The key to find the index of.</param> + /// <param name="startIndex">The index to start the search at.</param> + /// <returns>-1 if the key was not found, the index otherwise.</returns> + /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex" /> is not within the valid range of indexes.</exception> + public int IndexOf(TKey key, int startIndex) + { + if (key == null) + throw new ArgumentNullException("key"); + + return _keyOrder.IndexOf(key, startIndex); + } + + /// <summary> + /// Gets the index of <paramref name="key" /> between the range given by <paramref name="startIndex" /> and + /// <paramref name="count" />. + /// </summary> + /// <param name="key">The key to find the index of.</param> + /// <param name="startIndex">The index to start the search at.</param> + /// <param name="count">How many items to search, including the <paramref name="startIndex" />.</param> + /// <returns>-1 if the key was not found, the index otherwise.</returns> + /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex" /> is not within the valid range of indexes.</exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="startIndex" /> and <paramref name="count" /> are not a + /// valid range. + /// </exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="count" /> is less than 0.</exception> + public int IndexOf(TKey key, int startIndex, int count) + { + if (key == null) + throw new ArgumentNullException("key"); + + return _keyOrder.IndexOf(key, startIndex, count); + } + + /// <summary> + /// Inserts the <paramref name="key" /> and <paramref name="value" /> at the specified index. + /// </summary> + /// <param name="index">The index to insert the key and value at.</param> + /// <param name="key">The key to assicate with the <paramref name="value" />.</param> + /// <param name="value">The value to insert.</param> + /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index" /> is less than 0 or greater than + /// <see cref="Count" /> + /// </exception> + public void Insert(int index, TKey key, TValue value) + { + _keyOrder.Insert(index, key); + _dict.Add(key, value); + } + + public void Move(int oldIndex, int newIndex) + { + TKey key = _keyOrder[oldIndex]; + _keyOrder.RemoveAt(oldIndex); + _keyOrder.Insert(newIndex, key); + } + + class ReadOnlyValueCollection : IList<TValue> + { + readonly OrderedDictionary<TKey, TValue> _odict; + + public ReadOnlyValueCollection(OrderedDictionary<TKey, TValue> dict) + { + _odict = dict; + } + + public void Add(TValue item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(TValue item) + { + return _odict.ContainsValue(item); + } + + public void CopyTo(TValue[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array"); + if (Count > array.Length - arrayIndex) + throw new ArgumentException("Not enough space in array to copy"); + if (arrayIndex < 0 || arrayIndex > array.Length) + throw new ArgumentOutOfRangeException("arrayIndex"); + + for (var i = 0; i < _odict.Count; ++i) + array[arrayIndex++] = _odict[i]; + } + + public int Count + { + get { return _odict.Count; } + } + + public bool IsReadOnly + { + get { return true; } + } + + public bool Remove(TValue item) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator<TValue> GetEnumerator() + { + for (var i = 0; i < _odict._keyOrder.Count; ++i) + yield return _odict[i]; + } + + public int IndexOf(TValue item) + { + return _odict._dict.Values.IndexOf(item); + } + + public void Insert(int index, TValue item) + { + throw new NotSupportedException(); + } + + public TValue this[int index] + { + get { return _odict[index]; } + set { throw new NotSupportedException(); } + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Page.cs b/Xamarin.Forms.Core/Page.cs new file mode 100644 index 00000000..903f4b53 --- /dev/null +++ b/Xamarin.Forms.Core/Page.cs @@ -0,0 +1,403 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_PageRenderer))] + public class Page : VisualElement, ILayout + { + internal const string BusySetSignalName = "Xamarin.BusySet"; + + internal const string AlertSignalName = "Xamarin.SendAlert"; + + internal const string ActionSheetSignalName = "Xamarin.ShowActionSheet"; + + internal static readonly BindableProperty IgnoresContainerAreaProperty = BindableProperty.Create("IgnoresContainerArea", typeof(bool), typeof(Page), false); + + public static readonly BindableProperty BackgroundImageProperty = BindableProperty.Create("BackgroundImage", typeof(string), typeof(Page), default(string)); + + public static readonly BindableProperty IsBusyProperty = BindableProperty.Create("IsBusy", typeof(bool), typeof(Page), false, propertyChanged: (bo, o, n) => ((Page)bo).OnPageBusyChanged()); + + public static readonly BindableProperty PaddingProperty = BindableProperty.Create("Padding", typeof(Thickness), typeof(Page), default(Thickness), propertyChanged: (bindable, old, newValue) => + { + var layout = (Page)bindable; + layout.UpdateChildrenLayout(); + }); + + public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(Page), null); + + public static readonly BindableProperty IconProperty = BindableProperty.Create("Icon", typeof(FileImageSource), typeof(Page), default(FileImageSource)); + + bool _allocatedFlag; + Rectangle _containerArea; + + bool _containerAreaSet; + + bool _hasAppeared; + + ReadOnlyCollection<Element> _logicalChildren; + + public Page() + { + var toolbarItems = new ObservableCollection<ToolbarItem>(); + toolbarItems.CollectionChanged += OnToolbarItemsCollectionChanged; + ToolbarItems = toolbarItems; + InternalChildren.CollectionChanged += InternalChildrenOnCollectionChanged; + } + + public string BackgroundImage + { + get { return (string)GetValue(BackgroundImageProperty); } + set { SetValue(BackgroundImageProperty, value); } + } + + public FileImageSource Icon + { + get { return (FileImageSource)GetValue(IconProperty); } + set { SetValue(IconProperty, value); } + } + + public bool IsBusy + { + get { return (bool)GetValue(IsBusyProperty); } + set { SetValue(IsBusyProperty, value); } + } + + public Thickness Padding + { + get { return (Thickness)GetValue(PaddingProperty); } + set { SetValue(PaddingProperty, value); } + } + + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + public IList<ToolbarItem> ToolbarItems { get; internal set; } + + internal Rectangle ContainerArea + { + get { return _containerArea; } + set + { + if (_containerArea == value) + return; + _containerAreaSet = true; + _containerArea = value; + ForceLayout(); + } + } + + internal bool IgnoresContainerArea + { + get { return (bool)GetValue(IgnoresContainerAreaProperty); } + set { SetValue(IgnoresContainerAreaProperty, value); } + } + + internal ObservableCollection<Element> InternalChildren { get; } = new ObservableCollection<Element>(); + + internal override ReadOnlyCollection<Element> LogicalChildren + { + get { return _logicalChildren ?? (_logicalChildren = new ReadOnlyCollection<Element>(InternalChildren)); } + } + + public event EventHandler LayoutChanged; + + public event EventHandler Appearing; + + public event EventHandler Disappearing; + + public Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons) + { + var args = new ActionSheetArguments(title, cancel, destruction, buttons); + MessagingCenter.Send(this, ActionSheetSignalName, args); + return args.Result.Task; + } + + public Task DisplayAlert(string title, string message, string cancel) + { + return DisplayAlert(title, message, null, cancel); + } + + public Task<bool> DisplayAlert(string title, string message, string accept, string cancel) + { + if (string.IsNullOrEmpty(cancel)) + throw new ArgumentNullException("cancel"); + + var args = new AlertArguments(title, message, accept, cancel); + MessagingCenter.Send(this, AlertSignalName, args); + return args.Result.Task; + } + + public void ForceLayout() + { + SizeAllocated(Width, Height); + } + + public bool SendBackButtonPressed() + { + return OnBackButtonPressed(); + } + + protected virtual void LayoutChildren(double x, double y, double width, double height) + { + var area = new Rectangle(x, y, width, height); + Rectangle originalArea = area; + if (_containerAreaSet) + { + area = ContainerArea; + area.X += Padding.Left; + area.Y += Padding.Right; + area.Width -= Padding.HorizontalThickness; + area.Height -= Padding.VerticalThickness; + area.Width = Math.Max(0, area.Width); + area.Height = Math.Max(0, area.Height); + } + + foreach (Element element in LogicalChildren) + { + var child = element as VisualElement; + if (child == null) + continue; + var page = child as Page; + if (page != null && page.IgnoresContainerArea) + { + Forms.Layout.LayoutChildIntoBoundingRegion(child, originalArea); + } + else + { + Forms.Layout.LayoutChildIntoBoundingRegion(child, area); + } + } + } + + protected virtual void OnAppearing() + { + } + + protected virtual bool OnBackButtonPressed() + { + var application = RealParent as Application; + if (application == null || this == application.MainPage) + return false; + + var canceled = false; + EventHandler handler = (sender, args) => { canceled = true; }; + application.PopCanceled += handler; + Navigation.PopModalAsync().ContinueWith(t => { throw t.Exception; }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext()); + + application.PopCanceled -= handler; + return !canceled; + } + + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + foreach (ToolbarItem toolbarItem in ToolbarItems) + { + SetInheritedBindingContext(toolbarItem, BindingContext); + } + } + + protected virtual void OnChildMeasureInvalidated(object sender, EventArgs e) + { + InvalidationTrigger trigger = (e as InvalidationEventArgs)?.Trigger ?? InvalidationTrigger.Undefined; + OnChildMeasureInvalidated((VisualElement)sender, trigger); + } + + protected virtual void OnDisappearing() + { + } + + protected override void OnParentSet() + { + if (!Application.IsApplicationOrNull(RealParent) && !(RealParent is Page)) + throw new InvalidOperationException("Parent of a Page must also be a Page"); + base.OnParentSet(); + } + + protected override void OnSizeAllocated(double width, double height) + { + _allocatedFlag = true; + base.OnSizeAllocated(width, height); + UpdateChildrenLayout(); + } + + protected void UpdateChildrenLayout() + { + if (!ShouldLayoutChildren()) + return; + + var startingLayout = new List<Rectangle>(LogicalChildren.Count); + foreach (VisualElement c in LogicalChildren) + { + startingLayout.Add(c.Bounds); + } + + double x = Padding.Left; + double y = Padding.Top; + double w = Math.Max(0, Width - Padding.HorizontalThickness); + double h = Math.Max(0, Height - Padding.VerticalThickness); + + LayoutChildren(x, y, w, h); + + for (var i = 0; i < LogicalChildren.Count; i++) + { + var c = (VisualElement)LogicalChildren[i]; + + if (c.Bounds != startingLayout[i]) + { + EventHandler handler = LayoutChanged; + if (handler != null) + handler(this, EventArgs.Empty); + return; + } + } + } + + internal virtual void OnChildMeasureInvalidated(VisualElement child, InvalidationTrigger trigger) + { + var container = this as IPageContainer<Page>; + if (container != null) + { + Page page = container.CurrentPage; + if (page != null && page.IsVisible && (!page.IsPlatformEnabled || !page.IsNativeStateConsistent)) + return; + } + else + { + for (var i = 0; i < LogicalChildren.Count; i++) + { + var v = LogicalChildren[i] as VisualElement; + if (v != null && v.IsVisible && (!v.IsPlatformEnabled || !v.IsNativeStateConsistent)) + return; + } + } + + _allocatedFlag = false; + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + if (!_allocatedFlag && Width >= 0 && Height >= 0) + { + SizeAllocated(Width, Height); + } + } + + internal void SendAppearing() + { + if (_hasAppeared) + return; + + _hasAppeared = true; + + if (IsBusy) + MessagingCenter.Send(this, BusySetSignalName, true); + + OnAppearing(); + EventHandler handler = Appearing; + if (handler != null) + handler(this, EventArgs.Empty); + + var pageContainer = this as IPageContainer<Page>; + pageContainer?.CurrentPage?.SendAppearing(); + } + + internal void SendDisappearing() + { + if (!_hasAppeared) + return; + + _hasAppeared = false; + + if (IsBusy) + MessagingCenter.Send(this, BusySetSignalName, false); + + var pageContainer = this as IPageContainer<Page>; + pageContainer?.CurrentPage?.SendDisappearing(); + + OnDisappearing(); + EventHandler handler = Disappearing; + if (handler != null) + handler(this, EventArgs.Empty); + } + + void InternalChildrenOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + { + foreach (VisualElement item in e.OldItems.OfType<VisualElement>()) + OnInternalRemoved(item); + } + + if (e.NewItems != null) + { + foreach (VisualElement item in e.NewItems.OfType<VisualElement>()) + OnInternalAdded(item); + } + } + + void OnInternalAdded(VisualElement view) + { + view.MeasureInvalidated += OnChildMeasureInvalidated; + + OnChildAdded(view); + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + void OnInternalRemoved(VisualElement view) + { + view.MeasureInvalidated -= OnChildMeasureInvalidated; + + OnChildRemoved(view); + } + + void OnPageBusyChanged() + { + if (!_hasAppeared) + return; + + MessagingCenter.Send(this, BusySetSignalName, IsBusy); + } + + void OnToolbarItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + { + if (args.Action != NotifyCollectionChangedAction.Add) + return; + foreach (IElement item in args.NewItems) + item.Parent = this; + } + + bool ShouldLayoutChildren() + { + if (!LogicalChildren.Any() || Width <= 0 || Height <= 0 || !IsNativeStateConsistent) + return false; + + var container = this as IPageContainer<Page>; + if (container != null && container.CurrentPage != null) + { + if (InternalChildren.Contains(container.CurrentPage)) + return container.CurrentPage.IsPlatformEnabled && container.CurrentPage.IsNativeStateConsistent; + return true; + } + + var any = false; + for (var i = 0; i < LogicalChildren.Count; i++) + { + var v = LogicalChildren[i] as VisualElement; + if (v != null && (!v.IsPlatformEnabled || !v.IsNativeStateConsistent)) + { + any = true; + break; + } + } + return !any; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/PanGestureRecognizer.cs b/Xamarin.Forms.Core/PanGestureRecognizer.cs new file mode 100644 index 00000000..3fbf2f62 --- /dev/null +++ b/Xamarin.Forms.Core/PanGestureRecognizer.cs @@ -0,0 +1,37 @@ +using System; + +namespace Xamarin.Forms +{ + public class PanGestureRecognizer : GestureRecognizer, IPanGestureController + { + public static readonly BindableProperty TouchPointsProperty = BindableProperty.Create("TouchPoints", typeof(int), typeof(PanGestureRecognizer), 1); + + public int TouchPoints + { + get { return (int)GetValue(TouchPointsProperty); } + set { SetValue(TouchPointsProperty, value); } + } + + void IPanGestureController.SendPan(Element sender, double totalX, double totalY, int gestureId) + { + PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Running, gestureId, totalX, totalY)); + } + + void IPanGestureController.SendPanCanceled(Element sender, int gestureId) + { + PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Canceled, gestureId)); + } + + void IPanGestureController.SendPanCompleted(Element sender, int gestureId) + { + PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Completed, gestureId)); + } + + void IPanGestureController.SendPanStarted(Element sender, int gestureId) + { + PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Started, gestureId)); + } + + public event EventHandler<PanUpdatedEventArgs> PanUpdated; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/PanUpdatedEventArgs.cs b/Xamarin.Forms.Core/PanUpdatedEventArgs.cs new file mode 100644 index 00000000..4c9e9481 --- /dev/null +++ b/Xamarin.Forms.Core/PanUpdatedEventArgs.cs @@ -0,0 +1,27 @@ +using System; + +namespace Xamarin.Forms +{ + public class PanUpdatedEventArgs : EventArgs + { + public PanUpdatedEventArgs(GestureStatus type, int gestureId, double totalx, double totaly) : this(type, gestureId) + { + TotalX = totalx; + TotalY = totaly; + } + + public PanUpdatedEventArgs(GestureStatus type, int gestureId) + { + StatusType = type; + GestureId = gestureId; + } + + public int GestureId { get; } + + public GestureStatus StatusType { get; } + + public double TotalX { get; } + + public double TotalY { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ParameterAttribute.cs b/Xamarin.Forms.Core/ParameterAttribute.cs new file mode 100644 index 00000000..d8a07267 --- /dev/null +++ b/Xamarin.Forms.Core/ParameterAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class ParameterAttribute : Attribute + { + public ParameterAttribute(string name) + { + Name = name; + } + + public string Name { get; set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Performance.cs b/Xamarin.Forms.Core/Performance.cs new file mode 100644 index 00000000..e627817b --- /dev/null +++ b/Xamarin.Forms.Core/Performance.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Xamarin.Forms +{ + internal static class Performance + { + static readonly Dictionary<string, Stats> Statistics = new Dictionary<string, Stats>(); + + [Conditional("PERF")] + public static void Clear() + { + Statistics.Clear(); + } + + public static void Count(string tag = null, [CallerFilePath] string path = null, [CallerMemberName] string member = null) + { + string id = path + ":" + member + (tag != null ? "-" + tag : string.Empty); + + Stats stats; + if (!Statistics.TryGetValue(id, out stats)) + Statistics[id] = stats = new Stats(); + + stats.CallCount++; + } + + [Conditional("PERF")] + public static void DumpStats() + { + Debug.WriteLine(GetStats()); + } + + public static string GetStats() + { + var b = new StringBuilder(); + b.AppendLine("ID | Call Count | Total Time | Avg Time"); + foreach (KeyValuePair<string, Stats> kvp in Statistics.OrderBy(kvp => kvp.Key)) + { + string key = ShortenPath(kvp.Key); + double total = TimeSpan.FromTicks(kvp.Value.TotalTime).TotalMilliseconds; + double avg = total / kvp.Value.CallCount; + b.AppendFormat("{0,-80} | {1,-10} | {2,-10}ms | {3,-8}ms", key, kvp.Value.CallCount, total, avg); + b.AppendLine(); + } + return b.ToString(); + } + + [Conditional("PERF")] + public static void Start(string tag = null, [CallerFilePath] string path = null, [CallerMemberName] string member = null) + { + string id = path + ":" + member + (tag != null ? "-" + tag : string.Empty); + + Stats stats; + if (!Statistics.TryGetValue(id, out stats)) + Statistics[id] = stats = new Stats(); + + stats.CallCount++; + stats.StartTimes.Push(Stopwatch.GetTimestamp()); + } + + [Conditional("PERF")] + public static void Stop(string tag = null, [CallerFilePath] string path = null, [CallerMemberName] string member = null) + { + string id = path + ":" + member + (tag != null ? "-" + tag : string.Empty); + long stop = Stopwatch.GetTimestamp(); + + Stats stats = Statistics[id]; + long start = stats.StartTimes.Pop(); + if (!stats.StartTimes.Any()) + stats.TotalTime += stop - start; + } + + static string ShortenPath(string path) + { + int index = path.IndexOf("Xamarin.Forms."); + if (index > -1) + path = path.Substring(index + 14); + + return path; + } + + class Stats + { + public readonly Stack<long> StartTimes = new Stack<long>(); + public int CallCount; + public long TotalTime; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Picker.cs b/Xamarin.Forms.Core/Picker.cs new file mode 100644 index 00000000..733f2079 --- /dev/null +++ b/Xamarin.Forms.Core/Picker.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_PickerRenderer))] + public class Picker : View + { + public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(Picker), default(string)); + + public static readonly BindableProperty SelectedIndexProperty = BindableProperty.Create("SelectedIndex", typeof(int), typeof(Picker), -1, BindingMode.TwoWay, + propertyChanged: (bindable, oldvalue, newvalue) => + { + EventHandler eh = ((Picker)bindable).SelectedIndexChanged; + if (eh != null) + eh(bindable, EventArgs.Empty); + }, coerceValue: CoerceSelectedIndex); + + public Picker() + { + Items = new ObservableList<string>(); + ((ObservableList<string>)Items).CollectionChanged += OnItemsCollectionChanged; + } + + public IList<string> Items { get; } + + public int SelectedIndex + { + get { return (int)GetValue(SelectedIndexProperty); } + set { SetValue(SelectedIndexProperty, value); } + } + + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + public event EventHandler SelectedIndexChanged; + + static object CoerceSelectedIndex(BindableObject bindable, object value) + { + var picker = (Picker)bindable; + return picker.Items == null ? -1 : ((int)value).Clamp(-1, picker.Items.Count - 1); + } + + void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + SelectedIndex = SelectedIndex.Clamp(-1, Items.Count - 1); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/PinchGestureRecognizer.cs b/Xamarin.Forms.Core/PinchGestureRecognizer.cs new file mode 100644 index 00000000..aef07fbb --- /dev/null +++ b/Xamarin.Forms.Core/PinchGestureRecognizer.cs @@ -0,0 +1,51 @@ +using System; + +namespace Xamarin.Forms +{ + public sealed class PinchGestureRecognizer : GestureRecognizer, IPinchGestureController + { + bool IPinchGestureController.IsPinching { get; set; } + + void IPinchGestureController.SendPinch(Element sender, double delta, Point currentScalePoint) + { + EventHandler<PinchGestureUpdatedEventArgs> handler = PinchUpdated; + if (handler != null) + { + handler(sender, new PinchGestureUpdatedEventArgs(GestureStatus.Running, delta, currentScalePoint)); + } + ((IPinchGestureController)this).IsPinching = true; + } + + void IPinchGestureController.SendPinchCanceled(Element sender) + { + EventHandler<PinchGestureUpdatedEventArgs> handler = PinchUpdated; + if (handler != null) + { + handler(sender, new PinchGestureUpdatedEventArgs(GestureStatus.Canceled)); + } + ((IPinchGestureController)this).IsPinching = false; + } + + void IPinchGestureController.SendPinchEnded(Element sender) + { + EventHandler<PinchGestureUpdatedEventArgs> handler = PinchUpdated; + if (handler != null) + { + handler(sender, new PinchGestureUpdatedEventArgs(GestureStatus.Completed)); + } + ((IPinchGestureController)this).IsPinching = false; + } + + void IPinchGestureController.SendPinchStarted(Element sender, Point initialScalePoint) + { + EventHandler<PinchGestureUpdatedEventArgs> handler = PinchUpdated; + if (handler != null) + { + handler(sender, new PinchGestureUpdatedEventArgs(GestureStatus.Started, 1, initialScalePoint)); + } + ((IPinchGestureController)this).IsPinching = true; + } + + public event EventHandler<PinchGestureUpdatedEventArgs> PinchUpdated; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/PinchGestureUpdatedEventArgs.cs b/Xamarin.Forms.Core/PinchGestureUpdatedEventArgs.cs new file mode 100644 index 00000000..a5c0eacb --- /dev/null +++ b/Xamarin.Forms.Core/PinchGestureUpdatedEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace Xamarin.Forms +{ + public class PinchGestureUpdatedEventArgs : EventArgs + { + public PinchGestureUpdatedEventArgs(GestureStatus status, double scale, Point origin) : this(status) + { + ScaleOrigin = origin; + Scale = scale; + } + + public PinchGestureUpdatedEventArgs(GestureStatus status) + { + Status = status; + } + + public double Scale { get; } = 1; + + public Point ScaleOrigin { get; } + + public GestureStatus Status { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/PlatformEffect.cs b/Xamarin.Forms.Core/PlatformEffect.cs new file mode 100644 index 00000000..e92231ce --- /dev/null +++ b/Xamarin.Forms.Core/PlatformEffect.cs @@ -0,0 +1,28 @@ +using System.ComponentModel; + +namespace Xamarin.Forms +{ + public abstract class PlatformEffect<TContainer, TControl> : Effect where TContainer : class where TControl : class + { + public TContainer Container { get; internal set; } + + public TControl Control { get; internal set; } + + protected virtual void OnElementPropertyChanged(PropertyChangedEventArgs args) + { + } + + internal override void SendDetached() + { + base.SendDetached(); + Container = null; + Control = null; + } + + internal override void SendOnElementPropertyChanged(PropertyChangedEventArgs args) + { + if (IsAttached) + OnElementPropertyChanged(args); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Point.cs b/Xamarin.Forms.Core/Point.cs new file mode 100644 index 00000000..5c6f5471 --- /dev/null +++ b/Xamarin.Forms.Core/Point.cs @@ -0,0 +1,95 @@ +using System; +using System.Diagnostics; +using System.Globalization; + +namespace Xamarin.Forms +{ + [DebuggerDisplay("X={X}, Y={Y}")] + [TypeConverter(typeof(PointTypeConverter))] + public struct Point + { + public double X { get; set; } + + public double Y { get; set; } + + public static Point Zero = new Point(); + + public override string ToString() + { + return string.Format("{{X={0} Y={1}}}", X.ToString(CultureInfo.InvariantCulture), Y.ToString(CultureInfo.InvariantCulture)); + } + + public Point(double x, double y) : this() + { + X = x; + Y = y; + } + + public Point(Size sz) : this() + { + X = sz.Width; + Y = sz.Height; + } + + public override bool Equals(object o) + { + if (!(o is Point)) + return false; + + return this == (Point)o; + } + + public override int GetHashCode() + { + return X.GetHashCode() ^ (Y.GetHashCode() * 397); + } + + public Point Offset(double dx, double dy) + { + Point p = this; + p.X += dx; + p.Y += dy; + return p; + } + + public Point Round() + { + return new Point(Math.Round(X), Math.Round(Y)); + } + + public bool IsEmpty + { + get { return (X == 0) && (Y == 0); } + } + + public static explicit operator Size(Point pt) + { + return new Size(pt.X, pt.Y); + } + + public static Point operator +(Point pt, Size sz) + { + return new Point(pt.X + sz.Width, pt.Y + sz.Height); + } + + public static Point operator -(Point pt, Size sz) + { + return new Point(pt.X - sz.Width, pt.Y - sz.Height); + } + + public static bool operator ==(Point ptA, Point ptB) + { + return (ptA.X == ptB.X) && (ptA.Y == ptB.Y); + } + + public static bool operator !=(Point ptA, Point ptB) + { + return (ptA.X != ptB.X) || (ptA.Y != ptB.Y); + } + + public double Distance(Point other) + { + return Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/PointTypeConverter.cs b/Xamarin.Forms.Core/PointTypeConverter.cs new file mode 100644 index 00000000..9b949988 --- /dev/null +++ b/Xamarin.Forms.Core/PointTypeConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms +{ + public class PointTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + double x, y; + string[] xy = value.Split(','); + if (xy.Length == 2 && double.TryParse(xy[0], NumberStyles.Number, CultureInfo.InvariantCulture, out x) && double.TryParse(xy[1], NumberStyles.Number, CultureInfo.InvariantCulture, out y)) + return new Point(x, y); + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Point))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/PreserveAttribute.cs b/Xamarin.Forms.Core/PreserveAttribute.cs new file mode 100644 index 00000000..41487c86 --- /dev/null +++ b/Xamarin.Forms.Core/PreserveAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage( + AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | + AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate | AttributeTargets.All)] + internal class PreserveAttribute : Attribute + { + public bool AllMembers; + public bool Conditional; + + public PreserveAttribute(bool allMembers, bool conditional) + { + AllMembers = allMembers; + Conditional = conditional; + } + + public PreserveAttribute() + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ProgressBar.cs b/Xamarin.Forms.Core/ProgressBar.cs new file mode 100644 index 00000000..cd8addf6 --- /dev/null +++ b/Xamarin.Forms.Core/ProgressBar.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_ProgressBarRenderer))] + public class ProgressBar : View + { + public static readonly BindableProperty ProgressProperty = BindableProperty.Create("Progress", typeof(double), typeof(ProgressBar), 0d, coerceValue: (bo, v) => ((double)v).Clamp(0, 1)); + + public double Progress + { + get { return (double)GetValue(ProgressProperty); } + set { SetValue(ProgressProperty, value); } + } + + public Task<bool> ProgressTo(double value, uint length, Easing easing) + { + var tcs = new TaskCompletionSource<bool>(); + + this.Animate("Progress", d => Progress = d, Progress, value, length: length, easing: easing, finished: (d, finished) => tcs.SetResult(finished)); + + return tcs.Task; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Properties/AssemblyInfo.cs b/Xamarin.Forms.Core/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..b64ecc4d --- /dev/null +++ b/Xamarin.Forms.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,56 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Xamarin.Forms; + +[assembly: AssemblyTitle("Xamarin.Forms.Core")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The Page "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +//[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.iOS")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.iOS.Classic")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.Android")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.UAP")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WinRT")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WinRT.Tablet")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WinRT.Phone")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WP8")] +[assembly: InternalsVisibleTo("iOSUnitTests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Controls")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Core.Design")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Core.UnitTests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Core.Android.UnitTests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Core.WP8.UnitTests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Xaml")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Maps")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Maps.iOS")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Maps.iOS.Classic")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Maps.Android")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Xaml.UnitTests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.UITests")] +//[assembly:InternalsVisibleTo("Xamarin.Forms.Core.UITests")] + +[assembly: InternalsVisibleTo("Xamarin.Forms.Core.iOS.UITests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Core.Android.UITests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Core.Windows.UITests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.iOS.UITests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Android.UITests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Loader")] +[assembly: InternalsVisibleTo("Xamarin.Forms.UITest.Validator")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Build.Tasks")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Pages")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Pages.UnitTests")] +[assembly: Preserve]
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Properties/GlobalAssemblyInfo.cs b/Xamarin.Forms.Core/Properties/GlobalAssemblyInfo.cs new file mode 100644 index 00000000..358bc29b --- /dev/null +++ b/Xamarin.Forms.Core/Properties/GlobalAssemblyInfo.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +[assembly: AssemblyCompany("Xamarin Inc.")] +[assembly: AssemblyProduct("Xamarin.Forms")] +[assembly: AssemblyCopyright("Copyright © Xamarin Inc. 2013-2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")]
\ No newline at end of file diff --git a/Xamarin.Forms.Core/PropertyChangingEventArgs.cs b/Xamarin.Forms.Core/PropertyChangingEventArgs.cs new file mode 100644 index 00000000..75c78994 --- /dev/null +++ b/Xamarin.Forms.Core/PropertyChangingEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms +{ + public class PropertyChangingEventArgs : EventArgs + { + public PropertyChangingEventArgs(string propertyName) + { + PropertyName = propertyName; + } + + public virtual string PropertyName { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/PropertyChangingEventHandler.cs b/Xamarin.Forms.Core/PropertyChangingEventHandler.cs new file mode 100644 index 00000000..06227c76 --- /dev/null +++ b/Xamarin.Forms.Core/PropertyChangingEventHandler.cs @@ -0,0 +1,4 @@ +namespace Xamarin.Forms +{ + public delegate void PropertyChangingEventHandler(object sender, PropertyChangingEventArgs e); +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ReadOnlyCastingList.cs b/Xamarin.Forms.Core/ReadOnlyCastingList.cs new file mode 100644 index 00000000..a3f880c3 --- /dev/null +++ b/Xamarin.Forms.Core/ReadOnlyCastingList.cs @@ -0,0 +1,35 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + internal class ReadOnlyCastingList<T, TFrom> : IReadOnlyList<T> where T : class where TFrom : class + { + readonly IList<TFrom> _list; + + public ReadOnlyCastingList(IList<TFrom> list) + { + _list = list; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_list).GetEnumerator(); + } + + public IEnumerator<T> GetEnumerator() + { + return new CastingEnumerator<T, TFrom>(_list.GetEnumerator()); + } + + public int Count + { + get { return _list.Count; } + } + + public T this[int index] + { + get { return _list[index] as T; } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ReadOnlyListAdapter.cs b/Xamarin.Forms.Core/ReadOnlyListAdapter.cs new file mode 100644 index 00000000..0ffdaef5 --- /dev/null +++ b/Xamarin.Forms.Core/ReadOnlyListAdapter.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Forms +{ + internal sealed class ReadOnlyListAdapter : IList + { + readonly IReadOnlyCollection<object> _collection; + readonly IReadOnlyList<object> _list; + + public ReadOnlyListAdapter(IReadOnlyList<object> list) + { + _list = list; + _collection = list; + } + + public ReadOnlyListAdapter(IReadOnlyCollection<object> collection) + { + _collection = collection; + } + + public void CopyTo(Array array, int index) + { + throw new NotImplementedException(); + } + + public int Count + { + get { return _collection.Count; } + } + + public bool IsSynchronized + { + get { throw new NotImplementedException(); } + } + + public object SyncRoot + { + get { throw new NotImplementedException(); } + } + + public IEnumerator GetEnumerator() + { + return _collection.GetEnumerator(); + } + + public int Add(object value) + { + throw new NotImplementedException(); + } + + public void Clear() + { + throw new NotImplementedException(); + } + + public bool Contains(object value) + { + return _list.Contains(value); + } + + public int IndexOf(object value) + { + return _list.IndexOf(value); + } + + public void Insert(int index, object value) + { + throw new NotImplementedException(); + } + + public bool IsFixedSize + { + get { throw new NotImplementedException(); } + } + + public bool IsReadOnly + { + get { return true; } + } + + public object this[int index] + { + get { return _list[index]; } + set { throw new NotImplementedException(); } + } + + public void Remove(object value) + { + throw new NotImplementedException(); + } + + public void RemoveAt(int index) + { + throw new NotImplementedException(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Rectangle.cs b/Xamarin.Forms.Core/Rectangle.cs new file mode 100644 index 00000000..ac5460cb --- /dev/null +++ b/Xamarin.Forms.Core/Rectangle.cs @@ -0,0 +1,244 @@ +// +// Rectangle.cs +// +// Author: +// Lluis Sanchez <lluis@xamarin.com> +// +// Copyright (c) 2011 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.Diagnostics; +using System.Globalization; + +namespace Xamarin.Forms +{ + [DebuggerDisplay("X={X}, Y={Y}, Width={Width}, Height={Height}")] + [TypeConverter(typeof(RectangleTypeConverter))] + public struct Rectangle + { + public double X { get; set; } + + public double Y { get; set; } + + public double Width { get; set; } + + public double Height { get; set; } + + public static Rectangle Zero = new Rectangle(); + + public override string ToString() + { + return string.Format("{{X={0} Y={1} Width={2} Height={3}}}", X.ToString(CultureInfo.InvariantCulture), Y.ToString(CultureInfo.InvariantCulture), Width.ToString(CultureInfo.InvariantCulture), + Height.ToString(CultureInfo.InvariantCulture)); + } + + // constructors + public Rectangle(double x, double y, double width, double height) : this() + { + X = x; + Y = y; + Width = width; + Height = height; + } + + public Rectangle(Point loc, Size sz) : this(loc.X, loc.Y, sz.Width, sz.Height) + { + } + + public static Rectangle FromLTRB(double left, double top, double right, double bottom) + { + return new Rectangle(left, top, right - left, bottom - top); + } + + public bool Equals(Rectangle other) + { + return X.Equals(other.X) && Y.Equals(other.Y) && Width.Equals(other.Width) && Height.Equals(other.Height); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + return obj is Rectangle && Equals((Rectangle)obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = X.GetHashCode(); + hashCode = (hashCode * 397) ^ Y.GetHashCode(); + hashCode = (hashCode * 397) ^ Width.GetHashCode(); + hashCode = (hashCode * 397) ^ Height.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(Rectangle r1, Rectangle r2) + { + return (r1.Location == r2.Location) && (r1.Size == r2.Size); + } + + public static bool operator !=(Rectangle r1, Rectangle r2) + { + return !(r1 == r2); + } + + // Hit Testing / Intersection / Union + public bool Contains(Rectangle rect) + { + return X <= rect.X && Right >= rect.Right && Y <= rect.Y && Bottom >= rect.Bottom; + } + + public bool Contains(Point pt) + { + return Contains(pt.X, pt.Y); + } + + public bool Contains(double x, double y) + { + return (x >= Left) && (x < Right) && (y >= Top) && (y < Bottom); + } + + public bool IntersectsWith(Rectangle r) + { + return !((Left >= r.Right) || (Right <= r.Left) || (Top >= r.Bottom) || (Bottom <= r.Top)); + } + + public Rectangle Union(Rectangle r) + { + return Union(this, r); + } + + public static Rectangle Union(Rectangle r1, Rectangle r2) + { + return FromLTRB(Math.Min(r1.Left, r2.Left), Math.Min(r1.Top, r2.Top), Math.Max(r1.Right, r2.Right), Math.Max(r1.Bottom, r2.Bottom)); + } + + public Rectangle Intersect(Rectangle r) + { + return Intersect(this, r); + } + + public static Rectangle Intersect(Rectangle r1, Rectangle r2) + { + double x = Math.Max(r1.X, r2.X); + double y = Math.Max(r1.Y, r2.Y); + double width = Math.Min(r1.Right, r2.Right) - x; + double height = Math.Min(r1.Bottom, r2.Bottom) - y; + + if (width < 0 || height < 0) + { + return Zero; + } + return new Rectangle(x, y, width, height); + } + + // Position/Size + public double Top + { + get { return Y; } + set { Y = value; } + } + + public double Bottom + { + get { return Y + Height; } + set { Height = value - Y; } + } + + public double Right + { + get { return X + Width; } + set { Width = value - X; } + } + + public double Left + { + get { return X; } + set { X = value; } + } + + public bool IsEmpty + { + get { return (Width <= 0) || (Height <= 0); } + } + + public Size Size + { + get { return new Size(Width, Height); } + set + { + Width = value.Width; + Height = value.Height; + } + } + + public Point Location + { + get { return new Point(X, Y); } + set + { + X = value.X; + Y = value.Y; + } + } + + public Point Center + { + get { return new Point(X + Width / 2, Y + Height / 2); } + } + + // Inflate and Offset + public Rectangle Inflate(Size sz) + { + return Inflate(sz.Width, sz.Height); + } + + public Rectangle Inflate(double width, double height) + { + Rectangle r = this; + r.X -= width; + r.Y -= height; + r.Width += width * 2; + r.Height += height * 2; + return r; + } + + public Rectangle Offset(double dx, double dy) + { + Rectangle r = this; + r.X += dx; + r.Y += dy; + return r; + } + + public Rectangle Offset(Point dr) + { + return Offset(dr.X, dr.Y); + } + + public Rectangle Round() + { + return new Rectangle(Math.Round(X), Math.Round(Y), Math.Round(Width), Math.Round(Height)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/RectangleTypeConverter.cs b/Xamarin.Forms.Core/RectangleTypeConverter.cs new file mode 100644 index 00000000..b17a8c26 --- /dev/null +++ b/Xamarin.Forms.Core/RectangleTypeConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms +{ + public class RectangleTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + double x, y, w, h; + string[] xywh = value.Split(','); + if (xywh.Length == 4 && double.TryParse(xywh[0], NumberStyles.Number, CultureInfo.InvariantCulture, out x) && double.TryParse(xywh[1], NumberStyles.Number, CultureInfo.InvariantCulture, out y) && + double.TryParse(xywh[2], NumberStyles.Number, CultureInfo.InvariantCulture, out w) && double.TryParse(xywh[3], NumberStyles.Number, CultureInfo.InvariantCulture, out h)) + return new Rectangle(x, y, w, h); + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Rectangle))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ReflectionExtensions.cs b/Xamarin.Forms.Core/ReflectionExtensions.cs new file mode 100644 index 00000000..ab26fde5 --- /dev/null +++ b/Xamarin.Forms.Core/ReflectionExtensions.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Xamarin.Forms +{ + internal static class ReflectionExtensions + { + public static FieldInfo GetField(this Type type, Func<FieldInfo, bool> predicate) + { + return GetFields(type).SingleOrDefault(predicate); + } + + public static FieldInfo GetField(this Type type, string name) + { + return type.GetField(fi => fi.Name == name); + } + + public static IEnumerable<FieldInfo> GetFields(this Type type) + { + return GetParts(type, i => i.DeclaredFields); + } + + public static IEnumerable<PropertyInfo> GetProperties(this Type type) + { + return GetParts(type, ti => ti.DeclaredProperties); + } + + public static PropertyInfo GetProperty(this Type type, string name) + { + Type t = type; + while (t != null) + { + TypeInfo ti = t.GetTypeInfo(); + PropertyInfo property = ti.GetDeclaredProperty(name); + if (property != null) + return property; + + t = ti.BaseType; + } + + return null; + } + + public static bool IsAssignableFrom(this Type self, Type c) + { + return self.GetTypeInfo().IsAssignableFrom(c.GetTypeInfo()); + } + + public static bool IsInstanceOfType(this Type self, object o) + { + return self.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); + } + + static IEnumerable<T> GetParts<T>(Type type, Func<TypeInfo, IEnumerable<T>> selector) + { + Type t = type; + while (t != null) + { + TypeInfo ti = t.GetTypeInfo(); + foreach (T f in selector(ti)) + yield return f; + t = ti.BaseType; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Registrar.cs b/Xamarin.Forms.Core/Registrar.cs new file mode 100644 index 00000000..92c2e533 --- /dev/null +++ b/Xamarin.Forms.Core/Registrar.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Xamarin.Forms +{ + internal class Registrar<TRegistrable> where TRegistrable : class + { + readonly Dictionary<Type, Type> _handlers = new Dictionary<Type, Type>(); + + public void Register(Type tview, Type trender) + { + _handlers[tview] = trender; + } + + internal TRegistrable GetHandler(Type type) + { + Type handlerType = GetHandlerType(type); + if (handlerType == null) + return null; + + object handler = Activator.CreateInstance(handlerType); + return (TRegistrable)handler; + } + + internal TOut GetHandler<TOut>(Type type) where TOut : TRegistrable + { + return (TOut)GetHandler(type); + } + + internal Type GetHandlerType(Type viewType) + { + Type type = LookupHandlerType(viewType); + if (type != null) + return type; + + // lazy load render-view association with RenderWithAttribute (as opposed to using ExportRenderer) + // TODO: change Registrar to a LazyImmutableDictionary and pass this logic to ctor as a delegate. + var attribute = viewType.GetTypeInfo().GetCustomAttribute<RenderWithAttribute>(); + if (attribute == null) + return null; + type = attribute.Type; + + if (type.Name.StartsWith("_")) + { + // TODO: Remove attribute2 once renderer names have been unified across all platforms + var attribute2 = type.GetTypeInfo().GetCustomAttribute<RenderWithAttribute>(); + if (attribute2 != null) + type = attribute2.Type; + + if (type.Name.StartsWith("_")) + { + //var attrs = type.GetTypeInfo ().GetCustomAttributes ().ToArray (); + return null; + } + } + + Register(viewType, type); + return LookupHandlerType(viewType); + } + + Type LookupHandlerType(Type viewType) + { + Type type = viewType; + + while (true) + { + if (_handlers.ContainsKey(type)) + return _handlers[type]; + + type = type.GetTypeInfo().BaseType; + if (type == null) + break; + } + + return null; + } + } + + internal static class Registrar + { + static Registrar() + { + Registered = new Registrar<IRegisterable>(); + } + + internal static Dictionary<string, Type> Effects { get; } = new Dictionary<string, Type>(); + + internal static IEnumerable<Assembly> ExtraAssemblies { get; set; } + + internal static Registrar<IRegisterable> Registered { get; } + + internal static void RegisterAll(Type[] attrTypes) + { + Assembly[] assemblies = Device.GetAssemblies(); + if (ExtraAssemblies != null) + { + assemblies = assemblies.Union(ExtraAssemblies).ToArray(); + } + + Assembly defaultRendererAssembly = Device.PlatformServices.GetType().GetTypeInfo().Assembly; + int indexOfExecuting = Array.IndexOf(assemblies, defaultRendererAssembly); + + if (indexOfExecuting > 0) + { + assemblies[indexOfExecuting] = assemblies[0]; + assemblies[0] = defaultRendererAssembly; + } + + // Don't use LINQ for performance reasons + // Naive implementation can easily take over a second to run + foreach (Assembly assembly in assemblies) + { + foreach (Type attrType in attrTypes) + { + Attribute[] attributes = assembly.GetCustomAttributes(attrType).ToArray(); + if (attributes.Length == 0) + continue; + + foreach (HandlerAttribute attribute in attributes) + { + if (attribute.ShouldRegister()) + Registered.Register(attribute.HandlerType, attribute.TargetType); + } + } + + string resolutionName = assembly.FullName; + var resolutionNameAttribute = (ResolutionGroupNameAttribute)assembly.GetCustomAttribute(typeof(ResolutionGroupNameAttribute)); + if (resolutionNameAttribute != null) + { + resolutionName = resolutionNameAttribute.ShortName; + } + + Attribute[] effectAttributes = assembly.GetCustomAttributes(typeof(ExportEffectAttribute)).ToArray(); + if (effectAttributes.Length > 0) + { + foreach (Attribute attribute in effectAttributes) + { + var effect = (ExportEffectAttribute)attribute; + Effects.Add(resolutionName + "." + effect.Id, effect.Type); + } + } + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/RelativeLayout.cs b/Xamarin.Forms.Core/RelativeLayout.cs new file mode 100644 index 00000000..b3a1b615 --- /dev/null +++ b/Xamarin.Forms.Core/RelativeLayout.cs @@ -0,0 +1,317 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; + +namespace Xamarin.Forms +{ + public class RelativeLayout : Layout<View> + { + public static readonly BindableProperty XConstraintProperty = BindableProperty.CreateAttached("XConstraint", typeof(Constraint), typeof(RelativeLayout), null); + + public static readonly BindableProperty YConstraintProperty = BindableProperty.CreateAttached("YConstraint", typeof(Constraint), typeof(RelativeLayout), null); + + public static readonly BindableProperty WidthConstraintProperty = BindableProperty.CreateAttached("WidthConstraint", typeof(Constraint), typeof(RelativeLayout), null); + + public static readonly BindableProperty HeightConstraintProperty = BindableProperty.CreateAttached("HeightConstraint", typeof(Constraint), typeof(RelativeLayout), null); + + public static readonly BindableProperty BoundsConstraintProperty = BindableProperty.CreateAttached("BoundsConstraint", typeof(BoundsConstraint), typeof(RelativeLayout), null); + + readonly RelativeElementCollection _children; + + IEnumerable<View> _childrenInSolveOrder; + + public RelativeLayout() + { + VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand; + _children = new RelativeElementCollection(InternalChildren, this); + _children.Parent = this; + } + + public new IRelativeList<View> Children + { + get { return _children; } + } + + IEnumerable<View> ChildrenInSolveOrder + { + get + { + if (_childrenInSolveOrder != null) + return _childrenInSolveOrder; + + var result = new List<View>(); + var solveTable = new Dictionary<View, bool>(); + foreach (View child in Children.Cast<View>()) + { + solveTable[child] = false; + } + + List<View> unsolvedChildren = Children.Cast<View>().ToList(); + while (unsolvedChildren.Any()) + { + List<View> copy = unsolvedChildren.ToList(); + var solvedChild = false; + foreach (View child in copy) + { + if (CanSolveView(child, solveTable)) + { + result.Add(child); + solveTable[child] = true; + unsolvedChildren.Remove(child); + solvedChild = true; + } + } + if (!solvedChild) + throw new UnsolvableConstraintsException("Constraints as specified contain an unsolvable loop."); + } + + _childrenInSolveOrder = result; + return _childrenInSolveOrder; + } + } + + public static BoundsConstraint GetBoundsConstraint(BindableObject bindable) + { + return (BoundsConstraint)bindable.GetValue(BoundsConstraintProperty); + } + + public static Constraint GetHeightConstraint(BindableObject bindable) + { + return (Constraint)bindable.GetValue(HeightConstraintProperty); + } + + public static Constraint GetWidthConstraint(BindableObject bindable) + { + return (Constraint)bindable.GetValue(WidthConstraintProperty); + } + + public static Constraint GetXConstraint(BindableObject bindable) + { + return (Constraint)bindable.GetValue(XConstraintProperty); + } + + public static Constraint GetYConstraint(BindableObject bindable) + { + return (Constraint)bindable.GetValue(YConstraintProperty); + } + + public static void SetBoundsConstraint(BindableObject bindable, BoundsConstraint value) + { + bindable.SetValue(BoundsConstraintProperty, value); + } + + protected override void LayoutChildren(double x, double y, double width, double height) + { + foreach (View child in ChildrenInSolveOrder) + { + LayoutChildIntoBoundingRegion(child, SolveView(child)); + } + } + + protected override void OnAdded(View view) + { + BoundsConstraint boundsConstraint = GetBoundsConstraint(view); + if (boundsConstraint == null) + { + // user probably added the view through the strict Add method. + CreateBoundsFromConstraints(view, GetXConstraint(view), GetYConstraint(view), GetWidthConstraint(view), GetHeightConstraint(view)); + } + + _childrenInSolveOrder = null; + base.OnAdded(view); + } + + protected override void OnRemoved(View view) + { + _childrenInSolveOrder = null; + base.OnRemoved(view); + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + double mockWidth = double.IsPositiveInfinity(widthConstraint) ? ParentView.Width : widthConstraint; + double mockHeight = double.IsPositiveInfinity(heightConstraint) ? ParentView.Height : heightConstraint; + MockBounds(new Rectangle(0, 0, mockWidth, mockHeight)); + + var boundsRectangle = new Rectangle(); + var set = false; + foreach (View child in ChildrenInSolveOrder) + { + Rectangle bounds = SolveView(child); + child.MockBounds(bounds); + if (!set) + { + boundsRectangle = bounds; + set = true; + } + else + { + boundsRectangle.Left = Math.Min(boundsRectangle.Left, bounds.Left); + boundsRectangle.Top = Math.Min(boundsRectangle.Top, bounds.Top); + boundsRectangle.Right = Math.Max(boundsRectangle.Right, bounds.Right); + boundsRectangle.Bottom = Math.Max(boundsRectangle.Bottom, bounds.Bottom); + } + } + + foreach (View child in ChildrenInSolveOrder) + child.UnmockBounds(); + + UnmockBounds(); + + return new SizeRequest(new Size(boundsRectangle.Right, boundsRectangle.Bottom)); + } + + bool CanSolveView(View view, Dictionary<View, bool> solveTable) + { + BoundsConstraint boundsConstraint = GetBoundsConstraint(view); + var parents = new List<View>(); + if (boundsConstraint == null) + { + throw new Exception("BoundsConstraint should not be null at this point"); + } + parents.AddRange(boundsConstraint.RelativeTo); + // expressions probably referenced the base layout somewhere + while (parents.Remove(this)) // because winphone does not have RemoveAll... + ; + + if (!parents.Any()) + return true; + + for (var i = 0; i < parents.Count; i++) + { + View p = parents[i]; + + bool solvable; + if (!solveTable.TryGetValue(p, out solvable)) + { + throw new InvalidOperationException("Views that have relationships to or from them must be kept in the RelativeLayout."); + } + + if (!solvable) + return false; + } + + return true; + } + + void CreateBoundsFromConstraints(View view, Constraint xConstraint, Constraint yConstraint, Constraint widthConstraint, Constraint heightConstraint) + { + var parents = new List<View>(); + + Func<double> x; + if (xConstraint != null) + { + x = () => xConstraint.Compute(this); + if (xConstraint.RelativeTo != null) + parents.AddRange(xConstraint.RelativeTo); + } + else + x = () => 0; + + Func<double> y; + if (yConstraint != null) + { + y = () => yConstraint.Compute(this); + if (yConstraint.RelativeTo != null) + parents.AddRange(yConstraint.RelativeTo); + } + else + y = () => 0; + + Func<double> width; + if (widthConstraint != null) + { + width = () => widthConstraint.Compute(this); + if (widthConstraint.RelativeTo != null) + parents.AddRange(widthConstraint.RelativeTo); + } + else + width = () => view.Measure(Width, Height, MeasureFlags.IncludeMargins).Request.Width; + + Func<double> height; + if (heightConstraint != null) + { + height = () => heightConstraint.Compute(this); + if (heightConstraint.RelativeTo != null) + parents.AddRange(heightConstraint.RelativeTo); + } + else + height = () => view.Measure(Width, Height, MeasureFlags.IncludeMargins).Request.Height; + + BoundsConstraint bounds = BoundsConstraint.FromExpression(() => new Rectangle(x(), y(), width(), height()), parents.Distinct().ToArray()); + SetBoundsConstraint(view, bounds); + } + + static Rectangle SolveView(View view) + { + BoundsConstraint boundsConstraint = GetBoundsConstraint(view); + var result = new Rectangle(); + + if (boundsConstraint == null) + { + throw new Exception("BoundsConstraint should not be null at this point"); + } + result = boundsConstraint.Compute(); + + return result; + } + + public interface IRelativeList<T> : IList<T> where T : View + { + void Add(T view, Expression<Func<Rectangle>> bounds); + + void Add(T view, Expression<Func<double>> x = null, Expression<Func<double>> y = null, Expression<Func<double>> width = null, Expression<Func<double>> height = null); + + void Add(T view, Constraint xConstraint = null, Constraint yConstraint = null, Constraint widthConstraint = null, Constraint heightConstraint = null); + } + + class RelativeElementCollection : ElementCollection<View>, IRelativeList<View> + { + public RelativeElementCollection(ObservableCollection<Element> inner, RelativeLayout parent) : base(inner) + { + Parent = parent; + } + + internal RelativeLayout Parent { get; set; } + + public void Add(View view, Expression<Func<Rectangle>> bounds) + { + if (bounds == null) + throw new ArgumentNullException("bounds"); + SetBoundsConstraint(view, BoundsConstraint.FromExpression(bounds)); + + base.Add(view); + } + + public void Add(View view, Expression<Func<double>> x = null, Expression<Func<double>> y = null, Expression<Func<double>> width = null, Expression<Func<double>> height = null) + { + Func<double> xCompiled = x != null ? x.Compile() : () => 0; + Func<double> yCompiled = y != null ? y.Compile() : () => 0; + Func<double> widthCompiled = width != null ? width.Compile() : () => view.Measure(Parent.Width, Parent.Height, MeasureFlags.IncludeMargins).Request.Width; + Func<double> heightCompiled = height != null ? height.Compile() : () => view.Measure(Parent.Width, Parent.Height, MeasureFlags.IncludeMargins).Request.Height; + + var parents = new List<View>(); + parents.AddRange(ExpressionSearch.Default.FindObjects<View>(x)); + parents.AddRange(ExpressionSearch.Default.FindObjects<View>(y)); + parents.AddRange(ExpressionSearch.Default.FindObjects<View>(width)); + parents.AddRange(ExpressionSearch.Default.FindObjects<View>(height)); + + BoundsConstraint bounds = BoundsConstraint.FromExpression(() => new Rectangle(xCompiled(), yCompiled(), widthCompiled(), heightCompiled()), parents.Distinct().ToArray()); + + SetBoundsConstraint(view, bounds); + + base.Add(view); + } + + public void Add(View view, Constraint xConstraint = null, Constraint yConstraint = null, Constraint widthConstraint = null, Constraint heightConstraint = null) + { + Parent.CreateBoundsFromConstraints(view, xConstraint, yConstraint, widthConstraint, heightConstraint); + + base.Add(view); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/RenderWithAttribute.cs b/Xamarin.Forms.Core/RenderWithAttribute.cs new file mode 100644 index 00000000..75175306 --- /dev/null +++ b/Xamarin.Forms.Core/RenderWithAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Class)] + public sealed class RenderWithAttribute : Attribute + { + public RenderWithAttribute(Type type) + { + Type = type; + } + + public Type Type { get; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ResolutionGroupNameAttribute.cs b/Xamarin.Forms.Core/ResolutionGroupNameAttribute.cs new file mode 100644 index 00000000..eee92e76 --- /dev/null +++ b/Xamarin.Forms.Core/ResolutionGroupNameAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Assembly)] + public class ResolutionGroupNameAttribute : Attribute + { + public ResolutionGroupNameAttribute(string name) + { + ShortName = name; + } + + internal string ShortName { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ResourceDictionary.cs b/Xamarin.Forms.Core/ResourceDictionary.cs new file mode 100644 index 00000000..b19773ff --- /dev/null +++ b/Xamarin.Forms.Core/ResourceDictionary.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Xamarin.Forms +{ + public sealed class ResourceDictionary : IResourceDictionary, IDictionary<string, object> + { + readonly Dictionary<string, object> _innerDictionary = new Dictionary<string, object>(); + + void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) + { + ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Add(item); + OnValuesChanged(item); + } + + public void Clear() + { + _innerDictionary.Clear(); + } + + bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) + { + return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Contains(item); + } + + void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) + { + ((ICollection<KeyValuePair<string, object>>)_innerDictionary).CopyTo(array, arrayIndex); + } + + public int Count + { + get { return _innerDictionary.Count; } + } + + bool ICollection<KeyValuePair<string, object>>.IsReadOnly + { + get { return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).IsReadOnly; } + } + + bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) + { + return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Remove(item); + } + + public void Add(string key, object value) + { + _innerDictionary.Add(key, value); + OnValueChanged(key, value); + } + + public bool ContainsKey(string key) + { + return _innerDictionary.ContainsKey(key); + } + + [IndexerName("Item")] + public object this[string index] + { + get { return _innerDictionary[index]; } + set + { + _innerDictionary[index] = value; + OnValueChanged(index, value); + } + } + + public ICollection<string> Keys + { + get { return _innerDictionary.Keys; } + } + + public bool Remove(string key) + { + return _innerDictionary.Remove(key); + } + + public ICollection<object> Values + { + get { return _innerDictionary.Values; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_innerDictionary).GetEnumerator(); + } + + public IEnumerator<KeyValuePair<string, object>> GetEnumerator() + { + return _innerDictionary.GetEnumerator(); + } + + public bool TryGetValue(string key, out object value) + { + return _innerDictionary.TryGetValue(key, out value); + } + + event EventHandler<ResourcesChangedEventArgs> IResourceDictionary.ValuesChanged + { + add { ValuesChanged += value; } + remove { ValuesChanged -= value; } + } + + public void Add(Style style) + { + if (string.IsNullOrEmpty(style.Class)) + Add(style.TargetType.FullName, style); + else + { + IList<Style> classes; + object outclasses; + if (!TryGetValue(Style.StyleClassPrefix + style.Class, out outclasses) || (classes = outclasses as IList<Style>) == null) + classes = new List<Style>(); + classes.Add(style); + this[Style.StyleClassPrefix + style.Class] = classes; + } + } + + void OnValueChanged(string key, object value) + { + OnValuesChanged(new KeyValuePair<string, object>(key, value)); + } + + void OnValuesChanged(params KeyValuePair<string, object>[] values) + { + if (values == null || values.Length == 0) + return; + ValuesChanged?.Invoke(this, new ResourcesChangedEventArgs(values)); + } + + event EventHandler<ResourcesChangedEventArgs> ValuesChanged; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ResourcesChangedEventArgs.cs b/Xamarin.Forms.Core/ResourcesChangedEventArgs.cs new file mode 100644 index 00000000..2bab8565 --- /dev/null +++ b/Xamarin.Forms.Core/ResourcesChangedEventArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + internal class ResourcesChangedEventArgs : EventArgs + { + public ResourcesChangedEventArgs(IEnumerable<KeyValuePair<string, object>> values) + { + Values = values; + } + + public IEnumerable<KeyValuePair<string, object>> Values { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ResourcesExtensions.cs b/Xamarin.Forms.Core/ResourcesExtensions.cs new file mode 100644 index 00000000..a75e264a --- /dev/null +++ b/Xamarin.Forms.Core/ResourcesExtensions.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + internal static class ResourcesExtensions + { + public static IEnumerable<KeyValuePair<string, object>> GetMergedResources(this IElement element) + { + Dictionary<string, object> resources = null; + while (element != null) + { + var ve = element as IResourcesProvider; + if (ve != null && ve.Resources != null && ve.Resources.Count != 0) + { + resources = resources ?? new Dictionary<string, object>(ve.Resources.Count); + foreach (KeyValuePair<string, object> res in ve.Resources) + if (!resources.ContainsKey(res.Key)) + resources.Add(res.Key, res.Value); + else if (res.Key.StartsWith(Style.StyleClassPrefix, StringComparison.Ordinal)) + { + var mergedClassStyles = new List<Style>(resources[res.Key] as List<Style>); + mergedClassStyles.AddRange(res.Value as List<Style>); + resources[res.Key] = mergedClassStyles; + } + } + var app = element as Application; + if (app != null && app.SystemResources != null) + { + resources = resources ?? new Dictionary<string, object>(8); + foreach (KeyValuePair<string, object> res in app.SystemResources) + if (!resources.ContainsKey(res.Key)) + resources.Add(res.Key, res.Value); + else if (res.Key.StartsWith(Style.StyleClassPrefix, StringComparison.Ordinal)) + { + var mergedClassStyles = new List<Style>(resources[res.Key] as List<Style>); + mergedClassStyles.AddRange(res.Value as List<Style>); + resources[res.Key] = mergedClassStyles; + } + } + element = element.Parent; + } + return resources; + } + + public static bool TryGetResource(this IElement element, string key, out object value) + { + while (element != null) + { + var ve = element as IResourcesProvider; + if (ve != null && ve.Resources != null && ve.Resources.TryGetValue(key, out value)) + return true; + var app = element as Application; + if (app != null && app.SystemResources != null && app.SystemResources.TryGetValue(key, out value)) + return true; + element = element.Parent; + } + value = null; + return false; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/RoutingEffect.cs b/Xamarin.Forms.Core/RoutingEffect.cs new file mode 100644 index 00000000..41f040b9 --- /dev/null +++ b/Xamarin.Forms.Core/RoutingEffect.cs @@ -0,0 +1,42 @@ +using System.ComponentModel; + +namespace Xamarin.Forms +{ + public class RoutingEffect : Effect + { + internal readonly Effect Inner; + + protected RoutingEffect(string effectId) + { + Inner = Resolve(effectId); + } + + protected override void OnAttached() + { + } + + protected override void OnDetached() + { + } + + internal override void ClearEffect() + { + Inner?.ClearEffect(); + } + + internal override void SendAttached() + { + Inner?.SendAttached(); + } + + internal override void SendDetached() + { + Inner?.SendDetached(); + } + + internal override void SendOnElementPropertyChanged(PropertyChangedEventArgs args) + { + Inner?.SendOnElementPropertyChanged(args); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/RowDefinition.cs b/Xamarin.Forms.Core/RowDefinition.cs new file mode 100644 index 00000000..746332ea --- /dev/null +++ b/Xamarin.Forms.Core/RowDefinition.cs @@ -0,0 +1,34 @@ +using System; + +namespace Xamarin.Forms +{ + public sealed class RowDefinition : BindableObject, IDefinition + { + public static readonly BindableProperty HeightProperty = BindableProperty.Create("Height", typeof(GridLength), typeof(RowDefinition), new GridLength(1, GridUnitType.Star), + propertyChanged: (bindable, oldValue, newValue) => ((RowDefinition)bindable).OnSizeChanged()); + + public RowDefinition() + { + MinimumHeight = -1; + } + + public GridLength Height + { + get { return (GridLength)GetValue(HeightProperty); } + set { SetValue(HeightProperty, value); } + } + + internal double ActualHeight { get; set; } + + internal double MinimumHeight { get; set; } + + public event EventHandler SizeChanged; + + void OnSizeChanged() + { + EventHandler eh = SizeChanged; + if (eh != null) + eh(this, EventArgs.Empty); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/RowDefinitionCollection.cs b/Xamarin.Forms.Core/RowDefinitionCollection.cs new file mode 100644 index 00000000..4b979be5 --- /dev/null +++ b/Xamarin.Forms.Core/RowDefinitionCollection.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + public sealed class RowDefinitionCollection : DefinitionCollection<RowDefinition> + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ScrollOrientation.cs b/Xamarin.Forms.Core/ScrollOrientation.cs new file mode 100644 index 00000000..71cb335f --- /dev/null +++ b/Xamarin.Forms.Core/ScrollOrientation.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public enum ScrollOrientation + { + Vertical, + Horizontal, + Both + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ScrollToMode.cs b/Xamarin.Forms.Core/ScrollToMode.cs new file mode 100644 index 00000000..741f5df0 --- /dev/null +++ b/Xamarin.Forms.Core/ScrollToMode.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public enum ScrollToMode + { + Element = 0, + Position = 1 + // Item = 2, + // GroupAndItem = 3, + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ScrollToPosition.cs b/Xamarin.Forms.Core/ScrollToPosition.cs new file mode 100644 index 00000000..adc13771 --- /dev/null +++ b/Xamarin.Forms.Core/ScrollToPosition.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public enum ScrollToPosition + { + MakeVisible = 0, + Start = 1, + Center = 2, + End = 3 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ScrollToRequestedEventArgs.cs b/Xamarin.Forms.Core/ScrollToRequestedEventArgs.cs new file mode 100644 index 00000000..28231df7 --- /dev/null +++ b/Xamarin.Forms.Core/ScrollToRequestedEventArgs.cs @@ -0,0 +1,56 @@ +using System; + +namespace Xamarin.Forms +{ + public class ScrollToRequestedEventArgs : EventArgs + { + internal ScrollToRequestedEventArgs(double scrollX, double scrollY, bool shouldAnimate) + { + ScrollX = scrollX; + ScrollY = scrollY; + ShouldAnimate = shouldAnimate; + Mode = ScrollToMode.Position; + } + + internal ScrollToRequestedEventArgs(Element element, ScrollToPosition position, bool shouldAnimate) + { + Element = element; + Position = position; + ShouldAnimate = shouldAnimate; + Mode = ScrollToMode.Element; + } + + internal ScrollToRequestedEventArgs(object item, ScrollToPosition position, bool shouldAnimate) + { + Item = item; + Position = position; + ShouldAnimate = shouldAnimate; + //Mode = ScrollToMode.Item; + } + + internal ScrollToRequestedEventArgs(object item, object group, ScrollToPosition position, bool shouldAnimate) + { + Item = item; + Group = group; + Position = position; + ShouldAnimate = shouldAnimate; + //Mode = ScrollToMode.GroupAndIem; + } + + public Element Element { get; private set; } + + public ScrollToMode Mode { get; private set; } + + public ScrollToPosition Position { get; private set; } + + public double ScrollX { get; private set; } + + public double ScrollY { get; private set; } + + public bool ShouldAnimate { get; private set; } + + internal object Group { get; private set; } + + internal object Item { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ScrollView.cs b/Xamarin.Forms.Core/ScrollView.cs new file mode 100644 index 00000000..143d0a61 --- /dev/null +++ b/Xamarin.Forms.Core/ScrollView.cs @@ -0,0 +1,287 @@ +using System; +using System.Threading.Tasks; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [ContentProperty("Content")] + [RenderWith(typeof(_ScrollViewRenderer))] + public class ScrollView : Layout, IScrollViewController + { + public static readonly BindableProperty OrientationProperty = BindableProperty.Create("Orientation", typeof(ScrollOrientation), typeof(ScrollView), ScrollOrientation.Vertical); + + static readonly BindablePropertyKey ScrollXPropertyKey = BindableProperty.CreateReadOnly("ScrollX", typeof(double), typeof(ScrollView), 0d); + + public static readonly BindableProperty ScrollXProperty = ScrollXPropertyKey.BindableProperty; + + static readonly BindablePropertyKey ScrollYPropertyKey = BindableProperty.CreateReadOnly("ScrollY", typeof(double), typeof(ScrollView), 0d); + + public static readonly BindableProperty ScrollYProperty = ScrollYPropertyKey.BindableProperty; + + static readonly BindablePropertyKey ContentSizePropertyKey = BindableProperty.CreateReadOnly("ContentSize", typeof(Size), typeof(ScrollView), default(Size)); + + public static readonly BindableProperty ContentSizeProperty = ContentSizePropertyKey.BindableProperty; + + View _content; + + TaskCompletionSource<bool> _scrollCompletionSource; + + public View Content + { + get { return _content; } + set + { + if (_content == value) + return; + + OnPropertyChanging(); + if (_content != null) + InternalChildren.Remove(_content); + _content = value; + if (_content != null) + InternalChildren.Add(_content); + OnPropertyChanged(); + } + } + + public Size ContentSize + { + get { return (Size)GetValue(ContentSizeProperty); } + private set { SetValue(ContentSizePropertyKey, value); } + } + + public ScrollOrientation Orientation + { + get { return (ScrollOrientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + + public double ScrollX + { + get { return (double)GetValue(ScrollXProperty); } + private set { SetValue(ScrollXPropertyKey, value); } + } + + public double ScrollY + { + get { return (double)GetValue(ScrollYProperty); } + private set { SetValue(ScrollYPropertyKey, value); } + } + + Point IScrollViewController.GetScrollPositionForElement(VisualElement item, ScrollToPosition pos) + { + ScrollToPosition position = pos; + double y = GetCoordinate(item, "Y", 0); + double x = GetCoordinate(item, "X", 0); + + if (position == ScrollToPosition.MakeVisible) + { + bool isItemVisible = ScrollX < y && ScrollY + Height > y; + if (isItemVisible) + return new Point(ScrollX, ScrollY); + switch (Orientation) + { + case ScrollOrientation.Vertical: + position = y > ScrollY ? ScrollToPosition.End : ScrollToPosition.Start; + break; + case ScrollOrientation.Horizontal: + position = x > ScrollX ? ScrollToPosition.End : ScrollToPosition.Start; + break; + case ScrollOrientation.Both: + position = x > ScrollX || y > ScrollY ? ScrollToPosition.End : ScrollToPosition.Start; + break; + } + } + switch (position) + { + case ScrollToPosition.Center: + y = y - Height / 2 + item.Height / 2; + x = x - Width / 2 + item.Width / 2; + break; + case ScrollToPosition.End: + y = y - Height + item.Height; + x = x - Width + item.Width; + break; + } + return new Point(x, y); + } + + event EventHandler<ScrollToRequestedEventArgs> IScrollViewController.ScrollToRequested + { + add { ScrollToRequested += value; } + remove { ScrollToRequested -= value; } + } + + void IScrollViewController.SendScrollFinished() + { + if (_scrollCompletionSource != null) + _scrollCompletionSource.TrySetResult(true); + } + + void IScrollViewController.SetScrolledPosition(double x, double y) + { + if (ScrollX == x && ScrollY == y) + return; + + ScrollX = x; + ScrollY = y; + + EventHandler<ScrolledEventArgs> handler = Scrolled; + if (handler != null) + handler(this, new ScrolledEventArgs(x, y)); + } + + public event EventHandler<ScrolledEventArgs> Scrolled; + + public Task ScrollToAsync(double x, double y, bool animated) + { + var args = new ScrollToRequestedEventArgs(x, y, animated); + OnScrollToRequested(args); + return _scrollCompletionSource.Task; + } + + public Task ScrollToAsync(Element element, ScrollToPosition position, bool animated) + { + if (!Enum.IsDefined(typeof(ScrollToPosition), position)) + throw new ArgumentException("position is not a valid ScrollToPosition", "position"); + + if (element == null) + throw new ArgumentNullException("element"); + + if (!CheckElementBelongsToScrollViewer(element)) + throw new ArgumentException("element does not belong to this ScrollVIew", "element"); + + var args = new ScrollToRequestedEventArgs(element, position, animated); + OnScrollToRequested(args); + return _scrollCompletionSource.Task; + } + + protected override void LayoutChildren(double x, double y, double width, double height) + { + if (_content != null) + { + SizeRequest size; + switch (Orientation) + { + case ScrollOrientation.Horizontal: + size = _content.Measure(double.PositiveInfinity, height, MeasureFlags.IncludeMargins); + LayoutChildIntoBoundingRegion(_content, new Rectangle(x, y, GetMaxWidth(width, size), height)); + ContentSize = new Size(GetMaxWidth(width), height); + break; + case ScrollOrientation.Vertical: + size = _content.Measure(width, double.PositiveInfinity, MeasureFlags.IncludeMargins); + LayoutChildIntoBoundingRegion(_content, new Rectangle(x, y, width, GetMaxHeight(height, size))); + ContentSize = new Size(width, GetMaxHeight(height)); + break; + case ScrollOrientation.Both: + size = _content.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins); + LayoutChildIntoBoundingRegion(_content, new Rectangle(x, y, GetMaxWidth(width, size), GetMaxHeight(height, size))); + ContentSize = new Size(GetMaxWidth(width), GetMaxHeight(height)); + break; + } + } + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + if (Content == null) + return new SizeRequest(); + + switch (Orientation) + { + case ScrollOrientation.Horizontal: + widthConstraint = double.PositiveInfinity; + break; + case ScrollOrientation.Vertical: + heightConstraint = double.PositiveInfinity; + break; + case ScrollOrientation.Both: + widthConstraint = double.PositiveInfinity; + heightConstraint = double.PositiveInfinity; + break; + } + + SizeRequest contentRequest = Content.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins); + contentRequest.Minimum = new Size(Math.Min(40, contentRequest.Minimum.Width), Math.Min(40, contentRequest.Minimum.Height)); + return contentRequest; + } + + internal override void ComputeConstraintForView(View view) + { + switch (Orientation) + { + case ScrollOrientation.Horizontal: + LayoutOptions vOptions = view.VerticalOptions; + if (vOptions.Alignment == LayoutAlignment.Fill && (Constraint & LayoutConstraint.VerticallyFixed) != 0) + { + view.ComputedConstraint = LayoutConstraint.VerticallyFixed; + } + break; + case ScrollOrientation.Vertical: + LayoutOptions hOptions = view.HorizontalOptions; + if (hOptions.Alignment == LayoutAlignment.Fill && (Constraint & LayoutConstraint.HorizontallyFixed) != 0) + { + view.ComputedConstraint = LayoutConstraint.HorizontallyFixed; + } + break; + case ScrollOrientation.Both: + view.ComputedConstraint = LayoutConstraint.None; + break; + } + } + + bool CheckElementBelongsToScrollViewer(Element element) + { + return Equals(element, this) || element.RealParent != null && CheckElementBelongsToScrollViewer(element.RealParent); + } + + void CheckTaskCompletionSource() + { + if (_scrollCompletionSource != null && _scrollCompletionSource.Task.Status == TaskStatus.Running) + { + _scrollCompletionSource.TrySetCanceled(); + } + _scrollCompletionSource = new TaskCompletionSource<bool>(); + } + + double GetCoordinate(Element item, string coordinateName, double coordinate) + { + if (item == this) + return coordinate; + coordinate += (double)typeof(VisualElement).GetProperty(coordinateName).GetValue(item, null); + var visualParentElement = item.RealParent as VisualElement; + return visualParentElement != null ? GetCoordinate(visualParentElement, coordinateName, coordinate) : coordinate; + } + + double GetMaxHeight(double height) + { + return Math.Max(height, _content.Bounds.Bottom + Padding.Bottom); + } + + static double GetMaxHeight(double height, SizeRequest size) + { + return Math.Max(size.Request.Height, height); + } + + double GetMaxWidth(double width) + { + return Math.Max(width, _content.Bounds.Right + Padding.Right); + } + + static double GetMaxWidth(double width, SizeRequest size) + { + return Math.Max(size.Request.Width, width); + } + + void OnScrollToRequested(ScrollToRequestedEventArgs e) + { + CheckTaskCompletionSource(); + EventHandler<ScrollToRequestedEventArgs> handler = ScrollToRequested; + if (handler != null) + handler(this, e); + } + + event EventHandler<ScrollToRequestedEventArgs> ScrollToRequested; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ScrolledEventArgs.cs b/Xamarin.Forms.Core/ScrolledEventArgs.cs new file mode 100644 index 00000000..bc1d1f36 --- /dev/null +++ b/Xamarin.Forms.Core/ScrolledEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + public class ScrolledEventArgs : EventArgs + { + public ScrolledEventArgs(double x, double y) + { + ScrollX = x; + ScrollY = y; + } + + public double ScrollX { get; private set; } + + public double ScrollY { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/SearchBar.cs b/Xamarin.Forms.Core/SearchBar.cs new file mode 100644 index 00000000..089d9427 --- /dev/null +++ b/Xamarin.Forms.Core/SearchBar.cs @@ -0,0 +1,156 @@ +using System; +using System.Windows.Input; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_SearchBarRenderer))] + public class SearchBar : View, IFontElement + { + public static readonly BindableProperty SearchCommandProperty = BindableProperty.Create("SearchCommand", typeof(ICommand), typeof(SearchBar), null, propertyChanged: OnCommandChanged); + + public static readonly BindableProperty SearchCommandParameterProperty = BindableProperty.Create("SearchCommandParameter", typeof(object), typeof(SearchBar), null); + + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(SearchBar), default(string), BindingMode.TwoWay, + propertyChanged: (bindable, oldValue, newValue) => + { + var searchBar = (SearchBar)bindable; + EventHandler<TextChangedEventArgs> eh = searchBar.TextChanged; + if (eh != null) + eh(searchBar, new TextChangedEventArgs((string)oldValue, (string)newValue)); + }); + + public static readonly BindableProperty CancelButtonColorProperty = BindableProperty.Create("CancelButtonColor", typeof(Color), typeof(SearchBar), default(Color)); + + public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(SearchBar), null); + + public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create("FontFamily", typeof(string), typeof(SearchBar), default(string)); + + public static readonly BindableProperty FontSizeProperty = BindableProperty.Create("FontSize", typeof(double), typeof(SearchBar), -1.0, + defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (SearchBar)bindable)); + + public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create("FontAttributes", typeof(FontAttributes), typeof(SearchBar), FontAttributes.None); + + public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create("HorizontalTextAlignment", typeof(TextAlignment), typeof(SearchBar), TextAlignment.Start); + + public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(SearchBar), Color.Default); + + public static readonly BindableProperty PlaceholderColorProperty = BindableProperty.Create("PlaceholderColor", typeof(Color), typeof(SearchBar), Color.Default); + + public Color CancelButtonColor + { + get { return (Color)GetValue(CancelButtonColorProperty); } + set { SetValue(CancelButtonColorProperty, value); } + } + + public TextAlignment HorizontalTextAlignment + { + get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); } + set { SetValue(HorizontalTextAlignmentProperty, value); } + } + + public string Placeholder + { + get { return (string)GetValue(PlaceholderProperty); } + set { SetValue(PlaceholderProperty, value); } + } + + public Color PlaceholderColor + { + get { return (Color)GetValue(PlaceholderColorProperty); } + set { SetValue(PlaceholderColorProperty, value); } + } + + public ICommand SearchCommand + { + get { return (ICommand)GetValue(SearchCommandProperty); } + set { SetValue(SearchCommandProperty, value); } + } + + public object SearchCommandParameter + { + get { return GetValue(SearchCommandParameterProperty); } + set { SetValue(SearchCommandParameterProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public Color TextColor + { + get { return (Color)GetValue(TextColorProperty); } + set { SetValue(TextColorProperty, value); } + } + + bool IsEnabledCore + { + set { SetValueCore(IsEnabledProperty, value); } + } + + public FontAttributes FontAttributes + { + get { return (FontAttributes)GetValue(FontAttributesProperty); } + set { SetValue(FontAttributesProperty, value); } + } + + public string FontFamily + { + get { return (string)GetValue(FontFamilyProperty); } + set { SetValue(FontFamilyProperty, value); } + } + + [TypeConverter(typeof(FontSizeConverter))] + public double FontSize + { + get { return (double)GetValue(FontSizeProperty); } + set { SetValue(FontSizeProperty, value); } + } + + public event EventHandler SearchButtonPressed; + + public event EventHandler<TextChangedEventArgs> TextChanged; + + internal void OnSearchButtonPressed() + { + ICommand cmd = SearchCommand; + + if (cmd != null && !cmd.CanExecute(SearchCommandParameter)) + return; + + cmd?.Execute(SearchCommandParameter); + SearchButtonPressed?.Invoke(this, EventArgs.Empty); + } + + void CommandCanExecuteChanged(object sender, EventArgs eventArgs) + { + ICommand cmd = SearchCommand; + if (cmd != null) + IsEnabledCore = cmd.CanExecute(SearchCommandParameter); + } + + static void OnCommandChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (SearchBar)bindable; + var newCommand = (Command)newValue; + var oldCommand = (Command)oldValue; + + if (oldCommand != null) + { + oldCommand.CanExecuteChanged -= self.CommandCanExecuteChanged; + } + + if (newCommand != null) + { + newCommand.CanExecuteChanged += self.CommandCanExecuteChanged; + self.CommandCanExecuteChanged(self, EventArgs.Empty); + } + else + { + self.IsEnabledCore = true; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/SelectedItemChangedEventArgs.cs b/Xamarin.Forms.Core/SelectedItemChangedEventArgs.cs new file mode 100644 index 00000000..c37881f5 --- /dev/null +++ b/Xamarin.Forms.Core/SelectedItemChangedEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms +{ + public class SelectedItemChangedEventArgs : EventArgs + { + public SelectedItemChangedEventArgs(object selectedItem) + { + SelectedItem = selectedItem; + } + + public object SelectedItem { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/SelectedPositionChangedEventArgs.cs b/Xamarin.Forms.Core/SelectedPositionChangedEventArgs.cs new file mode 100644 index 00000000..5f696b28 --- /dev/null +++ b/Xamarin.Forms.Core/SelectedPositionChangedEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms +{ + public class SelectedPositionChangedEventArgs : EventArgs + { + public SelectedPositionChangedEventArgs(int selectedPosition) + { + SelectedPosition = selectedPosition; + } + + public object SelectedPosition { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/SeparatorMenuItem.cs b/Xamarin.Forms.Core/SeparatorMenuItem.cs new file mode 100644 index 00000000..5ddf70ae --- /dev/null +++ b/Xamarin.Forms.Core/SeparatorMenuItem.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + internal class SeparatorMenuItem : BaseMenuItem + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/SeparatorVisibility.cs b/Xamarin.Forms.Core/SeparatorVisibility.cs new file mode 100644 index 00000000..6d2fdfc1 --- /dev/null +++ b/Xamarin.Forms.Core/SeparatorVisibility.cs @@ -0,0 +1,8 @@ +namespace Xamarin.Forms +{ + public enum SeparatorVisibility + { + Default = 0, + None = 1 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Setter.cs b/Xamarin.Forms.Core/Setter.cs new file mode 100644 index 00000000..b260e8f0 --- /dev/null +++ b/Xamarin.Forms.Core/Setter.cs @@ -0,0 +1,88 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Xml; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + [ContentProperty("Value")] + public sealed class Setter : IValueProvider + { + readonly ConditionalWeakTable<BindableObject, object> _originalValues = new ConditionalWeakTable<BindableObject, object>(); + + public BindableProperty Property { get; set; } + + public object Value { get; set; } + + object IValueProvider.ProvideValue(IServiceProvider serviceProvider) + { + if (Property == null) + { + var lineInfoProvider = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider; + IXmlLineInfo lineInfo = lineInfoProvider != null ? lineInfoProvider.XmlLineInfo : new XmlLineInfo(); + throw new XamlParseException("Property not set", lineInfo); + } + var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider; + + Func<MemberInfo> minforetriever = + () => + (MemberInfo)Property.DeclaringType.GetRuntimeProperty(Property.PropertyName) ?? (MemberInfo)Property.DeclaringType.GetRuntimeMethod("Get" + Property.PropertyName, new[] { typeof(BindableObject) }); + + object value = valueconverter.Convert(Value, Property.ReturnType, minforetriever, serviceProvider); + Value = value; + return this; + } + + internal void Apply(BindableObject target, bool fromStyle = false) + { + if (target == null) + throw new ArgumentNullException("target"); + if (Property == null) + return; + + object originalValue = target.GetValue(Property); + if (!Equals(originalValue, Property.DefaultValue)) + { + _originalValues.Remove(target); + _originalValues.Add(target, originalValue); + } + + var dynamicResource = Value as DynamicResource; + var binding = Value as BindingBase; + if (binding != null) + target.SetBinding(Property, binding.Clone(), fromStyle); + else if (dynamicResource != null) + target.SetDynamicResource(Property, dynamicResource.Key, fromStyle); + else + target.SetValue(Property, Value, fromStyle); + } + + internal void UnApply(BindableObject target, bool fromStyle = false) + { + if (target == null) + throw new ArgumentNullException("target"); + if (Property == null) + return; + + object actual = target.GetValue(Property); + if (!fromStyle && !Equals(actual, Value)) + { + //Do not reset default value if the value has been changed + _originalValues.Remove(target); + return; + } + + object defaultValue; + if (_originalValues.TryGetValue(target, out defaultValue)) + { + //reset default value, unapply bindings and dynamicResource + target.SetValue(Property, defaultValue, fromStyle); + _originalValues.Remove(target); + } + else + target.ClearValue(Property); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/SettersExtensions.cs b/Xamarin.Forms.Core/SettersExtensions.cs new file mode 100644 index 00000000..8f94e6d8 --- /dev/null +++ b/Xamarin.Forms.Core/SettersExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms +{ + public static class SettersExtensions + { + public static void Add(this IList<Setter> setters, BindableProperty property, object value) + { + setters.Add(new Setter { Property = property, Value = value }); + } + + public static void AddBinding(this IList<Setter> setters, BindableProperty property, Binding binding) + { + if (binding == null) + throw new ArgumentNullException("binding"); + + setters.Add(new Setter { Property = property, Value = binding }); + } + + public static void AddDynamicResource(this IList<Setter> setters, BindableProperty property, string key) + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentNullException("key"); + setters.Add(new Setter { Property = property, Value = new DynamicResource(key) }); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Size.cs b/Xamarin.Forms.Core/Size.cs new file mode 100644 index 00000000..4e709a10 --- /dev/null +++ b/Xamarin.Forms.Core/Size.cs @@ -0,0 +1,110 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; + +namespace Xamarin.Forms +{ + [DebuggerDisplay("Width={Width}, Height={Height}")] + public struct Size + { + double _width; + double _height; + + public static readonly Size Zero; + + public Size(double width, double height) + { + if (double.IsNaN(width)) + throw new ArgumentException("NaN is not a valid value for width"); + if (double.IsNaN(height)) + throw new ArgumentException("NaN is not a valid value for height"); + _width = width; + _height = height; + } + + public bool IsZero + { + get { return (_width == 0) && (_height == 0); } + } + + [DefaultValue(0d)] + public double Width + { + get { return _width; } + set + { + if (double.IsNaN(value)) + throw new ArgumentException("NaN is not a valid value for Width"); + _width = value; + } + } + + [DefaultValue(0d)] + public double Height + { + get { return _height; } + set + { + if (double.IsNaN(value)) + throw new ArgumentException("NaN is not a valid value for Height"); + _height = value; + } + } + + public static Size operator +(Size s1, Size s2) + { + return new Size(s1._width + s2._width, s1._height + s2._height); + } + + public static Size operator -(Size s1, Size s2) + { + return new Size(s1._width - s2._width, s1._height - s2._height); + } + + public static Size operator *(Size s1, double value) + { + return new Size(s1._width * value, s1._height * value); + } + + public static bool operator ==(Size s1, Size s2) + { + return (s1._width == s2._width) && (s1._height == s2._height); + } + + public static bool operator !=(Size s1, Size s2) + { + return (s1._width != s2._width) || (s1._height != s2._height); + } + + public static explicit operator Point(Size size) + { + return new Point(size.Width, size.Height); + } + + public bool Equals(Size other) + { + return _width.Equals(other._width) && _height.Equals(other._height); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + return obj is Size && Equals((Size)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (_width.GetHashCode() * 397) ^ _height.GetHashCode(); + } + } + + public override string ToString() + { + return string.Format("{{Width={0} Height={1}}}", _width.ToString(CultureInfo.InvariantCulture), _height.ToString(CultureInfo.InvariantCulture)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/SizeRequest.cs b/Xamarin.Forms.Core/SizeRequest.cs new file mode 100644 index 00000000..3a54aca2 --- /dev/null +++ b/Xamarin.Forms.Core/SizeRequest.cs @@ -0,0 +1,29 @@ +using System.Diagnostics; + +namespace Xamarin.Forms +{ + [DebuggerDisplay("Request={Request.Width}x{Request.Height}, Minimum={Minimum.Width}x{Minimum.Height}")] + public struct SizeRequest + { + public Size Request { get; set; } + + public Size Minimum { get; set; } + + public SizeRequest(Size request, Size minimum) + { + Request = request; + Minimum = minimum; + } + + public SizeRequest(Size request) + { + Request = request; + Minimum = request; + } + + public override string ToString() + { + return string.Format("{{Request={0} Minimum={1}}}", Request, Minimum); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Slider.cs b/Xamarin.Forms.Core/Slider.cs new file mode 100644 index 00000000..6363c410 --- /dev/null +++ b/Xamarin.Forms.Core/Slider.cs @@ -0,0 +1,85 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_SliderRenderer))] + public class Slider : View + { + public static readonly BindableProperty MinimumProperty = BindableProperty.Create("Minimum", typeof(double), typeof(Slider), 0d, validateValue: (bindable, value) => + { + var slider = (Slider)bindable; + return (double)value < slider.Maximum; + }, coerceValue: (bindable, value) => + { + var slider = (Slider)bindable; + slider.Value = slider.Value.Clamp((double)value, slider.Maximum); + return value; + }); + + public static readonly BindableProperty MaximumProperty = BindableProperty.Create("Maximum", typeof(double), typeof(Slider), 1d, validateValue: (bindable, value) => + { + var slider = (Slider)bindable; + return (double)value > slider.Minimum; + }, coerceValue: (bindable, value) => + { + var slider = (Slider)bindable; + slider.Value = slider.Value.Clamp(slider.Minimum, (double)value); + return value; + }); + + public static readonly BindableProperty ValueProperty = BindableProperty.Create("Value", typeof(double), typeof(Slider), 0d, BindingMode.TwoWay, coerceValue: (bindable, value) => + { + var slider = (Slider)bindable; + return ((double)value).Clamp(slider.Minimum, slider.Maximum); + }, propertyChanged: (bindable, oldValue, newValue) => + { + var slider = (Slider)bindable; + EventHandler<ValueChangedEventArgs> eh = slider.ValueChanged; + if (eh != null) + eh(slider, new ValueChangedEventArgs((double)oldValue, (double)newValue)); + }); + + public Slider() + { + } + + public Slider(double min, double max, double val) + { + if (min >= max) + throw new ArgumentOutOfRangeException("min"); + + if (max > Minimum) + { + Maximum = max; + Minimum = min; + } + else + { + Minimum = min; + Maximum = max; + } + Value = val.Clamp(min, max); + } + + public double Maximum + { + get { return (double)GetValue(MaximumProperty); } + set { SetValue(MaximumProperty, value); } + } + + public double Minimum + { + get { return (double)GetValue(MinimumProperty); } + set { SetValue(MinimumProperty, value); } + } + + public double Value + { + get { return (double)GetValue(ValueProperty); } + set { SetValue(ValueProperty, value); } + } + + public event EventHandler<ValueChangedEventArgs> ValueChanged; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Span.cs b/Xamarin.Forms.Core/Span.cs new file mode 100644 index 00000000..3789ddff --- /dev/null +++ b/Xamarin.Forms.Core/Span.cs @@ -0,0 +1,169 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Xamarin.Forms +{ + [ContentProperty("Text")] + public sealed class Span : INotifyPropertyChanged, IFontElement + { + Color _backgroundColor; + + Font _font; + FontAttributes _fontAttributes; + string _fontFamily; + double _fontSize; + + Color _foregroundColor; + bool _inUpdate; // if we ever make this thread safe we need to move to a mutex + + string _text; + + public Span() + { + _fontFamily = null; + _fontAttributes = FontAttributes.None; + _fontSize = Device.GetNamedSize(NamedSize.Default, typeof(Label), true); + _font = Font.SystemFontOfSize(_fontSize); + } + + public Color BackgroundColor + { + get { return _backgroundColor; } + set + { + if (_backgroundColor == value) + return; + _backgroundColor = value; + OnPropertyChanged(); + } + } + + [Obsolete("Please use the Font properties directly. Obsolete in 1.3.0")] + public Font Font + { + get { return _font; } + set + { + if (_font == value) + return; + _font = value; + OnPropertyChanged(); + UpdateFontPropertiesFromStruct(); + } + } + + public Color ForegroundColor + { + get { return _foregroundColor; } + set + { + if (_foregroundColor == value) + return; + _foregroundColor = value; + OnPropertyChanged(); + } + } + + public string Text + { + get { return _text; } + set + { + if (_text == value) + return; + _text = value; + OnPropertyChanged(); + } + } + + public FontAttributes FontAttributes + { + get { return _fontAttributes; } + set + { + if (_fontAttributes == value) + return; + _fontAttributes = value; + OnPropertyChanged(); + UpdateStructFromFontProperties(); + } + } + + public string FontFamily + { + get { return _fontFamily; } + set + { + if (_fontFamily == value) + return; + _fontFamily = value; + OnPropertyChanged(); + UpdateStructFromFontProperties(); + } + } + + [TypeConverter(typeof(FontSizeConverter))] + public double FontSize + { + get { return _fontSize; } + set + { + if (_fontSize == value) + return; + _fontSize = value; + OnPropertyChanged(); + UpdateStructFromFontProperties(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChangedEventHandler handler = PropertyChanged; + if (handler != null) + handler(this, new PropertyChangedEventArgs(propertyName)); + } + + void UpdateFontPropertiesFromStruct() + { + if (_inUpdate) + return; + _inUpdate = true; + + if (Font == Font.Default) + { + FontSize = Device.GetNamedSize(NamedSize.Default, typeof(Label), true); + FontFamily = null; + FontAttributes = FontAttributes.None; + } + else + { + FontSize = Font.UseNamedSize ? Device.GetNamedSize(Font.NamedSize, typeof(Label), true) : Font.FontSize; + FontFamily = Font.FontFamily; + FontAttributes = Font.FontAttributes; + } + + _inUpdate = false; + } + + void UpdateStructFromFontProperties() + { + if (_inUpdate) + return; + _inUpdate = true; + + if (FontFamily != null) + { + Font = Font.OfSize(FontFamily, FontSize).WithAttributes(FontAttributes); + } + else + { + Font = Font.SystemFontOfSize(FontSize).WithAttributes(FontAttributes); + } + + _inUpdate = false; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/SplitOrderedList.cs b/Xamarin.Forms.Core/SplitOrderedList.cs new file mode 100644 index 00000000..6f423210 --- /dev/null +++ b/Xamarin.Forms.Core/SplitOrderedList.cs @@ -0,0 +1,497 @@ +// SplitOrderedList.cs +// +// Copyright (c) 2010 Jérémie "Garuma" Laval +// +// 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.Collections.Generic; +using System.Threading; + +namespace Xamarin.Forms +{ + internal class SplitOrderedList<TKey, T> + { + const int MaxLoad = 5; + const uint BucketSize = 512; + + static readonly byte[] ReverseTable = + { + 0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240, 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248, 4, + 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244, 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, + 50, 178, 114, 242, 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, 14, 142, 78, 206, 46, 174, 110, + 238, 30, 158, 94, 222, 62, 190, 126, 254, 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5, 133, 69, + 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, + 115, 243, 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, 15, 143, 79, 207, 47, 175, 111, 239, 31, + 159, 95, 223, 63, 191, 127, 255 + }; + + static readonly byte[] LogTable = + { + 0xFF, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7 + }; + + readonly IEqualityComparer<TKey> _comparer; + + readonly Node _head; + readonly Node _tail; + + Node[] _buckets = new Node[BucketSize]; + int _count; + int _size = 2; + + SimpleRwLock _slim = new SimpleRwLock(); + + public SplitOrderedList(IEqualityComparer<TKey> comparer) + { + _comparer = comparer; + _head = new Node().Init(0); + _tail = new Node().Init(ulong.MaxValue); + _head.Next = _tail; + SetBucket(0, _head); + } + + public int Count + { + get { return _count; } + } + + public bool CompareExchange(uint key, TKey subKey, T data, Func<T, bool> check) + { + Node node; + uint b = key % (uint)_size; + Node bucket; + + if ((bucket = GetBucket(b)) == null) + bucket = InitializeBucket(b); + + if (!ListFind(ComputeRegularKey(key), subKey, bucket, out node)) + return false; + + if (!check(node.Data)) + return false; + + node.Data = data; + + return true; + } + + public bool Delete(uint key, TKey subKey, out T data) + { + uint b = key % (uint)_size; + Node bucket; + + if ((bucket = GetBucket(b)) == null) + bucket = InitializeBucket(b); + + if (!ListDelete(bucket, ComputeRegularKey(key), subKey, out data)) + return false; + + Interlocked.Decrement(ref _count); + return true; + } + + public bool Find(uint key, TKey subKey, out T data) + { + Node node; + uint b = key % (uint)_size; + data = default(T); + Node bucket; + + if ((bucket = GetBucket(b)) == null) + bucket = InitializeBucket(b); + + if (!ListFind(ComputeRegularKey(key), subKey, bucket, out node)) + return false; + + data = node.Data; + + return !node.Marked; + } + + public IEnumerator<T> GetEnumerator() + { + Node node = _head.Next; + + while (node != _tail) + { + while (node.Marked || (node.Key & 1) == 0) + { + node = node.Next; + if (node == _tail) + yield break; + } + yield return node.Data; + node = node.Next; + } + } + + public bool Insert(uint key, TKey subKey, T data) + { + Node current; + return InsertInternal(key, subKey, data, null, out current); + } + + public T InsertOrGet(uint key, TKey subKey, T data, Func<T> dataCreator) + { + Node current; + InsertInternal(key, subKey, data, dataCreator, out current); + return current.Data; + } + + public T InsertOrUpdate(uint key, TKey subKey, Func<T> addGetter, Func<T, T> updateGetter) + { + Node current; + bool result = InsertInternal(key, subKey, default(T), addGetter, out current); + + if (result) + return current.Data; + + // FIXME: this should have a CAS-like behavior + return current.Data = updateGetter(current.Data); + } + + public T InsertOrUpdate(uint key, TKey subKey, T addValue, T updateValue) + { + Node current; + if (InsertInternal(key, subKey, addValue, null, out current)) + return current.Data; + + // FIXME: this should have a CAS-like behavior + return current.Data = updateValue; + } + + // When we run out of space for bucket storage, we use a lock-based array resize + void CheckSegment(uint segment, bool readLockTaken) + { + if (segment < _buckets.Length) + return; + + if (readLockTaken) + _slim.ExitReadLock(); + try + { + _slim.EnterWriteLock(); + while (segment >= _buckets.Length) + Array.Resize(ref _buckets, _buckets.Length * 2); + } + finally + { + _slim.ExitWriteLock(); + } + if (readLockTaken) + _slim.EnterReadLock(); + } + + // Reverse integer bits + static ulong ComputeDummyKey(uint key) + { + return (ulong)(((uint)ReverseTable[key & 0xff] << 24) | ((uint)ReverseTable[(key >> 8) & 0xff] << 16) | ((uint)ReverseTable[(key >> 16) & 0xff] << 8) | ReverseTable[(key >> 24) & 0xff]) << 1; + } + + // Reverse integer bits and make sure LSB is set + static ulong ComputeRegularKey(uint key) + { + return ComputeDummyKey(key) | 1; + } + + // Bucket storage is abstracted in a simple two-layer tree to avoid too much memory resize + Node GetBucket(uint index) + { + if (index >= _buckets.Length) + return null; + return _buckets[index]; + } + + // Turn v's MSB off + static uint GetParent(uint v) + { + uint t, tt; + + // Find MSB position in v + int pos = (tt = v >> 16) > 0 ? (t = tt >> 8) > 0 ? 24 + LogTable[t] : 16 + LogTable[tt] : (t = v >> 8) > 0 ? 8 + LogTable[t] : LogTable[v]; + + return (uint)(v & ~(1 << pos)); + } + + Node InitializeBucket(uint b) + { + Node current; + uint parent = GetParent(b); + Node bucket; + + if ((bucket = GetBucket(parent)) == null) + bucket = InitializeBucket(parent); + + Node dummy = new Node().Init(ComputeDummyKey(b)); + if (!ListInsert(dummy, bucket, out current, null)) + return current; + + return SetBucket(b, dummy); + } + + bool InsertInternal(uint key, TKey subKey, T data, Func<T> dataCreator, out Node current) + { + Node node = new Node().Init(ComputeRegularKey(key), subKey, data); + + uint b = key % (uint)_size; + Node bucket; + + if ((bucket = GetBucket(b)) == null) + bucket = InitializeBucket(b); + + if (!ListInsert(node, bucket, out current, dataCreator)) + return false; + + int csize = _size; + if (Interlocked.Increment(ref _count) / csize > MaxLoad && (csize & 0x40000000) == 0) + Interlocked.CompareExchange(ref _size, 2 * csize, csize); + + current = node; + + return true; + } + + bool ListDelete(Node startPoint, ulong key, TKey subKey, out T data) + { + Node rightNode = null, rightNodeNext = null, leftNode = null; + data = default(T); + Node markedNode = null; + + do + { + rightNode = ListSearch(key, subKey, ref leftNode, startPoint); + if (rightNode == _tail || rightNode.Key != key || !_comparer.Equals(subKey, rightNode.SubKey)) + return false; + + data = rightNode.Data; + rightNodeNext = rightNode.Next; + + if (!rightNodeNext.Marked) + { + if (markedNode == null) + markedNode = new Node(); + markedNode.Init(rightNodeNext); + + if (Interlocked.CompareExchange(ref rightNode.Next, markedNode, rightNodeNext) == rightNodeNext) + break; + } + } while (true); + + if (Interlocked.CompareExchange(ref leftNode.Next, rightNodeNext, rightNode) != rightNode) + ListSearch(rightNode.Key, subKey, ref leftNode, startPoint); + + return true; + } + + bool ListFind(ulong key, TKey subKey, Node startPoint, out Node data) + { + Node rightNode = null, leftNode = null; + data = null; + + rightNode = ListSearch(key, subKey, ref leftNode, startPoint); + data = rightNode; + + return rightNode != _tail && rightNode.Key == key && _comparer.Equals(subKey, rightNode.SubKey); + } + + bool ListInsert(Node newNode, Node startPoint, out Node current, Func<T> dataCreator) + { + ulong key = newNode.Key; + Node rightNode = null, leftNode = null; + + do + { + rightNode = current = ListSearch(key, newNode.SubKey, ref leftNode, startPoint); + if (rightNode != _tail && rightNode.Key == key && _comparer.Equals(newNode.SubKey, rightNode.SubKey)) + return false; + + newNode.Next = rightNode; + if (dataCreator != null) + newNode.Data = dataCreator(); + if (Interlocked.CompareExchange(ref leftNode.Next, newNode, rightNode) == rightNode) + return true; + } while (true); + } + + Node ListSearch(ulong key, TKey subKey, ref Node left, Node h) + { + Node leftNodeNext = null, rightNode = null; + + do + { + Node t = h; + Node tNext = t.Next; + do + { + if (!tNext.Marked) + { + left = t; + leftNodeNext = tNext; + } + t = tNext.Marked ? tNext.Next : tNext; + if (t == _tail) + break; + + tNext = t.Next; + } while (tNext.Marked || t.Key < key || (tNext.Key == key && !_comparer.Equals(subKey, t.SubKey))); + + rightNode = t; + + if (leftNodeNext == rightNode) + { + if (rightNode != _tail && rightNode.Next.Marked) + continue; + return rightNode; + } + + if (Interlocked.CompareExchange(ref left.Next, rightNode, leftNodeNext) == leftNodeNext) + { + if (rightNode != _tail && rightNode.Next.Marked) + continue; + return rightNode; + } + } while (true); + } + + Node SetBucket(uint index, Node node) + { + try + { + _slim.EnterReadLock(); + CheckSegment(index, true); + + Interlocked.CompareExchange(ref _buckets[index], node, null); + return _buckets[index]; + } + finally + { + _slim.ExitReadLock(); + } + } + + class Node + { + public T Data; + public ulong Key; + public bool Marked; + public Node Next; + public TKey SubKey; + + public Node Init(ulong key, TKey subKey, T data) + { + Key = key; + SubKey = subKey; + Data = data; + + Marked = false; + Next = null; + + return this; + } + + // Used to create dummy node + public Node Init(ulong key) + { + Key = key; + Data = default(T); + + Next = null; + Marked = false; + SubKey = default(TKey); + + return this; + } + + // Used to create marked node + public Node Init(Node wrapped) + { + Marked = true; + Next = wrapped; + + Key = 0; + Data = default(T); + SubKey = default(TKey); + + return this; + } + } + + struct SimpleRwLock + { + const int RwWait = 1; + const int RwWrite = 2; + const int RwRead = 4; + + int _rwlock; + + public void EnterReadLock() + { + var sw = new SpinWait(); + do + { + while ((_rwlock & (RwWrite | RwWait)) > 0) + sw.SpinOnce(); + + if ((Interlocked.Add(ref _rwlock, RwRead) & (RwWait | RwWait)) == 0) + return; + + Interlocked.Add(ref _rwlock, -RwRead); + } while (true); + } + + public void ExitReadLock() + { + Interlocked.Add(ref _rwlock, -RwRead); + } + + public void EnterWriteLock() + { + var sw = new SpinWait(); + do + { + int state = _rwlock; + if (state < RwWrite) + { + if (Interlocked.CompareExchange(ref _rwlock, RwWrite, state) == state) + return; + state = _rwlock; + } + // We register our interest in taking the Write lock (if upgradeable it's already done) + while ((state & RwWait) == 0 && Interlocked.CompareExchange(ref _rwlock, state | RwWait, state) != state) + state = _rwlock; + // Before falling to sleep + while (_rwlock > RwWait) + sw.SpinOnce(); + } while (true); + } + + public void ExitWriteLock() + { + Interlocked.Add(ref _rwlock, -RwWrite); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/StackLayout.cs b/Xamarin.Forms.Core/StackLayout.cs new file mode 100644 index 00000000..4dc7bfa3 --- /dev/null +++ b/Xamarin.Forms.Core/StackLayout.cs @@ -0,0 +1,460 @@ +using System; + +namespace Xamarin.Forms +{ + public class StackLayout : Layout<View> + { + public static readonly BindableProperty OrientationProperty = BindableProperty.Create("Orientation", typeof(StackOrientation), typeof(StackLayout), StackOrientation.Vertical, + propertyChanged: (bindable, oldvalue, newvalue) => ((StackLayout)bindable).InvalidateLayout()); + + public static readonly BindableProperty SpacingProperty = BindableProperty.Create("Spacing", typeof(double), typeof(StackLayout), 6d, + propertyChanged: (bindable, oldvalue, newvalue) => ((StackLayout)bindable).InvalidateLayout()); + + LayoutInformation _layoutInformation = new LayoutInformation(); + + public StackOrientation Orientation + { + get { return (StackOrientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + + public double Spacing + { + get { return (double)GetValue(SpacingProperty); } + set { SetValue(SpacingProperty, value); } + } + + protected override void LayoutChildren(double x, double y, double width, double height) + { + if (!HasVisibileChildren()) + { + return; + } + + if (width == _layoutInformation.Constraint.Width && height == _layoutInformation.Constraint.Height) + { + StackOrientation orientation = Orientation; + + AlignOffAxis(_layoutInformation, orientation, width, height); + ProcessExpanders(_layoutInformation, orientation, x, y, width, height); + } + else + { + CalculateLayout(_layoutInformation, x, y, width, height, true); + } + + LayoutInformation layoutInformationCopy = _layoutInformation; + + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = (View)LogicalChildren[i]; + if (child.IsVisible) + LayoutChildIntoBoundingRegion(child, layoutInformationCopy.Plots[i], layoutInformationCopy.Requests[i]); + } + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + if (!HasVisibileChildren()) + { + return new SizeRequest(); + } + + // calculate with padding inset for X,Y so we can hopefully re-use this in the layout pass + Thickness padding = Padding; + CalculateLayout(_layoutInformation, padding.Left, padding.Top, widthConstraint, heightConstraint, false); + var result = new SizeRequest(_layoutInformation.Bounds.Size, _layoutInformation.MinimumSize); + return result; + } + + internal override void ComputeConstraintForView(View view) + { + ComputeConstraintForView(view, false); + } + + internal override void InvalidateMeasure(InvalidationTrigger trigger) + { + _layoutInformation = new LayoutInformation(); + base.InvalidateMeasure(trigger); + } + + void AlignOffAxis(LayoutInformation layout, StackOrientation orientation, double widthConstraint, double heightConstraint) + { + for (var i = 0; i < layout.Plots.Length; i++) + { + if (!((View)LogicalChildren[i]).IsVisible) + continue; + if (orientation == StackOrientation.Vertical) + { + layout.Plots[i].Width = widthConstraint; + } + else + { + layout.Plots[i].Height = heightConstraint; + } + } + } + + void CalculateLayout(LayoutInformation layout, double x, double y, double widthConstraint, double heightConstraint, bool processExpanders) + { + layout.Constraint = new Size(widthConstraint, heightConstraint); + layout.Expanders = 0; + layout.CompressionSpace = 0; + layout.Plots = new Rectangle[Children.Count]; + layout.Requests = new SizeRequest[Children.Count]; + + StackOrientation orientation = Orientation; + + CalculateNaiveLayout(layout, orientation, x, y, widthConstraint, heightConstraint); + CompressNaiveLayout(layout, orientation, widthConstraint, heightConstraint); + + if (processExpanders) + { + AlignOffAxis(layout, orientation, widthConstraint, heightConstraint); + ProcessExpanders(layout, orientation, x, y, widthConstraint, heightConstraint); + } + } + + void CalculateNaiveLayout(LayoutInformation layout, StackOrientation orientation, double x, double y, double widthConstraint, double heightConstraint) + { + layout.CompressionSpace = 0; + + double xOffset = x; + double yOffset = y; + double boundsWidth = 0; + double boundsHeight = 0; + double minimumWidth = 0; + double minimumHeight = 0; + double spacing = Spacing; + if (orientation == StackOrientation.Vertical) + { + View expander = null; + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + + if (child.VerticalOptions.Expands) + { + layout.Expanders++; + if (expander != null) + { + // we have multiple expanders, make sure previous expanders are reset to not be fixed because they no logner are + ComputeConstraintForView(child, false); + } + expander = child; + } + SizeRequest request = child.Measure(widthConstraint, double.PositiveInfinity, MeasureFlags.IncludeMargins); + + var bounds = new Rectangle(x, yOffset, request.Request.Width, request.Request.Height); + layout.Plots[i] = bounds; + layout.Requests[i] = request; + layout.CompressionSpace += Math.Max(0, request.Request.Height - request.Minimum.Height); + yOffset = bounds.Bottom + spacing; + + boundsWidth = Math.Max(boundsWidth, request.Request.Width); + boundsHeight = bounds.Bottom - y; + minimumHeight += request.Minimum.Height + spacing; + minimumWidth = Math.Max(minimumWidth, request.Minimum.Width); + } + minimumHeight -= spacing; + if (expander != null) + ComputeConstraintForView(expander, layout.Expanders == 1); // warning : slightly obtuse, but we either need to setup the expander or clear the last one + } + else + { + View expander = null; + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + + if (child.HorizontalOptions.Expands) + { + layout.Expanders++; + if (expander != null) + { + ComputeConstraintForView(child, false); + } + expander = child; + } + SizeRequest request = child.Measure(double.PositiveInfinity, heightConstraint, MeasureFlags.IncludeMargins); + + var bounds = new Rectangle(xOffset, y, request.Request.Width, request.Request.Height); + layout.Plots[i] = bounds; + layout.Requests[i] = request; + layout.CompressionSpace += Math.Max(0, request.Request.Width - request.Minimum.Width); + xOffset = bounds.Right + spacing; + + boundsWidth = bounds.Right - x; + boundsHeight = Math.Max(boundsHeight, request.Request.Height); + minimumWidth += request.Minimum.Width + spacing; + minimumHeight = Math.Max(minimumHeight, request.Minimum.Height); + } + minimumWidth -= spacing; + if (expander != null) + ComputeConstraintForView(expander, layout.Expanders == 1); + } + + layout.Bounds = new Rectangle(x, y, boundsWidth, boundsHeight); + layout.MinimumSize = new Size(minimumWidth, minimumHeight); + } + + void CompressHorizontalLayout(LayoutInformation layout, double widthConstraint, double heightConstraint) + { + double xOffset = 0; + + if (widthConstraint >= layout.Bounds.Width) + { + // no need to compress + return; + } + + double requiredCompression = layout.Bounds.Width - widthConstraint; + double compressionSpace = layout.CompressionSpace; + double compressionPressure = (requiredCompression / layout.CompressionSpace).Clamp(0, 1); + + for (var i = 0; i < layout.Plots.Length; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + + Size minimum = layout.Requests[i].Minimum; + + layout.Plots[i].X -= xOffset; + + Rectangle plot = layout.Plots[i]; + double availableSpace = plot.Width - minimum.Width; + if (availableSpace <= 0) + continue; + + compressionSpace -= availableSpace; + + double compression = availableSpace * compressionPressure; + xOffset += compression; + + double newWidth = plot.Width - compression; + SizeRequest newRequest = child.Measure(newWidth, heightConstraint, MeasureFlags.IncludeMargins); + + layout.Requests[i] = newRequest; + + plot.Height = newRequest.Request.Height; + + if (newRequest.Request.Width < newWidth) + { + double delta = newWidth - newRequest.Request.Width; + newWidth = newRequest.Request.Width; + xOffset += delta; + requiredCompression = requiredCompression - xOffset; + compressionPressure = (requiredCompression / compressionSpace).Clamp(0, 1); + } + plot.Width = newWidth; + + layout.Bounds.Height = Math.Max(layout.Bounds.Height, plot.Height); + + layout.Plots[i] = plot; + } + } + + void CompressNaiveLayout(LayoutInformation layout, StackOrientation orientation, double widthConstraint, double heightConstraint) + { + if (layout.CompressionSpace <= 0) + return; + + if (orientation == StackOrientation.Vertical) + { + CompressVerticalLayout(layout, widthConstraint, heightConstraint); + } + else + { + CompressHorizontalLayout(layout, widthConstraint, heightConstraint); + } + } + + void CompressVerticalLayout(LayoutInformation layout, double widthConstraint, double heightConstraint) + { + double yOffset = 0; + + if (heightConstraint >= layout.Bounds.Height) + { + // no need to compress + return; + } + + double requiredCompression = layout.Bounds.Height - heightConstraint; + double compressionSpace = layout.CompressionSpace; + double compressionPressure = (requiredCompression / layout.CompressionSpace).Clamp(0, 1); + + for (var i = 0; i < layout.Plots.Length; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + + Size minimum = layout.Requests[i].Minimum; + + layout.Plots[i].Y -= yOffset; + + Rectangle plot = layout.Plots[i]; + double availableSpace = plot.Height - minimum.Height; + if (availableSpace <= 0) + continue; + + compressionSpace -= availableSpace; + + double compression = availableSpace * compressionPressure; + yOffset += compression; + + double newHeight = plot.Height - compression; + SizeRequest newRequest = child.Measure(widthConstraint, newHeight, MeasureFlags.IncludeMargins); + + layout.Requests[i] = newRequest; + + plot.Width = newRequest.Request.Width; + + if (newRequest.Request.Height < newHeight) + { + double delta = newHeight - newRequest.Request.Height; + newHeight = newRequest.Request.Height; + yOffset += delta; + requiredCompression = requiredCompression - yOffset; + compressionPressure = (requiredCompression / compressionSpace).Clamp(0, 1); + } + plot.Height = newHeight; + + layout.Bounds.Width = Math.Max(layout.Bounds.Width, plot.Width); + + layout.Plots[i] = plot; + } + } + + void ComputeConstraintForView(View view, bool isOnlyExpander) + { + if (Orientation == StackOrientation.Horizontal) + { + if ((Constraint & LayoutConstraint.VerticallyFixed) != 0 && view.VerticalOptions.Alignment == LayoutAlignment.Fill) + { + if (isOnlyExpander && view.HorizontalOptions.Alignment == LayoutAlignment.Fill && Constraint == LayoutConstraint.Fixed) + { + view.ComputedConstraint = LayoutConstraint.Fixed; + } + else + { + view.ComputedConstraint = LayoutConstraint.VerticallyFixed; + } + } + else + { + view.ComputedConstraint = LayoutConstraint.None; + } + } + else + { + if ((Constraint & LayoutConstraint.HorizontallyFixed) != 0 && view.HorizontalOptions.Alignment == LayoutAlignment.Fill) + { + if (isOnlyExpander && view.VerticalOptions.Alignment == LayoutAlignment.Fill && Constraint == LayoutConstraint.Fixed) + { + view.ComputedConstraint = LayoutConstraint.Fixed; + } + else + { + view.ComputedConstraint = LayoutConstraint.HorizontallyFixed; + } + } + else + { + view.ComputedConstraint = LayoutConstraint.None; + } + } + } + + bool HasVisibileChildren() + { + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (VisualElement)InternalChildren[index]; + if (child.IsVisible) + return true; + } + return false; + } + + void ProcessExpanders(LayoutInformation layout, StackOrientation orientation, double x, double y, double widthConstraint, double heightConstraint) + { + if (layout.Expanders <= 0) + return; + + if (orientation == StackOrientation.Vertical) + { + double extraSpace = heightConstraint - layout.Bounds.Height; + if (extraSpace <= 0) + return; + + double spacePerExpander = extraSpace / layout.Expanders; + double yOffset = 0; + + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + Rectangle plot = layout.Plots[i]; + plot.Y += yOffset; + + if (child.VerticalOptions.Expands) + { + plot.Height += spacePerExpander; + yOffset += spacePerExpander; + } + + layout.Plots[i] = plot; + } + + layout.Bounds.Height = heightConstraint; + } + else + { + double extraSpace = widthConstraint - layout.Bounds.Width; + if (extraSpace <= 0) + return; + + double spacePerExpander = extraSpace / layout.Expanders; + double xOffset = 0; + + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + Rectangle plot = layout.Plots[i]; + plot.X += xOffset; + + if (child.HorizontalOptions.Expands) + { + plot.Width += spacePerExpander; + xOffset += spacePerExpander; + } + + layout.Plots[i] = plot; + } + + layout.Bounds.Width = widthConstraint; + } + } + + class LayoutInformation + { + public Rectangle Bounds; + public double CompressionSpace; + public Size Constraint; + public int Expanders; + public Size MinimumSize; + public Rectangle[] Plots; + public SizeRequest[] Requests; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/StackOrientation.cs b/Xamarin.Forms.Core/StackOrientation.cs new file mode 100644 index 00000000..4ab00fd2 --- /dev/null +++ b/Xamarin.Forms.Core/StackOrientation.cs @@ -0,0 +1,8 @@ +namespace Xamarin.Forms +{ + public enum StackOrientation + { + Vertical, + Horizontal + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Stepper.cs b/Xamarin.Forms.Core/Stepper.cs new file mode 100644 index 00000000..2a3de841 --- /dev/null +++ b/Xamarin.Forms.Core/Stepper.cs @@ -0,0 +1,94 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_StepperRenderer))] + public class Stepper : View + { + public static readonly BindableProperty MaximumProperty = BindableProperty.Create("Maximum", typeof(double), typeof(Stepper), 100.0, validateValue: (bindable, value) => + { + var stepper = (Stepper)bindable; + return (double)value > stepper.Minimum; + }, coerceValue: (bindable, value) => + { + var stepper = (Stepper)bindable; + stepper.Value = stepper.Value.Clamp(stepper.Minimum, (double)value); + return value; + }); + + public static readonly BindableProperty MinimumProperty = BindableProperty.Create("Minimum", typeof(double), typeof(Stepper), 0.0, validateValue: (bindable, value) => + { + var stepper = (Stepper)bindable; + return (double)value < stepper.Maximum; + }, coerceValue: (bindable, value) => + { + var stepper = (Stepper)bindable; + stepper.Value = stepper.Value.Clamp((double)value, stepper.Maximum); + return value; + }); + + public static readonly BindableProperty ValueProperty = BindableProperty.Create("Value", typeof(double), typeof(Stepper), 0.0, BindingMode.TwoWay, coerceValue: (bindable, value) => + { + var stepper = (Stepper)bindable; + return ((double)value).Clamp(stepper.Minimum, stepper.Maximum); + }, propertyChanged: (bindable, oldValue, newValue) => + { + var stepper = (Stepper)bindable; + EventHandler<ValueChangedEventArgs> eh = stepper.ValueChanged; + if (eh != null) + eh(stepper, new ValueChangedEventArgs((double)oldValue, (double)newValue)); + }); + + public static readonly BindableProperty IncrementProperty = BindableProperty.Create("Increment", typeof(double), typeof(Stepper), 1.0); + + public Stepper() + { + } + + public Stepper(double min, double max, double val, double increment) + { + if (min >= max) + throw new ArgumentOutOfRangeException("min"); + if (max > Minimum) + { + Maximum = max; + Minimum = min; + } + else + { + Minimum = min; + Maximum = max; + } + + Value = val.Clamp(min, max); + Increment = increment; + } + + public double Increment + { + get { return (double)GetValue(IncrementProperty); } + set { SetValue(IncrementProperty, value); } + } + + public double Maximum + { + get { return (double)GetValue(MaximumProperty); } + set { SetValue(MaximumProperty, value); } + } + + public double Minimum + { + get { return (double)GetValue(MinimumProperty); } + set { SetValue(MinimumProperty, value); } + } + + public double Value + { + get { return (double)GetValue(ValueProperty); } + set { SetValue(ValueProperty, value); } + } + + public event EventHandler<ValueChangedEventArgs> ValueChanged; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/StreamImageSource.cs b/Xamarin.Forms.Core/StreamImageSource.cs new file mode 100644 index 00000000..ae59ea3c --- /dev/null +++ b/Xamarin.Forms.Core/StreamImageSource.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + public class StreamImageSource : ImageSource + { + public static readonly BindableProperty StreamProperty = BindableProperty.Create("Stream", typeof(Func<CancellationToken, Task<Stream>>), typeof(StreamImageSource), + default(Func<CancellationToken, Task<Stream>>)); + + public virtual Func<CancellationToken, Task<Stream>> Stream + { + get { return (Func<CancellationToken, Task<Stream>>)GetValue(StreamProperty); } + set { SetValue(StreamProperty, value); } + } + + protected override void OnPropertyChanged(string propertyName) + { + if (propertyName == StreamProperty.PropertyName) + OnSourceChanged(); + base.OnPropertyChanged(propertyName); + } + + internal async Task<Stream> GetStreamAsync(CancellationToken userToken = default(CancellationToken)) + { + if (Stream == null) + return null; + + OnLoadingStarted(); + userToken.Register(CancellationTokenSource.Cancel); + Stream stream = null; + try + { + stream = await Stream(CancellationTokenSource.Token); + OnLoadingCompleted(false); + } + catch (OperationCanceledException) + { + OnLoadingCompleted(true); + throw; + } + return stream; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/StreamWrapper.cs b/Xamarin.Forms.Core/StreamWrapper.cs new file mode 100644 index 00000000..5d4e2cae --- /dev/null +++ b/Xamarin.Forms.Core/StreamWrapper.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; + +namespace Xamarin.Forms +{ + internal class StreamWrapper : Stream + { + readonly Stream _wrapped; + + public StreamWrapper(Stream wrapped) + { + if (wrapped == null) + throw new ArgumentNullException("wrapped"); + + _wrapped = wrapped; + } + + public override bool CanRead + { + get { return _wrapped.CanRead; } + } + + public override bool CanSeek + { + get { return _wrapped.CanSeek; } + } + + public override bool CanWrite + { + get { return _wrapped.CanWrite; } + } + + public override long Length + { + get { return _wrapped.Length; } + } + + public override long Position + { + get { return _wrapped.Position; } + set { _wrapped.Position = value; } + } + + public event EventHandler Disposed; + + public override void Flush() + { + _wrapped.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _wrapped.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _wrapped.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _wrapped.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + _wrapped.Write(buffer, offset, count); + } + + protected override void Dispose(bool disposing) + { + _wrapped.Dispose(); + EventHandler eh = Disposed; + if (eh != null) + eh(this, EventArgs.Empty); + + base.Dispose(disposing); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Style.cs b/Xamarin.Forms.Core/Style.cs new file mode 100644 index 00000000..8d27f010 --- /dev/null +++ b/Xamarin.Forms.Core/Style.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Xamarin.Forms +{ + [ContentProperty("Setters")] + public sealed class Style : IStyle + { + internal const string StyleClassPrefix = "Xamarin.Forms.StyleClass."; + + readonly BindableProperty _basedOnResourceProperty = BindableProperty.CreateAttached("BasedOnResource", typeof(Style), typeof(Style), default(Style), + propertyChanged: OnBasedOnResourceChanged); + + readonly List<WeakReference<BindableObject>> _targets = new List<WeakReference<BindableObject>>(4); + + Style _basedOnStyle; + + string _baseResourceKey; + + IList<Behavior> _behaviors; + + IList<TriggerBase> _triggers; + + public Style([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) + { + if (targetType == null) + throw new ArgumentNullException("targetType"); + + TargetType = targetType; + Setters = new List<Setter>(); + } + + public bool ApplyToDerivedTypes { get; set; } + + public Style BasedOn + { + get { return _basedOnStyle; } + set + { + if (_basedOnStyle == value) + return; + if (!ValidateBasedOn(value)) + throw new ArgumentException("BasedOn.TargetType is not compatible with TargetType"); + Style oldValue = _basedOnStyle; + _basedOnStyle = value; + BasedOnChanged(oldValue, value); + if (value != null) + BaseResourceKey = null; + } + } + + public string BaseResourceKey + { + get { return _baseResourceKey; } + set + { + if (_baseResourceKey == value) + return; + _baseResourceKey = value; + //update all DynamicResources + foreach (WeakReference<BindableObject> bindableWr in _targets) + { + BindableObject target; + if (!bindableWr.TryGetTarget(out target)) + continue; + target.RemoveDynamicResource(_basedOnResourceProperty); + if (value != null) + target.SetDynamicResource(_basedOnResourceProperty, value); + } + if (value != null) + BasedOn = null; + } + } + + public IList<Behavior> Behaviors + { + get { return _behaviors ?? (_behaviors = new AttachedCollection<Behavior>()); } + } + + public bool CanCascade { get; set; } + + public string Class { get; set; } + + public IList<Setter> Setters { get; } + + public IList<TriggerBase> Triggers + { + get { return _triggers ?? (_triggers = new AttachedCollection<TriggerBase>()); } + } + + void IStyle.Apply(BindableObject bindable) + { + _targets.Add(new WeakReference<BindableObject>(bindable)); + if (BaseResourceKey != null) + bindable.SetDynamicResource(_basedOnResourceProperty, BaseResourceKey); + ApplyCore(bindable, BasedOn ?? GetBasedOnResource(bindable)); + } + + public Type TargetType { get; } + + void IStyle.UnApply(BindableObject bindable) + { + UnApplyCore(bindable, BasedOn ?? GetBasedOnResource(bindable)); + bindable.RemoveDynamicResource(_basedOnResourceProperty); + _targets.RemoveAll(wr => + { + BindableObject target; + return wr.TryGetTarget(out target) && target == bindable; + }); + } + + internal bool CanBeAppliedTo(Type targetType) + { + if (TargetType == targetType) + return true; + if (!ApplyToDerivedTypes) + return false; + do + { + targetType = targetType.GetTypeInfo().BaseType; + if (TargetType == targetType) + return true; + } while (targetType != typeof(Element)); + return false; + } + + void ApplyCore(BindableObject bindable, Style basedOn) + { + if (basedOn != null) + ((IStyle)basedOn).Apply(bindable); + + foreach (Setter setter in Setters) + setter.Apply(bindable, true); + ((AttachedCollection<Behavior>)Behaviors).AttachTo(bindable); + ((AttachedCollection<TriggerBase>)Triggers).AttachTo(bindable); + } + + void BasedOnChanged(Style oldValue, Style newValue) + { + foreach (WeakReference<BindableObject> bindableRef in _targets) + { + BindableObject bindable; + if (!bindableRef.TryGetTarget(out bindable)) + continue; + + UnApplyCore(bindable, oldValue); + ApplyCore(bindable, newValue); + } + } + + Style GetBasedOnResource(BindableObject bindable) + { + return (Style)bindable.GetValue(_basedOnResourceProperty); + } + + static void OnBasedOnResourceChanged(BindableObject bindable, object oldValue, object newValue) + { + Style style = (bindable as VisualElement).Style; + if (style == null) + return; + style.UnApplyCore(bindable, (Style)oldValue); + style.ApplyCore(bindable, (Style)newValue); + } + + void UnApplyCore(BindableObject bindable, Style basedOn) + { + ((AttachedCollection<TriggerBase>)Triggers).DetachFrom(bindable); + ((AttachedCollection<Behavior>)Behaviors).DetachFrom(bindable); + foreach (Setter setter in Setters) + setter.UnApply(bindable, true); + + if (basedOn != null) + ((IStyle)basedOn).UnApply(bindable); + } + + bool ValidateBasedOn(Style value) + { + if (value == null) + return true; + return value.TargetType.IsAssignableFrom(TargetType); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Switch.cs b/Xamarin.Forms.Core/Switch.cs new file mode 100644 index 00000000..394e050a --- /dev/null +++ b/Xamarin.Forms.Core/Switch.cs @@ -0,0 +1,24 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_SwitchRenderer))] + public class Switch : View + { + public static readonly BindableProperty IsToggledProperty = BindableProperty.Create("IsToggled", typeof(bool), typeof(Switch), false, propertyChanged: (bindable, oldValue, newValue) => + { + EventHandler<ToggledEventArgs> eh = ((Switch)bindable).Toggled; + if (eh != null) + eh(bindable, new ToggledEventArgs((bool)newValue)); + }, defaultBindingMode: BindingMode.TwoWay); + + public bool IsToggled + { + get { return (bool)GetValue(IsToggledProperty); } + set { SetValue(IsToggledProperty, value); } + } + + public event EventHandler<ToggledEventArgs> Toggled; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/SynchronizedList.cs b/Xamarin.Forms.Core/SynchronizedList.cs new file mode 100644 index 00000000..edc6f575 --- /dev/null +++ b/Xamarin.Forms.Core/SynchronizedList.cs @@ -0,0 +1,130 @@ +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Xamarin.Forms +{ + internal class SynchronizedList<T> : IList<T>, IReadOnlyList<T> + { + readonly List<T> _list = new List<T>(); + ReadOnlyCollection<T> _snapshot; + + public void Add(T item) + { + lock(_list) + { + _list.Add(item); + _snapshot = null; + } + } + + public void Clear() + { + lock(_list) + { + _list.Clear(); + _snapshot = null; + } + } + + public bool Contains(T item) + { + lock(_list) + return _list.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + lock(_list) + _list.CopyTo(array, arrayIndex); + } + + public int Count + { + get { return _list.Count; } + } + + bool ICollection<T>.IsReadOnly + { + get { return false; } + } + + public bool Remove(T item) + { + lock(_list) + { + if (_list.Remove(item)) + { + _snapshot = null; + return true; + } + + return false; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator<T> GetEnumerator() + { + ReadOnlyCollection<T> snap = _snapshot; + if (snap == null) + { + lock(_list) + _snapshot = snap = new ReadOnlyCollection<T>(_list.ToList()); + } + + return snap.GetEnumerator(); + } + + public int IndexOf(T item) + { + lock(_list) + return _list.IndexOf(item); + } + + public void Insert(int index, T item) + { + lock(_list) + { + _list.Insert(index, item); + _snapshot = null; + } + } + + public T this[int index] + { + get + { + ReadOnlyCollection<T> snap = _snapshot; + if (snap != null) + return snap[index]; + + lock(_list) + return _list[index]; + } + + set + { + lock(_list) + { + _list[index] = value; + _snapshot = null; + } + } + } + + public void RemoveAt(int index) + { + lock(_list) + { + _list.RemoveAt(index); + _snapshot = null; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TabbedPage.cs b/Xamarin.Forms.Core/TabbedPage.cs new file mode 100644 index 00000000..53b736cb --- /dev/null +++ b/Xamarin.Forms.Core/TabbedPage.cs @@ -0,0 +1,17 @@ +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_TabbedPageRenderer))] + public class TabbedPage : MultiPage<Page> + { + protected override Page CreateDefault(object item) + { + var page = new Page(); + if (item != null) + page.Title = item.ToString(); + + return page; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TableIntent.cs b/Xamarin.Forms.Core/TableIntent.cs new file mode 100644 index 00000000..7d4737ca --- /dev/null +++ b/Xamarin.Forms.Core/TableIntent.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public enum TableIntent + { + Menu, + Settings, + Form, + Data + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TableModel.cs b/Xamarin.Forms.Core/TableModel.cs new file mode 100644 index 00000000..866b7a69 --- /dev/null +++ b/Xamarin.Forms.Core/TableModel.cs @@ -0,0 +1,76 @@ +using System; + +namespace Xamarin.Forms +{ + internal abstract class TableModel + { + public virtual Cell GetCell(int section, int row) + { + object item = GetItem(section, row); + var cell = item as Cell; + if (cell != null) + return cell; + + return new TextCell { Text = item.ToString() }; + } + + public virtual Cell GetHeaderCell(int section) + { + return null; + } + + public abstract object GetItem(int section, int row); + + public abstract int GetRowCount(int section); + + public abstract int GetSectionCount(); + + public virtual string[] GetSectionIndexTitles() + { + return null; + } + + public virtual string GetSectionTitle(int section) + { + return null; + } + + public event EventHandler<EventArg<object>> ItemLongPressed; + + public event EventHandler<EventArg<object>> ItemSelected; + + public void RowLongPressed(int section, int row) + { + RowLongPressed(GetItem(section, row)); + } + + public void RowLongPressed(object item) + { + if (ItemLongPressed != null) + ItemLongPressed(this, new EventArg<object>(item)); + + OnRowLongPressed(item); + } + + public void RowSelected(int section, int row) + { + RowSelected(GetItem(section, row)); + } + + public void RowSelected(object item) + { + if (ItemSelected != null) + ItemSelected(this, new EventArg<object>(item)); + + OnRowSelected(item); + } + + protected virtual void OnRowLongPressed(object item) + { + } + + protected virtual void OnRowSelected(object item) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TableRoot.cs b/Xamarin.Forms.Core/TableRoot.cs new file mode 100644 index 00000000..60ef6d95 --- /dev/null +++ b/Xamarin.Forms.Core/TableRoot.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Specialized; +using System.ComponentModel; + +namespace Xamarin.Forms +{ + public sealed class TableRoot : TableSectionBase<TableSection> + { + public TableRoot() + { + SetupEvents(); + } + + public TableRoot(string title) : base(title) + { + SetupEvents(); + } + + internal event EventHandler<ChildCollectionChangedEventArgs> SectionCollectionChanged; + + void ChildCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) + { + EventHandler<ChildCollectionChangedEventArgs> handler = SectionCollectionChanged; + if (handler != null) + handler(this, new ChildCollectionChangedEventArgs(notifyCollectionChangedEventArgs)); + } + + void ChildPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) + { + if (propertyChangedEventArgs.PropertyName == TitleProperty.PropertyName) + { + OnPropertyChanged(TitleProperty.PropertyName); + } + } + + void SetupEvents() + { + CollectionChanged += (sender, args) => + { + if (args.NewItems != null) + { + foreach (TableSection section in args.NewItems) + { + section.CollectionChanged += ChildCollectionChanged; + section.PropertyChanged += ChildPropertyChanged; + } + } + + if (args.OldItems != null) + { + foreach (TableSection section in args.OldItems) + { + section.CollectionChanged -= ChildCollectionChanged; + section.PropertyChanged -= ChildPropertyChanged; + } + } + }; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TableSection.cs b/Xamarin.Forms.Core/TableSection.cs new file mode 100644 index 00000000..9ae7caf5 --- /dev/null +++ b/Xamarin.Forms.Core/TableSection.cs @@ -0,0 +1,137 @@ +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; + +namespace Xamarin.Forms +{ + public abstract class TableSectionBase<T> : TableSectionBase, IList<T>, INotifyCollectionChanged where T : BindableObject + { + readonly ObservableCollection<T> _children = new ObservableCollection<T>(); + + /// <summary> + /// Constructs a Section without an empty header. + /// </summary> + protected TableSectionBase() + { + _children.CollectionChanged += OnChildrenChanged; + } + + /// <summary> + /// Constructs a Section with the specified header. + /// </summary> + protected TableSectionBase(string title) : base(title) + { + _children.CollectionChanged += OnChildrenChanged; + } + + public void Add(T item) + { + _children.Add(item); + } + + public void Clear() + { + _children.Clear(); + } + + public bool Contains(T item) + { + return _children.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _children.CopyTo(array, arrayIndex); + } + + public int Count + { + get { return _children.Count; } + } + + bool ICollection<T>.IsReadOnly + { + get { return false; } + } + + public bool Remove(T item) + { + return _children.Remove(item); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator<T> GetEnumerator() + { + return _children.GetEnumerator(); + } + + public int IndexOf(T item) + { + return _children.IndexOf(item); + } + + public void Insert(int index, T item) + { + _children.Insert(index, item); + } + + public T this[int index] + { + get { return _children[index]; } + set { _children[index] = value; } + } + + public void RemoveAt(int index) + { + _children.RemoveAt(index); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged + { + add { _children.CollectionChanged += value; } + remove { _children.CollectionChanged -= value; } + } + + public void Add(IEnumerable<T> items) + { + items.ForEach(_children.Add); + } + + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + + object bc = BindingContext; + foreach (T child in _children) + SetInheritedBindingContext(child, bc); + } + + void OnChildrenChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) + { + // We need to hook up the binding context for new items. + if (notifyCollectionChangedEventArgs.NewItems == null) + return; + object bc = BindingContext; + foreach (BindableObject item in notifyCollectionChangedEventArgs.NewItems) + { + SetInheritedBindingContext(item, bc); + } + } + } + + public sealed class TableSection : TableSectionBase<Cell> + { + public TableSection() + { + } + + public TableSection(string title) : base(title) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TableSectionBase.cs b/Xamarin.Forms.Core/TableSectionBase.cs new file mode 100644 index 00000000..9cfa5351 --- /dev/null +++ b/Xamarin.Forms.Core/TableSectionBase.cs @@ -0,0 +1,33 @@ +using System; + +namespace Xamarin.Forms +{ + public abstract class TableSectionBase : BindableObject + { + public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(TableSectionBase), null); + + /// <summary> + /// Constructs a Section without an empty header. + /// </summary> + protected TableSectionBase() + { + } + + /// <summary> + /// Constructs a Section with the specified header. + /// </summary> + protected TableSectionBase(string title) + { + if (title == null) + throw new ArgumentNullException("title"); + + Title = title; + } + + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TableView.cs b/Xamarin.Forms.Core/TableView.cs new file mode 100644 index 00000000..a88999b7 --- /dev/null +++ b/Xamarin.Forms.Core/TableView.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [ContentProperty("Root")] + [RenderWith(typeof(_TableViewRenderer))] + public class TableView : View + { + public static readonly BindableProperty RowHeightProperty = BindableProperty.Create("RowHeight", typeof(int), typeof(TableView), -1); + + public static readonly BindableProperty HasUnevenRowsProperty = BindableProperty.Create("HasUnevenRows", typeof(bool), typeof(TableView), false); + + readonly TableSectionModel _tableModel; + + TableIntent _intent = TableIntent.Data; + + TableModel _model; + + public TableView() : this(null) + { + } + + public TableView(TableRoot root) + { + VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand; + Model = _tableModel = new TableSectionModel(this, root); + } + + public bool HasUnevenRows + { + get { return (bool)GetValue(HasUnevenRowsProperty); } + set { SetValue(HasUnevenRowsProperty, value); } + } + + public TableIntent Intent + { + get { return _intent; } + set + { + if (_intent == value) + return; + + OnPropertyChanging(); + _intent = value; + OnPropertyChanged(); + } + } + + public TableRoot Root + { + get { return _tableModel.Root; } + set + { + if (_tableModel.Root != null) + { + _tableModel.Root.SectionCollectionChanged -= OnSectionCollectionChanged; + _tableModel.Root.PropertyChanged -= OnTableModelRootPropertyChanged; + } + _tableModel.Root = value ?? new TableRoot(); + SetInheritedBindingContext(_tableModel.Root, BindingContext); + + Root.SelectMany(r => r).ForEach(cell => cell.Parent = this); + _tableModel.Root.SectionCollectionChanged += OnSectionCollectionChanged; + _tableModel.Root.PropertyChanged += OnTableModelRootPropertyChanged; + OnModelChanged(); + } + } + + public int RowHeight + { + get { return (int)GetValue(RowHeightProperty); } + set { SetValue(RowHeightProperty, value); } + } + + internal TableModel Model + { + get { return _model; } + set + { + _model = value; + OnModelChanged(); + } + } + + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + if (Root != null) + SetInheritedBindingContext(Root, BindingContext); + } + + protected virtual void OnModelChanged() + { + foreach (Cell cell in Root.SelectMany(r => r)) + cell.Parent = this; + + if (ModelChanged != null) + ModelChanged(this, EventArgs.Empty); + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + var minimumSize = new Size(40, 40); + double width = Math.Min(Device.Info.ScaledScreenSize.Width, Device.Info.ScaledScreenSize.Height); + var request = new Size(width, Math.Max(Device.Info.ScaledScreenSize.Width, Device.Info.ScaledScreenSize.Height)); + + return new SizeRequest(request, minimumSize); + } + + internal event EventHandler ModelChanged; + + void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnModelChanged(); + } + + void OnSectionCollectionChanged(object sender, ChildCollectionChangedEventArgs childCollectionChangedEventArgs) + { + if (childCollectionChangedEventArgs.Args.NewItems != null) + childCollectionChangedEventArgs.Args.NewItems.Cast<Cell>().ForEach(cell => cell.Parent = this); + OnModelChanged(); + } + + void OnTableModelRootPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == TableSectionBase.TitleProperty.PropertyName) + OnModelChanged(); + } + + internal class TableSectionModel : TableModel + { + static readonly BindableProperty PathProperty = BindableProperty.Create("Path", typeof(Tuple<int, int>), typeof(Cell), null); + + readonly TableView _parent; + TableRoot _root; + + public TableSectionModel(TableView tableParent, TableRoot tableRoot) + { + _parent = tableParent; + Root = tableRoot ?? new TableRoot(); + } + + public TableRoot Root + { + get { return _root; } + set + { + if (_root == value) + return; + + RemoveEvents(_root); + _root = value; + ApplyEvents(_root); + } + } + + public override Cell GetCell(int section, int row) + { + var cell = (Cell)GetItem(section, row); + SetPath(cell, new Tuple<int, int>(section, row)); + return cell; + } + + public override object GetItem(int section, int row) + { + return _root[section][row]; + } + + public override int GetRowCount(int section) + { + return _root[section].Count; + } + + public override int GetSectionCount() + { + return _root.Count; + } + + public override string GetSectionTitle(int section) + { + return _root[section].Title; + } + + protected override void OnRowSelected(object item) + { + base.OnRowSelected(item); + + ((Cell)item).OnTapped(); + } + + internal static Tuple<int, int> GetPath(Cell item) + { + if (item == null) + throw new ArgumentNullException("item"); + + return (Tuple<int, int>)item.GetValue(PathProperty); + } + + void ApplyEvents(TableRoot tableRoot) + { + tableRoot.CollectionChanged += _parent.CollectionChanged; + tableRoot.SectionCollectionChanged += _parent.OnSectionCollectionChanged; + } + + void RemoveEvents(TableRoot tableRoot) + { + if (tableRoot == null) + return; + + tableRoot.CollectionChanged -= _parent.CollectionChanged; + tableRoot.SectionCollectionChanged -= _parent.OnSectionCollectionChanged; + } + + static void SetPath(Cell item, Tuple<int, int> index) + { + if (item == null) + return; + + item.SetValue(PathProperty, index); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TapGestureRecognizer.cs b/Xamarin.Forms.Core/TapGestureRecognizer.cs new file mode 100644 index 00000000..0489985d --- /dev/null +++ b/Xamarin.Forms.Core/TapGestureRecognizer.cs @@ -0,0 +1,95 @@ +using System; +using System.Windows.Input; + +namespace Xamarin.Forms +{ + public sealed class TapGestureRecognizer : GestureRecognizer + { + public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(TapGestureRecognizer), null); + + public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(TapGestureRecognizer), null); + + public static readonly BindableProperty NumberOfTapsRequiredProperty = BindableProperty.Create("NumberOfTapsRequired", typeof(int), typeof(TapGestureRecognizer), 1); + + public TapGestureRecognizer() + { + } + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public object CommandParameter + { + get { return GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public int NumberOfTapsRequired + { + get { return (int)GetValue(NumberOfTapsRequiredProperty); } + set { SetValue(NumberOfTapsRequiredProperty, value); } + } + + public event EventHandler Tapped; + + internal void SendTapped(View sender) + { + ICommand cmd = Command; + if (cmd != null && cmd.CanExecute(CommandParameter)) + cmd.Execute(CommandParameter); + + EventHandler handler = Tapped; + if (handler != null) + handler(sender, new TappedEventArgs(CommandParameter)); + + Action<View, object> callback = TappedCallback; + if (callback != null) + callback(sender, TappedCallbackParameter); + } + + #region obsolete cruft + + // call empty constructor to hack around bug in mono where compiler generates invalid IL + [Obsolete("Obsolete in 1.0.2. Use Command instead")] + public TapGestureRecognizer(Action<View, object> tappedCallback) : this() + { + if (tappedCallback == null) + throw new ArgumentNullException("tappedCallback"); + TappedCallback = tappedCallback; + } + + // call empty constructor to hack around bug in mono where compiler generates invalid IL + [Obsolete("Obsolete in 1.0.2. Use Command instead")] + public TapGestureRecognizer(Action<View> tappedCallback) : this() + { + if (tappedCallback == null) + throw new ArgumentNullException("tappedCallback"); + TappedCallback = (s, o) => tappedCallback(s); + } + + [Obsolete("Obsolete in 1.0.2. Use Command instead")] public static readonly BindableProperty TappedCallbackProperty = BindableProperty.Create("TappedCallback", typeof(Action<View, object>), + typeof(TapGestureRecognizer), null); + + [Obsolete("Obsolete in 1.0.2. Use Command instead")] + public Action<View, object> TappedCallback + { + get { return (Action<View, object>)GetValue(TappedCallbackProperty); } + set { SetValue(TappedCallbackProperty, value); } + } + + [Obsolete("Obsolete in 1.0.2. Use Command instead")] public static readonly BindableProperty TappedCallbackParameterProperty = BindableProperty.Create("TappedCallbackParameter", typeof(object), + typeof(TapGestureRecognizer), null); + + [Obsolete("Obsolete in 1.0.2. Use Command instead")] + public object TappedCallbackParameter + { + get { return GetValue(TappedCallbackParameterProperty); } + set { SetValue(TappedCallbackParameterProperty, value); } + } + + #endregion + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TappedEventArgs.cs b/Xamarin.Forms.Core/TappedEventArgs.cs new file mode 100644 index 00000000..9f36b7a2 --- /dev/null +++ b/Xamarin.Forms.Core/TappedEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms +{ + public class TappedEventArgs : EventArgs + { + public TappedEventArgs(object parameter) + { + Parameter = parameter; + } + + public object Parameter { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TargetIdiom.cs b/Xamarin.Forms.Core/TargetIdiom.cs new file mode 100644 index 00000000..d19875db --- /dev/null +++ b/Xamarin.Forms.Core/TargetIdiom.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public enum TargetIdiom + { + Unsupported, + Phone, + Tablet, + Desktop + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TargetPlatform.cs b/Xamarin.Forms.Core/TargetPlatform.cs new file mode 100644 index 00000000..d9fe6b76 --- /dev/null +++ b/Xamarin.Forms.Core/TargetPlatform.cs @@ -0,0 +1,11 @@ +namespace Xamarin.Forms +{ + public enum TargetPlatform + { + Other, + iOS, + Android, + WinPhone, + Windows + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TelephoneKeyboard.cs b/Xamarin.Forms.Core/TelephoneKeyboard.cs new file mode 100644 index 00000000..36f03d67 --- /dev/null +++ b/Xamarin.Forms.Core/TelephoneKeyboard.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + internal sealed class TelephoneKeyboard : Keyboard + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TemplateBinding.cs b/Xamarin.Forms.Core/TemplateBinding.cs new file mode 100644 index 00000000..b01f0671 --- /dev/null +++ b/Xamarin.Forms.Core/TemplateBinding.cs @@ -0,0 +1,132 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms +{ + public class TemplateBinding : BindingBase + { + internal const string SelfPath = "."; + IValueConverter _converter; + object _converterParameter; + + BindingExpression _expression; + string _path; + + public TemplateBinding() + { + } + + public TemplateBinding(string path, BindingMode mode = BindingMode.Default, IValueConverter converter = null, object converterParameter = null, string stringFormat = null) + { + if (path == null) + throw new ArgumentNullException("path"); + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("path can not be an empty string", "path"); + + AllowChaining = true; + Path = path; + Converter = converter; + ConverterParameter = converterParameter; + Mode = mode; + StringFormat = stringFormat; + } + + public IValueConverter Converter + { + get { return _converter; } + set + { + ThrowIfApplied(); + + _converter = value; + } + } + + public object ConverterParameter + { + get { return _converterParameter; } + set + { + ThrowIfApplied(); + + _converterParameter = value; + } + } + + public string Path + { + get { return _path; } + set + { + ThrowIfApplied(); + + _path = value; + _expression = GetBindingExpression(value); + } + } + + internal override void Apply(bool fromTarget) + { + base.Apply(fromTarget); + + if (_expression == null) + _expression = new BindingExpression(this, SelfPath); + + _expression.Apply(fromTarget); + } + + internal override async void Apply(object newContext, BindableObject bindObj, BindableProperty targetProperty) + { + var view = bindObj as Element; + if (view == null) + throw new InvalidOperationException(); + + base.Apply(newContext, bindObj, targetProperty); + + Element templatedParent = await TemplateUtilities.FindTemplatedParentAsync(view); + ApplyInner(templatedParent, bindObj, targetProperty); + } + + internal override BindingBase Clone() + { + return new TemplateBinding(Path, Mode) { Converter = Converter, ConverterParameter = ConverterParameter, StringFormat = StringFormat }; + } + + internal override object GetSourceValue(object value, Type targetPropertyType) + { + if (Converter != null) + value = Converter.Convert(value, targetPropertyType, ConverterParameter, CultureInfo.CurrentUICulture); + + return base.GetSourceValue(value, targetPropertyType); + } + + internal override object GetTargetValue(object value, Type sourcePropertyType) + { + if (Converter != null) + value = Converter.ConvertBack(value, sourcePropertyType, ConverterParameter, CultureInfo.CurrentUICulture); + + return base.GetTargetValue(value, sourcePropertyType); + } + + internal override void Unapply() + { + base.Unapply(); + + if (_expression != null) + _expression.Unapply(); + } + + void ApplyInner(Element templatedParent, BindableObject bindableObject, BindableProperty targetProperty) + { + if (_expression == null && templatedParent != null) + _expression = new BindingExpression(this, SelfPath); + + _expression?.Apply(templatedParent, bindableObject, targetProperty); + } + + BindingExpression GetBindingExpression(string path) + { + return new BindingExpression(this, !string.IsNullOrWhiteSpace(path) ? path : SelfPath); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TemplateExtensions.cs b/Xamarin.Forms.Core/TemplateExtensions.cs new file mode 100644 index 00000000..3e62c52d --- /dev/null +++ b/Xamarin.Forms.Core/TemplateExtensions.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Forms +{ + public static class TemplateExtensions + { + public static void SetBinding(this DataTemplate self, BindableProperty targetProperty, string path) + { + if (self == null) + throw new ArgumentNullException("self"); + + self.SetBinding(targetProperty, new Binding(path)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TemplateUtilities.cs b/Xamarin.Forms.Core/TemplateUtilities.cs new file mode 100644 index 00000000..0599e747 --- /dev/null +++ b/Xamarin.Forms.Core/TemplateUtilities.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + internal static class TemplateUtilities + { + public static async Task<Element> FindTemplatedParentAsync(Element element) + { + if (element.RealParent is Application) + return null; + + var skipCount = 0; + element = await GetRealParentAsync(element); + while (!Application.IsApplicationOrNull(element)) + { + var controlTemplated = element as IControlTemplated; + if (controlTemplated?.ControlTemplate != null) + { + if (skipCount == 0) + return element; + skipCount--; + } + if (element is ContentPresenter) + skipCount++; + element = await GetRealParentAsync(element); + } + + return null; + } + + public static Task<Element> GetRealParentAsync(Element element) + { + Element parent = element.RealParent; + if (parent is Application) + return Task.FromResult<Element>(null); + + if (parent != null) + return Task.FromResult(parent); + + var tcs = new TaskCompletionSource<Element>(); + EventHandler handler = null; + handler = (sender, args) => + { + tcs.TrySetResult(element.RealParent); + element.ParentSet -= handler; + }; + element.ParentSet += handler; + + return tcs.Task; + } + + public static void OnContentChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (IControlTemplated)bindable; + var newElement = (Element)newValue; + if (self.ControlTemplate == null) + { + for (var i = 0; i < self.InternalChildren.Count; i++) + { + self.InternalChildren.Remove(self.InternalChildren[i]); + } + + if (newValue != null) + self.InternalChildren.Add(newElement); + } + else + { + if (newElement != null) + { + BindableObject.SetInheritedBindingContext(newElement, bindable.BindingContext); + } + } + } + + public static void OnControlTemplateChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (IControlTemplated)bindable; + + // First make sure any old ContentPresenters are no longer bound up. This MUST be + // done before we attempt to make the new template. + if (oldValue != null) + { + var queue = new Queue<Element>(16); + queue.Enqueue((Element)self); + + while (queue.Count > 0) + { + ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildren; + for (var i = 0; i < children.Count; i++) + { + Element child = children[i]; + var controlTemplated = child as IControlTemplated; + + var presenter = child as ContentPresenter; + if (presenter != null) + presenter.Clear(); + else if (controlTemplated == null || controlTemplated.ControlTemplate == null) + queue.Enqueue(child); + } + } + } + + // Now remove all remnants of any other children just to be sure + for (var i = 0; i < self.InternalChildren.Count; i++) + { + self.InternalChildren.Remove(self.InternalChildren[i]); + } + + ControlTemplate template = self.ControlTemplate; + var content = template.CreateContent() as View; + + if (content == null) + { + throw new NotSupportedException("ControlTemplate must return a type derived from View."); + } + + self.InternalChildren.Add(content); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TemplatedItemsList.cs b/Xamarin.Forms.Core/TemplatedItemsList.cs new file mode 100644 index 00000000..814f5835 --- /dev/null +++ b/Xamarin.Forms.Core/TemplatedItemsList.cs @@ -0,0 +1,1325 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using Cadenza.Collections; + +namespace Xamarin.Forms +{ + internal sealed class TemplatedItemsList<TView, TItem> : BindableObject, IReadOnlyList<TItem>, IList, INotifyCollectionChanged, IDisposable where TView : BindableObject, IItemsView<TItem> + where TItem : BindableObject + { + public static readonly BindableProperty NameProperty = BindableProperty.Create("Name", typeof(string), typeof(TemplatedItemsList<TView, TItem>), null); + + public static readonly BindableProperty ShortNameProperty = BindableProperty.Create("ShortName", typeof(string), typeof(TemplatedItemsList<TView, TItem>), null); + + static readonly BindablePropertyKey HeaderContentPropertyKey = BindableProperty.CreateReadOnly("HeaderContent", typeof(TItem), typeof(TemplatedItemsList<TView, TItem>), null); + + internal static readonly BindablePropertyKey ListProxyPropertyKey = BindableProperty.CreateReadOnly("ListProxy", typeof(ListProxy), typeof(TemplatedItemsList<TView, TItem>), null, + propertyChanged: OnListProxyChanged); + + static readonly BindableProperty GroupProperty = BindableProperty.Create("Group", typeof(TemplatedItemsList<TView, TItem>), typeof(TItem), null); + + static readonly BindableProperty IndexProperty = BindableProperty.Create("Index", typeof(int), typeof(TItem), -1); + + static readonly BindablePropertyKey IsGroupHeaderPropertyKey = BindableProperty.CreateAttachedReadOnly("IsGroupHeader", typeof(bool), typeof(Cell), false); + + readonly BindableProperty _itemSourceProperty; + readonly BindableProperty _itemTemplateProperty; + + readonly TView _itemsView; + + readonly List<TItem> _templatedObjects = new List<TItem>(); + + bool _disposed; + BindingBase _groupDisplayBinding; + OrderedDictionary<object, TemplatedItemsList<TView, TItem>> _groupedItems; + DataTemplate _groupHeaderTemplate; + BindingBase _groupShortNameBinding; + ShortNamesProxy _shortNames; + + internal TemplatedItemsList(TView itemsView, BindableProperty itemSourceProperty, BindableProperty itemTemplateProperty) + { + if (itemsView == null) + throw new ArgumentNullException("itemsView"); + if (itemSourceProperty == null) + throw new ArgumentNullException("itemSourceProperty"); + if (itemTemplateProperty == null) + throw new ArgumentNullException("itemTemplateProperty"); + + _itemsView = itemsView; + _itemsView.PropertyChanged += BindableOnPropertyChanged; + + _itemSourceProperty = itemSourceProperty; + _itemTemplateProperty = itemTemplateProperty; + + IEnumerable source = GetItemsViewSource(); + if (source != null) + ListProxy = new ListProxy(source); + else + ListProxy = new ListProxy(new object[0]); + } + + internal TemplatedItemsList(TemplatedItemsList<TView, TItem> parent, IEnumerable itemSource, TView itemsView, BindableProperty itemTemplateProperty, int windowSize = int.MaxValue) + { + if (itemsView == null) + throw new ArgumentNullException("itemsView"); + if (itemTemplateProperty == null) + throw new ArgumentNullException("itemTemplateProperty"); + + Parent = parent; + + _itemsView = itemsView; + _itemsView.PropertyChanged += BindableOnPropertyChanged; + _itemTemplateProperty = itemTemplateProperty; + + if (itemSource != null) + { + ListProxy = new ListProxy(itemSource, windowSize); + ListProxy.CollectionChanged += OnProxyCollectionChanged; + } + else + ListProxy = new ListProxy(new object[0]); + } + + public BindingBase GroupDisplayBinding + { + get { return _groupDisplayBinding; } + set + { + _groupDisplayBinding = value; + OnHeaderTemplateChanged(); + } + } + + public DataTemplate GroupHeaderTemplate + { + get + { + DataTemplate groupHeader = null; + if (GroupHeaderTemplateProperty != null) + groupHeader = (DataTemplate)_itemsView.GetValue(GroupHeaderTemplateProperty); + + return groupHeader ?? _groupHeaderTemplate; + } + + set + { + if (_groupHeaderTemplate == value) + return; + + _groupHeaderTemplate = value; + OnHeaderTemplateChanged(); + } + } + + public BindableProperty GroupHeaderTemplateProperty { get; set; } + + public BindingBase GroupShortNameBinding + { + get { return _groupShortNameBinding; } + set + { + _groupShortNameBinding = value; + OnShortNameBindingChanged(); + } + } + + public TItem HeaderContent + { + get { return (TItem)GetValue(HeaderContentPropertyKey.BindableProperty); } + private set { SetValue(HeaderContentPropertyKey, value); } + } + + public bool IsGroupingEnabled + { + get { return (IsGroupingEnabledProperty != null) && (bool)_itemsView.GetValue(IsGroupingEnabledProperty); } + } + + public BindableProperty IsGroupingEnabledProperty { get; set; } + + public IEnumerable ItemsSource + { + get { return ListProxy.ProxiedEnumerable; } + } + + public string Name + { + get { return (string)GetValue(NameProperty); } + set { SetValue(NameProperty, value); } + } + + public TemplatedItemsList<TView, TItem> Parent { get; } + + public BindableProperty ProgressiveLoadingProperty { get; set; } + + public string ShortName + { + get { return (string)GetValue(ShortNameProperty); } + set { SetValue(ShortNameProperty, value); } + } + + public IReadOnlyList<string> ShortNames + { + get { return _shortNames; } + } + + internal ListViewCachingStrategy CachingStrategy + { + get + { + var listView = _itemsView as ListView; + if (listView == null) + return ListViewCachingStrategy.RetainElement; + + return listView.CachingStrategy; + } + } + + internal ListProxy ListProxy + { + get { return (ListProxy)GetValue(ListProxyPropertyKey.BindableProperty); } + private set { SetValue(ListProxyPropertyKey, value); } + } + + DataTemplate ItemTemplate + { + get { return (DataTemplate)_itemsView.GetValue(_itemTemplateProperty); } + } + + bool ProgressiveLoading + { + get { return (ProgressiveLoadingProperty != null) && (bool)_itemsView.GetValue(ProgressiveLoadingProperty); } + } + + void ICollection.CopyTo(Array array, int arrayIndex) + { + throw new NotImplementedException(); + } + + bool ICollection.IsSynchronized + { + get { return false; } + } + + object ICollection.SyncRoot + { + get { return this; } + } + + public void Dispose() + { + if (_disposed) + return; + + _itemsView.PropertyChanged -= BindableOnPropertyChanged; + + TItem header = HeaderContent; + if (header != null) + UnhookItem(header); + + for (var i = 0; i < _templatedObjects.Count; i++) + { + TItem item = _templatedObjects[i]; + if (item != null) + UnhookItem(item); + } + + _disposed = true; + } + + IEnumerator IEnumerable.GetEnumerator() + { + if (IsGroupingEnabled) + return _groupedItems.Values.GetEnumerator(); + + return GetEnumerator(); + } + + public IEnumerator<TItem> GetEnumerator() + { + var i = 0; + foreach (object item in ListProxy) + yield return GetOrCreateContent(i++, item); + } + + int IList.Add(object item) + { + throw new NotSupportedException(); + } + + void IList.Clear() + { + throw new NotSupportedException(); + } + + bool IList.Contains(object item) + { + throw new NotImplementedException(); + } + + int IList.IndexOf(object item) + { + if (IsGroupingEnabled) + { + var til = item as TemplatedItemsList<TView, TItem>; + if (til != null) + return _groupedItems.Values.IndexOf(til); + } + + return IndexOf((TItem)item); + } + + void IList.Insert(int index, object item) + { + throw new NotSupportedException(); + } + + bool IList.IsFixedSize + { + get { return false; } + } + + bool IList.IsReadOnly + { + get { return true; } + } + + object IList.this[int index] + { + get + { + if (IsGroupingEnabled) + return GetGroup(index); + + return this[index]; + } + set { throw new NotSupportedException(); } + } + + void IList.Remove(object item) + { + throw new NotSupportedException(); + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException(); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public int Count + { + get { return ListProxy.Count; } + } + + public TItem this[int index] + { + get { return GetOrCreateContent(index, ListProxy[index]); } + } + + public int GetDescendantCount() + { + if (!IsGroupingEnabled) + return Count; + + if (_groupedItems == null) + return 0; + + int count = Count; + foreach (TemplatedItemsList<TView, TItem> group in _groupedItems.Values) + count += group.GetDescendantCount(); + + return count; + } + + public int GetGlobalIndexForGroup(TemplatedItemsList<TView, TItem> group) + { + if (group == null) + throw new ArgumentNullException("group"); + + int groupIndex = _groupedItems.Values.IndexOf(group); + + var index = 0; + for (var i = 0; i < groupIndex; i++) + index += _groupedItems[i].GetDescendantCount() + 1; + + return index; + } + + public int GetGlobalIndexOfGroup(object item) + { + var count = 0; + if (IsGroupingEnabled && _groupedItems != null) + { + foreach (object group in _groupedItems.Keys) + { + if (group == item) + return count; + count++; + } + } + + return -1; + } + + public int GetGlobalIndexOfItem(object item) + { + if (!IsGroupingEnabled) + return ListProxy.IndexOf(item); + + var count = 0; + if (_groupedItems != null) + { + foreach (TemplatedItemsList<TView, TItem> children in _groupedItems.Values) + { + count++; + + int index = children.GetGlobalIndexOfItem(item); + if (index != -1) + return count + index; + + count += children.GetDescendantCount(); + } + } + + return -1; + } + + public int GetGlobalIndexOfItem(object group, object item) + { + if (!IsGroupingEnabled) + return ListProxy.IndexOf(item); + + var count = 0; + if (_groupedItems != null) + { + foreach (KeyValuePair<object, TemplatedItemsList<TView, TItem>> kvp in _groupedItems) + { + count++; + + if (ReferenceEquals(group, kvp.Key)) + { + int index = kvp.Value.GetGlobalIndexOfItem(item); + if (index != -1) + return count + index; + } + + count += kvp.Value.GetDescendantCount(); + } + } + + return -1; + } + + public Tuple<int, int> GetGroupAndIndexOfItem(object item) + { + if (item == null) + return new Tuple<int, int>(-1, -1); + if (!IsGroupingEnabled) + return new Tuple<int, int>(0, GetGlobalIndexOfItem(item)); + + var group = 0; + if (_groupedItems != null) + { + foreach (TemplatedItemsList<TView, TItem> children in _groupedItems.Values) + { + int index = children.GetGlobalIndexOfItem(item); + if (index != -1) + return new Tuple<int, int>(group, index); + + group++; + } + } + + return new Tuple<int, int>(-1, -1); + } + + public Tuple<int, int> GetGroupAndIndexOfItem(object group, object item) + { + if (!IsGroupingEnabled) + return new Tuple<int, int>(0, GetGlobalIndexOfItem(item)); + if (_groupedItems == null) + return new Tuple<int, int>(-1, -1); + + var groupIndex = 0; + foreach (TemplatedItemsList<TView, TItem> children in _groupedItems.Values) + { + if (ReferenceEquals(children.BindingContext, group) || group == null) + { + for (var i = 0; i < children.Count; i++) + { + if (ReferenceEquals(children[i].BindingContext, item)) + return new Tuple<int, int>(groupIndex, i); + } + + if (group != null) + return new Tuple<int, int>(groupIndex, -1); + } + + groupIndex++; + } + + return new Tuple<int, int>(-1, -1); + } + + public int GetGroupIndexFromGlobal(int globalIndex, out int leftOver) + { + leftOver = 0; + + var index = 0; + for (var i = 0; i < _groupedItems.Count; i++) + { + if (index == globalIndex) + return i; + + TemplatedItemsList<TView, TItem> group = _groupedItems[i]; + int count = group.GetDescendantCount(); + + if (index + count >= globalIndex) + { + leftOver = globalIndex - index; + return i; + } + + index += count + 1; + } + + return -1; + } + + public event NotifyCollectionChangedEventHandler GroupedCollectionChanged; + + public int IndexOf(TItem item) + { + TemplatedItemsList<TView, TItem> group = GetGroup(item); + if (group != null && group != this) + return -1; + + return GetIndex(item); + } + + internal TItem CreateContent(int index, object item, bool insert = false) + { + TItem content = ItemTemplate != null ? (TItem)ItemTemplate.CreateContent(item, _itemsView) : _itemsView.CreateDefault(item); + + content = UpdateContent(content, index, item); + + if (CachingStrategy == ListViewCachingStrategy.RecycleElement) + return content; + + for (int i = _templatedObjects.Count; i <= index; i++) + _templatedObjects.Add(null); + + if (!insert) + _templatedObjects[index] = content; + else + _templatedObjects.Insert(index, content); + + return content; + } + + internal void ForceUpdate() + { + ListProxy.Clear(); + } + + internal TemplatedItemsList<TView, TItem> GetGroup(int index) + { + if (!IsGroupingEnabled) + return this; + + return _groupedItems[index]; + } + + internal static TemplatedItemsList<TView, TItem> GetGroup(TItem item) + { + if (item == null) + throw new ArgumentNullException("item"); + + return (TemplatedItemsList<TView, TItem>)item.GetValue(GroupProperty); + } + + internal static int GetIndex(TItem item) + { + if (item == null) + throw new ArgumentNullException("item"); + + return (int)item.GetValue(IndexProperty); + } + + internal static bool GetIsGroupHeader(BindableObject bindable) + { + return (bool)bindable.GetValue(IsGroupHeaderPropertyKey.BindableProperty); + } + + internal TItem GetOrCreateContent(int index, object item) + { + TItem content; + if (_templatedObjects.Count <= index || (content = _templatedObjects[index]) == null) + content = CreateContent(index, item); + + return content; + } + + internal static void SetIsGroupHeader(BindableObject bindable, bool value) + { + bindable.SetValue(IsGroupHeaderPropertyKey, value); + } + + internal TItem UpdateContent(TItem content, int index, object item) + { + content.BindingContext = item; + + if (Parent != null) + SetGroup(content, this); + + SetIndex(content, index); + + _itemsView.SetupContent(content, index); + + return content; + } + + internal TItem UpdateContent(TItem content, int index) + { + object item = ListProxy[index]; + return UpdateContent(content, index, item); + } + + internal TItem UpdateHeader(TItem content, int groupIndex) + { + if (Parent != null && Parent.GroupHeaderTemplate == null) + { + content.BindingContext = this; + } + else + { + content.BindingContext = ListProxy.ProxiedEnumerable; + } + + SetIndex(content, groupIndex); + + _itemsView.SetupContent(content, groupIndex); + + return content; + } + + void BindableOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_itemSourceProperty != null && e.PropertyName == _itemSourceProperty.PropertyName) + OnItemsSourceChanged(); + else if (e.PropertyName == _itemTemplateProperty.PropertyName) + OnItemTemplateChanged(); + else if (ProgressiveLoadingProperty != null && e.PropertyName == ProgressiveLoadingProperty.PropertyName) + OnInfiniteScrollingChanged(); + else if (GroupHeaderTemplateProperty != null && e.PropertyName == GroupHeaderTemplateProperty.PropertyName) + OnHeaderTemplateChanged(); + else if (IsGroupingEnabledProperty != null && e.PropertyName == IsGroupingEnabledProperty.PropertyName) + OnGroupingEnabledChanged(); + } + + IList ConvertContent(int startingIndex, IList items, bool forceCreate = false, bool setIndex = false) + { + var contentItems = new List<TItem>(items.Count); + for (var i = 0; i < items.Count; i++) + { + int index = i + startingIndex; + TItem content = !forceCreate ? GetOrCreateContent(index, items[i]) : CreateContent(index, items[i]); + if (setIndex) + SetIndex(content, index); + + contentItems.Add(content); + } + + return contentItems; + } + + IEnumerable GetItemsViewSource() + { + return (IEnumerable)_itemsView.GetValue(_itemSourceProperty); + } + + void GroupedReset() + { + if (_groupedItems != null) + { + foreach (KeyValuePair<object, TemplatedItemsList<TView, TItem>> group in _groupedItems) + { + group.Value.CollectionChanged -= OnInnerCollectionChanged; + group.Value.Dispose(); + } + _groupedItems.Clear(); + } + + _templatedObjects.Clear(); + + var i = 0; + foreach (object item in ListProxy) + InsertGrouped(item, i++); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + TemplatedItemsList<TView, TItem> InsertGrouped(object item, int index) + { + var children = item as IEnumerable; + + var groupProxy = new TemplatedItemsList<TView, TItem>(this, children, _itemsView, _itemTemplateProperty); + if (GroupDisplayBinding != null) + groupProxy.SetBinding(NameProperty, GroupDisplayBinding.Clone()); + else if (GroupHeaderTemplate == null && item != null) + groupProxy.Name = item.ToString(); + + if (GroupShortNameBinding != null) + groupProxy.SetBinding(ShortNameProperty, GroupShortNameBinding.Clone()); + + groupProxy.BindingContext = item; + + if (GroupHeaderTemplate != null) + { + groupProxy.HeaderContent = (TItem)GroupHeaderTemplate.CreateContent(groupProxy.ItemsSource, _itemsView); + groupProxy.HeaderContent.BindingContext = groupProxy.ItemsSource; + //groupProxy.HeaderContent.BindingContext = groupProxy; + //groupProxy.HeaderContent.SetBinding (BindingContextProperty, "ItemsSource"); + } + else + { + // HACK: TemplatedItemsList shouldn't assume what the default is, but it needs + // to be able to setup bindings. Needs some internal-API tweaking there isn't + // time for right now. + groupProxy.HeaderContent = _itemsView.CreateDefault(ListProxy.ProxiedEnumerable); + groupProxy.HeaderContent.BindingContext = groupProxy; + groupProxy.HeaderContent.SetBinding(TextCell.TextProperty, "Name"); + } + + SetIndex(groupProxy.HeaderContent, index); + SetIsGroupHeader(groupProxy.HeaderContent, true); + + _itemsView.SetupContent(groupProxy.HeaderContent, index); + + _templatedObjects.Insert(index, groupProxy.HeaderContent); + _groupedItems.Insert(index, item, groupProxy); + + groupProxy.CollectionChanged += OnInnerCollectionChanged; + + return groupProxy; + } + + void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + NotifyCollectionChangedEventHandler changed = CollectionChanged; + if (changed != null) + changed(this, e); + } + + void OnCollectionChangedGrouped(NotifyCollectionChangedEventArgs e) + { + if (_groupedItems == null) + _groupedItems = new OrderedDictionary<object, TemplatedItemsList<TView, TItem>>(); + + List<TemplatedItemsList<TView, TItem>> newItems = null, oldItems = null; + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (e.NewStartingIndex == -1) + goto case NotifyCollectionChangedAction.Reset; + + for (int i = e.NewStartingIndex; i < _templatedObjects.Count; i++) + SetIndex(_templatedObjects[i], i + e.NewItems.Count); + + newItems = new List<TemplatedItemsList<TView, TItem>>(e.NewItems.Count); + + for (var i = 0; i < e.NewItems.Count; i++) + { + TemplatedItemsList<TView, TItem> converted = InsertGrouped(e.NewItems[i], e.NewStartingIndex + i); + newItems.Add(converted); + } + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, e.NewStartingIndex)); + + break; + + case NotifyCollectionChangedAction.Remove: + if (e.OldStartingIndex == -1) + goto case NotifyCollectionChangedAction.Reset; + + int removeIndex = e.OldStartingIndex; + for (int i = removeIndex + e.OldItems.Count; i < _templatedObjects.Count; i++) + SetIndex(_templatedObjects[i], removeIndex++); + + oldItems = new List<TemplatedItemsList<TView, TItem>>(e.OldItems.Count); + for (var i = 0; i < e.OldItems.Count; i++) + { + int index = e.OldStartingIndex + i; + TemplatedItemsList<TView, TItem> til = _groupedItems[index]; + til.CollectionChanged -= OnInnerCollectionChanged; + oldItems.Add(til); + _groupedItems.RemoveAt(index); + _templatedObjects.RemoveAt(index); + til.Dispose(); + } + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, e.OldStartingIndex)); + + break; + + case NotifyCollectionChangedAction.Replace: + if (e.OldStartingIndex == -1) + goto case NotifyCollectionChangedAction.Reset; + + oldItems = new List<TemplatedItemsList<TView, TItem>>(e.OldItems.Count); + newItems = new List<TemplatedItemsList<TView, TItem>>(e.NewItems.Count); + + for (var i = 0; i < e.OldItems.Count; i++) + { + int index = e.OldStartingIndex + i; + + TemplatedItemsList<TView, TItem> til = _groupedItems[index]; + til.CollectionChanged -= OnInnerCollectionChanged; + oldItems.Add(til); + + _groupedItems.RemoveAt(index); + _templatedObjects.RemoveAt(index); + + newItems.Add(InsertGrouped(e.NewItems[i], index)); + til.Dispose(); + } + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, e.OldStartingIndex)); + + break; + + case NotifyCollectionChangedAction.Move: + if (e.OldStartingIndex == -1 || e.NewStartingIndex == -1) + goto case NotifyCollectionChangedAction.Reset; + + bool movingForward = e.OldStartingIndex < e.NewStartingIndex; + + if (movingForward) + { + int moveIndex = e.OldStartingIndex; + for (int i = moveIndex + e.OldItems.Count; i <= e.NewStartingIndex; i++) + SetIndex(_templatedObjects[i], moveIndex++); + } + else + { + for (var i = 0; i < e.OldStartingIndex - e.NewStartingIndex; i++) + { + TItem item = _templatedObjects[i + e.NewStartingIndex]; + SetIndex(item, GetIndex(item) + e.OldItems.Count); + } + } + + oldItems = new List<TemplatedItemsList<TView, TItem>>(e.OldItems.Count); + + for (var i = 0; i < e.OldItems.Count; i++) + { + oldItems.Add(_groupedItems[e.OldStartingIndex]); + + _templatedObjects.RemoveAt(e.OldStartingIndex); + _groupedItems.RemoveAt(e.OldStartingIndex); + } + + int insertIndex = e.NewStartingIndex; + if (e.OldStartingIndex < e.NewStartingIndex) + insertIndex -= e.OldItems.Count - 1; + + for (var i = 0; i < oldItems.Count; i++) + { + TemplatedItemsList<TView, TItem> til = oldItems[i]; + _templatedObjects.Insert(insertIndex + i, til.HeaderContent); + _groupedItems.Insert(insertIndex + i, til.BindingContext, til); + SetIndex(til.HeaderContent, insertIndex + i); + } + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, oldItems, e.OldStartingIndex, e.NewStartingIndex)); + + break; + + case NotifyCollectionChangedAction.Reset: + GroupedReset(); + break; + } + } + + void OnGroupingEnabledChanged() + { + if (CachingStrategy == ListViewCachingStrategy.RecycleElement) + _templatedObjects.Clear(); + + OnItemsSourceChanged(true); + + if (!IsGroupingEnabled && _shortNames != null) + { + _shortNames.Dispose(); + _shortNames = null; + } + else + OnShortNameBindingChanged(); + } + + void OnHeaderTemplateChanged() + { + OnItemTemplateChanged(); + } + + void OnInfiniteScrollingChanged() + { + OnItemsSourceChanged(); + } + + void OnInnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + NotifyCollectionChangedEventHandler handler = GroupedCollectionChanged; + if (handler != null) + handler(sender, e); + } + + void OnItemsSourceChanged(bool fromGrouping = false) + { + ListProxy.CollectionChanged -= OnProxyCollectionChanged; + + IEnumerable itemSource = GetItemsViewSource(); + if (itemSource == null) + ListProxy = new ListProxy(new object[0]); + else + ListProxy = new ListProxy(itemSource); + + ListProxy.CollectionChanged += OnProxyCollectionChanged; + OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + void OnItemTemplateChanged() + { + if (ListProxy.Count == 0) + return; + + OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + static void OnListProxyChanged(BindableObject bindable, object oldValue, object newValue) + { + var til = (TemplatedItemsList<TView, TItem>)bindable; + til.OnPropertyChanged("ItemsSource"); + } + + void OnProxyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnProxyCollectionChanged(sender, e, true); + } + + void OnProxyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, bool fixWindows = true) + { + if (IsGroupingEnabled) + { + OnCollectionChangedGrouped(e); + return; + } + + if (CachingStrategy == ListViewCachingStrategy.RecycleElement) + { + OnCollectionChanged(e); + return; + } + + /* HACKAHACKHACK: LongListSelector on WP SL has a bug in that it completely fails to deal with + * INCC notifications that include more than 1 item. */ + if (fixWindows && Device.OS == TargetPlatform.WinPhone) + { + SplitCollectionChangedItems(e); + return; + } + + int count = Count; + var ex = e as NotifyCollectionChangedEventArgsEx; + if (ex != null) + count = ex.Count; + + var maxindex = 0; + if (e.NewStartingIndex >= 0 && e.NewItems != null) + maxindex = Math.Max(maxindex, e.NewStartingIndex + e.NewItems.Count); + if (e.OldStartingIndex >= 0 && e.OldItems != null) + maxindex = Math.Max(maxindex, e.OldStartingIndex + e.OldItems.Count); + if (maxindex > _templatedObjects.Count) + _templatedObjects.InsertRange(_templatedObjects.Count, Enumerable.Repeat<TItem>(null, maxindex - _templatedObjects.Count)); + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (e.NewStartingIndex >= 0) + { + for (int i = e.NewStartingIndex; i < _templatedObjects.Count; i++) + SetIndex(_templatedObjects[i], i + e.NewItems.Count); + + _templatedObjects.InsertRange(e.NewStartingIndex, Enumerable.Repeat<TItem>(null, e.NewItems.Count)); + + IList items = ConvertContent(e.NewStartingIndex, e.NewItems, true, true); + e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Add, items, e.NewStartingIndex); + } + else + { + goto case NotifyCollectionChangedAction.Reset; + } + + break; + + case NotifyCollectionChangedAction.Move: + if (e.NewStartingIndex < 0 || e.OldStartingIndex < 0) + goto case NotifyCollectionChangedAction.Reset; + + bool movingForward = e.OldStartingIndex < e.NewStartingIndex; + + if (movingForward) + { + int moveIndex = e.OldStartingIndex; + for (int i = moveIndex + e.OldItems.Count; i <= e.NewStartingIndex; i++) + SetIndex(_templatedObjects[i], moveIndex++); + } + else + { + for (var i = 0; i < e.OldStartingIndex - e.NewStartingIndex; i++) + { + TItem item = _templatedObjects[i + e.NewStartingIndex]; + if (item != null) + SetIndex(item, GetIndex(item) + e.OldItems.Count); + } + } + + TItem[] itemsToMove = _templatedObjects.Skip(e.OldStartingIndex).Take(e.OldItems.Count).ToArray(); + + _templatedObjects.RemoveRange(e.OldStartingIndex, e.OldItems.Count); + _templatedObjects.InsertRange(e.NewStartingIndex, itemsToMove); + for (var i = 0; i < itemsToMove.Length; i++) + SetIndex(itemsToMove[i], e.NewStartingIndex + i); + + e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Move, itemsToMove, e.NewStartingIndex, e.OldStartingIndex); + break; + + case NotifyCollectionChangedAction.Remove: + if (e.OldStartingIndex >= 0) + { + int removeIndex = e.OldStartingIndex; + for (int i = removeIndex + e.OldItems.Count; i < _templatedObjects.Count; i++) + SetIndex(_templatedObjects[i], removeIndex++); + + var items = new TItem[e.OldItems.Count]; + for (var i = 0; i < items.Length; i++) + { + TItem item = _templatedObjects[e.OldStartingIndex + i]; + if (item == null) + continue; + + UnhookItem(item); + items[i] = item; + } + + _templatedObjects.RemoveRange(e.OldStartingIndex, e.OldItems.Count); + e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Remove, items, e.OldStartingIndex); + } + else + { + goto case NotifyCollectionChangedAction.Reset; + } + break; + + case NotifyCollectionChangedAction.Replace: + if (e.NewStartingIndex >= 0) + { + IList oldItems = ConvertContent(e.NewStartingIndex, e.OldItems); + IList newItems = ConvertContent(e.NewStartingIndex, e.NewItems, true, true); + + for (var i = 0; i < oldItems.Count; i++) + { + UnhookItem((TItem)oldItems[i]); + } + + e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Replace, newItems, oldItems, e.NewStartingIndex); + } + else + { + goto case NotifyCollectionChangedAction.Reset; + } + + break; + + case NotifyCollectionChangedAction.Reset: + e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Reset); + UnhookAndClear(); + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + OnCollectionChanged(e); + } + + void OnShortNameBindingChanged() + { + if (!IsGroupingEnabled) + return; + + if (GroupShortNameBinding != null && _shortNames == null) + _shortNames = new ShortNamesProxy(this); + else if (GroupShortNameBinding == null && _shortNames != null) + { + _shortNames.Dispose(); + _shortNames = null; + } + + if (_groupedItems != null) + { + if (GroupShortNameBinding == null) + { + foreach (TemplatedItemsList<TView, TItem> list in _groupedItems.Values) + list.SetValue(ShortNameProperty, null); + + return; + } + + foreach (TemplatedItemsList<TView, TItem> list in _groupedItems.Values) + list.SetBinding(ShortNameProperty, GroupShortNameBinding.Clone()); + } + + if (_shortNames != null) + _shortNames.Reset(); + } + + static void SetGroup(TItem item, TemplatedItemsList<TView, TItem> group) + { + if (item == null) + throw new ArgumentNullException("item"); + + item.SetValue(GroupProperty, group); + } + + static void SetIndex(TItem item, int index) + { + if (item == null) + return; + + item.SetValue(IndexProperty, index); + } + + void SplitCollectionChangedItems(NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (e.NewStartingIndex < 0) + goto default; + + for (var i = 0; i < e.NewItems.Count; i++) + OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, e.NewItems[i], e.NewStartingIndex + i), false); + + break; + + case NotifyCollectionChangedAction.Remove: + if (e.OldStartingIndex < 0) + goto default; + + for (var i = 0; i < e.OldItems.Count; i++) + OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, e.OldItems[i], e.OldStartingIndex + i), false); + + break; + + case NotifyCollectionChangedAction.Replace: + if (e.OldStartingIndex < 0) + goto default; + + for (var i = 0; i < e.OldItems.Count; i++) + OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, e.NewItems[i], e.OldItems[i], e.OldStartingIndex + i), false); + + break; + + default: + OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset), false); + break; + } + } + + void UnhookAndClear() + { + for (var i = 0; i < _templatedObjects.Count; i++) + { + TItem item = _templatedObjects[i]; + if (item == null) + continue; + + UnhookItem(item); + } + + _templatedObjects.Clear(); + } + + async void UnhookItem(TItem item) + { + SetIndex(item, -1); + _itemsView.UnhookContent(item); + + //Hack: the cell could still be visible on iOS because the cells are reloaded after this unhook + //this causes some visual updates caused by a null datacontext and default values like IsVisible + if (Device.OS == TargetPlatform.iOS && CachingStrategy == ListViewCachingStrategy.RetainElement) + await Task.Delay(100); + item.BindingContext = null; + } + + class ShortNamesProxy : IReadOnlyList<string>, INotifyCollectionChanged, IDisposable + { + readonly HashSet<TemplatedItemsList<TView, TItem>> _attachedItems = new HashSet<TemplatedItemsList<TView, TItem>>(); + readonly TemplatedItemsList<TView, TItem> _itemsList; + + readonly Dictionary<TemplatedItemsList<TView, TItem>, string> _oldNames = new Dictionary<TemplatedItemsList<TView, TItem>, string>(); + + bool _disposed; + + internal ShortNamesProxy(TemplatedItemsList<TView, TItem> itemsList) + { + _itemsList = itemsList; + _itemsList.CollectionChanged += OnItemsListCollectionChanged; + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + + _itemsList.CollectionChanged -= OnItemsListCollectionChanged; + + ResetCore(false); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator<string> GetEnumerator() + { + if (_itemsList._groupedItems == null) + yield break; + + foreach (TemplatedItemsList<TView, TItem> item in _itemsList._groupedItems.Values) + { + AttachList(item); + yield return item.ShortName; + } + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public int Count + { + get { return _itemsList._groupedItems.Count; } + } + + public string this[int index] + { + get + { + TemplatedItemsList<TView, TItem> list = _itemsList._groupedItems[index]; + AttachList(list); + + return list.ShortName; + } + } + + public void Reset() + { + ResetCore(true); + } + + void AttachList(TemplatedItemsList<TView, TItem> list) + { + if (_attachedItems.Contains(list)) + return; + + list.PropertyChanging += OnChildListPropertyChanging; + list.PropertyChanged += OnChildListPropertyChanged; + _attachedItems.Add(list); + } + + List<string> ConvertItems(IList list) + { + var newList = new List<string>(list.Count); + newList.AddRange(list.Cast<TemplatedItemsList<TView, TItem>>().Select(tl => tl.ShortName)); + return newList; + } + + void OnChildListPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName != ShortNameProperty.PropertyName) + return; + + var list = (TemplatedItemsList<TView, TItem>)sender; + string old = _oldNames[list]; + _oldNames.Remove(list); + + int index = _itemsList._groupedItems.Values.IndexOf(list); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, list.ShortName, old, index)); + } + + void OnChildListPropertyChanging(object sender, PropertyChangingEventArgs e) + { + if (e.PropertyName != ShortNameProperty.PropertyName) + return; + + var list = (TemplatedItemsList<TView, TItem>)sender; + _oldNames[list] = list.ShortName; + } + + void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + NotifyCollectionChangedEventHandler changed = CollectionChanged; + if (changed != null) + changed(this, e); + } + + void OnItemsListCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, ConvertItems(e.NewItems), e.NewStartingIndex); + break; + + case NotifyCollectionChangedAction.Move: + e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, ConvertItems(e.OldItems), e.NewStartingIndex, e.OldStartingIndex); + break; + + case NotifyCollectionChangedAction.Remove: + e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, ConvertItems(e.OldItems), e.OldStartingIndex); + break; + + case NotifyCollectionChangedAction.Replace: + e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, ConvertItems(e.NewItems), ConvertItems(e.OldItems), e.OldStartingIndex); + break; + } + + OnCollectionChanged(e); + } + + void ResetCore(bool raiseReset) + { + foreach (TemplatedItemsList<TView, TItem> list in _attachedItems) + { + list.PropertyChanged -= OnChildListPropertyChanged; + list.PropertyChanging -= OnChildListPropertyChanging; + } + + _attachedItems.Clear(); + + if (raiseReset) + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TemplatedPage.cs b/Xamarin.Forms.Core/TemplatedPage.cs new file mode 100644 index 00000000..69c2d58b --- /dev/null +++ b/Xamarin.Forms.Core/TemplatedPage.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + public class TemplatedPage : Page, IControlTemplated + { + public static readonly BindableProperty ControlTemplateProperty = BindableProperty.Create(nameof(ControlTemplate), typeof(ControlTemplate), typeof(TemplatedPage), null, + propertyChanged: TemplateUtilities.OnControlTemplateChanged); + + public ControlTemplate ControlTemplate + { + get { return (ControlTemplate)GetValue(ControlTemplateProperty); } + set { SetValue(ControlTemplateProperty, value); } + } + + IList<Element> IControlTemplated.InternalChildren => InternalChildren; + + internal override void ComputeConstraintForView(View view) + { + LayoutOptions vOptions = view.VerticalOptions; + LayoutOptions hOptions = view.HorizontalOptions; + + var result = LayoutConstraint.None; + if (vOptions.Alignment == LayoutAlignment.Fill) + result |= LayoutConstraint.VerticallyFixed; + if (hOptions.Alignment == LayoutAlignment.Fill) + result |= LayoutConstraint.HorizontallyFixed; + + view.ComputedConstraint = result; + } + + internal override void SetChildInheritedBindingContext(Element child, object context) + { + if (ControlTemplate == null) + base.SetChildInheritedBindingContext(child, context); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TemplatedView.cs b/Xamarin.Forms.Core/TemplatedView.cs new file mode 100644 index 00000000..30dadb92 --- /dev/null +++ b/Xamarin.Forms.Core/TemplatedView.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + public class TemplatedView : Layout, IControlTemplated + { + public static readonly BindableProperty ControlTemplateProperty = BindableProperty.Create(nameof(ControlTemplate), typeof(ControlTemplate), typeof(TemplatedView), null, + propertyChanged: TemplateUtilities.OnControlTemplateChanged); + + public ControlTemplate ControlTemplate + { + get { return (ControlTemplate)GetValue(ControlTemplateProperty); } + set { SetValue(ControlTemplateProperty, value); } + } + + IList<Element> IControlTemplated.InternalChildren => InternalChildren; + + protected override void LayoutChildren(double x, double y, double width, double height) + { + for (var i = 0; i < LogicalChildren.Count; i++) + { + Element element = LogicalChildren[i]; + var child = element as View; + if (child != null) + LayoutChildIntoBoundingRegion(child, new Rectangle(x, y, width, height)); + } + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + double widthRequest = WidthRequest; + double heightRequest = HeightRequest; + var childRequest = new SizeRequest(); + + if ((widthRequest == -1 || heightRequest == -1) && InternalChildren.Count > 0) + { + childRequest = ((View)InternalChildren[0]).Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins); + } + + return new SizeRequest + { + Request = new Size { Width = widthRequest != -1 ? widthRequest : childRequest.Request.Width, Height = heightRequest != -1 ? heightRequest : childRequest.Request.Height }, + Minimum = childRequest.Minimum + }; + } + + internal override void ComputeConstraintForView(View view) + { + bool isFixedHorizontally = (Constraint & LayoutConstraint.HorizontallyFixed) != 0; + bool isFixedVertically = (Constraint & LayoutConstraint.VerticallyFixed) != 0; + + var result = LayoutConstraint.None; + if (isFixedVertically && view.VerticalOptions.Alignment == LayoutAlignment.Fill) + result |= LayoutConstraint.VerticallyFixed; + if (isFixedHorizontally && view.HorizontalOptions.Alignment == LayoutAlignment.Fill) + result |= LayoutConstraint.HorizontallyFixed; + view.ComputedConstraint = result; + } + + internal override void SetChildInheritedBindingContext(Element child, object context) + { + if (ControlTemplate == null) + base.SetChildInheritedBindingContext(child, context); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TextAlignment.cs b/Xamarin.Forms.Core/TextAlignment.cs new file mode 100644 index 00000000..cee32c97 --- /dev/null +++ b/Xamarin.Forms.Core/TextAlignment.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public enum TextAlignment + { + Start, + Center, + End + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TextChangedEventArgs.cs b/Xamarin.Forms.Core/TextChangedEventArgs.cs new file mode 100644 index 00000000..dc4202ae --- /dev/null +++ b/Xamarin.Forms.Core/TextChangedEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + public class TextChangedEventArgs : EventArgs + { + public TextChangedEventArgs(string oldTextValue, string newTextValue) + { + OldTextValue = oldTextValue; + NewTextValue = newTextValue; + } + + public string NewTextValue { get; private set; } + + public string OldTextValue { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TextKeyboard.cs b/Xamarin.Forms.Core/TextKeyboard.cs new file mode 100644 index 00000000..d2d1942f --- /dev/null +++ b/Xamarin.Forms.Core/TextKeyboard.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + internal sealed class TextKeyboard : Keyboard + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Thickness.cs b/Xamarin.Forms.Core/Thickness.cs new file mode 100644 index 00000000..ee1850df --- /dev/null +++ b/Xamarin.Forms.Core/Thickness.cs @@ -0,0 +1,92 @@ +using System.Diagnostics; + +namespace Xamarin.Forms +{ + [DebuggerDisplay("Left={Left}, Top={Top}, Right={Right}, Bottom={Bottom}, HorizontalThickness={HorizontalThickness}, VerticalThickness={VerticalThickness}")] + [TypeConverter(typeof(ThicknessTypeConverter))] + public struct Thickness + { + public double Left { get; set; } + + public double Top { get; set; } + + public double Right { get; set; } + + public double Bottom { get; set; } + + public double HorizontalThickness + { + get { return Left + Right; } + } + + public double VerticalThickness + { + get { return Top + Bottom; } + } + + internal bool IsDefault + { + get { return Left == 0 && Top == 0 && Right == 0 && Left == 0; } + } + + public Thickness(double uniformSize) : this(uniformSize, uniformSize, uniformSize, uniformSize) + { + } + + public Thickness(double horizontalSize, double verticalSize) : this(horizontalSize, verticalSize, horizontalSize, verticalSize) + { + } + + public Thickness(double left, double top, double right, double bottom) : this() + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + + public static implicit operator Thickness(Size size) + { + return new Thickness(size.Width, size.Height, size.Width, size.Height); + } + + public static implicit operator Thickness(double uniformSize) + { + return new Thickness(uniformSize); + } + + bool Equals(Thickness other) + { + return Left.Equals(other.Left) && Top.Equals(other.Top) && Right.Equals(other.Right) && Bottom.Equals(other.Bottom); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + return obj is Thickness && Equals((Thickness)obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = Left.GetHashCode(); + hashCode = (hashCode * 397) ^ Top.GetHashCode(); + hashCode = (hashCode * 397) ^ Right.GetHashCode(); + hashCode = (hashCode * 397) ^ Bottom.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(Thickness left, Thickness right) + { + return left.Equals(right); + } + + public static bool operator !=(Thickness left, Thickness right) + { + return !left.Equals(right); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ThicknessTypeConverter.cs b/Xamarin.Forms.Core/ThicknessTypeConverter.cs new file mode 100644 index 00000000..5e79d25c --- /dev/null +++ b/Xamarin.Forms.Core/ThicknessTypeConverter.cs @@ -0,0 +1,35 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms +{ + public class ThicknessTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + double l, t, r, b; + string[] thickness = value.Split(','); + switch (thickness.Length) + { + case 1: + if (double.TryParse(thickness[0], NumberStyles.Number, CultureInfo.InvariantCulture, out l)) + return new Thickness(l); + break; + case 2: + if (double.TryParse(thickness[0], NumberStyles.Number, CultureInfo.InvariantCulture, out l) && double.TryParse(thickness[1], NumberStyles.Number, CultureInfo.InvariantCulture, out t)) + return new Thickness(l, t); + break; + case 4: + if (double.TryParse(thickness[0], NumberStyles.Number, CultureInfo.InvariantCulture, out l) && double.TryParse(thickness[1], NumberStyles.Number, CultureInfo.InvariantCulture, out t) && + double.TryParse(thickness[2], NumberStyles.Number, CultureInfo.InvariantCulture, out r) && double.TryParse(thickness[3], NumberStyles.Number, CultureInfo.InvariantCulture, out b)) + return new Thickness(l, t, r, b); + break; + } + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Thickness))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Ticker.cs b/Xamarin.Forms.Core/Ticker.cs new file mode 100644 index 00000000..ed828f8a --- /dev/null +++ b/Xamarin.Forms.Core/Ticker.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Xamarin.Forms +{ + internal class Ticker + { + static Ticker s_ticker; + readonly Stopwatch _stopwatch; + readonly SynchronizationContext _sync; + readonly List<Tuple<int, Func<long, bool>>> _timeouts; + + readonly ITimer _timer; + int _count; + bool _enabled; + + internal Ticker() + { + _sync = SynchronizationContext.Current; + _count = 0; + _timer = Device.PlatformServices.CreateTimer(HandleElapsed, null, Timeout.Infinite, Timeout.Infinite); + _timeouts = new List<Tuple<int, Func<long, bool>>>(); + + _stopwatch = new Stopwatch(); + } + + public static Ticker Default + { + internal set { s_ticker = value; } + get { return s_ticker ?? (s_ticker = new Ticker()); } + } + + public virtual int Insert(Func<long, bool> timeout) + { + _count++; + _timeouts.Add(new Tuple<int, Func<long, bool>>(_count, timeout)); + + if (!_enabled) + { + _enabled = true; + Enable(); + } + + return _count; + } + + public virtual void Remove(int handle) + { + Device.BeginInvokeOnMainThread(() => + { + _timeouts.RemoveAll(t => t.Item1 == handle); + + if (!_timeouts.Any()) + { + _enabled = false; + Disable(); + } + }); + } + + protected virtual void DisableTimer() + { + _timer.Change(Timeout.Infinite, Timeout.Infinite); + } + + protected virtual void EnableTimer() + { + _timer.Change(16, 16); + } + + protected void SendSignals(int timestep = -1) + { + long step = timestep >= 0 ? timestep : _stopwatch.ElapsedMilliseconds; + _stopwatch.Reset(); + _stopwatch.Start(); + + var localCopy = new List<Tuple<int, Func<long, bool>>>(_timeouts); + foreach (Tuple<int, Func<long, bool>> timeout in localCopy) + { + bool remove = !timeout.Item2(step); + if (remove) + _timeouts.RemoveAll(t => t.Item1 == timeout.Item1); + } + + if (!_timeouts.Any()) + { + _enabled = false; + Disable(); + } + } + + void Disable() + { + _stopwatch.Reset(); + DisableTimer(); + } + + void Enable() + { + _stopwatch.Start(); + EnableTimer(); + } + + void HandleElapsed(object state) + { + if (_timeouts.Count > 0) + { + _sync.Post(o => SendSignals(), null); + _stopwatch.Reset(); + _stopwatch.Start(); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TimePicker.cs b/Xamarin.Forms.Core/TimePicker.cs new file mode 100644 index 00000000..a9cf2e19 --- /dev/null +++ b/Xamarin.Forms.Core/TimePicker.cs @@ -0,0 +1,29 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_TimePickerRenderer))] + public class TimePicker : View + { + public static readonly BindableProperty FormatProperty = BindableProperty.Create("Format", typeof(string), typeof(TimePicker), "t"); + + public static readonly BindableProperty TimeProperty = BindableProperty.Create("Time", typeof(TimeSpan), typeof(TimePicker), new TimeSpan(0), BindingMode.TwoWay, (bindable, value) => + { + var time = (TimeSpan)value; + return time.TotalHours < 24 && time.TotalMilliseconds >= 0; + }); + + public string Format + { + get { return (string)GetValue(FormatProperty); } + set { SetValue(FormatProperty, value); } + } + + public TimeSpan Time + { + get { return (TimeSpan)GetValue(TimeProperty); } + set { SetValue(TimeProperty, value); } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ToggledEventArgs.cs b/Xamarin.Forms.Core/ToggledEventArgs.cs new file mode 100644 index 00000000..227e93cb --- /dev/null +++ b/Xamarin.Forms.Core/ToggledEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms +{ + public class ToggledEventArgs : EventArgs + { + public ToggledEventArgs(bool value) + { + Value = value; + } + + public bool Value { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Toolbar.cs b/Xamarin.Forms.Core/Toolbar.cs new file mode 100644 index 00000000..4405d1f6 --- /dev/null +++ b/Xamarin.Forms.Core/Toolbar.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + // Marked as internal for 1.0 release until we are ready to release this + [RenderWith(typeof(_ToolbarRenderer))] + internal class Toolbar : View + { + readonly List<ToolbarItem> _items = new List<ToolbarItem>(); + + public ReadOnlyCollection<ToolbarItem> Items + { + get { return new ReadOnlyCollection<ToolbarItem>(_items); } + } + + public void Add(ToolbarItem item) + { + if (_items.Contains(item)) + return; + _items.Add(item); + if (ItemAdded != null) + ItemAdded(this, new ToolbarItemEventArgs(item)); + } + + public void Clear() + { + _items.Clear(); + } + + public event EventHandler<ToolbarItemEventArgs> ItemAdded; + + public event EventHandler<ToolbarItemEventArgs> ItemRemoved; + + public void Remove(ToolbarItem item) + { + if (_items.Remove(item)) + { + if (ItemRemoved != null) + ItemRemoved(this, new ToolbarItemEventArgs(item)); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ToolbarItem.cs b/Xamarin.Forms.Core/ToolbarItem.cs new file mode 100644 index 00000000..0a0a2e05 --- /dev/null +++ b/Xamarin.Forms.Core/ToolbarItem.cs @@ -0,0 +1,57 @@ +using System; + +namespace Xamarin.Forms +{ + public class ToolbarItem : MenuItem + { + static readonly BindableProperty OrderProperty = BindableProperty.Create("Order", typeof(ToolbarItemOrder), typeof(ToolbarItem), ToolbarItemOrder.Default, validateValue: (bo, o) => + { + var order = (ToolbarItemOrder)o; + return order == ToolbarItemOrder.Default || order == ToolbarItemOrder.Primary || order == ToolbarItemOrder.Secondary; + }); + + static readonly BindableProperty PriorityProperty = BindableProperty.Create("Priority", typeof(int), typeof(ToolbarItem), 0); + + public ToolbarItem() + { + } + + public ToolbarItem(string name, string icon, Action activated, ToolbarItemOrder order = ToolbarItemOrder.Default, int priority = 0) + { + if (activated == null) + throw new ArgumentNullException("activated"); + + Text = name; + Icon = icon; + Clicked += (s, e) => activated(); + Order = order; + Priority = priority; + } + + [Obsolete("Now that ToolbarItem is based on MenuItem, .Text has replaced .Name")] + public string Name + { + get { return Text; } + set { Text = value; } + } + + public ToolbarItemOrder Order + { + get { return (ToolbarItemOrder)GetValue(OrderProperty); } + set { SetValue(OrderProperty, value); } + } + + public int Priority + { + get { return (int)GetValue(PriorityProperty); } + set { SetValue(PriorityProperty, value); } + } + + [Obsolete("Activated has been replaced by the more consistent 'Clicked'")] + public event EventHandler Activated + { + add { Clicked += value; } + remove { Clicked -= value; } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ToolbarItemEventArgs.cs b/Xamarin.Forms.Core/ToolbarItemEventArgs.cs new file mode 100644 index 00000000..8a6e0124 --- /dev/null +++ b/Xamarin.Forms.Core/ToolbarItemEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Forms +{ + internal class ToolbarItemEventArgs : EventArgs + { + public ToolbarItemEventArgs(ToolbarItem item) + { + ToolbarItem = item; + } + + public ToolbarItem ToolbarItem { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ToolbarItemOrder.cs b/Xamarin.Forms.Core/ToolbarItemOrder.cs new file mode 100644 index 00000000..0fbf6c17 --- /dev/null +++ b/Xamarin.Forms.Core/ToolbarItemOrder.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms +{ + public enum ToolbarItemOrder + { + Default, + Primary, + Secondary + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ToolbarTracker.cs b/Xamarin.Forms.Core/ToolbarTracker.cs new file mode 100644 index 00000000..74580065 --- /dev/null +++ b/Xamarin.Forms.Core/ToolbarTracker.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; + +namespace Xamarin.Forms +{ + internal class ToolbarTracker + { + int _masterDetails; + Page _target; + + public IEnumerable<Page> AdditionalTargets { get; set; } + + public bool HaveMasterDetail + { + get { return _masterDetails > 0; } + } + + public bool SeparateMasterDetail { get; set; } + + public Page Target + { + get { return _target; } + set + { + if (_target == value) + return; + + UntrackTarget(_target); + _target = value; + + if (_target != null) + TrackTarget(_target); + EmitCollectionChanged(); + } + } + + public IEnumerable<ToolbarItem> ToolbarItems + { + get + { + if (Target == null) + return Enumerable.Empty<ToolbarItem>(); + IEnumerable<ToolbarItem> items = GetCurrentToolbarItems(Target); + if (AdditionalTargets != null) + items = items.Concat(AdditionalTargets.SelectMany(t => t.ToolbarItems)); + + return items.OrderBy(ti => ti.Priority); + } + } + + public event EventHandler CollectionChanged; + + void EmitCollectionChanged() + { + if (CollectionChanged != null) + CollectionChanged(this, EventArgs.Empty); + } + + IEnumerable<ToolbarItem> GetCurrentToolbarItems(Page page) + { + var result = new List<ToolbarItem>(); + result.AddRange(page.ToolbarItems); + + if (page is MasterDetailPage) + { + var masterDetail = (MasterDetailPage)page; + if (SeparateMasterDetail) + { + if (masterDetail.IsPresented) + { + if (masterDetail.Master != null) + result.AddRange(GetCurrentToolbarItems(masterDetail.Master)); + } + else + { + if (masterDetail.Detail != null) + result.AddRange(GetCurrentToolbarItems(masterDetail.Detail)); + } + } + else + { + if (masterDetail.Master != null) + result.AddRange(GetCurrentToolbarItems(masterDetail.Master)); + if (masterDetail.Detail != null) + result.AddRange(GetCurrentToolbarItems(masterDetail.Detail)); + } + } + else if (page is IPageContainer<Page>) + { + var container = (IPageContainer<Page>)page; + if (container.CurrentPage != null) + result.AddRange(GetCurrentToolbarItems(container.CurrentPage)); + } + + return result; + } + + void OnChildAdded(object sender, ElementEventArgs eventArgs) + { + var page = eventArgs.Element as Page; + if (page == null) + return; + + RegisterChildPage(page); + } + + void OnChildRemoved(object sender, ElementEventArgs eventArgs) + { + var page = eventArgs.Element as Page; + if (page == null) + return; + + UnregisterChildPage(page); + } + + void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) + { + EmitCollectionChanged(); + } + + void OnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) + { + if (propertyChangedEventArgs.PropertyName == NavigationPage.CurrentPageProperty.PropertyName || propertyChangedEventArgs.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName || + propertyChangedEventArgs.PropertyName == "Detail" || propertyChangedEventArgs.PropertyName == "Master") + { + EmitCollectionChanged(); + } + } + + void RegisterChildPage(Page page) + { + if (page is MasterDetailPage) + _masterDetails++; + + ((ObservableCollection<ToolbarItem>)page.ToolbarItems).CollectionChanged += OnCollectionChanged; + page.PropertyChanged += OnPropertyChanged; + } + + void TrackTarget(Page page) + { + if (page == null) + return; + + if (page is MasterDetailPage) + _masterDetails++; + + ((ObservableCollection<ToolbarItem>)page.ToolbarItems).CollectionChanged += OnCollectionChanged; + page.Descendants().OfType<Page>().ForEach(RegisterChildPage); + + page.DescendantAdded += OnChildAdded; + page.DescendantRemoved += OnChildRemoved; + page.PropertyChanged += OnPropertyChanged; + } + + void UnregisterChildPage(Page page) + { + if (page is MasterDetailPage) + _masterDetails--; + + ((ObservableCollection<ToolbarItem>)page.ToolbarItems).CollectionChanged -= OnCollectionChanged; + page.PropertyChanged -= OnPropertyChanged; + } + + void UntrackTarget(Page page) + { + if (page == null) + return; + + if (page is MasterDetailPage) + _masterDetails--; + + ((ObservableCollection<ToolbarItem>)page.ToolbarItems).CollectionChanged -= OnCollectionChanged; + page.Descendants().OfType<Page>().ForEach(UnregisterChildPage); + + page.DescendantAdded -= OnChildAdded; + page.DescendantRemoved -= OnChildRemoved; + page.PropertyChanged -= OnPropertyChanged; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TrackableCollection.cs b/Xamarin.Forms.Core/TrackableCollection.cs new file mode 100644 index 00000000..959007ad --- /dev/null +++ b/Xamarin.Forms.Core/TrackableCollection.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.ObjectModel; + +namespace Xamarin.Forms +{ + internal class TrackableCollection<T> : ObservableCollection<T> + { + public event EventHandler Clearing; + + protected override void ClearItems() + { + Clearing?.Invoke(this, EventArgs.Empty); + base.ClearItems(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Tweener.cs b/Xamarin.Forms.Core/Tweener.cs new file mode 100644 index 00000000..73ed7853 --- /dev/null +++ b/Xamarin.Forms.Core/Tweener.cs @@ -0,0 +1,124 @@ +// +// Tweener.cs +// +// Author: +// Jason Smith <jason.smith@xamarin.com> +// +// Copyright (c) 2012 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; + +namespace Xamarin.Forms +{ + internal class Tweener + { + long _lastMilliseconds; + + int _timer; + + public Tweener(uint length) + { + Value = 0.0f; + Length = length; + Loop = false; + } + + public AnimatableKey Handle { get; set; } + + public uint Length { get; } + + public bool Loop { get; set; } + + public double Value { get; private set; } + + public event EventHandler Finished; + + public void Pause() + { + if (_timer != 0) + { + Ticker.Default.Remove(_timer); + _timer = 0; + } + } + + public void Start() + { + Pause(); + + _lastMilliseconds = 0; + _timer = Ticker.Default.Insert(step => + { + long ms = step + _lastMilliseconds; + + Value = Math.Min(1.0f, ms / (double)Length); + + _lastMilliseconds = ms; + + if (ValueUpdated != null) + ValueUpdated(this, EventArgs.Empty); + + if (Value >= 1.0f) + { + if (Loop) + { + _lastMilliseconds = 0; + Value = 0.0f; + return true; + } + + if (Finished != null) + Finished(this, EventArgs.Empty); + Value = 0.0f; + _timer = 0; + return false; + } + return true; + }); + } + + public void Stop() + { + Pause(); + Value = 1.0f; + if (Finished != null) + Finished(this, EventArgs.Empty); + Value = 0.0f; + } + + public event EventHandler ValueUpdated; + + ~Tweener() + { + if (_timer != 0) + { + try + { + Ticker.Default.Remove(_timer); + } + catch (InvalidOperationException) + { + } + } + _timer = 0; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TypeConverter.cs b/Xamarin.Forms.Core/TypeConverter.cs new file mode 100644 index 00000000..7bbf221f --- /dev/null +++ b/Xamarin.Forms.Core/TypeConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; + +namespace Xamarin.Forms +{ + public abstract class TypeConverter + { + public virtual bool CanConvertFrom(Type sourceType) + { + if (sourceType == null) + throw new ArgumentNullException("sourceType"); + + return sourceType == typeof(string); + } + + [Obsolete("use ConvertFromInvariantString (string)")] + public virtual object ConvertFrom(object o) + { + return null; + } + + [Obsolete("use ConvertFromInvariantString (string)")] + public virtual object ConvertFrom(CultureInfo culture, object o) + { + return null; + } + + public virtual object ConvertFromInvariantString(string value) + { + return ConvertFrom(CultureInfo.InvariantCulture, value); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TypeConverterAttribute.cs b/Xamarin.Forms.Core/TypeConverterAttribute.cs new file mode 100644 index 00000000..261b00e0 --- /dev/null +++ b/Xamarin.Forms.Core/TypeConverterAttribute.cs @@ -0,0 +1,74 @@ +// +// System.ComponentModel.TypeConverterAttribute +// +// Authors: +// Gonzalo Paniagua Javier (gonzalo@ximian.com) +// Andreas Nahr (ClassDevelopment@A-SoftTech.com) +// +// (C) 2002 Ximian, Inc (http://www.ximian.com) +// (C) 2003 Andreas Nahr +// + +// +// 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; + +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.All)] + public sealed class TypeConverterAttribute : Attribute + { + internal static string[] TypeConvertersType = { "Xamarin.Forms.TypeConverterAttribute", "System.ComponentModel.TypeConverterAttribute" }; + + public static readonly TypeConverterAttribute Default = new TypeConverterAttribute(); + + public TypeConverterAttribute() + { + ConverterTypeName = ""; + } + + public TypeConverterAttribute(string typeName) + { + ConverterTypeName = typeName; + } + + public TypeConverterAttribute(Type type) + { + ConverterTypeName = type.AssemblyQualifiedName; + } + + public string ConverterTypeName { get; } + + public override bool Equals(object obj) + { + if (!(obj is TypeConverterAttribute)) + return false; + + return ((TypeConverterAttribute)obj).ConverterTypeName == ConverterTypeName; + } + + public override int GetHashCode() + { + return ConverterTypeName.GetHashCode(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/TypeTypeConverter.cs b/Xamarin.Forms.Core/TypeTypeConverter.cs new file mode 100644 index 00000000..115f1d96 --- /dev/null +++ b/Xamarin.Forms.Core/TypeTypeConverter.cs @@ -0,0 +1,31 @@ +using System; +using System.Globalization; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms +{ + public sealed class TypeTypeConverter : TypeConverter, IExtendedTypeConverter + { + [Obsolete("Use ConvertFromInvariantString (string, IServiceProvider)")] + object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider) + { + return ((IExtendedTypeConverter)this).ConvertFromInvariantString((string)value, serviceProvider); + } + + object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider) + { + if (serviceProvider == null) + throw new ArgumentNullException("serviceProvider"); + var typeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver; + if (typeResolver == null) + throw new ArgumentException("No IXamlTypeResolver in IServiceProvider"); + + return typeResolver.Resolve(value, serviceProvider); + } + + public override object ConvertFromInvariantString(string value) + { + throw new NotImplementedException(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/UnsolvableConstraintsException.cs b/Xamarin.Forms.Core/UnsolvableConstraintsException.cs new file mode 100644 index 00000000..40e37812 --- /dev/null +++ b/Xamarin.Forms.Core/UnsolvableConstraintsException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Xamarin.Forms +{ + public class UnsolvableConstraintsException : Exception + { + public UnsolvableConstraintsException(string message) : base(message) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/UriImageSource.cs b/Xamarin.Forms.Core/UriImageSource.cs new file mode 100644 index 00000000..f6b3ead1 --- /dev/null +++ b/Xamarin.Forms.Core/UriImageSource.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + public sealed class UriImageSource : ImageSource + { + internal const string CacheName = "ImageLoaderCache"; + + public static readonly BindableProperty UriProperty = BindableProperty.Create("Uri", typeof(Uri), typeof(UriImageSource), default(Uri), + propertyChanged: (bindable, oldvalue, newvalue) => ((UriImageSource)bindable).OnUriChanged(), validateValue: (bindable, value) => value == null || ((Uri)value).IsAbsoluteUri); + + static readonly IIsolatedStorageFile Store = Device.PlatformServices.GetUserStoreForApplication(); + + static readonly object s_syncHandle = new object(); + static readonly Dictionary<string, LockingSemaphore> s_semaphores = new Dictionary<string, LockingSemaphore>(); + + TimeSpan _cacheValidity = TimeSpan.FromDays(1); + + bool _cachingEnabled = true; + + static UriImageSource() + { + if (!Store.GetDirectoryExistsAsync(CacheName).Result) + Store.CreateDirectoryAsync(CacheName).Wait(); + } + + public TimeSpan CacheValidity + { + get { return _cacheValidity; } + set + { + if (_cacheValidity == value) + return; + + OnPropertyChanging(); + _cacheValidity = value; + OnPropertyChanged(); + } + } + + public bool CachingEnabled + { + get { return _cachingEnabled; } + set + { + if (_cachingEnabled == value) + return; + + OnPropertyChanging(); + _cachingEnabled = value; + OnPropertyChanged(); + } + } + + [TypeConverter(typeof(UriTypeConverter))] + public Uri Uri + { + get { return (Uri)GetValue(UriProperty); } + set { SetValue(UriProperty, value); } + } + + internal async Task<Stream> GetStreamAsync(CancellationToken userToken = default(CancellationToken)) + { + OnLoadingStarted(); + userToken.Register(CancellationTokenSource.Cancel); + Stream stream = null; + try + { + stream = await GetStreamAsync(Uri, CancellationTokenSource.Token); + OnLoadingCompleted(false); + } + catch (OperationCanceledException) + { + OnLoadingCompleted(true); + throw; +#if DEBUG + } + catch (Exception e) + { + Debug.WriteLine(e); + throw; +#endif + } + return stream; + } + + static string GetCacheKey(Uri uri) + { + return Device.PlatformServices.GetMD5Hash(uri.AbsoluteUri); + } + + async Task<bool> GetHasLocallyCachedCopyAsync(string key, bool checkValidity = true) + { + DateTime now = DateTime.UtcNow; + DateTime? lastWriteTime = await GetLastWriteTimeUtcAsync(key).ConfigureAwait(false); + return lastWriteTime.HasValue && now - lastWriteTime.Value < CacheValidity; + } + + static async Task<DateTime?> GetLastWriteTimeUtcAsync(string key) + { + string path = Path.Combine(CacheName, key); + if (!await Store.GetFileExistsAsync(path).ConfigureAwait(false)) + return null; + + return (await Store.GetLastWriteTimeAsync(path).ConfigureAwait(false)).UtcDateTime; + } + + async Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Stream stream; + if (!CachingEnabled) + { + try + { + stream = await Device.GetStreamAsync(uri, cancellationToken).ConfigureAwait(false); + } + catch (Exception) + { + stream = null; + } + } + else + stream = await GetStreamFromCacheAsync(uri, cancellationToken).ConfigureAwait(false); + return stream; + } + + async Task<Stream> GetStreamAsyncUnchecked(string key, Uri uri, CancellationToken cancellationToken) + { + if (await GetHasLocallyCachedCopyAsync(key).ConfigureAwait(false)) + { + var retry = 5; + while (retry >= 0) + { + int backoff; + try + { + Stream result = await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Open, FileAccess.Read).ConfigureAwait(false); + return result; + } + catch (IOException) + { + // iOS seems to not like 2 readers opening the file at the exact same time, back off for random amount of time + backoff = new Random().Next(1, 5); + retry--; + } + + if (backoff > 0) + { + await Task.Delay(backoff); + } + } + return null; + } + + Stream stream; + try + { + stream = await Device.GetStreamAsync(uri, cancellationToken).ConfigureAwait(false); + if (stream == null) + return null; + } + catch (Exception) + { + return null; + } + + Stream writeStream = await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Create, FileAccess.Write).ConfigureAwait(false); + await stream.CopyToAsync(writeStream, 16384, cancellationToken).ConfigureAwait(false); + if (writeStream != null) + writeStream.Dispose(); + + stream.Dispose(); + + return await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Open, FileAccess.Read).ConfigureAwait(false); + } + + async Task<Stream> GetStreamFromCacheAsync(Uri uri, CancellationToken cancellationToken) + { + string key = GetCacheKey(uri); + LockingSemaphore sem; + lock(s_syncHandle) + { + if (s_semaphores.ContainsKey(key)) + sem = s_semaphores[key]; + else + s_semaphores.Add(key, sem = new LockingSemaphore(1)); + } + + try + { + await sem.WaitAsync(cancellationToken); + Stream stream = await GetStreamAsyncUnchecked(key, uri, cancellationToken); + if (stream == null) + { + sem.Release(); + return null; + } + var wrapped = new StreamWrapper(stream); + wrapped.Disposed += (o, e) => sem.Release(); + return wrapped; + } + catch (OperationCanceledException) + { + sem.Release(); + throw; + } + } + + void OnUriChanged() + { + if (CancellationTokenSource != null) + CancellationTokenSource.Cancel(); + OnSourceChanged(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/UriTypeConverter.cs b/Xamarin.Forms.Core/UriTypeConverter.cs new file mode 100644 index 00000000..0a24cab3 --- /dev/null +++ b/Xamarin.Forms.Core/UriTypeConverter.cs @@ -0,0 +1,24 @@ +using System; + +namespace Xamarin.Forms +{ + public class UriTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + return new Uri(value, UriKind.RelativeOrAbsolute); + } + + bool CanConvert(Type type) + { + if (type == typeof(string)) + return true; + if (type == typeof(Uri)) + return true; + + return false; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/UrlKeyboard.cs b/Xamarin.Forms.Core/UrlKeyboard.cs new file mode 100644 index 00000000..8266d95e --- /dev/null +++ b/Xamarin.Forms.Core/UrlKeyboard.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms +{ + internal sealed class UrlKeyboard : Keyboard + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/UrlWebViewSource.cs b/Xamarin.Forms.Core/UrlWebViewSource.cs new file mode 100644 index 00000000..72541045 --- /dev/null +++ b/Xamarin.Forms.Core/UrlWebViewSource.cs @@ -0,0 +1,19 @@ +namespace Xamarin.Forms +{ + public class UrlWebViewSource : WebViewSource + { + public static readonly BindableProperty UrlProperty = BindableProperty.Create("Url", typeof(string), typeof(UrlWebViewSource), default(string), + propertyChanged: (bindable, oldvalue, newvalue) => ((UrlWebViewSource)bindable).OnSourceChanged()); + + public string Url + { + get { return (string)GetValue(UrlProperty); } + set { SetValue(UrlProperty, value); } + } + + internal override void Load(IWebViewRenderer renderer) + { + renderer.LoadUrl(Url); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ValueChangedEventArgs.cs b/Xamarin.Forms.Core/ValueChangedEventArgs.cs new file mode 100644 index 00000000..8ea718c4 --- /dev/null +++ b/Xamarin.Forms.Core/ValueChangedEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace Xamarin.Forms +{ + public class ValueChangedEventArgs : EventArgs + { + public ValueChangedEventArgs(double oldValue, double newValue) + { + OldValue = oldValue; + NewValue = newValue; + } + + public double NewValue { get; private set; } + + public double OldValue { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Vec2.cs b/Xamarin.Forms.Core/Vec2.cs new file mode 100644 index 00000000..2c7512d3 --- /dev/null +++ b/Xamarin.Forms.Core/Vec2.cs @@ -0,0 +1,14 @@ +namespace Xamarin.Forms +{ + public struct Vec2 + { + public double X; + public double Y; + + public Vec2(double x, double y) + { + X = x; + Y = y; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/View.cs b/Xamarin.Forms.Core/View.cs new file mode 100644 index 00000000..d03c74bb --- /dev/null +++ b/Xamarin.Forms.Core/View.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace Xamarin.Forms +{ + public class View : VisualElement, IViewController + { + public static readonly BindableProperty VerticalOptionsProperty = BindableProperty.Create("VerticalOptions", typeof(LayoutOptions), typeof(View), LayoutOptions.Fill, + propertyChanged: (bindable, oldvalue, newvalue) => ((View)bindable).InvalidateMeasure(InvalidationTrigger.VerticalOptionsChanged)); + + public static readonly BindableProperty HorizontalOptionsProperty = BindableProperty.Create("HorizontalOptions", typeof(LayoutOptions), typeof(View), LayoutOptions.Fill, + propertyChanged: (bindable, oldvalue, newvalue) => ((View)bindable).InvalidateMeasure(InvalidationTrigger.HorizontalOptionsChanged)); + + public static readonly BindableProperty MarginProperty = BindableProperty.Create("Margin", typeof(Thickness), typeof(View), default(Thickness), propertyChanged: MarginPropertyChanged); + + readonly ObservableCollection<IGestureRecognizer> _gestureRecognizers = new ObservableCollection<IGestureRecognizer>(); + + protected internal View() + { + _gestureRecognizers.CollectionChanged += (sender, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (IElement item in args.NewItems.OfType<IElement>()) + { + ValidateGesture(item as IGestureRecognizer); + item.Parent = this; + } + break; + case NotifyCollectionChangedAction.Remove: + foreach (IElement item in args.OldItems.OfType<IElement>()) + item.Parent = null; + break; + case NotifyCollectionChangedAction.Replace: + foreach (IElement item in args.NewItems.OfType<IElement>()) + { + ValidateGesture(item as IGestureRecognizer); + item.Parent = this; + } + foreach (IElement item in args.OldItems.OfType<IElement>()) + item.Parent = null; + break; + case NotifyCollectionChangedAction.Reset: + foreach (IElement item in _gestureRecognizers.OfType<IElement>()) + item.Parent = this; + break; + } + }; + } + + public IList<IGestureRecognizer> GestureRecognizers + { + get { return _gestureRecognizers; } + } + + public LayoutOptions HorizontalOptions + { + get { return (LayoutOptions)GetValue(HorizontalOptionsProperty); } + set { SetValue(HorizontalOptionsProperty, value); } + } + + public Thickness Margin + { + get { return (Thickness)GetValue(MarginProperty); } + set { SetValue(MarginProperty, value); } + } + + public LayoutOptions VerticalOptions + { + get { return (LayoutOptions)GetValue(VerticalOptionsProperty); } + set { SetValue(VerticalOptionsProperty, value); } + } + + protected override void OnBindingContextChanged() + { + var gotBindingContext = false; + object bc = null; + + for (var i = 0; i < GestureRecognizers.Count; i++) + { + var bo = GestureRecognizers[i] as BindableObject; + if (bo == null) + continue; + + if (!gotBindingContext) + { + bc = BindingContext; + gotBindingContext = true; + } + + SetInheritedBindingContext(bo, bc); + } + + base.OnBindingContextChanged(); + } + + static void MarginPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + ((View)bindable).InvalidateMeasure(InvalidationTrigger.MarginChanged); + } + + void ValidateGesture(IGestureRecognizer gesture) + { + if (gesture == null) + return; + if (gesture is PinchGestureRecognizer && _gestureRecognizers.GetGesturesFor<PinchGestureRecognizer>().Count() > 1) + throw new InvalidOperationException($"Only one {nameof(PinchGestureRecognizer)} per view is allowed"); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ViewExtensions.cs b/Xamarin.Forms.Core/ViewExtensions.cs new file mode 100644 index 00000000..14a70868 --- /dev/null +++ b/Xamarin.Forms.Core/ViewExtensions.cs @@ -0,0 +1,197 @@ +using System; +using System.Threading.Tasks; + +namespace Xamarin.Forms +{ + public static class ViewExtensions + { + public static void CancelAnimations(VisualElement view) + { + if (view == null) + throw new ArgumentNullException("view"); + view.AbortAnimation("LayoutTo"); + view.AbortAnimation("TranslateTo"); + view.AbortAnimation("RotateTo"); + view.AbortAnimation("RotateYTo"); + view.AbortAnimation("RotateXTo"); + view.AbortAnimation("ScaleTo"); + view.AbortAnimation("FadeTo"); + view.AbortAnimation("SizeTo"); + } + + public static Task<bool> FadeTo(this VisualElement view, double opacity, uint length = 250, Easing easing = null) + { + if (view == null) + throw new ArgumentNullException("view"); + if (easing == null) + easing = Easing.Linear; + + var tcs = new TaskCompletionSource<bool>(); + var weakView = new WeakReference<VisualElement>(view); + Action<double> fade = f => + { + VisualElement v; + if (weakView.TryGetTarget(out v)) + v.Opacity = f; + }; + + new Animation(fade, view.Opacity, opacity, easing).Commit(view, "FadeTo", 16, length, finished: (f, a) => tcs.SetResult(a)); + + return tcs.Task; + } + + public static Task<bool> LayoutTo(this VisualElement view, Rectangle bounds, uint length = 250, Easing easing = null) + { + if (view == null) + throw new ArgumentNullException("view"); + if (easing == null) + easing = Easing.Linear; + + var tcs = new TaskCompletionSource<bool>(); + Rectangle start = view.Bounds; + Func<double, Rectangle> computeBounds = progress => + { + double x = start.X + (bounds.X - start.X) * progress; + double y = start.Y + (bounds.Y - start.Y) * progress; + double w = start.Width + (bounds.Width - start.Width) * progress; + double h = start.Height + (bounds.Height - start.Height) * progress; + + return new Rectangle(x, y, w, h); + }; + var weakView = new WeakReference<VisualElement>(view); + Action<double> layout = f => + { + VisualElement v; + if (weakView.TryGetTarget(out v)) + v.Layout(computeBounds(f)); + }; + new Animation(layout, 0, 1, easing).Commit(view, "LayoutTo", 16, length, finished: (f, a) => tcs.SetResult(a)); + + return tcs.Task; + } + + public static Task<bool> RelRotateTo(this VisualElement view, double drotation, uint length = 250, Easing easing = null) + { + if (view == null) + throw new ArgumentNullException("view"); + return view.RotateTo(view.Rotation + drotation, length, easing); + } + + public static Task<bool> RelScaleTo(this VisualElement view, double dscale, uint length = 250, Easing easing = null) + { + if (view == null) + throw new ArgumentNullException("view"); + return view.ScaleTo(view.Scale + dscale, length, easing); + } + + public static Task<bool> RotateTo(this VisualElement view, double rotation, uint length = 250, Easing easing = null) + { + if (view == null) + throw new ArgumentNullException("view"); + if (easing == null) + easing = Easing.Linear; + + var tcs = new TaskCompletionSource<bool>(); + var weakView = new WeakReference<VisualElement>(view); + Action<double> rotate = f => + { + VisualElement v; + if (weakView.TryGetTarget(out v)) + v.Rotation = f; + }; + + new Animation(rotate, view.Rotation, rotation, easing).Commit(view, "RotateTo", 16, length, finished: (f, a) => tcs.SetResult(a)); + + return tcs.Task; + } + + public static Task<bool> RotateXTo(this VisualElement view, double rotation, uint length = 250, Easing easing = null) + { + if (view == null) + throw new ArgumentNullException("view"); + if (easing == null) + easing = Easing.Linear; + + var tcs = new TaskCompletionSource<bool>(); + var weakView = new WeakReference<VisualElement>(view); + Action<double> rotatex = f => + { + VisualElement v; + if (weakView.TryGetTarget(out v)) + v.RotationX = f; + }; + + new Animation(rotatex, view.RotationX, rotation, easing).Commit(view, "RotateXTo", 16, length, finished: (f, a) => tcs.SetResult(a)); + + return tcs.Task; + } + + public static Task<bool> RotateYTo(this VisualElement view, double rotation, uint length = 250, Easing easing = null) + { + if (view == null) + throw new ArgumentNullException("view"); + if (easing == null) + easing = Easing.Linear; + + var tcs = new TaskCompletionSource<bool>(); + var weakView = new WeakReference<VisualElement>(view); + Action<double> rotatey = f => + { + VisualElement v; + if (weakView.TryGetTarget(out v)) + v.RotationY = f; + }; + + new Animation(rotatey, view.RotationY, rotation, easing).Commit(view, "RotateYTo", 16, length, finished: (f, a) => tcs.SetResult(a)); + + return tcs.Task; + } + + public static Task<bool> ScaleTo(this VisualElement view, double scale, uint length = 250, Easing easing = null) + { + if (view == null) + throw new ArgumentNullException("view"); + if (easing == null) + easing = Easing.Linear; + + var tcs = new TaskCompletionSource<bool>(); + var weakView = new WeakReference<VisualElement>(view); + Action<double> _scale = f => + { + VisualElement v; + if (weakView.TryGetTarget(out v)) + v.Scale = f; + }; + + new Animation(_scale, view.Scale, scale, easing).Commit(view, "ScaleTo", 16, length, finished: (f, a) => tcs.SetResult(a)); + + return tcs.Task; + } + + public static Task<bool> TranslateTo(this VisualElement view, double x, double y, uint length = 250, Easing easing = null) + { + if (view == null) + throw new ArgumentNullException("view"); + easing = easing ?? Easing.Linear; + + var tcs = new TaskCompletionSource<bool>(); + var weakView = new WeakReference<VisualElement>(view); + Action<double> translateX = f => + { + VisualElement v; + if (weakView.TryGetTarget(out v)) + v.TranslationX = f; + }; + Action<double> translateY = f => + { + VisualElement v; + if (weakView.TryGetTarget(out v)) + v.TranslationY = f; + }; + new Animation { { 0, 1, new Animation(translateX, view.TranslationX, x) }, { 0, 1, new Animation(translateY, view.TranslationY, y) } }.Commit(view, "TranslateTo", 16, length, easing, + (f, a) => tcs.SetResult(a)); + + return tcs.Task; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/ViewState.cs b/Xamarin.Forms.Core/ViewState.cs new file mode 100644 index 00000000..e7db908a --- /dev/null +++ b/Xamarin.Forms.Core/ViewState.cs @@ -0,0 +1,12 @@ +using System; + +namespace Xamarin.Forms +{ + [Flags] + public enum ViewState + { + Default = 0, + Prelight = 1, + Pressed = 1 << 1 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/VisualElement.cs b/Xamarin.Forms.Core/VisualElement.cs new file mode 100644 index 00000000..16efe730 --- /dev/null +++ b/Xamarin.Forms.Core/VisualElement.cs @@ -0,0 +1,764 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Forms +{ + public partial class VisualElement : Element, IAnimatable, IVisualElementController, IResourcesProvider + { + internal static readonly BindablePropertyKey NavigationPropertyKey = BindableProperty.CreateReadOnly("Navigation", typeof(INavigation), typeof(VisualElement), default(INavigation)); + + public static readonly BindableProperty NavigationProperty = NavigationPropertyKey.BindableProperty; + + public static readonly BindableProperty InputTransparentProperty = BindableProperty.Create("InputTransparent", typeof(bool), typeof(VisualElement), default(bool)); + + public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create("IsEnabled", typeof(bool), typeof(VisualElement), true); + + static readonly BindablePropertyKey XPropertyKey = BindableProperty.CreateReadOnly("X", typeof(double), typeof(VisualElement), default(double)); + + public static readonly BindableProperty XProperty = XPropertyKey.BindableProperty; + + static readonly BindablePropertyKey YPropertyKey = BindableProperty.CreateReadOnly("Y", typeof(double), typeof(VisualElement), default(double)); + + public static readonly BindableProperty YProperty = YPropertyKey.BindableProperty; + + public static readonly BindableProperty AnchorXProperty = BindableProperty.Create("AnchorX", typeof(double), typeof(VisualElement), .5d); + + public static readonly BindableProperty AnchorYProperty = BindableProperty.Create("AnchorY", typeof(double), typeof(VisualElement), .5d); + + public static readonly BindableProperty TranslationXProperty = BindableProperty.Create("TranslationX", typeof(double), typeof(VisualElement), 0d); + + public static readonly BindableProperty TranslationYProperty = BindableProperty.Create("TranslationY", typeof(double), typeof(VisualElement), 0d); + + static readonly BindablePropertyKey WidthPropertyKey = BindableProperty.CreateReadOnly("Width", typeof(double), typeof(VisualElement), -1d, + coerceValue: (bindable, value) => double.IsNaN((double)value) ? 0d : value); + + public static readonly BindableProperty WidthProperty = WidthPropertyKey.BindableProperty; + + static readonly BindablePropertyKey HeightPropertyKey = BindableProperty.CreateReadOnly("Height", typeof(double), typeof(VisualElement), -1d, + coerceValue: (bindable, value) => double.IsNaN((double)value) ? 0d : value); + + public static readonly BindableProperty HeightProperty = HeightPropertyKey.BindableProperty; + + public static readonly BindableProperty RotationProperty = BindableProperty.Create("Rotation", typeof(double), typeof(VisualElement), default(double)); + + public static readonly BindableProperty RotationXProperty = BindableProperty.Create("RotationX", typeof(double), typeof(VisualElement), default(double)); + + public static readonly BindableProperty RotationYProperty = BindableProperty.Create("RotationY", typeof(double), typeof(VisualElement), default(double)); + + public static readonly BindableProperty ScaleProperty = BindableProperty.Create("Scale", typeof(double), typeof(VisualElement), 1d); + + public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create("IsVisible", typeof(bool), typeof(VisualElement), true, + propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable).OnIsVisibleChanged((bool)oldvalue, (bool)newvalue)); + + public static readonly BindableProperty OpacityProperty = BindableProperty.Create("Opacity", typeof(double), typeof(VisualElement), 1d, coerceValue: (bindable, value) => ((double)value).Clamp(0, 1)); + + public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create("BackgroundColor", typeof(Color), typeof(VisualElement), Color.Default); + + internal static readonly BindablePropertyKey BehaviorsPropertyKey = BindableProperty.CreateReadOnly("Behaviors", typeof(IList<Behavior>), typeof(VisualElement), default(IList<Behavior>), + defaultValueCreator: bindable => + { + var collection = new AttachedCollection<Behavior>(); + collection.AttachTo(bindable); + return collection; + }); + + public static readonly BindableProperty BehaviorsProperty = BehaviorsPropertyKey.BindableProperty; + + internal static readonly BindablePropertyKey TriggersPropertyKey = BindableProperty.CreateReadOnly("Triggers", typeof(IList<TriggerBase>), typeof(VisualElement), default(IList<TriggerBase>), + defaultValueCreator: bindable => + { + var collection = new AttachedCollection<TriggerBase>(); + collection.AttachTo(bindable); + return collection; + }); + + public static readonly BindableProperty TriggersProperty = TriggersPropertyKey.BindableProperty; + + public static readonly BindableProperty StyleProperty = BindableProperty.Create("Style", typeof(Style), typeof(VisualElement), default(Style), + propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable)._mergedStyle.Style = (Style)newvalue); + + public static readonly BindableProperty WidthRequestProperty = BindableProperty.Create("WidthRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged); + + public static readonly BindableProperty HeightRequestProperty = BindableProperty.Create("HeightRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged); + + public static readonly BindableProperty MinimumWidthRequestProperty = BindableProperty.Create("MinimumWidthRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged); + + public static readonly BindableProperty MinimumHeightRequestProperty = BindableProperty.Create("MinimumHeightRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged); + + internal static readonly BindablePropertyKey IsFocusedPropertyKey = BindableProperty.CreateReadOnly("IsFocused", typeof(bool), typeof(VisualElement), default(bool), + propertyChanged: OnIsFocusedPropertyChanged); + + public static readonly BindableProperty IsFocusedProperty = IsFocusedPropertyKey.BindableProperty; + + readonly Dictionary<Size, SizeRequest> _measureCache = new Dictionary<Size, SizeRequest>(); + + readonly MergedStyle _mergedStyle; + + int _batched; + LayoutConstraint _computedConstraint; + + bool _isInNativeLayout; + + bool _isNativeStateConsistent = true; + + bool _isPlatformEnabled; + + double _mockHeight = -1; + + double _mockWidth = -1; + + double _mockX = -1; + + double _mockY = -1; + + ResourceDictionary _resources; + LayoutConstraint _selfConstraint; + + internal VisualElement() + { + Navigation = new NavigationProxy(); + _mergedStyle = new MergedStyle(GetType(), this); + } + + public double AnchorX + { + get { return (double)GetValue(AnchorXProperty); } + set { SetValue(AnchorXProperty, value); } + } + + public double AnchorY + { + get { return (double)GetValue(AnchorYProperty); } + set { SetValue(AnchorYProperty, value); } + } + + public Color BackgroundColor + { + get { return (Color)GetValue(BackgroundColorProperty); } + set { SetValue(BackgroundColorProperty, value); } + } + + public IList<Behavior> Behaviors + { + get { return (IList<Behavior>)GetValue(BehaviorsProperty); } + } + + public Rectangle Bounds + { + get { return new Rectangle(X, Y, Width, Height); } + private set + { + if (value.X == X && value.Y == Y && value.Height == Height && value.Width == Width) + return; + BatchBegin(); + X = value.X; + Y = value.Y; + SetSize(value.Width, value.Height); + BatchCommit(); + } + } + + public double Height + { + get { return _mockHeight == -1 ? (double)GetValue(HeightProperty) : _mockHeight; } + private set { SetValue(HeightPropertyKey, value); } + } + + public double HeightRequest + { + get { return (double)GetValue(HeightRequestProperty); } + set { SetValue(HeightRequestProperty, value); } + } + + public bool InputTransparent + { + get { return (bool)GetValue(InputTransparentProperty); } + set { SetValue(InputTransparentProperty, value); } + } + + public bool IsEnabled + { + get { return (bool)GetValue(IsEnabledProperty); } + set { SetValue(IsEnabledProperty, value); } + } + + public bool IsFocused + { + get { return (bool)GetValue(IsFocusedProperty); } + } + + public bool IsVisible + { + get { return (bool)GetValue(IsVisibleProperty); } + set { SetValue(IsVisibleProperty, value); } + } + + public double MinimumHeightRequest + { + get { return (double)GetValue(MinimumHeightRequestProperty); } + set { SetValue(MinimumHeightRequestProperty, value); } + } + + public double MinimumWidthRequest + { + get { return (double)GetValue(MinimumWidthRequestProperty); } + set { SetValue(MinimumWidthRequestProperty, value); } + } + + public INavigation Navigation + { + get { return (INavigation)GetValue(NavigationProperty); } + internal set { SetValue(NavigationPropertyKey, value); } + } + + public double Opacity + { + get { return (double)GetValue(OpacityProperty); } + set { SetValue(OpacityProperty, value); } + } + + public double Rotation + { + get { return (double)GetValue(RotationProperty); } + set { SetValue(RotationProperty, value); } + } + + public double RotationX + { + get { return (double)GetValue(RotationXProperty); } + set { SetValue(RotationXProperty, value); } + } + + public double RotationY + { + get { return (double)GetValue(RotationYProperty); } + set { SetValue(RotationYProperty, value); } + } + + public double Scale + { + get { return (double)GetValue(ScaleProperty); } + set { SetValue(ScaleProperty, value); } + } + + public Style Style + { + get { return (Style)GetValue(StyleProperty); } + set { SetValue(StyleProperty, value); } + } + + public string StyleClass + { + get { return _mergedStyle.StyleClass; } + set { _mergedStyle.StyleClass = value; } + } + + public double TranslationX + { + get { return (double)GetValue(TranslationXProperty); } + set { SetValue(TranslationXProperty, value); } + } + + public double TranslationY + { + get { return (double)GetValue(TranslationYProperty); } + set { SetValue(TranslationYProperty, value); } + } + + public IList<TriggerBase> Triggers + { + get { return (IList<TriggerBase>)GetValue(TriggersProperty); } + } + + public double Width + { + get { return _mockWidth == -1 ? (double)GetValue(WidthProperty) : _mockWidth; } + private set { SetValue(WidthPropertyKey, value); } + } + + public double WidthRequest + { + get { return (double)GetValue(WidthRequestProperty); } + set { SetValue(WidthRequestProperty, value); } + } + + public double X + { + get { return _mockX == -1 ? (double)GetValue(XProperty) : _mockX; } + private set { SetValue(XPropertyKey, value); } + } + + public double Y + { + get { return _mockY == -1 ? (double)GetValue(YProperty) : _mockY; } + private set { SetValue(YPropertyKey, value); } + } + + internal bool Batched + { + get { return _batched > 0; } + } + + internal LayoutConstraint ComputedConstraint + { + get { return _computedConstraint; } + set + { + if (_computedConstraint == value) + return; + + LayoutConstraint oldConstraint = Constraint; + _computedConstraint = value; + LayoutConstraint newConstraint = Constraint; + if (oldConstraint != newConstraint) + OnConstraintChanged(oldConstraint, newConstraint); + } + } + + internal LayoutConstraint Constraint + { + get { return ComputedConstraint | SelfConstraint; } + } + + internal bool DisableLayout { get; set; } + + internal bool IsInNativeLayout + { + get + { + if (_isInNativeLayout) + return true; + + Element parent = RealParent; + while (parent != null) + { + var visualElement = parent as VisualElement; + if (visualElement != null && visualElement.IsInNativeLayout) + return true; + parent = parent.RealParent; + } + + return false; + } + set { _isInNativeLayout = value; } + } + + internal bool IsNativeStateConsistent + { + get { return _isNativeStateConsistent; } + set + { + if (_isNativeStateConsistent == value) + return; + _isNativeStateConsistent = value; + if (value && IsPlatformEnabled) + InvalidateMeasure(InvalidationTrigger.RendererReady); + } + } + + internal bool IsPlatformEnabled + { + get { return _isPlatformEnabled; } + set + { + if (value == _isPlatformEnabled) + return; + + _isPlatformEnabled = value; + if (value && IsNativeStateConsistent) + InvalidateMeasure(InvalidationTrigger.RendererReady); + + OnIsPlatformEnabledChanged(); + } + } + + internal NavigationProxy NavigationProxy + { + get { return Navigation as NavigationProxy; } + } + + internal LayoutConstraint SelfConstraint + { + get { return _selfConstraint; } + set + { + if (_selfConstraint == value) + return; + + LayoutConstraint oldConstraint = Constraint; + _selfConstraint = value; + LayoutConstraint newConstraint = Constraint; + if (oldConstraint != newConstraint) + { + OnConstraintChanged(oldConstraint, newConstraint); + } + } + } + + public void BatchBegin() + { + _batched++; + } + + public void BatchCommit() + { + _batched = Math.Max(0, _batched - 1); + if (!Batched && BatchCommitted != null) + BatchCommitted(this, new EventArg<VisualElement>(this)); + } + + public ResourceDictionary Resources + { + get { return _resources; } + set + { + if (_resources == value) + return; + OnPropertyChanging(); + if (_resources != null) + ((IResourceDictionary)_resources).ValuesChanged -= OnResourcesChanged; + _resources = value; + OnResourcesChanged(value); + if (_resources != null) + ((IResourceDictionary)_resources).ValuesChanged += OnResourcesChanged; + OnPropertyChanged(); + } + } + + void IVisualElementController.NativeSizeChanged() + { + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + public event EventHandler ChildrenReordered; + + public bool Focus() + { + if (IsFocused) + return true; + + if (FocusChangeRequested == null) + return false; + + var arg = new FocusRequestArgs { Focus = true }; + FocusChangeRequested(this, arg); + return arg.Result; + } + + public event EventHandler<FocusEventArgs> Focused; + + [Obsolete("Use Measure")] + public virtual SizeRequest GetSizeRequest(double widthConstraint, double heightConstraint) + { + SizeRequest cachedResult; + var constraintSize = new Size(widthConstraint, heightConstraint); + if (_measureCache.TryGetValue(constraintSize, out cachedResult)) + { + return cachedResult; + } + + double widthRequest = WidthRequest; + double heightRequest = HeightRequest; + if (widthRequest >= 0) + widthConstraint = Math.Min(widthConstraint, widthRequest); + if (heightRequest >= 0) + heightConstraint = Math.Min(heightConstraint, heightRequest); + + SizeRequest result = OnMeasure(widthConstraint, heightConstraint); + bool hasMinimum = result.Minimum != result.Request; + Size request = result.Request; + Size minimum = result.Minimum; + + if (heightRequest != -1) + { + request.Height = heightRequest; + if (!hasMinimum) + minimum.Height = heightRequest; + } + + if (widthRequest != -1) + { + request.Width = widthRequest; + if (!hasMinimum) + minimum.Width = widthRequest; + } + + double minimumHeightRequest = MinimumHeightRequest; + double minimumWidthRequest = MinimumWidthRequest; + + if (minimumHeightRequest != -1) + minimum.Height = minimumHeightRequest; + if (minimumWidthRequest != -1) + minimum.Width = minimumWidthRequest; + + minimum.Height = Math.Min(request.Height, minimum.Height); + minimum.Width = Math.Min(request.Width, minimum.Width); + + var r = new SizeRequest(request, minimum); + + if (r.Request.Width > 0 && r.Request.Height > 0) + { + _measureCache[constraintSize] = r; + } + + return r; + } + + public void Layout(Rectangle bounds) + { + Bounds = bounds; + } + + public SizeRequest Measure(double widthConstraint, double heightConstraint, MeasureFlags flags = MeasureFlags.None) + { + SizeRequest result = GetSizeRequest(widthConstraint, heightConstraint); + + if ((flags & MeasureFlags.IncludeMargins) != 0) + { + Thickness margin = default(Thickness); + var view = this as View; + if (view != null) + margin = view.Margin; + + if (!margin.IsDefault) + { + result.Minimum = new Size(result.Minimum.Width + margin.HorizontalThickness, result.Minimum.Height + margin.VerticalThickness); + result.Request = new Size(result.Request.Width + margin.HorizontalThickness, result.Request.Height + margin.VerticalThickness); + } + } + + return result; + } + + public event EventHandler MeasureInvalidated; + + public event EventHandler SizeChanged; + + public void Unfocus() + { + if (!IsFocused) + return; + + EventHandler<FocusRequestArgs> unfocus = FocusChangeRequested; + if (unfocus != null) + { + unfocus(this, new FocusRequestArgs()); + } + } + + public event EventHandler<FocusEventArgs> Unfocused; + + protected virtual void InvalidateMeasure() + { + InvalidateMeasure(InvalidationTrigger.MeasureChanged); + } + + protected override void OnChildAdded(Element child) + { + base.OnChildAdded(child); + var view = child as View; + if (view != null) + ComputeConstraintForView(view); + } + + protected override void OnChildRemoved(Element child) + { + base.OnChildRemoved(child); + var view = child as View; + if (view != null) + view.ComputedConstraint = LayoutConstraint.None; + } + + protected void OnChildrenReordered() + { + if (ChildrenReordered != null) + ChildrenReordered(this, EventArgs.Empty); + } + + protected virtual SizeRequest OnMeasure(double widthConstraint, double heightConstraint) + { + return OnSizeRequest(widthConstraint, heightConstraint); + } + + protected override void OnParentSet() + { + base.OnParentSet(); + + if (ParentView != null) + { + NavigationProxy.Inner = ParentView.NavigationProxy; + } + else + { + NavigationProxy.Inner = null; + } + } + + protected virtual void OnSizeAllocated(double width, double height) + { + } + + [Obsolete("Use OnMeasure")] + protected virtual SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + if (Platform == null || !IsPlatformEnabled) + { + return new SizeRequest(new Size(-1, -1)); + } + return Platform.GetNativeSize(this, widthConstraint, heightConstraint); + } + + protected void SizeAllocated(double width, double height) + { + OnSizeAllocated(width, height); + } + + internal event EventHandler<EventArg<VisualElement>> BatchCommitted; + + internal void ComputeConstrainsForChildren() + { + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = LogicalChildren[i] as View; + if (child != null) + ComputeConstraintForView(child); + } + } + + internal virtual void ComputeConstraintForView(View view) + { + view.ComputedConstraint = LayoutConstraint.None; + } + + internal event EventHandler<FocusRequestArgs> FocusChangeRequested; + + internal virtual void InvalidateMeasure(InvalidationTrigger trigger) + { + _measureCache.Clear(); + MeasureInvalidated?.Invoke(this, new InvalidationEventArgs(trigger)); + } + + internal void MockBounds(Rectangle bounds) + { + _mockX = bounds.X; + _mockY = bounds.Y; + _mockWidth = bounds.Width; + _mockHeight = bounds.Height; + } + + internal virtual void OnConstraintChanged(LayoutConstraint oldConstraint, LayoutConstraint newConstraint) + { + ComputeConstrainsForChildren(); + } + + internal virtual void OnIsPlatformEnabledChanged() + { + } + + internal virtual void OnIsVisibleChanged(bool oldValue, bool newValue) + { + InvalidateMeasure(InvalidationTrigger.Undefined); + } + + internal override void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values) + { + if (values == null) + return; + + if (Resources == null || Resources.Count == 0) + { + base.OnParentResourcesChanged(values); + return; + } + + var innerKeys = new HashSet<string>(); + var changedResources = new List<KeyValuePair<string, object>>(); + foreach (KeyValuePair<string, object> c in Resources) + innerKeys.Add(c.Key); + foreach (KeyValuePair<string, object> value in values) + { + if (innerKeys.Add(value.Key)) + changedResources.Add(value); + else if (value.Key.StartsWith(Style.StyleClassPrefix, StringComparison.Ordinal)) + { + var mergedClassStyles = new List<Style>(Resources[value.Key] as List<Style>); + mergedClassStyles.AddRange(value.Value as List<Style>); + changedResources.Add(new KeyValuePair<string, object>(value.Key, mergedClassStyles)); + } + } + if (changedResources.Count != 0) + OnResourcesChanged(changedResources); + } + + internal void UnmockBounds() + { + _mockX = _mockY = _mockWidth = _mockHeight = -1; + } + + void OnFocused() + { + EventHandler<FocusEventArgs> focus = Focused; + if (focus != null) + focus(this, new FocusEventArgs(this, true)); + } + + static void OnIsFocusedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue) + { + var element = bindable as VisualElement; + + var isFocused = (bool)newvalue; + if (isFocused) + { + element.OnFocused(); + } + else + { + element.OnUnfocus(); + } + } + + static void OnRequestChanged(BindableObject bindable, object oldvalue, object newvalue) + { + var constraint = LayoutConstraint.None; + var element = (VisualElement)bindable; + if (element.WidthRequest >= 0 && element.MinimumWidthRequest >= 0) + { + constraint |= LayoutConstraint.HorizontallyFixed; + } + if (element.HeightRequest >= 0 && element.MinimumHeightRequest >= 0) + { + constraint |= LayoutConstraint.VerticallyFixed; + } + + element.SelfConstraint = constraint; + ((VisualElement)bindable).InvalidateMeasure(InvalidationTrigger.SizeRequestChanged); + } + + void OnUnfocus() + { + EventHandler<FocusEventArgs> unFocus = Unfocused; + if (unFocus != null) + unFocus(this, new FocusEventArgs(this, false)); + } + + void SetSize(double width, double height) + { + if (Width == width && Height == height) + return; + + Width = width; + Height = height; + + SizeAllocated(width, height); + if (SizeChanged != null) + SizeChanged(this, EventArgs.Empty); + } + + internal class FocusRequestArgs : EventArgs + { + public bool Focus { get; set; } + + public bool Result { get; set; } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/WeakReferenceExtensions.cs b/Xamarin.Forms.Core/WeakReferenceExtensions.cs new file mode 100644 index 00000000..e4e883a4 --- /dev/null +++ b/Xamarin.Forms.Core/WeakReferenceExtensions.cs @@ -0,0 +1,16 @@ +using System; + +namespace Xamarin.Forms +{ + internal static class WeakReferenceExtensions + { + internal static bool TryGetTarget<T>(this WeakReference self, out T target) where T : class + { + if (self == null) + throw new ArgumentNullException("self"); + + target = (T)self.Target; + return target != null; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/WebNavigatedEventArgs.cs b/Xamarin.Forms.Core/WebNavigatedEventArgs.cs new file mode 100644 index 00000000..29bc3d11 --- /dev/null +++ b/Xamarin.Forms.Core/WebNavigatedEventArgs.cs @@ -0,0 +1,12 @@ +namespace Xamarin.Forms +{ + public class WebNavigatedEventArgs : WebNavigationEventArgs + { + public WebNavigatedEventArgs(WebNavigationEvent navigationEvent, WebViewSource source, string url, WebNavigationResult result) : base(navigationEvent, source, url) + { + Result = result; + } + + public WebNavigationResult Result { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/WebNavigatingEventArgs.cs b/Xamarin.Forms.Core/WebNavigatingEventArgs.cs new file mode 100644 index 00000000..49aa2dc1 --- /dev/null +++ b/Xamarin.Forms.Core/WebNavigatingEventArgs.cs @@ -0,0 +1,11 @@ +namespace Xamarin.Forms +{ + public class WebNavigatingEventArgs : WebNavigationEventArgs + { + public WebNavigatingEventArgs(WebNavigationEvent navigationEvent, WebViewSource source, string url) : base(navigationEvent, source, url) + { + } + + public bool Cancel { get; set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/WebNavigationEvent.cs b/Xamarin.Forms.Core/WebNavigationEvent.cs new file mode 100644 index 00000000..b47bd0ea --- /dev/null +++ b/Xamarin.Forms.Core/WebNavigationEvent.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public enum WebNavigationEvent + { + Back = 1, + Forward = 2, + NewPage = 3, + Refresh = 4 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/WebNavigationEventArgs.cs b/Xamarin.Forms.Core/WebNavigationEventArgs.cs new file mode 100644 index 00000000..e6124c31 --- /dev/null +++ b/Xamarin.Forms.Core/WebNavigationEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Xamarin.Forms +{ + public class WebNavigationEventArgs : EventArgs + { + protected WebNavigationEventArgs(WebNavigationEvent navigationEvent, WebViewSource source, string url) + { + NavigationEvent = navigationEvent; + Source = source; + Url = url; + } + + public WebNavigationEvent NavigationEvent { get; internal set; } + + public WebViewSource Source { get; internal set; } + + public string Url { get; internal set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/WebNavigationResult.cs b/Xamarin.Forms.Core/WebNavigationResult.cs new file mode 100644 index 00000000..eb0f6e73 --- /dev/null +++ b/Xamarin.Forms.Core/WebNavigationResult.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Forms +{ + public enum WebNavigationResult + { + Success = 1, + Cancel = 2, + Timeout = 3, + Failure = 4 + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/WebView.cs b/Xamarin.Forms.Core/WebView.cs new file mode 100644 index 00000000..06a5f72c --- /dev/null +++ b/Xamarin.Forms.Core/WebView.cs @@ -0,0 +1,126 @@ +using System; +using Xamarin.Forms.Platform; + +namespace Xamarin.Forms +{ + [RenderWith(typeof(_WebViewRenderer))] + public class WebView : View + { + public static readonly BindableProperty SourceProperty = BindableProperty.Create("Source", typeof(WebViewSource), typeof(WebView), default(WebViewSource), + propertyChanging: (bindable, oldvalue, newvalue) => + { + var source = oldvalue as WebViewSource; + if (source != null) + source.SourceChanged -= ((WebView)bindable).OnSourceChanged; + }, propertyChanged: (bindable, oldvalue, newvalue) => + { + var source = newvalue as WebViewSource; + var webview = (WebView)bindable; + if (source != null) + { + source.SourceChanged += webview.OnSourceChanged; + SetInheritedBindingContext(source, webview.BindingContext); + } + }); + + static readonly BindablePropertyKey CanGoBackPropertyKey = BindableProperty.CreateReadOnly("CanGoBack", typeof(bool), typeof(WebView), false); + + public static readonly BindableProperty CanGoBackProperty = CanGoBackPropertyKey.BindableProperty; + + static readonly BindablePropertyKey CanGoForwardPropertyKey = BindableProperty.CreateReadOnly("CanGoForward", typeof(bool), typeof(WebView), false); + + public static readonly BindableProperty CanGoForwardProperty = CanGoForwardPropertyKey.BindableProperty; + + public bool CanGoBack + { + get { return (bool)GetValue(CanGoBackProperty); } + internal set { SetValue(CanGoBackPropertyKey, value); } + } + + public bool CanGoForward + { + get { return (bool)GetValue(CanGoForwardProperty); } + internal set { SetValue(CanGoForwardPropertyKey, value); } + } + + [TypeConverter(typeof(WebViewSourceTypeConverter))] + public WebViewSource Source + { + get { return (WebViewSource)GetValue(SourceProperty); } + set { SetValue(SourceProperty, value); } + } + + public void Eval(string script) + { + EventHandler<EventArg<string>> handler = EvalRequested; + if (handler != null) + handler(this, new EventArg<string>(script)); + } + + public void GoBack() + { + EventHandler handler = GoBackRequested; + if (handler != null) + handler(this, EventArgs.Empty); + } + + public void GoForward() + { + EventHandler handler = GoForwardRequested; + if (handler != null) + handler(this, EventArgs.Empty); + } + + public event EventHandler<WebNavigatedEventArgs> Navigated; + + public event EventHandler<WebNavigatingEventArgs> Navigating; + + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + + WebViewSource source = Source; + if (source != null) + { + SetInheritedBindingContext(source, BindingContext); + } + } + + protected override void OnPropertyChanged(string propertyName) + { + if (propertyName == "BindingContext") + { + WebViewSource source = Source; + if (source != null) + SetInheritedBindingContext(source, BindingContext); + } + + base.OnPropertyChanged(propertyName); + } + + protected void OnSourceChanged(object sender, EventArgs e) + { + OnPropertyChanged(SourceProperty.PropertyName); + } + + internal event EventHandler<EventArg<string>> EvalRequested; + + internal event EventHandler GoBackRequested; + + internal event EventHandler GoForwardRequested; + + internal void SendNavigated(WebNavigatedEventArgs args) + { + EventHandler<WebNavigatedEventArgs> handler = Navigated; + if (handler != null) + handler(this, args); + } + + internal void SendNavigating(WebNavigatingEventArgs args) + { + EventHandler<WebNavigatingEventArgs> handler = Navigating; + if (handler != null) + handler(this, args); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/WebViewSource.cs b/Xamarin.Forms.Core/WebViewSource.cs new file mode 100644 index 00000000..6dc2635f --- /dev/null +++ b/Xamarin.Forms.Core/WebViewSource.cs @@ -0,0 +1,28 @@ +using System; + +namespace Xamarin.Forms +{ + public abstract class WebViewSource : BindableObject + { + public static implicit operator WebViewSource(Uri url) + { + return new UrlWebViewSource { Url = url?.AbsoluteUri }; + } + + public static implicit operator WebViewSource(string url) + { + return new UrlWebViewSource { Url = url }; + } + + protected void OnSourceChanged() + { + EventHandler eh = SourceChanged; + if (eh != null) + eh(this, EventArgs.Empty); + } + + internal abstract void Load(IWebViewRenderer renderer); + + internal event EventHandler SourceChanged; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/WebViewSourceTypeConverter.cs b/Xamarin.Forms.Core/WebViewSourceTypeConverter.cs new file mode 100644 index 00000000..5955eb14 --- /dev/null +++ b/Xamarin.Forms.Core/WebViewSourceTypeConverter.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Forms +{ + public class WebViewSourceTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + return new UrlWebViewSource { Url = value }; + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Color))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj new file mode 100644 index 00000000..ce2a3b57 --- /dev/null +++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj @@ -0,0 +1,408 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}</ProjectGuid> + <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <OutputType>Library</OutputType> + <RootNamespace>Xamarin.Forms</RootNamespace> + <AssemblyName>Xamarin.Forms.Core</AssemblyName> + <TargetFrameworkProfile>Profile259</TargetFrameworkProfile> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <NoWarn>0618</NoWarn> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>full</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Turkey|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <OutputPath>bin\Turkey\</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <NoWarn>0618</NoWarn> + <DebugType>full</DebugType> + <PlatformTarget>AnyCPU</PlatformTarget> + <ErrorReport>prompt</ErrorReport> + <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + <ItemGroup> + <Compile Include="AbsoluteLayoutFlags.cs" /> + <Compile Include="ActionSheetArguments.cs" /> + <Compile Include="AlertArguments.cs" /> + <Compile Include="AnimatableKey.cs" /> + <Compile Include="Application.cs" /> + <Compile Include="Aspect.cs" /> + <Compile Include="BackButtonPressedEventArgs.cs" /> + <Compile Include="BaseMenuItem.cs" /> + <Compile Include="BindablePropertyConverter.cs" /> + <Compile Include="BindingMode.cs" /> + <Compile Include="BindingTypeConverter.cs" /> + <Compile Include="BoundsConstraint.cs" /> + <Compile Include="BoundsTypeConverter.cs" /> + <Compile Include="CarouselView.cs" /> + <Compile Include="CastingEnumerator.cs" /> + <Compile Include="Cells\EntryCell.cs" /> + <Compile Include="Cells\ImageCell.cs" /> + <Compile Include="Cells\INativeElementView.cs" /> + <Compile Include="ChatKeyboard.cs" /> + <Compile Include="ChildCollectionChangedEventArgs.cs" /> + <Compile Include="CollectionSynchronizationCallback.cs" /> + <Compile Include="CollectionSynchronizationContext.cs" /> + <Compile Include="ColorTypeConverter.cs" /> + <Compile Include="ColumnDefinition.cs" /> + <Compile Include="ColumnDefinitionCollection.cs" /> + <Compile Include="Constraint.cs" /> + <Compile Include="ConstraintExpression.cs" /> + <Compile Include="ConstraintType.cs" /> + <Compile Include="ConstraintTypeConverter.cs" /> + <Compile Include="ContentPresenter.cs" /> + <Compile Include="ControlTemplate.cs" /> + <Compile Include="CustomKeyboard.cs" /> + <Compile Include="DataTemplateExtensions.cs" /> + <Compile Include="DataTemplateSelector.cs" /> + <Compile Include="DateChangedEventArgs.cs" /> + <Compile Include="DelegateLogListener.cs" /> + <Compile Include="DependencyAttribute.cs" /> + <Compile Include="DependencyFetchTarget.cs" /> + <Compile Include="DeviceOrientation.cs" /> + <Compile Include="DeviceOrientationExtensions.cs" /> + <Compile Include="Effect.cs" /> + <Compile Include="ElementEventArgs.cs" /> + <Compile Include="ElementTemplate.cs" /> + <Compile Include="EmailKeyboard.cs" /> + <Compile Include="ExportEffectAttribute.cs" /> + <Compile Include="ExpressionSearch.cs" /> + <Compile Include="FileAccess.cs" /> + <Compile Include="FileImageSourceConverter.cs" /> + <Compile Include="FileMode.cs" /> + <Compile Include="FileShare.cs" /> + <Compile Include="FontAttributes.cs" /> + <Compile Include="FontSizeConverter.cs" /> + <Compile Include="FontTypeConverter.cs" /> + <Compile Include="GestureRecognizer.cs" /> + <Compile Include="GridLength.cs" /> + <Compile Include="GridLengthTypeConverter.cs" /> + <Compile Include="GridUnitType.cs" /> + <Compile Include="HandlerAttribute.cs" /> + <Compile Include="HtmlWebViewSource.cs" /> + <Compile Include="IButtonController.cs" /> + <Compile Include="ICarouselViewController.cs" /> + <Compile Include="IControlTemplated.cs" /> + <Compile Include="IDefinition.cs" /> + <Compile Include="IEffectControlProvider.cs" /> + <Compile Include="IElementController.cs" /> + <Compile Include="IExpressionSearch.cs" /> + <Compile Include="IExtendedTypeConverter.cs" /> + <Compile Include="IFontElement.cs" /> + <Compile Include="IGestureRecognizer.cs" /> + <Compile Include="IItemsView.cs" /> + <Compile Include="IItemViewController.cs" /> + <Compile Include="ILayoutController.cs" /> + <Compile Include="IListViewController.cs" /> + <Compile Include="ImageSourceConverter.cs" /> + <Compile Include="INavigation.cs" /> + <Compile Include="InvalidationEventArgs.cs" /> + <Compile Include="InvalidationTrigger.cs" /> + <Compile Include="InvalidNavigationException.cs" /> + <Compile Include="IOpenGlViewController.cs" /> + <Compile Include="IPanGestureController.cs" /> + <Compile Include="IPinchGestureController.cs" /> + <Compile Include="IProvideParentValues.cs" /> + <Compile Include="IProvideValueTarget.cs" /> + <Compile Include="IRegisterable.cs" /> + <Compile Include="IResourceDictionary.cs" /> + <Compile Include="IResourcesProvider.cs" /> + <Compile Include="IRootObjectProvider.cs" /> + <Compile Include="IScrollViewController.cs" /> + <Compile Include="ItemsViewSimple.cs" /> + <Compile Include="ItemTappedEventArgs.cs" /> + <Compile Include="ItemVisibilityEventArgs.cs" /> + <Compile Include="IValueConverterProvider.cs" /> + <Compile Include="IValueProvider.cs" /> + <Compile Include="IViewController.cs" /> + <Compile Include="IVisualElementController.cs" /> + <Compile Include="IWebViewRenderer.cs" /> + <Compile Include="IXamlTypeResolver.cs" /> + <Compile Include="IXmlLineInfoProvider.cs" /> + <Compile Include="KeyboardFlags.cs" /> + <Compile Include="KeyboardTypeConverter.cs" /> + <Compile Include="LayoutAlignment.cs" /> + <Compile Include="LayoutAlignmentExtensions.cs" /> + <Compile Include="LayoutConstraint.cs" /> + <Compile Include="LayoutExpandFlag.cs" /> + <Compile Include="LayoutOptionsConverter.cs" /> + <Compile Include="ListViewCachingStrategy.cs" /> + <Compile Include="LockingSemaphore.cs" /> + <Compile Include="Log.cs" /> + <Compile Include="LogListener.cs" /> + <Compile Include="MasterBehavior.cs" /> + <Compile Include="MeasureFlags.cs" /> + <Compile Include="MenuItem.cs" /> + <Compile Include="ModalEventArgs.cs" /> + <Compile Include="ModalPoppedEventArgs.cs" /> + <Compile Include="ModalPoppingEventArgs.cs" /> + <Compile Include="ModalPushedEventArgs.cs" /> + <Compile Include="ModalPushingEventArgs.cs" /> + <Compile Include="NavigationEventArgs.cs" /> + <Compile Include="NavigationModel.cs" /> + <Compile Include="NavigationRequestedEventArgs.cs" /> + <Compile Include="NotifyCollectionChangedEventArgsEx.cs" /> + <Compile Include="NotifyCollectionChangedEventArgsExtensions.cs" /> + <Compile Include="NullEffect.cs" /> + <Compile Include="NumericKeyboard.cs" /> + <Compile Include="ObservableList.cs" /> + <Compile Include="OrderedDictionary.cs" /> + <Compile Include="PanGestureRecognizer.cs" /> + <Compile Include="PanUpdatedEventArgs.cs" /> + <Compile Include="Performance.cs" /> + <Compile Include="PinchGestureUpdatedEventArgs.cs" /> + <Compile Include="PlatformEffect.cs" /> + <Compile Include="PointTypeConverter.cs" /> + <Compile Include="PreserveAttribute.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="BindableObject.cs" /> + <Compile Include="BindableObjectExtensions.cs" /> + <Compile Include="BindableProperty.cs" /> + <Compile Include="Binding.cs" /> + <Compile Include="BindingBase.cs" /> + <Compile Include="BindingBaseExtensions.cs" /> + <Compile Include="BindingExpression.cs" /> + <Compile Include="Properties\GlobalAssemblyInfo.cs" /> + <Compile Include="PropertyChangingEventArgs.cs" /> + <Compile Include="IValueConverter.cs" /> + <Compile Include="ConcurrentDictionary.cs" /> + <Compile Include="PropertyChangingEventHandler.cs" /> + <Compile Include="ReadOnlyCastingList.cs" /> + <Compile Include="ReadOnlyListAdapter.cs" /> + <Compile Include="RectangleTypeConverter.cs" /> + <Compile Include="RenderWithAttribute.cs" /> + <Compile Include="ResolutionGroupNameAttribute.cs" /> + <Compile Include="ResourcesChangedEventArgs.cs" /> + <Compile Include="ResourcesExtensions.cs" /> + <Compile Include="RoutingEffect.cs" /> + <Compile Include="RowDefinition.cs" /> + <Compile Include="RowDefinitionCollection.cs" /> + <Compile Include="ScrollOrientation.cs" /> + <Compile Include="ScrollToMode.cs" /> + <Compile Include="ScrollToPosition.cs" /> + <Compile Include="SelectedItemChangedEventArgs.cs" /> + <Compile Include="SelectedPositionChangedEventArgs.cs" /> + <Compile Include="SeparatorMenuItem.cs" /> + <Compile Include="SeparatorVisibility.cs" /> + <Compile Include="SizeRequest.cs" /> + <Compile Include="Span.cs" /> + <Compile Include="SplitOrderedList.cs" /> + <Compile Include="StackOrientation.cs" /> + <Compile Include="StreamWrapper.cs" /> + <Compile Include="SynchronizedList.cs" /> + <Compile Include="TableIntent.cs" /> + <Compile Include="TableRoot.cs" /> + <Compile Include="TableSectionBase.cs" /> + <Compile Include="TapGestureRecognizer.cs" /> + <Compile Include="TelephoneKeyboard.cs" /> + <Compile Include="TemplateBinding.cs" /> + <Compile Include="TemplatedItemsList.cs" /> + <Compile Include="TemplatedPage.cs" /> + <Compile Include="TemplatedView.cs" /> + <Compile Include="TemplateUtilities.cs" /> + <Compile Include="TextChangedEventArgs.cs" /> + <Compile Include="TextKeyboard.cs" /> + <Compile Include="ThicknessTypeConverter.cs" /> + <Compile Include="Ticker.cs" /> + <Compile Include="ToggledEventArgs.cs" /> + <Compile Include="ToolbarItemEventArgs.cs" /> + <Compile Include="ToolbarItemOrder.cs" /> + <Compile Include="TrackableCollection.cs" /> + <Compile Include="TypeConverterAttribute.cs" /> + <Compile Include="TypeConverter.cs" /> + <Compile Include="ReflectionExtensions.cs" /> + <Compile Include="Device.cs" /> + <Compile Include="TargetPlatform.cs" /> + <Compile Include="TargetIdiom.cs" /> + <Compile Include="UnsolvableConstraintsException.cs" /> + <Compile Include="UrlKeyboard.cs" /> + <Compile Include="UrlWebViewSource.cs" /> + <Compile Include="ValueChangedEventArgs.cs" /> + <Compile Include="Vec2.cs" /> + <Compile Include="View.cs" /> + <Compile Include="IElement.cs" /> + <Compile Include="IAnimatable.cs" /> + <Compile Include="Easing.cs" /> + <Compile Include="NavigationProxy.cs" /> + <Compile Include="NavigationPage.cs" /> + <Compile Include="Page.cs" /> + <Compile Include="LayoutOptions.cs" /> + <Compile Include="CarouselPage.cs" /> + <Compile Include="Rectangle.cs" /> + <Compile Include="Color.cs" /> + <Compile Include="ResourceDictionary.cs" /> + <Compile Include="EventArg.cs" /> + <Compile Include="IPlatform.cs" /> + <Compile Include="EnumerableExtensions.cs" /> + <Compile Include="Size.cs" /> + <Compile Include="GestureState.cs" /> + <Compile Include="Point.cs" /> + <Compile Include="Thickness.cs" /> + <Compile Include="ToolbarItem.cs" /> + <Compile Include="ContentPropertyAttribute.cs" /> + <Compile Include="MessagingCenter.cs" /> + <Compile Include="Cells\Cell.cs" /> + <Compile Include="Cells\SwitchCell.cs" /> + <Compile Include="Cells\TextCell.cs" /> + <Compile Include="Cells\ViewCell.cs" /> + <Compile Include="ListView.cs" /> + <Compile Include="TableView.cs" /> + <Compile Include="ItemsView.cs" /> + <Compile Include="TableModel.cs" /> + <Compile Include="TableSection.cs" /> + <Compile Include="DataTemplate.cs" /> + <Compile Include="ListProxy.cs" /> + <Compile Include="AbsoluteLayout.cs" /> + <Compile Include="Registrar.cs" /> + <Compile Include="ActivityIndicator.cs" /> + <Compile Include="BoxView.cs" /> + <Compile Include="Button.cs" /> + <Compile Include="Command.cs" /> + <Compile Include="ContentView.cs" /> + <Compile Include="DatePicker.cs" /> + <Compile Include="DependencyService.cs" /> + <Compile Include="Editor.cs" /> + <Compile Include="Entry.cs" /> + <Compile Include="Frame.cs" /> + <Compile Include="Image.cs" /> + <Compile Include="UriImageSource.cs" /> + <Compile Include="IMarkupExtension.cs" /> + <Compile Include="InputView.cs" /> + <Compile Include="Keyboard.cs" /> + <Compile Include="Label.cs" /> + <Compile Include="LineBreakMode.cs" /> + <Compile Include="MasterDetailPage.cs" /> + <Compile Include="NavigationMenu.cs" /> + <Compile Include="OpenGLView.cs" /> + <Compile Include="ProgressBar.cs" /> + <Compile Include="RelativeLayout.cs" /> + <Compile Include="ScrollView.cs" /> + <Compile Include="SearchBar.cs" /> + <Compile Include="Slider.cs" /> + <Compile Include="StackLayout.cs" /> + <Compile Include="Stepper.cs" /> + <Compile Include="Switch.cs" /> + <Compile Include="TabbedPage.cs" /> + <Compile Include="TemplateExtensions.cs" /> + <Compile Include="TimePicker.cs" /> + <Compile Include="Toolbar.cs" /> + <Compile Include="ToolbarTracker.cs" /> + <Compile Include="ViewExtensions.cs" /> + <Compile Include="ViewState.cs" /> + <Compile Include="WeakReferenceExtensions.cs" /> + <Compile Include="WebNavigatedEventArgs.cs" /> + <Compile Include="WebNavigatingEventArgs.cs" /> + <Compile Include="WebNavigationEvent.cs" /> + <Compile Include="WebNavigationEventArgs.cs" /> + <Compile Include="WebNavigationResult.cs" /> + <Compile Include="WebView.cs" /> + <Compile Include="Animation.cs" /> + <Compile Include="AnimationExtensions.cs" /> + <Compile Include="Tweener.cs" /> + <Compile Include="IPlatformServices.cs" /> + <Compile Include="UriTypeConverter.cs" /> + <Compile Include="ITimer.cs" /> + <Compile Include="IIsolatedStorageFile.cs" /> + <Compile Include="Grid.cs" /> + <Compile Include="GridCalc.cs" /> + <Compile Include="DefinitionCollection.cs" /> + <Compile Include="Element.cs" /> + <Compile Include="ObservableWrapper.cs" /> + <Compile Include="ElementCollection.cs" /> + <Compile Include="IViewContainer.cs" /> + <Compile Include="Layout.cs" /> + <Compile Include="ContentPage.cs" /> + <Compile Include="IPageContainer.cs" /> + <Compile Include="MultiPage.cs" /> + <Compile Include="ILayout.cs" /> + <Compile Include="FocusEventArgs.cs" /> + <Compile Include="VisualElement.cs" /> + <Compile Include="NameScopeExtensions.cs" /> + <Compile Include="NamedSize.cs" /> + <Compile Include="TextAlignment.cs" /> + <Compile Include="Font.cs" /> + <Compile Include="Picker.cs" /> + <Compile Include="NumericExtensions.cs" /> + <Compile Include="BindablePropertyKey.cs" /> + <Compile Include="ImageSource.cs" /> + <Compile Include="FileImageSource.cs" /> + <Compile Include="StreamImageSource.cs" /> + <Compile Include="OnPlatform.cs" /> + <Compile Include="OnIdiom.cs" /> + <Compile Include="TappedEventArgs.cs" /> + <Compile Include="FormattedString.cs" /> + <Compile Include="Interactivity\Behavior.cs" /> + <Compile Include="Interactivity\IAttachedObject.cs" /> + <Compile Include="Interactivity\AttachedCollection.cs" /> + <Compile Include="Interactivity\TriggerAction.cs" /> + <Compile Include="Interactivity\EventTrigger.cs" /> + <Compile Include="Interactivity\TriggerBase.cs" /> + <Compile Include="Interactivity\Trigger.cs" /> + <Compile Include="Setter.cs" /> + <Compile Include="Style.cs" /> + <Compile Include="ParameterAttribute.cs" /> + <Compile Include="TypeTypeConverter.cs" /> + <Compile Include="SettersExtensions.cs" /> + <Compile Include="IDeserializer.cs" /> + <Compile Include="ISystemResourcesProvider.cs" /> + <Compile Include="Interactivity\DataTrigger.cs" /> + <Compile Include="Interactivity\Condition.cs" /> + <Compile Include="Interactivity\BindingCondition.cs" /> + <Compile Include="Interactivity\PropertyCondition.cs" /> + <Compile Include="Interactivity\MultiTrigger.cs" /> + <Compile Include="Interactivity\MultiCondition.cs" /> + <Compile Include="DeviceInfo.cs" /> + <Compile Include="WebViewSource.cs" /> + <Compile Include="WebViewSourceTypeConverter.cs" /> + <Compile Include="XamlParseException.cs" /> + <Compile Include="ScrolledEventArgs.cs" /> + <Compile Include="ScrollToRequestedEventArgs.cs" /> + <Compile Include="XmlLineInfo.cs" /> + <Compile Include="GestureStatus.cs" /> + <Compile Include="Internals\INameScope.cs" /> + <Compile Include="Internals\NameScope.cs" /> + <Compile Include="Internals\IDataTemplate.cs" /> + <Compile Include="Internals\IDynamicResourceHandler.cs" /> + <Compile Include="Internals\DynamicResource.cs" /> + <Compile Include="PinchGestureRecognizer.cs" /> + <Compile Include="IStyle.cs" /> + <Compile Include="MergedStyle.cs" /> + <Compile Include="IApplicationController.cs" /> + </ItemGroup> + <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> + <ItemGroup> + <ProjectReference Include="..\Xamarin.Forms.Platform\Xamarin.Forms.Platform.csproj"> + <Project>{67f9d3a8-f71e-4428-913f-c37ae82cdb24}</Project> + <Name>Xamarin.Forms.Platform</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup /> + <PropertyGroup> + <PostBuildEvent> + </PostBuildEvent> + </PropertyGroup> + <ItemGroup /> +</Project>
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Xamarin.Forms.Core.nuspec b/Xamarin.Forms.Core/Xamarin.Forms.Core.nuspec new file mode 100644 index 00000000..b5dff885 --- /dev/null +++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.nuspec @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<package > + <metadata> + <id>Xamarin.Forms.Core</id> + <version>$version$</version> + <title>Xamarin.Forms (.NET 4.5)</title> + <authors>Xamarin Inc.</authors> + <owners>Xamarin Inc.</owners> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Xamarin.Forms platform abstruction library for mobile applications.</description> + <releaseNotes></releaseNotes> + <copyright>Copyright 2013</copyright> + </metadata> +</package> diff --git a/Xamarin.Forms.Core/XamlParseException.cs b/Xamarin.Forms.Core/XamlParseException.cs new file mode 100644 index 00000000..42e8b618 --- /dev/null +++ b/Xamarin.Forms.Core/XamlParseException.cs @@ -0,0 +1,30 @@ +using System; +using System.Xml; + +namespace Xamarin.Forms.Xaml +{ + public class XamlParseException : Exception + { + readonly string _unformattedMessage; + + public XamlParseException(string message, IXmlLineInfo xmlInfo) : base(FormatMessage(message, xmlInfo)) + { + _unformattedMessage = message; + XmlInfo = xmlInfo; + } + + public IXmlLineInfo XmlInfo { get; private set; } + + internal string UnformattedMessage + { + get { return _unformattedMessage ?? Message; } + } + + static string FormatMessage(string message, IXmlLineInfo xmlinfo) + { + if (xmlinfo == null || !xmlinfo.HasLineInfo()) + return message; + return string.Format("Position {0}:{1}. {2}", xmlinfo.LineNumber, xmlinfo.LinePosition, message); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/XmlLineInfo.cs b/Xamarin.Forms.Core/XmlLineInfo.cs new file mode 100644 index 00000000..eb11e316 --- /dev/null +++ b/Xamarin.Forms.Core/XmlLineInfo.cs @@ -0,0 +1,29 @@ +using System.Xml; + +namespace Xamarin.Forms.Xaml +{ + public class XmlLineInfo : IXmlLineInfo + { + readonly bool _hasLineInfo; + + public XmlLineInfo() + { + } + + public XmlLineInfo(int linenumber, int lineposition) + { + _hasLineInfo = true; + LineNumber = linenumber; + LinePosition = lineposition; + } + + public bool HasLineInfo() + { + return _hasLineInfo; + } + + public int LineNumber { get; } + + public int LinePosition { get; } + } +}
\ No newline at end of file |