Files
Studio/Assets/Scripts/XED/UI/TreeView/PooledScrollView.cs
2025-02-19 17:24:26 +09:00

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();
}
}
}
}