using System;
using UnityEngine;
using UnityEditor;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System.Collections.Generic;
using System.Reflection;
using Object = UnityEngine.Object;
namespace Unity.Cinemachine.Editor
{
///
/// Helper for drawing embedded asset editors
///
static class EmbeddedAssetEditorUtility
{
///
/// Called after the asset editor is created, in case it needs to be customized
///
public delegate void OnCreateEditorDelegate(UnityEditor.Editor editor);
static bool s_CustomBlendsExpanded;
// This is to enable lambda capture of read/write variables
class EmbeddedEditorContext
{
public UnityEditor.Editor Editor = null;
public InspectorElement Inspector = null;
}
///
/// Call this to create an embedded inspector.
/// Will draw the asset reference field, and the embedded editor, or a Create
/// Asset button if no asset is set.
///
public static VisualElement EmbeddedAssetInspector(
SerializedProperty property, OnCreateEditorDelegate onCreateEditor) where T : ScriptableObject
{
var ux = new VisualElement();
// Asset field with create button
var unassignedUx = ux.AddChild(AssetSelectorWithPresets(property));
var foldout = new Foldout() { text = property.displayName, tooltip = property.tooltip, value = s_CustomBlendsExpanded };
foldout.RegisterValueChangedCallback((evt) =>
{
if (evt.target == foldout)
{
s_CustomBlendsExpanded = evt.newValue;
evt.StopPropagation();
}
});
var assignedUx = ux.AddChild(new InspectorUtility.FoldoutWithOverlay(
foldout, AssetSelectorWithPresets(property, ""), null) { style = { flexGrow = 1 }});
foldout.Add(AssetSelectorWithPresets(property, "Asset"));
foldout.AddSpace();
var borderColor = Color.grey;
const float borderWidth = 1f;
const float borderRadius = 5f;
var embeddedInspectorParent = foldout.AddChild(new VisualElement() { style =
{
borderTopColor = borderColor, borderTopWidth = borderWidth, borderTopLeftRadius = borderRadius,
borderBottomColor = borderColor, borderBottomWidth = borderWidth, borderBottomLeftRadius = borderRadius,
borderLeftColor = borderColor, borderLeftWidth = borderWidth, borderTopRightRadius = borderRadius,
borderRightColor = borderColor, borderRightWidth = borderWidth, borderBottomRightRadius = borderRadius,
}});
embeddedInspectorParent.Add(new HelpBox(
"This is a shared asset. Changes made here will apply to all users of this asset.",
HelpBoxMessageType.Info));
EmbeddedEditorContext context = new ();
OnAssetChanged(property, context);
ux.TrackPropertyValue(property, (p) => OnAssetChanged(p, context));
embeddedInspectorParent.RegisterCallback((e) => DestroyEditor(context));
return ux;
// Local function
void OnAssetChanged(SerializedProperty sProp, EmbeddedEditorContext eContext)
{
if (sProp.serializedObject == null)
return; // object deleted
sProp.serializedObject.ApplyModifiedProperties();
var target = sProp.objectReferenceValue;
if (eContext.Editor != null && eContext.Editor.target != target)
{
eContext.Inspector?.RemoveFromHierarchy();
DestroyEditor(eContext);
}
if (target != null)
{
if (eContext.Editor == null)
{
UnityEditor.Editor.CreateCachedEditor(target, null, ref eContext.Editor);
onCreateEditor?.Invoke(eContext.Editor);
}
if (embeddedInspectorParent != null)
eContext.Inspector = embeddedInspectorParent.AddChild(new InspectorElement(eContext.Editor));
}
if (unassignedUx != null)
unassignedUx.SetVisible(target == null);
if (assignedUx != null)
assignedUx.SetVisible(target != null);
}
// Local function
void DestroyEditor(EmbeddedEditorContext eContext)
{
if (eContext.Editor != null)
{
Object.DestroyImmediate(eContext.Editor);
eContext.Editor = null;
}
}
}
///
/// Create an asset selector widget with a presets popup.
///
public static VisualElement AssetSelectorWithPresets(
SerializedProperty property, string label = null,
string presetsPath = null, string warningTextIfNull = null) where T : ScriptableObject
{
var row = InspectorUtility.PropertyRow(property, out var selector, label);
var contents = row.Contents;
Label warningIcon = null;
if (!string.IsNullOrEmpty(warningTextIfNull))
{
warningIcon = InspectorUtility.MiniHelpIcon(warningTextIfNull);
contents.Insert(0, warningIcon);
}
var presetName = contents.AddChild(new TextField
{
isReadOnly = true,
tooltip = property.tooltip,
style = { alignSelf = Align.Center, flexBasis = 40, flexGrow = 1, marginLeft = 0 }
});
var defaultName = property.serializedObject.targetObject.name + " " + property.displayName;
var assetTypes = GetAssetTypes(typeof(T));
var presetAssets = GetPresets(assetTypes, presetsPath, out var presetNames);
contents.Add(InspectorUtility.MiniPopupButton(null, new ContextualMenuManipulator((evt) =>
{
evt.menu.AppendAction("Clear",
(action) =>
{
property.objectReferenceValue = null;
property.serializedObject.ApplyModifiedProperties();
},
(status) =>
{
var copyFrom = property.objectReferenceValue as ScriptableObject;
return copyFrom == null ? DropdownMenuAction.Status.Disabled : DropdownMenuAction.Status.Normal;
}
);
for (int i = 0; i < presetAssets.Count; ++i)
{
var a = presetAssets[i];
evt.menu.AppendAction(presetNames[i],
(action) =>
{
property.objectReferenceValue = a;
property.serializedObject.ApplyModifiedProperties();
}
);
}
evt.menu.AppendAction("Clone",
(action) =>
{
var copyFrom = property.objectReferenceValue as ScriptableObject;
if (copyFrom != null)
{
string title = "Create New " + copyFrom.GetType().Name + " asset";
var asset = CreateAsset(copyFrom.GetType(), copyFrom, defaultName, title);
if (asset != null)
{
property.objectReferenceValue = asset;
property.serializedObject.ApplyModifiedProperties();
}
}
},
(status) =>
{
var copyFrom = property.objectReferenceValue as ScriptableObject;
return copyFrom == null ? DropdownMenuAction.Status.Disabled : DropdownMenuAction.Status.Normal;
}
);
for (int i = 0; i < assetTypes.Count; ++i)
{
var t = assetTypes[i];
evt.menu.AppendAction("New " + InspectorUtility.NicifyClassName(t),
(action) =>
{
var asset = CreateAsset(t, null, defaultName, "Create New " + t.Name + " asset");
if (asset != null)
{
property.objectReferenceValue = asset;
property.serializedObject.ApplyModifiedProperties();
}
}
);
}
})));
row.TrackPropertyWithInitialCallback(property, (p) =>
{
if (p.serializedObject == null)
return; // object deleted
var target = p.objectReferenceValue as ScriptableObject;
warningIcon?.SetVisible(target == null);
// Is it a preset?
int presetIndex;
for (presetIndex = presetAssets.Count - 1; presetIndex >= 0; --presetIndex)
if (target == presetAssets[presetIndex])
break;
if (presetIndex >= 0)
presetName.value = presetNames[presetIndex];
presetName.SetVisible(presetIndex >= 0);
selector.SetVisible(presetIndex < 0);
});
return row;
// Local function
static List GetAssetTypes(Type baseType)
{
// GML todo: optimize with TypeCache
var allTypes = ReflectionHelpers.GetTypesInAllDependentAssemblies(
(Type t) => baseType.IsAssignableFrom(t) && !t.IsAbstract
&& t.GetCustomAttribute() == null);
var list = new List();
var iter = allTypes.GetEnumerator();
while (iter.MoveNext())
list.Add(iter.Current);
return list;
}
// Local function
static List GetPresets(
List assetTypes, string presetPath, out List presetNames)
{
presetNames = new List();
var presetAssets = new List();
if (!string.IsNullOrEmpty(presetPath))
{
for (int i = 0; i < assetTypes.Count; ++i)
InspectorUtility.AddAssetsFromPackageSubDirectory(assetTypes[i], presetAssets, presetPath);
for (int i = 0; i < presetAssets.Count; ++i)
presetNames.Add("Presets/" + presetAssets[i].name);
}
return presetAssets;
}
// Local function
static ScriptableObject CreateAsset(
Type assetType, ScriptableObject copyFrom, string defaultName, string dialogTitle)
{
ScriptableObject asset = null;
string path = EditorUtility.SaveFilePanelInProject(
dialogTitle, defaultName, "asset", string.Empty);
if (!string.IsNullOrEmpty(path))
{
if (copyFrom == null)
asset = ScriptableObjectUtility.CreateAt(assetType, path);
else
{
string fromPath = AssetDatabase.GetAssetPath(copyFrom);
if (AssetDatabase.CopyAsset(fromPath, path))
asset = AssetDatabase.LoadAssetAtPath(path, assetType) as ScriptableObject;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
return asset;
}
}
}
}