Skip to content

Instantly share code, notes, and snippets.

@FlaShG
Last active March 14, 2024 04:53
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FlaShG/1c5f0042d06458696253f96098a83a99 to your computer and use it in GitHub Desktop.
Save FlaShG/1c5f0042d06458696253f96098a83a99 to your computer and use it in GitHub Desktop.
A Unity EditorWindow to pin objects to for quick selection.
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
using System.Collections.Generic;
/// <summary>
/// An editor window that lets you store assets and GameObjects in it for quickly finding them again.
/// </summary>
public class FavoritesWindow : EditorWindow, IHasCustomMenu
{
private readonly struct BackgroundColor : System.IDisposable
{
private readonly Color previousColor;
public BackgroundColor(Color color)
{
previousColor = GUI.backgroundColor;
GUI.backgroundColor = color;
}
public void Dispose()
{
GUI.backgroundColor = previousColor;
}
public static BackgroundColor Multiply(Color color)
{
return new BackgroundColor(color * GUI.backgroundColor);
}
}
[System.Serializable]
private struct ObjectInfo : ISerializationCallbackReceiver
{
public Object obj;
[SerializeField]
private string serializedId;
public GlobalObjectId id { get; private set; }
[SerializeField]
private string _name;
public string name
{
get
{
if (obj)
{
_name = obj.name;
}
return _name;
}
}
public ObjectInfo(Object obj)
{
this.obj = obj;
id = GlobalObjectId.GetGlobalObjectIdSlow(obj);
serializedId = id.ToString();
_name = obj.name;
}
public ObjectInfo(GlobalObjectId id, string name)
{
obj = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(id);
this.id = id;
serializedId = id.ToString();
_name = obj ? obj.name : name;
}
public override bool Equals(object obj)
{
if (GetType() != obj.GetType())
{
return false;
}
return this.obj == ((ObjectInfo)obj).obj;
}
public override int GetHashCode()
{
return obj.GetHashCode();
}
public void FindReferenceIfNull()
{
if (!obj)
{
obj = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(id);
}
}
public void OnBeforeSerialize() { }
public void OnAfterDeserialize()
{
GlobalObjectId.TryParse(serializedId, out var id);
this.id = id;
}
}
private static class DragAndDrop
{
private const float minDragPixels = 7.2f;
private static Vector2 clickPosition;
private static Object dragObject;
public static void MakeDragSource(Rect rect, Object objectToDrag)
{
var currentEvent = Event.current;
if (rect.Contains(currentEvent.mousePosition))
{
if (currentEvent.type == EventType.MouseDown)
{
clickPosition = currentEvent.mousePosition;
dragObject = objectToDrag;
}
else if (dragObject == objectToDrag &&
currentEvent.type == EventType.MouseDrag &&
Vector2.Distance(clickPosition, currentEvent.mousePosition) >= minDragPixels)
{
UnityEditor.DragAndDrop.PrepareStartDrag();
UnityEditor.DragAndDrop.objectReferences = new Object[] { objectToDrag };
// PrepareStartDrag is supposed to clear paths, but it didn't really.
UnityEditor.DragAndDrop.paths = System.Array.Empty<string>();
UnityEditor.DragAndDrop.StartDrag(objectToDrag.name);
currentEvent.Use();
}
}
}
}
private ReorderableList list;
[SerializeField]
private List<ObjectInfo> items = default;
private static GUIStyle buttonStyle;
private static GUIStyle removeStyle;
private Vector2 scrollPosition;
[MenuItem("Window/Favorites")]
private static void Open()
{
var window = GetWindow<FavoritesWindow>();
window.SetWindowTitle();
}
private void SetWindowTitle(string title = null)
{
if (title == null)
{
title = "Favorites";
}
var icon = EditorGUIUtility.IconContent("d_Favorite").image;
titleContent = new GUIContent(title, icon);
Repaint();
}
private void OnEnable()
{
RefreshList();
}
private void OnHierarchyChange()
{
RefreshList();
}
private void GenerateList()
{
if (items == null)
{
items = new List<ObjectInfo>();
}
if (list != null) return;
list = new ReorderableList(items, typeof(Object), true, false, false, false);
list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
if (index >= items.Count)
{
GUI.Label(rect, "Error");
return;
}
var typeRect = new Rect(rect.x, rect.y, 150, rect.height);
var buttonRect = new Rect(typeRect.xMax, rect.y, rect.width - 30 - 150, rect.height);
var removeRect = new Rect(buttonRect.xMax + 5, rect.y, 25, rect.height);
var referencedObject = items[index].obj;
if (referencedObject != null)
{
var isPrefab = referencedObject is GameObject && PrefabUtility.IsPartOfAnyPrefab(referencedObject);
GUI.Label(typeRect, isPrefab ? "Prefab" : referencedObject.GetType().Name);
DisplayItemButton(items[index], buttonRect);
}
else
{
GUI.Label(typeRect, "Missing");
GUI.Label(buttonRect, items[index].name);
}
using (BackgroundColor.Multiply(new Color(1, 0.5f, 0.5f)))
{
if (GUI.Button(removeRect, "X", removeStyle))
{
items.RemoveAt(index);
}
}
};
list.drawHeaderCallback = rect =>
{
GUI.Label(rect, titleContent.text);
};
}
private void DisplayItemButton(ObjectInfo info, Rect buttonRect)
{
var isScene = info.obj is SceneAsset;
var isPrefab = PrefabUtility.IsPartOfAnyPrefab(info.obj);
if (isScene || isPrefab)
{
var openButtonRect = new Rect(buttonRect);
openButtonRect.xMin = openButtonRect.xMax - 48;
if (GUI.Button(openButtonRect, "Open"))
{
if (isScene)
{
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(AssetDatabase.GetAssetPath(info.obj));
}
else
{
AssetDatabase.OpenAsset(info.obj);
}
}
buttonRect.xMax -= 50;
}
DragAndDrop.MakeDragSource(buttonRect, info.obj);
var icon = GetIconForObject(info.obj);
var buttonContent = new GUIContent(info.name, icon);
if (GUI.Button(buttonRect, buttonContent, buttonStyle))
{
Selection.activeObject = info.obj;
EditorGUIUtility.PingObject(info.obj);
}
}
private static Texture GetIconForObject(Object obj)
{
if (obj is GameObject)
{
return EditorGUIUtility.IconContent("GameObject Icon").image;
}
return AssetDatabase.GetCachedIcon(AssetDatabase.GetAssetPath(obj));
}
private void OnGUI()
{
GenerateList();
GenerateStyles();
const int padding = 10;
var addRect = new Rect(padding, padding, position.width - padding * 2, EditorGUIUtility.singleLineHeight);
var newObject = EditorGUI.ObjectField(addRect, new GUIContent("Add object"), null, typeof(Object), true);
if (newObject)
{
var objectInfo = new ObjectInfo(newObject);
items.Remove(objectInfo);
items.Insert(0, objectInfo);
}
var scrollRect = new Rect(5, 35, position.width - 5, position.height - 35);
var viewRect = new Rect(0, 0, position.width - 20, items.Count * list.elementHeight + 28);
scrollPosition = GUI.BeginScrollView(scrollRect, scrollPosition, viewRect, false, true, GUIStyle.none, GUI.skin.verticalScrollbar);
if (items != null && items.Count > 0)
{
list.DoList(viewRect);
}
else
{
DrawHintsWindow();
}
GUI.EndScrollView();
}
private void GenerateStyles()
{
if (buttonStyle != null) return;
buttonStyle = new GUIStyle(GUI.skin.button);
buttonStyle.alignment = TextAnchor.MiddleLeft;
removeStyle = new GUIStyle(GUI.skin.button);
removeStyle.border = new RectOffset(4, 4, 4, 4);
}
private void RefreshList()
{
if (items == null) return;
for (var i = 0; i < items.Count; i++)
{
if (items[i].obj == null)
{
items[i] = new ObjectInfo(items[i].id, items[i].name);
}
else
{
items[i] = new ObjectInfo(items[i].obj);
}
}
Repaint();
}
private void DrawHintsWindow()
{
const string hints = "• Drag an object into the box at the top to add it to the list.\n"
+ "• You can click on an object to select it or drag it somewhere.\n"
+ "• Use the context menu (top right) to open an additional favorites window with its own list.\n"
+ "• Use the context menu again to change the window title.";
EditorGUILayout.HelpBox(hints, MessageType.Info);
}
void IHasCustomMenu.AddItemsToMenu(GenericMenu menu)
{
menu.AddItem(new GUIContent("Open Additional Window"), false, () =>
{
var window = CreateWindow<FavoritesWindow>(typeof(FavoritesWindow));
window.SetWindowTitle();
});
menu.AddItem(new GUIContent("Change Window Title"), false, () =>
{
StringInputDialog.Show("Rename FavoritesWindow", "Please enter a new title:", titleContent.text, "Ok", "Cancel", SetWindowTitle);
});
}
}
using UnityEngine;
using UnityEditor;
using System;
public class StringInputDialog : EditorWindow
{
private const string inputField = "input field";
private string text;
private string ok;
private string cancel;
private string input;
private Action<string> callback;
public static void Show(string title,
string text,
string initialInput,
string ok,
string cancel,
Action<string> callback)
{
EditorApplication.delayCall += () =>
{
var window = GetWindow<StringInputDialog>(true, title);
window.text = text;
window.ok = ok;
window.input = initialInput;
window.cancel = cancel;
window.callback = callback;
window.maxSize = new Vector2(300, 100);
window.minSize = window.maxSize;
window.ShowModalUtility();
};
}
private void OnGUI()
{
GUILayout.Space(20);
GUILayout.Label(text);
GUI.FocusControl(inputField);
GUI.SetNextControlName(inputField);
input = GUILayout.TextField(input);
DisplayButtons();
}
private void DisplayButtons()
{
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button(ok) || IsEnterPressed())
{
callback(input);
Close();
}
if (GUILayout.Button(cancel) || IsEscapePressed())
{
Close();
}
GUILayout.EndHorizontal();
}
private static bool IsEnterPressed()
{
var e = Event.current;
return e.isKey && (e.keyCode == KeyCode.Return || e.keyCode == KeyCode.KeypadEnter);
}
private static bool IsEscapePressed()
{
var e = Event.current;
return e.isKey && (e.keyCode == KeyCode.Escape);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment