RoboticArms/Library/PackageCache/com.unity.animation.rigging@68167b505d2b/Editor/AnimationRig/WeightedTransformArrayDrawer.cs
2025-11-17 15:16:36 +07:00

250 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Animations.Rigging;
using UnityObject = UnityEngine.Object;
namespace UnityEditor.Animations.Rigging
{
[CustomPropertyDrawer(typeof(WeightedTransformArray))]
sealed class WeightedTransformArrayDrawer : PropertyDrawer
{
const string k_LengthPath = "m_Length";
const string k_ItemTransformPath = nameof(WeightedTransform.transform);
const string k_ItemWeightPath = nameof(WeightedTransform.weight);
static readonly string[] k_ItemPropertyPaths =
Enumerable.Range(0, WeightedTransformArray.k_MaxLength).Select(i => $"m_Item{i}").ToArray();
// one reorderable list per unique property path
readonly Dictionary<string, (ReorderableList listControl, SerializedProperty lengthProperty)> m_GUIState =
new Dictionary<string, (ReorderableList listControl, SerializedProperty lengthProperty)>();
// function to get WeightedTransform from item property
static WeightedTransform GetValueSingle(SerializedProperty item) => new WeightedTransform
{
transform = item.FindPropertyRelative(k_ItemTransformPath).objectReferenceValue as Transform,
weight = item.FindPropertyRelative(k_ItemWeightPath).floatValue
};
// function to modify a list of items per target
static void ModifyItemsSingle(SerializedProperty parentProperty, Action<List<WeightedTransform>> modifyList)
{
foreach (var target in parentProperty.serializedObject.targetObjects)
{
using (var so = new SerializedObject(target))
{
var sp = so.FindProperty(parentProperty.propertyPath);
var length = sp.FindPropertyRelative(k_LengthPath);
// create a live list of items
var items =
Enumerable.Range(0, length.intValue)
.Select(i => GetValueSingle(sp.FindPropertyRelative(k_ItemPropertyPaths[i])))
.ToList();
// modify the list
modifyList(items);
// write the results back to the serialized data stream
for (var i = 0; i < items.Count; ++i)
{
var item = sp.FindPropertyRelative(k_ItemPropertyPaths[i]);
item.FindPropertyRelative(k_ItemTransformPath).objectReferenceValue = items[i].transform;
item.FindPropertyRelative(k_ItemWeightPath).floatValue = items[i].weight;
}
// clear other items
for (var i = items.Count; i < WeightedTransformArray.k_MaxLength; ++i)
{
var item = sp.FindPropertyRelative(k_ItemPropertyPaths[i]);
item.FindPropertyRelative(k_ItemTransformPath).objectReferenceValue = default;
item.FindPropertyRelative(k_ItemWeightPath).floatValue = default;
}
// synchronize length property
length.intValue = items.Count;
// write back results
so.ApplyModifiedProperties();
}
}
// update parent property's serialized data stream to get new (shared) values for all targets
parentProperty.serializedObject.Update();
}
(ReorderableList listControl, SerializedProperty lengthProperty) GetGUIState(SerializedProperty property, GUIContent label)
{
var lengthProperty = property.FindPropertyRelative(k_LengthPath);
// create a new reorderable list if one does not exist
if (!m_GUIState.TryGetValue(property.propertyPath, out var guiState))
{
// bind the control to a proxy list
var proxy = Enumerable.Range(0, lengthProperty.intValue)
.ToList();
var reorderableList = new ReorderableList(proxy, typeof(int));
reorderableList.headerHeight = Styles.minHeaderHeight;
// default array control only allocates single line height, but that leaves no spacing between object fields
reorderableList.elementHeight =
EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
var attr = fieldInfo.GetCustomAttribute<WeightRangeAttribute>();
var legacyAttr = fieldInfo.GetCustomAttribute<RangeAttribute>();
var min = attr?.min ?? legacyAttr?.min ?? float.NaN;
var max = attr?.max ?? legacyAttr?.max ?? float.NaN;
var spacing = EditorGUIUtility.standardVerticalSpacing / 2f;
reorderableList.drawElementCallback += (rect, index, active, focused) =>
{
rect = new Rect(rect) { height = EditorGUIUtility.singleLineHeight, y = rect.y + spacing };
WeightedTransformDrawer.DoGUI(rect, property.FindPropertyRelative(k_ItemPropertyPaths[index]), min, max);
};
reorderableList.onCanAddCallback += list =>
!Application.isPlaying
&& !AnimationMode.InAnimationMode()
&& lengthProperty.intValue < WeightedTransformArray.k_MaxLength;
reorderableList.onCanRemoveCallback += list =>
!Application.isPlaying
&& !AnimationMode.InAnimationMode()
&& lengthProperty.intValue > 0;
reorderableList.onAddCallback += list =>
{
ModifyItemsSingle(property, items =>
{
int insertIndex = Math.Max(0, reorderableList.index >= 0 ? reorderableList.index : items.Count - 1);
if (items.Count < WeightedTransformArray.k_MaxLength)
items.Insert(insertIndex, insertIndex < items.Count ? items[insertIndex] : WeightedTransform.Default(1f));
});
proxy.Add(proxy.Count);
};
reorderableList.onRemoveCallback += list =>
{
ModifyItemsSingle(property, items =>
{
int removeIndex = Math.Max(0, reorderableList.index >= 0 ? reorderableList.index : items.Count - 1);
if (removeIndex >= 0)
items.RemoveAt(removeIndex);
});
proxy.RemoveAt(proxy.Count - 1);
};
reorderableList.onReorderCallbackWithDetails += (list, srcIndex, dstIndex) =>
ModifyItemsSingle(property, items =>
{
var moved = items[srcIndex];
items.RemoveAt(srcIndex);
items.Insert(dstIndex, moved);
});
guiState = m_GUIState[property.propertyPath] = (reorderableList, lengthProperty.Copy());
}
// synchronize proxy list to serialized length
var proxyList = guiState.listControl.list;
while (proxyList.Count < lengthProperty.intValue)
proxyList.Add(proxyList.Count);
while (proxyList.Count > lengthProperty.intValue)
proxyList.RemoveAt(proxyList.Count - 1);
return guiState;
}
static class Styles
{
// cf. ReorderableList.Defaults.minHeaderHeight;
public static float minHeaderHeight = 2f;
// cf. ReorderableListWrapper.cs
public const float headerPadding = 3f;
public const float arraySizeWidth = 50f; // 48 in ReorderableListWrapper, but EditorGUI.Slider() field is 50
public const float defaultFoldoutHeaderHeight = 18f;
public static readonly GUIContent sizeLabel = EditorGUIUtility.TrTextContent("", "Length");
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) =>
Styles.defaultFoldoutHeaderHeight
+ (property.isExpanded ? Styles.headerPadding + GetGUIState(property, label).listControl.GetHeight() : 0f);
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var guiState = GetGUIState(property, label);
Rect headerRect = new Rect(position) { height = EditorGUIUtility.singleLineHeight };
Rect sizeRect = new Rect(headerRect) { xMin = headerRect.xMax - Styles.arraySizeWidth };
EventType prevType = Event.current.type;
if (Event.current.type == EventType.MouseUp && sizeRect.Contains(Event.current.mousePosition))
{
Event.current.type = EventType.Used;
}
property.isExpanded = EditorGUI.BeginFoldoutHeaderGroup(headerRect, property.isExpanded, label);
EditorGUI.EndFoldoutHeaderGroup();
if (Event.current.type == EventType.Used && sizeRect.Contains(Event.current.mousePosition)) Event.current.type = prevType;
EditorGUI.BeginChangeCheck();
EditorGUI.DelayedIntField(sizeRect, guiState.lengthProperty, Styles.sizeLabel);
if (EditorGUI.EndChangeCheck())
guiState.lengthProperty.intValue = Mathf.Clamp(guiState.lengthProperty.intValue, 0, WeightedTransformArray.k_MaxLength);
EditorGUI.LabelField(sizeRect, Styles.sizeLabel);
if (headerRect.Contains(Event.current.mousePosition))
{
if (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform)
{
OnDropObjects(property, DragAndDrop.objectReferences, guiState.listControl);
DragAndDrop.AcceptDrag();
Event.current.Use();
}
}
if (Event.current.type == EventType.DragExited)
{
DragAndDrop.visualMode = DragAndDropVisualMode.None;
Event.current.Use();
}
if (property.isExpanded)
guiState.listControl.DoList(new Rect(position) { yMin = headerRect.yMax + Styles.headerPadding });
}
static void OnDropObjects(SerializedProperty property, UnityObject[] objectReferences, ReorderableList listControl)
{
foreach (var o in objectReferences)
{
var go = o as GameObject;
var c = o as Component;
if (go == null && c == null)
continue;
if (listControl.list.Count >= WeightedTransformArray.k_MaxLength)
{
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
continue;
}
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
var t = c == null ? go.transform : c.transform;
if (Event.current.type == EventType.DragPerform)
{
ModifyItemsSingle(property, items =>
{
var weight = items.Count == 0 ? 1f : items[items.Count - 1].weight;
items.Add(new WeightedTransform(t, weight));
});
listControl.list.Add(listControl.list.Count);
}
}
}
}
}