A game about forced loneliness, made by TACStudios
1#if UNITY_EDITOR
2using System;
3using System.Collections.Generic;
4using System.Linq;
5using System.Reflection;
6using System.Text;
7using UnityEditor;
8using UnityEngine.InputSystem.Layouts;
9using UnityEngine.InputSystem.Utilities;
10
11////TODO: resolving bindings to actions needs to take "{id}" form into account
12
13namespace UnityEngine.InputSystem.Editor
14{
15 // Helpers for doctoring around in InputActions using SerializedProperties.
16 internal static class InputActionSerializationHelpers
17 {
18 public static string GetName(SerializedProperty element)
19 {
20 using (var nameProperty = element.FindPropertyRelative("m_Name"))
21 {
22 Debug.Assert(nameProperty != null, $"Cannot find m_Name property in {element.propertyPath}");
23 return nameProperty.stringValue;
24 }
25 }
26
27 public static Guid GetId(SerializedProperty element)
28 {
29 using (var idProperty = element.FindPropertyRelative("m_Id"))
30 {
31 Debug.Assert(idProperty != null, $"Cannot find m_Id property in {element.propertyPath}");
32 return new Guid(idProperty.stringValue);
33 }
34 }
35
36 public static int GetIndex(SerializedProperty arrayProperty, Guid id)
37 {
38 Debug.Assert(arrayProperty.isArray, $"Property {arrayProperty.propertyPath} is not an array");
39 for (var i = 0; i < arrayProperty.arraySize; ++i)
40 {
41 using (var element = arrayProperty.GetArrayElementAtIndex(i))
42 if (GetId(element) == id)
43 return i;
44 }
45 return -1;
46 }
47
48 public static int GetIndex(SerializedProperty arrayProperty, SerializedProperty arrayElement)
49 {
50 return GetIndex(arrayProperty, GetId(arrayElement));
51 }
52
53 public static int GetIndex(SerializedProperty arrayElement)
54 {
55 var arrayProperty = arrayElement.GetArrayPropertyFromElement();
56 return GetIndex(arrayProperty, arrayElement);
57 }
58
59 /// <summary>
60 /// Starting with the given binding, find the composite that the binding belongs to. The given binding
61 /// must either be the composite or be part of a composite.
62 /// </summary>
63 public static int GetCompositeStartIndex(SerializedProperty bindingArrayProperty, int bindingIndex)
64 {
65 for (var i = bindingIndex; i >= 0; --i)
66 {
67 var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(i);
68 var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
69 if ((bindingFlags & InputBinding.Flags.Composite) != 0)
70 return i;
71 Debug.Assert((bindingFlags & InputBinding.Flags.PartOfComposite) != 0,
72 "Binding is neither a composite nor part of a composite");
73 }
74 return -1;
75 }
76
77 public static int GetCompositePartCount(SerializedProperty bindingArrayProperty, int bindingIndex)
78 {
79 var compositeStartIndex = GetCompositeStartIndex(bindingArrayProperty, bindingIndex);
80 if (compositeStartIndex == -1)
81 return 0;
82
83 var numParts = 0;
84 for (var i = compositeStartIndex + 1; i < bindingArrayProperty.arraySize; ++i, ++numParts)
85 {
86 var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(i);
87 var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
88 if ((bindingFlags & InputBinding.Flags.PartOfComposite) == 0)
89 break;
90 }
91
92 return numParts;
93 }
94
95 public static int ConvertBindingIndexOnActionToBindingIndexInArray(SerializedProperty bindingArrayProperty, string actionName,
96 int bindingIndexOnAction)
97 {
98 var bindingCount = bindingArrayProperty.arraySize;
99 var indexOnAction = -1;
100 var indexInArray = 0;
101 for (; indexInArray < bindingCount; ++indexInArray)
102 {
103 var bindingActionName = bindingArrayProperty.GetArrayElementAtIndex(indexInArray).FindPropertyRelative("m_Action")
104 .stringValue;
105 if (actionName.Equals(bindingActionName, StringComparison.InvariantCultureIgnoreCase))
106 {
107 ++indexOnAction;
108 if (indexOnAction == bindingIndexOnAction)
109 return indexInArray;
110 }
111 }
112 return indexInArray;
113 }
114
115#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
116 public static void AddActionMaps(SerializedObject asset, SerializedObject sourceAsset)
117 {
118 Debug.Assert(asset.targetObject is InputActionAsset);
119 Debug.Assert(sourceAsset.targetObject is InputActionAsset);
120
121 var mapArrayPropertySrc = sourceAsset.FindProperty(nameof(InputActionAsset.m_ActionMaps));
122 var mapArrayPropertyDst = asset.FindProperty(nameof(InputActionAsset.m_ActionMaps));
123
124 // Copy each action map from source and paste at the end of destination
125 var buffer = new StringBuilder();
126 for (var i = 0; i < mapArrayPropertySrc.arraySize; ++i)
127 {
128 buffer.Clear();
129 var mapProperty = mapArrayPropertySrc.GetArrayElementAtIndex(i);
130 CopyPasteHelper.CopyItems(new List<SerializedProperty> {mapProperty}, buffer, typeof(InputActionMap), mapProperty);
131 CopyPasteHelper.PasteItems(buffer.ToString(), new[] { mapArrayPropertyDst.arraySize - 1 }, mapArrayPropertyDst);
132 }
133 }
134
135 public static void AddControlSchemes(SerializedObject asset, SerializedObject sourceAsset)
136 {
137 Debug.Assert((asset.targetObject is InputActionAsset));
138 Debug.Assert((sourceAsset.targetObject is InputActionAsset));
139
140 var src = sourceAsset.FindProperty(nameof(InputActionAsset.m_ControlSchemes));
141 var dst = asset.FindProperty(nameof(InputActionAsset.m_ControlSchemes));
142
143 var buffer = new StringBuilder();
144 src.CopyToJson(buffer, ignoreObjectReferences: true);
145 dst.RestoreFromJson(buffer.ToString());
146 }
147
148#endif
149
150 public static SerializedProperty AddActionMap(SerializedObject asset, int index = -1)
151 {
152 if (!(asset.targetObject is InputActionAsset))
153 throw new InvalidOperationException(
154 $"Can only add action maps to InputActionAsset objects (actual object is {asset.targetObject}");
155
156 var mapArrayProperty = asset.FindProperty("m_ActionMaps");
157 var name = FindUniqueName(mapArrayProperty, "New action map");
158 if (index < 0)
159 index = mapArrayProperty.arraySize;
160
161 mapArrayProperty.InsertArrayElementAtIndex(index);
162 var mapProperty = mapArrayProperty.GetArrayElementAtIndex(index);
163
164 mapProperty.FindPropertyRelative("m_Name").stringValue = name;
165 mapProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString();
166 mapProperty.FindPropertyRelative("m_Actions").ClearArray();
167 mapProperty.FindPropertyRelative("m_Bindings").ClearArray();
168 // NB: This isn't always required: If there's already values in the mapArrayProperty, then inserting a new
169 // element will duplicate the values from the adjacent element to the new element.
170 // However, if the array has been emptied - i.e. if all action maps have been deleted -
171 // then the m_Asset property is null, and needs setting here.
172 if (mapProperty.FindPropertyRelative("m_Asset").objectReferenceValue == null)
173 mapProperty.FindPropertyRelative("m_Asset").objectReferenceValue = asset.targetObject;
174
175 return mapProperty;
176 }
177
178 public static void DeleteActionMap(SerializedObject asset, Guid id)
179 {
180 var mapArrayProperty = asset.FindProperty("m_ActionMaps");
181 var mapIndex = GetIndex(mapArrayProperty, id);
182 if (mapIndex == -1)
183 throw new ArgumentException($"No map with id {id} in {asset}", nameof(id));
184 mapArrayProperty.DeleteArrayElementAtIndex(mapIndex);
185 }
186
187 public static void DeleteAllActionMaps(SerializedObject asset)
188 {
189 Debug.Assert(asset.targetObject is InputActionAsset);
190
191 var mapArrayProperty = asset.FindProperty("m_ActionMaps");
192 while (mapArrayProperty.arraySize > 0)
193 mapArrayProperty.DeleteArrayElementAtIndex(0);
194 }
195
196 public static void MoveActionMap(SerializedObject asset, int fromIndex, int toIndex)
197 {
198 var mapArrayProperty = asset.FindProperty("m_ActionMaps");
199 mapArrayProperty.MoveArrayElement(fromIndex, toIndex);
200 }
201
202 public static void MoveAction(SerializedProperty actionMap, int fromIndex, int toIndex)
203 {
204 var actionArrayProperty = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Actions));
205 actionArrayProperty.MoveArrayElement(fromIndex, toIndex);
206 }
207
208 public static void MoveBinding(SerializedProperty actionMap, int fromIndex, int toIndex)
209 {
210 var arrayProperty = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
211 arrayProperty.MoveArrayElement(fromIndex, toIndex);
212 }
213
214 // Append a new action to the end of the set.
215 public static SerializedProperty AddAction(SerializedProperty actionMap, int index = -1)
216 {
217 var actionsArrayProperty = actionMap.FindPropertyRelative("m_Actions");
218 if (index < 0)
219 index = actionsArrayProperty.arraySize;
220
221 var actionName = FindUniqueName(actionsArrayProperty, "New action");
222
223 actionsArrayProperty.InsertArrayElementAtIndex(index);
224 var actionProperty = actionsArrayProperty.GetArrayElementAtIndex(index);
225
226 actionProperty.FindPropertyRelative("m_Name").stringValue = actionName;
227 actionProperty.FindPropertyRelative("m_Type").intValue = (int)InputActionType.Button; // Default to creating button actions.
228 actionProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString();
229 actionProperty.FindPropertyRelative("m_ExpectedControlType").stringValue = "Button";
230 actionProperty.FindPropertyRelative("m_Flags").intValue = 0;
231 actionProperty.FindPropertyRelative("m_Interactions").stringValue = "";
232 actionProperty.FindPropertyRelative("m_Processors").stringValue = "";
233
234 return actionProperty;
235 }
236
237 public static void DeleteActionAndBindings(SerializedProperty actionMap, Guid actionId)
238 {
239 using (var actionsArrayProperty = actionMap.FindPropertyRelative("m_Actions"))
240 using (var bindingsArrayProperty = actionMap.FindPropertyRelative("m_Bindings"))
241 {
242 // Find index of action.
243 var actionIndex = GetIndex(actionsArrayProperty, actionId);
244 if (actionIndex == -1)
245 throw new ArgumentException($"No action with ID {actionId} in {actionMap.propertyPath}",
246 nameof(actionId));
247
248 using (var actionsProperty = actionsArrayProperty.GetArrayElementAtIndex(actionIndex))
249 {
250 var actionName = GetName(actionsProperty);
251 var actionIdString = actionId.ToString();
252
253 // Delete all bindings that refer to the action by ID or name.
254 for (var i = 0; i < bindingsArrayProperty.arraySize; ++i)
255 {
256 using (var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i))
257 using (var bindingActionProperty = bindingProperty.FindPropertyRelative("m_Action"))
258 {
259 var targetAction = bindingActionProperty.stringValue;
260 if (targetAction.Equals(actionName, StringComparison.InvariantCultureIgnoreCase) ||
261 targetAction == actionIdString)
262 {
263 bindingsArrayProperty.DeleteArrayElementAtIndex(i);
264 --i;
265 }
266 }
267 }
268 }
269
270 actionsArrayProperty.DeleteArrayElementAtIndex(actionIndex);
271 }
272 }
273
274 // Equivalent to InputAction.AddBinding().
275 public static SerializedProperty AddBinding(SerializedProperty actionProperty,
276 SerializedProperty actionMapProperty = null, SerializedProperty afterBinding = null,
277 string groups = "", string path = "", string name = "",
278 string interactions = "", string processors = "",
279 InputBinding.Flags flags = InputBinding.Flags.None)
280 {
281 var bindingsArrayProperty = actionMapProperty != null
282 ? actionMapProperty.FindPropertyRelative("m_Bindings")
283 : actionProperty.FindPropertyRelative("m_SingletonActionBindings");
284 var bindingsCount = bindingsArrayProperty.arraySize;
285 var actionName = actionProperty.FindPropertyRelative("m_Name").stringValue;
286
287 int bindingIndex;
288 if (afterBinding != null)
289 {
290 // If we're supposed to put the binding right after another binding, find the
291 // binding's index. Also, if it's a composite, skip past all its parts.
292 bindingIndex = GetIndex(bindingsArrayProperty, afterBinding);
293 if (IsCompositeBinding(afterBinding))
294 bindingIndex += GetCompositePartCount(bindingsArrayProperty, bindingIndex);
295 ++bindingIndex; // Put it *after* the binding.
296 }
297 else
298 {
299 // Find the index of the last binding for the action in the array.
300 var indexOfLastBindingForAction = -1;
301 for (var i = 0; i < bindingsCount; ++i)
302 {
303 var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i);
304 var bindingActionName = bindingProperty.FindPropertyRelative("m_Action").stringValue;
305 if (actionName.Equals(bindingActionName, StringComparison.InvariantCultureIgnoreCase))
306 indexOfLastBindingForAction = i;
307 }
308
309 // Insert after last binding or at end of array.
310 bindingIndex = indexOfLastBindingForAction != -1 ? indexOfLastBindingForAction + 1 : bindingsCount;
311 }
312
313 ////TODO: bind using {id} rather than action name
314 return AddBindingToBindingArray(bindingsArrayProperty,
315 bindingIndex: bindingIndex,
316 actionName: actionName,
317 groups: groups,
318 path: path,
319 name: name,
320 interactions: interactions,
321 processors: processors,
322 flags: flags);
323 }
324
325 public static SerializedProperty AddBindingToBindingArray(SerializedProperty bindingsArrayProperty, int bindingIndex = -1,
326 string actionName = "", string groups = "", string path = "", string name = "", string interactions = "", string processors = "",
327 InputBinding.Flags flags = InputBinding.Flags.None)
328 {
329 Debug.Assert(bindingsArrayProperty != null);
330 Debug.Assert(bindingsArrayProperty.isArray, "SerializedProperty is not an array of bindings");
331 Debug.Assert(bindingIndex == -1 || (bindingIndex >= 0 && bindingIndex <= bindingsArrayProperty.arraySize));
332
333 if (bindingIndex == -1)
334 bindingIndex = bindingsArrayProperty.arraySize;
335
336 bindingsArrayProperty.InsertArrayElementAtIndex(bindingIndex);
337
338 var newBindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(bindingIndex);
339 newBindingProperty.FindPropertyRelative("m_Path").stringValue = path;
340 newBindingProperty.FindPropertyRelative("m_Groups").stringValue = groups;
341 newBindingProperty.FindPropertyRelative("m_Interactions").stringValue = interactions;
342 newBindingProperty.FindPropertyRelative("m_Processors").stringValue = processors;
343 newBindingProperty.FindPropertyRelative("m_Flags").intValue = (int)flags;
344 newBindingProperty.FindPropertyRelative("m_Action").stringValue = actionName;
345 newBindingProperty.FindPropertyRelative("m_Name").stringValue = name;
346 newBindingProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString();
347
348 ////FIXME: this likely leaves m_Bindings in the map for singleton actions unsync'd in some cases
349
350 return newBindingProperty;
351 }
352
353 public static void SetBindingPartName(SerializedProperty bindingProperty, string partName)
354 {
355 //expects beautified partName
356 bindingProperty.FindPropertyRelative("m_Name").stringValue = partName;
357 }
358
359 public static void ChangeBinding(SerializedProperty bindingProperty, string path = null, string groups = null,
360 string interactions = null, string processors = null, string action = null)
361 {
362 // Path.
363 if (!string.IsNullOrEmpty(path))
364 {
365 var pathProperty = bindingProperty.FindPropertyRelative("m_Path");
366 pathProperty.stringValue = path;
367 }
368
369 // Groups.
370 if (!string.IsNullOrEmpty(groups))
371 {
372 var groupsProperty = bindingProperty.FindPropertyRelative("m_Groups");
373 groupsProperty.stringValue = groups;
374 }
375
376 // Interactions.
377 if (!string.IsNullOrEmpty(interactions))
378 {
379 var interactionsProperty = bindingProperty.FindPropertyRelative("m_Interactions");
380 interactionsProperty.stringValue = interactions;
381 }
382
383 // Processors.
384 if (!string.IsNullOrEmpty(processors))
385 {
386 var processorsProperty = bindingProperty.FindPropertyRelative("m_Processors");
387 processorsProperty.stringValue = processors;
388 }
389
390 // Action.
391 if (!string.IsNullOrEmpty(action))
392 {
393 var actionProperty = bindingProperty.FindPropertyRelative("m_Action");
394 actionProperty.stringValue = action;
395 }
396 }
397
398 public static void DeleteBinding(SerializedProperty binding, SerializedProperty actionMap)
399 {
400 var bindingsProperty = actionMap.FindPropertyRelative("m_Bindings");
401 DeleteBinding(binding, bindingsProperty, binding.GetIndexOfArrayElement());
402 }
403
404 private static void DeleteBinding(SerializedProperty bindingProperty, SerializedProperty bindingArrayProperty, int bindingIndex)
405 {
406 var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
407 var isComposite = (bindingFlags & InputBinding.Flags.Composite) != 0;
408 // If it's a composite, delete all its parts first.
409 if (isComposite)
410 {
411 for (var partIndex = bindingIndex + 1; partIndex < bindingArrayProperty.arraySize;)
412 {
413 var part = bindingArrayProperty.GetArrayElementAtIndex(partIndex);
414 var flags = (InputBinding.Flags)part.FindPropertyRelative("m_Flags").intValue;
415 if ((flags & InputBinding.Flags.PartOfComposite) == 0)
416 break;
417 bindingArrayProperty.DeleteArrayElementAtIndex(partIndex);
418 }
419 }
420
421 bindingArrayProperty.DeleteArrayElementAtIndex(bindingIndex);
422 }
423
424 public static void DeleteBinding(SerializedProperty bindingArrayProperty, Guid id)
425 {
426 var bindingIndex = GetIndex(bindingArrayProperty, id);
427 var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(bindingIndex);
428 DeleteBinding(bindingProperty, bindingArrayProperty, bindingIndex);
429 }
430
431 public static void EnsureUniqueName(SerializedProperty arrayElement)
432 {
433 var arrayProperty = arrayElement.GetArrayPropertyFromElement();
434 var arrayIndexOfElement = arrayElement.GetIndexOfArrayElement();
435 var nameProperty = arrayElement.FindPropertyRelative("m_Name");
436 var baseName = nameProperty.stringValue;
437 nameProperty.stringValue = FindUniqueName(arrayProperty, baseName, ignoreIndex: arrayIndexOfElement);
438 }
439
440 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "False positive (possibly caused by lambda expression?).")]
441 public static string FindUniqueName(SerializedProperty arrayProperty, string baseName, int ignoreIndex = -1)
442 {
443 return StringHelpers.MakeUniqueName(baseName,
444 Enumerable.Range(0, arrayProperty.arraySize),
445 index =>
446 {
447 if (index == ignoreIndex)
448 return string.Empty;
449 var elementProperty = arrayProperty.GetArrayElementAtIndex(index);
450 var nameProperty = elementProperty.FindPropertyRelative("m_Name");
451 if (nameProperty == null)
452 throw new ArgumentException($"Cannot find m_Name property in elements of array",
453 nameof(arrayProperty));
454 return nameProperty.stringValue;
455 });
456 }
457
458 public static void AssignUniqueIDs(SerializedProperty element)
459 {
460 AssignUniqueID(element);
461 foreach (var child in element.GetChildren())
462 {
463 if (!child.isArray)
464 continue;
465
466 var fieldType = child.GetFieldType();
467 if (fieldType == typeof(InputBinding[]) || fieldType == typeof(InputAction[]) ||
468 fieldType == typeof(InputActionMap))
469 {
470 for (var i = 0; i < child.arraySize; ++i)
471 using (var childElement = child.GetArrayElementAtIndex(i))
472 AssignUniqueIDs(childElement);
473 }
474 }
475 }
476
477 private static void AssignUniqueID(SerializedProperty property)
478 {
479 var idProperty = property.FindPropertyRelative("m_Id");
480 idProperty.stringValue = Guid.NewGuid().ToString();
481 }
482
483 public static void RenameAction(SerializedProperty actionProperty, SerializedProperty actionMapProperty, string newName)
484 {
485 // Make sure name is unique.
486 var actionsArrayProperty = actionMapProperty.FindPropertyRelative("m_Actions");
487 var uniqueName = FindUniqueName(actionsArrayProperty, newName, actionProperty.GetIndexOfArrayElement());
488
489 // Update all bindings that refer to the action.
490 var nameProperty = actionProperty.FindPropertyRelative("m_Name");
491 var oldName = nameProperty.stringValue;
492 var bindingsProperty = actionMapProperty.FindPropertyRelative("m_Bindings");
493 for (var i = 0; i < bindingsProperty.arraySize; i++)
494 {
495 var element = bindingsProperty.GetArrayElementAtIndex(i);
496 var actionNameProperty = element.FindPropertyRelative("m_Action");
497 if (actionNameProperty.stringValue.Equals(oldName, StringComparison.InvariantCultureIgnoreCase))
498 actionNameProperty.stringValue = uniqueName;
499 }
500
501 // Update name.
502 nameProperty.stringValue = uniqueName;
503 }
504
505 public static void RenameActionMap(SerializedProperty actionMapProperty, string newName)
506 {
507 // Make sure name is unique in InputActionAsset.
508 var assetObject = actionMapProperty.serializedObject;
509 var mapsArrayProperty = assetObject.FindProperty("m_ActionMaps");
510 var uniqueName = FindUniqueName(mapsArrayProperty, newName, actionMapProperty.GetIndexOfArrayElement());
511
512 // Assign to map.
513 var nameProperty = actionMapProperty.FindPropertyRelative("m_Name");
514 nameProperty.stringValue = uniqueName;
515 }
516
517 public static void RenameComposite(SerializedProperty compositeGroupProperty, string newName)
518 {
519 var nameProperty = compositeGroupProperty.FindPropertyRelative("m_Name");
520 nameProperty.stringValue = newName;
521 }
522
523 public static SerializedProperty AddCompositeBinding(SerializedProperty actionProperty, SerializedProperty actionMapProperty,
524 string compositeName, Type compositeType = null, string groups = "", bool addPartBindings = true)
525 {
526 var newProperty = AddBinding(actionProperty, actionMapProperty);
527 newProperty.FindPropertyRelative("m_Name").stringValue = ObjectNames.NicifyVariableName(compositeName);
528 newProperty.FindPropertyRelative("m_Path").stringValue = compositeName;
529 newProperty.FindPropertyRelative("m_Flags").intValue = (int)InputBinding.Flags.Composite;
530
531 if (addPartBindings)
532 {
533 var fields = compositeType.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance);
534 foreach (var field in fields)
535 {
536 // Skip fields that aren't marked with [InputControl] attribute.
537 if (field.GetCustomAttribute<InputControlAttribute>(false) == null)
538 continue;
539
540 var partProperty = AddBinding(actionProperty, actionMapProperty, groups: groups);
541 partProperty.FindPropertyRelative("m_Name").stringValue = field.Name;
542 partProperty.FindPropertyRelative("m_Flags").intValue = (int)InputBinding.Flags.PartOfComposite;
543 }
544 }
545
546 return newProperty;
547 }
548
549 public static bool IsCompositeBinding(SerializedProperty bindingProperty)
550 {
551 using (var flagsProperty = bindingProperty.FindPropertyRelative("m_Flags"))
552 {
553 var flags = (InputBinding.Flags)flagsProperty.intValue;
554 return (flags & InputBinding.Flags.Composite) != 0;
555 }
556 }
557
558 public static SerializedProperty ChangeCompositeBindingType(SerializedProperty bindingProperty,
559 NameAndParameters nameAndParameters)
560 {
561 var bindingsArrayProperty = bindingProperty.GetArrayPropertyFromElement();
562 Debug.Assert(bindingsArrayProperty != null, "SerializedProperty is not an array of bindings");
563 var bindingIndex = bindingProperty.GetIndexOfArrayElement();
564
565 Debug.Assert(IsCompositeBinding(bindingProperty),
566 $"Binding {bindingProperty.propertyPath} is not a composite");
567
568 // If the composite still has the default name, change it to the default
569 // one for the new composite type.
570 var pathProperty = bindingProperty.FindPropertyRelative("m_Path");
571 var nameProperty = bindingProperty.FindPropertyRelative("m_Name");
572 if (nameProperty.stringValue ==
573 ObjectNames.NicifyVariableName(NameAndParameters.Parse(pathProperty.stringValue).name))
574 nameProperty.stringValue = ObjectNames.NicifyVariableName(nameAndParameters.name);
575
576 pathProperty.stringValue = nameAndParameters.ToString();
577
578 // Adjust part bindings if we have information on the registered composite. If we don't have
579 // a type, we don't know about the parts. In that case, leave part bindings untouched.
580 var compositeType = InputBindingComposite.s_Composites.LookupTypeRegistration(nameAndParameters.name);
581 if (compositeType != null)
582 {
583 var actionName = bindingProperty.FindPropertyRelative("m_Action").stringValue;
584
585 // Repurpose existing part bindings for the new composite or add any part bindings that
586 // we're missing.
587 var fields = compositeType.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance);
588 var partIndex = 0;
589 var partBindingsStartIndex = bindingIndex + 1;
590 foreach (var field in fields)
591 {
592 // Skip fields that aren't marked with [InputControl] attribute.
593 if (field.GetCustomAttribute<InputControlAttribute>(false) == null)
594 continue;
595
596 // See if we can reuse an existing part binding.
597 SerializedProperty partProperty = null;
598 if (partBindingsStartIndex + partIndex < bindingsArrayProperty.arraySize)
599 {
600 ////REVIEW: this should probably look up part bindings by name rather than going sequentially
601 var element = bindingsArrayProperty.GetArrayElementAtIndex(partBindingsStartIndex + partIndex);
602 if (((InputBinding.Flags)element.FindPropertyRelative("m_Flags").intValue & InputBinding.Flags.PartOfComposite) != 0)
603 partProperty = element;
604 }
605
606 // If not, insert a new binding.
607 if (partProperty == null)
608 {
609 partProperty = AddBindingToBindingArray(bindingsArrayProperty, partBindingsStartIndex + partIndex,
610 flags: InputBinding.Flags.PartOfComposite);
611 }
612
613 // Initialize.
614 partProperty.FindPropertyRelative("m_Name").stringValue = ObjectNames.NicifyVariableName(field.Name);
615 partProperty.FindPropertyRelative("m_Action").stringValue = actionName;
616 ++partIndex;
617 }
618
619 ////REVIEW: when we allow adding the same part multiple times, we may want to do something smarter here
620 // Delete extraneous part bindings.
621 while (partBindingsStartIndex + partIndex < bindingsArrayProperty.arraySize)
622 {
623 var element = bindingsArrayProperty.GetArrayElementAtIndex(partBindingsStartIndex + partIndex);
624 if (((InputBinding.Flags)element.FindPropertyRelative("m_Flags").intValue & InputBinding.Flags.PartOfComposite) == 0)
625 break;
626
627 bindingsArrayProperty.DeleteArrayElementAtIndex(partBindingsStartIndex + partIndex);
628 // No incrementing of partIndex.
629 }
630 }
631
632 return bindingProperty;
633 }
634
635 public static void ReplaceBindingGroup(SerializedObject asset, string oldBindingGroup, string newBindingGroup, bool deleteOrphanedBindings = false)
636 {
637 var mapArrayProperty = asset.FindProperty("m_ActionMaps");
638 var mapCount = mapArrayProperty.arraySize;
639
640 for (var k = 0; k < mapCount; ++k)
641 {
642 var actionMapProperty = mapArrayProperty.GetArrayElementAtIndex(k);
643 var bindingsArrayProperty = actionMapProperty.FindPropertyRelative("m_Bindings");
644 var bindingsCount = bindingsArrayProperty.arraySize;
645
646 for (var i = 0; i < bindingsCount; ++i)
647 {
648 var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i);
649 var groupsProperty = bindingProperty.FindPropertyRelative("m_Groups");
650 var groups = groupsProperty.stringValue;
651
652 // Ignore bindings not belonging to any control scheme.
653 if (string.IsNullOrEmpty(groups))
654 continue;
655
656 var groupsArray = groups.Split(InputBinding.Separator);
657 var numGroups = groupsArray.LengthSafe();
658 var didRename = false;
659 for (var n = 0; n < numGroups; ++n)
660 {
661 if (string.Compare(groupsArray[n], oldBindingGroup, StringComparison.InvariantCultureIgnoreCase) != 0)
662 continue;
663 if (string.IsNullOrEmpty(newBindingGroup))
664 {
665 ArrayHelpers.EraseAt(ref groupsArray, n);
666 --n;
667 --numGroups;
668 }
669 else
670 groupsArray[n] = newBindingGroup;
671 didRename = true;
672 }
673 if (!didRename)
674 continue;
675
676 if (groupsArray != null)
677 groupsProperty.stringValue = string.Join(InputBinding.kSeparatorString, groupsArray);
678 else
679 {
680 if (deleteOrphanedBindings)
681 {
682 // Binding no long belongs to any binding group. Delete it.
683 bindingsArrayProperty.DeleteArrayElementAtIndex(i);
684 --i;
685 --bindingsCount;
686 }
687 else
688 {
689 groupsProperty.stringValue = string.Empty;
690 }
691 }
692 }
693 }
694 }
695
696 public static void RemoveUnusedBindingGroups(SerializedProperty binding, ReadOnlyArray<InputControlScheme> controlSchemes)
697 {
698 var groupsProperty = binding.FindPropertyRelative(nameof(InputBinding.m_Groups));
699 groupsProperty.stringValue = string.Join(InputBinding.kSeparatorString,
700 groupsProperty.stringValue
701 .Split(InputBinding.Separator)
702 .Where(g => controlSchemes.Any(c => c.bindingGroup.Equals(g, StringComparison.InvariantCultureIgnoreCase))));
703 }
704
705 #region Control Schemes
706
707 public static void DeleteAllControlSchemes(SerializedObject asset)
708 {
709 var schemes = GetControlSchemesArray(asset);
710 while (schemes.arraySize > 0)
711 schemes.DeleteArrayElementAtIndex(0);
712 }
713
714 public static int IndexOfControlScheme(SerializedProperty controlSchemeArray, string controlSchemeName)
715 {
716 var serializedControlScheme = controlSchemeArray.FirstOrDefault(sp =>
717 sp.FindPropertyRelative(nameof(InputControlScheme.m_Name)).stringValue == controlSchemeName);
718 return serializedControlScheme?.GetIndexOfArrayElement() ?? -1;
719 }
720
721 public static SerializedProperty GetControlSchemesArray(SerializedObject asset)
722 {
723 return asset.FindProperty(nameof(InputActionAsset.m_ControlSchemes));
724 }
725
726 #endregion // Control Schemes
727 }
728}
729#endif // UNITY_EDITOR