using System; using System.Linq; using UnityEditor; using UnityEditor.IMGUI.Controls; using UnityEngine; using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.DependencyGraph; using UnityEditorInternal; namespace HeurekaGames.AssetHunterPRO { public class AH_DependencyGraphWindow : EditorWindow { private static AH_DependencyGraphWindow window; [SerializeField] public AH_DependencyGraphManager dependencyGraphManager; private GUIContent lockedReference; private GUIContent unlockedReference; private GUIContent contentToggleRefsTo; private GUIContent contentToggleRefsFrom; [SerializeField] GUIContent guiContentRefresh; [SerializeField] SearchField searchField; private bool initialized; // Editor gameObjectEditor; private UnityEngine.Object previewObject; //UI Rect Vector2 uiStartPos = new Vector2(10, 50); [SerializeField] private bool seeRefsToInProject; [SerializeField] private bool seeRefsFromInProject; private Texture2D previewTexture; private static readonly string WINDOWNAME = "AH Dependency Graph"; //Add menu named "Dependency Graph" to the window menu [UnityEditor.MenuItem("Tools/Asset Hunter PRO/Dependency Graph", priority = AH_Window.WINDOWMENUITEMPRIO + 1)] [UnityEditor.MenuItem("Window/Heureka/Asset Hunter PRO/Dependency Graph _%#h", priority = AH_Window.WINDOWMENUITEMPRIO + 1)] public static void OpenDependencyGraph() { Init(); } public static void Init() { window = AH_DependencyGraphWindow.GetWindow(WINDOWNAME, true); if (window.dependencyGraphManager == null) window.dependencyGraphManager = AH_DependencyGraphManager.instance; window.initializeGUIContent(); } public static void Init(Docker.DockPosition dockPosition = Docker.DockPosition.Right) { Init(); AH_Window[] mainWindows = Resources.FindObjectsOfTypeAll(); if (mainWindows.Length != 0) { HeurekaGames.Docker.Dock(mainWindows[0], window, dockPosition); } } private void OnEnable() { Selection.selectionChanged += OnSelectionChanged; EditorApplication.projectChanged += EditorApplication_projectChanged; EditorApplication.projectWindowItemOnGUI += EditorApplication_ProjectWindowItemCallback; contentToggleRefsFrom = new GUIContent(EditorGUIUtility.IconContent("sv_icon_dot9_sml")); contentToggleRefsFrom.text = "Is dependency"; contentToggleRefsTo = new GUIContent(EditorGUIUtility.IconContent("sv_icon_dot12_sml")); contentToggleRefsTo.text = "Has dependencies"; lockedReference = new GUIContent() { tooltip = "Target Asset is locked, click to unlock", image = EditorGUIUtility.IconContent("LockIcon-On").image }; unlockedReference = new GUIContent() { tooltip = "Target Asset is unlocked, click to lock", image = EditorGUIUtility.IconContent("LockIcon").image }; seeRefsToInProject = EditorPrefs.GetBool("AHP_seeRefsToInProject", true); seeRefsFromInProject = EditorPrefs.GetBool("AHP_seeRefsFromInProject", true); } void EditorApplication_ProjectWindowItemCallback(string guid, Rect r) { //If nothing references this asset, ignore it if (!seeRefsFromInProject && !seeRefsToInProject) return; var frame = new Rect(r); frame.x += frame.width; if (seeRefsFromInProject && dependencyGraphManager!=null && dependencyGraphManager.GetReferencesFrom().ContainsKey(guid)) { frame.x += -12; frame.width += 10f; GUI.Label(frame, contentToggleRefsFrom.image, EditorStyles.miniLabel); } if (seeRefsToInProject && dependencyGraphManager.GetReferencesTo().ContainsKey(guid)) { frame.x += -12f; frame.width += 10f; GUI.Label(frame, contentToggleRefsTo.image, EditorStyles.miniLabel); } } private void EditorApplication_projectChanged() { dependencyGraphManager.IsDirty = true; dependencyGraphManager.ResetHistory(); } private void OnGUI() { initIfNeeded(); doHeader(); if (dependencyGraphManager != null) { //If window has no cached data if (!dependencyGraphManager.HasCache()) { Heureka_WindowStyler.DrawCenteredMessage(window, AH_EditorData.Instance.RefFromWhiteIcon.Icon, 240f, 110f, "No Graph" + Environment.NewLine + "Build Graph"); EditorGUILayout.BeginVertical(); GUILayout.FlexibleSpace(); Color origClr = GUI.backgroundColor; GUI.backgroundColor = Heureka_WindowStyler.clr_Red; if (GUILayout.Button("Build Graph", GUILayout.Height(40))) { dependencyGraphManager.RefreshReferenceGraph(); } GUI.backgroundColor = origClr; EditorGUILayout.EndVertical(); return; } if (dependencyGraphManager.HasSelection) { using (new EditorGUILayout.VerticalScope("box")) { using (new EditorGUILayout.HorizontalScope()) { if (GUILayout.Button($"{dependencyGraphManager.GetSelectedName()}" + (dependencyGraphManager.LockedSelection ? " (Locked)" : ""), GUILayout.ExpandWidth(true))) EditorGUIUtility.PingObject(dependencyGraphManager.SelectedAsset); if (GUILayout.Button(dependencyGraphManager.LockedSelection ? lockedReference : unlockedReference, EditorStyles.boldLabel, GUILayout.ExpandWidth(false))) dependencyGraphManager.LockedSelection = !dependencyGraphManager.LockedSelection; } drawPreview(); } var viewFrom = dependencyGraphManager.GetTreeViewFrom(); bool isValidFrom = (viewFrom?.treeModel?.numberOfDataElements > 1); var viewTo = dependencyGraphManager.GetTreeViewTo(); bool isValidTo = (viewTo?.treeModel?.numberOfDataElements > 1); using (new EditorGUILayout.VerticalScope("box", GUILayout.ExpandWidth(true))) { using (new EditorGUILayout.HorizontalScope("box", GUILayout.ExpandWidth(true))) { GUILayout.Label(AH_EditorData.Instance.RefFromIcon.Icon, GUILayout.Width(32), GUILayout.Height(32)); using (new EditorGUILayout.VerticalScope(GUILayout.Height(32))) { GUILayout.FlexibleSpace(); EditorGUILayout.LabelField($"A dependency of {(isValidFrom ? (viewFrom.treeModel.root.children.Count()) : 0)}", EditorStyles.boldLabel); GUILayout.FlexibleSpace(); } } if (isValidFrom) drawAssetList(dependencyGraphManager.GetTreeViewFrom()); using (new EditorGUILayout.HorizontalScope("box", GUILayout.ExpandWidth(true))) { GUILayout.Label(AH_EditorData.Instance.RefToIcon.Icon, GUILayout.Width(32), GUILayout.Height(32)); using (new EditorGUILayout.VerticalScope(GUILayout.Height(32))) { GUILayout.FlexibleSpace(); EditorGUILayout.LabelField($"Depends on {(isValidTo ? (viewTo.treeModel.root.children.Count()) : 0)}", EditorStyles.boldLabel); GUILayout.FlexibleSpace(); } } if (isValidTo) drawAssetList(dependencyGraphManager.GetTreeViewTo()); //Force flexible size here to make sure the preview area doesn't fill entire window if (!isValidTo && !isValidFrom) GUILayout.FlexibleSpace(); } } else { Heureka_WindowStyler.DrawCenteredMessage(window, AH_EditorData.Instance.RefFromWhiteIcon.Icon, 240f, 110f, "No selection" + Environment.NewLine + "Select asset in project view"); } } doFooter(); //Make sure this window has focus to update contents Repaint(); } private void drawPreview() { if (dependencyGraphManager.SelectedAsset != null) { var old = previewObject; previewObject = dependencyGraphManager.SelectedAsset; //if (previewObject != old) { previewTexture = AssetPreview.GetAssetPreview(previewObject); //Asnyc, so we have to do this each frame if (previewTexture == null) previewTexture = AssetPreview.GetMiniThumbnail(previewObject); } using (new EditorGUILayout.VerticalScope("box")) { EditorGUILayout.BeginHorizontal(); drawHistoryButton(-1); GUILayout.FlexibleSpace(); if (GUILayout.Button(previewTexture, EditorStyles.boldLabel, /*GUILayout.Width(64),*/ GUILayout.MaxHeight(64), GUILayout.ExpandWidth(true))) { EditorGUIUtility.PingObject(previewObject); } GUILayout.FlexibleSpace(); drawHistoryButton(1); EditorGUILayout.EndHorizontal(); } } } private void drawHistoryButton(int direction) { if (!dependencyGraphManager.LockedSelection) { var style = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter }; string tooltip; bool validDirection = dependencyGraphManager.HasHistory(direction, out tooltip); EditorGUI.BeginDisabledGroup(!validDirection); var content = new GUIContent(validDirection ? direction == -1 ? "<" : ">" : string.Empty); if (!string.IsNullOrEmpty(tooltip)) content.tooltip = tooltip; if (GUILayout.Button(content, style, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(false), GUILayout.Width(12))) { if (direction == -1) dependencyGraphManager.SelectPreviousFromHistory(); else if (direction == 1) dependencyGraphManager.SelectNextFromHistory(); else Debug.LogWarning("Wrong integer. You must select -1 or 1"); } EditorGUI.EndDisabledGroup(); } } private void initIfNeeded() { if (!dependencyGraphManager || !window) Init(); if (dependencyGraphManager.RequiresRefresh()) OnSelectionChanged(); //We dont need to do stuff when in play mode if (dependencyGraphManager && !initialized) { if (searchField == null) searchField = new SearchField(); dependencyGraphManager.Initialize(searchField, multiColumnTreeViewRect); initialized = true; InternalEditorUtility.RepaintAllViews(); } //This is an (ugly) fix to make sure we dotn loose our icons due to some singleton issues after play/stop if (guiContentRefresh.image == null) initializeGUIContent(); } private void initializeGUIContent() { titleContent = new GUIContent(WINDOWNAME, AH_EditorData.Instance.RefFromIcon.Icon); guiContentRefresh = new GUIContent(AH_EditorData.Instance.RefreshIcon.Icon, "Refresh data"); } private void doFooter() { if (dependencyGraphManager != null) { if (!dependencyGraphManager.HasSelection) GUILayout.FlexibleSpace(); GUIContent RefreshGUIContent = new GUIContent(guiContentRefresh); Color origColor = GUI.color; if (dependencyGraphManager.IsDirty) { GUI.color = Heureka_WindowStyler.clr_Red; RefreshGUIContent.tooltip = String.Format("{0}{1}", RefreshGUIContent.tooltip, " (Project has changed which means that treeview is out of date)"); } if (AH_UIUtilities.DrawSelectionButton(RefreshGUIContent)) dependencyGraphManager.RefreshReferenceGraph(); GUI.color = origColor; EditorGUILayout.BeginHorizontal(); EditorGUI.BeginChangeCheck(); seeRefsToInProject = GUILayout.Toggle(seeRefsToInProject, contentToggleRefsTo); seeRefsFromInProject = GUILayout.Toggle(seeRefsFromInProject, contentToggleRefsFrom); //Do we need to repaint projewct view? if (EditorGUI.EndChangeCheck()) { EditorPrefs.SetBool("AHP_seeRefsToInProject", seeRefsToInProject); EditorPrefs.SetBool("AHP_seeRefsFromInProject", seeRefsFromInProject); InternalEditorUtility.RepaintAllViews(); } EditorGUILayout.EndHorizontal(); } } private void doHeader() { Heureka_WindowStyler.DrawGlobalHeader(Heureka_WindowStyler.clr_lBlue, WINDOWNAME); bool hasReferenceGraph = (dependencyGraphManager != null); if (hasReferenceGraph) { if (dependencyGraphManager.HasSelection && dependencyGraphManager.HasCache()) { EditorGUILayout.BeginHorizontal(GUILayout.Height(AH_Window.ButtonMaxHeight)); doSearchBar(searchBar); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); } } } private void drawAssetList(TreeView view) { if (view != null) { var rect = EditorGUILayout.BeginVertical(); GUILayout.FlexibleSpace(); view.OnGUI(rect); EditorGUILayout.EndVertical(); } } /*private bool doSelectionButton(GUIContent content) { GUIContent btnContent = new GUIContent(content); if (AH_SettingsManager.Instance.HideButtonText) btnContent.text = null; return GUILayout.Button(btnContent, GUILayout.MaxHeight(AH_SettingsManager.Instance.HideButtonText ? AH_Window.ButtonMaxHeight * 2f : AH_Window.ButtonMaxHeight)); }*/ void doSearchBar(Rect rect) { if (searchField != null) dependencyGraphManager.SearchString = searchField.OnGUI(rect, dependencyGraphManager.SearchString); } private void OnSelectionChanged() { if (!dependencyGraphManager.LockedSelection) { dependencyGraphManager.UpdateSelectedAsset((Selection.activeObject) ? Selection.activeObject : null); initialized = false; } } Rect searchBar { get { return new Rect(uiStartPos.x + AH_Window.ButtonMaxHeight, uiStartPos.y - (AH_Window.ButtonMaxHeight + 6), position.width - (uiStartPos.x * 2) - AH_Window.ButtonMaxHeight * 2, AH_Window.ButtonMaxHeight); } } Rect multiColumnTreeViewRect { get { Rect newRect = new Rect(uiStartPos.x, uiStartPos.y + 20 + (AH_SettingsManager.Instance.HideButtonText ? 20 : 0), position.width - (uiStartPos.x * 2), position.height - 90 - (AH_SettingsManager.Instance.HideButtonText ? 20 : 0)); return newRect; } } private void OnDisable() { Selection.selectionChanged -= OnSelectionChanged; EditorApplication.projectChanged -= EditorApplication_projectChanged; EditorApplication.projectWindowItemOnGUI -= EditorApplication_ProjectWindowItemCallback; } private void OnDestroy() { DestroyImmediate(dependencyGraphManager); } } }