You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1280 lines
39 KiB
C#
1280 lines
39 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace AssetUsageDetectorNamespace
|
|
{
|
|
[Serializable]
|
|
public class SearchResultDrawParameters
|
|
{
|
|
public SearchResult searchResult;
|
|
public PathDrawingMode pathDrawingMode;
|
|
public bool showTooltips;
|
|
public bool noAssetDatabaseChanges;
|
|
public Rect guiRect;
|
|
public bool shouldRefreshEditorWindow;
|
|
public string tooltip;
|
|
|
|
public SearchResultDrawParameters( PathDrawingMode pathDrawingMode, bool showTooltips, bool noAssetDatabaseChanges )
|
|
{
|
|
this.pathDrawingMode = pathDrawingMode;
|
|
this.showTooltips = showTooltips;
|
|
this.noAssetDatabaseChanges = noAssetDatabaseChanges;
|
|
}
|
|
}
|
|
|
|
// Custom class to hold search results
|
|
[Serializable]
|
|
public class SearchResult : ISerializationCallbackReceiver
|
|
{
|
|
// Credit: https://docs.unity3d.com/Manual/script-Serialization-Custom.html
|
|
[Serializable]
|
|
public class SerializableResultGroup
|
|
{
|
|
public string title;
|
|
public SearchResultGroup.GroupType type;
|
|
public bool isExpanded;
|
|
public bool pendingSearch;
|
|
|
|
public List<int> initialSerializedNodes;
|
|
}
|
|
|
|
[Serializable]
|
|
public class SerializableNode
|
|
{
|
|
public int instanceId;
|
|
public bool isUnityObject;
|
|
public string description;
|
|
|
|
public List<int> links;
|
|
public List<string> linkDescriptions;
|
|
}
|
|
|
|
#pragma warning disable 1692
|
|
#pragma warning disable IDE0044
|
|
private bool success; // This is not readonly so that it can be serialized
|
|
#pragma warning restore IDE0044
|
|
#pragma warning restore 1692
|
|
|
|
private List<SearchResultGroup> result;
|
|
private SceneSetup[] initialSceneSetup;
|
|
|
|
private AssetUsageDetector searchHandler;
|
|
private AssetUsageDetector.Parameters m_searchParameters;
|
|
|
|
private float guiHeight; // Prevents scroll view's position from getting reset at domain reload
|
|
|
|
private List<SerializableNode> serializedNodes;
|
|
private List<SerializableResultGroup> serializedGroups;
|
|
|
|
public int NumberOfGroups { get { return result.Count; } }
|
|
public SearchResultGroup this[int index] { get { return result[index]; } }
|
|
|
|
public bool SearchCompletedSuccessfully { get { return success; } }
|
|
public bool InitialSceneSetupConfigured { get { return initialSceneSetup != null && initialSceneSetup.Length > 0; } }
|
|
public AssetUsageDetector.Parameters SearchParameters { get { return m_searchParameters; } }
|
|
|
|
public SearchResult( bool success, List<SearchResultGroup> result, SceneSetup[] initialSceneSetup, AssetUsageDetector searchHandler, AssetUsageDetector.Parameters searchParameters )
|
|
{
|
|
if( result == null )
|
|
result = new List<SearchResultGroup>( 0 );
|
|
|
|
this.success = success;
|
|
this.result = result;
|
|
this.initialSceneSetup = initialSceneSetup;
|
|
this.searchHandler = searchHandler;
|
|
this.m_searchParameters = searchParameters;
|
|
}
|
|
|
|
public void RefreshSearchResultGroup( SearchResultGroup searchResultGroup, bool noAssetDatabaseChanges )
|
|
{
|
|
if( searchResultGroup == null )
|
|
{
|
|
Debug.LogError( "SearchResultGroup is null!" );
|
|
return;
|
|
}
|
|
|
|
int searchResultGroupIndex = -1;
|
|
for( int i = 0; i < result.Count; i++ )
|
|
{
|
|
if( result[i] == searchResultGroup )
|
|
{
|
|
searchResultGroupIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( searchResultGroupIndex < 0 )
|
|
{
|
|
Debug.LogError( "SearchResultGroup is not a part of SearchResult!" );
|
|
return;
|
|
}
|
|
|
|
if( searchResultGroup.Type == SearchResultGroup.GroupType.Scene && EditorApplication.isPlaying && !EditorSceneManager.GetSceneByPath( searchResultGroup.Title ).isLoaded )
|
|
{
|
|
Debug.LogError( "Can't search unloaded scene while in Play Mode!" );
|
|
return;
|
|
}
|
|
|
|
if( searchHandler == null )
|
|
searchHandler = new AssetUsageDetector();
|
|
|
|
SceneSearchMode searchInScenes = m_searchParameters.searchInScenes;
|
|
Object[] searchInScenesSubset = m_searchParameters.searchInScenesSubset;
|
|
bool searchInAssetsFolder = m_searchParameters.searchInAssetsFolder;
|
|
Object[] searchInAssetsSubset = m_searchParameters.searchInAssetsSubset;
|
|
bool searchInProjectSettings = m_searchParameters.searchInProjectSettings;
|
|
|
|
try
|
|
{
|
|
if( searchResultGroup.Type == SearchResultGroup.GroupType.Assets )
|
|
{
|
|
m_searchParameters.searchInScenes = SceneSearchMode.None;
|
|
m_searchParameters.searchInScenesSubset = null;
|
|
m_searchParameters.searchInProjectSettings = false;
|
|
}
|
|
else if( searchResultGroup.Type == SearchResultGroup.GroupType.ProjectSettings )
|
|
{
|
|
m_searchParameters.searchInScenes = SceneSearchMode.None;
|
|
m_searchParameters.searchInScenesSubset = null;
|
|
m_searchParameters.searchInAssetsFolder = false;
|
|
m_searchParameters.searchInAssetsSubset = null;
|
|
m_searchParameters.searchInProjectSettings = true;
|
|
}
|
|
else if( searchResultGroup.Type == SearchResultGroup.GroupType.Scene )
|
|
{
|
|
m_searchParameters.searchInScenes = SceneSearchMode.None;
|
|
m_searchParameters.searchInScenesSubset = new Object[1] { AssetDatabase.LoadAssetAtPath<SceneAsset>( searchResultGroup.Title ) };
|
|
m_searchParameters.searchInAssetsFolder = false;
|
|
m_searchParameters.searchInAssetsSubset = null;
|
|
m_searchParameters.searchInProjectSettings = false;
|
|
}
|
|
else
|
|
{
|
|
m_searchParameters.searchInScenes = (SceneSearchMode) 1024; // A unique value to search only the DontDestroyOnLoad scene
|
|
m_searchParameters.searchInScenesSubset = null;
|
|
m_searchParameters.searchInAssetsFolder = false;
|
|
m_searchParameters.searchInAssetsSubset = null;
|
|
m_searchParameters.searchInProjectSettings = false;
|
|
}
|
|
|
|
m_searchParameters.lazySceneSearch = false;
|
|
m_searchParameters.noAssetDatabaseChanges = noAssetDatabaseChanges;
|
|
|
|
// Make sure the AssetDatabase is up-to-date
|
|
AssetDatabase.SaveAssets();
|
|
|
|
SearchResult searchResult = searchHandler.Run( m_searchParameters );
|
|
if( !searchResult.success )
|
|
{
|
|
EditorUtility.DisplayDialog( "Error", "Couldn't refresh, check console for more info.", "OK" );
|
|
return;
|
|
}
|
|
|
|
List<SearchResultGroup> searchResultGroups = searchResult.result;
|
|
if( searchResultGroups != null )
|
|
{
|
|
int newSearchResultGroupIndex = -1;
|
|
for( int i = 0; i < searchResultGroups.Count; i++ )
|
|
{
|
|
if( searchResultGroup.Title == searchResultGroups[i].Title )
|
|
{
|
|
newSearchResultGroupIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( newSearchResultGroupIndex >= 0 )
|
|
result[searchResultGroupIndex] = searchResultGroups[newSearchResultGroupIndex];
|
|
else
|
|
searchResultGroup.Clear();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
m_searchParameters.searchInScenes = searchInScenes;
|
|
m_searchParameters.searchInScenesSubset = searchInScenesSubset;
|
|
m_searchParameters.searchInAssetsFolder = searchInAssetsFolder;
|
|
m_searchParameters.searchInAssetsSubset = searchInAssetsSubset;
|
|
m_searchParameters.searchInProjectSettings = searchInProjectSettings;
|
|
}
|
|
}
|
|
|
|
public void DrawOnGUI( SearchResultDrawParameters parameters )
|
|
{
|
|
parameters.searchResult = this;
|
|
parameters.tooltip = null;
|
|
parameters.guiRect = EditorGUILayout.GetControlRect( Utilities.GL_EXPAND_WIDTH, Utilities.GL_HEIGHT_0 );
|
|
|
|
float guiInitialY = parameters.guiRect.y;
|
|
|
|
for( int i = 0; i < result.Count; i++ )
|
|
{
|
|
result[i].DrawOnGUI( parameters );
|
|
|
|
if( i < result.Count - 1 )
|
|
{
|
|
Rect rect = parameters.guiRect;
|
|
rect.y += 10f;
|
|
parameters.guiRect = rect;
|
|
}
|
|
}
|
|
|
|
if( Event.current.type == EventType.Repaint )
|
|
guiHeight = parameters.guiRect.y - guiInitialY;
|
|
|
|
// To work nicely with GUILayout and scroll view
|
|
EditorGUILayout.GetControlRect( Utilities.GL_EXPAND_WIDTH, GUILayout.Height( guiHeight ) );
|
|
|
|
if( !string.IsNullOrEmpty( parameters.tooltip ) )
|
|
{
|
|
// Show tooltip at mouse position
|
|
Vector2 mousePos = Event.current.mousePosition;
|
|
Vector2 size = Utilities.TooltipGUIStyle.CalcSize( new GUIContent( parameters.tooltip ) );
|
|
size.x += 10f;
|
|
|
|
Rect tooltipRect = new Rect( new Vector2( mousePos.x - size.x * 0.5f, mousePos.y - size.y ), size );
|
|
if( tooltipRect.xMin < 0f )
|
|
tooltipRect.x -= tooltipRect.xMin;
|
|
else if( tooltipRect.xMax > parameters.guiRect.width )
|
|
tooltipRect.x -= tooltipRect.xMax - parameters.guiRect.width;
|
|
|
|
GUI.Box( tooltipRect, parameters.tooltip, Utilities.TooltipGUIStyle );
|
|
}
|
|
|
|
if( parameters.shouldRefreshEditorWindow )
|
|
{
|
|
parameters.shouldRefreshEditorWindow = false;
|
|
|
|
EditorWindow focusedWindow = EditorWindow.focusedWindow;
|
|
if( focusedWindow != null )
|
|
focusedWindow.Repaint();
|
|
|
|
EditorWindow mouseOverWindow = EditorWindow.mouseOverWindow;
|
|
if( mouseOverWindow != null && mouseOverWindow != focusedWindow )
|
|
mouseOverWindow.Repaint();
|
|
}
|
|
}
|
|
|
|
public Object[] SelectAllAsObjects()
|
|
{
|
|
HashSet<Object> uniqueObjects = new HashSet<Object>();
|
|
for( int i = 0; i < result.Count; i++ )
|
|
result[i].AddObjectsTo( uniqueObjects );
|
|
|
|
if( uniqueObjects.Count > 0 )
|
|
{
|
|
Object[] objects = new Object[uniqueObjects.Count];
|
|
uniqueObjects.CopyTo( objects );
|
|
|
|
return objects;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public GameObject[] SelectAllAsGameObjects()
|
|
{
|
|
HashSet<GameObject> uniqueGameObjects = new HashSet<GameObject>();
|
|
for( int i = 0; i < result.Count; i++ )
|
|
result[i].AddGameObjectsTo( uniqueGameObjects );
|
|
|
|
if( uniqueGameObjects.Count > 0 )
|
|
{
|
|
GameObject[] objects = new GameObject[uniqueGameObjects.Count];
|
|
uniqueGameObjects.CopyTo( objects );
|
|
|
|
return objects;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Returns if RestoreInitialSceneSetup will have any effect on the current scene setup
|
|
public bool IsSceneSetupDifferentThanCurrentSetup()
|
|
{
|
|
if( initialSceneSetup == null )
|
|
return false;
|
|
|
|
SceneSetup[] sceneFinalSetup = EditorSceneManager.GetSceneManagerSetup();
|
|
if( initialSceneSetup.Length != sceneFinalSetup.Length )
|
|
return true;
|
|
|
|
for( int i = 0; i < sceneFinalSetup.Length; i++ )
|
|
{
|
|
bool sceneIsOneOfInitials = false;
|
|
for( int j = 0; j < initialSceneSetup.Length; j++ )
|
|
{
|
|
if( sceneFinalSetup[i].path == initialSceneSetup[j].path )
|
|
{
|
|
if( sceneFinalSetup[i].isLoaded != initialSceneSetup[j].isLoaded )
|
|
return true;
|
|
|
|
sceneIsOneOfInitials = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !sceneIsOneOfInitials )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Close the scenes that were not part of the initial scene setup
|
|
// Returns true if initial scene setup is restored successfully
|
|
public bool RestoreInitialSceneSetup( bool optional )
|
|
{
|
|
if( initialSceneSetup == null || initialSceneSetup.Length == 0 )
|
|
return true;
|
|
|
|
if( EditorApplication.isPlaying || !EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo() )
|
|
return false;
|
|
|
|
if( optional && IsSceneSetupDifferentThanCurrentSetup() && !EditorUtility.DisplayDialog( "Scenes", "Restore initial scene setup?", "Yes", "Leave it as is" ) )
|
|
return true;
|
|
|
|
for( int i = 0; i < initialSceneSetup.Length; i++ )
|
|
{
|
|
Scene scene = EditorSceneManager.GetSceneByPath( initialSceneSetup[i].path );
|
|
if( !scene.isLoaded )
|
|
scene = EditorSceneManager.OpenScene( initialSceneSetup[i].path, initialSceneSetup[i].isLoaded ? OpenSceneMode.Additive : OpenSceneMode.AdditiveWithoutLoading );
|
|
|
|
if( initialSceneSetup[i].isActive )
|
|
EditorSceneManager.SetActiveScene( scene );
|
|
}
|
|
|
|
SceneSetup[] sceneFinalSetup = EditorSceneManager.GetSceneManagerSetup();
|
|
for( int i = 0; i < sceneFinalSetup.Length; i++ )
|
|
{
|
|
bool sceneIsOneOfInitials = false;
|
|
for( int j = 0; j < initialSceneSetup.Length; j++ )
|
|
{
|
|
if( sceneFinalSetup[i].path == initialSceneSetup[j].path )
|
|
{
|
|
sceneIsOneOfInitials = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !sceneIsOneOfInitials )
|
|
EditorSceneManager.CloseScene( EditorSceneManager.GetSceneByPath( sceneFinalSetup[i].path ), true );
|
|
}
|
|
|
|
for( int i = 0; i < initialSceneSetup.Length; i++ )
|
|
{
|
|
if( !initialSceneSetup[i].isLoaded )
|
|
EditorSceneManager.CloseScene( EditorSceneManager.GetSceneByPath( initialSceneSetup[i].path ), false );
|
|
}
|
|
|
|
initialSceneSetup = null;
|
|
return true;
|
|
}
|
|
|
|
// Assembly reloading; serialize nodes in a way that Unity can serialize
|
|
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
|
{
|
|
if( result == null )
|
|
return;
|
|
|
|
if( serializedGroups == null )
|
|
serializedGroups = new List<SerializableResultGroup>( result.Count );
|
|
else
|
|
serializedGroups.Clear();
|
|
|
|
if( serializedNodes == null )
|
|
serializedNodes = new List<SerializableNode>( result.Count * 16 );
|
|
else
|
|
serializedNodes.Clear();
|
|
|
|
Dictionary<ReferenceNode, int> nodeToIndex = new Dictionary<ReferenceNode, int>( result.Count * 16 );
|
|
for( int i = 0; i < result.Count; i++ )
|
|
serializedGroups.Add( result[i].Serialize( nodeToIndex, serializedNodes ) );
|
|
}
|
|
|
|
// Assembly reloaded; deserialize nodes to construct the original graph
|
|
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
|
{
|
|
if( serializedGroups == null || serializedNodes == null )
|
|
return;
|
|
|
|
if( result == null )
|
|
result = new List<SearchResultGroup>( serializedGroups.Count );
|
|
else
|
|
result.Clear();
|
|
|
|
List<ReferenceNode> allNodes = new List<ReferenceNode>( serializedNodes.Count );
|
|
for( int i = 0; i < serializedNodes.Count; i++ )
|
|
allNodes.Add( new ReferenceNode() );
|
|
|
|
for( int i = 0; i < serializedNodes.Count; i++ )
|
|
allNodes[i].Deserialize( serializedNodes[i], allNodes );
|
|
|
|
for( int i = 0; i < serializedGroups.Count; i++ )
|
|
{
|
|
result.Add( new SearchResultGroup( serializedGroups[i].title, serializedGroups[i].type, serializedGroups[i].isExpanded, serializedGroups[i].pendingSearch ) );
|
|
result[i].Deserialize( serializedGroups[i], allNodes );
|
|
}
|
|
|
|
serializedNodes.Clear();
|
|
serializedGroups.Clear();
|
|
}
|
|
}
|
|
|
|
// Custom class to hold the results for a single scene or Assets folder
|
|
public class SearchResultGroup
|
|
{
|
|
public enum GroupType { Assets = 0, Scene = 1, DontDestroyOnLoad = 2, ProjectSettings = 3 };
|
|
|
|
// Custom struct to hold a single path to a reference
|
|
public struct ReferencePath
|
|
{
|
|
public readonly ReferenceNode startNode;
|
|
public readonly int[] pathLinksToFollow;
|
|
|
|
public ReferencePath( ReferenceNode startNode, int[] pathIndices )
|
|
{
|
|
this.startNode = startNode;
|
|
pathLinksToFollow = pathIndices;
|
|
}
|
|
}
|
|
|
|
private const float HEADER_HEIGHT = 40f;
|
|
|
|
public string Title { get; private set; }
|
|
public GroupType Type { get; private set; }
|
|
public bool IsExpanded { get; private set; }
|
|
public bool PendingSearch { get; private set; }
|
|
|
|
private readonly List<ReferenceNode> references;
|
|
private List<ReferencePath> referencePathsShortUnique;
|
|
private List<ReferencePath> referencePathsShortest;
|
|
|
|
private float calculatedGUIWidth;
|
|
private float calculatedGUIHeight;
|
|
private PathDrawingMode calculatedGUIMode;
|
|
private ReferenceNodeGUI[] guiNodes;
|
|
|
|
public int NumberOfReferences { get { return references.Count; } }
|
|
public ReferenceNode this[int index] { get { return references[index]; } }
|
|
|
|
public SearchResultGroup( string title, GroupType type, bool isExpanded = true, bool pendingSearch = false )
|
|
{
|
|
Title = title;
|
|
Type = type;
|
|
IsExpanded = isExpanded;
|
|
PendingSearch = pendingSearch;
|
|
|
|
references = new List<ReferenceNode>();
|
|
referencePathsShortUnique = null;
|
|
referencePathsShortest = null;
|
|
|
|
calculatedGUIWidth = 0f;
|
|
guiNodes = null;
|
|
}
|
|
|
|
// Add a reference to the list
|
|
public void AddReference( ReferenceNode node )
|
|
{
|
|
references.Add( node );
|
|
}
|
|
|
|
// Removes all nodes
|
|
public void Clear()
|
|
{
|
|
references.Clear();
|
|
|
|
if( referencePathsShortUnique != null )
|
|
referencePathsShortUnique.Clear();
|
|
if( referencePathsShortest != null )
|
|
referencePathsShortest.Clear();
|
|
|
|
PendingSearch = false;
|
|
calculatedGUIWidth = 0f;
|
|
guiNodes = null;
|
|
}
|
|
|
|
// Initializes commonly used variables of the nodes
|
|
public void InitializeNodes( Func<object, ReferenceNode> nodeGetter )
|
|
{
|
|
// Remove root nodes that don't have any outgoing links or have null node objects (somehow)
|
|
for( int i = references.Count - 1; i >= 0; i-- )
|
|
{
|
|
if( references[i].NumberOfOutgoingLinks == 0 )
|
|
references.RemoveAtFast( i );
|
|
else
|
|
{
|
|
object nodeObject = references[i].nodeObject;
|
|
if( nodeObject == null || nodeObject.Equals( null ) )
|
|
references.RemoveAtFast( i );
|
|
}
|
|
}
|
|
|
|
for( int i = references.Count - 1; i >= 0; i-- )
|
|
{
|
|
references[i].VerifyLinksRecursively();
|
|
|
|
// Some links might be removed during verification
|
|
if( references[i].NumberOfOutgoingLinks == 0 )
|
|
references.RemoveAtFast( i );
|
|
}
|
|
|
|
if( references.Count == 0 )
|
|
return;
|
|
|
|
List<ReferenceNode> callStack = new List<ReferenceNode>( 8 );
|
|
|
|
// For simplicity's sake, get rid of root nodes that are already part of another node's hierarchy
|
|
for( int i = references.Count - 1; i >= 0; i-- )
|
|
{
|
|
if( IsRootNodePartOfAnotherRootNode( i, callStack ) )
|
|
references.RemoveAtFast( i );
|
|
}
|
|
|
|
// For clarity, a reference path shouldn't start with a sub-asset but instead with its corresponding main asset
|
|
for( int i = references.Count - 1; i >= 0; i-- )
|
|
{
|
|
object nodeObject = references[i].nodeObject;
|
|
if( nodeObject.IsAsset() && !AssetDatabase.IsMainAsset( (Object) nodeObject ) )
|
|
{
|
|
string assetPath = AssetDatabase.GetAssetPath( (Object) nodeObject );
|
|
if( string.IsNullOrEmpty( assetPath ) )
|
|
continue;
|
|
|
|
Object mainAsset = AssetDatabase.LoadMainAssetAtPath( assetPath );
|
|
if( mainAsset == null || mainAsset.Equals( null ) )
|
|
continue;
|
|
|
|
// We're already calling AssetDatabase.IsMainAsset but just in case...
|
|
if( ReferenceEquals( nodeObject, mainAsset ) )
|
|
continue;
|
|
|
|
if( nodeObject is Component && ( (Component) nodeObject ).gameObject == mainAsset )
|
|
continue;
|
|
|
|
// Get a ReferenceNode for the main asset, add a link to the sub-asset's node and change the root node
|
|
ReferenceNode newRootNode = nodeGetter( mainAsset );
|
|
newRootNode.AddLinkTo( references[i], ( nodeObject is Component || nodeObject is GameObject ) ? "Child object" : "Sub-asset" );
|
|
references[i] = newRootNode;
|
|
|
|
// Make sure that the new root node isn't already a part of another node's hierarchy
|
|
if( IsRootNodePartOfAnotherRootNode( i, callStack ) )
|
|
references.RemoveAtFast( i );
|
|
}
|
|
}
|
|
|
|
for( int i = 0; i < references.Count; i++ )
|
|
references[i].InitializeRecursively();
|
|
}
|
|
|
|
// Check if node exists in this results set
|
|
public bool Contains( ReferenceNode node )
|
|
{
|
|
for( int i = 0; i < references.Count; i++ )
|
|
{
|
|
if( references[i] == node )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool IsRootNodePartOfAnotherRootNode( int index, List<ReferenceNode> callStack )
|
|
{
|
|
ReferenceNode node = references[index];
|
|
for( int i = references.Count - 1; i >= 0; i-- )
|
|
{
|
|
if( index == i )
|
|
continue;
|
|
|
|
if( references[i].nodeObject == node.nodeObject || references[i].NodeExistsInChildrenRecursive( node, callStack ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Add all the Object's in this container to the set
|
|
public void AddObjectsTo( HashSet<Object> objectsSet )
|
|
{
|
|
if( PendingSearch )
|
|
return;
|
|
|
|
CalculateShortestPathsToReferences();
|
|
|
|
for( int i = 0; i < referencePathsShortUnique.Count; i++ )
|
|
{
|
|
Object obj = referencePathsShortUnique[i].startNode.UnityObject;
|
|
if( obj != null && !obj.Equals( null ) )
|
|
objectsSet.Add( obj );
|
|
}
|
|
}
|
|
|
|
// Add all the GameObject's in this container to the set
|
|
public void AddGameObjectsTo( HashSet<GameObject> gameObjectsSet )
|
|
{
|
|
if( PendingSearch )
|
|
return;
|
|
|
|
CalculateShortestPathsToReferences();
|
|
|
|
for( int i = 0; i < referencePathsShortUnique.Count; i++ )
|
|
{
|
|
Object obj = referencePathsShortUnique[i].startNode.UnityObject;
|
|
if( obj != null && !obj.Equals( null ) )
|
|
{
|
|
if( obj is GameObject )
|
|
gameObjectsSet.Add( (GameObject) obj );
|
|
else if( obj is Component )
|
|
gameObjectsSet.Add( ( (Component) obj ).gameObject );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate short unique paths to the references
|
|
private void CalculateShortestPathsToReferences()
|
|
{
|
|
if( referencePathsShortUnique != null )
|
|
return;
|
|
|
|
referencePathsShortUnique = new List<ReferencePath>( 32 );
|
|
for( int i = 0; i < references.Count; i++ )
|
|
references[i].CalculateShortUniquePaths( referencePathsShortUnique, Type == GroupType.Scene || Type == GroupType.DontDestroyOnLoad );
|
|
|
|
referencePathsShortest = new List<ReferencePath>( referencePathsShortUnique.Count );
|
|
for( int i = 0; i < referencePathsShortUnique.Count; i++ )
|
|
{
|
|
int[] linksToFollow = referencePathsShortUnique[i].pathLinksToFollow;
|
|
|
|
// Find the last two nodes in this path
|
|
ReferenceNode nodeBeforeLast = referencePathsShortUnique[i].startNode;
|
|
for( int j = 0; j < linksToFollow.Length - 1; j++ )
|
|
nodeBeforeLast = nodeBeforeLast[linksToFollow[j]].targetNode;
|
|
|
|
// Check if these two nodes are unique
|
|
bool isUnique = true;
|
|
for( int j = 0; j < referencePathsShortest.Count; j++ )
|
|
{
|
|
ReferencePath path = referencePathsShortest[j];
|
|
if( path.startNode == nodeBeforeLast && path.pathLinksToFollow[0] == linksToFollow[linksToFollow.Length - 1] )
|
|
{
|
|
isUnique = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( isUnique )
|
|
referencePathsShortest.Add( new ReferencePath( nodeBeforeLast, new int[1] { linksToFollow[linksToFollow.Length - 1] } ) );
|
|
}
|
|
}
|
|
|
|
// Draw the results found for this container
|
|
public void DrawOnGUI( SearchResultDrawParameters parameters )
|
|
{
|
|
if( Event.current.type == EventType.Repaint && ( guiNodes == null || parameters.pathDrawingMode != calculatedGUIMode || parameters.guiRect.width != calculatedGUIWidth ) )
|
|
{
|
|
GenerateGUINodes( parameters );
|
|
parameters.shouldRefreshEditorWindow = true; // A repaint is needed to work nicely with GUILayout
|
|
}
|
|
|
|
Color c = GUI.backgroundColor;
|
|
GUI.backgroundColor = Color.cyan;
|
|
|
|
Rect rect = parameters.guiRect;
|
|
float width = rect.width;
|
|
rect.width = 40f;
|
|
rect.height = HEADER_HEIGHT;
|
|
if( GUI.Button( rect, IsExpanded ? "v" : ">" ) )
|
|
{
|
|
IsExpanded = !IsExpanded;
|
|
|
|
parameters.shouldRefreshEditorWindow = true;
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
|
|
rect.x += 40f;
|
|
rect.width = width - ( parameters.searchResult != null ? 140f : 40f );
|
|
if( GUI.Button( rect, Title, Utilities.BoxGUIStyle ) && Type == GroupType.Scene )
|
|
{
|
|
if( Event.current.button != 1 )
|
|
{
|
|
// If the container (scene, usually) is left clicked, highlight it on Project view
|
|
AssetDatabase.LoadAssetAtPath<SceneAsset>( Title ).SelectInEditor();
|
|
}
|
|
else if( !EditorApplication.isPlaying && SceneManager.loadedSceneCount > 1 )
|
|
{
|
|
// Show context menu when SearchResultGroup's header is right clicked
|
|
Scene scene = EditorSceneManager.GetSceneByPath( Title );
|
|
if( scene.isLoaded )
|
|
{
|
|
GenericMenu contextMenu = new GenericMenu();
|
|
contextMenu.AddItem( new GUIContent( "Close Scene" ), false, () =>
|
|
{
|
|
if( !scene.isDirty || EditorSceneManager.SaveModifiedScenesIfUserWantsTo( new Scene[1] { scene } ) )
|
|
EditorSceneManager.CloseScene( scene, true );
|
|
} );
|
|
contextMenu.ShowAsContext();
|
|
}
|
|
}
|
|
}
|
|
|
|
if( parameters.searchResult != null )
|
|
{
|
|
rect.x += width - 140f;
|
|
rect.width = 100f;
|
|
if( GUI.Button( rect, "Refresh" ) )
|
|
{
|
|
if( Utilities.AreScenesSaved() || ( EditorUtility.DisplayDialog( "Refresh", "Some scene(s) have unsaved changes, they must be saved before the refresh.", "Save Scenes", "Cancel" ) && EditorSceneManager.SaveOpenScenes() ) )
|
|
{
|
|
parameters.searchResult.RefreshSearchResultGroup( this, parameters.noAssetDatabaseChanges );
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
}
|
|
}
|
|
|
|
rect = parameters.guiRect;
|
|
rect.y += HEADER_HEIGHT + 5f;
|
|
|
|
if( IsExpanded )
|
|
{
|
|
// On light skin, yellow background looks better than light grey background
|
|
GUI.backgroundColor = EditorGUIUtility.isProSkin ? c : Color.yellow;
|
|
|
|
if( PendingSearch )
|
|
{
|
|
rect.height = 25f;
|
|
GUI.Box( rect, "Lazy Search: this scene potentially has some references, hit Refresh to find them", Utilities.BoxGUIStyle );
|
|
}
|
|
else if( references.Count == 0 )
|
|
{
|
|
rect.height = 25f;
|
|
GUI.Box( rect, "No references found...", Utilities.BoxGUIStyle );
|
|
}
|
|
else if( guiNodes != null )
|
|
{
|
|
rect.height = calculatedGUIHeight;
|
|
parameters.guiRect = rect;
|
|
|
|
for( int i = 0; i < guiNodes.Length; i++ )
|
|
guiNodes[i].DrawOnGUI( parameters );
|
|
}
|
|
|
|
rect.y += rect.height;
|
|
}
|
|
|
|
parameters.guiRect = rect;
|
|
GUI.backgroundColor = c;
|
|
}
|
|
|
|
private void GenerateGUINodes( SearchResultDrawParameters parameters )
|
|
{
|
|
float guiRectWidth = parameters.guiRect.width;
|
|
|
|
if( guiNodes == null || parameters.pathDrawingMode != calculatedGUIMode )
|
|
{
|
|
if( parameters.pathDrawingMode == PathDrawingMode.Full )
|
|
{
|
|
List<ReferenceNode> stack = new List<ReferenceNode>( 8 );
|
|
guiNodes = new ReferenceNodeGUI[references.Count];
|
|
|
|
for( int i = 0; i < guiNodes.Length; i++ )
|
|
guiNodes[i] = references[i].GenerateGUINodeRecursive( stack, null );
|
|
}
|
|
else
|
|
{
|
|
if( referencePathsShortUnique == null )
|
|
CalculateShortestPathsToReferences();
|
|
|
|
List<ReferencePath> pathsToDraw;
|
|
if( parameters.pathDrawingMode == PathDrawingMode.ShortRelevantParts )
|
|
pathsToDraw = referencePathsShortUnique;
|
|
else
|
|
pathsToDraw = referencePathsShortest;
|
|
|
|
guiNodes = new ReferenceNodeGUI[pathsToDraw.Count];
|
|
|
|
for( int i = 0; i < guiNodes.Length; i++ )
|
|
{
|
|
ReferencePath path = pathsToDraw[i];
|
|
ReferenceNode currentNode = path.startNode;
|
|
|
|
ReferenceNodeGUI currentNodeGUI = currentNode.GenerateGUINode( null );
|
|
guiNodes[i] = currentNodeGUI;
|
|
|
|
for( int j = 0; j < path.pathLinksToFollow.Length; j++ )
|
|
{
|
|
ReferenceNode.Link link = currentNode[path.pathLinksToFollow[j]];
|
|
currentNodeGUI.links = new ReferenceNodeGUI[1] { link.targetNode.GenerateGUINode( link.description ) };
|
|
|
|
currentNode = link.targetNode;
|
|
currentNodeGUI = currentNodeGUI.links[0];
|
|
}
|
|
|
|
currentNodeGUI.links = new ReferenceNodeGUI[0];
|
|
}
|
|
}
|
|
|
|
for( int i = 0; i < guiNodes.Length; i++ )
|
|
guiNodes[i].CalculateDepth();
|
|
}
|
|
|
|
calculatedGUIMode = parameters.pathDrawingMode;
|
|
calculatedGUIWidth = guiRectWidth;
|
|
|
|
calculatedGUIHeight = -5f; // will start from -5f + 5f = 0f
|
|
for( int i = 0; i < guiNodes.Length; i++ )
|
|
{
|
|
calculatedGUIHeight += 5f;
|
|
guiNodes[i].CalculateHeight( guiRectWidth );
|
|
guiNodes[i].CalculateOffset( new Vector2( 0f, calculatedGUIHeight ) );
|
|
calculatedGUIHeight += guiNodes[i].size.y;
|
|
}
|
|
}
|
|
|
|
// Serialize this result group
|
|
public SearchResult.SerializableResultGroup Serialize( Dictionary<ReferenceNode, int> nodeToIndex, List<SearchResult.SerializableNode> serializedNodes )
|
|
{
|
|
SearchResult.SerializableResultGroup serializedResultGroup = new SearchResult.SerializableResultGroup()
|
|
{
|
|
title = Title,
|
|
type = Type,
|
|
isExpanded = IsExpanded,
|
|
pendingSearch = PendingSearch
|
|
};
|
|
|
|
if( references != null )
|
|
{
|
|
serializedResultGroup.initialSerializedNodes = new List<int>( references.Count );
|
|
|
|
for( int i = 0; i < references.Count; i++ )
|
|
serializedResultGroup.initialSerializedNodes.Add( references[i].SerializeRecursively( nodeToIndex, serializedNodes ) );
|
|
}
|
|
|
|
return serializedResultGroup;
|
|
}
|
|
|
|
// Deserialize this result group from the serialized data
|
|
public void Deserialize( SearchResult.SerializableResultGroup serializedResultGroup, List<ReferenceNode> allNodes )
|
|
{
|
|
if( serializedResultGroup.initialSerializedNodes != null )
|
|
{
|
|
for( int i = 0; i < serializedResultGroup.initialSerializedNodes.Count; i++ )
|
|
references.Add( allNodes[serializedResultGroup.initialSerializedNodes[i]] );
|
|
}
|
|
|
|
referencePathsShortUnique = null;
|
|
referencePathsShortest = null;
|
|
}
|
|
}
|
|
|
|
// Custom class to hold an object in the path to a reference as a node
|
|
public class ReferenceNode
|
|
{
|
|
public struct Link
|
|
{
|
|
public readonly ReferenceNode targetNode;
|
|
public readonly string description;
|
|
|
|
public Link( ReferenceNode targetNode, string description )
|
|
{
|
|
this.targetNode = targetNode;
|
|
this.description = description;
|
|
}
|
|
}
|
|
|
|
// Unique identifier is used while serializing the node
|
|
private static int uid_last = 0;
|
|
private readonly int uid;
|
|
|
|
internal object nodeObject;
|
|
private int? instanceId; // instanceId of the nodeObject if it is a Unity object, null otherwise
|
|
private string description; // String to print on this node
|
|
|
|
private readonly List<Link> links;
|
|
|
|
public Object UnityObject { get { return instanceId.HasValue ? EditorUtility.InstanceIDToObject( instanceId.Value ) : null; } }
|
|
|
|
public int NumberOfOutgoingLinks { get { return links.Count; } }
|
|
public Link this[int index] { get { return links[index]; } }
|
|
|
|
public ReferenceNode()
|
|
{
|
|
links = new List<Link>( 2 );
|
|
uid = uid_last++;
|
|
}
|
|
|
|
// Add a one-way connection to another node
|
|
public void AddLinkTo( ReferenceNode nextNode, string description = null )
|
|
{
|
|
if( nextNode != null && nextNode != this )
|
|
{
|
|
if( !string.IsNullOrEmpty( description ) )
|
|
description = "[" + description + "]";
|
|
|
|
// Avoid duplicate links
|
|
for( int i = 0; i < links.Count; i++ )
|
|
{
|
|
if( links[i].targetNode == nextNode )
|
|
{
|
|
if( !string.IsNullOrEmpty( description ) )
|
|
{
|
|
if( !string.IsNullOrEmpty( links[i].description ) )
|
|
links[i] = new Link( links[i].targetNode, string.Concat( links[i].description, Environment.NewLine, description ) );
|
|
else
|
|
links[i] = new Link( links[i].targetNode, description );
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
links.Add( new Link( nextNode, description ) );
|
|
}
|
|
}
|
|
|
|
public void CopyReferencesTo( ReferenceNode other )
|
|
{
|
|
other.links.Clear();
|
|
other.links.AddRange( links );
|
|
}
|
|
|
|
// Remove any redundant connections that this node has
|
|
public void VerifyLinksRecursively()
|
|
{
|
|
// Make sure that this functions isn't called multiple times per node (avoids StackOverflowException)
|
|
if( description != null )
|
|
return;
|
|
|
|
description = "";
|
|
|
|
for( int i = links.Count - 1; i >= 0; i-- )
|
|
{
|
|
// Links from a GameObject to its components should be omitted if the component has no references (these are redundant links)
|
|
if( links[i].targetNode.links.Count == 0 && nodeObject is GameObject )
|
|
{
|
|
Component component = links[i].targetNode.nodeObject as Component;
|
|
if( component != null && !component.Equals( null ) && component.gameObject == (GameObject) nodeObject )
|
|
{
|
|
links.RemoveAtFast( i );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
links[i].targetNode.VerifyLinksRecursively();
|
|
}
|
|
|
|
description = null;
|
|
}
|
|
|
|
// Initialize node's commonly used variables
|
|
public void InitializeRecursively()
|
|
{
|
|
if( description != null ) // Already initialized
|
|
return;
|
|
|
|
Object unityObject = nodeObject as Object;
|
|
if( unityObject != null )
|
|
{
|
|
instanceId = unityObject.GetInstanceID();
|
|
description = unityObject.name + " (" + unityObject.GetType() + ")";
|
|
}
|
|
else if( nodeObject != null )
|
|
{
|
|
instanceId = null;
|
|
description = nodeObject.GetType() + " object";
|
|
}
|
|
else
|
|
{
|
|
instanceId = null;
|
|
description = "<<destroyed>>";
|
|
}
|
|
|
|
nodeObject = null; // don't hold Object reference, allow Unity to GC used memory
|
|
|
|
for( int i = 0; i < links.Count; i++ )
|
|
links[i].targetNode.InitializeRecursively();
|
|
}
|
|
|
|
// Returns whether or not specified node is part of this node's siblings
|
|
public bool NodeExistsInChildrenRecursive( ReferenceNode node, List<ReferenceNode> callStack )
|
|
{
|
|
if( callStack.ContainsFast( this ) )
|
|
return false;
|
|
|
|
for( int i = 0; i < links.Count; i++ )
|
|
{
|
|
if( links[i].targetNode == node )
|
|
return true;
|
|
}
|
|
|
|
callStack.Add( this );
|
|
|
|
for( int i = 0; i < links.Count; i++ )
|
|
{
|
|
if( links[i].targetNode.NodeExistsInChildrenRecursive( node, callStack ) )
|
|
{
|
|
callStack.RemoveAt( callStack.Count - 1 );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
callStack.RemoveAt( callStack.Count - 1 );
|
|
return false;
|
|
}
|
|
|
|
// Clear this node so that it can be reused later
|
|
public void Clear()
|
|
{
|
|
nodeObject = null;
|
|
links.Clear();
|
|
}
|
|
|
|
// Calculate short unique paths that start with this node
|
|
public void CalculateShortUniquePaths( List<SearchResultGroup.ReferencePath> currentPaths, bool startPathsWithSceneObjects )
|
|
{
|
|
CalculateShortUniquePaths( currentPaths, startPathsWithSceneObjects, new List<ReferenceNode>( 8 ), new List<int>( 8 ) { -1 }, 0, new List<ReferenceNode>( 8 ) );
|
|
}
|
|
|
|
// Just some boring calculations to find the short unique paths recursively
|
|
private void CalculateShortUniquePaths( List<SearchResultGroup.ReferencePath> shortestPaths, bool startPathsWithSceneObjects, List<ReferenceNode> currentPath, List<int> currentPathIndices, int latestObjectIndexInPath, List<ReferenceNode> stack )
|
|
{
|
|
int currentIndex = currentPath.Count;
|
|
currentPath.Add( this );
|
|
|
|
if( links.Count == 0 || stack.ContainsFast( this ) )
|
|
{
|
|
// Check if the path to the reference is unique (not discovered so far)
|
|
bool isUnique = true;
|
|
for( int i = 0; i < shortestPaths.Count; i++ )
|
|
{
|
|
if( shortestPaths[i].startNode == currentPath[latestObjectIndexInPath] && shortestPaths[i].pathLinksToFollow.Length == currentPathIndices.Count - latestObjectIndexInPath - 1 )
|
|
{
|
|
int j = latestObjectIndexInPath + 1;
|
|
for( int k = 0; j < currentPathIndices.Count; j++, k++ )
|
|
{
|
|
if( shortestPaths[i].pathLinksToFollow[k] != currentPathIndices[j] )
|
|
break;
|
|
}
|
|
|
|
if( j == currentPathIndices.Count )
|
|
{
|
|
isUnique = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't allow duplicate short paths
|
|
if( isUnique )
|
|
{
|
|
int[] pathIndices = new int[currentPathIndices.Count - latestObjectIndexInPath - 1];
|
|
for( int i = latestObjectIndexInPath + 1, j = 0; i < currentPathIndices.Count; i++, j++ )
|
|
pathIndices[j] = currentPathIndices[i];
|
|
|
|
shortestPaths.Add( new SearchResultGroup.ReferencePath( currentPath[latestObjectIndexInPath], pathIndices ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( instanceId.HasValue ) // nodeObject is Unity object
|
|
{
|
|
if( !startPathsWithSceneObjects || !AssetDatabase.Contains( instanceId.Value ) )
|
|
latestObjectIndexInPath = currentIndex;
|
|
}
|
|
|
|
for( int i = 0; i < links.Count; i++ )
|
|
{
|
|
currentPathIndices.Add( i );
|
|
stack.Add( this );
|
|
|
|
links[i].targetNode.CalculateShortUniquePaths( shortestPaths, startPathsWithSceneObjects, currentPath, currentPathIndices, latestObjectIndexInPath, stack );
|
|
|
|
stack.RemoveAt( stack.Count - 1 );
|
|
currentPathIndices.RemoveAt( currentIndex + 1 );
|
|
}
|
|
}
|
|
|
|
currentPath.RemoveAt( currentIndex );
|
|
}
|
|
|
|
public ReferenceNodeGUI GenerateGUINode( string linkToPrevNodeDescription )
|
|
{
|
|
return new ReferenceNodeGUI()
|
|
{
|
|
label = new GUIContent( string.IsNullOrEmpty( linkToPrevNodeDescription ) ? description : ( linkToPrevNodeDescription + "\n" + description ) ),
|
|
instanceId = instanceId
|
|
};
|
|
}
|
|
|
|
public ReferenceNodeGUI GenerateGUINodeRecursive( List<ReferenceNode> stack, string linkToPrevNodeDescription )
|
|
{
|
|
ReferenceNodeGUI guiNode = GenerateGUINode( linkToPrevNodeDescription );
|
|
|
|
if( stack.ContainsFast( this ) )
|
|
guiNode.links = new ReferenceNodeGUI[0];
|
|
else
|
|
{
|
|
stack.Add( this );
|
|
|
|
guiNode.links = new ReferenceNodeGUI[links.Count];
|
|
for( int i = 0; i < links.Count; i++ )
|
|
guiNode.links[i] = links[i].targetNode.GenerateGUINodeRecursive( stack, links[i].description );
|
|
|
|
stack.RemoveAt( stack.Count - 1 );
|
|
}
|
|
|
|
return guiNode;
|
|
}
|
|
|
|
// Serialize this node and its connected nodes recursively
|
|
public int SerializeRecursively( Dictionary<ReferenceNode, int> nodeToIndex, List<SearchResult.SerializableNode> serializedNodes )
|
|
{
|
|
int index;
|
|
if( nodeToIndex.TryGetValue( this, out index ) )
|
|
return index;
|
|
|
|
SearchResult.SerializableNode serializedNode = new SearchResult.SerializableNode()
|
|
{
|
|
instanceId = instanceId ?? 0,
|
|
isUnityObject = instanceId.HasValue,
|
|
description = description
|
|
};
|
|
|
|
index = serializedNodes.Count;
|
|
nodeToIndex[this] = index;
|
|
serializedNodes.Add( serializedNode );
|
|
|
|
if( links.Count > 0 )
|
|
{
|
|
serializedNode.links = new List<int>( links.Count );
|
|
serializedNode.linkDescriptions = new List<string>( links.Count );
|
|
|
|
for( int i = 0; i < links.Count; i++ )
|
|
{
|
|
serializedNode.links.Add( links[i].targetNode.SerializeRecursively( nodeToIndex, serializedNodes ) );
|
|
serializedNode.linkDescriptions.Add( links[i].description );
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
// Deserialize this node and its links from the serialized data
|
|
public void Deserialize( SearchResult.SerializableNode serializedNode, List<ReferenceNode> allNodes )
|
|
{
|
|
if( serializedNode.isUnityObject )
|
|
instanceId = serializedNode.instanceId;
|
|
else
|
|
instanceId = null;
|
|
|
|
description = serializedNode.description;
|
|
|
|
if( serializedNode.links != null )
|
|
{
|
|
for( int i = 0; i < serializedNode.links.Count; i++ )
|
|
links.Add( new Link( allNodes[serializedNode.links[i]], serializedNode.linkDescriptions[i] ) );
|
|
}
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return uid;
|
|
}
|
|
}
|
|
|
|
public class ReferenceNodeGUI
|
|
{
|
|
public GUIContent label;
|
|
public ReferenceNodeGUI[] links;
|
|
public int? instanceId;
|
|
|
|
private int depth;
|
|
|
|
private Vector2 offset;
|
|
public Vector2 size;
|
|
|
|
public void CalculateDepth()
|
|
{
|
|
depth = 0;
|
|
|
|
for( int i = 0; i < links.Length; i++ )
|
|
{
|
|
links[i].CalculateDepth();
|
|
if( links[i].depth > depth )
|
|
depth = links[i].depth;
|
|
}
|
|
|
|
depth++;
|
|
}
|
|
|
|
public void CalculateHeight( float totalWidth )
|
|
{
|
|
size.x = totalWidth / depth;
|
|
totalWidth -= size.x;
|
|
size.y = Utilities.BoxGUIStyle.CalcHeight( label, size.x );
|
|
float connectedNodesHeight = 0f;
|
|
for( int i = 0; i < links.Length; i++ )
|
|
{
|
|
links[i].CalculateHeight( totalWidth );
|
|
connectedNodesHeight += links[i].size.y;
|
|
}
|
|
|
|
if( size.y < connectedNodesHeight )
|
|
size.y = connectedNodesHeight;
|
|
else if( size.y > connectedNodesHeight )
|
|
ExpandHeightLeftToRight();
|
|
}
|
|
|
|
private void ExpandHeightLeftToRight()
|
|
{
|
|
float connectedNodesHeight = 0f;
|
|
for( int i = 0; i < links.Length; i++ )
|
|
connectedNodesHeight += links[i].size.y;
|
|
|
|
if( size.y > connectedNodesHeight )
|
|
{
|
|
for( int i = 0; i < links.Length; i++ )
|
|
{
|
|
links[i].size.y = size.y * links[i].size.y / connectedNodesHeight;
|
|
links[i].ExpandHeightLeftToRight();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CalculateOffset( Vector2 baseOffset )
|
|
{
|
|
offset = baseOffset;
|
|
baseOffset.x += size.x;
|
|
|
|
for( int i = 0; i < links.Length; i++ )
|
|
{
|
|
links[i].CalculateOffset( baseOffset );
|
|
baseOffset.y += links[i].size.y;
|
|
}
|
|
}
|
|
|
|
public void DrawOnGUI( SearchResultDrawParameters parameters )
|
|
{
|
|
Rect rect = parameters.guiRect;
|
|
rect.position += offset;
|
|
rect.size = size;
|
|
|
|
if( GUI.Button( rect, label, Utilities.BoxGUIStyle ) && instanceId.HasValue )
|
|
{
|
|
// If a reference is clicked, highlight it (either on Hierarchy view or Project view)
|
|
EditorUtility.InstanceIDToObject( instanceId.Value ).SelectInEditor();
|
|
}
|
|
|
|
for( int i = 0; i < links.Length; i++ )
|
|
links[i].DrawOnGUI( parameters );
|
|
|
|
if( parameters.showTooltips && Event.current.type == EventType.Repaint && rect.Contains( Event.current.mousePosition ) )
|
|
parameters.tooltip = label.text;
|
|
}
|
|
}
|
|
} |