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 { public struct LayoutEnumerator : IEnumerator { 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 positions = new(); private readonly List 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 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 GetEnumerator() { return new LayoutEnumerator(this); } public IEnumerator GetEnumerator(int startIndex) { return new LayoutEnumerator(startIndex, this); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }