510 строки
18 KiB
C#
510 строки
18 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Runtime.CompilerServices;
|
|
using Xamarin.Forms.Internals;
|
|
|
|
namespace Xamarin.Forms
|
|
{
|
|
public class FlexLayout : Layout<View>
|
|
{
|
|
public static readonly BindableProperty DirectionProperty =
|
|
BindableProperty.Create(nameof(Direction), typeof(FlexDirection), typeof(FlexLayout), FlexDirection.Row,
|
|
propertyChanged: OnDirectionPropertyChanged);
|
|
|
|
public static readonly BindableProperty JustifyContentProperty =
|
|
BindableProperty.Create(nameof(JustifyContent), typeof(FlexJustify), typeof(FlexLayout), FlexJustify.Start,
|
|
propertyChanged: OnJustifyContentPropertyChanged);
|
|
|
|
public static readonly BindableProperty AlignContentProperty =
|
|
BindableProperty.Create(nameof(AlignContent), typeof(FlexAlignContent), typeof(FlexLayout), FlexAlignContent.Stretch,
|
|
propertyChanged: OnAlignContentPropertyChanged);
|
|
|
|
public static readonly BindableProperty AlignItemsProperty =
|
|
BindableProperty.Create(nameof(AlignItems), typeof(FlexAlignItems), typeof(FlexLayout), FlexAlignItems.Stretch,
|
|
propertyChanged: OnAlignItemsPropertyChanged);
|
|
|
|
public static readonly BindableProperty PositionProperty =
|
|
BindableProperty.Create(nameof(Position), typeof(FlexPosition), typeof(FlexLayout), FlexPosition.Relative,
|
|
propertyChanged: OnPositionPropertyChanged);
|
|
|
|
public static readonly BindableProperty WrapProperty =
|
|
BindableProperty.Create(nameof(Wrap), typeof(FlexWrap), typeof(FlexLayout), FlexWrap.NoWrap,
|
|
propertyChanged: OnWrapPropertyChanged);
|
|
|
|
static readonly BindableProperty FlexItemProperty =
|
|
BindableProperty.CreateAttached("FlexItem", typeof(Flex.Item), typeof(FlexLayout), null);
|
|
|
|
public static readonly BindableProperty OrderProperty =
|
|
BindableProperty.CreateAttached("Order", typeof(int), typeof(FlexLayout), default(int),
|
|
propertyChanged: OnOrderPropertyChanged);
|
|
|
|
public static readonly BindableProperty GrowProperty =
|
|
BindableProperty.CreateAttached("Grow", typeof(float), typeof(FlexLayout), default(float),
|
|
propertyChanged: OnGrowPropertyChanged, validateValue: (bindable, value) => (float)value >= 0);
|
|
|
|
public static readonly BindableProperty ShrinkProperty =
|
|
BindableProperty.CreateAttached("Shrink", typeof(float), typeof(FlexLayout), 1f,
|
|
propertyChanged: OnShrinkPropertyChanged, validateValue: (bindable, value) => (float)value >= 0);
|
|
|
|
public static readonly BindableProperty AlignSelfProperty =
|
|
BindableProperty.CreateAttached("AlignSelf", typeof(FlexAlignSelf), typeof(FlexLayout), FlexAlignSelf.Auto,
|
|
propertyChanged: OnAlignSelfPropertyChanged);
|
|
|
|
public static readonly BindableProperty BasisProperty =
|
|
BindableProperty.CreateAttached("Basis", typeof(FlexBasis), typeof(FlexLayout), FlexBasis.Auto,
|
|
propertyChanged: OnBasisPropertyChanged);
|
|
|
|
public FlexDirection Direction {
|
|
get => (FlexDirection)GetValue(DirectionProperty);
|
|
set => SetValue(DirectionProperty, value);
|
|
}
|
|
|
|
public FlexJustify JustifyContent {
|
|
get => (FlexJustify)GetValue(JustifyContentProperty);
|
|
set => SetValue(JustifyContentProperty, value);
|
|
}
|
|
|
|
public FlexAlignContent AlignContent {
|
|
get => (FlexAlignContent)GetValue(AlignContentProperty);
|
|
set => SetValue(AlignContentProperty, value);
|
|
}
|
|
|
|
public FlexAlignItems AlignItems {
|
|
get => (FlexAlignItems)GetValue(AlignItemsProperty);
|
|
set => SetValue(AlignItemsProperty, value);
|
|
}
|
|
|
|
public FlexPosition Position {
|
|
get => (FlexPosition)GetValue(PositionProperty);
|
|
set => SetValue(PositionProperty, value);
|
|
}
|
|
|
|
public FlexWrap Wrap {
|
|
get => (FlexWrap)GetValue(WrapProperty);
|
|
set => SetValue(WrapProperty, value);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
static Flex.Item GetFlexItem(BindableObject bindable)
|
|
=> (Flex.Item)bindable.GetValue(FlexItemProperty);
|
|
|
|
static void SetFlexItem(BindableObject bindable, Flex.Item node)
|
|
=> bindable.SetValue(FlexItemProperty, node);
|
|
|
|
public static int GetOrder(BindableObject bindable)
|
|
=> (int)bindable.GetValue(OrderProperty);
|
|
|
|
public static void SetOrder(BindableObject bindable, int value)
|
|
=> bindable.SetValue(OrderProperty, value);
|
|
|
|
public static float GetGrow(BindableObject bindable)
|
|
=> (float)bindable.GetValue(GrowProperty);
|
|
|
|
public static void SetGrow(BindableObject bindable, float value)
|
|
=> bindable.SetValue(GrowProperty, value);
|
|
|
|
public static float GetShrink(BindableObject bindable)
|
|
=> (float)bindable.GetValue(ShrinkProperty);
|
|
|
|
public static void SetShrink(BindableObject bindable, float value)
|
|
=> bindable.SetValue(ShrinkProperty, value);
|
|
|
|
public static FlexAlignSelf GetAlignSelf(BindableObject bindable)
|
|
=> (FlexAlignSelf)bindable.GetValue(AlignSelfProperty);
|
|
|
|
public static void SetAlignSelf(BindableObject bindable, FlexAlignSelf value)
|
|
=> bindable.SetValue(AlignSelfProperty, value);
|
|
|
|
public static FlexBasis GetBasis(BindableObject bindable)
|
|
=> (FlexBasis)bindable.GetValue(BasisProperty);
|
|
|
|
public static void SetBasis(BindableObject bindable, FlexBasis value)
|
|
=> bindable.SetValue(BasisProperty, value);
|
|
|
|
static void OnOrderPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (!bindable.IsSet(FlexItemProperty))
|
|
return;
|
|
GetFlexItem(bindable).Order = (int)newValue;
|
|
((VisualElement)bindable).InvalidateMeasureInternal(InvalidationTrigger.Undefined);
|
|
}
|
|
|
|
static void OnGrowPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (!bindable.IsSet(FlexItemProperty))
|
|
return;
|
|
GetFlexItem(bindable).Grow = (float)newValue;
|
|
((VisualElement)bindable).InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
|
}
|
|
|
|
static void OnShrinkPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (!bindable.IsSet(FlexItemProperty))
|
|
return;
|
|
GetFlexItem(bindable).Shrink = (float)newValue;
|
|
((VisualElement)bindable).InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
|
}
|
|
|
|
static void OnAlignSelfPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (!bindable.IsSet(FlexItemProperty))
|
|
return;
|
|
GetFlexItem(bindable).AlignSelf = (Flex.AlignSelf)(FlexAlignSelf)newValue;
|
|
((VisualElement)bindable).InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
|
}
|
|
|
|
static void OnBasisPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (!bindable.IsSet(FlexItemProperty))
|
|
return;
|
|
GetFlexItem(bindable).Basis = ((FlexBasis)newValue).ToFlexBasis();
|
|
((VisualElement)bindable).InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
|
}
|
|
|
|
static void OnDirectionPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
var flexLayout = bindable as FlexLayout;
|
|
if (flexLayout._root == null)
|
|
return;
|
|
flexLayout._root.Direction = (Flex.Direction)(FlexDirection)newValue;
|
|
flexLayout.InvalidateLayout();
|
|
}
|
|
|
|
static void OnJustifyContentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
var flexLayout = bindable as FlexLayout;
|
|
if (flexLayout._root == null)
|
|
return;
|
|
flexLayout._root.JustifyContent = (Flex.Justify)(FlexJustify)newValue;
|
|
flexLayout.InvalidateLayout();
|
|
}
|
|
|
|
static void OnAlignContentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
var flexLayout = bindable as FlexLayout;
|
|
if (flexLayout._root == null)
|
|
return;
|
|
flexLayout._root.AlignContent = (Flex.AlignContent)(FlexAlignContent)newValue;
|
|
flexLayout.InvalidateLayout();
|
|
}
|
|
|
|
static void OnAlignItemsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
var flexLayout = (FlexLayout)bindable;
|
|
if (flexLayout._root == null)
|
|
return;
|
|
flexLayout._root.AlignItems = (Flex.AlignItems)(FlexAlignItems)newValue;
|
|
flexLayout.InvalidateLayout();
|
|
}
|
|
|
|
static void OnPositionPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
var flexLayout = (FlexLayout)bindable;
|
|
if (flexLayout._root == null)
|
|
return;
|
|
flexLayout._root.Position = (Flex.Position)(FlexPosition)newValue;
|
|
flexLayout.InvalidateLayout();
|
|
}
|
|
|
|
static void OnWrapPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
var flexLayout = bindable as FlexLayout;
|
|
if (flexLayout._root == null)
|
|
return;
|
|
flexLayout._root.Wrap = (Flex.Wrap)(FlexWrap)newValue;
|
|
flexLayout.InvalidateLayout();
|
|
}
|
|
|
|
Flex.Item _root;
|
|
//this should only be used in unitTests. layout creation will normally happen on OnParentSet
|
|
internal override void OnIsPlatformEnabledChanged()
|
|
{
|
|
base.OnIsPlatformEnabledChanged();
|
|
if (IsPlatformEnabled && _root == null)
|
|
PopulateLayout();
|
|
else if(!IsPlatformEnabled && _root != null)
|
|
ClearLayout();
|
|
}
|
|
|
|
protected override void OnParentSet()
|
|
{
|
|
base.OnParentSet();
|
|
if (Parent != null && _root == null)
|
|
PopulateLayout();
|
|
else if (Parent == null && _root != null)
|
|
ClearLayout();
|
|
}
|
|
|
|
void PopulateLayout()
|
|
{
|
|
InitLayoutProperties(_root = new Flex.Item());
|
|
foreach (var child in Children)
|
|
AddChild(child);
|
|
}
|
|
|
|
void InitLayoutProperties(Flex.Item item)
|
|
{
|
|
var values = GetValues(AlignContentProperty,
|
|
AlignItemsProperty,
|
|
DirectionProperty,
|
|
JustifyContentProperty,
|
|
WrapProperty);
|
|
item.AlignContent = (Flex.AlignContent)(FlexAlignContent)values[0];
|
|
item.AlignItems = (Flex.AlignItems)(FlexAlignItems)values[1];
|
|
item.Direction = (Flex.Direction)(FlexDirection)values[2];
|
|
item.JustifyContent = (Flex.Justify)(FlexJustify)values[3];
|
|
item.Wrap = (Flex.Wrap)(FlexWrap)values[4];
|
|
}
|
|
|
|
void ClearLayout()
|
|
{
|
|
foreach (var child in Children)
|
|
RemoveChild(child);
|
|
_root = null;
|
|
}
|
|
|
|
protected override void OnAdded(View view)
|
|
{
|
|
AddChild(view);
|
|
view.PropertyChanged += OnChildPropertyChanged;
|
|
base.OnAdded(view);
|
|
}
|
|
|
|
protected override void OnRemoved(View view)
|
|
{
|
|
view.PropertyChanged -= OnChildPropertyChanged;
|
|
RemoveChild(view);
|
|
base.OnRemoved(view);
|
|
}
|
|
|
|
void AddChild(View view)
|
|
{
|
|
if (_root == null)
|
|
return;
|
|
var item = (view as FlexLayout)?._root ?? new Flex.Item();
|
|
InitItemProperties(view, item);
|
|
if (!(view is FlexLayout)) { //inner layouts don't get measured
|
|
item.SelfSizing = (Flex.Item it, ref float w, ref float h) => {
|
|
var sizeConstrains = item.GetConstraints();
|
|
sizeConstrains.Width = (_measuring && sizeConstrains.Width == 0) ? double.PositiveInfinity : sizeConstrains.Width;
|
|
sizeConstrains.Height = (_measuring && sizeConstrains.Height == 0) ? double.PositiveInfinity : sizeConstrains.Height;
|
|
var request = view.Measure(sizeConstrains.Width, sizeConstrains.Height).Request;
|
|
w = (float)request.Width;
|
|
h = (float)request.Height;
|
|
};
|
|
}
|
|
|
|
_root.InsertAt(Children.IndexOf(view), item);
|
|
SetFlexItem(view, item);
|
|
}
|
|
|
|
void InitItemProperties(View view, Flex.Item item)
|
|
{
|
|
var values = view.GetValues(OrderProperty,
|
|
GrowProperty,
|
|
ShrinkProperty,
|
|
BasisProperty,
|
|
AlignSelfProperty,
|
|
MarginProperty,
|
|
WidthRequestProperty,
|
|
HeightRequestProperty,
|
|
IsVisibleProperty);
|
|
item.Order = (int)values[0];
|
|
item.Grow = (float)values[1];
|
|
item.Shrink = (float)values[2];
|
|
item.Basis = ((FlexBasis)values[3]).ToFlexBasis();
|
|
item.AlignSelf = (Flex.AlignSelf)(FlexAlignSelf)values[4];
|
|
item.MarginLeft = (float)((Thickness)values[5]).Left;
|
|
item.MarginTop = (float)((Thickness)values[5]).Top;
|
|
item.MarginRight = (float)((Thickness)values[5]).Right;
|
|
item.MarginBottom = (float)((Thickness)values[5]).Bottom;
|
|
item.Width = (double)values[6] < 0 ? float.NaN : (float)(double)values[6];
|
|
item.Height = (double)values[7] < 0 ? float.NaN : (float)(double)values[7];
|
|
item.IsVisible = (bool)values[8];
|
|
if (view is FlexLayout) {
|
|
var padding = view.GetValue(PaddingProperty);
|
|
item.PaddingLeft = (float)((Thickness)padding).Left;
|
|
item.PaddingTop = (float)((Thickness)padding).Top;
|
|
item.PaddingRight = (float)((Thickness)padding).Right;
|
|
item.PaddingBottom = (float)((Thickness)padding).Bottom;
|
|
}
|
|
}
|
|
|
|
void RemoveChild(View view)
|
|
{
|
|
if (_root == null)
|
|
return;
|
|
var item = GetFlexItem(view);
|
|
_root.Remove(item);
|
|
view.ClearValue(FlexItemProperty);
|
|
}
|
|
|
|
void OnChildPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if ( e.PropertyName == WidthRequestProperty.PropertyName
|
|
|| e.PropertyName == HeightRequestProperty.PropertyName) {
|
|
var item = (sender as FlexLayout)?._root ?? GetFlexItem((BindableObject)sender);
|
|
item.Width = ((View)sender).WidthRequest < 0 ? float.NaN : (float)((View)sender).WidthRequest;
|
|
item.Height = ((View)sender).HeightRequest < 0 ? float.NaN : (float)((View)sender).HeightRequest;
|
|
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
|
return;
|
|
}
|
|
|
|
if (e.PropertyName == MarginProperty.PropertyName) {
|
|
var item = (sender as FlexLayout)?._root ?? GetFlexItem((BindableObject)sender);
|
|
if (item == null)
|
|
return;
|
|
var margin = (Thickness)((View)sender).GetValue(MarginProperty);
|
|
item.MarginLeft = (float)margin.Left;
|
|
item.MarginTop = (float)margin.Top;
|
|
item.MarginRight = (float)margin.Right;
|
|
item.MarginBottom = (float)margin.Bottom;
|
|
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
|
return;
|
|
}
|
|
|
|
if (e.PropertyName == PaddingProperty.PropertyName) {
|
|
var item = (sender as FlexLayout)?._root ?? GetFlexItem((BindableObject)sender);
|
|
if (item == null)
|
|
return;
|
|
var padding = (Thickness)((View)sender).GetValue(PaddingProperty);
|
|
item.PaddingLeft = (float)padding.Left;
|
|
item.PaddingTop = (float)padding.Top;
|
|
item.PaddingRight = (float)padding.Right;
|
|
item.PaddingBottom = (float)padding.Bottom;
|
|
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
|
return;
|
|
}
|
|
|
|
if (e.PropertyName == IsVisibleProperty.PropertyName) {
|
|
var item = (sender as FlexLayout)?._root ?? GetFlexItem((BindableObject)sender);
|
|
if (item == null)
|
|
return;
|
|
item.IsVisible = (bool)((View)sender).GetValue(IsVisibleProperty);
|
|
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
|
return;
|
|
}
|
|
}
|
|
|
|
protected override void LayoutChildren(double x, double y, double width, double height)
|
|
{
|
|
if (_root == null)
|
|
return;
|
|
|
|
Layout(x, y, width, height);
|
|
foreach (var child in Children) {
|
|
var frame = GetFlexItem(child).GetFrame();
|
|
if (double.IsNaN(frame.X)
|
|
|| double.IsNaN(frame.Y)
|
|
|| double.IsNaN(frame.Width)
|
|
|| double.IsNaN(frame.Height))
|
|
throw new Exception("something is deeply wrong");
|
|
child.Layout(GetFlexItem(child).GetFrame());
|
|
}
|
|
}
|
|
|
|
bool _measuring;
|
|
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
|
|
{
|
|
//All of this is a HACK as X.Flex doesn't supports measuring
|
|
if (!double.IsPositiveInfinity(widthConstraint) && !double.IsPositiveInfinity(heightConstraint))
|
|
return new SizeRequest(new Size(widthConstraint, heightConstraint));
|
|
|
|
_measuring = true;
|
|
//1. Set Shrink to 0, set align-self to start (to avoid stretching)
|
|
// Set Image.Aspect to Fill to get the value we expect in measuring
|
|
foreach (var child in Children) {
|
|
var item = GetFlexItem(child);
|
|
item.Shrink = 0;
|
|
item.AlignSelf = Flex.AlignSelf.Start;
|
|
}
|
|
Layout(0, 0, widthConstraint, heightConstraint);
|
|
|
|
//2. look at the children location
|
|
if (double.IsPositiveInfinity(widthConstraint)) {
|
|
widthConstraint = 0;
|
|
foreach (var item in _root)
|
|
widthConstraint = Math.Max(widthConstraint, item.Frame[0] + item.Frame[2] + item.MarginRight);
|
|
}
|
|
if (double.IsPositiveInfinity(heightConstraint)) {
|
|
heightConstraint = 0;
|
|
foreach (var item in _root)
|
|
heightConstraint = Math.Max(heightConstraint, item.Frame[1] + item.Frame[3] + item.MarginBottom);
|
|
}
|
|
|
|
//3. reset Shrink, algin-self, and image.aspect
|
|
foreach (var child in Children) {
|
|
var item = GetFlexItem(child);
|
|
item.Shrink = (float)child.GetValue(ShrinkProperty);
|
|
item.AlignSelf = (Flex.AlignSelf)(FlexAlignSelf)child.GetValue(AlignSelfProperty);
|
|
}
|
|
_measuring = false;
|
|
return new SizeRequest(new Size(widthConstraint, heightConstraint));
|
|
}
|
|
|
|
void Layout(double x, double y, double width, double height)
|
|
{
|
|
if (_root.Parent != null) //Layout is only computed at root level
|
|
return;
|
|
_root.Left = (float)x;
|
|
_root.Top = (float)y;
|
|
_root.Width = !double.IsPositiveInfinity((width)) ? (float)width : 0;
|
|
_root.Height = !double.IsPositiveInfinity((height)) ? (float)height : 0;
|
|
_root.Layout();
|
|
}
|
|
}
|
|
|
|
static class FlexExtensions
|
|
{
|
|
public static int IndexOf(this Flex.Item parent, Flex.Item child)
|
|
{
|
|
var index = -1;
|
|
foreach (var it in parent) {
|
|
index++;
|
|
if (it == child)
|
|
return index;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public static void Remove(this Flex.Item parent, Flex.Item child)
|
|
{
|
|
var index = parent.IndexOf(child);
|
|
if (index < 0)
|
|
return;
|
|
parent.RemoveAt((uint)index);
|
|
}
|
|
|
|
public static Rectangle GetFrame(this Flex.Item item)
|
|
{
|
|
return new Rectangle(item.Frame[0], item.Frame[1], item.Frame[2], item.Frame[3]);
|
|
}
|
|
|
|
public static Size GetConstraints(this Flex.Item item)
|
|
{
|
|
var widthConstraint = -1d;
|
|
var heightConstraint = -1d;
|
|
var parent = item.Parent;
|
|
do
|
|
{
|
|
if (parent == null)
|
|
break;
|
|
if (widthConstraint < 0 && !float.IsNaN(parent.Width))
|
|
widthConstraint = (double)parent.Width;
|
|
if (heightConstraint < 0 && !float.IsNaN(parent.Height))
|
|
heightConstraint = (double)parent.Height;
|
|
parent = parent.Parent;
|
|
} while (widthConstraint < 0 || heightConstraint < 0);
|
|
return new Size(widthConstraint, heightConstraint);
|
|
}
|
|
|
|
public static Flex.Basis ToFlexBasis(this FlexBasis basis)
|
|
{
|
|
if (basis.IsAuto)
|
|
return Flex.Basis.Auto;
|
|
if (basis.IsRelative)
|
|
return new Flex.Basis(basis.Length, isRelative: true);
|
|
return new Flex.Basis(basis.Length, isRelative: false);
|
|
}
|
|
}
|
|
} |