Last active
February 9, 2018 09:36
-
-
Save KirillAshikhmin/3ac657e1e7a80cd6ccd97e330470b471 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using Xamarin.Forms; | |
namespace Binwell.UI.Layouts | |
{ | |
public class PositionLayout : Layout<View> | |
{ | |
public enum Positions | |
{ | |
Default, | |
Center, | |
TopLeft, | |
TopCenter, | |
TopRight, | |
CenterRight, | |
BottomRight, | |
BottomCenter, | |
BottomLeft, | |
CenterLeft | |
} | |
public enum Sizes | |
{ | |
Default, | |
Full, | |
HorizontalExpand, | |
VerticalExpand | |
} | |
public bool UseHardcodedSize { get; set; } | |
public static readonly BindableProperty PositionProperty = BindableProperty.CreateAttached(@"Position", typeof(Positions), typeof(PositionLayout), Positions.Default); | |
public static readonly BindableProperty ViewSizeProperty = BindableProperty.CreateAttached(@"ViewSize", typeof(Sizes), typeof(PositionLayout), Sizes.Default); | |
public static Positions GetPosition(BindableObject bindable) | |
{ | |
return (Positions)bindable.GetValue(PositionProperty); | |
} | |
public static void SetPosition(BindableObject bindable, Positions position) | |
{ | |
bindable.SetValue(PositionProperty, position); | |
} | |
public static Sizes GetViewSize(BindableObject bindable) | |
{ | |
return (Sizes)bindable.GetValue(ViewSizeProperty); | |
} | |
public static void SetViewSize(BindableObject bindable, Sizes size) | |
{ | |
bindable.SetValue(ViewSizeProperty, size); | |
} | |
public void Add(View view, Positions position, Sizes size) | |
{ | |
SetPosition(view,position); | |
SetViewSize(view,size); | |
Children.Add(view); | |
} | |
readonly Dictionary<Size, SizeRequest> _measureCache = new Dictionary<Size, SizeRequest>(); | |
protected override void InvalidateMeasure() | |
{ | |
_measureCache.Clear(); | |
base.InvalidateMeasure(); | |
} | |
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) | |
{ | |
if (UseHardcodedSize) return new SizeRequest(new Size(WidthRequest, HeightRequest)); | |
SizeRequest cachedResult; | |
var constraintSize = new Size(widthConstraint, heightConstraint); | |
if (_measureCache.TryGetValue(constraintSize, out cachedResult)) | |
{ | |
return cachedResult; | |
} | |
var height = 0d; | |
var minHeight = 0d; | |
var width = 0d; | |
var minWidth = 0d; | |
foreach (var child in Children) | |
{ | |
if (!child.IsVisible) | |
continue; | |
var childSizeRequest = child.Measure(widthConstraint, double.PositiveInfinity, MeasureFlags.IncludeMargins); | |
height = Math.Max(height, childSizeRequest.Request.Height); | |
minHeight = Math.Max(minHeight, childSizeRequest.Minimum.Height); | |
width += childSizeRequest.Request.Width; | |
minWidth += childSizeRequest.Minimum.Width; | |
} | |
if (MinimumHeightRequest > 0 && height < MinimumHeightRequest) height = MinimumHeightRequest; | |
if (MinimumWidthRequest > 0 && width < MinimumWidthRequest) width = MinimumWidthRequest; | |
// store our result in the cache for next time | |
if (WidthRequest>0 && !double.IsPositiveInfinity(WidthRequest)) width = Math.Max(width, WidthRequest); | |
if (HeightRequest > 0 && !double.IsPositiveInfinity(HeightRequest)) height = Math.Max(height, HeightRequest); | |
var result = new SizeRequest(new Size(width, height), new Size(minWidth, minHeight)); | |
_measureCache[constraintSize] = result; | |
return result; | |
} | |
protected override void LayoutChildren(double x, double y, double width, double height) | |
{ | |
if (UseHardcodedSize) | |
{ | |
width = WidthRequest; | |
height = HeightRequest; | |
} | |
foreach (var child in Children) | |
{ | |
if (!child.IsVisible) | |
continue; | |
var rect = ComputeLayoutForRegion(child, width, height); | |
rect.X += x; | |
rect.Y += y; | |
LayoutChildIntoBoundingRegion(child, rect); | |
} | |
} | |
Rectangle ComputeLayoutForRegion(View view, double width, double height) | |
{ | |
var needSize = GetViewSize(view); | |
if (needSize==Sizes.Full) return new Rectangle(0,0,width,height); | |
var result = new Rectangle(); | |
var position = GetPosition(view); | |
Size size; | |
if (view.HeightRequest > 0 && view.WidthRequest > 0) | |
{ | |
size = new Size(view.WidthRequest + view.Margin.HorizontalThickness, view.HeightRequest + view.Margin.VerticalThickness); | |
} | |
else | |
{ | |
var sizeRequest = view.Measure(width, height, MeasureFlags.IncludeMargins); | |
size = sizeRequest.Request; | |
} | |
size.Width = Math.Max(size.Width, view.WidthRequest); | |
size.Height = Math.Max(size.Height, view.HeightRequest); | |
result.Width = size.Width; | |
result.Height = size.Height; | |
if (needSize == Sizes.HorizontalExpand) result.Width = width; | |
else if (needSize == Sizes.VerticalExpand) result.Height = height; | |
double x, y; | |
switch (position) | |
{ | |
case Positions.Default: | |
x = 0; | |
y = 0; | |
break; | |
case Positions.Center: | |
x = width/2 - result.Width/2; | |
y = height/2 - result.Height/2; | |
break; | |
case Positions.TopLeft: | |
x = 0; | |
y = 0; | |
break; | |
case Positions.TopCenter: | |
x = width / 2 - result.Width / 2; | |
y = 0; | |
break; | |
case Positions.TopRight: | |
x = width-result.Width; | |
y = 0; | |
break; | |
case Positions.CenterRight: | |
x = width - result.Width; | |
y = height / 2 - result.Height / 2; | |
break; | |
case Positions.BottomRight: | |
x = width - result.Width; | |
y = height - result.Height; | |
break; | |
case Positions.BottomCenter: | |
x = width / 2 - result.Width / 2; | |
y = height - result.Height; | |
break; | |
case Positions.BottomLeft: | |
x = 0; | |
y = height - result.Height; | |
break; | |
case Positions.CenterLeft: | |
x = 0; | |
y = height / 2 - result.Height / 2; | |
break; | |
default: | |
throw new ArgumentOutOfRangeException(); | |
} | |
result.X -= view.Margin.Bottom; | |
result.X += view.Margin.Top; | |
result.Y -= view.Margin.Right; | |
result.Y += view.Margin.Left; | |
result.X = Math.Max(x,0); | |
result.Y = Math.Max(y,0); | |
return result; | |
} | |
} | |
public static class PositionLayoutExtensions | |
{ | |
public static T SetPositionLayoutPosition<T>(this T self, PositionLayout.Positions position) | |
{ | |
(self as BindableObject)?.SetValue(PositionLayout.PositionProperty, position); | |
return self; | |
} | |
public static T SetPositionLayoutViewSize<T>(this T self, PositionLayout.Sizes size) | |
{ | |
(self as BindableObject)?.SetValue(PositionLayout.ViewSizeProperty, size); | |
return self; | |
} | |
public static T SetPositionLayoutProperties<T>(this T self, | |
PositionLayout.Positions position = PositionLayout.Positions.Default, | |
PositionLayout.Sizes size = PositionLayout.Sizes.Default) where T : View | |
{ | |
PositionLayout.SetPosition(self, position); | |
PositionLayout.SetViewSize(self, size); | |
return self; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment