canto/Assets/Scripts/UI/Elements/Layout/DynamicLayout.cs

139 lines
3.2 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace KitsuneCafe.UI
{
public class DynamicLayout : ILayout
{
public float DefaultItemSize { get; set; }
public float GutterSize { get; set; }
public FlowDirection Direction { get; set; }
public float ContentSize { get; private set; }
public int Count { get; set; }
public int Buffer { get; 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);
}
Count = itemCount;
ContentSize = LastPositionOrDefault();
}
private float CalculatePositions(int startingIndex = 0, float initialPosition = 0)
{
var position = initialPosition;
for (int i = startingIndex; i < 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 offset)
{
if (offset <= 0) { return 0; }
for (int i = 0; i < Count; i++)
{
if (positions[i] >= offset)
{
return i;
}
}
return positions.Count - 1;
}
public Range GetVisibleRange(float offset, float size)
{
var halfBuffer = Mathf.CeilToInt(Buffer / 2);
var first = Math.Max(
GetFirstVisibleIndex(offset) - halfBuffer,
0
);
var count = 1;
var position = GetItemPosition(first);
var width = position + size;
for (int i = first; i < Count; i++)
{
var item = GetItemPosition(i);
if (item > width) { break; }
count += 1;
}
count += halfBuffer;
return new Range(first, Math.Min(Count, first + count));
}
}
}