243 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			243 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using UnityEditor; | ||
|  | using UnityEngine; | ||
|  | 
 | ||
|  | namespace Unity.Mathematics.Editor | ||
|  | { | ||
|  |     [CustomPropertyDrawer(typeof(bool2)), CustomPropertyDrawer(typeof(bool3)), CustomPropertyDrawer(typeof(bool4))] | ||
|  |     [CustomPropertyDrawer(typeof(double2)), CustomPropertyDrawer(typeof(double3)), CustomPropertyDrawer(typeof(double4))] | ||
|  |     [CustomPropertyDrawer(typeof(float2)), CustomPropertyDrawer(typeof(float3)), CustomPropertyDrawer(typeof(float4))] | ||
|  |     [CustomPropertyDrawer(typeof(int2)), CustomPropertyDrawer(typeof(int3)), CustomPropertyDrawer(typeof(int4))] | ||
|  |     [CustomPropertyDrawer(typeof(uint2)), CustomPropertyDrawer(typeof(uint3)), CustomPropertyDrawer(typeof(uint4))] | ||
|  |     [CustomPropertyDrawer(typeof(DoNotNormalizeAttribute))] | ||
|  |     class PrimitiveVectorDrawer : PropertyDrawer | ||
|  |     { | ||
|  |         private string _PropertyType; | ||
|  | 
 | ||
|  |         string GetPropertyType(SerializedProperty property) | ||
|  |         { | ||
|  |             if (_PropertyType == null) | ||
|  |             { | ||
|  |                 _PropertyType = property.type; | ||
|  |                 var isManagedRef = property.type.StartsWith("managedReference", StringComparison.Ordinal); | ||
|  |                 if (isManagedRef) | ||
|  |                 { | ||
|  |                     var startIndex = "managedReference<".Length; | ||
|  |                     var length = _PropertyType.Length - startIndex - 1; | ||
|  |                     _PropertyType = _PropertyType.Substring("managedReference<".Length, length); | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return _PropertyType; | ||
|  |         } | ||
|  | 
 | ||
|  |         static class Content | ||
|  |         { | ||
|  |             public static readonly string doNotNormalizeCompatibility = L10n.Tr( | ||
|  |                 $"{typeof(DoNotNormalizeAttribute).Name} only works with {typeof(quaternion)} and primitive vector types." | ||
|  |             ); | ||
|  |             public static readonly string doNotNormalizeTooltip = | ||
|  |                 L10n.Tr("This value is not normalized, which may produce unexpected results."); | ||
|  | 
 | ||
|  |             public static readonly GUIContent[] labels2 = { new GUIContent("X"), new GUIContent("Y") }; | ||
|  |             public static readonly GUIContent[] labels3 = { new GUIContent("X"), new GUIContent("Y"), new GUIContent("Z") }; | ||
|  |             public static readonly GUIContent[] labels4 = { new GUIContent("X"), new GUIContent("Y"), new GUIContent("Z"), new GUIContent("W") }; | ||
|  |         } | ||
|  | 
 | ||
|  | #if !UNITY_2023_2_OR_NEWER | ||
|  |         public override bool CanCacheInspectorGUI(SerializedProperty property) | ||
|  |         { | ||
|  |             return false; | ||
|  |         } | ||
|  | #endif | ||
|  | 
 | ||
|  |         public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | ||
|  |         { | ||
|  |             var height = EditorGUIUtility.singleLineHeight; | ||
|  |             if (!EditorGUIUtility.wideMode) | ||
|  |                 height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; | ||
|  |             return height; | ||
|  |         } | ||
|  | 
 | ||
|  |         public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | ||
|  |         { | ||
|  |             var subLabels = Content.labels4; | ||
|  |             var startIter = "x"; | ||
|  |             var propertyType = GetPropertyType(property); | ||
|  |             switch (propertyType[propertyType.Length - 1]) | ||
|  |             { | ||
|  |                 case '2': | ||
|  |                     subLabels = Content.labels2; | ||
|  |                     break; | ||
|  |                 case '3': | ||
|  |                     subLabels = Content.labels3; | ||
|  |                     break; | ||
|  |                 case '4': | ||
|  |                     subLabels = Content.labels4; | ||
|  |                     break; | ||
|  |                 default: | ||
|  |                 { | ||
|  |                     if (property.type == nameof(quaternion)) | ||
|  |                         startIter = "value.x"; | ||
|  |                     else if (attribute is DoNotNormalizeAttribute) | ||
|  |                     { | ||
|  |                         EditorGUI.HelpBox(EditorGUI.PrefixLabel(position, label), Content.doNotNormalizeCompatibility, MessageType.None); | ||
|  |                         return; | ||
|  |                     } | ||
|  |                     break; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             if (attribute is DoNotNormalizeAttribute && string.IsNullOrEmpty(label.tooltip)) | ||
|  |                 label.tooltip = Content.doNotNormalizeTooltip; | ||
|  | 
 | ||
|  |             label = EditorGUI.BeginProperty(position, label, property); | ||
|  |             var valuesIterator = property.FindPropertyRelative(startIter); | ||
|  |             MultiPropertyField(position, subLabels, valuesIterator, label); | ||
|  |             EditorGUI.EndProperty(); | ||
|  |         } | ||
|  | 
 | ||
|  |         void MultiPropertyField(Rect position, GUIContent[] subLabels, SerializedProperty valuesIterator, GUIContent label) | ||
|  |         { | ||
|  | #if UNITY_2022_1_OR_NEWER | ||
|  |             EditorGUI.MultiPropertyField(position, subLabels, valuesIterator, label, EditorGUI.PropertyVisibility.All); | ||
|  | #else | ||
|  |             EditorGUICopy.MultiPropertyField(position, subLabels, valuesIterator, label); | ||
|  | #endif | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  | #if !UNITY_2022_1_OR_NEWER | ||
|  |     internal class EditorGUICopy | ||
|  |     { | ||
|  |         internal const float kSpacingSubLabel = 4; | ||
|  |         private const float kIndentPerLevel = 15; | ||
|  |         internal const float kPrefixPaddingRight = 2; | ||
|  |         internal static int indentLevel = 0; | ||
|  |         private static readonly int s_FoldoutHash = "Foldout".GetHashCode(); | ||
|  | 
 | ||
|  |         // internal static readonly SVC<float> kVerticalSpacingMultiField = new SVC<float>("--theme-multifield-vertical-spacing", 0.0f); | ||
|  |         // kVerticalSpacingMultiField should actually look like the above line ^^^ but we don't have access to SVC<T>, | ||
|  |         // so instead we just set this value to what is observed in the debugger with the Unity dark theme. | ||
|  |         internal const float kVerticalSpacingMultiField = 2; | ||
|  | 
 | ||
|  |         internal enum PropertyVisibility | ||
|  |         { | ||
|  |             All, | ||
|  |             OnlyVisible | ||
|  |         } | ||
|  | 
 | ||
|  |         // This code is basically EditorGUI.MultiPropertyField(Rect, GUIContent[], SerializedProperty, GUIContent), | ||
|  |         // but with the property visibility assumed to be "All" instead of "OnlyVisible". We really want to have "All" | ||
|  |         // because it's possible for someone to hide something in the inspector with [HideInInspector] but then manually | ||
|  |         // draw it themselves later. In this case, if you called EditorGUI.MultiPropertyField() directly, you'd | ||
|  |         // end up with some fields that point to some unrelated visible property. | ||
|  |         public static void MultiPropertyField(Rect position, GUIContent[] subLabels, SerializedProperty valuesIterator, GUIContent label) | ||
|  |         { | ||
|  |             int id = GUIUtility.GetControlID(s_FoldoutHash, FocusType.Keyboard, position); | ||
|  |             position = MultiFieldPrefixLabel(position, id, label, subLabels.Length); | ||
|  |             position.height = EditorGUIUtility.singleLineHeight; | ||
|  |             MultiPropertyFieldInternal(position, subLabels, valuesIterator, PropertyVisibility.All); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void BeginDisabled(bool disabled) | ||
|  |         { | ||
|  |             // Unused, but left here to minimize changes in EditorGUICopy.MultiPropertyFieldInternal(). | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void EndDisabled() | ||
|  |         { | ||
|  |             // Unused, but left here to minimize changes in EditorGUICopy.MultiPropertyFieldInternal(). | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static float CalcPrefixLabelWidth(GUIContent label, GUIStyle style = null) | ||
|  |         { | ||
|  |             if (style == null) | ||
|  |                 style = EditorStyles.label; | ||
|  |             return style.CalcSize(label).x; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void MultiPropertyFieldInternal(Rect position, GUIContent[] subLabels, SerializedProperty valuesIterator, PropertyVisibility visibility, bool[] disabledMask = null, float prefixLabelWidth = -1) | ||
|  |         { | ||
|  |             int eCount = subLabels.Length; | ||
|  |             float w = (position.width - (eCount - 1) * kSpacingSubLabel) / eCount; | ||
|  |             Rect nr = new Rect(position) {width = w}; | ||
|  |             float t = EditorGUIUtility.labelWidth; | ||
|  |             int l = indentLevel; | ||
|  |             indentLevel = 0; | ||
|  |             for (int i = 0; i < subLabels.Length; i++) | ||
|  |             { | ||
|  |                 EditorGUIUtility.labelWidth = prefixLabelWidth > 0 ? prefixLabelWidth : CalcPrefixLabelWidth(subLabels[i]); | ||
|  | 
 | ||
|  |                 if (disabledMask != null) | ||
|  |                     BeginDisabled(disabledMask[i]); | ||
|  |                 EditorGUI.PropertyField(nr, valuesIterator, subLabels[i]); | ||
|  |                 if (disabledMask != null) | ||
|  |                     EndDisabled(); | ||
|  |                 nr.x += w + kSpacingSubLabel; | ||
|  | 
 | ||
|  |                 switch (visibility) | ||
|  |                 { | ||
|  |                     case PropertyVisibility.All: | ||
|  |                         valuesIterator.Next(false); | ||
|  |                         break; | ||
|  | 
 | ||
|  |                     case PropertyVisibility.OnlyVisible: | ||
|  |                         valuesIterator.NextVisible(false); | ||
|  |                         break; | ||
|  |                 } | ||
|  |             } | ||
|  |             EditorGUIUtility.labelWidth = t; | ||
|  |             indentLevel = l; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static bool LabelHasContent(GUIContent label) | ||
|  |         { | ||
|  |             if (label == null) | ||
|  |             { | ||
|  |                 return true; | ||
|  |             } | ||
|  |             // @TODO: find out why checking for GUIContent.none doesn't work | ||
|  |             return label.text != string.Empty || label.image != null; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static float indent => indentLevel * kIndentPerLevel; | ||
|  | 
 | ||
|  |         internal static Rect MultiFieldPrefixLabel(Rect totalPosition, int id, GUIContent label, int columns) | ||
|  |         { | ||
|  |             if (!LabelHasContent(label)) | ||
|  |             { | ||
|  |                 return EditorGUI.IndentedRect(totalPosition); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (EditorGUIUtility.wideMode) | ||
|  |             { | ||
|  |                 Rect labelPosition = new Rect(totalPosition.x + indent, totalPosition.y, EditorGUIUtility.labelWidth - indent, EditorGUIUtility.singleLineHeight); | ||
|  |                 Rect fieldPosition = totalPosition; | ||
|  |                 fieldPosition.xMin += EditorGUIUtility.labelWidth + kPrefixPaddingRight; | ||
|  | 
 | ||
|  |                 // If there are 2 columns we use the same column widths as if there had been 3 columns | ||
|  |                 // in order to make columns line up neatly. | ||
|  |                 if (columns == 2) | ||
|  |                 { | ||
|  |                     float columnWidth = (fieldPosition.width - (3 - 1) * kSpacingSubLabel) / 3f; | ||
|  |                     fieldPosition.xMax -= (columnWidth + kSpacingSubLabel); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 EditorGUI.HandlePrefixLabel(totalPosition, labelPosition, label, id); | ||
|  |                 return fieldPosition; | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 Rect labelPosition = new Rect(totalPosition.x + indent, totalPosition.y, totalPosition.width - indent, EditorGUIUtility.singleLineHeight); | ||
|  |                 Rect fieldPosition = totalPosition; | ||
|  |                 fieldPosition.xMin += indent + kIndentPerLevel; | ||
|  |                 fieldPosition.yMin += EditorGUIUtility.singleLineHeight + kVerticalSpacingMultiField; | ||
|  |                 EditorGUI.HandlePrefixLabel(totalPosition, labelPosition, label, id); | ||
|  |                 return fieldPosition; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | #endif | ||
|  | } |