A game about forced loneliness, made by TACStudios
1using System;
2using UnityEditor;
3using UnityEngine;
4
5namespace Unity.Mathematics.Editor
6{
7 [CustomPropertyDrawer(typeof(bool2)), CustomPropertyDrawer(typeof(bool3)), CustomPropertyDrawer(typeof(bool4))]
8 [CustomPropertyDrawer(typeof(double2)), CustomPropertyDrawer(typeof(double3)), CustomPropertyDrawer(typeof(double4))]
9 [CustomPropertyDrawer(typeof(float2)), CustomPropertyDrawer(typeof(float3)), CustomPropertyDrawer(typeof(float4))]
10 [CustomPropertyDrawer(typeof(int2)), CustomPropertyDrawer(typeof(int3)), CustomPropertyDrawer(typeof(int4))]
11 [CustomPropertyDrawer(typeof(uint2)), CustomPropertyDrawer(typeof(uint3)), CustomPropertyDrawer(typeof(uint4))]
12 [CustomPropertyDrawer(typeof(DoNotNormalizeAttribute))]
13 class PrimitiveVectorDrawer : PropertyDrawer
14 {
15 private string _PropertyType;
16
17 string GetPropertyType(SerializedProperty property)
18 {
19 if (_PropertyType == null)
20 {
21 _PropertyType = property.type;
22 var isManagedRef = property.type.StartsWith("managedReference", StringComparison.Ordinal);
23 if (isManagedRef)
24 {
25 var startIndex = "managedReference<".Length;
26 var length = _PropertyType.Length - startIndex - 1;
27 _PropertyType = _PropertyType.Substring("managedReference<".Length, length);
28 }
29 }
30
31 return _PropertyType;
32 }
33
34 static class Content
35 {
36 public static readonly string doNotNormalizeCompatibility = L10n.Tr(
37 $"{typeof(DoNotNormalizeAttribute).Name} only works with {typeof(quaternion)} and primitive vector types."
38 );
39 public static readonly string doNotNormalizeTooltip =
40 L10n.Tr("This value is not normalized, which may produce unexpected results.");
41
42 public static readonly GUIContent[] labels2 = { new GUIContent("X"), new GUIContent("Y") };
43 public static readonly GUIContent[] labels3 = { new GUIContent("X"), new GUIContent("Y"), new GUIContent("Z") };
44 public static readonly GUIContent[] labels4 = { new GUIContent("X"), new GUIContent("Y"), new GUIContent("Z"), new GUIContent("W") };
45 }
46
47#if !UNITY_2023_2_OR_NEWER
48 public override bool CanCacheInspectorGUI(SerializedProperty property)
49 {
50 return false;
51 }
52#endif
53
54 public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
55 {
56 var height = EditorGUIUtility.singleLineHeight;
57 if (!EditorGUIUtility.wideMode)
58 height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
59 return height;
60 }
61
62 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
63 {
64 var subLabels = Content.labels4;
65 var startIter = "x";
66 var propertyType = GetPropertyType(property);
67 switch (propertyType[propertyType.Length - 1])
68 {
69 case '2':
70 subLabels = Content.labels2;
71 break;
72 case '3':
73 subLabels = Content.labels3;
74 break;
75 case '4':
76 subLabels = Content.labels4;
77 break;
78 default:
79 {
80 if (property.type == nameof(quaternion))
81 startIter = "value.x";
82 else if (attribute is DoNotNormalizeAttribute)
83 {
84 EditorGUI.HelpBox(EditorGUI.PrefixLabel(position, label), Content.doNotNormalizeCompatibility, MessageType.None);
85 return;
86 }
87 break;
88 }
89 }
90
91 if (attribute is DoNotNormalizeAttribute && string.IsNullOrEmpty(label.tooltip))
92 label.tooltip = Content.doNotNormalizeTooltip;
93
94 label = EditorGUI.BeginProperty(position, label, property);
95 var valuesIterator = property.FindPropertyRelative(startIter);
96 MultiPropertyField(position, subLabels, valuesIterator, label);
97 EditorGUI.EndProperty();
98 }
99
100 void MultiPropertyField(Rect position, GUIContent[] subLabels, SerializedProperty valuesIterator, GUIContent label)
101 {
102#if UNITY_2022_1_OR_NEWER
103 EditorGUI.MultiPropertyField(position, subLabels, valuesIterator, label, EditorGUI.PropertyVisibility.All);
104#else
105 EditorGUICopy.MultiPropertyField(position, subLabels, valuesIterator, label);
106#endif
107 }
108 }
109
110#if !UNITY_2022_1_OR_NEWER
111 internal class EditorGUICopy
112 {
113 internal const float kSpacingSubLabel = 4;
114 private const float kIndentPerLevel = 15;
115 internal const float kPrefixPaddingRight = 2;
116 internal static int indentLevel = 0;
117 private static readonly int s_FoldoutHash = "Foldout".GetHashCode();
118
119 // internal static readonly SVC<float> kVerticalSpacingMultiField = new SVC<float>("--theme-multifield-vertical-spacing", 0.0f);
120 // kVerticalSpacingMultiField should actually look like the above line ^^^ but we don't have access to SVC<T>,
121 // so instead we just set this value to what is observed in the debugger with the Unity dark theme.
122 internal const float kVerticalSpacingMultiField = 2;
123
124 internal enum PropertyVisibility
125 {
126 All,
127 OnlyVisible
128 }
129
130 // This code is basically EditorGUI.MultiPropertyField(Rect, GUIContent[], SerializedProperty, GUIContent),
131 // but with the property visibility assumed to be "All" instead of "OnlyVisible". We really want to have "All"
132 // because it's possible for someone to hide something in the inspector with [HideInInspector] but then manually
133 // draw it themselves later. In this case, if you called EditorGUI.MultiPropertyField() directly, you'd
134 // end up with some fields that point to some unrelated visible property.
135 public static void MultiPropertyField(Rect position, GUIContent[] subLabels, SerializedProperty valuesIterator, GUIContent label)
136 {
137 int id = GUIUtility.GetControlID(s_FoldoutHash, FocusType.Keyboard, position);
138 position = MultiFieldPrefixLabel(position, id, label, subLabels.Length);
139 position.height = EditorGUIUtility.singleLineHeight;
140 MultiPropertyFieldInternal(position, subLabels, valuesIterator, PropertyVisibility.All);
141 }
142
143 internal static void BeginDisabled(bool disabled)
144 {
145 // Unused, but left here to minimize changes in EditorGUICopy.MultiPropertyFieldInternal().
146 }
147
148 internal static void EndDisabled()
149 {
150 // Unused, but left here to minimize changes in EditorGUICopy.MultiPropertyFieldInternal().
151 }
152
153 internal static float CalcPrefixLabelWidth(GUIContent label, GUIStyle style = null)
154 {
155 if (style == null)
156 style = EditorStyles.label;
157 return style.CalcSize(label).x;
158 }
159
160 internal static void MultiPropertyFieldInternal(Rect position, GUIContent[] subLabels, SerializedProperty valuesIterator, PropertyVisibility visibility, bool[] disabledMask = null, float prefixLabelWidth = -1)
161 {
162 int eCount = subLabels.Length;
163 float w = (position.width - (eCount - 1) * kSpacingSubLabel) / eCount;
164 Rect nr = new Rect(position) {width = w};
165 float t = EditorGUIUtility.labelWidth;
166 int l = indentLevel;
167 indentLevel = 0;
168 for (int i = 0; i < subLabels.Length; i++)
169 {
170 EditorGUIUtility.labelWidth = prefixLabelWidth > 0 ? prefixLabelWidth : CalcPrefixLabelWidth(subLabels[i]);
171
172 if (disabledMask != null)
173 BeginDisabled(disabledMask[i]);
174 EditorGUI.PropertyField(nr, valuesIterator, subLabels[i]);
175 if (disabledMask != null)
176 EndDisabled();
177 nr.x += w + kSpacingSubLabel;
178
179 switch (visibility)
180 {
181 case PropertyVisibility.All:
182 valuesIterator.Next(false);
183 break;
184
185 case PropertyVisibility.OnlyVisible:
186 valuesIterator.NextVisible(false);
187 break;
188 }
189 }
190 EditorGUIUtility.labelWidth = t;
191 indentLevel = l;
192 }
193
194 internal static bool LabelHasContent(GUIContent label)
195 {
196 if (label == null)
197 {
198 return true;
199 }
200 // @TODO: find out why checking for GUIContent.none doesn't work
201 return label.text != string.Empty || label.image != null;
202 }
203
204 internal static float indent => indentLevel * kIndentPerLevel;
205
206 internal static Rect MultiFieldPrefixLabel(Rect totalPosition, int id, GUIContent label, int columns)
207 {
208 if (!LabelHasContent(label))
209 {
210 return EditorGUI.IndentedRect(totalPosition);
211 }
212
213 if (EditorGUIUtility.wideMode)
214 {
215 Rect labelPosition = new Rect(totalPosition.x + indent, totalPosition.y, EditorGUIUtility.labelWidth - indent, EditorGUIUtility.singleLineHeight);
216 Rect fieldPosition = totalPosition;
217 fieldPosition.xMin += EditorGUIUtility.labelWidth + kPrefixPaddingRight;
218
219 // If there are 2 columns we use the same column widths as if there had been 3 columns
220 // in order to make columns line up neatly.
221 if (columns == 2)
222 {
223 float columnWidth = (fieldPosition.width - (3 - 1) * kSpacingSubLabel) / 3f;
224 fieldPosition.xMax -= (columnWidth + kSpacingSubLabel);
225 }
226
227 EditorGUI.HandlePrefixLabel(totalPosition, labelPosition, label, id);
228 return fieldPosition;
229 }
230 else
231 {
232 Rect labelPosition = new Rect(totalPosition.x + indent, totalPosition.y, totalPosition.width - indent, EditorGUIUtility.singleLineHeight);
233 Rect fieldPosition = totalPosition;
234 fieldPosition.xMin += indent + kIndentPerLevel;
235 fieldPosition.yMin += EditorGUIUtility.singleLineHeight + kVerticalSpacingMultiField;
236 EditorGUI.HandlePrefixLabel(totalPosition, labelPosition, label, id);
237 return fieldPosition;
238 }
239 }
240 }
241#endif
242}