天津23维预案
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

872 lines
19 KiB

2 years ago
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using System;
using System.Linq;
namespace UIWidgets
{
/// <summary>
/// ListViewBase event.
/// </summary>
[Serializable]
public class ListViewBaseEvent : UnityEvent<int, ListViewItem> {
}
/// <summary>
/// ListViewFocus event.
/// </summary>
[Serializable]
public class ListViewFocusEvent : UnityEvent<BaseEventData> {
}
/// <summary>
/// ListViewBase.
/// You can use it for creating custom ListViews.
/// </summary>
abstract public class ListViewBase : MonoBehaviour,
ISelectHandler, IDeselectHandler,
ISubmitHandler, ICancelHandler
{
[SerializeField]
[HideInInspector]
List<ListViewItem> items = new List<ListViewItem>();
List<UnityAction> callbacks = new List<UnityAction>();
/// <summary>
/// Gets or sets the items.
/// </summary>
/// <value>Items.</value>
public List<ListViewItem> Items {
get {
return new List<ListViewItem>(items);
}
set {
UpdateItems(value);
}
}
/// <summary>
/// The destroy game objects after setting new items.
/// </summary>
[SerializeField]
[HideInInspector]
public bool DestroyGameObjects = true;
/// <summary>
/// Allow select multiple items.
/// </summary>
[SerializeField]
public bool Multiple;
[SerializeField]
int selectedIndex = -1;
/// <summary>
/// Gets or sets the index of the selected item.
/// </summary>
/// <value>The index of the selected.</value>
public int SelectedIndex {
get {
return selectedIndex;
}
set {
if (value==-1)
{
if (selectedIndex!=-1)
{
Deselect(selectedIndex);
}
selectedIndex = value;
}
else
{
Select(value);
}
}
}
[SerializeField]
List<int> selectedIndicies = new List<int>();
/// <summary>
/// Gets or sets indicies of the selected items.
/// </summary>
/// <value>The selected indicies.</value>
public List<int> SelectedIndicies {
get {
return new List<int>(selectedIndicies);
}
set {
var deselect = selectedIndicies.Except(value).ToArray();
var select = value.Except(selectedIndicies).ToArray();
deselect.ForEach(Deselect);
select.ForEach(Select);
}
}
/// <summary>
/// OnSelect event.
/// </summary>
public ListViewBaseEvent OnSelect = new ListViewBaseEvent();
/// <summary>
/// OnDeselect event.
/// </summary>
public ListViewBaseEvent OnDeselect = new ListViewBaseEvent();
/// <summary>
/// OnSubmit event.
/// </summary>
public UnityEvent onSubmit = new UnityEvent();
/// <summary>
/// OnCancel event.
/// </summary>
public UnityEvent onCancel = new UnityEvent();
/// <summary>
/// OnItemSelect event.
/// </summary>
public UnityEvent onItemSelect = new UnityEvent();
/// <summary>
/// onItemCancel event.
/// </summary>
public UnityEvent onItemCancel = new UnityEvent();
/// <summary>
/// The container for items objects.
/// </summary>
[SerializeField]
public Transform Container;
/// <summary>
/// OnFocusIn event.
/// </summary>
public ListViewFocusEvent OnFocusIn = new ListViewFocusEvent();
/// <summary>
/// OnFocusOut event.
/// </summary>
public ListViewFocusEvent OnFocusOut = new ListViewFocusEvent();
/// <summary>
/// Set item indicies when items updated.
/// </summary>
[NonSerialized]
protected bool SetItemIndicies = true;
GameObject Unused;
void Awake()
{
Start();
}
[System.NonSerialized]
bool isStartedListViewBase;
/// <summary>
/// Start this instance.
/// </summary>
public virtual void Start()
{
if (isStartedListViewBase)
{
return ;
}
isStartedListViewBase = true;
Unused = new GameObject("unused base");
Unused.SetActive(false);
Unused.transform.SetParent(transform, false);
if ((selectedIndex!=-1) && (selectedIndicies.Count==0))
{
selectedIndicies.Add(selectedIndex);
}
selectedIndicies.RemoveAll(NotIsValid);
if (selectedIndicies.Count==0)
{
selectedIndex = -1;
}
}
/// <summary>
/// Determines if item not exists with the specified index.
/// </summary>
/// <returns><c>true</c>, if item not exists, <c>false</c> otherwise.</returns>
/// <param name="index">Index.</param>
protected bool NotIsValid(int index)
{
return !IsValid(index);
}
/// <summary>
/// Updates the items.
/// </summary>
public virtual void UpdateItems()
{
UpdateItems(items);
}
/// <summary>
/// Determines whether this instance is horizontal. Not implemented for ListViewBase.
/// </summary>
/// <returns><c>true</c> if this instance is horizontal; otherwise, <c>false</c>.</returns>
public virtual bool IsHorizontal()
{
throw new NotSupportedException();
}
/// <summary>
/// Gets the default height of the item. Not implemented for ListViewBase.
/// </summary>
/// <returns>The default item height.</returns>
public virtual float GetDefaultItemHeight()
{
throw new NotSupportedException();
}
/// <summary>
/// Gets the default width of the item. Not implemented for ListViewBase.
/// </summary>
/// <returns>The default item width.</returns>
public virtual float GetDefaultItemWidth()
{
throw new NotSupportedException();
}
/// <summary>
/// Gets the spacing between items. Not implemented for ListViewBase.
/// </summary>
/// <returns>The item spacing.</returns>
public virtual float GetItemSpacing()
{
throw new NotSupportedException();
}
/// <summary>
/// Removes the callback.
/// </summary>
/// <param name="item">Item.</param>
/// <param name="index">Index.</param>
void RemoveCallback(ListViewItem item, int index)
{
if (item == null)
{
return;
}
if (index < callbacks.Count)
{
item.onClick.RemoveListener(callbacks[index]);
}
item.onSubmit.RemoveListener(Toggle);
item.onCancel.RemoveListener(OnItemCancel);
item.onSelect.RemoveListener(HighlightColoring);
item.onDeselect.RemoveListener(Coloring);
item.onMove.RemoveListener(OnItemMove);
}
/// <summary>
/// Raises the item cancel event.
/// </summary>
/// <param name="item">Item.</param>
void OnItemCancel(ListViewItem item)
{
if (EventSystem.current.alreadySelecting)
{
return;
}
EventSystem.current.SetSelectedGameObject(gameObject);
onItemCancel.Invoke();
}
/// <summary>
/// Removes the callbacks.
/// </summary>
void RemoveCallbacks()
{
if (callbacks.Count > 0)
{
items.ForEach(RemoveCallback);
}
callbacks.Clear();
}
/// <summary>
/// Adds the callbacks.
/// </summary>
void AddCallbacks()
{
items.ForEach(AddCallback);
}
/// <summary>
/// Adds the callback.
/// </summary>
/// <param name="item">Item.</param>
/// <param name="index">Index.</param>
void AddCallback(ListViewItem item, int index)
{
callbacks.Insert(index, () => Toggle(item));
item.onClick.AddListener(callbacks[index]);
item.onSubmit.AddListener(OnItemSubmit);
item.onCancel.AddListener(OnItemCancel);
item.onSelect.AddListener(HighlightColoring);
item.onDeselect.AddListener(Coloring);
item.onMove.AddListener(OnItemMove);
}
/// <summary>
/// Raises the item select event.
/// </summary>
/// <param name="item">Item.</param>
void OnItemSelect(ListViewItem item)
{
onItemSelect.Invoke();
}
/// <summary>
/// Raises the item submit event.
/// </summary>
/// <param name="item">Item.</param>
void OnItemSubmit(ListViewItem item)
{
Toggle(item);
if (!IsSelected(item.Index))
{
HighlightColoring(item);
}
}
/// <summary>
/// Raises the item move event.
/// </summary>
/// <param name="eventData">Event data.</param>
/// <param name="item">Item.</param>
protected virtual void OnItemMove(AxisEventData eventData, ListViewItem item)
{
switch (eventData.moveDir)
{
case MoveDirection.Left:
break;
case MoveDirection.Right:
break;
case MoveDirection.Up:
if (item.Index > 0)
{
SelectComponentByIndex(item.Index - 1);
}
break;
case MoveDirection.Down:
if (IsValid(item.Index + 1))
{
SelectComponentByIndex(item.Index + 1);
}
break;
}
}
/// <summary>
/// Scrolls to item with specifid index.
/// </summary>
/// <param name="index">Index.</param>
public virtual void ScrollTo(int index)
{
}
/// <summary>
/// Add the specified item.
/// </summary>
/// <param name="item">Item.</param>
/// <returns>Index of added item.</returns>
public virtual int Add(ListViewItem item)
{
if (item.transform.parent!=Container)
{
item.transform.SetParent(Container, false);
}
AddCallback(item, items.Count);
items.Add(item);
item.Index = callbacks.Count - 1;
return callbacks.Count - 1;
}
/// <summary>
/// Clear items of this instance.
/// </summary>
public virtual void Clear()
{
items.Clear();
UpdateItems();
}
/// <summary>
/// Remove the specified item.
/// </summary>
/// <param name="item">Item.</param>
/// <returns>Index of removed item.</returns>
protected virtual int Remove(ListViewItem item)
{
RemoveCallbacks();
var index = item.Index;
selectedIndicies = selectedIndicies.Where(x => x!=index).Select(x => x > index ? x-- : x).ToList();
if (selectedIndex==index)
{
Deselect(index);
selectedIndex = selectedIndicies.Count > 0 ? selectedIndicies.Last() : -1;
}
else if (selectedIndex > index)
{
selectedIndex -= 1;
}
items.Remove(item);
Free(item);
AddCallbacks();
return index;
}
/// <summary>
/// Free the specified item.
/// </summary>
/// <param name="item">Item.</param>
void Free(Component item)
{
if (item==null)
{
return ;
}
if (DestroyGameObjects)
{
if (item.gameObject==null)
{
return ;
}
Destroy(item.gameObject);
}
else
{
if ((item.transform==null) || (Unused==null) || (Unused.transform==null))
{
return ;
}
item.transform.SetParent(Unused.transform, false);
}
}
/// <summary>
/// Updates the items.
/// </summary>
/// <param name="newItems">New items.</param>
void UpdateItems(List<ListViewItem> newItems)
{
RemoveCallbacks();
items.Where(item => item!=null && !newItems.Contains(item)).ForEach(Free);
newItems.ForEach(UpdateItem);
//selectedIndicies.Clear();
//selectedIndex = -1;
items = newItems;
AddCallbacks();
}
void UpdateItem(ListViewItem item, int index)
{
if (item==null)
{
return ;
}
if (SetItemIndicies)
{
item.Index = index;
}
item.transform.SetParent(Container, false);
}
/// <summary>
/// Determines if item exists with the specified index.
/// </summary>
/// <returns><c>true</c> if item exists with the specified index; otherwise, <c>false</c>.</returns>
/// <param name="index">Index.</param>
public virtual bool IsValid(int index)
{
return (index >= 0) && (index < items.Count);
}
/// <summary>
/// Gets the item.
/// </summary>
/// <returns>The item.</returns>
/// <param name="index">Index.</param>
protected ListViewItem GetItem(int index)
{
return items.Find(x => x.Index==index);
}
/// <summary>
/// Select item by the specified index.
/// </summary>
/// <param name="index">Index.</param>
public virtual void Select(int index)
{
if (index==-1)
{
return ;
}
if (!IsValid(index))
{
var message = string.Format("Index must be between 0 and Items.Count ({0}). Gameobject {1}.", items.Count - 1, name);
throw new IndexOutOfRangeException(message);
}
if (IsSelected(index) && Multiple)
{
return ;
}
if (!Multiple)
{
if ((selectedIndex!=-1) && (selectedIndex!=index))
{
Deselect(selectedIndex);
}
selectedIndicies.Clear();
}
selectedIndicies.Add(index);
selectedIndex = index;
SelectItem(index);
OnSelect.Invoke(index, GetItem(index));
}
/// <summary>
/// Silents the deselect specified indicies.
/// </summary>
/// <param name="indicies">Indicies.</param>
protected void SilentDeselect(List<int> indicies)
{
selectedIndicies = selectedIndicies.Except(indicies).ToList();
selectedIndex = (selectedIndicies.Count > 0) ? selectedIndicies[selectedIndicies.Count - 1] : -1;
}
/// <summary>
/// Silents the select specified indicies.
/// </summary>
/// <param name="indicies">Indicies.</param>
protected void SilentSelect(List<int> indicies)
{
if (indicies==null)
{
indicies = new List<int>();
}
selectedIndicies.AddRange(indicies.Except(selectedIndicies));
selectedIndex = (selectedIndicies.Count > 0) ? selectedIndicies[selectedIndicies.Count - 1] : -1;
}
/// <summary>
/// Deselect item by the specified index.
/// </summary>
/// <param name="index">Index.</param>
public void Deselect(int index)
{
if (index==-1)
{
return ;
}
if (!IsSelected(index))
{
return ;
}
selectedIndicies.Remove(index);
selectedIndex = (selectedIndicies.Count > 0)
? selectedIndicies.Last()
: - 1;
if (IsValid(index))
{
DeselectItem(index);
OnDeselect.Invoke(index, GetItem(index));
}
}
/// <summary>
/// Determines if item is selected with the specified index.
/// </summary>
/// <returns><c>true</c> if item is selected with the specified index; otherwise, <c>false</c>.</returns>
/// <param name="index">Index.</param>
public bool IsSelected(int index)
{
return selectedIndicies.Contains(index);
}
/// <summary>
/// Toggle item by the specified index.
/// </summary>
/// <param name="index">Index.</param>
public void Toggle(int index)
{
if (IsSelected(index) && Multiple)
{
Deselect(index);
}
else
{
Select(index);
}
}
/// <summary>
/// Toggle the specified item.
/// </summary>
/// <param name="item">Item.</param>
void Toggle(ListViewItem item)
{
var shift_pressed = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
var have_selected = selectedIndicies.Count > 0;
if (Multiple && shift_pressed && have_selected && selectedIndicies[0]!=item.Index)
{
// deselect all items except first
selectedIndicies.GetRange(1, selectedIndicies.Count - 1).ForEach(Deselect);
// find min and max indicies
var min = Mathf.Min(selectedIndicies[0], item.Index);
var max = Mathf.Max(selectedIndicies[0], item.Index);
// select items from min to max
Enumerable.Range(min, max - min + 1).ForEach(Select);
return ;
}
Toggle(item.Index);
}
/// <summary>
/// Gets the index of the component.
/// </summary>
/// <returns>The component index.</returns>
/// <param name="item">Item.</param>
protected int GetComponentIndex(ListViewItem item)
{
return item.Index;
}
/// <summary>
/// Move the component transform to the end of the local transform list.
/// </summary>
/// <param name="item">Item.</param>
protected void SetComponentAsLastSibling(ListViewItem item)
{
item.transform.SetAsLastSibling();
}
/// <summary>
/// Called when item selected.
/// Use it for change visible style of selected item.
/// </summary>
/// <param name="index">Index.</param>
protected virtual void SelectItem(int index)
{
}
/// <summary>
/// Called when item deselected.
/// Use it for change visible style of deselected item.
/// </summary>
/// <param name="index">Index.</param>
protected virtual void DeselectItem(int index)
{
}
/// <summary>
/// Coloring the specified component.
/// </summary>
/// <param name="component">Component.</param>
protected virtual void Coloring(ListViewItem component)
{
}
/// <summary>
/// Set highlights colors of specified component.
/// </summary>
/// <param name="component">Component.</param>
protected virtual void HighlightColoring(ListViewItem component)
{
}
/// <summary>
/// This function is called when the MonoBehaviour will be destroyed.
/// </summary>
protected virtual void OnDestroy()
{
RemoveCallbacks();
items.ForEach(Free);
}
/// <summary>
/// Set EventSystem.current.SetSelectedGameObject with selected or first item.
/// </summary>
/// <returns><c>true</c>, if component was selected, <c>false</c> otherwise.</returns>
public virtual bool SelectComponent()
{
if (items.Count==0)
{
return false;
}
var index = (SelectedIndex!=-1) ? SelectedIndex : 0;
SelectComponentByIndex(index);
return true;
}
/// <summary>
/// Selects the component by index.
/// </summary>
/// <param name="index">Index.</param>
protected void SelectComponentByIndex(int index)
{
ScrollTo(index);
var ev = new ListViewItemEventData(EventSystem.current) {
NewSelectedObject = GetItem(index).gameObject
};
ExecuteEvents.Execute<ISelectHandler>(ev.NewSelectedObject, ev, ExecuteEvents.selectHandler);
}
/// <summary>
/// Raises the select event.
/// </summary>
/// <param name="eventData">Event data.</param>
void ISelectHandler.OnSelect(BaseEventData eventData)
{
if (!EventSystem.current.alreadySelecting)
{
EventSystem.current.SetSelectedGameObject(gameObject);
}
OnFocusIn.Invoke(eventData);
}
/// <summary>
/// Raises the deselect event.
/// </summary>
/// <param name="eventData">Current event data.</param>
void IDeselectHandler.OnDeselect(BaseEventData eventData)
{
OnFocusOut.Invoke(eventData);
}
/// <summary>
/// Raises the submit event.
/// </summary>
/// <param name="eventData">Event data.</param>
void ISubmitHandler.OnSubmit(BaseEventData eventData)
{
SelectComponent();
onSubmit.Invoke();
}
/// <summary>
/// Raises the cancel event.
/// </summary>
/// <param name="eventData">Event data.</param>
void ICancelHandler.OnCancel(BaseEventData eventData)
{
onCancel.Invoke();
}
/// <summary>
/// Calls specified function with each component.
/// </summary>
/// <param name="func">Func.</param>
public virtual void ForEachComponent(Action<ListViewItem> func)
{
items.ForEach(func);
}
#region ListViewPaginator support
/// <summary>
/// Gets the ScrollRect.
/// </summary>
/// <returns>The ScrollRect.</returns>
public virtual ScrollRect GetScrollRect()
{
throw new NotImplementedException();
}
/// <summary>
/// Gets the items count.
/// </summary>
/// <returns>The items count.</returns>
public virtual int GetItemsCount()
{
throw new NotImplementedException();
}
/// <summary>
/// Gets the items per block count.
/// </summary>
/// <returns>The items per block.</returns>
public virtual int GetItemsPerBlock()
{
throw new NotImplementedException();
}
/// <summary>
/// Gets the item position by index.
/// </summary>
/// <returns>The item position.</returns>
/// <param name="index">Index.</param>
public virtual float GetItemPosition(int index)
{
throw new NotImplementedException();
}
/// <summary>
/// Gets the index of the nearest item.
/// </summary>
/// <returns>The nearest item index.</returns>
public virtual int GetNearestItemIndex()
{
throw new NotImplementedException();
}
#endregion
}
}