using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using Xamarin.Forms.Internals; namespace Xamarin.Forms { public class AbsoluteLayout : Layout { 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 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 LogicalChildrenInternal) { 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 LogicalChildrenInternal) { 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) { InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged); UpdateChildrenLayout(); } } static SizeRequest ComputeBoundingRegionDesiredSize(View view) { var width = 0.0; var height = 0.0; var sizeRequest = new Lazy(() => 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 : IList where T : View { void Add(View view, Rectangle bounds, AbsoluteLayoutFlags flags = AbsoluteLayoutFlags.None); void Add(View view, Point position); } class AbsoluteElementCollection : ElementCollection, IAbsoluteList { public AbsoluteElementCollection(ObservableCollection 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); } } } }