Skip to content

Instantly share code, notes, and snippets.

@zombience
Last active June 12, 2025 17:32
Show Gist options
  • 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

This code is deprecated. Leaving it here for archival purposes. Use package version here which is built on UIToolkit and usable in Unity 2022.3+

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