252 lines
9.0 KiB
C#
252 lines
9.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEditor.IMGUI.Controls;
|
|
using UnityEngine;
|
|
|
|
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl
|
|
{
|
|
|
|
internal class TreeViewItem<T> : TreeViewItem where T : TreeElement
|
|
{
|
|
//Data storage
|
|
public T data { get; set; }
|
|
|
|
public TreeViewItem(int id, int depth, string displayName, T data) : base(id, depth, displayName)
|
|
{
|
|
this.data = data;
|
|
}
|
|
}
|
|
|
|
internal class TreeViewWithTreeModel<T> : TreeView where T : TreeElement
|
|
{
|
|
TreeModel<T> m_TreeModel;
|
|
protected readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
|
|
public event Action treeChanged;
|
|
|
|
public TreeModel<T> treeModel { get { return m_TreeModel; } }
|
|
public event Action<IList<TreeViewItem>> beforeDroppingDraggedItems;
|
|
|
|
public TreeViewWithTreeModel(TreeViewState state, TreeModel<T> model) : base(state)
|
|
{
|
|
Init(model);
|
|
}
|
|
|
|
public TreeViewWithTreeModel(TreeViewState state, MultiColumnHeader multiColumnHeader, TreeModel<T> model)
|
|
: base(state, multiColumnHeader)
|
|
{
|
|
Init(model);
|
|
}
|
|
|
|
void Init(TreeModel<T> model)
|
|
{
|
|
m_TreeModel = model;
|
|
m_TreeModel.modelChanged += ModelChanged;
|
|
}
|
|
|
|
protected void ModelChanged()
|
|
{
|
|
if (treeChanged != null)
|
|
treeChanged();
|
|
|
|
Reload();
|
|
}
|
|
|
|
protected override TreeViewItem BuildRoot()
|
|
{
|
|
int depthForHiddenRoot = -1;
|
|
return new TreeViewItem<T>(m_TreeModel.root.id, depthForHiddenRoot, m_TreeModel.root.Name, m_TreeModel.root);
|
|
}
|
|
|
|
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
|
|
{
|
|
if (m_TreeModel.root == null)
|
|
{
|
|
Debug.LogError("tree model root is null. did you call SetData()?");
|
|
}
|
|
|
|
m_Rows.Clear();
|
|
|
|
if (RequiresSorting()) //TODO MAYBE JUST CHECK HERE IS WE ARE SORTING OR NOT, IF SORTING JUST USE THE SEARCH
|
|
{
|
|
Search(m_TreeModel.root, searchString, m_Rows, IsValidElement);
|
|
}
|
|
else
|
|
{
|
|
if (m_TreeModel.root.hasChildren)
|
|
AddChildrenRecursive(m_TreeModel.root, 0, m_Rows);
|
|
}
|
|
|
|
// We still need to setup the child parent information for the rows since this
|
|
// information is used by the TreeView internal logic (navigation, dragging etc)
|
|
SetupParentsAndChildrenFromDepths(root, m_Rows);
|
|
|
|
return m_Rows;
|
|
}
|
|
|
|
//Override if we need to show lists instead of tree
|
|
protected virtual bool RequiresSorting()
|
|
{
|
|
return !string.IsNullOrEmpty(searchString);
|
|
}
|
|
|
|
protected virtual void AddChildrenRecursive(T parent, int depth, IList<TreeViewItem> newRows)
|
|
{
|
|
foreach (T child in parent.children)
|
|
{
|
|
var item = new TreeViewItem<T>(child.id, depth, child.Name, child);
|
|
newRows.Add(item);
|
|
|
|
if (child.hasChildren)
|
|
{
|
|
if (IsExpanded(child.id))
|
|
{
|
|
AddChildrenRecursive(child, depth + 1, newRows);
|
|
}
|
|
else
|
|
{
|
|
item.children = CreateChildListForCollapsedParent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Override this to add additional requirements
|
|
protected virtual bool IsValidElement(TreeElement element, string searchString)
|
|
{
|
|
return (string.IsNullOrEmpty(searchString) || element.Name.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0);
|
|
}
|
|
|
|
protected virtual void Search(T searchFromThis, string search, List<TreeViewItem> result, Func<T, string, bool> IsValidElement)
|
|
{
|
|
const int kItemDepth = 0; // tree is flattened when searching
|
|
|
|
Stack<T> stack = new Stack<T>();
|
|
|
|
if (searchFromThis?.children != null)
|
|
{
|
|
foreach (var element in searchFromThis.children)
|
|
stack.Push((T)element);
|
|
}
|
|
|
|
while (stack.Count > 0)
|
|
{
|
|
T current = stack.Pop();
|
|
// Matches search?
|
|
if (IsValidElement(current, search))
|
|
{
|
|
result.Add(new TreeViewItem<T>(current.id, kItemDepth, current.Name, current));
|
|
}
|
|
|
|
if (current.children != null && current.children.Count > 0)
|
|
{
|
|
foreach (var element in current.children)
|
|
{
|
|
stack.Push((T)element);
|
|
}
|
|
}
|
|
}
|
|
SortSearchResult(result);
|
|
}
|
|
|
|
protected virtual void SortSearchResult(List<TreeViewItem> rows)
|
|
{
|
|
rows.Sort((x, y) => EditorUtility.NaturalCompare(x.displayName, y.displayName)); // sort by displayName by default, can be overriden for multicolumn solutions
|
|
}
|
|
|
|
protected override IList<int> GetAncestors(int id)
|
|
{
|
|
return m_TreeModel.GetAncestors(id);
|
|
}
|
|
|
|
protected override IList<int> GetDescendantsThatHaveChildren(int id)
|
|
{
|
|
return m_TreeModel.GetDescendantsThatHaveChildren(id);
|
|
}
|
|
|
|
// Dragging
|
|
//-----------
|
|
|
|
const string k_GenericDragID = "GenericDragColumnDragging";
|
|
|
|
protected override bool CanStartDrag(CanStartDragArgs args)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
protected override void SetupDragAndDrop(SetupDragAndDropArgs args)
|
|
{
|
|
if (hasSearch)
|
|
return;
|
|
|
|
DragAndDrop.PrepareStartDrag();
|
|
var draggedRows = GetRows().Where(item => args.draggedItemIDs.Contains(item.id)).ToList();
|
|
DragAndDrop.SetGenericData(k_GenericDragID, draggedRows);
|
|
DragAndDrop.objectReferences = new UnityEngine.Object[] { }; // this IS required for dragging to work
|
|
string title = draggedRows.Count == 1 ? draggedRows[0].displayName : "< Multiple >";
|
|
DragAndDrop.StartDrag(title);
|
|
}
|
|
|
|
protected override DragAndDropVisualMode HandleDragAndDrop(DragAndDropArgs args)
|
|
{
|
|
// Check if we can handle the current drag data (could be dragged in from other areas/windows in the editor)
|
|
var draggedRows = DragAndDrop.GetGenericData(k_GenericDragID) as List<TreeViewItem>;
|
|
if (draggedRows == null)
|
|
return DragAndDropVisualMode.None;
|
|
|
|
// Parent item is null when dragging outside any tree view items.
|
|
switch (args.dragAndDropPosition)
|
|
{
|
|
case DragAndDropPosition.UponItem:
|
|
case DragAndDropPosition.BetweenItems:
|
|
{
|
|
bool validDrag = ValidDrag(args.parentItem, draggedRows);
|
|
if (args.performDrop && validDrag)
|
|
{
|
|
T parentData = ((TreeViewItem<T>)args.parentItem).data;
|
|
OnDropDraggedElementsAtIndex(draggedRows, parentData, args.insertAtIndex == -1 ? 0 : args.insertAtIndex);
|
|
}
|
|
return validDrag ? DragAndDropVisualMode.Move : DragAndDropVisualMode.None;
|
|
}
|
|
|
|
case DragAndDropPosition.OutsideItems:
|
|
{
|
|
if (args.performDrop)
|
|
OnDropDraggedElementsAtIndex(draggedRows, m_TreeModel.root, m_TreeModel.root.children.Count);
|
|
|
|
return DragAndDropVisualMode.Move;
|
|
}
|
|
default:
|
|
Debug.LogError("Unhandled enum " + args.dragAndDropPosition);
|
|
return DragAndDropVisualMode.None;
|
|
}
|
|
}
|
|
|
|
public virtual void OnDropDraggedElementsAtIndex(List<TreeViewItem> draggedRows, T parent, int insertIndex)
|
|
{
|
|
if (beforeDroppingDraggedItems != null)
|
|
beforeDroppingDraggedItems(draggedRows);
|
|
|
|
var draggedElements = new List<TreeElement>();
|
|
foreach (var x in draggedRows)
|
|
draggedElements.Add(((TreeViewItem<T>)x).data);
|
|
|
|
var selectedIDs = draggedElements.Select(x => x.id).ToArray();
|
|
m_TreeModel.MoveElements(parent, insertIndex, draggedElements);
|
|
SetSelection(selectedIDs, TreeViewSelectionOptions.RevealAndFrame);
|
|
}
|
|
|
|
bool ValidDrag(TreeViewItem parent, List<TreeViewItem> draggedItems)
|
|
{
|
|
TreeViewItem currentParent = parent;
|
|
while (currentParent != null)
|
|
{
|
|
if (draggedItems.Contains(currentParent))
|
|
return false;
|
|
currentParent = currentParent.parent;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
} |