#if UNITY_EDITOR using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using UnityEngine; using UnityEditor; using UnityEditor.ShortcutManagement; using UnityEngine.UIElements; using UnityEngine.SceneManagement; using UnityEditor.SceneManagement; using UnityEditor.IMGUI.Controls; using Type = System.Type; using static VFolders.Libs.VUtils; using static VFolders.Libs.VGUI; // using static VTools.VDebug; using static VFolders.VFolders; using static VFolders.VFoldersData; using static VFolders.VFoldersCache; namespace VFolders { public class VFoldersController { public void UpdateExpandQueue() { if (treeViewAnimatesExpansion) return; if (!expandQueue_toAnimate.Any()) { if (!expandQueue_toCollapseAfterAnimation.Any()) return; foreach (var id in expandQueue_toCollapseAfterAnimation) SetExpanded_withoutAnimation(id, false); expandQueue_toCollapseAfterAnimation.Clear(); return; } var iid = expandQueue_toAnimate.First().id; var expand = expandQueue_toAnimate.First().expand; if (expandedIds.Contains(iid) != expand) SetExpanded_withAnimation(iid, expand); expandQueue_toAnimate.RemoveAt(0); window.Repaint(); } public List expandQueue_toAnimate = new(); public List expandQueue_toCollapseAfterAnimation = new(); public struct ExpandQueueEntry { public int id; public bool expand; } public bool animatingExpansion => expandQueue_toAnimate.Any() || expandQueue_toCollapseAfterAnimation.Any(); public void UpdateScrollAnimation() { if (!animatingScroll) return; var lerpSpeed = 10; var lerpedScrollPos = MathUtil.SmoothDamp(currentScrollPos, targetScrollPos, lerpSpeed, ref scrollPosDerivative, editorDeltaTime); SetScrollPos(lerpedScrollPos); window.Repaint(); if (lerpedScrollPos.DistanceTo(targetScrollPos) > .4f) return; SetScrollPos(targetScrollPos); animatingScroll = false; } public float targetScrollPos; public float scrollPosDerivative; public bool animatingScroll; public void UpdateHighlightAnimation() { if (!animatingHighlight) return; var lerpSpeed = 1.2f; MathUtil.SmoothDamp(ref highlightAmount, 0, lerpSpeed, ref highlightDerivative, editorDeltaTime); window.Repaint(); if (highlightAmount > .05f) return; highlightAmount = 0; animatingHighlight = false; } public float highlightAmount; public float highlightDerivative; public bool animatingHighlight; public string folderToHighlight; public void UpdateState() { isOneColumn = window.GetFieldValue("m_ViewMode") == 0; treeViewController = window.GetFieldValue(isOneColumn ? "m_AssetTree" : "m_FolderTree"); treeViewControllerData = treeViewController?.GetPropertyValue("data"); var treeViewState = treeViewController?.GetPropertyValue>("state"); currentScrollPos = treeViewState?.scrollPos.y ?? 0; expandedIds = treeViewState?.expandedIDs ?? new List(); var treeViewAnimator = treeViewController?.GetMemberValue("m_ExpansionAnimator"); var treeViewAnimatorSetup = treeViewAnimator?.GetMemberValue("m_Setup"); treeViewAnimatesScroll = treeViewController?.GetMemberValue("m_FramingAnimFloat").isAnimating ?? false; treeViewAnimatesExpansion = treeViewAnimator?.GetMemberValue("isAnimating") ?? false; } bool isOneColumn; object treeViewController; object treeViewControllerData; public float currentScrollPos; public List expandedIds = new(); public bool treeViewAnimatesScroll; public bool treeViewAnimatesExpansion; public int GetRowIndex(int instanceId) { return treeViewControllerData.InvokeMethod("GetRow", instanceId); } public void ToggleExpanded(TreeViewItem item) { SetExpanded_withAnimation(item.id, !expandedIds.Contains(item.id)); window.Repaint(); } public void CollapseAll() { var idsToCollapse_roots = expandedIds.Where(id => EditorUtility.InstanceIDToObject(id).GetPath() is string path && path.HasParentPath() && (path.GetParentPath() == "Assets" || path.GetParentPath() == "Packages")); var idsToCollapse_children = expandedIds.Where(id => EditorUtility.InstanceIDToObject(id).GetPath() is string path && !path.IsNullOrEmpty() && path != "Assets" && path != "Packages" && !idsToCollapse_roots.Contains(id)); expandQueue_toCollapseAfterAnimation = idsToCollapse_children.ToList(); expandQueue_toAnimate = idsToCollapse_roots.Select(id => new ExpandQueueEntry { id = id, expand = false }) .OrderBy(row => GetRowIndex(row.id)).ToList(); StartScrollAnimation(targetScrollPos: 0); window.Repaint(); } public void Isolate(TreeViewItem targetItem) { List> getParents(TreeViewItem item) { var parents = new List>(); while (item.parent != null) parents.Add(item = item.parent); return parents; } var targetItemParents = getParents(targetItem); var expandedVisibleItems = new List>(); foreach (var expandedId in expandedIds) if (GetRowIndex(expandedId) is int rowIndex && rowIndex != -1) expandedVisibleItems.Add(treeViewControllerData.InvokeMethod>("GetItem", rowIndex)); var itemsToCollapse = expandedVisibleItems.ToList(); itemsToCollapse.Remove(targetItem); itemsToCollapse.RemoveAll(r => targetItemParents.Contains(r)); itemsToCollapse.RemoveAll(r => itemsToCollapse.Intersect(getParents(r)).Any()); expandQueue_toAnimate = itemsToCollapse.Select(item => new ExpandQueueEntry { id = item.id, expand = false }) .Append(new ExpandQueueEntry { id = targetItem.id, expand = true }) .OrderBy(r => GetRowIndex(r.id)).ToList(); window.Repaint(); } public void StartExpandAnimation(List targetExpandedIds) { var toExpand = targetExpandedIds.Except(expandedIds).ToHashSet(); var toCollapse = expandedIds.Except(targetExpandedIds).ToHashSet(); // hanlde non-animated expansions/collapses bool hasParentToCollapse(int id) { var o = Resources.InstanceIDToObject(id); if (!o) return false; var assetPath = AssetDatabase.GetAssetPath(o); if (!assetPath.HasParentPath()) return false; if (assetPath == "Assets") return false; var parentAsset = AssetDatabase.LoadAssetAtPath(assetPath.GetParentPath()); if (!parentAsset) return false; // packages item var parentId = parentAsset.GetInstanceID(); return toCollapse.Contains(parentId) || hasParentToCollapse(parentId); } bool areAllParentsExpanded(int id) { var o = Resources.InstanceIDToObject(id); if (!o) return true; var assetPath = AssetDatabase.GetAssetPath(o); if (!assetPath.HasParentPath()) return true; if (assetPath == "Assets") return true; var parentAsset = AssetDatabase.LoadAssetAtPath(assetPath.GetParentPath()); if (!parentAsset) return true; // packages item var parentId = parentAsset.GetInstanceID(); return expandedIds.Contains(parentId) && areAllParentsExpanded(parentId); } var toExpand_beforeAnimation = toExpand.Where(id => !areAllParentsExpanded(id)).ToHashSet(); var toCollapse_afterAnimation = toCollapse.Where(id => hasParentToCollapse(id)).ToHashSet(); foreach (var id in toExpand_beforeAnimation) SetExpanded_withoutAnimation(id, true); foreach (var id in toCollapse_afterAnimation) expandQueue_toCollapseAfterAnimation.Add(id); toExpand.ExceptWith(toExpand_beforeAnimation); toCollapse.ExceptWith(toCollapse_afterAnimation); // setup animation expandQueue_toAnimate = toCollapse.Select(id => new ExpandQueueEntry { id = id, expand = false }) .Concat(toExpand.Select(id => new ExpandQueueEntry { id = id, expand = true })) .OrderBy(r => GetRowIndex(r.id)).ToList(); } public void SetExpandedIds(List targetExpandedIds) { treeViewControllerData.InvokeMethod("SetExpandedIDs", targetExpandedIds.ToArray()); } public void SetExpanded_withAnimation(int instanceId, bool expanded) { treeViewController.InvokeMethod("ChangeFoldingForSingleItem", instanceId, expanded); } public void SetExpanded_withoutAnimation(int instanceId, bool expanded) { treeViewControllerData.InvokeMethod("SetExpanded", instanceId, expanded); } public void StartScrollAnimation(float targetScrollPos) { if (targetScrollPos.DistanceTo(currentScrollPos) < .05f) return; this.targetScrollPos = targetScrollPos; animatingScroll = true; } public void SetScrollPos(float targetScrollPos) { treeViewController.GetPropertyValue>("state").scrollPos = Vector2.up * targetScrollPos; } public void RevealFolder(string path, bool expand, bool highlight, bool snapToTopMargin) { int getId(string path) => AssetDatabase.LoadAssetAtPath(path).GetInstanceID(); var idsToExpand = new List(); if (expand) idsToExpand.Add(getId(path)); var cur = path; while (!(cur = cur.GetParentPath()).IsNullOrEmpty()) idsToExpand.Add(getId(cur)); idsToExpand.RemoveAll(r => expandedIds.Contains(r)); foreach (var id in idsToExpand.SkipLast(1)) SetExpanded_withoutAnimation(id, true); if (idsToExpand.Any()) SetExpanded_withAnimation(idsToExpand.Last(), true); var rowCount = treeViewControllerData.GetMemberValue("m_Rows").Count; var maxScrollPos = rowCount * 16 - window.position.height + (isOneColumn ? 49.9f : 45.9f); var rowIndex = treeViewControllerData.InvokeMethod("GetRow", getId(path)); var rowPos = rowIndex * 16f + (isOneColumn ? 11 : 23); var scrollAreaHeight = window.GetMemberValue("m_TreeViewRect").height; var margin = 48; var targetScrollPos = 0f; if (snapToTopMargin) targetScrollPos = (rowPos - margin).Min(maxScrollPos) .Max(0); else targetScrollPos = currentScrollPos.Min(rowPos - margin) .Max(rowPos - scrollAreaHeight + margin) .Min(maxScrollPos) .Max(0); if (targetScrollPos < 25) targetScrollPos = 0; StartScrollAnimation(targetScrollPos); if (!highlight) return; highlightAmount = 2.2f; animatingHighlight = true; folderToHighlight = path; } public void OpenFolder(string path) { // update search window.GetMemberValue("m_SearchFilter").InvokeMethod("ClearSearch"); window.GetMemberValue("m_SearchFilter").SetMemberValue("folders", new[] { path }); // update folder tree window.GetMemberValue("m_FolderTree").InvokeMethod("SetSelection", new[] { AssetDatabase.LoadAssetAtPath(path).GetInstanceID() }, false); // update list area var listAreaRect = window.GetMemberValue("m_ListAreaRect"); var searchFilter = window.GetMemberValue("m_SearchFilter"); var checkThumbnails = false; var assetToInstanceId = (System.Func)((s) => typeof(AssetDatabase).InvokeMethod("GetMainAssetInstanceID", s)); window.GetMemberValue("m_ListArea")?.InvokeMethod("InitForSearch", listAreaRect, HierarchyType.Assets, searchFilter, checkThumbnails, assetToInstanceId); // updat breadcrumbs window.GetMemberValue("m_BreadCrumbs").Clear(); // pretty much the same as ProjectBrowser.ShowFolderContents() // but without m_FolderTree.SetSelection() } public VFoldersController(EditorWindow window) => this.window = window; public EditorWindow window; public VFoldersGUI gui => VFolders.guis_byWindow[window]; } } #endif