507 lines
19 KiB
C#
507 lines
19 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Pool;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.UI;
|
|
using XED.Core;
|
|
using XED.Util;
|
|
using TMPro;
|
|
using XED.Manage;
|
|
|
|
namespace XED.Hierarchy
|
|
{
|
|
public class PooledScrollView : UnityEngine.MonoBehaviour
|
|
{
|
|
private RectTransform content; // Content RectTransform of the ScrollView.
|
|
private CustomScrollRect scrollRect; // ScrollRect component.
|
|
private TMP_InputField searchInput;
|
|
private Button searchCancelButton;
|
|
private GameObject itemPrefab; // Prefab for scroll items.
|
|
private UnityEvent<HierarchyItem> onPointerDown;
|
|
private UnityEvent<HierarchyItem> onDragAndDrop;
|
|
|
|
private ObjectPool<ScrollItemUI> pool; // Pool for UI items.
|
|
private List<HierarchyItem> data = new List<HierarchyItem>(); // The list of hierarchy items.
|
|
private HierarchyItem root;
|
|
|
|
private List<ScrollItemUI> activeItems = new List<ScrollItemUI>();
|
|
private List<HierarchyItem> selectedItems = new List<HierarchyItem>();
|
|
private HierarchyItem lastSelectedItem = null;
|
|
|
|
private float itemHeight; // Height of a single item.
|
|
private int visibleItemCount; // Number of visible items that fit in the viewport.
|
|
private int currentIndex = -1; // The index of the first visible item.
|
|
|
|
public UnityEvent<List<HierarchyItem>> onSelect;
|
|
public UnityEvent<HierarchyItem, HierarchyItem> onParentChanged;
|
|
private void Awake()
|
|
{
|
|
content = UtilityFunction.FindDeepChild(transform, "Content").GetComponent<RectTransform>();
|
|
scrollRect = GetComponent<CustomScrollRect>();
|
|
searchInput = GetComponentInChildren<TMP_InputField>();
|
|
searchCancelButton = UtilityFunction.FindDeepChild(transform, "Button_SearchBar").GetComponent<Button>();
|
|
itemPrefab = Resources.Load<GameObject>("Prefabs/UI/PRF_HierarchyItem");
|
|
|
|
//스크롤 뷰에 사용될 오브젝트 풀을 미리 20개 생성
|
|
pool = new ObjectPool<ScrollItemUI>(
|
|
createFunc: () => Instantiate(itemPrefab).GetComponent<ScrollItemUI>(),
|
|
actionOnGet: (item) => item.gameObject.SetActive(true),
|
|
actionOnRelease: (item) => item.gameObject.SetActive(false),
|
|
actionOnDestroy: (item) => Destroy(item.gameObject),
|
|
defaultCapacity: 20,
|
|
maxSize: 40);
|
|
|
|
root = new HierarchyItem("Root", -1);
|
|
root.onParentChanged = onParentChanged;
|
|
}
|
|
private void Start()
|
|
{
|
|
//스크롤뷰의 값이 변할때마다 내용물 갱신해주는 함수 콜백 추가
|
|
scrollRect.onValueChanged.AddListener(OnScrollValueChanged);
|
|
scrollRect.setToChildItem.AddListener(SetToChildItem);
|
|
scrollRect.setToPriorSiblingItem.AddListener(SetToPriorSiblingItem);
|
|
scrollRect.setToNextSiblingItem.AddListener(SetToNextSiblingItem);
|
|
//아이템 하나당 높이값 저장
|
|
itemHeight = itemPrefab.GetComponent<RectTransform>().rect.height;
|
|
//뷰포트에 몇개의 아이템이 보이는지 한개의 여유분을 두고 계산
|
|
visibleItemCount = Mathf.CeilToInt(scrollRect.GetComponent<RectTransform>().sizeDelta.y / itemHeight) + 1;
|
|
|
|
searchInput.onValueChanged.AddListener(SearchData);
|
|
searchCancelButton.onClick.RemoveAllListeners();
|
|
searchCancelButton.onClick.AddListener(CancelSearch);
|
|
|
|
ResetData();
|
|
}
|
|
public HierarchyItem AddItem(string name, GameObject linkedObject, HierarchyItemType type, HierarchyItem parent = null)
|
|
{
|
|
HierarchyItem tempItem = new HierarchyItem(name);
|
|
tempItem.linkedObject = linkedObject;
|
|
tempItem.type = type;
|
|
tempItem.onParentChanged = onParentChanged;
|
|
if (parent == null)
|
|
{
|
|
root.AddChild(tempItem);
|
|
}
|
|
else
|
|
{
|
|
parent.AddChild(tempItem);
|
|
}
|
|
ResetData();
|
|
return tempItem;
|
|
}
|
|
public void AddItem(HierarchyItem item)
|
|
{
|
|
root.AddChild(item);
|
|
ResetData();
|
|
}
|
|
public void AttachItem(HierarchyItem parent, HierarchyItem item, int index = -1)
|
|
{
|
|
if (parent.HasChildItem(item))
|
|
return;
|
|
parent.AddChild(item, index);
|
|
ResetData();
|
|
}
|
|
public void RemoveItem(HierarchyItem removeItem)
|
|
{
|
|
root.RemoveItem(removeItem);
|
|
ResetData();
|
|
}
|
|
public void RemoveItem(string name, HierarchyItemType type)
|
|
{
|
|
HierarchyItem removeItem = FindItem(name, type);
|
|
root.RemoveItem(removeItem);
|
|
ResetData();
|
|
}
|
|
public List<HierarchyItem> GetAllItems()
|
|
{
|
|
return root.GetAll(true);
|
|
}
|
|
public int GetItemIndex(HierarchyItem item)
|
|
{
|
|
return root.GetItemIndexInHierarchy(item);
|
|
}
|
|
public bool HasItem(HierarchyItem item)
|
|
{
|
|
return root.HasChildItem(item);
|
|
}
|
|
public HierarchyItem FindItem(string name, HierarchyItemType type)
|
|
{
|
|
return root.Find(name, type);
|
|
}
|
|
public void SetData()
|
|
{
|
|
data.Clear();
|
|
data = root.GetAll();
|
|
RebuildVisibleItems();
|
|
}
|
|
public void ResetData()
|
|
{
|
|
data.Clear();
|
|
data = root.GetAll();
|
|
currentIndex = -1;
|
|
RebuildVisibleItems();
|
|
}
|
|
public void SetData(List<HierarchyItem> hierarchyData)
|
|
{
|
|
data = hierarchyData;
|
|
RebuildVisibleItems();
|
|
}
|
|
|
|
private void OnScrollValueChanged(Vector2 scrollPos)
|
|
{
|
|
RebuildVisibleItems();
|
|
}
|
|
|
|
private void RebuildVisibleItems()
|
|
{
|
|
float scrollY = content.anchoredPosition.y;
|
|
int newIndex = Mathf.FloorToInt(scrollY / itemHeight);
|
|
if (newIndex < 0)
|
|
{
|
|
scrollY = 0;
|
|
newIndex = 0;
|
|
}
|
|
|
|
// If the visible index hasn't changed, do nothing.
|
|
if (newIndex == currentIndex) return;
|
|
|
|
currentIndex = newIndex;
|
|
|
|
// Clear and recycle active items.
|
|
foreach (var item in activeItems)
|
|
{
|
|
pool.Release(item);
|
|
}
|
|
activeItems.Clear();
|
|
if (data.Count == 0)
|
|
return;
|
|
|
|
int startIndex = Mathf.Clamp(currentIndex, 0, data.Count - 1);
|
|
int endIndex = Mathf.Clamp(currentIndex + visibleItemCount, 0, data.Count);
|
|
SetScrollItemUI(startIndex, endIndex);
|
|
}
|
|
private void RebuildVisibleItems(HierarchyItem focusItem)
|
|
{
|
|
int newIndex = data.FindIndex((x) => x == focusItem);
|
|
currentIndex = newIndex;
|
|
content.anchoredPosition = new Vector2(content.anchoredPosition.x, currentIndex * itemHeight);
|
|
|
|
// Clear and recycle active items.
|
|
foreach (var item in activeItems)
|
|
{
|
|
pool.Release(item);
|
|
}
|
|
activeItems.Clear();
|
|
if (data.Count == 0)
|
|
return;
|
|
|
|
int startIndex = Mathf.Clamp(currentIndex, 0, data.Count - 1);
|
|
int endIndex = Mathf.Clamp(currentIndex + visibleItemCount, 0, data.Count);
|
|
SetScrollItemUI(startIndex, endIndex);
|
|
}
|
|
private void SetScrollItemUI(int startIndex, int endIndex)
|
|
{
|
|
int maxWidth = Mathf.RoundToInt(scrollRect.GetComponent<RectTransform>().sizeDelta.x);
|
|
for (int i = startIndex; i < endIndex; i++)
|
|
{
|
|
HierarchyItem item = data[i];
|
|
ScrollItemUI itemUI = GetItemUI();
|
|
int w = itemUI.SetItemData(item);
|
|
itemUI.SetSelected(selectedItems);
|
|
if (w > maxWidth) maxWidth = w;
|
|
itemUI.transform.SetParent(content, false);
|
|
|
|
float yPos = -((float)i + 0.5f) * itemHeight;
|
|
itemUI.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, yPos);
|
|
|
|
activeItems.Add(itemUI);
|
|
}
|
|
|
|
content.sizeDelta = new Vector2(maxWidth, data.Count * itemHeight); // Set content height.
|
|
}
|
|
private ScrollItemUI GetItemUI()
|
|
{
|
|
ScrollItemUI itemUI = pool.Get();
|
|
itemUI.onClickSingle.RemoveAllListeners();
|
|
itemUI.onClickSingle.AddListener(OnSelectSingle);
|
|
itemUI.onClickAddSingle.RemoveAllListeners();
|
|
itemUI.onClickAddSingle.AddListener(OnSelectAnother);
|
|
itemUI.onClickMultiple.RemoveAllListeners();
|
|
itemUI.onClickMultiple.AddListener(OnSelectMultiple);
|
|
itemUI.onToggleExpand.RemoveAllListeners();
|
|
itemUI.onToggleExpand.AddListener(OnToggleExpand);
|
|
return itemUI;
|
|
}
|
|
public void OnSelect(List<HierarchyItem> items)
|
|
{
|
|
selectedItems = items;
|
|
foreach (var itemUI in activeItems)
|
|
{
|
|
itemUI.SetSelected(items);
|
|
}
|
|
}
|
|
public void OnSelectSingle(HierarchyItem item)
|
|
{
|
|
if (selectedItems.Count == 1 && selectedItems[0] == item)
|
|
{
|
|
selectedItems.Clear();
|
|
}
|
|
else
|
|
{
|
|
selectedItems.Clear();
|
|
selectedItems.Add(item);
|
|
}
|
|
foreach (var itemUI in activeItems)
|
|
{
|
|
itemUI.SetSelected(selectedItems);
|
|
}
|
|
lastSelectedItem = item;
|
|
onSelect?.Invoke(selectedItems);
|
|
}
|
|
public void OnSelectAnother(HierarchyItem item)
|
|
{
|
|
if (selectedItems.Count == 1 && selectedItems[0] == item)
|
|
{
|
|
selectedItems.Clear();
|
|
}
|
|
else if (selectedItems.Count > 1 && selectedItems.Contains(item))
|
|
{
|
|
selectedItems.Remove(item);
|
|
}
|
|
else
|
|
{
|
|
selectedItems.Add(item);
|
|
}
|
|
foreach (var itemUI in activeItems)
|
|
{
|
|
itemUI.SetSelected(selectedItems);
|
|
}
|
|
lastSelectedItem = item;
|
|
onSelect?.Invoke(selectedItems);
|
|
}
|
|
public void OnSelectMultiple(HierarchyItem item)
|
|
{
|
|
int lastSelectedIndex = 0;
|
|
if (lastSelectedItem != null)
|
|
{
|
|
lastSelectedIndex = data.FindIndex(x => x == lastSelectedItem);
|
|
}
|
|
//int lowestIndex = data
|
|
// .Select((data, index) => new { data, index })
|
|
// .Where(pair => selectedItems.Contains(pair.data))
|
|
// .Min(pair => pair.index);
|
|
int currentIndex = data.FindIndex(x => x == item);
|
|
int startIndex = currentIndex > lastSelectedIndex ? lastSelectedIndex : currentIndex;
|
|
int endIndex = currentIndex > lastSelectedIndex ? currentIndex : lastSelectedIndex;
|
|
selectedItems.Clear();
|
|
for (int i = startIndex; i <= endIndex; i++)
|
|
{
|
|
HierarchyItem tempItem = data[i];
|
|
selectedItems.Add(tempItem);
|
|
}
|
|
foreach (var itemUI in activeItems)
|
|
{
|
|
itemUI.SetSelected(selectedItems);
|
|
}
|
|
onSelect?.Invoke(selectedItems);
|
|
}
|
|
public void DeselectAll()
|
|
{
|
|
foreach (var itemUI in activeItems)
|
|
{
|
|
itemUI.SetSelected(false);
|
|
}
|
|
selectedItems.Clear();
|
|
lastSelectedItem = null;
|
|
}
|
|
public void OnToggleExpand()
|
|
{
|
|
currentIndex = -1;
|
|
SetData();
|
|
}
|
|
public void SetToChildItem(HierarchyItem srcItem, HierarchyItem destItem)
|
|
{
|
|
if (srcItem == null || destItem == null)
|
|
return;
|
|
if (selectedItems.Contains(srcItem))
|
|
{
|
|
for (int i = 0; i < selectedItems.Count; i++)
|
|
{
|
|
HierarchyItem selectedItem = selectedItems[i];
|
|
if (selectedItem == destItem || selectedItem.HasChildItem(destItem))
|
|
continue;
|
|
|
|
HierarchyItem oldParent = selectedItem.parent;
|
|
int oldIndex = selectedItem.GetSiblingIndex();
|
|
var command = new ActionCommand(
|
|
() =>
|
|
{
|
|
root.RemoveItem(selectedItem);
|
|
destItem.AddChild(selectedItem);
|
|
ResetData();
|
|
},
|
|
() =>
|
|
{
|
|
root.RemoveItem(selectedItem);
|
|
oldParent.AddChild(selectedItem, oldIndex);
|
|
ResetData();
|
|
});
|
|
CommandManager.I.ExecuteCommand(command);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (srcItem == destItem || srcItem.HasChildItem(destItem))
|
|
return;
|
|
|
|
HierarchyItem oldParent = srcItem.parent;
|
|
int oldIndex = srcItem.GetSiblingIndex();
|
|
var command = new ActionCommand(
|
|
() =>
|
|
{
|
|
root.RemoveItem(srcItem);
|
|
destItem.AddChild(srcItem);
|
|
ResetData();
|
|
},
|
|
() =>
|
|
{
|
|
root.RemoveItem(srcItem);
|
|
oldParent.AddChild(srcItem, oldIndex);
|
|
ResetData();
|
|
});
|
|
CommandManager.I.ExecuteCommand(command);
|
|
}
|
|
}
|
|
public void SetToNextSiblingItem(HierarchyItem srcItem, HierarchyItem destItem)
|
|
{
|
|
if (srcItem == null || destItem == null)
|
|
return;
|
|
if (selectedItems.Contains(srcItem))
|
|
{
|
|
for (int i = 0; i < selectedItems.Count; i++)
|
|
{
|
|
HierarchyItem selectedItem = selectedItems[i];
|
|
if (selectedItem == destItem || selectedItem.HasChildItem(destItem))
|
|
continue;
|
|
|
|
HierarchyItem oldParent = selectedItem.parent;
|
|
int oldIndex = selectedItem.GetSiblingIndex();
|
|
var command = new ActionCommand(
|
|
() =>
|
|
{
|
|
root.RemoveItem(selectedItem);
|
|
destItem.AddToNextSibling(selectedItem);
|
|
ResetData();
|
|
},
|
|
() =>
|
|
{
|
|
root.RemoveItem(selectedItem);
|
|
oldParent.AddChild(selectedItem, oldIndex);
|
|
ResetData();
|
|
});
|
|
CommandManager.I.ExecuteCommand(command);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (srcItem == destItem || srcItem.HasChildItem(destItem))
|
|
return;
|
|
|
|
HierarchyItem oldParent = srcItem.parent;
|
|
int oldIndex = srcItem.GetSiblingIndex();
|
|
var command = new ActionCommand(
|
|
() =>
|
|
{
|
|
root.RemoveItem(srcItem);
|
|
destItem.AddToNextSibling(srcItem);
|
|
ResetData();
|
|
},
|
|
() =>
|
|
{
|
|
root.RemoveItem(srcItem);
|
|
oldParent.AddChild(srcItem, oldIndex);
|
|
ResetData();
|
|
});
|
|
CommandManager.I.ExecuteCommand(command);
|
|
}
|
|
}
|
|
public void SetToPriorSiblingItem(HierarchyItem srcItem, HierarchyItem destItem)
|
|
{
|
|
if (srcItem == null || destItem == null)
|
|
return;
|
|
if (selectedItems.Contains(srcItem))
|
|
{
|
|
for (int i = 0; i < selectedItems.Count; i++)
|
|
{
|
|
HierarchyItem selectedItem = selectedItems[i];
|
|
if (selectedItem == destItem || selectedItem.HasChildItem(destItem))
|
|
continue;
|
|
|
|
HierarchyItem oldParent = selectedItem.parent;
|
|
int oldIndex = selectedItem.GetSiblingIndex();
|
|
var command = new ActionCommand(
|
|
() =>
|
|
{
|
|
root.RemoveItem(selectedItem);
|
|
destItem.AddToPriorSibling(selectedItem);
|
|
ResetData();
|
|
},
|
|
() =>
|
|
{
|
|
root.RemoveItem(selectedItem);
|
|
oldParent.AddChild(selectedItem, oldIndex);
|
|
ResetData();
|
|
});
|
|
CommandManager.I.ExecuteCommand(command);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (srcItem == destItem || srcItem.HasChildItem(destItem))
|
|
return;
|
|
|
|
HierarchyItem oldParent = srcItem.parent;
|
|
int oldIndex = srcItem.GetSiblingIndex();
|
|
var command = new ActionCommand(
|
|
() =>
|
|
{
|
|
root.RemoveItem(srcItem);
|
|
destItem.AddToPriorSibling(srcItem);
|
|
ResetData();
|
|
},
|
|
() =>
|
|
{
|
|
root.RemoveItem(srcItem);
|
|
oldParent.AddChild(srcItem, oldIndex);
|
|
ResetData();
|
|
});
|
|
CommandManager.I.ExecuteCommand(command);
|
|
}
|
|
}
|
|
public void SearchData(string name)
|
|
{
|
|
data.Clear();
|
|
data = root.FindAll(name);
|
|
currentIndex = -1;
|
|
RebuildVisibleItems();
|
|
}
|
|
public void CancelSearch()
|
|
{
|
|
searchInput.text = "";
|
|
data.Clear();
|
|
currentIndex = -1;
|
|
if (selectedItems.Count == 1)
|
|
{
|
|
selectedItems[0].ExpandInHierarchy();
|
|
data = root.GetAll();
|
|
RebuildVisibleItems(selectedItems[0]);
|
|
}
|
|
else
|
|
{
|
|
data = root.GetAll();
|
|
RebuildVisibleItems();
|
|
}
|
|
}
|
|
}
|
|
} |