Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,78 @@ public static class ManagedReferenceContextualPropertyMenu

private const string CopiedPropertyPathKey = "SerializeReferenceExtensions.CopiedPropertyPath";
private const string ClipboardKey = "SerializeReferenceExtensions.CopyAndPasteProperty";
private const string CopiedPropertyType = "SerializeReferenceExtensions.CopiedPropertyType";

private static readonly GUIContent PasteContent = new GUIContent("Paste Property");
private static readonly GUIContent NewInstanceContent = new GUIContent("New Instance");
private static readonly GUIContent ResetAndNewInstanceContent = new GUIContent("Reset and New Instance");

private enum ValuePasteState
{
Unavailable, // No copied value in clipboard
Allowed, // No issues detected
WillChangeType, // Pasting will overwrite type currently assigned to property
IncompatibleType, // The copied type isn't compatible with the target property
TypeNotFound, // The copied data referenced a type that cannot be found or is invalid
WillNullify, // The copied value is null, will nullify/delete the property value
TargetNullAllowed, // The target property is null, but otherwise compatible
TargetNullIncompatible // The target property is null and copied type is incompatible
}

[InitializeOnLoadMethod]
private static void Initialize ()
{
EditorApplication.contextualPropertyMenu += OnContextualPropertyMenu;
}

private static ValuePasteState GetValuePasteState (SerializedProperty property)
{
string copiedPropertyPath = SessionState.GetString(CopiedPropertyPathKey, string.Empty);

if (string.IsNullOrEmpty(copiedPropertyPath))
{
return ValuePasteState.Unavailable;
}

string copiedValueTypeName = SessionState.GetString(CopiedPropertyType, string.Empty);
Type copiedValueType = Type.GetType(copiedValueTypeName);

object currentPropertyValue = property.managedReferenceValue;

if (copiedValueType == null)
{
if (!string.IsNullOrEmpty(copiedValueTypeName)) return ValuePasteState.TypeNotFound;

return currentPropertyValue == null
? ValuePasteState.Unavailable
: ValuePasteState.WillNullify;
}

if (currentPropertyValue == null)
{
return IsValidTypeFor(property, copiedValueType)
? ValuePasteState.TargetNullAllowed
: ValuePasteState.TargetNullIncompatible;
}

if (copiedValueType == currentPropertyValue.GetType())
{
return ValuePasteState.Allowed;
}

return IsValidTypeFor(property, copiedValueType)
? ValuePasteState.WillChangeType
: ValuePasteState.IncompatibleType;
}

private static bool IsValidTypeFor (SerializedProperty property, Type candidateType)
{
Type baseType = ManagedReferenceUtility.GetType(property.managedReferenceFieldTypename);
if (baseType == null) return false;

return TypeSearchService.TypeCandiateService.IsCandidateQualified(baseType, candidateType);
}

