Files
maui-linux/Views/SkiaFlexLayout.cs

259 lines
10 KiB
C#
Raw Normal View History

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);
2026-01-17 05:22:37 +00:00
protected override Size MeasureOverride(Size availableSize)
{
bool isRow = Direction == FlexDirection.Row || Direction == FlexDirection.RowReverse;
float totalMain = 0f;
float maxCross = 0f;
2026-01-17 05:22:37 +00:00
var sizeAvailable = new Size(availableSize.Width, availableSize.Height);
foreach (var child in Children)
{
if (!child.IsVisible)
continue;
2026-01-17 05:22:37 +00:00
var childSize = child.Measure(sizeAvailable);
if (isRow)
{
2026-01-17 05:22:37 +00:00
totalMain += (float)childSize.Width;
maxCross = Math.Max(maxCross, (float)childSize.Height);
}
else
{
2026-01-17 05:22:37 +00:00
totalMain += (float)childSize.Height;
maxCross = Math.Max(maxCross, (float)childSize.Width);
}
}
2026-01-17 05:22:37 +00:00
return isRow ? new Size(totalMain, maxCross) : new Size(maxCross, totalMain);
}
2026-01-17 05:22:37 +00:00
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;
2026-01-17 05:22:37 +00:00
float mainSize = isRow ? (float)bounds.Width : (float)bounds.Height;
float crossSize = isRow ? (float)bounds.Height : (float)bounds.Width;
2026-01-17 05:22:37 +00:00
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);
2026-01-17 05:22:37 +00:00
Size size;
if (basis.IsAuto)
{
2026-01-17 05:22:37 +00:00
size = child.Measure(new Size(bounds.Width, bounds.Height));
}
else
{
float length = basis.Length;
size = isRow
2026-01-17 05:22:37 +00:00
? child.Measure(new Size(length, bounds.Height))
: child.Measure(new Size(bounds.Width, length));
}
childInfos.Add((child, size, grow, shrink));
2026-01-17 05:22:37 +00:00
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)
{
2026-01-17 05:22:37 +00:00
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);
2026-01-17 05:22:37 +00:00
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;
2026-01-17 05:22:37 +00:00
float crossPos = isRow ? (float)bounds.Top : (float)bounds.Left;
float finalCrossSize = childCrossSize;
switch (effectiveAlign)
{
case FlexAlignItems.End:
2026-01-17 05:22:37 +00:00
crossPos = (isRow ? (float)bounds.Bottom : (float)bounds.Right) - finalCrossSize;
break;
case FlexAlignItems.Center:
crossPos += (crossSize - finalCrossSize) / 2f;
break;
case FlexAlignItems.Stretch:
finalCrossSize = crossSize;
break;
}
2026-01-17 05:22:37 +00:00
Rect childBounds;
if (isRow)
{
2026-01-17 05:22:37 +00:00
childBounds = new Rect(position, crossPos, childMainSize, finalCrossSize);
}
else
{
2026-01-17 05:22:37 +00:00
childBounds = new Rect(crossPos, position, finalCrossSize, childMainSize);
}
child.Arrange(childBounds);
position += childMainSize + spacing;
}
return bounds;
}
}