forked from Shardstone/trail-into-darkness
Added a bunch of utilities and modfief the character data structue
This commit is contained in:
8
Packages/com.jovian.recentassets/Editor.meta
Normal file
8
Packages/com.jovian.recentassets/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67dd42ef1f8eb4c4d82ef17efd711ead
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Jovian.AssetsHistory",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 924d34ce263e4c44189ac3e93726603c
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
387
Packages/com.jovian.recentassets/Editor/RecentAssetsMenu.cs
Normal file
387
Packages/com.jovian.recentassets/Editor/RecentAssetsMenu.cs
Normal file
@@ -0,0 +1,387 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.IO;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Jovian.Recents {
|
||||
public class RecentAssets : EditorWindow {
|
||||
|
||||
[System.Serializable]
|
||||
private class AssetSelection {
|
||||
public bool expanded;
|
||||
public string assetPath;
|
||||
|
||||
[System.NonSerialized]
|
||||
private Object actualAsset;
|
||||
|
||||
public Object Asset {
|
||||
get {
|
||||
if(actualAsset == null) {
|
||||
actualAsset = AssetDatabase.LoadMainAssetAtPath(assetPath);
|
||||
}
|
||||
return actualAsset;
|
||||
}
|
||||
set => actualAsset = value;
|
||||
}
|
||||
|
||||
public List<SubAssetSelection> subAssets;
|
||||
public bool isPinned;
|
||||
public string FileName => Path.GetFileName(assetPath);
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct SubAssetSelection {
|
||||
// In the current structure of this tool, to reload this object is quite messy.
|
||||
// The SubAsset doesn't know if the current context, e.g. scene, is the one it was previously selected from.
|
||||
// There might be another asset in this other scene also called "Main Camera" etc
|
||||
[System.NonSerialized]
|
||||
public Object subAsset;
|
||||
|
||||
public string subAssetPath;
|
||||
}
|
||||
|
||||
private bool subscribeToSelectionEvents;
|
||||
|
||||
private bool SubscribeToSelectionEvents {
|
||||
set {
|
||||
if(value) {
|
||||
Selection.selectionChanged -= OnSelectionChanged;
|
||||
Selection.selectionChanged += OnSelectionChanged;
|
||||
}
|
||||
else {
|
||||
Selection.selectionChanged -= OnSelectionChanged;
|
||||
}
|
||||
subscribeToSelectionEvents = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool subscribeToPrefabOpenEvents;
|
||||
|
||||
private bool SubscribeToPrefabOpen {
|
||||
set {
|
||||
PrefabStage.prefabStageOpened -= OnPrefabStageOpened;
|
||||
PrefabStage.prefabStageOpened += OnPrefabStageOpened;
|
||||
PrefabStage.prefabStageClosing -= OnPrefabStageClosing;
|
||||
PrefabStage.prefabStageClosing += OnPrefabStageClosing;
|
||||
subscribeToPrefabOpenEvents = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool subscribeToSceneOpenEvents;
|
||||
|
||||
private bool SubscribeToSceneOpenEvents {
|
||||
set {
|
||||
if(value) {
|
||||
EditorSceneManager.activeSceneChangedInEditMode -= OnActiveSceneChangedInEditMode;
|
||||
EditorSceneManager.activeSceneChangedInEditMode += OnActiveSceneChangedInEditMode;
|
||||
}
|
||||
else {
|
||||
EditorSceneManager.activeSceneChangedInEditMode -= OnActiveSceneChangedInEditMode;
|
||||
}
|
||||
subscribeToSceneOpenEvents = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string EditorPrefsSettingsSelectionKey => $"{Application.dataPath}Jovian_RecentOpenedAssetsWindow_AssetSelections_Selections";
|
||||
private string EditorPrefsSettingsPrefabsKey => $"{Application.dataPath}Jovian_RecentOpenedAssetsWindow_AssetSelections_Prefabs";
|
||||
private string EditorPrefsSettingsScenesKey => $"{Application.dataPath}Jovian_RecentOpenedAssetsWindow_AssetSelections_Scenes";
|
||||
private string EditorPrefsSettingsCountKey => $"{Application.dataPath}Jovian_RecentOpenedAssetsWindow_AssetSelections_MaxHistory";
|
||||
private string EditorPrefsSettingsHistoryKey => $"{Application.dataPath}Jovian_RecentOpenedAssetsWindow_AssetSelections";
|
||||
|
||||
private const string PinnedIcon = "IN LockButton on";
|
||||
private const string UnPinnedIcon = "IN LockButton";
|
||||
|
||||
[System.Serializable]
|
||||
private class JsonWrapper {
|
||||
public JsonWrapper(List<AssetSelection> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public List<AssetSelection> data;
|
||||
}
|
||||
|
||||
private List<AssetSelection> selectionHistory = new();
|
||||
private int maxHistory = 10;
|
||||
private Vector2 scrollPos;
|
||||
|
||||
private PrefabStage prefabStage;
|
||||
private GUIStyle guiStyle;
|
||||
|
||||
[MenuItem("Jovian/Assets History...", false, 20)]
|
||||
private static void Init() {
|
||||
var window = GetWindow<RecentAssets>(false, "Assets History");
|
||||
window.minSize = new Vector2(100f, 100f);
|
||||
window.Focus();
|
||||
}
|
||||
|
||||
private void OnEnable() {
|
||||
// In case of Editor reload, perhaps because of recompile, events needs to be resubscribed
|
||||
LoadSettings();
|
||||
SubscribeToSelectionEvents = subscribeToSelectionEvents;
|
||||
SubscribeToSceneOpenEvents = subscribeToSceneOpenEvents;
|
||||
SubscribeToPrefabOpen = subscribeToPrefabOpenEvents;
|
||||
|
||||
LoadEntries();
|
||||
}
|
||||
|
||||
private void OnDisable() {
|
||||
SaveEntries();
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
private void OnDestroy() {
|
||||
SubscribeToSelectionEvents = false;
|
||||
SubscribeToSceneOpenEvents = false;
|
||||
SubscribeToPrefabOpen = false;
|
||||
}
|
||||
private void SaveEntries() {
|
||||
var jsonList = JsonUtility.ToJson(new JsonWrapper(selectionHistory));
|
||||
EditorPrefs.SetString(EditorPrefsSettingsHistoryKey, jsonList);
|
||||
}
|
||||
|
||||
private void SaveSettings() {
|
||||
EditorPrefs.SetBool(EditorPrefsSettingsSelectionKey, subscribeToSelectionEvents);
|
||||
EditorPrefs.SetBool(EditorPrefsSettingsPrefabsKey, subscribeToPrefabOpenEvents);
|
||||
EditorPrefs.SetBool(EditorPrefsSettingsScenesKey, subscribeToSceneOpenEvents);
|
||||
EditorPrefs.SetInt(EditorPrefsSettingsCountKey, maxHistory);
|
||||
}
|
||||
|
||||
private void LoadEntries() {
|
||||
var jsonList = EditorPrefs.GetString(EditorPrefsSettingsHistoryKey);
|
||||
var oldList = JsonUtility.FromJson<JsonWrapper>(jsonList)?.data;
|
||||
if(oldList != null) {
|
||||
selectionHistory = oldList;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSettings() {
|
||||
if(EditorPrefs.HasKey(EditorPrefsSettingsSelectionKey)) {
|
||||
subscribeToSelectionEvents = EditorPrefs.GetBool(EditorPrefsSettingsSelectionKey);
|
||||
}
|
||||
if(EditorPrefs.HasKey(EditorPrefsSettingsPrefabsKey)) {
|
||||
subscribeToPrefabOpenEvents = EditorPrefs.GetBool(EditorPrefsSettingsPrefabsKey);
|
||||
}
|
||||
if(EditorPrefs.HasKey(EditorPrefsSettingsScenesKey)) {
|
||||
subscribeToSceneOpenEvents = EditorPrefs.GetBool(EditorPrefsSettingsScenesKey);
|
||||
}
|
||||
if(EditorPrefs.HasKey(EditorPrefsSettingsCountKey)) {
|
||||
maxHistory = EditorPrefs.GetInt(EditorPrefsSettingsCountKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActiveSceneChangedInEditMode(Scene previousScene, Scene newScene) {
|
||||
var selectedObject = AssetDatabase.LoadMainAssetAtPath(newScene.path);
|
||||
if(selectedObject != null) {
|
||||
OnAssetInteracted(selectedObject, newScene.path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnPrefabStageOpened(PrefabStage prefab) {
|
||||
// We actually need to know if we're in prefabStage because of how asset parenting works during prefab editing, e.g. when _subscribeToSelectionEvents==true;
|
||||
prefabStage = prefab;
|
||||
if(subscribeToPrefabOpenEvents) {
|
||||
OnAssetInteracted(prefab.prefabContentsRoot, prefab.assetPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPrefabStageClosing(PrefabStage prefab) {
|
||||
prefabStage = null;
|
||||
}
|
||||
|
||||
private void OnSelectionChanged() {
|
||||
var selectedObject = Selection.activeObject;
|
||||
if(!selectedObject) {
|
||||
return; // When you select something in e.g. Project View that isn't an asset or object, perhaps the category header "Packages"
|
||||
}
|
||||
string assetPath;
|
||||
if(prefabStage != null && selectedObject is GameObject selectedGameObject && prefabStage.IsPartOfPrefabContents(selectedGameObject)) {
|
||||
assetPath = prefabStage.assetPath; // If we're in a PrefabStage and selecting a child GO, the assetPath is the actual prefab itself, not this potentially nested entity
|
||||
}
|
||||
else {
|
||||
assetPath = AssetDatabase.GetAssetOrScenePath(selectedObject);
|
||||
}
|
||||
|
||||
OnAssetInteracted(selectedObject, assetPath);
|
||||
}
|
||||
|
||||
private void OnAssetInteracted(Object selectedObject, string assetPath) {
|
||||
AssetSelection selection = null;
|
||||
for(var i = 0; i < selectionHistory.Count; i++) {
|
||||
if(selectionHistory[i].assetPath == assetPath) {
|
||||
selection = selectionHistory[i];
|
||||
selectionHistory.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(selection == null) {
|
||||
// This is a new asset selection
|
||||
selection = new AssetSelection {
|
||||
expanded = false,
|
||||
assetPath = assetPath,
|
||||
Asset = AssetDatabase.LoadMainAssetAtPath(assetPath),
|
||||
subAssets = new List<SubAssetSelection>()
|
||||
};
|
||||
}
|
||||
|
||||
selectionHistory.Insert(0, selection); // Selection is now first in list
|
||||
|
||||
var subAssetSelection = new SubAssetSelection {
|
||||
subAsset = selectedObject,
|
||||
subAssetPath = selectedObject.name
|
||||
};
|
||||
for(var i = 0; i < selection.subAssets.Count; i++) {
|
||||
if(selection.subAssets[i].subAsset == selectedObject ||
|
||||
// In case of unloading and reloading a scene, we try to "reuse" entries to the same object.
|
||||
// If several objects exist by the same name, they're already null and become a new entry "anyway"
|
||||
(selection.subAssets[i].subAsset == null && selection.subAssets[i].subAssetPath == subAssetSelection.subAssetPath)) {
|
||||
selection.subAssets.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
selection.subAssets.Insert(0, subAssetSelection);
|
||||
|
||||
// while instead of if, since you can change the number of tracked assets
|
||||
while(selectionHistory.Count > maxHistory) {
|
||||
selectionHistory.RemoveAt(selectionHistory.Count - 1);
|
||||
}
|
||||
UpdatePinned();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void UpdatePinned() {
|
||||
var numberOfAlreadyPinnedEntries = 0;
|
||||
for(var i = 0; i < selectionHistory.Count; i++) {
|
||||
var currSelection = selectionHistory[i];
|
||||
if(currSelection.isPinned) {
|
||||
if(i == numberOfAlreadyPinnedEntries) {
|
||||
numberOfAlreadyPinnedEntries++;
|
||||
continue;
|
||||
}
|
||||
selectionHistory.RemoveAt(i);
|
||||
selectionHistory.Insert(numberOfAlreadyPinnedEntries, currSelection);
|
||||
numberOfAlreadyPinnedEntries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI() {
|
||||
var originalIconSize = EditorGUIUtility.GetIconSize();
|
||||
EditorGUIUtility.SetIconSize(new Vector2(16f, 16f));
|
||||
if(guiStyle == null) {
|
||||
guiStyle = new GUIStyle(GUI.skin.button) {
|
||||
alignment = TextAnchor.MiddleLeft
|
||||
};
|
||||
}
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
TightLabel("History count", "The number of recent assets currently tracked");
|
||||
TightLabel(selectionHistory.Count.ToString());
|
||||
if(GUILayout.Button("Clear", GUILayout.ExpandWidth(false))) {
|
||||
selectionHistory.Clear();
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
TightLabel("What to track", "Select for what interactions you want this list to be updated");
|
||||
var onSelection = GUILayout.Toggle(subscribeToSelectionEvents, "On selection", GUILayout.ExpandWidth(false));
|
||||
if(onSelection != subscribeToSelectionEvents) {
|
||||
SubscribeToSelectionEvents = onSelection;
|
||||
}
|
||||
EditorGUI.BeginDisabledGroup(subscribeToSelectionEvents); // If we're listening to selection the other two doesn't matter
|
||||
subscribeToPrefabOpenEvents = GUILayout.Toggle(subscribeToPrefabOpenEvents, "Open prefabs", GUILayout.ExpandWidth(false));
|
||||
SubscribeToSceneOpenEvents = GUILayout.Toggle(subscribeToSceneOpenEvents, "Open scenes", GUILayout.ExpandWidth(false));
|
||||
EditorGUI.EndDisabledGroup();
|
||||
GUILayout.FlexibleSpace();
|
||||
TightLabel("Max history", "The number of recent assets to track");
|
||||
maxHistory = EditorGUILayout.DelayedIntField(maxHistory, GUILayout.Width(50f));
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
|
||||
var pinStatusChanged = false;
|
||||
for(var i = 0; i < selectionHistory.Count; i++) {
|
||||
var selection = selectionHistory[i];
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
// Pinning
|
||||
var pinIcon = selection.isPinned ? PinnedIcon : UnPinnedIcon;
|
||||
if(GUILayout.Button(EditorGUIUtility.IconContent(pinIcon), GUILayout.Width(20f), GUILayout.ExpandWidth(false))) {
|
||||
selection.isPinned = !selection.isPinned;
|
||||
pinStatusChanged = true;
|
||||
}
|
||||
|
||||
// Open
|
||||
if(GUILayout.Button(EditorGUIUtility.IconContent("d_editicon.sml"), GUILayout.Width(26f), GUILayout.ExpandWidth(false))) {
|
||||
if(Event.current.button == 1) {
|
||||
// On right click we only ping the object.
|
||||
EditorGUIUtility.PingObject(selection.Asset);
|
||||
}
|
||||
else {
|
||||
AssetDatabase.OpenAsset(selection.Asset);
|
||||
}
|
||||
}
|
||||
|
||||
// Object
|
||||
GUI.enabled = false;
|
||||
if(selection.Asset != null) {
|
||||
EditorGUILayout.ObjectField(selection.Asset, typeof(Object), false);
|
||||
}
|
||||
else {
|
||||
var labelNotFound = new GUIContent($"{selection.FileName} not found.",
|
||||
EditorGUIUtility.ObjectContent(selection.Asset, typeof(Object)).image, selection.assetPath);
|
||||
EditorGUILayout.LabelField(labelNotFound);
|
||||
}
|
||||
GUI.enabled = true;
|
||||
|
||||
// Sub entries
|
||||
GUILayout.Label(GUIContent.none, GUILayout.MinWidth(80f), GUILayout.MaxWidth(80f));
|
||||
var rect = GUILayoutUtility.GetLastRect();
|
||||
selection.expanded = EditorGUI.Foldout(rect, selection.expanded, "Sub entries", true);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
if(selection.expanded) {
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUI.indentLevel++;
|
||||
for(var y = 0; y < selection.subAssets.Count; y++) {
|
||||
var subAsset = selection.subAssets[y];
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.Space(10f, false); // As an indent
|
||||
var label = new GUIContent(subAsset.subAssetPath, EditorGUIUtility.ObjectContent(subAsset.subAsset, typeof(Object)).image);
|
||||
if(GUILayout.Button(label, guiStyle, GUILayout.MinWidth(50f), GUILayout.MaxWidth(200f), GUILayout.ExpandWidth(false))) {
|
||||
if(Event.current.button == 1) {
|
||||
// On right click we only ping the object.
|
||||
EditorGUIUtility.PingObject(subAsset.subAsset);
|
||||
}
|
||||
else {
|
||||
Selection.activeObject = subAsset.subAsset;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
EditorGUIUtility.SetIconSize(originalIconSize);
|
||||
if(pinStatusChanged) {
|
||||
UpdatePinned();
|
||||
}
|
||||
}
|
||||
|
||||
public static void TightLabel(string labelStr) {
|
||||
var label = new GUIContent(labelStr);
|
||||
TightLabel(label);
|
||||
}
|
||||
|
||||
public static void TightLabel(string labelStr, string tooltip) {
|
||||
var label = new GUIContent(labelStr, tooltip);
|
||||
TightLabel(label);
|
||||
}
|
||||
|
||||
public static void TightLabel(GUIContent label) {
|
||||
//This is the important bit, we set the width to the calculated width of the content in the GUIStyle of the control
|
||||
EditorGUILayout.LabelField(label, GUILayout.Width(GUI.skin.label.CalcSize(label).x));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 537374a9d1704fb408713e76df4b85f9
|
||||
6
Packages/com.jovian.recentassets/package.json
Normal file
6
Packages/com.jovian.recentassets/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "com.jovian.assets-history",
|
||||
"displayName": "Assets History Tracker",
|
||||
"version": "1.0.0",
|
||||
"description": "Helper to track recently used assets in the editor and display them in a menu."
|
||||
}
|
||||
7
Packages/com.jovian.recentassets/package.json.meta
Normal file
7
Packages/com.jovian.recentassets/package.json.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74e2fe99929f7e84a92d5d34512b6942
|
||||
PackageManifestImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user