using UnityEngine; using UnityEngine.UI; using UnityEngine.Events; using UnityEngine.EventSystems; using UnityEngine.Serialization; using System.Collections.Generic; using System; using System.Linq; namespace UIWidgets { /// /// ListView direction. /// Direction to scroll items. Used for optimization or TileView. /// Horizontal - Horizontal. /// Vertical - Vertical. /// public enum ListViewDirection { Horizontal = 0, Vertical = 1, } /// /// Custom ListView event. /// [Serializable] public class ListViewCustomEvent : UnityEvent { } /// /// Base class for custom ListViews. /// public class ListViewCustom : ListViewBase where TComponent : ListViewItem { /// /// The items. /// [SerializeField] protected List customItems = new List(); //[SerializeField] //[HideInInspector] protected ObservableList dataSource; /// /// Gets or sets the data source. /// /// The data source. public virtual ObservableList DataSource { get { if (dataSource==null) { #pragma warning disable 0618 dataSource = new ObservableList(customItems); dataSource.OnChange += UpdateItems; customItems = null; #pragma warning restore 0618 } return dataSource; } set { SetNewItems(value); SetScrollValue(0f); } } /// /// Gets or sets the items. /// /// Items. [Obsolete("Use DataSource instead.")] new public List Items { get { return new List(DataSource); } set { SetNewItems(new ObservableList(value)); SetScrollValue(0f); } } /// /// The default item. /// [SerializeField] public TComponent DefaultItem; /// /// The components list. /// protected List components = new List(); /// /// The components cache list. /// protected List componentsCache = new List(); Dictionary> callbacksEnter = new Dictionary>(); Dictionary> callbacksExit = new Dictionary>(); /// /// Gets the selected item. /// /// The selected item. public TItem SelectedItem { get { if (SelectedIndex==-1) { return default(TItem); } return DataSource[SelectedIndex]; } } /// /// Gets the selected items. /// /// The selected items. public List SelectedItems { get { return SelectedIndicies.Convert(GetDataItem); } } [SerializeField] [FormerlySerializedAs("Sort")] bool sort = true; /// /// Sort items. /// Advice to use DataSource.Comparison instead Sort and SortFunc. /// public bool Sort { get { return sort; } set { sort = value; if (sort && isStartedListViewCustom) { UpdateItems(); } } } Func,IEnumerable> sortFunc; /// /// Sort function. /// Advice to use DataSource.Comparison instead Sort and SortFunc. /// public Func, IEnumerable> SortFunc { get { return sortFunc; } set { sortFunc = value; if (Sort && isStartedListViewCustom) { UpdateItems(); } } } /// /// What to do when the object selected. /// public ListViewCustomEvent OnSelectObject = new ListViewCustomEvent(); /// /// What to do when the object deselected. /// public ListViewCustomEvent OnDeselectObject = new ListViewCustomEvent(); /// /// What to do when the event system send a pointer enter Event. /// public ListViewCustomEvent OnPointerEnterObject = new ListViewCustomEvent(); /// /// What to do when the event system send a pointer exit Event. /// public ListViewCustomEvent OnPointerExitObject = new ListViewCustomEvent(); [SerializeField] Color defaultBackgroundColor = Color.white; [SerializeField] Color defaultColor = Color.black; /// /// Default background color. /// public Color DefaultBackgroundColor { get { return defaultBackgroundColor; } set { defaultBackgroundColor = value; UpdateColors(); } } /// /// Default text color. /// public Color DefaultColor { get { return defaultColor; } set { DefaultColor = value; UpdateColors(); } } /// /// Color of background on pointer over. /// [SerializeField] public Color HighlightedBackgroundColor = new Color(203, 230, 244, 255); /// /// Color of text on pointer text. /// [SerializeField] public Color HighlightedColor = Color.black; [SerializeField] Color selectedBackgroundColor = new Color(53, 83, 227, 255); [SerializeField] Color selectedColor = Color.black; /// /// Background color of selected item. /// public Color SelectedBackgroundColor { get { return selectedBackgroundColor; } set { selectedBackgroundColor = value; UpdateColors(); } } /// /// Text color of selected item. /// public Color SelectedColor { get { return selectedColor; } set { selectedColor = value; UpdateColors(); } } /// /// The ScrollRect. /// [SerializeField] protected ScrollRect scrollRect; /// /// Gets or sets the ScrollRect. /// /// The ScrollRect. public ScrollRect ScrollRect { get { return scrollRect; } set { if (scrollRect!=null) { var r = scrollRect.GetComponent(); if (r!=null) { r.OnResize.RemoveListener(SetNeedResize); } scrollRect.onValueChanged.RemoveListener(OnScrollRectUpdate); } scrollRect = value; if (scrollRect!=null) { var resizeListener = scrollRect.GetComponent(); if (resizeListener==null) { resizeListener = scrollRect.gameObject.AddComponent(); } resizeListener.OnResize.AddListener(SetNeedResize); scrollRect.onValueChanged.AddListener(OnScrollRectUpdate); } } } /// /// The height of the DefaultItem. /// [SerializeField] [Tooltip("Minimal height of item")] protected float itemHeight; /// /// The width of the DefaultItem. /// [SerializeField] [Tooltip("Minimal width of item")] protected float itemWidth; /// /// The height of the ScrollRect. /// protected float scrollHeight; /// /// The width of the ScrollRect. /// protected float scrollWidth; /// /// Count of visible items. /// protected int maxVisibleItems; /// /// Count of visible items. /// protected int visibleItems; /// /// Count of hidden items by top filler. /// protected int topHiddenItems; /// /// Count of hidden items by bottom filler. /// protected int bottomHiddenItems; /// /// The direction. /// [SerializeField] protected ListViewDirection direction = ListViewDirection.Vertical; bool _setContentSizeFitter = true; /// /// The set ContentSizeFitter parametres according direction. /// protected bool setContentSizeFitter { get { return _setContentSizeFitter; } set { _setContentSizeFitter = value; if (LayoutBridge!=null) { LayoutBridge.UpdateContentSizeFitter = value; } } } /// /// Gets or sets the direction. /// /// The direction. public ListViewDirection Direction { get { return direction; } set { SetDirection(value, isStartedListViewCustom); } } /// /// Awake this instance. /// protected virtual void Awake() { Start(); } [System.NonSerialized] bool isStartedListViewCustom = false; /// /// The layout. /// protected LayoutGroup layout; /// /// Gets the layout. /// /// The layout. public EasyLayout.EasyLayout Layout { get { return layout as EasyLayout.EasyLayout; } } /// /// Selected items cache (to keep valid selected indicies with updates). /// protected List SelectedItemsCache; /// /// LayoutBridge. /// protected ILayoutBridge LayoutBridge; /// /// Start this instance. /// public override void Start() { if (isStartedListViewCustom) { return ; } isStartedListViewCustom = true; 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; var scroll_rect_transform = scrollRect.transform as RectTransform; scrollHeight = scroll_rect_transform.rect.height; scrollWidth = scroll_rect_transform.rect.width; layout = Container.GetComponent(); if (layout is EasyLayout.EasyLayout) { LayoutBridge = new EasyLayoutBridge(layout as EasyLayout.EasyLayout, DefaultItem.transform as RectTransform, setContentSizeFitter); LayoutBridge.IsHorizontal = IsHorizontal(); } else if (layout is HorizontalOrVerticalLayoutGroup) { LayoutBridge = new StandardLayoutBridge(layout as HorizontalOrVerticalLayoutGroup, DefaultItem.transform as RectTransform, setContentSizeFitter); } CalculateItemSize(); CalculateMaxVisibleItems(); var resizeListener = scrollRect.GetComponent(); if (resizeListener==null) { resizeListener = scrollRect.gameObject.AddComponent(); } resizeListener.OnResize.AddListener(SetNeedResize); } DefaultItem.gameObject.SetActive(false); SetDirection(direction, false); UpdateItems(); OnSelect.AddListener(OnSelectCallback); OnDeselect.AddListener(OnDeselectCallback); } /// /// Sets the direction. /// /// New direction. /// If set to true is inited. protected virtual void SetDirection(ListViewDirection newDirection, bool isInited = true) { direction = newDirection; if (scrollRect) { scrollRect.horizontal = IsHorizontal(); scrollRect.vertical = !IsHorizontal(); } (Container as RectTransform).anchoredPosition = Vector2.zero; if (CanOptimize() && (layout is EasyLayout.EasyLayout)) { LayoutBridge.IsHorizontal = IsHorizontal(); if (isInited) { CalculateMaxVisibleItems(); } } if (isInited) { UpdateView(); } } /// /// Determines whether is sort enabled. /// /// true if is sort enabled; otherwise, false. public bool IsSortEnabled() { if (DataSource.Comparison!=null) { return true; } return Sort && SortFunc!=null; } /// /// Gets the index of the nearest item. /// /// The nearest index. /// Event data. public virtual int GetNearestIndex(PointerEventData eventData) { if (IsSortEnabled()) { return -1; } Vector2 point; var rectTransform = Container as RectTransform; if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out point)) { return DataSource.Count; } var rect = rectTransform.rect; if (!rect.Contains(point)) { return DataSource.Count; } return GetNearestIndex(point); } /// /// Gets the index of the nearest item. /// /// The nearest item index. /// Point. public virtual int GetNearestIndex(Vector2 point) { if (IsSortEnabled()) { return -1; } var pos = IsHorizontal() ? point.x : -point.y; var index = Mathf.RoundToInt(pos / GetItemSize()); return Mathf.Min(index, DataSource.Count); } /// /// Gets the spacing between items. /// /// The item spacing. public override float GetItemSpacing() { return LayoutBridge.GetSpacing(); } /// /// Gets the item. /// /// The item. /// Index. protected TItem GetDataItem(int index) { return DataSource[index]; } /// /// Calculates the size of the item. /// protected virtual void CalculateItemSize() { if (LayoutBridge==null) { return ; } var size = LayoutBridge.GetItemSize(); if (itemHeight==0f) { itemHeight = size.y; } if (itemWidth==0f) { itemWidth = size.x; } } /// /// Determines whether this instance is horizontal. /// /// true if this instance is horizontal; otherwise, false. public override bool IsHorizontal() { return direction==ListViewDirection.Horizontal; } /// /// Calculates the max count of visible items. /// protected virtual void CalculateMaxVisibleItems() { if (IsHorizontal()) { maxVisibleItems = Mathf.CeilToInt(scrollWidth / itemWidth); } else { maxVisibleItems = Mathf.CeilToInt(scrollHeight / itemHeight); } maxVisibleItems = Mathf.Max(maxVisibleItems, 1) + 1; } /// /// Resize this instance. /// public virtual void Resize() { needResize = false; var scroll_rect_transform = scrollRect.transform as RectTransform; scrollHeight = scroll_rect_transform.rect.height; scrollWidth = scroll_rect_transform.rect.width; itemHeight = 0; itemWidth = 0; CalculateItemSize(); CalculateMaxVisibleItems(); UpdateView(); components.Sort(ComponentsComparer); components.ForEach(SetComponentAsLastSibling); } /// /// Determines whether this instance can optimize. /// /// true if this instance can optimize; otherwise, false. protected virtual bool CanOptimize() { var scrollRectSpecified = scrollRect!=null; var containerSpecified = Container!=null; var currentLayout = containerSpecified ? ((layout!=null) ? layout : Container.GetComponent()) : null; var validLayout = currentLayout ? ((currentLayout is EasyLayout.EasyLayout) || (currentLayout is HorizontalOrVerticalLayoutGroup)) : false; return scrollRectSpecified && validLayout; } /// /// Raises the select callback event. /// /// Index. /// Item. void OnSelectCallback(int index, ListViewItem item) { if (SelectedItemsCache!=null) { SelectedItemsCache.Add(DataSource[index]); } OnSelectObject.Invoke(index); if (item!=null) { SelectColoring(item); } } /// /// Raises the deselect callback event. /// /// Index. /// Item. void OnDeselectCallback(int index, ListViewItem item) { if (SelectedItemsCache!=null) { SelectedItemsCache.Remove(DataSource[index]); } OnDeselectObject.Invoke(index); if (item!=null) { DefaultColoring(item); } } /// /// Raises the pointer enter callback event. /// /// Item. void OnPointerEnterCallback(ListViewItem item) { OnPointerEnterObject.Invoke(item.Index); if (!IsSelected(item.Index)) { HighlightColoring(item); } } /// /// Raises the pointer exit callback event. /// /// Item. void OnPointerExitCallback(ListViewItem item) { OnPointerExitObject.Invoke(item.Index); if (!IsSelected(item.Index)) { DefaultColoring(item); } } /// /// Updates thitemsms. /// public override void UpdateItems() { SetNewItems(DataSource); } /// /// Clear items of this instance. /// public override void Clear() { DataSource.Clear(); SetScrollValue(0f); } /// /// Add the specified item. /// /// Item. /// Index of added item. public virtual int Add(TItem item) { if (item==null) { throw new ArgumentNullException("item", "Item is null."); } DataSource.Add(item); return DataSource.IndexOf(item); } /// /// Remove the specified item. /// /// Item. /// Index of removed TItem. public virtual int Remove(TItem item) { var index = DataSource.IndexOf(item); if (index==-1) { return index; } DataSource.RemoveAt(index); return index; } /// /// Remove item by specifieitemsex. /// /// Index of removed item. /// Index. public virtual void Remove(int index) { DataSource.RemoveAt(index); } /// /// Sets the scroll value. /// /// Value. /// Call ScrollUpdate() if position changed. protected void SetScrollValue(float value, bool callScrollUpdate=true) { if (scrollRect.content==null) { return ; } var current_position = scrollRect.content.anchoredPosition; var new_position = IsHorizontal() ? new Vector2(value, current_position.y) : new Vector2(current_position.x, value); var diff_x = IsHorizontal() ? Mathf.Abs(current_position.x - new_position.x) > 0.1f : false; var diff_y = IsHorizontal() ? false : Mathf.Abs(current_position.y - new_position.y) > 0.1f; if (diff_x || diff_y) { scrollRect.content.anchoredPosition = new_position; if (callScrollUpdate) { ScrollUpdate(); } } } /// /// Gets the scroll value. /// /// The scroll value. protected float GetScrollValue() { var pos = scrollRect.content.anchoredPosition; return Mathf.Max(0f, (IsHorizontal()) ? -pos.x : pos.y); } /// /// Scrolls to item with specifid index. /// /// Index. public override void ScrollTo(int index) { if (!CanOptimize()) { return ; } var first_visible = GetFirstVisibleIndex(true); var last_visible = GetLastVisibleIndex(true); if (first_visible > index) { SetScrollValue(GetItemPosition(index)); } else if (last_visible < index) { SetScrollValue(GetItemPositionBottom(index)); } } /// /// Gets the item position by index. /// /// The item position. /// Index. public override float GetItemPosition(int index) { return index * GetItemSize() - GetItemSpacing(); } /// /// Gets the item bottom position by index. /// /// The item bottom position. /// Index. public virtual float GetItemPositionBottom(int index) { return GetItemPosition(index) + GetItemSize() - LayoutBridge.GetSpacing() + LayoutBridge.GetMargin() - GetScrollSize(); } /// /// Removes the callbacks. /// protected void RemoveCallbacks() { base.Items.ForEach(RemoveCallback); } /// /// Adds the callbacks. /// protected void AddCallbacks() { base.Items.ForEach(AddCallback); } /// /// Removes the callback. /// /// Item. /// Index. 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); } } /// /// Adds the callback. /// /// Item. /// Index. protected virtual void AddCallback(ListViewItem item, int index) { callbacksEnter.Add(index, ev => OnPointerEnterCallback(item)); callbacksExit.Add(index, ev => OnPointerExitCallback(item)); item.onPointerEnter.AddListener(callbacksEnter[index]); item.onPointerExit.AddListener(callbacksExit[index]); } /// /// Set the specified item. /// /// Item. /// If set to true allow duplicate. /// Index of item. public int Set(TItem item, bool allowDuplicate=true) { int index; if (!allowDuplicate) { index = DataSource.IndexOf(item); if (index==-1) { index = Add(item); } } else { index = Add(item); } Select(index); return index; } /// /// Sets component data with specified item. /// /// Component. /// Item. protected virtual void SetData(TComponent component, TItem item) { } /// /// Updates the components count. /// protected void UpdateComponentsCount() { components.RemoveAll(IsNullComponent); if (components.Count==visibleItems) { return ; } if (components.Count < visibleItems) { componentsCache.RemoveAll(IsNullComponent); Enumerable.Range(0, visibleItems - components.Count).ForEach(AddComponent); } else { var to_cache = components.GetRange(visibleItems, components.Count - visibleItems).OrderByDescending(GetComponentIndex); to_cache.ForEach(DeactivateComponent); componentsCache.AddRange(to_cache); components.RemoveRange(visibleItems, components.Count - visibleItems); } base.Items = components.Convert(x => x as ListViewItem); } bool IsNullComponent(TComponent component) { return component==null; } void AddComponent(int index) { TComponent component; if (componentsCache.Count > 0) { component = componentsCache[componentsCache.Count - 1]; componentsCache.RemoveAt(componentsCache.Count - 1); } else { component = Instantiate(DefaultItem) as TComponent; component.transform.SetParent(Container, false); Utilites.FixInstantiated(DefaultItem, component); } component.Index = -1; component.transform.SetAsLastSibling(); component.gameObject.SetActive(true); components.Add(component); } void DeactivateComponent(TComponent component) { RemoveCallback(component, component.Index); if (component!=null) { component.MovedToCache(); component.Index = -1; component.gameObject.SetActive(false); } } /// /// Gets the default width of the item. /// /// The default item width. public override float GetDefaultItemWidth() { return itemWidth; } /// /// Gets the default height of the item. /// /// The default item height. public override float GetDefaultItemHeight() { return itemHeight; } /// /// Gets the size of the item. /// /// The item size. protected float GetItemSize() { return (IsHorizontal()) ? itemWidth + LayoutBridge.GetSpacing() : itemHeight + LayoutBridge.GetSpacing(); } /// /// Gets the size of the scroll. /// /// The scroll size. protected float GetScrollSize() { return (IsHorizontal()) ? scrollWidth : scrollHeight; } /// /// Gets the last index of the visible. /// /// The last visible index. /// If set to true strict. protected virtual int GetLastVisibleIndex(bool strict=false) { var window = GetScrollValue() + GetScrollSize(); var last_visible_index = (strict) ? Mathf.FloorToInt(window / GetItemSize()) : Mathf.CeilToInt(window / GetItemSize()); return last_visible_index - 1; } /// /// Gets the first index of the visible. /// /// The first visible index. /// If set to true strict. protected virtual int GetFirstVisibleIndex(bool strict=false) { var first_visible_index = (strict) ? Mathf.CeilToInt(GetScrollValue() / GetItemSize()) : Mathf.FloorToInt(GetScrollValue() / GetItemSize()); first_visible_index = Mathf.Max(0, first_visible_index); if (strict) { return first_visible_index; } return Mathf.Min(first_visible_index, Mathf.Max(0, DataSource.Count - visibleItems)); } /// /// On ScrollUpdate. /// protected virtual void ScrollUpdate() { var oldTopHiddenItems = topHiddenItems; topHiddenItems = GetFirstVisibleIndex(); bottomHiddenItems = Mathf.Max(0, DataSource.Count - visibleItems - topHiddenItems); if (oldTopHiddenItems==topHiddenItems) { //do nothing } // optimization on +-1 item scroll else if (oldTopHiddenItems==(topHiddenItems + 1)) { var bottomComponent = components[components.Count - 1]; components.RemoveAt(components.Count - 1); components.Insert(0, bottomComponent); bottomComponent.transform.SetAsFirstSibling(); bottomComponent.Index = topHiddenItems; SetData(bottomComponent, DataSource[topHiddenItems]); Coloring(bottomComponent as ListViewItem); } else if (oldTopHiddenItems==(topHiddenItems - 1)) { var topComponent = components[0]; components.RemoveAt(0); components.Add(topComponent); topComponent.transform.SetAsLastSibling(); topComponent.Index = topHiddenItems + visibleItems - 1; SetData(topComponent, DataSource[topHiddenItems + visibleItems - 1]); Coloring(topComponent as ListViewItem); } // all other cases else { var current_visible_range = components.Convert(GetComponentIndex); var new_visible_range = Enumerable.Range(topHiddenItems, visibleItems).ToArray(); var new_indicies_to_change = new_visible_range.Except(current_visible_range).ToList(); var components_to_change = new Stack(components.Where(x => !new_visible_range.Contains(x.Index))); new_indicies_to_change.ForEach(index => { var component = components_to_change.Pop(); component.Index = index; SetData(component, DataSource[index]); Coloring(component as ListViewItem); }); components.Sort(ComponentsComparer); components.ForEach(SetComponentAsLastSibling); } if (LayoutBridge!=null) { LayoutBridge.SetFiller(CalculateTopFillerSize(), CalculateBottomFillerSize()); LayoutBridge.UpdateLayout(); } } /// /// Compare components by component index. /// /// A signed integer that indicates the relative values of x and y. /// The x coordinate. /// The y coordinate. protected int ComponentsComparer(TComponent x, TComponent y) { return x.Index.CompareTo(y.Index); } /// /// Raises the scroll rect update event. /// /// Position. protected virtual void OnScrollRectUpdate(Vector2 position) { StartScrolling(); ScrollUpdate(); } /// /// Updates the view. /// protected void UpdateView() { RemoveCallbacks(); if ((CanOptimize()) && (DataSource.Count > 0)) { visibleItems = (maxVisibleItems < DataSource.Count) ? maxVisibleItems : DataSource.Count; } 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(); var indicies = Enumerable.Range(topHiddenItems, visibleItems).ToArray(); components.ForEach((x, i) => { x.Index = indicies[i]; SetData(x, DataSource[indicies[i]]); Coloring(x as ListViewItem); }); AddCallbacks(); if (LayoutBridge!=null) { LayoutBridge.SetFiller(CalculateTopFillerSize(), CalculateBottomFillerSize()); LayoutBridge.UpdateLayout(); if (ScrollRect!=null) { var item_ends = (DataSource.Count==0) ? 0f : Mathf.Max(0f, GetItemPositionBottom(DataSource.Count - 1)); if (GetScrollValue() > item_ends) { SetScrollValue(item_ends); } } } } /// /// Keep selected items on items update. /// [SerializeField] protected bool KeepSelection = true; bool IndexNotFound(int index) { return index==-1; } /// /// Updates the items. /// /// New items. protected virtual void SetNewItems(ObservableList newItems) { //Start(); DataSource.OnChange -= UpdateItems; if (Sort && SortFunc!=null) { newItems.BeginUpdate(); var sorted = SortFunc(newItems).ToArray(); newItems.Clear(); newItems.AddRange(sorted); newItems.EndUpdate(); } SilentDeselect(SelectedIndicies); var new_selected_indicies = SelectedItemsCache.Convert(newItems.IndexOf); new_selected_indicies.RemoveAll(IndexNotFound); dataSource = newItems; if (KeepSelection) { SilentSelect(new_selected_indicies); } SelectedItemsCache = SelectedItems; UpdateView(); DataSource.OnChange += UpdateItems; } /// /// Calculates the size of the bottom filler. /// /// The bottom filler size. protected virtual float CalculateBottomFillerSize() { if (bottomHiddenItems==0) { return 0f; } return Mathf.Max(0, bottomHiddenItems * GetItemSize() - LayoutBridge.GetSpacing()); } /// /// Calculates the size of the top filler. /// /// The top filler size. protected virtual float CalculateTopFillerSize() { if (topHiddenItems==0) { return 0f; } return Mathf.Max(0, topHiddenItems * GetItemSize() - LayoutBridge.GetSpacing()); } /// /// Determines if item exists with the specified index. /// /// true if item exists with the specified index; otherwise, false. /// Index. public override bool IsValid(int index) { return (index >= 0) && (index < DataSource.Count); } /// /// Coloring the specified component. /// /// Component. protected override void Coloring(ListViewItem component) { if (component==null) { return ; } if (SelectedIndicies.Contains(component.Index)) { SelectColoring(component); } else { DefaultColoring(component); } } /// /// Set highlights colors of specified component. /// /// Component. protected override void HighlightColoring(ListViewItem component) { if (IsSelected(component.Index)) { return ; } HighlightColoring(component as TComponent); } /// /// Set highlights colors of specified component. /// /// Component. protected virtual void HighlightColoring(TComponent component) { if (component.Background!=null) { component.Background.color = HighlightedBackgroundColor; } } /// /// Set select colors of specified component. /// /// Component. protected virtual void SelectColoring(ListViewItem component) { if (component==null) { return ; } SelectColoring(component as TComponent); } /// /// Set select colors of specified component. /// /// Component. protected virtual void SelectColoring(TComponent component) { if (component.Background!=null) { component.Background.color = SelectedBackgroundColor; } } /// /// Set default colors of specified component. /// /// Component. protected virtual void DefaultColoring(ListViewItem component) { if (component==null) { return ; } DefaultColoring(component as TComponent); } /// /// Set default colors of specified component. /// /// Component. protected virtual void DefaultColoring(TComponent component) { if (component.Background!=null) { component.Background.color = DefaultBackgroundColor; } } /// /// Updates the colors. /// void UpdateColors() { components.ForEach(x => Coloring(x as ListViewItem)); } /// /// This function is called when the MonoBehaviour will be destroyed. /// protected override void OnDestroy() { layout = null; LayoutBridge = null; OnSelect.RemoveListener(OnSelectCallback); OnDeselect.RemoveListener(OnDeselectCallback); ScrollRect = null; RemoveCallbacks(); base.OnDestroy(); } /// /// Calls specified function with each component. /// /// Func. public override void ForEachComponent(Action func) { base.ForEachComponent(func); func(DefaultItem); componentsCache.Select(x => x as ListViewItem).ForEach(func); } /// /// Determines whether item visible. /// /// true if item visible; otherwise, false. /// Index. public bool IsItemVisible(int index) { return topHiddenItems<=index && index<=(topHiddenItems + visibleItems - 1); } /// /// Gets the visible indicies. /// /// The visible indicies. public List GetVisibleIndicies() { return Enumerable.Range(topHiddenItems, visibleItems).ToList(); } /// /// OnStartScrolling event. /// public UnityEvent OnStartScrolling = new UnityEvent(); /// /// OnEndScrolling event. /// public UnityEvent OnEndScrolling = new UnityEvent(); /// /// Time before raise OnEndScrolling event since last OnScrollRectUpdate event raised. /// public float EndScrollDelay = 0.3f; bool scrolling; float lastScrollingTime; void Update() { if (needResize) { Resize(); } if (IsEndScrolling()) { EndScrolling(); } } /// /// This function is called when the object becomes enabled and active. /// public virtual void OnEnable() { StartCoroutine(ForceRebuild()); } System.Collections.IEnumerator ForceRebuild() { yield return null; ForEachComponent(MarkLayoutForRebuild); } void MarkLayoutForRebuild(ListViewItem item) { LayoutRebuilder.MarkLayoutForRebuild(item.transform as RectTransform); } void StartScrolling() { lastScrollingTime = Time.unscaledTime; if (scrolling) { return ; } scrolling = true; OnStartScrolling.Invoke(); } bool IsEndScrolling() { if (!scrolling) { return false; } return (lastScrollingTime + EndScrollDelay) <= Time.unscaledTime; } void EndScrolling() { scrolling = false; OnEndScrolling.Invoke(); } bool needResize; void SetNeedResize() { if (!CanOptimize()) { return ; } needResize = true; } #region ListViewPaginator support /// /// Gets the ScrollRect. /// /// The ScrollRect. public override ScrollRect GetScrollRect() { return ScrollRect; } /// /// Gets the items count. /// /// The items count. public override int GetItemsCount() { return DataSource.Count; } /// /// Gets the items per block count. /// /// The items per block. public override int GetItemsPerBlock() { return 1; } /// /// Gets the index of the nearest item. /// /// The nearest item index. public override int GetNearestItemIndex() { return Mathf.Clamp(Mathf.RoundToInt(GetScrollValue() / GetItemSize()), 0, DataSource.Count - 1); } #endregion } }