Skip to content

Instantly share code, notes, and snippets.

@zombience
Last active February 26, 2020 00:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zombience/6103d72bdd9e2d6cc0126f891917253d to your computer and use it in GitHub Desktop.
Save zombience/6103d72bdd9e2d6cc0126f891917253d to your computer and use it in GitHub Desktop.
Selection Tracker: track your project and scene selection history. Get back to the last thing you were looking at. Good for large projects.
using UnityEditor;
using UnityEngine;
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using UObject = UnityEngine.Object;
namespace SelectionTracker
{
public class SelectionTrackerWindow : EditorWindow
{
[MenuItem("Utilities/Selection Tracker")]
static public void Init()
{
EditorWindow.GetWindow<SelectionTrackerWindow>().Show();
}
static SelectionStorage Storage => _storage ?? (_storage = GetStorageAsset());
static SelectionStorage _storage;
static string StorageLocation => _storageLocation ?? (_storageLocation = FindStorageLocation());
static string _storageLocation;
UObject lastObject;
List<SelectionHelper> ProjectSelectionHistory => Storage.projectSelections;
List<SelectionHelper> SceneSelectionHistory => Storage.sceneSelections;
Vector2
editorHistoryScroll = Vector2.zero,
sceneHistoryScroll = Vector2.zero;
void OnGUI()
{
Color c = GUI.backgroundColor;
GUI.backgroundColor = Color.cyan;
if (Storage == null)
{
GUILayout.Label("null storage object");
_storage = GetStorageAsset();
GUI.backgroundColor = c;
return;
}
Storage.historySize = EditorGUILayout.IntField(new GUIContent("history size"), Storage.historySize);
if (GUILayout.Button((Storage.showEditorAsset ? "hide" : "show") + " project history"))
{
Storage.showEditorAsset = !Storage.showEditorAsset;
}
GUI.backgroundColor = c;
if (Storage.showEditorAsset)
{
GUI.backgroundColor = Color.red;
if (GUILayout.Button("clear project history"))
{
ProjectSelectionHistory.Clear();
}
GUI.backgroundColor = c;
editorHistoryScroll = EditorGUILayout.BeginScrollView(editorHistoryScroll);
for (int i = ProjectSelectionHistory.Count - 1; i >= 0; i--)
{
var item = ProjectSelectionHistory[i];
EditorGUILayout.ObjectField(new GUIContent(), item.lastSelected, item.GetType(), true);
}
EditorGUILayout.EndScrollView();
}
GUI.backgroundColor = Color.cyan;
if (GUILayout.Button((Storage.showSceneAssets ? "hide" : "show") + " scene history"))
{
Storage.showSceneAssets = !Storage.showSceneAssets;
}
GUI.backgroundColor = c;
if (Storage.showSceneAssets)
{
GUI.backgroundColor = Color.red;
if (GUILayout.Button("clear scene history"))
{
SceneSelectionHistory.Clear();
}
GUI.backgroundColor = c;
sceneHistoryScroll = EditorGUILayout.BeginScrollView(sceneHistoryScroll);
for (int i = SceneSelectionHistory.Count - 1; i >= 0; i--)
{
var item = SceneSelectionHistory[i];
EditorGUILayout.ObjectField(new GUIContent(), item.lastSelected, item.GetType(), true);
}
EditorGUILayout.EndScrollView();
}
}
void Update()
{
if (Application.isPlaying) return; // ignore selections during play mode
if (Selection.activeObject != null && Selection.activeObject != lastObject)
{
ProjectSelectionHistory.RemoveAll(m => m == null || m.lastSelected == null);
SceneSelectionHistory.RemoveAll(m => m == null || m.lastSelected == null);
if (Selection.activeObject as DefaultAsset != null) return; // ignore assets such as folders
if (Selection.activeObject.name.Contains("Animation Event")) return; // ignore animation events
lastObject = Selection.activeObject;
if (lastObject as GameObject != null && (lastObject as GameObject).scene != null)
{
UpdateList(lastObject, SceneSelectionHistory);
TruncateList(SceneSelectionHistory);
}
else
{
UpdateList(lastObject, ProjectSelectionHistory);
TruncateList(ProjectSelectionHistory);
}
Repaint();
}
}
void OnFocus()
{
lastObject = null;
}
void UpdateList(UObject selected, List<SelectionHelper> list)
{
var match = list
.Where(h => h != null && h.lastSelected == selected)
.FirstOrDefault();
if (match != null)
{
list.Remove(match);
match.timeSelected = DateTime.UtcNow;
list.Add(match);
list = list
.OrderByDescending(h => h.timeSelected)
.ToList();
return;
}
match = new SelectionHelper()
{
lastSelected = selected,
timeSelected = DateTime.UtcNow,
};
list.Add(match);
list = list
.OrderByDescending(h => h.timeSelected)
.ToList();
}
void TruncateList(List<SelectionHelper> list)
{
// for some reason list = linq.RemoveRange was not actually removing items
// also list remains in reverse order despite linq.OrderBy
while (list.Count > Storage.historySize) list.RemoveAt(0);
}
static SelectionStorage GetStorageAsset()
{
SelectionStorage storageAsset = null;
storageAsset = AssetDatabase.LoadAssetAtPath<SelectionStorage>(StorageLocation);
if(storageAsset == null)
{
storageAsset = ScriptableObject.CreateInstance<SelectionStorage>();
AssetDatabase.CreateAsset(storageAsset, StorageLocation);
AssetDatabase.Refresh();
Debug.LogFormat("#EDITOR# SelectionStorage asset was not found, creating new keystorage at path: {0}", StorageLocation);
}
return storageAsset;
}
static public string FindStorageLocation()
{
var monoScript = AssetDatabase.FindAssets("SelectionTrackerWindow t: TextAsset")
.Select(a => AssetDatabase.GUIDToAssetPath(a))
.FirstOrDefault();
var fullpath = Directory.GetParent(monoScript).FullName;
return Path.Combine(fullpath.Substring(fullpath.IndexOf(@"Assets\")), "SelectionStorage.asset");
}
#region storage classes
public class SelectionStorage : ScriptableObject
{
public bool
showEditorAsset = true,
showSceneAssets = true;
public int historySize = 15;
public List<SelectionHelper> projectSelections = new List<SelectionHelper>();
public List<SelectionHelper> sceneSelections = new List<SelectionHelper>();
}
[Serializable]
public class SelectionHelper
{
public UObject lastSelected;
public DateTime timeSelected;
}
#endregion
}
[CustomEditor(typeof(SelectionTrackerWindow.SelectionStorage))]
public class SelectionStorageEditor : Editor
{
public override void OnInspectorGUI()
{
if(GUILayout.Button("open selection tracker window"))
{
SelectionTrackerWindow.Init();
}
}
}
}
@zombience
Copy link
Author

zombience commented Feb 3, 2020

Selection Tracker

This Unity3D editor script allows for both Project and Scene selection history for prefabs, scripts, assets, etc.

Why

This is intended to emulate Visual Studio's selection history where you can navigate backwards through code with a key command. I use it so often that I began to want this selection history in Unity. So here it is.

How

This code can be put anywhere in your project, but expects to be stored finally in folder "SelectionTracker/Editor". A bit of editor code searches for this directory to create a ScriptableObject asset for selection history. If it cannot find this directory it will fail. To open, this utility defaults to a dropdown menu option under "Utilities/Selection Tracker" In order to actively track history, this window needs to be open and visible (i.e. docked is ok, but docked behind another tab will not work). It does not need to be in focus.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment