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 : ListViewBase where TComponent : ListViewItem { [SerializeField] protected List customItems = new List(); public ObservableList dataSource; [SerializeField] public TComponent DefaultItem; protected List components = new List(); protected List componentsCache = new List(); private Dictionary> callbacksEnter = new Dictionary>(); private Dictionary> callbacksExit = new Dictionary>(); [SerializeField] [FormerlySerializedAs("Sort")] private bool sort = true; private Func, IEnumerable> 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 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 DataSource { get { if (dataSource == null) { dataSource = new ObservableList(customItems); dataSource.OnChange += UpdateItems; customItems = null; } return dataSource; } set { SetNewItems(value); SetScrollValue(0f); } } [Obsolete("Use DataSource instead.")] public new List Items { get { return new List(DataSource); } set { SetNewItems(new ObservableList(value)); SetScrollValue(0f); } } public TItem SelectedItem { get { if (base.SelectedIndex == -1) { return default(TItem); } return DataSource[base.SelectedIndex]; } } public List SelectedItems { get { return base.SelectedIndicies.Convert(GetDataItem); } } public bool Sort { get { return sort; } set { sort = value; if (Sort && isStartedListViewCustom) { UpdateItems(); } } } public Func, IEnumerable> 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(); if (component != null) { component.OnResize.RemoveListener(SetNeedResize); } scrollRect.onValueChanged.RemoveListener(OnScrollRectUpdate); } scrollRect = value; if (scrollRect != null) { ResizeListener resizeListener = scrollRect.GetComponent() ?? scrollRect.gameObject.AddComponent(); 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(); 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(); 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.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())); 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 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 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 second = components.Convert(base.GetComponentIndex); int[] new_visible_range = Enumerable.Range(topHiddenItems, visibleItems).ToArray(); List list = new_visible_range.Except(second).ToList(); Stack components_to_change = new Stack(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 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 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 func) { base.ForEachComponent(func); func(DefaultItem); ((IEnumerable)componentsCache).Select((Func)((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; } } } }