canto/Assets/Scripts/UI/Elements/RecycleView/RecycleView.cs

250 lines
6.4 KiB
C#

using System;
using Unity.Properties;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace KitsuneCafe.UI
{
public enum FlowDirection
{
Vertical = 0,
Horizontal = 1
}
public abstract record CollectionItemSize;
[Serializable]
public record FixedItemSize(float Size) : CollectionItemSize;
[Serializable]
public record DynamicItemSize() : CollectionItemSize
{
public static readonly DynamicItemSize Instance = new();
}
public class CollectionItemSizeConverter : UxmlAttributeConverter<CollectionItemSize>
{
public override CollectionItemSize FromString(string value)
{
return value switch
{
"()" => new DynamicItemSize(),
var v => new FixedItemSize(int.Parse(v))
};
}
public override string ToString(CollectionItemSize value)
{
return value switch
{
DynamicItemSize => "()",
var v => v.ToString(),
};
}
}
[UxmlElement]
public partial class RecycleView : VisualElement, IDisposable
{
public const string RecycleViewBaseClass = "kitsunecafe__recycle-view";
public const string RecycleViewScrollViewClass = "kitsunecafe__recycle-view--scroll-view";
public const string RecycleViewContentContainerClass = "kitsunecafe__recycle-view--content-container";
public const string RecycleViewDynamicItemClass = "kitsunecafe__recycle-view--dynamic-item";
private FlowDirection direction;
[UxmlAttribute, CreateProperty]
public FlowDirection Direction
{
get => direction;
set
{
if (direction != value)
{
direction = value;
virtualizationController.Layout = CreateLayout();
UpdateDirection();
}
}
}
private bool isDynamicSize = false;
[UxmlAttribute, CreateProperty]
public bool IsDynamicSize
{
get => isDynamicSize;
set
{
if (isDynamicSize != value)
{
isDynamicSize = value;
virtualizationController.Layout = CreateLayout();
}
}
}
private float itemSize = 22;
[UxmlAttribute, CreateProperty, Delayed, Tooltip("In a dynamic layout, this is used for initial calculations")]
public float ItemSize
{
get => itemSize;
set
{
if (itemSize != value)
{
itemSize = value;
if (virtualizationController.Layout is FixedLayout fixedLayout)
{
fixedLayout.ItemSize = value;
}
else if (virtualizationController.Layout is DynamicLayout dynamicLayout)
{
dynamicLayout.DefaultItemSize = value;
}
}
}
}
private int bufferCount = 8;
[UxmlAttribute, CreateProperty, Delayed]
public int BufferCount
{
get => bufferCount;
set
{
if (bufferCount != value)
{
bufferCount = value;
if (virtualizationController.Layout is FixedLayout fixedLayout)
{
fixedLayout.Buffer = value;
}
else if (virtualizationController.Layout is DynamicLayout dynamicLayout)
{
dynamicLayout.Buffer = value;
}
}
}
}
private float gutter = 0;
[UxmlAttribute, CreateProperty, Delayed]
public float Gutter
{
get => gutter;
set
{
if (gutter != value)
{
gutter = value;
if (virtualizationController.Layout is FixedLayout fixedLayout)
{
fixedLayout.GutterSize = value;
}
else if (virtualizationController.Layout is DynamicLayout dynamicLayout)
{
dynamicLayout.GutterSize = value;
}
}
}
}
[CreateProperty]
public ICollectionDataSource DataSource
{
get => virtualizationController?.DataSource;
set
{
if (virtualizationController != null)
{
virtualizationController.DataSource = value;
virtualizationController.Setup();
}
}
}
private readonly ScrollView scrollView;
public override VisualElement contentContainer => scrollView.contentContainer;
private readonly IRecycleVirtualizationContainer virtualizationController;
public RecycleView()
{
AddToClassList(RecycleViewBaseClass);
scrollView = new ScrollView();
scrollView.AddToClassList(RecycleViewScrollViewClass);
hierarchy.Add(scrollView);
contentContainer.AddToClassList(RecycleViewContentContainerClass);
virtualizationController = new RecycleVirtualizationController
{
Container = contentContainer
};
RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
}
private ILayout CreateLayout()
{
return isDynamicSize switch
{
true => new DynamicLayout(Direction, itemSize) { Buffer = bufferCount, GutterSize = gutter },
false => new FixedLayout(Direction, itemSize) { Buffer = bufferCount, GutterSize = gutter }
};
}
private void UpdateDirection()
{
switch (direction)
{
case FlowDirection.Vertical:
scrollView.mode = ScrollViewMode.Vertical;
scrollView.verticalScrollerVisibility = ScrollerVisibility.Auto;
scrollView.horizontalScrollerVisibility = ScrollerVisibility.Hidden;
scrollView.horizontalScroller.valueChanged -= virtualizationController.OnScrolled;
scrollView.verticalScroller.valueChanged += virtualizationController.OnScrolled;
break;
case FlowDirection.Horizontal:
scrollView.mode = ScrollViewMode.Horizontal;
scrollView.verticalScrollerVisibility = ScrollerVisibility.Hidden;
scrollView.horizontalScrollerVisibility = ScrollerVisibility.Auto;
scrollView.verticalScroller.valueChanged -= virtualizationController.OnScrolled;
scrollView.horizontalScroller.valueChanged += virtualizationController.OnScrolled;
break;
}
}
public void OnGeometryChanged(GeometryChangedEvent evt)
{
if (evt.newRect.size != evt.oldRect.size && virtualizationController != null)
{
virtualizationController.OnParentSizeChanged(evt.newRect.size);
}
}
public void Dispose()
{
if (virtualizationController is IDisposable disposable)
{
disposable.Dispose();
}
}
~RecycleView()
{
Dispose();
}
}
}