No Description
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.

InputBindingPropertiesView.cs 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. #if UNITY_EDITOR
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. using UnityEditor;
  7. using UnityEngine.InputSystem.Editor.Lists;
  8. using UnityEngine.InputSystem.Layouts;
  9. using UnityEngine.InputSystem.Utilities;
  10. ////REVIEW: when we start with a blank tree view state, we should initialize the control picker to select the control currently
  11. //// selected by the path property
  12. namespace UnityEngine.InputSystem.Editor
  13. {
  14. /// <summary>
  15. /// UI for editing properties of an <see cref="InputBinding"/>. Right-most pane in action editor when
  16. /// binding is selected in middle pane.
  17. /// </summary>
  18. internal class InputBindingPropertiesView : PropertiesViewBase, IDisposable
  19. {
  20. public static FourCC k_GroupsChanged => new FourCC("GRPS");
  21. public static FourCC k_PathChanged => new FourCC("PATH");
  22. public static FourCC k_CompositeTypeChanged => new FourCC("COMP");
  23. public static FourCC k_CompositePartAssignmentChanged => new FourCC("PART");
  24. public InputBindingPropertiesView(
  25. SerializedProperty bindingProperty,
  26. Action<FourCC> onChange = null,
  27. InputControlPickerState controlPickerState = null,
  28. string expectedControlLayout = null,
  29. ReadOnlyArray<InputControlScheme> controlSchemes = new ReadOnlyArray<InputControlScheme>(),
  30. IEnumerable<string> controlPathsToMatch = null)
  31. : base(InputActionSerializationHelpers.IsCompositeBinding(bindingProperty) ? "Composite" : "Binding",
  32. bindingProperty, onChange, expectedControlLayout)
  33. {
  34. m_BindingProperty = bindingProperty;
  35. m_GroupsProperty = bindingProperty.FindPropertyRelative("m_Groups");
  36. m_PathProperty = bindingProperty.FindPropertyRelative("m_Path");
  37. m_BindingGroups = m_GroupsProperty.stringValue
  38. .Split(new[] {InputBinding.Separator}, StringSplitOptions.RemoveEmptyEntries).ToList();
  39. m_ExpectedControlLayout = expectedControlLayout;
  40. m_ControlSchemes = controlSchemes;
  41. var flags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
  42. m_IsPartOfComposite = (flags & InputBinding.Flags.PartOfComposite) != 0;
  43. m_IsComposite = (flags & InputBinding.Flags.Composite) != 0;
  44. // Set up control picker for m_Path. Not needed if the binding is a composite.
  45. if (!m_IsComposite)
  46. {
  47. m_ControlPickerState = controlPickerState ?? new InputControlPickerState();
  48. m_ControlPathEditor = new InputControlPathEditor(m_PathProperty, m_ControlPickerState, OnPathChanged);
  49. m_ControlPathEditor.SetExpectedControlLayout(m_ExpectedControlLayout);
  50. if (controlPathsToMatch != null)
  51. m_ControlPathEditor.SetControlPathsToMatch(controlPathsToMatch);
  52. }
  53. }
  54. public void Dispose()
  55. {
  56. m_ControlPathEditor?.Dispose();
  57. }
  58. protected override void DrawGeneralProperties()
  59. {
  60. var currentPath = m_PathProperty.stringValue;
  61. InputSystem.OnDrawCustomWarningForBindingPath(currentPath);
  62. if (m_IsComposite)
  63. {
  64. if (m_CompositeParameters == null)
  65. InitializeCompositeProperties();
  66. // Composite type dropdown.
  67. var selectedCompositeType = EditorGUILayout.Popup(s_CompositeTypeLabel, m_SelectedCompositeType, m_CompositeTypeOptions);
  68. if (selectedCompositeType != m_SelectedCompositeType)
  69. {
  70. m_SelectedCompositeType = selectedCompositeType;
  71. OnCompositeTypeChanged();
  72. }
  73. // Composite parameters.
  74. m_CompositeParameters.OnGUI();
  75. }
  76. else
  77. {
  78. // Path.
  79. m_ControlPathEditor.OnGUI();
  80. // Composite part.
  81. if (m_IsPartOfComposite)
  82. {
  83. if (m_CompositeParts == null)
  84. InitializeCompositePartProperties();
  85. var selectedPart = EditorGUILayout.Popup(s_CompositePartAssignmentLabel, m_SelectedCompositePart,
  86. m_CompositePartOptions);
  87. if (selectedPart != m_SelectedCompositePart)
  88. {
  89. m_SelectedCompositePart = selectedPart;
  90. OnCompositePartAssignmentChanged();
  91. }
  92. }
  93. // Show the specific controls which match the current path
  94. DrawMatchingControlPaths();
  95. // Control scheme matrix.
  96. DrawUseInControlSchemes();
  97. }
  98. }
  99. /// <summary>
  100. /// Used to keep track of which foldouts are expanded.
  101. /// </summary>
  102. private static bool showMatchingLayouts = false;
  103. private static Dictionary<string, bool> showMatchingChildLayouts = new Dictionary<string, bool>();
  104. private static void DrawMatchingControlPaths(List<MatchingControlPath> matchingControlPaths)
  105. {
  106. foreach (var matchingControlPath in matchingControlPaths)
  107. {
  108. bool showLayout = false;
  109. EditorGUI.indentLevel++;
  110. var text = $"{matchingControlPath.deviceName} > {matchingControlPath.controlName}";
  111. if (matchingControlPath.children.Count() > 0 && !matchingControlPath.isRoot)
  112. {
  113. showMatchingChildLayouts.TryGetValue(matchingControlPath.deviceName, out showLayout);
  114. showMatchingChildLayouts[matchingControlPath.deviceName] = EditorGUILayout.Foldout(showLayout, text);
  115. }
  116. else
  117. {
  118. EditorGUILayout.LabelField(text);
  119. }
  120. showLayout |= matchingControlPath.isRoot;
  121. if (showLayout)
  122. DrawMatchingControlPaths(matchingControlPath.children);
  123. EditorGUI.indentLevel--;
  124. }
  125. }
  126. /// <summary>
  127. /// Finds all registered control paths implemented by concrete classes which match the current binding path and renders it.
  128. /// </summary>
  129. private void DrawMatchingControlPaths()
  130. {
  131. bool controlPathUsagePresent = false;
  132. List<MatchingControlPath> matchingControlPaths = MatchingControlPath.CollectMatchingControlPaths(m_ControlPathEditor.pathProperty.stringValue, showMatchingLayouts, ref controlPathUsagePresent);
  133. if (matchingControlPaths == null || matchingControlPaths.Count != 0)
  134. {
  135. EditorGUILayout.BeginVertical();
  136. showMatchingLayouts = EditorGUILayout.Foldout(showMatchingLayouts, "Derived Bindings");
  137. if (showMatchingLayouts)
  138. {
  139. if (matchingControlPaths == null)
  140. {
  141. if (controlPathUsagePresent)
  142. EditorGUILayout.HelpBox("No registered controls match this current binding. Some controls are only registered at runtime.", MessageType.Warning);
  143. else
  144. EditorGUILayout.HelpBox("No other registered controls match this current binding. Some controls are only registered at runtime.", MessageType.Warning);
  145. }
  146. else
  147. {
  148. DrawMatchingControlPaths(matchingControlPaths);
  149. }
  150. }
  151. EditorGUILayout.EndVertical();
  152. }
  153. }
  154. /// <summary>
  155. /// Draw control scheme matrix that allows selecting which control schemes a particular
  156. /// binding appears in.
  157. /// </summary>
  158. private void DrawUseInControlSchemes()
  159. {
  160. if (m_ControlSchemes.Count <= 0)
  161. return;
  162. EditorGUILayout.Space();
  163. EditorGUILayout.Space();
  164. EditorGUILayout.LabelField(s_UseInControlSchemesLAbel, EditorStyles.boldLabel);
  165. EditorGUILayout.BeginVertical();
  166. foreach (var scheme in m_ControlSchemes)
  167. {
  168. EditorGUI.BeginChangeCheck();
  169. var result = EditorGUILayout.Toggle(scheme.name, m_BindingGroups.Contains(scheme.bindingGroup));
  170. if (EditorGUI.EndChangeCheck())
  171. {
  172. if (result)
  173. {
  174. m_BindingGroups.Add(scheme.bindingGroup);
  175. }
  176. else
  177. {
  178. m_BindingGroups.Remove(scheme.bindingGroup);
  179. }
  180. OnBindingGroupsChanged();
  181. }
  182. }
  183. EditorGUILayout.EndVertical();
  184. }
  185. private void InitializeCompositeProperties()
  186. {
  187. // Find name of current composite.
  188. var path = m_PathProperty.stringValue;
  189. var compositeNameAndParameters = NameAndParameters.Parse(path);
  190. var compositeName = compositeNameAndParameters.name;
  191. var compositeType = InputBindingComposite.s_Composites.LookupTypeRegistration(compositeName);
  192. // Collect all possible composite types.
  193. var selectedCompositeIndex = -1;
  194. var compositeTypeOptionsList = new List<GUIContent>();
  195. var compositeTypeList = new List<string>();
  196. var currentIndex = 0;
  197. foreach (var composite in InputBindingComposite.s_Composites.internedNames.Where(x =>
  198. !InputBindingComposite.s_Composites.aliases.Contains(x)).OrderBy(x => x))
  199. {
  200. if (!string.IsNullOrEmpty(m_ExpectedControlLayout))
  201. {
  202. var valueType = InputBindingComposite.GetValueType(composite);
  203. if (valueType != null &&
  204. !InputControlLayout.s_Layouts.ValueTypeIsAssignableFrom(
  205. new InternedString(m_ExpectedControlLayout), valueType))
  206. continue;
  207. }
  208. if (InputBindingComposite.s_Composites.LookupTypeRegistration(composite) == compositeType)
  209. selectedCompositeIndex = currentIndex;
  210. var name = ObjectNames.NicifyVariableName(composite);
  211. compositeTypeOptionsList.Add(new GUIContent(name));
  212. compositeTypeList.Add(composite);
  213. ++currentIndex;
  214. }
  215. // If the current composite type isn't a registered type, add it to the list as
  216. // an extra option.
  217. if (selectedCompositeIndex == -1)
  218. {
  219. selectedCompositeIndex = compositeTypeList.Count;
  220. compositeTypeOptionsList.Add(new GUIContent(ObjectNames.NicifyVariableName(compositeName)));
  221. compositeTypeList.Add(compositeName);
  222. }
  223. m_CompositeTypes = compositeTypeList.ToArray();
  224. m_CompositeTypeOptions = compositeTypeOptionsList.ToArray();
  225. m_SelectedCompositeType = selectedCompositeIndex;
  226. // Initialize parameters.
  227. m_CompositeParameters = new ParameterListView
  228. {
  229. onChange = OnCompositeParametersModified
  230. };
  231. if (compositeType != null)
  232. m_CompositeParameters.Initialize(compositeType, compositeNameAndParameters.parameters);
  233. }
  234. private void InitializeCompositePartProperties()
  235. {
  236. var currentCompositePart = m_BindingProperty.FindPropertyRelative("m_Name").stringValue;
  237. ////REVIEW: this makes a lot of assumptions about the serialized data based on the one property we've been given in the ctor
  238. // Determine the name of the current composite type that the part belongs to.
  239. var bindingArrayProperty = m_BindingProperty.GetArrayPropertyFromElement();
  240. var partBindingIndex = InputActionSerializationHelpers.GetIndex(bindingArrayProperty, m_BindingProperty);
  241. var compositeBindingIndex =
  242. InputActionSerializationHelpers.GetCompositeStartIndex(bindingArrayProperty, partBindingIndex);
  243. if (compositeBindingIndex == -1)
  244. return;
  245. var compositeBindingProperty = bindingArrayProperty.GetArrayElementAtIndex(compositeBindingIndex);
  246. var compositePath = compositeBindingProperty.FindPropertyRelative("m_Path").stringValue;
  247. var compositeNameAndParameters = NameAndParameters.Parse(compositePath);
  248. // Initialize option list from all parts available for the composite.
  249. var optionList = new List<GUIContent>();
  250. var nameList = new List<string>();
  251. var currentIndex = 0;
  252. var selectedPartNameIndex = -1;
  253. foreach (var partName in InputBindingComposite.GetPartNames(compositeNameAndParameters.name))
  254. {
  255. if (partName.Equals(currentCompositePart, StringComparison.InvariantCultureIgnoreCase))
  256. selectedPartNameIndex = currentIndex;
  257. var niceName = ObjectNames.NicifyVariableName(partName);
  258. optionList.Add(new GUIContent(niceName));
  259. nameList.Add(partName);
  260. ++currentIndex;
  261. }
  262. // If currently selected part is not in list, add it as an option.
  263. if (selectedPartNameIndex == -1)
  264. {
  265. selectedPartNameIndex = nameList.Count;
  266. optionList.Add(new GUIContent(ObjectNames.NicifyVariableName(currentCompositePart)));
  267. nameList.Add(currentCompositePart);
  268. }
  269. m_CompositeParts = nameList.ToArray();
  270. m_CompositePartOptions = optionList.ToArray();
  271. m_SelectedCompositePart = selectedPartNameIndex;
  272. }
  273. private void OnCompositeParametersModified()
  274. {
  275. Debug.Assert(m_CompositeParameters != null);
  276. var path = m_PathProperty.stringValue;
  277. var nameAndParameters = NameAndParameters.Parse(path);
  278. nameAndParameters.parameters = m_CompositeParameters.GetParameters();
  279. m_PathProperty.stringValue = nameAndParameters.ToString();
  280. m_PathProperty.serializedObject.ApplyModifiedProperties();
  281. OnPathChanged();
  282. }
  283. private void OnBindingGroupsChanged()
  284. {
  285. m_GroupsProperty.stringValue = string.Join(InputBinding.kSeparatorString, m_BindingGroups.ToArray());
  286. m_GroupsProperty.serializedObject.ApplyModifiedProperties();
  287. onChange?.Invoke(k_GroupsChanged);
  288. }
  289. private void OnPathChanged()
  290. {
  291. m_BindingProperty.serializedObject.ApplyModifiedProperties();
  292. onChange?.Invoke(k_PathChanged);
  293. }
  294. private void OnCompositeTypeChanged()
  295. {
  296. var nameAndParameters = new NameAndParameters
  297. {
  298. name = m_CompositeTypes[m_SelectedCompositeType],
  299. parameters = m_CompositeParameters.GetParameters()
  300. };
  301. InputActionSerializationHelpers.ChangeCompositeBindingType(m_BindingProperty, nameAndParameters);
  302. m_PathProperty.serializedObject.ApplyModifiedProperties();
  303. onChange?.Invoke(k_CompositeTypeChanged);
  304. }
  305. private void OnCompositePartAssignmentChanged()
  306. {
  307. m_BindingProperty.FindPropertyRelative("m_Name").stringValue = m_CompositeParts[m_SelectedCompositePart];
  308. m_BindingProperty.serializedObject.ApplyModifiedProperties();
  309. onChange?.Invoke(k_CompositePartAssignmentChanged);
  310. }
  311. private readonly bool m_IsComposite;
  312. private ParameterListView m_CompositeParameters;
  313. private int m_SelectedCompositeType;
  314. private GUIContent[] m_CompositeTypeOptions;
  315. private string[] m_CompositeTypes;
  316. private int m_SelectedCompositePart;
  317. private GUIContent[] m_CompositePartOptions;
  318. private string[] m_CompositeParts;
  319. private readonly SerializedProperty m_GroupsProperty;
  320. private readonly SerializedProperty m_BindingProperty;
  321. private readonly SerializedProperty m_PathProperty;
  322. private readonly InputControlPickerState m_ControlPickerState;
  323. private readonly InputControlPathEditor m_ControlPathEditor;
  324. private static readonly GUIContent s_CompositeTypeLabel = EditorGUIUtility.TrTextContent("Composite Type",
  325. "Type of composite. Allows changing the composite type retroactively. Doing so will modify the bindings that are part of the composite.");
  326. private static readonly GUIContent s_UseInControlSchemesLAbel = EditorGUIUtility.TrTextContent("Use in control scheme",
  327. "In which control schemes the binding is active. A binding can be used by arbitrary many control schemes. If a binding is not "
  328. + "assigned to a specific control schemes, it is active in all of them.");
  329. private static readonly GUIContent s_CompositePartAssignmentLabel = EditorGUIUtility.TrTextContent(
  330. "Composite Part",
  331. "The named part of the composite that the binding is assigned to. Multiple bindings may be assigned the same part. All controls from "
  332. + "all bindings that are assigned the same part will collectively feed values into that part of the composite.");
  333. private ReadOnlyArray<InputControlScheme> m_ControlSchemes;
  334. private readonly List<string> m_BindingGroups;
  335. private readonly string m_ExpectedControlLayout;
  336. }
  337. }
  338. #endif // UNITY_EDITOR