Skip to content

Instantly share code, notes, and snippets.

@KirillAshikhmin
Last active February 9, 2018 09:36
Show Gist options
  • Save KirillAshikhmin/3ac657e1e7a80cd6ccd97e330470b471 to your computer and use it in GitHub Desktop.
Save KirillAshikhmin/3ac657e1e7a80cd6ccd97e330470b471 to your computer and use it in GitHub Desktop.
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