diff options
Diffstat (limited to 'Xamarin.Forms.Core/RelativeLayout.cs')
-rw-r--r-- | Xamarin.Forms.Core/RelativeLayout.cs | 317 |
1 files changed, 317 insertions, 0 deletions
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 |