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
}
}