canto/Assets/Scripts/UI/Elements/Layout/DynamicLayout.cs
2025-08-06 12:51:11 -04:00

176 lines
4.1 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace KitsuneCafe.UI
{
public record LayoutItem(float Position, float Size)
{
public float Min => Position;
public float Max => Position + Size;
}
public class DynamicLayout : ILayout, IEnumerable<LayoutItem>
{
public struct LayoutEnumerator : IEnumerator<LayoutItem>
{
public readonly int Start;
public int Index;
private readonly DynamicLayout layout;
public LayoutEnumerator(int index, DynamicLayout layout)
{
Start = index;
Index = index;
this.layout = layout;
}
public LayoutEnumerator(DynamicLayout layout) : this(0, layout) { }
public readonly LayoutItem Current => new(
layout.positions[Index - 1],
layout.sizes[Index - 1]
);
readonly object IEnumerator.Current => Current;
public readonly void Dispose() { }
public bool MoveNext()
{
Index += 1;
return Index < layout.positions.Count;
}
public void Reset()
{
Index = Start;
}
}
public float DefaultItemSize { get; set; }
public float GutterSize { get; set; }
public FlowDirection Direction { get; set; }
public float ContentSize { get; private set; }
private readonly List<float> positions = new();
private readonly List<float> sizes = new();
public DynamicLayout(FlowDirection direction, float defaultItemSize = 22, float gutterSize = 0)
{
Direction = direction;
GutterSize = gutterSize;
DefaultItemSize = defaultItemSize;
}
public float GetItemPosition(int index)
{
if (index > 0)
{
return GetItemPosition(index - 1) + GetItemSize(index - 1) + GutterSize;
}
return 0;
}
public float GetItemSize(int index)
{
if (index >= 0 && index < sizes.Count)
{
return sizes[index];
}
return DefaultItemSize;
}
private float LastPositionOrDefault()
{
return positions.Count > 0 ? positions[^1] : DefaultItemSize + GutterSize;
}
public void Update(int itemCount, VisualElement container)
{
while (positions.Count < itemCount)
{
var last = LastPositionOrDefault();
positions.Add(last + DefaultItemSize + GutterSize);
sizes.Add(DefaultItemSize);
}
if (positions.Count > itemCount)
{
positions.RemoveRange(itemCount, positions.Count - itemCount);
sizes.RemoveRange(itemCount, sizes.Count - itemCount);
}
ContentSize = LastPositionOrDefault();
}
private float CalculatePositions(int startingIndex = 0, float initialPosition = 0)
{
var position = initialPosition;
for (int i = startingIndex; i < positions.Count; i++)
{
positions[i] = position;
position += GetItemSize(i) + GutterSize;
}
return ContentSize = position;
}
public void SetMeasuredItemSize(int index, float size)
{
if (index < sizes.Count && Mathf.Approximately(sizes[index], size))
{
return;
}
sizes[index] = size;
var position = index > 0 ? positions[index - 1] + sizes[index - 1] + GutterSize : 0;
CalculatePositions(index, position);
}
public int GetFirstVisibleIndex(float scrollOffset)
{
if (scrollOffset <= 0) { return 0; }
for (int i = 0; i < positions.Count; i++)
{
if (positions[i] >= scrollOffset)
{
return i;
}
}
return positions.Count - 1;
}
public IEnumerable<LayoutItem> Enumerate(int startIndex = 0)
{
var len = positions.Count;
for (int i = startIndex; i < len; i++)
{
yield return new LayoutItem(positions[i], sizes[i]);
}
}
public IEnumerator<LayoutItem> GetEnumerator()
{
return new LayoutEnumerator(this);
}
public IEnumerator<LayoutItem> GetEnumerator(int startIndex)
{
return new LayoutEnumerator(startIndex, this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}