using UnityEngine; using UnityEditor; using System; using UnityEditor.IMGUI.Controls; using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView; using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl; //Only avaliable in 2018 #if UNITY_2018_1_OR_NEWER using UnityEditor.Build.Reporting; #endif namespace HeurekaGames.AssetHunterPRO { public class AH_Window : EditorWindow { public const int WINDOWMENUITEMPRIO = 11; public const string VERSION = "2.2.1"; private static AH_Window m_window; [NonSerialized] bool m_Initialized; [SerializeField] TreeViewState m_TreeViewState; // Serialized in the window layout file so it survives assembly reloading [SerializeField] MultiColumnHeaderState m_MultiColumnHeaderState; SearchField m_SearchField; private AH_TreeViewWithTreeModel m_TreeView; [SerializeField] public AH_BuildInfoManager buildInfoManager; public bool m_BuildLogLoaded { get; set; } //Button guiContent [SerializeField] GUIContent guiContentLoadBuildInfo; [SerializeField] GUIContent guiContentSettings; [SerializeField] GUIContent guiContentGenerateReferenceGraph; [SerializeField] GUIContent guiContentDuplicates; //Only avaliable in 2018 #if UNITY_2018_1_OR_NEWER [SerializeField] GUIContent guiContentBuildReport; #endif [SerializeField] GUIContent guiContentReadme; [SerializeField] GUIContent guiContentDeleteAll; [SerializeField] GUIContent guiContentRefresh; //UI Rect Vector2 uiStartPos = new Vector2(10, 50); public static float ButtonMaxHeight = 18; //Add menu named "Asset Hunter" to the window menu [UnityEditor.MenuItem("Tools/Asset Hunter PRO/Asset Hunter PRO", priority = WINDOWMENUITEMPRIO)] [UnityEditor.MenuItem("Window/Heureka/Asset Hunter PRO/Asset Hunter PRO _%h", priority = WINDOWMENUITEMPRIO)] public static void OpenAssetHunter() { if (!m_window) initializeWindow(); } private static AH_Window initializeWindow() { //Open ReadMe Heureka_PackageDataManagerEditor.SelectReadme(); m_window = EditorWindow.GetWindow(); AH_TreeViewSelectionInfo.OnAssetDeleted += m_window.OnAssetDeleted; #if UNITY_2018_1_OR_NEWER EditorApplication.projectChanged += m_window.OnProjectChanged; #elif UNITY_5_6_OR_NEWER EditorApplication.projectWindowChanged += m_window.OnProjectChanged; #endif if (m_window.buildInfoManager == null) m_window.buildInfoManager = ScriptableObject.CreateInstance(); m_window.initializeGUIContent(); //Subscribe to changes to list of ignored items AH_SettingsManager.Instance.IgnoreListUpdatedEvent += m_window.OnIgnoreListUpdatedEvent; return m_window; } internal static AH_BuildInfoManager GetBuildInfoManager() { if (!m_window) initializeWindow(); return m_window.buildInfoManager; } private void OnEnable() { AH_SerializationHelper.NewBuildInfoCreated += onBuildInfoCreated; } private void OnDisable() { AH_SerializationHelper.NewBuildInfoCreated -= onBuildInfoCreated; } void OnInspectorUpdate() { if (!m_window) initializeWindow(); } void OnGUI() { /*if (Application.isPlaying) return;*/ InitIfNeeded(); doHeader(); if (buildInfoManager == null || !buildInfoManager.HasSelection) { doNoBuildInfoLoaded(); return; } if (buildInfoManager.IsProjectClean() && ((AH_MultiColumnHeader)m_TreeView.multiColumnHeader).ShowMode == AH_MultiColumnHeader.AssetShowMode.Unused) { Heureka_WindowStyler.DrawCenteredImage(m_window, AH_EditorData.Instance.AchievementIcon.Icon); return; } doSearchBar(toolbarRect); doTreeView(multiColumnTreeViewRect); doBottomToolBar(bottomToolbarRect); } void OnProjectChanged() { buildInfoManager.ProjectDirty = true; } //Callback private void OnAssetDeleted() { //TODO need to improve the deletion of empty folder. Currently leaves meta file behind, causing warnings if (EditorUtility.DisplayDialog("Delete empty folders", "Do you want to delete any empty folders?", "Yes", "No")) { deleteEmptyFolders(); } //This might be called excessively if (AH_SettingsManager.Instance.AutoRefreshLog) RefreshBuildLog(); } //callback private void onBuildInfoCreated(string path) { if (EditorUtility.DisplayDialog( "New buildinfo log created", "Do you want to load it into Asset Hunter", "Ok", "Cancel")) { m_Initialized = false; buildInfoManager.SelectBuildInfo(path); } } void InitIfNeeded() { //We dont need to do stuff when in play mode if (buildInfoManager && buildInfoManager.HasSelection && !m_Initialized) { // Check if it already exists (deserialized from window layout file or scriptable object) if (m_TreeViewState == null) m_TreeViewState = new TreeViewState(); bool firstInit = m_MultiColumnHeaderState == null; var headerState = AH_TreeViewWithTreeModel.CreateDefaultMultiColumnHeaderState(multiColumnTreeViewRect.width); if (MultiColumnHeaderState.CanOverwriteSerializedFields(m_MultiColumnHeaderState, headerState)) MultiColumnHeaderState.OverwriteSerializedFields(m_MultiColumnHeaderState, headerState); m_MultiColumnHeaderState = headerState; var multiColumnHeader = new AH_MultiColumnHeader(headerState); if (firstInit) multiColumnHeader.ResizeToFit(); var treeModel = new TreeModel(buildInfoManager.GetTreeViewData()); m_TreeView = new AH_TreeViewWithTreeModel(m_TreeViewState, multiColumnHeader, treeModel); m_SearchField = new SearchField(); m_SearchField.downOrUpArrowKeyPressed += m_TreeView.SetFocusAndEnsureSelectedItem; m_Initialized = true; buildInfoManager.ProjectDirty = false; } //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 deleteEmptyFolders() { checkEmptyFolder(Application.dataPath); AssetDatabase.Refresh(); } //This needs some work, it leaves the meta file. TAken out of codebase until a fix has been found private bool checkEmptyFolder(string dataPath) { if (dataPath.EndsWith(".git", StringComparison.InvariantCultureIgnoreCase)) return false; string[] files = System.IO.Directory.GetFiles(dataPath); bool hasValidAsset = false; for (int i = 0; i < files.Length; i++) { string relativePath; string assetID; AH_Utils.GetRelativePathAndAssetID(files[i], out relativePath, out assetID); //This folder has a valid asset inside if (!string.IsNullOrEmpty(assetID)) { hasValidAsset = true; break; } } string[] folders = System.IO.Directory.GetDirectories(dataPath); bool hasFolderWithContents = false; for (int i = 0; i < folders.Length; i++) { bool folderIsEmpty = checkEmptyFolder(folders[i]); if (!folderIsEmpty) { hasFolderWithContents = true; } else { //if (EditorUtility.DisplayDialog("Delete folder", folders[i] + " seems to be empty, do you want to delete it?", "Yes", "No")) { Debug.Log("AH: Deleting empty folder " + folders[i]); AssetDatabase.DeleteAsset(FileUtil.GetProjectRelativePath(folders[i])); } } } return (!hasValidAsset && !hasFolderWithContents); } private void initializeGUIContent() { titleContent = new GUIContent("Asset Hunter", AH_EditorData.Instance.WindowPaneIcon.Icon); guiContentLoadBuildInfo = new GUIContent("Load", AH_EditorData.Instance.LoadLogIcon.Icon, "Load info from a previous build"); guiContentSettings = new GUIContent("Settings", AH_EditorData.Instance.Settings.Icon, "Open settings"); guiContentGenerateReferenceGraph = new GUIContent("Dependencies", AH_EditorData.Instance.RefFromIcon.Icon, "See asset dependency graph"); guiContentDuplicates = new GUIContent("Duplicates", AH_EditorData.Instance.DuplicateIcon.Icon, "Find duplicate assets"); //Only avaliable in 2018 #if UNITY_2018_1_OR_NEWER guiContentBuildReport = new GUIContent("Report", AH_EditorData.Instance.ReportIcon.Icon, "Build report overview (Build size information)"); #endif guiContentReadme = new GUIContent("Info", AH_EditorData.Instance.HelpIcon.Icon, "Open the readme file for all installed Heureka Games products"); guiContentDeleteAll = new GUIContent("Clean ALL", AH_EditorData.Instance.DeleteIcon.Icon, "Delete ALL unused assets in project ({0}) Remember to manually exclude relevant assets in the settings window"); guiContentRefresh = new GUIContent(AH_EditorData.Instance.RefreshIcon.Icon, "Refresh data"); } private void doNoBuildInfoLoaded() { Heureka_WindowStyler.DrawCenteredMessage(m_window, AH_EditorData.Instance.WindowHeaderIcon.Icon, 380f, 110f, "Buildinfo not yet loaded" + Environment.NewLine + "Load existing / create new build"); } private void doHeader() { Heureka_WindowStyler.DrawGlobalHeader(Heureka_WindowStyler.clr_Pink, "ASSET HUNTER PRO", VERSION); EditorGUILayout.BeginHorizontal(); bool infoLoaded = (buildInfoManager != null && buildInfoManager.HasSelection); if (infoLoaded) { GUIContent RefreshGUIContent = new GUIContent(guiContentRefresh); Color origColor = GUI.color; if (buildInfoManager.ProjectDirty) { 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 (doSelectionButton(RefreshGUIContent)) RefreshBuildLog(); GUI.color = origColor; } if (doSelectionButton(guiContentLoadBuildInfo)) openBuildInfoSelector(); if (doSelectionButton(guiContentDuplicates)) AH_DuplicatesWindow.Init(Docker.DockPosition.Left); if (doSelectionButton(guiContentGenerateReferenceGraph)) AH_DependencyGraphWindow.Init(Docker.DockPosition.Right); //Only avaliable in 2018 #if UNITY_2018_1_OR_NEWER if (infoLoaded && doSelectionButton(guiContentBuildReport)) AH_BuildReportWindow.Init(); #endif if (doSelectionButton(guiContentSettings)) AH_SettingsWindow.Init(true); /* #if AH_HAS_OLD_INSTALLED //Transfer settings to PRO GUIContent TransferSettingsContent = new GUIContent("Transfer Settings", "Transfer your settings from old Asset Hunter into PRO"); if (AH_VersionUpgrader.VersionUpgraderReady && GUILayout.Button(TransferSettingsContent, GUILayout.MaxHeight(18))) AH_VersionUpgrader.RunUpgrade(); #endif */ if (infoLoaded && m_TreeView.GetCombinedUnusedSize() > 0) { string sizeAsString = AH_Utils.GetSizeAsString(m_TreeView.GetCombinedUnusedSize()); GUIContent instancedGUIContent = new GUIContent(guiContentDeleteAll); instancedGUIContent.tooltip = string.Format(instancedGUIContent.tooltip, sizeAsString); if (AH_SettingsManager.Instance.HideButtonText) instancedGUIContent.text = null; GUIStyle btnStyle = "button"; GUIStyle newStyle = new GUIStyle(btnStyle); newStyle.normal.textColor = Heureka_WindowStyler.clr_Pink; m_TreeView.DrawDeleteAllButton(instancedGUIContent, newStyle, GUILayout.MaxHeight(AH_SettingsManager.Instance.HideButtonText ? ButtonMaxHeight * 2f : ButtonMaxHeight)); } GUILayout.FlexibleSpace(); GUILayout.Space(20); if (m_TreeView != null) m_TreeView.AssetSelectionToolBarGUI(); if (doSelectionButton(guiContentReadme)) { Heureka_PackageDataManagerEditor.SelectReadme(); if (AH_EditorData.Instance.Documentation != null) AssetDatabase.OpenAsset(AH_EditorData.Instance.Documentation); } EditorGUILayout.EndHorizontal(); } private void doSearchBar(Rect rect) { if (m_TreeView != null) m_TreeView.searchString = m_SearchField.OnGUI(rect, m_TreeView.searchString); } private void doTreeView(Rect rect) { if (m_TreeView != null) m_TreeView.OnGUI(rect); } private void doBottomToolBar(Rect rect) { if (m_TreeView == null) return; GUILayout.BeginArea(rect); using (new EditorGUILayout.HorizontalScope()) { GUIStyle style = "miniButton"; if (GUILayout.Button("Expand All", style)) { m_TreeView.ExpandAll(); } if (GUILayout.Button("Collapse All", style)) { m_TreeView.CollapseAll(); } GUILayout.Label("Build: " + buildInfoManager.GetSelectedBuildDate() + " (" + buildInfoManager.GetSelectedBuildTarget() + ")"); GUILayout.FlexibleSpace(); GUILayout.Label(buildInfoManager.TreeView != null ? AssetDatabase.GetAssetPath(buildInfoManager.TreeView) : string.Empty); GUILayout.FlexibleSpace(); if (((AH_MultiColumnHeader)m_TreeView.multiColumnHeader).mode == AH_MultiColumnHeader.Mode.SortedList || !string.IsNullOrEmpty(m_TreeView.searchString)) { if (GUILayout.Button("Return to Treeview", style)) { m_TreeView.ShowTreeMode(); } } GUIContent exportContent = new GUIContent("Export list", "Export all the assets in the list above to a json file"); if (GUILayout.Button(exportContent, style)) { AH_ElementList.DumpCurrentListToFile(m_TreeView); } } GUILayout.EndArea(); } 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 ? ButtonMaxHeight * 2f : ButtonMaxHeight)); } private void OnIgnoreListUpdatedEvent() { buildInfoManager.ProjectDirty = true; if (AH_SettingsManager.Instance.AutoOpenLog) RefreshBuildLog(); } private void RefreshBuildLog() { if (buildInfoManager != null && buildInfoManager.HasSelection) { m_Initialized = false; buildInfoManager.RefreshBuildInfo(); } } private void openBuildInfoSelector() { string fileSelected = EditorUtility.OpenFilePanel("", AH_SerializationHelper.GetBuildInfoFolder(), AH_SerializationHelper.BuildInfoExtension); if (!string.IsNullOrEmpty(fileSelected)) { m_Initialized = false; buildInfoManager.SelectBuildInfo(fileSelected); } } Rect toolbarRect { get { return new Rect(UiStartPos.x, UiStartPos.y + (AH_SettingsManager.Instance.HideButtonText ? 20 : 0), position.width - (UiStartPos.x * 2), 20f); } } Rect multiColumnTreeViewRect { get { return 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)); } } Rect assetInfoRect { get { return new Rect(UiStartPos.x, position.height - 66f, position.width - (UiStartPos.x * 2), 16f); } } Rect bottomToolbarRect { get { return new Rect(UiStartPos.x, position.height - 18, position.width - (UiStartPos.x * 2), 16f); } } public Vector2 UiStartPos { get { return uiStartPos; } set { uiStartPos = value; } } private void OnDestroy() { AH_TreeViewSelectionInfo.OnAssetDeleted -= m_window.OnAssetDeleted; #if UNITY_2018_1_OR_NEWER EditorApplication.projectChanged -= m_window.OnProjectChanged; #elif UNITY_5_6_OR_NEWER EditorApplication.projectWindowChanged -= m_window.OnProjectChanged; #endif } } }