Brak opisu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InputActionDrawerBase.cs 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #if UNITY_EDITOR
  2. using System.Collections.Generic;
  3. using UnityEditor;
  4. using UnityEditor.IMGUI.Controls;
  5. namespace UnityEngine.InputSystem.Editor
  6. {
  7. /// <summary>
  8. /// Base class for property drawers that display input actions.
  9. /// </summary>
  10. internal abstract class InputActionDrawerBase : PropertyDrawer
  11. {
  12. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
  13. {
  14. InitTreeIfNeeded(property);
  15. return GetOrCreateViewData(property).TreeView.totalHeight;
  16. }
  17. #if UNITY_2023_2_OR_NEWER
  18. [System.Obsolete("CanCacheInspectorGUI has been deprecated and is no longer used.", false)]
  19. #endif
  20. public override bool CanCacheInspectorGUI(SerializedProperty property)
  21. {
  22. return false;
  23. }
  24. public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
  25. {
  26. InitTreeIfNeeded(property);
  27. EditorGUI.BeginProperty(position, label, property);
  28. SetNameIfNotSet(property);
  29. GetOrCreateViewData(property).TreeView.OnGUI(position);
  30. EditorGUI.EndProperty();
  31. }
  32. private void InitTreeIfNeeded(SerializedProperty property)
  33. {
  34. // NOTE: Unlike InputActionEditorWindow, we do not need to protect against the SerializedObject
  35. // changing behind our backs by undo/redo here. Being a PropertyDrawer, we will automatically
  36. // get recreated by Unity when it touches our serialized data.
  37. var viewData = GetOrCreateViewData(property);
  38. var propertyIsClone = IsPropertyAClone(property);
  39. if (!propertyIsClone && viewData.TreeView != null && viewData.TreeView.serializedObject == property.serializedObject)
  40. return;
  41. if (propertyIsClone)
  42. ResetProperty(property);
  43. viewData.TreeView = new InputActionTreeView(property.serializedObject)
  44. {
  45. onBuildTree = () => BuildTree(property),
  46. onDoubleClick = item => OnItemDoubleClicked(item, property),
  47. drawActionPropertiesButton = true,
  48. title = (GetPropertyTitle(property), property.GetTooltip())
  49. };
  50. viewData.TreeView.Reload();
  51. }
  52. private void SetNameIfNotSet(SerializedProperty actionProperty)
  53. {
  54. var nameProperty = actionProperty.FindPropertyRelative("m_Name");
  55. if (!string.IsNullOrEmpty(nameProperty.stringValue))
  56. return;
  57. // Special case for InputActionProperty where we want to take the name not from
  58. // the m_Action property embedded in it but rather from the InputActionProperty field
  59. // itself.
  60. var name = actionProperty.displayName;
  61. var parent = actionProperty.GetParentProperty();
  62. if (parent != null && parent.type == "InputActionProperty")
  63. name = parent.displayName;
  64. var suffix = GetSuffixToRemoveFromPropertyDisplayName();
  65. if (name.EndsWith(suffix))
  66. name = name.Substring(0, name.Length - suffix.Length);
  67. // If it's a singleton action, we also need to adjust the InputBinding.action
  68. // property values in its binding list.
  69. var singleActionBindings = actionProperty.FindPropertyRelative("m_SingletonActionBindings");
  70. if (singleActionBindings != null)
  71. {
  72. var bindingCount = singleActionBindings.arraySize;
  73. for (var i = 0; i < bindingCount; ++i)
  74. {
  75. var binding = singleActionBindings.GetArrayElementAtIndex(i);
  76. var actionNameProperty = binding.FindPropertyRelative("m_Action");
  77. actionNameProperty.stringValue = name;
  78. }
  79. }
  80. nameProperty.stringValue = name;
  81. actionProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo();
  82. EditorUtility.SetDirty(actionProperty.serializedObject.targetObject);
  83. }
  84. private static string GetPropertyTitle(SerializedProperty property)
  85. {
  86. var propertyTitleNumeral = string.Empty;
  87. if (property.GetParentProperty() != null && property.GetParentProperty().isArray)
  88. propertyTitleNumeral = $" {property.GetIndexOfArrayElement()}";
  89. if (property.displayName != null &&
  90. property.displayName.Length > 0 &&
  91. (property.type == nameof(InputAction) || property.type == nameof(InputActionMap)))
  92. {
  93. return $"{property.displayName}{propertyTitleNumeral}";
  94. }
  95. return property.type == nameof(InputActionMap) ? $"Input Action Map{propertyTitleNumeral}" : $"Input Action{propertyTitleNumeral}";
  96. }
  97. private void OnItemDoubleClicked(ActionTreeItemBase item, SerializedProperty property)
  98. {
  99. var viewData = GetOrCreateViewData(property);
  100. // Double-clicking on binding or action item opens property popup.
  101. PropertiesViewBase propertyView = null;
  102. if (item is BindingTreeItem)
  103. {
  104. if (viewData.ControlPickerState == null)
  105. viewData.ControlPickerState = new InputControlPickerState();
  106. propertyView = new InputBindingPropertiesView(item.property,
  107. controlPickerState: viewData.ControlPickerState,
  108. expectedControlLayout: item.expectedControlLayout,
  109. onChange:
  110. change => viewData.TreeView.Reload());
  111. }
  112. else if (item is ActionTreeItem)
  113. {
  114. propertyView = new InputActionPropertiesView(item.property,
  115. onChange: change => viewData.TreeView.Reload());
  116. }
  117. if (propertyView != null)
  118. {
  119. var rect = new Rect(GUIUtility.GUIToScreenPoint(Event.current.mousePosition), Vector2.zero);
  120. PropertiesViewPopup.Show(rect, propertyView);
  121. }
  122. }
  123. private InputActionDrawerViewData GetOrCreateViewData(SerializedProperty property)
  124. {
  125. if (m_PerPropertyViewData == null)
  126. m_PerPropertyViewData = new Dictionary<string, InputActionDrawerViewData>();
  127. if (m_PerPropertyViewData.TryGetValue(property.propertyPath, out var data)) return data;
  128. data = new InputActionDrawerViewData();
  129. m_PerPropertyViewData.Add(property.propertyPath, data);
  130. return data;
  131. }
  132. protected abstract TreeViewItem BuildTree(SerializedProperty property);
  133. protected abstract string GetSuffixToRemoveFromPropertyDisplayName();
  134. protected abstract bool IsPropertyAClone(SerializedProperty property);
  135. protected abstract void ResetProperty(SerializedProperty property);
  136. // Unity creates a single instance of a property drawer to draw multiple instances of the property drawer type,
  137. // so we can't store state in the property drawer for each item. We do need that though, because each InputAction
  138. // needs to have it's own instance of the InputActionTreeView to correctly draw it's own bindings. So what we do
  139. // is keep this array around that stores a tree view instance for each unique property path that the property
  140. // drawer encounters. The tree view will be recreated if we detect that the property being drawn has changed.
  141. private Dictionary<string, InputActionDrawerViewData> m_PerPropertyViewData;
  142. internal class PropertiesViewPopup : EditorWindow
  143. {
  144. public static void Show(Rect btnRect, PropertiesViewBase view)
  145. {
  146. var window = CreateInstance<PropertiesViewPopup>();
  147. window.m_PropertyView = view;
  148. window.ShowPopup();
  149. window.ShowAsDropDown(btnRect, new Vector2(300, 350));
  150. }
  151. private void OnGUI()
  152. {
  153. m_PropertyView.OnGUI();
  154. }
  155. private PropertiesViewBase m_PropertyView;
  156. }
  157. private class InputActionDrawerViewData
  158. {
  159. public InputActionTreeView TreeView;
  160. public InputControlPickerState ControlPickerState;
  161. }
  162. }
  163. }
  164. #endif // UNITY_EDITOR