private static void OnContextualPropertyMenu (GenericMenu menu, SerializedProperty property)
{
if (property.propertyType == SerializedPropertyType.ManagedReference)
Expand All @@ -32,14 +93,49 @@ private static void OnContextualPropertyMenu (GenericMenu menu, SerializedProper

menu.AddItem(new GUIContent($"Copy \"{property.propertyPath}\" property"), false, Copy, clonedProperty);

string typeName = SessionState.GetString(CopiedPropertyType, string.Empty);

string copiedPropertyPath = SessionState.GetString(CopiedPropertyPathKey, string.Empty);
if (!string.IsNullOrEmpty(copiedPropertyPath))
{
menu.AddItem(new GUIContent($"Paste \"{copiedPropertyPath}\" property"), false, Paste, clonedProperty);
}
else

Type targetType = Type.GetType(typeName);

string pasteLabel = $"Paste \"{copiedPropertyPath}\" property";

switch (GetValuePasteState(clonedProperty))
{
menu.AddDisabledItem(PasteContent);
case ValuePasteState.Allowed:
menu.AddItem(
new GUIContent(pasteLabel), false, PasteValues, clonedProperty);
break;
case ValuePasteState.WillChangeType:
menu.AddItem(
new GUIContent($"{pasteLabel}/Paste Type and Values (⚠️ Change type to {targetType?.Name})"),
false,
PasteTypeAndValues,
clonedProperty
);
menu.AddItem(new GUIContent($"{pasteLabel}/Paste Values Only"), false, PasteValues, clonedProperty);
break;
case ValuePasteState.WillNullify:
menu.AddItem(new GUIContent($"{pasteLabel} (⚠️ Set to null)"), false, PasteTypeAndValues, clonedProperty);
break;
case ValuePasteState.IncompatibleType:
menu.AddDisabledItem(new GUIContent($"{pasteLabel}/Paste Type and Values (❌ Incompatible type {targetType?.FullName})"));
menu.AddItem(new GUIContent($"{pasteLabel}/Paste Values Only"), false, PasteValues, clonedProperty);
break;
case ValuePasteState.TypeNotFound:
menu.AddDisabledItem(new GUIContent($"{pasteLabel}/Paste Type and Values (❌ Invalid type)"));
menu.AddItem(new GUIContent($"{pasteLabel}/Paste Values Only"), false, PasteValues, clonedProperty);
break;
case ValuePasteState.TargetNullAllowed:
menu.AddItem(new GUIContent($"Create New and Paste \"{copiedPropertyPath}\" property"), false, PasteTypeAndValues, clonedProperty);
break;
case ValuePasteState.TargetNullIncompatible:
menu.AddDisabledItem(new GUIContent($"Create New and Paste (❌ Incompatible type {targetType?.FullName})"));
break;
default:
menu.AddDisabledItem(PasteContent);
break;
}

menu.AddSeparator("");
Expand All @@ -64,22 +160,80 @@ private static void Copy (object customData)
string json = JsonUtility.ToJson(property.managedReferenceValue);
SessionState.SetString(CopiedPropertyPathKey, property.propertyPath);
SessionState.SetString(ClipboardKey, json);
if (property.managedReferenceValue == null)
{
SessionState.SetString(CopiedPropertyType, string.Empty);
}
else
{
string typeName = property.managedReferenceValue.GetType().AssemblyQualifiedName;
SessionState.SetString(CopiedPropertyType, typeName);
}
}

private static void Paste (object customData)
private static void PasteValues (object customData)
{
SerializedProperty property = (SerializedProperty)customData;
string json = SessionState.GetString(ClipboardKey, string.Empty);

if (string.IsNullOrEmpty(json))
{
return;
}

Undo.RecordObject(property.serializedObject.targetObject, "Paste Property");

object currentTargetValue = property.managedReferenceValue;

if (currentTargetValue == null)
{
return;
}
JsonUtility.FromJsonOverwrite(json, property.managedReferenceValue);
property.serializedObject.ApplyModifiedProperties();
}

private static void PasteTypeAndValues (object customData)
{
SerializedProperty property = (SerializedProperty)customData;
string json = SessionState.GetString(ClipboardKey, string.Empty);

string typeName = SessionState.GetString(CopiedPropertyType, string.Empty);
Type targetType = Type.GetType(typeName);

if (string.IsNullOrEmpty(json) && targetType != null)
{
return;
}
Undo.RecordObject(property.serializedObject.targetObject, "Paste Property");

// targetType can be null under two conditions:
// 1) The type was actually null (empty typeName)
// 2) The type was not found (non-empty typename)
if (targetType == null && !string.IsNullOrEmpty(typeName))
{
Debug.LogError($"Paste Failed: Could not find type {typeName}");
return;
}

object currentTargetValue = property.managedReferenceValue;

if (targetType == null)
{
property.managedReferenceValue = null;
}
else if (currentTargetValue == null || targetType != currentTargetValue.GetType())
{
object newInstance = Activator.CreateInstance(targetType);
JsonUtility.FromJsonOverwrite(json, newInstance);
property.managedReferenceValue = newInstance;
}
else
{
JsonUtility.FromJsonOverwrite(json, property.managedReferenceValue);
}
property.serializedObject.ApplyModifiedProperties();
}

private static void NewInstance (object customData)
{
SerializedProperty property = (SerializedProperty)customData;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,18 @@ public IReadOnlyList<Type> GetDisplayableTypes (Type baseType)

var candiateTypes = typeCandiateProvider.GetTypeCandidates(baseType);
var result = candiateTypes
.Where(intrinsicTypePolicy.IsAllowed)
.Where(t => typeCompatibilityPolicy.IsCompatible(baseType, t))
.Where(t => IsCandidateQualified(baseType, t))
.Distinct()
.ToArray();

typeCache.Add(baseType, result);
return result;
}

public bool IsCandidateQualified (Type baseType, Type candidateType)
{
return intrinsicTypePolicy.IsAllowed(candidateType)
&& typeCompatibilityPolicy.IsCompatible(baseType, candidateType);
}
}
}
Loading