Files
2026-02-21 16:45:37 +08:00

1023 lines
23 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using EasyLayout;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace UIWidgets
{
public class ListViewCustom<TComponent, TItem> : ListViewBase where TComponent : ListViewItem
{
[SerializeField]
protected List<TItem> customItems = new List<TItem>();
public ObservableList<TItem> dataSource;
[SerializeField]
public TComponent DefaultItem;
protected List<TComponent> components = new List<TComponent>();
protected List<TComponent> componentsCache = new List<TComponent>();
private Dictionary<int, UnityAction<PointerEventData>> callbacksEnter = new Dictionary<int, UnityAction<PointerEventData>>();
private Dictionary<int, UnityAction<PointerEventData>> callbacksExit = new Dictionary<int, UnityAction<PointerEventData>>();
[SerializeField]
[FormerlySerializedAs("Sort")]
private bool sort = true;
private Func<IEnumerable<TItem>, IEnumerable<TItem>> sortFunc;
public ListViewCustomEvent OnSelectObject = new ListViewCustomEvent();
public ListViewCustomEvent OnDeselectObject = new ListViewCustomEvent();
public ListViewCustomEvent OnPointerEnterObject = new ListViewCustomEvent();
public ListViewCustomEvent OnPointerExitObject = new ListViewCustomEvent();
[SerializeField]
private Color defaultBackgroundColor = Color.white;
[SerializeField]
private Color defaultColor = Color.black;
[SerializeField]
public Color HighlightedBackgroundColor = new Color(203f, 230f, 244f, 255f);
[SerializeField]
public Color HighlightedColor = Color.black;
[SerializeField]
private Color selectedBackgroundColor = new Color(53f, 83f, 227f, 255f);
[SerializeField]
private Color selectedColor = Color.black;
[SerializeField]
protected ScrollRect scrollRect;
[Tooltip("Minimal height of item")]
[SerializeField]
protected float itemHeight;
[Tooltip("Minimal width of item")]
[SerializeField]
protected float itemWidth;
protected float scrollHeight;
protected float scrollWidth;
protected int maxVisibleItems;
protected int visibleItems;
protected int topHiddenItems;
protected int bottomHiddenItems;
[SerializeField]
private ListViewDirection direction = ListViewDirection.Vertical;
protected bool setContentSizeFitter = true;
[NonSerialized]
private bool isStartedListViewCustom;
protected LayoutGroup layout;
protected List<TItem> SelectedItemsCache;
protected ILayoutBridge LayoutBridge;
[SerializeField]
protected bool KeepSelection = true;
public UnityEvent OnStartScrolling = new UnityEvent();
public UnityEvent OnEndScrolling = new UnityEvent();
public float EndScrollDelay = 0.3f;
private bool scrolling;
private float lastScrollingTime;
private bool needResize;
public ObservableList<TItem> DataSource
{
get
{
if (dataSource == null)
{
dataSource = new ObservableList<TItem>(customItems);
dataSource.OnChange += UpdateItems;
customItems = null;
}
return dataSource;
}
set
{
SetNewItems(value);
SetScrollValue(0f);
}
}
[Obsolete("Use DataSource instead.")]
public new List<TItem> Items
{
get
{
return new List<TItem>(DataSource);
}
set
{
SetNewItems(new ObservableList<TItem>(value));
SetScrollValue(0f);
}
}
public TItem SelectedItem
{
get
{
if (base.SelectedIndex == -1)
{
return default(TItem);
}
return DataSource[base.SelectedIndex];
}
}
public List<TItem> SelectedItems
{
get
{
return base.SelectedIndicies.Convert(GetDataItem);
}
}
public bool Sort
{
get
{
return sort;
}
set
{
sort = value;
if (Sort && isStartedListViewCustom)
{
UpdateItems();
}
}
}
public Func<IEnumerable<TItem>, IEnumerable<TItem>> SortFunc
{
get
{
return sortFunc;
}
set
{
sortFunc = value;
if (Sort && isStartedListViewCustom)
{
UpdateItems();
}
}
}
public Color DefaultBackgroundColor
{
get
{
return defaultBackgroundColor;
}
set
{
defaultBackgroundColor = value;
UpdateColors();
}
}
public Color DefaultColor
{
get
{
return defaultColor;
}
set
{
DefaultColor = value;
UpdateColors();
}
}
public Color SelectedBackgroundColor
{
get
{
return selectedBackgroundColor;
}
set
{
selectedBackgroundColor = value;
UpdateColors();
}
}
public Color SelectedColor
{
get
{
return selectedColor;
}
set
{
selectedColor = value;
UpdateColors();
}
}
public ScrollRect ScrollRect
{
get
{
return scrollRect;
}
set
{
if (scrollRect != null)
{
ResizeListener component = scrollRect.GetComponent<ResizeListener>();
if (component != null)
{
component.OnResize.RemoveListener(SetNeedResize);
}
scrollRect.onValueChanged.RemoveListener(OnScrollRectUpdate);
}
scrollRect = value;
if (scrollRect != null)
{
ResizeListener resizeListener = scrollRect.GetComponent<ResizeListener>() ?? scrollRect.gameObject.AddComponent<ResizeListener>();
resizeListener.OnResize.AddListener(SetNeedResize);
scrollRect.onValueChanged.AddListener(OnScrollRectUpdate);
}
}
}
public ListViewDirection Direction
{
get
{
return direction;
}
set
{
direction = value;
if ((bool)scrollRect)
{
scrollRect.horizontal = IsHorizontal();
scrollRect.vertical = !IsHorizontal();
}
if (CanOptimize() && layout is global::EasyLayout.EasyLayout)
{
LayoutBridge.IsHorizontal = IsHorizontal();
if (isStartedListViewCustom)
{
CalculateMaxVisibleItems();
}
}
if (isStartedListViewCustom)
{
UpdateView();
}
}
}
public global::EasyLayout.EasyLayout Layout
{
get
{
return layout as global::EasyLayout.EasyLayout;
}
}
protected virtual void Awake()
{
Start();
}
public override void Start()
{
if (isStartedListViewCustom)
{
return;
}
base.Start();
base.Items = new List<ListViewItem>();
SelectedItemsCache = SelectedItems;
SetItemIndicies = false;
DestroyGameObjects = false;
if (DefaultItem == null)
{
throw new NullReferenceException(string.Format("DefaultItem is null. Set component of type {0} to DefaultItem.", typeof(TComponent).FullName));
}
DefaultItem.gameObject.SetActive(true);
if (CanOptimize())
{
ScrollRect = scrollRect;
RectTransform rectTransform = scrollRect.transform as RectTransform;
scrollHeight = rectTransform.rect.height;
scrollWidth = rectTransform.rect.width;
layout = Container.GetComponent<LayoutGroup>();
if (layout is global::EasyLayout.EasyLayout)
{
LayoutBridge = new EasyLayoutBridge(layout as global::EasyLayout.EasyLayout, DefaultItem.transform as RectTransform);
LayoutBridge.IsHorizontal = IsHorizontal();
}
else if (layout is HorizontalOrVerticalLayoutGroup)
{
LayoutBridge = new StandardLayoutBridge(layout as HorizontalOrVerticalLayoutGroup, DefaultItem.transform as RectTransform);
}
CalculateItemSize();
CalculateMaxVisibleItems();
ResizeListener resizeListener = scrollRect.gameObject.AddComponent<ResizeListener>();
resizeListener.OnResize.AddListener(SetNeedResize);
}
DefaultItem.gameObject.SetActive(false);
Direction = direction;
UpdateItems();
OnSelect.AddListener(OnSelectCallback);
OnDeselect.AddListener(OnDeselectCallback);
isStartedListViewCustom = true;
}
protected TItem GetDataItem(int index)
{
return DataSource[index];
}
protected virtual void CalculateItemSize()
{
if (LayoutBridge != null)
{
Vector2 itemSize = LayoutBridge.GetItemSize();
if (itemHeight == 0f)
{
itemHeight = itemSize.y;
}
if (itemWidth == 0f)
{
itemWidth = itemSize.x;
}
}
}
protected bool IsHorizontal()
{
return direction == ListViewDirection.Horizontal;
}
protected virtual void CalculateMaxVisibleItems()
{
if (IsHorizontal())
{
maxVisibleItems = Mathf.CeilToInt(scrollWidth / itemWidth);
}
else
{
maxVisibleItems = Mathf.CeilToInt(scrollHeight / itemHeight);
}
maxVisibleItems = Mathf.Max(maxVisibleItems, 1) + 1;
}
public virtual void Resize()
{
needResize = false;
RectTransform rectTransform = scrollRect.transform as RectTransform;
scrollHeight = rectTransform.rect.height;
scrollWidth = rectTransform.rect.width;
itemHeight = 0f;
itemWidth = 0f;
CalculateItemSize();
CalculateMaxVisibleItems();
UpdateView();
components.Sort(ComponentsComparer);
components.ForEach(base.SetComponentAsLastSibling);
}
protected virtual bool CanOptimize()
{
bool flag = scrollRect != null;
LayoutGroup layoutGroup = ((!(Container != null)) ? null : (layout ?? Container.GetComponent<LayoutGroup>()));
bool flag2 = (bool)layoutGroup && (layoutGroup is global::EasyLayout.EasyLayout || layoutGroup is HorizontalOrVerticalLayoutGroup);
return flag && flag2;
}
private void OnSelectCallback(int index, ListViewItem item)
{
if (SelectedItemsCache != null)
{
SelectedItemsCache.Add(DataSource[index]);
}
OnSelectObject.Invoke(index);
if (item != null)
{
SelectColoring(item);
}
}
private void OnDeselectCallback(int index, ListViewItem item)
{
if (SelectedItemsCache != null)
{
SelectedItemsCache.Remove(DataSource[index]);
}
OnDeselectObject.Invoke(index);
if (item != null)
{
DefaultColoring(item);
}
}
private void OnPointerEnterCallback(ListViewItem item)
{
OnPointerEnterObject.Invoke(item.Index);
if (!IsSelected(item.Index))
{
HighlightColoring(item);
}
}
private void OnPointerExitCallback(ListViewItem item)
{
OnPointerExitObject.Invoke(item.Index);
if (!IsSelected(item.Index))
{
DefaultColoring(item);
}
}
public override void UpdateItems()
{
SetNewItems(DataSource);
}
public override void Clear()
{
DataSource.Clear();
SetScrollValue(0f);
}
public virtual int Add(TItem item)
{
if (item == null)
{
throw new ArgumentNullException("item", "Item is null.");
}
DataSource.Add(item);
return DataSource.IndexOf(item);
}
public virtual int Remove(TItem item)
{
int num = DataSource.IndexOf(item);
if (num == -1)
{
return num;
}
DataSource.RemoveAt(num);
return num;
}
public virtual void Remove(int index)
{
DataSource.RemoveAt(index);
}
protected void SetScrollValue(float value, bool callScrollUpdate = true)
{
if (scrollRect.content == null)
{
return;
}
Vector2 anchoredPosition = scrollRect.content.anchoredPosition;
Vector2 anchoredPosition2 = ((!IsHorizontal()) ? new Vector2(anchoredPosition.x, value) : new Vector2(value, anchoredPosition.y));
bool flag = IsHorizontal() && Mathf.Abs(anchoredPosition.x - anchoredPosition2.x) > 0.1f;
bool flag2 = !IsHorizontal() && Mathf.Abs(anchoredPosition.y - anchoredPosition2.y) > 0.1f;
if (flag || flag2)
{
scrollRect.content.anchoredPosition = anchoredPosition2;
if (callScrollUpdate)
{
ScrollUpdate();
}
}
}
protected float GetScrollValue()
{
Vector2 anchoredPosition = scrollRect.content.anchoredPosition;
return Mathf.Max(0f, (!IsHorizontal()) ? anchoredPosition.y : (0f - anchoredPosition.x));
}
protected override void ScrollTo(int index)
{
if (CanOptimize())
{
int firstVisibleIndex = GetFirstVisibleIndex(true);
int lastVisibleIndex = GetLastVisibleIndex(true);
if (firstVisibleIndex > index)
{
SetScrollValue(GetItemPosition(index));
}
else if (lastVisibleIndex < index)
{
SetScrollValue(GetItemPositionBottom(index));
}
}
}
protected virtual float GetItemPosition(int index)
{
return (float)index * GetItemSize();
}
protected virtual float GetItemPositionBottom(int index)
{
return GetItemPosition(index) + GetItemSize() - LayoutBridge.GetSpacing() + LayoutBridge.GetMargin() - GetScrollSize();
}
protected void RemoveCallbacks()
{
base.Items.ForEach(RemoveCallback);
}
protected void AddCallbacks()
{
base.Items.ForEach(AddCallback);
}
protected virtual void RemoveCallback(ListViewItem item, int index)
{
if (callbacksEnter.ContainsKey(index))
{
if (item != null)
{
item.onPointerEnter.RemoveListener(callbacksEnter[index]);
}
callbacksEnter.Remove(index);
}
if (callbacksExit.ContainsKey(index))
{
if (item != null)
{
item.onPointerExit.RemoveListener(callbacksExit[index]);
}
callbacksExit.Remove(index);
}
}
protected virtual void AddCallback(ListViewItem item, int index)
{
callbacksEnter.Add(index, delegate
{
OnPointerEnterCallback(item);
});
callbacksExit.Add(index, delegate
{
OnPointerExitCallback(item);
});
item.onPointerEnter.AddListener(callbacksEnter[index]);
item.onPointerExit.AddListener(callbacksExit[index]);
}
public int Set(TItem item, bool allowDuplicate = true)
{
int num;
if (!allowDuplicate)
{
num = DataSource.IndexOf(item);
if (num == -1)
{
num = Add(item);
}
}
else
{
num = Add(item);
}
Select(num);
return num;
}
protected virtual void SetData(TComponent component, TItem item)
{
}
protected void UpdateComponentsCount()
{
components.RemoveAll(IsNullComponent);
if (components.Count != visibleItems)
{
if (components.Count < visibleItems)
{
componentsCache.RemoveAll(IsNullComponent);
Enumerable.Range(0, visibleItems - components.Count).ForEach(AddComponent);
}
else
{
IOrderedEnumerable<TComponent> orderedEnumerable = components.GetRange(visibleItems, components.Count - visibleItems).OrderByDescending(base.GetComponentIndex);
orderedEnumerable.ForEach(DeactivateComponent);
componentsCache.AddRange(orderedEnumerable);
components.RemoveRange(visibleItems, components.Count - visibleItems);
}
base.Items = components.Convert((Converter<TComponent, ListViewItem>)((TComponent x) => x));
}
}
private bool IsNullComponent(TComponent component)
{
return component == null;
}
private void AddComponent(int index)
{
TComponent val;
if (componentsCache.Count > 0)
{
val = componentsCache[componentsCache.Count - 1];
componentsCache.RemoveAt(componentsCache.Count - 1);
}
else
{
val = UnityEngine.Object.Instantiate(DefaultItem);
Utilites.FixInstantiated(DefaultItem, val);
}
val.Index = -1;
val.transform.SetAsLastSibling();
val.gameObject.SetActive(true);
components.Add(val);
}
private void DeactivateComponent(TComponent component)
{
RemoveCallback(component, component.Index);
if (component != null)
{
component.MovedToCache();
component.Index = -1;
component.gameObject.SetActive(false);
}
}
protected float GetItemSize()
{
return (!IsHorizontal()) ? (itemHeight + LayoutBridge.GetSpacing()) : (itemWidth + LayoutBridge.GetSpacing());
}
protected float GetScrollSize()
{
return (!IsHorizontal()) ? scrollHeight : scrollWidth;
}
protected virtual int GetLastVisibleIndex(bool strict = false)
{
float num = GetScrollValue() + GetScrollSize();
int num2 = ((!strict) ? Mathf.CeilToInt(num / GetItemSize()) : Mathf.FloorToInt(num / GetItemSize()));
return num2 - 1;
}
protected virtual int GetFirstVisibleIndex(bool strict = false)
{
int b = ((!strict) ? Mathf.FloorToInt(GetScrollValue() / GetItemSize()) : Mathf.CeilToInt(GetScrollValue() / GetItemSize()));
b = Mathf.Max(0, b);
if (strict)
{
return b;
}
return Mathf.Min(b, Mathf.Max(0, DataSource.Count - visibleItems));
}
protected virtual void ScrollUpdate()
{
int num = topHiddenItems;
topHiddenItems = GetFirstVisibleIndex();
bottomHiddenItems = Mathf.Max(0, DataSource.Count - visibleItems - topHiddenItems);
if (num != topHiddenItems)
{
if (num == topHiddenItems + 1)
{
TComponent val = components[components.Count - 1];
components.RemoveAt(components.Count - 1);
components.Insert(0, val);
val.transform.SetAsFirstSibling();
val.Index = topHiddenItems;
SetData(val, DataSource[topHiddenItems]);
Coloring(val);
}
else if (num == topHiddenItems - 1)
{
TComponent val2 = components[0];
components.RemoveAt(0);
components.Add(val2);
val2.transform.SetAsLastSibling();
val2.Index = topHiddenItems + visibleItems - 1;
SetData(val2, DataSource[topHiddenItems + visibleItems - 1]);
Coloring(val2);
}
else
{
List<int> second = components.Convert(base.GetComponentIndex);
int[] new_visible_range = Enumerable.Range(topHiddenItems, visibleItems).ToArray();
List<int> list = new_visible_range.Except(second).ToList();
Stack<TComponent> components_to_change = new Stack<TComponent>(components.Where((TComponent x) => !new_visible_range.Contains(x.Index)));
list.ForEach(delegate(int index)
{
TComponent val3 = components_to_change.Pop();
val3.Index = index;
SetData(val3, DataSource[index]);
Coloring(val3);
});
components.Sort(ComponentsComparer);
components.ForEach(base.SetComponentAsLastSibling);
}
}
if (LayoutBridge != null)
{
LayoutBridge.SetFiller(CalculateTopFillerSize(), CalculateBottomFillerSize());
LayoutBridge.UpdateLayout();
}
}
protected int ComponentsComparer(TComponent x, TComponent y)
{
return x.Index.CompareTo(y.Index);
}
protected virtual void OnScrollRectUpdate(Vector2 position)
{
StartScrolling();
ScrollUpdate();
}
protected void UpdateView()
{
RemoveCallbacks();
if (CanOptimize() && DataSource.Count > 0)
{
visibleItems = ((maxVisibleItems >= DataSource.Count) ? DataSource.Count : maxVisibleItems);
}
else
{
visibleItems = DataSource.Count;
}
if (CanOptimize())
{
topHiddenItems = GetFirstVisibleIndex();
if (topHiddenItems > DataSource.Count - 1)
{
topHiddenItems = Mathf.Max(0, DataSource.Count - 2);
}
if (topHiddenItems + visibleItems > DataSource.Count)
{
visibleItems = DataSource.Count - topHiddenItems;
}
bottomHiddenItems = Mathf.Max(0, DataSource.Count - visibleItems - topHiddenItems);
}
else
{
topHiddenItems = 0;
bottomHiddenItems = DataSource.Count() - visibleItems;
}
UpdateComponentsCount();
int[] indicies = Enumerable.Range(topHiddenItems, visibleItems).ToArray();
components.ForEach(delegate(TComponent x, int i)
{
x.Index = indicies[i];
SetData(x, DataSource[indicies[i]]);
Coloring(x);
});
AddCallbacks();
if (LayoutBridge != null)
{
LayoutBridge.SetFiller(CalculateTopFillerSize(), CalculateBottomFillerSize());
LayoutBridge.UpdateLayout();
}
if (ScrollRect != null)
{
float num = ((DataSource.Count != 0) ? Mathf.Max(0f, GetItemPositionBottom(DataSource.Count - 1)) : 0f);
if (GetScrollValue() > num)
{
SetScrollValue(num);
}
}
}
private bool IndexNotFound(int index)
{
return index == -1;
}
protected virtual void SetNewItems(ObservableList<TItem> newItems)
{
DataSource.OnChange -= UpdateItems;
if (Sort && SortFunc != null)
{
newItems.BeginUpdate();
TItem[] array = SortFunc(newItems).ToArray();
newItems.Clear();
newItems.AddRange(array);
newItems.EndUpdate();
}
SilentDeselect(base.SelectedIndicies);
List<int> list = SelectedItemsCache.Convert(newItems.IndexOf);
list.RemoveAll(IndexNotFound);
dataSource = newItems;
if (KeepSelection)
{
SilentSelect(list);
}
SelectedItemsCache = SelectedItems;
UpdateView();
DataSource.OnChange += UpdateItems;
}
protected virtual float CalculateBottomFillerSize()
{
if (bottomHiddenItems == 0)
{
return 0f;
}
return Mathf.Max(0f, (float)bottomHiddenItems * GetItemSize() - LayoutBridge.GetSpacing());
}
protected virtual float CalculateTopFillerSize()
{
if (topHiddenItems == 0)
{
return 0f;
}
return Mathf.Max(0f, (float)topHiddenItems * GetItemSize() - LayoutBridge.GetSpacing());
}
public override bool IsValid(int index)
{
return index >= 0 && index < DataSource.Count;
}
protected override void Coloring(ListViewItem component)
{
if (!(component == null))
{
if (base.SelectedIndicies.Contains(component.Index))
{
SelectColoring(component);
}
else
{
DefaultColoring(component);
}
}
}
protected override void HighlightColoring(ListViewItem component)
{
if (!IsSelected(component.Index))
{
HighlightColoring(component as TComponent);
}
}
protected virtual void HighlightColoring(TComponent component)
{
component.Background.color = HighlightedBackgroundColor;
}
protected virtual void SelectColoring(ListViewItem component)
{
if (!(component == null))
{
SelectColoring(component as TComponent);
}
}
protected virtual void SelectColoring(TComponent component)
{
component.Background.color = SelectedBackgroundColor;
}
protected virtual void DefaultColoring(ListViewItem component)
{
if (!(component == null))
{
DefaultColoring(component as TComponent);
}
}
protected virtual void DefaultColoring(TComponent component)
{
component.Background.color = DefaultBackgroundColor;
}
private void UpdateColors()
{
components.ForEach(delegate(TComponent x)
{
Coloring(x);
});
}
protected override void OnDestroy()
{
layout = null;
LayoutBridge = null;
OnSelect.RemoveListener(OnSelectCallback);
OnDeselect.RemoveListener(OnDeselectCallback);
ScrollRect = null;
RemoveCallbacks();
base.OnDestroy();
}
public override void ForEachComponent(Action<ListViewItem> func)
{
base.ForEachComponent(func);
func(DefaultItem);
((IEnumerable<TComponent>)componentsCache).Select((Func<TComponent, ListViewItem>)((TComponent x) => x)).ForEach(func);
}
private void Update()
{
if (needResize)
{
Resize();
}
if (IsEndScrolling())
{
EndScrolling();
}
}
public virtual void OnEnable()
{
StartCoroutine(ForceRebuild());
}
public IEnumerator ForceRebuild()
{
yield return null;
ForEachComponent(MarkLayoutForRebuild);
}
public void MarkLayoutForRebuild(ListViewItem item)
{
LayoutRebuilder.MarkLayoutForRebuild(item.transform as RectTransform);
}
private void StartScrolling()
{
lastScrollingTime = Time.time;
if (!scrolling)
{
scrolling = true;
OnStartScrolling.Invoke();
}
}
private bool IsEndScrolling()
{
if (!scrolling)
{
return false;
}
return lastScrollingTime + EndScrollDelay <= Time.time;
}
private void EndScrolling()
{
scrolling = false;
OnEndScrolling.Invoke();
}
public void SetNeedResize()
{
if (CanOptimize())
{
needResize = true;
}
}
}
}