using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEditor.IMGUI.Controls; using UnityEngine; namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl { internal class TreeViewItem : 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 : TreeView where T : TreeElement { TreeModel m_TreeModel; protected readonly List m_Rows = new List(100); public event Action treeChanged; public TreeModel treeModel { get { return m_TreeModel; } } public event Action> beforeDroppingDraggedItems; public TreeViewWithTreeModel(TreeViewState state, TreeModel model) : base(state) { Init(model); } public TreeViewWithTreeModel(TreeViewState state, MultiColumnHeader multiColumnHeader, TreeModel model) : base(state, multiColumnHeader) { Init(model); } void Init(TreeModel 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(m_TreeModel.root.id, depthForHiddenRoot, m_TreeModel.root.Name, m_TreeModel.root); } protected override IList 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 newRows) { foreach (T child in parent.children) { var item = new TreeViewItem(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 result, Func IsValidElement) { const int kItemDepth = 0; // tree is flattened when searching Stack stack = new Stack(); 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(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 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 GetAncestors(int id) { return m_TreeModel.GetAncestors(id); } protected override IList 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; 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)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 draggedRows, T parent, int insertIndex) { if (beforeDroppingDraggedItems != null) beforeDroppingDraggedItems(draggedRows); var draggedElements = new List(); foreach (var x in draggedRows) draggedElements.Add(((TreeViewItem)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 draggedItems) { TreeViewItem currentParent = parent; while (currentParent != null) { if (draggedItems.Contains(currentParent)) return false; currentParent = currentParent.parent; } return true; } } }