259 lines
10 KiB
C#
259 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Microsoft.Maui.Controls;
|
|
using SkiaSharp;
|
|
|
|
namespace Microsoft.Maui.Platform;
|
|
|
|
public class SkiaFlexLayout : SkiaLayoutView
|
|
{
|
|
public static readonly BindableProperty DirectionProperty = BindableProperty.Create(
|
|
nameof(Direction), typeof(FlexDirection), typeof(SkiaFlexLayout), FlexDirection.Row,
|
|
BindingMode.TwoWay, propertyChanged: (b, o, n) => ((SkiaFlexLayout)b).InvalidateMeasure());
|
|
|
|
public static readonly BindableProperty WrapProperty = BindableProperty.Create(
|
|
nameof(Wrap), typeof(FlexWrap), typeof(SkiaFlexLayout), FlexWrap.NoWrap,
|
|
BindingMode.TwoWay, propertyChanged: (b, o, n) => ((SkiaFlexLayout)b).InvalidateMeasure());
|
|
|
|
public static readonly BindableProperty JustifyContentProperty = BindableProperty.Create(
|
|
nameof(JustifyContent), typeof(FlexJustify), typeof(SkiaFlexLayout), FlexJustify.Start,
|
|
BindingMode.TwoWay, propertyChanged: (b, o, n) => ((SkiaFlexLayout)b).InvalidateMeasure());
|
|
|
|
public static readonly BindableProperty AlignItemsProperty = BindableProperty.Create(
|
|
nameof(AlignItems), typeof(FlexAlignItems), typeof(SkiaFlexLayout), FlexAlignItems.Stretch,
|
|
BindingMode.TwoWay, propertyChanged: (b, o, n) => ((SkiaFlexLayout)b).InvalidateMeasure());
|
|
|
|
public static readonly BindableProperty AlignContentProperty = BindableProperty.Create(
|
|
nameof(AlignContent), typeof(FlexAlignContent), typeof(SkiaFlexLayout), FlexAlignContent.Stretch,
|
|
BindingMode.TwoWay, propertyChanged: (b, o, n) => ((SkiaFlexLayout)b).InvalidateMeasure());
|
|
|
|
public static readonly BindableProperty OrderProperty = BindableProperty.CreateAttached(
|
|
"Order", typeof(int), typeof(SkiaFlexLayout), 0, BindingMode.TwoWay);
|
|
|
|
public static readonly BindableProperty GrowProperty = BindableProperty.CreateAttached(
|
|
"Grow", typeof(float), typeof(SkiaFlexLayout), 0f, BindingMode.TwoWay);
|
|
|
|
public static readonly BindableProperty ShrinkProperty = BindableProperty.CreateAttached(
|
|
"Shrink", typeof(float), typeof(SkiaFlexLayout), 1f, BindingMode.TwoWay);
|
|
|
|
public static readonly BindableProperty BasisProperty = BindableProperty.CreateAttached(
|
|
"Basis", typeof(FlexBasis), typeof(SkiaFlexLayout), FlexBasis.Auto, BindingMode.TwoWay);
|
|
|
|
public static readonly BindableProperty AlignSelfProperty = BindableProperty.CreateAttached(
|
|
"AlignSelf", typeof(FlexAlignSelf), typeof(SkiaFlexLayout), FlexAlignSelf.Auto, BindingMode.TwoWay);
|
|
|
|
public FlexDirection Direction
|
|
{
|
|
get => (FlexDirection)GetValue(DirectionProperty);
|
|
set => SetValue(DirectionProperty, value);
|
|
}
|
|
|
|
public FlexWrap Wrap
|
|
{
|
|
get => (FlexWrap)GetValue(WrapProperty);
|
|
set => SetValue(WrapProperty, value);
|
|
}
|
|
|
|
public FlexJustify JustifyContent
|
|
{
|
|
get => (FlexJustify)GetValue(JustifyContentProperty);
|
|
set => SetValue(JustifyContentProperty, value);
|
|
}
|
|
|
|
public FlexAlignItems AlignItems
|
|
{
|
|
get => (FlexAlignItems)GetValue(AlignItemsProperty);
|
|
set => SetValue(AlignItemsProperty, value);
|
|
}
|
|
|
|
public FlexAlignContent AlignContent
|
|
{
|
|
get => (FlexAlignContent)GetValue(AlignContentProperty);
|
|
set => SetValue(AlignContentProperty, value);
|
|
}
|
|
|
|
public static int GetOrder(SkiaView view) => (int)view.GetValue(OrderProperty);
|
|
public static void SetOrder(SkiaView view, int value) => view.SetValue(OrderProperty, value);
|
|
|
|
public static float GetGrow(SkiaView view) => (float)view.GetValue(GrowProperty);
|
|
public static void SetGrow(SkiaView view, float value) => view.SetValue(GrowProperty, value);
|
|
|
|
public static float GetShrink(SkiaView view) => (float)view.GetValue(ShrinkProperty);
|
|
public static void SetShrink(SkiaView view, float value) => view.SetValue(ShrinkProperty, value);
|
|
|
|
public static FlexBasis GetBasis(SkiaView view) => (FlexBasis)view.GetValue(BasisProperty);
|
|
public static void SetBasis(SkiaView view, FlexBasis value) => view.SetValue(BasisProperty, value);
|
|
|
|
public static FlexAlignSelf GetAlignSelf(SkiaView view) => (FlexAlignSelf)view.GetValue(AlignSelfProperty);
|
|
public static void SetAlignSelf(SkiaView view, FlexAlignSelf value) => view.SetValue(AlignSelfProperty, value);
|
|
|
|
protected override Size MeasureOverride(Size availableSize)
|
|
{
|
|
bool isRow = Direction == FlexDirection.Row || Direction == FlexDirection.RowReverse;
|
|
float totalMain = 0f;
|
|
float maxCross = 0f;
|
|
|
|
var sizeAvailable = new Size(availableSize.Width, availableSize.Height);
|
|
|
|
foreach (var child in Children)
|
|
{
|
|
if (!child.IsVisible)
|
|
continue;
|
|
|
|
var childSize = child.Measure(sizeAvailable);
|
|
if (isRow)
|
|
{
|
|
totalMain += (float)childSize.Width;
|
|
maxCross = Math.Max(maxCross, (float)childSize.Height);
|
|
}
|
|
else
|
|
{
|
|
totalMain += (float)childSize.Height;
|
|
maxCross = Math.Max(maxCross, (float)childSize.Width);
|
|
}
|
|
}
|
|
|
|
return isRow ? new Size(totalMain, maxCross) : new Size(maxCross, totalMain);
|
|
}
|
|
|
|
protected override Rect ArrangeOverride(Rect bounds)
|
|
{
|
|
if (Children.Count == 0)
|
|
return bounds;
|
|
|
|
bool isRow = Direction == FlexDirection.Row || Direction == FlexDirection.RowReverse;
|
|
bool isReverse = Direction == FlexDirection.RowReverse || Direction == FlexDirection.ColumnReverse;
|
|
|
|
var orderedChildren = Children.Where(c => c.IsVisible).OrderBy(c => GetOrder(c)).ToList();
|
|
if (orderedChildren.Count == 0)
|
|
return bounds;
|
|
|
|
float mainSize = isRow ? (float)bounds.Width : (float)bounds.Height;
|
|
float crossSize = isRow ? (float)bounds.Height : (float)bounds.Width;
|
|
|
|
var childInfos = new List<(SkiaView child, Size size, float grow, float shrink)>();
|
|
float totalBasis = 0f;
|
|
float totalGrow = 0f;
|
|
float totalShrink = 0f;
|
|
|
|
foreach (var child in orderedChildren)
|
|
{
|
|
var basis = GetBasis(child);
|
|
float grow = GetGrow(child);
|
|
float shrink = GetShrink(child);
|
|
|
|
Size size;
|
|
if (basis.IsAuto)
|
|
{
|
|
size = child.Measure(new Size(bounds.Width, bounds.Height));
|
|
}
|
|
else
|
|
{
|
|
float length = basis.Length;
|
|
size = isRow
|
|
? child.Measure(new Size(length, bounds.Height))
|
|
: child.Measure(new Size(bounds.Width, length));
|
|
}
|
|
|
|
childInfos.Add((child, size, grow, shrink));
|
|
totalBasis += isRow ? (float)size.Width : (float)size.Height;
|
|
totalGrow += grow;
|
|
totalShrink += shrink;
|
|
}
|
|
|
|
float freeSpace = mainSize - totalBasis;
|
|
|
|
var resolvedSizes = new List<(SkiaView child, float mainSize, float crossSize)>();
|
|
foreach (var (child, size, grow, shrink) in childInfos)
|
|
{
|
|
float childMainSize = isRow ? (float)size.Width : (float)size.Height;
|
|
float childCrossSize = isRow ? (float)size.Height : (float)size.Width;
|
|
|
|
if (freeSpace > 0f && totalGrow > 0f)
|
|
{
|
|
childMainSize += freeSpace * (grow / totalGrow);
|
|
}
|
|
else if (freeSpace < 0f && totalShrink > 0f)
|
|
{
|
|
childMainSize += freeSpace * (shrink / totalShrink);
|
|
}
|
|
|
|
resolvedSizes.Add((child, Math.Max(0f, childMainSize), childCrossSize));
|
|
}
|
|
|
|
float usedSpace = resolvedSizes.Sum(s => s.mainSize);
|
|
float remainingSpace = Math.Max(0f, mainSize - usedSpace);
|
|
|
|
float position = isRow ? (float)bounds.Left : (float)bounds.Top;
|
|
float spacing = 0f;
|
|
|
|
switch (JustifyContent)
|
|
{
|
|
case FlexJustify.Center:
|
|
position += remainingSpace / 2f;
|
|
break;
|
|
case FlexJustify.End:
|
|
position += remainingSpace;
|
|
break;
|
|
case FlexJustify.SpaceBetween:
|
|
if (resolvedSizes.Count > 1)
|
|
spacing = remainingSpace / (resolvedSizes.Count - 1);
|
|
break;
|
|
case FlexJustify.SpaceAround:
|
|
if (resolvedSizes.Count > 0)
|
|
{
|
|
spacing = remainingSpace / resolvedSizes.Count;
|
|
position += spacing / 2f;
|
|
}
|
|
break;
|
|
case FlexJustify.SpaceEvenly:
|
|
if (resolvedSizes.Count > 0)
|
|
{
|
|
spacing = remainingSpace / (resolvedSizes.Count + 1);
|
|
position += spacing;
|
|
}
|
|
break;
|
|
}
|
|
|
|
var items = isReverse ? resolvedSizes.AsEnumerable().Reverse() : resolvedSizes;
|
|
|
|
foreach (var (child, childMainSize, childCrossSize) in items)
|
|
{
|
|
var alignSelf = GetAlignSelf(child);
|
|
var effectiveAlign = alignSelf == FlexAlignSelf.Auto ? AlignItems : (FlexAlignItems)alignSelf;
|
|
|
|
float crossPos = isRow ? (float)bounds.Top : (float)bounds.Left;
|
|
float finalCrossSize = childCrossSize;
|
|
|
|
switch (effectiveAlign)
|
|
{
|
|
case FlexAlignItems.End:
|
|
crossPos = (isRow ? (float)bounds.Bottom : (float)bounds.Right) - finalCrossSize;
|
|
break;
|
|
case FlexAlignItems.Center:
|
|
crossPos += (crossSize - finalCrossSize) / 2f;
|
|
break;
|
|
case FlexAlignItems.Stretch:
|
|
finalCrossSize = crossSize;
|
|
break;
|
|
}
|
|
|
|
Rect childBounds;
|
|
if (isRow)
|
|
{
|
|
childBounds = new Rect(position, crossPos, childMainSize, finalCrossSize);
|
|
}
|
|
else
|
|
{
|
|
childBounds = new Rect(crossPos, position, finalCrossSize, childMainSize);
|
|
}
|
|
|
|
child.Arrange(childBounds);
|
|
position += childMainSize + spacing;
|
|
}
|
|
|
|
return bounds;
|
|
}
|
|
}
|