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