A game about forced loneliness, made by TACStudios
1#if UNITY_EDITOR || PACKAGE_DOCS_GENERATION 2using System; 3using System.Collections.Generic; 4using System.Linq; 5using System.Reflection; 6using UnityEditor; 7using UnityEngine.InputSystem.Layouts; 8 9namespace UnityEngine.InputSystem.Editor 10{ 11 /// <summary> 12 /// Custom editor UI for editing control paths. 13 /// </summary> 14 /// <remarks> 15 /// This is the implementation underlying <see cref="InputControlPathDrawer"/>. It is useful primarily when 16 /// greater control is required than is offered by the <see cref="PropertyDrawer"/> mechanism. In particular, 17 /// it allows applying additional constraints such as requiring control paths to match ... 18 /// </remarks> 19 public sealed class InputControlPathEditor : IDisposable 20 { 21 /// <summary> 22 /// Initialize the control path editor. 23 /// </summary> 24 /// <param name="pathProperty"><see cref="string"/> type property that will receive the picked input control path.</param> 25 /// <param name="pickerState">Persistent editing state of the path editor. Used to retain state across domain reloads.</param> 26 /// <param name="onModified">Delegate that is called when the path has been modified.</param> 27 /// <param name="label">Optional label to display instead of display name of <paramref name="pathProperty"/>.</param> 28 /// <exception cref="ArgumentNullException"><paramref name="pathProperty"/> is <c>null</c>.</exception> 29 public InputControlPathEditor(SerializedProperty pathProperty, InputControlPickerState pickerState, Action onModified, GUIContent label = null) 30 { 31 if (pathProperty == null) 32 throw new ArgumentNullException(nameof(pathProperty)); 33 34 this.pathProperty = pathProperty; 35 this.onModified = onModified; 36 m_PickerState = pickerState ?? new InputControlPickerState(); 37 m_PathLabel = label ?? new GUIContent(pathProperty.displayName, pathProperty.GetTooltip()); 38 } 39 40 public void Dispose() 41 { 42 m_PickerDropdown?.Dispose(); 43 } 44 45 public void SetControlPathsToMatch(IEnumerable<string> controlPaths) 46 { 47 m_ControlPathsToMatch = controlPaths.ToArray(); 48 m_PickerDropdown?.SetControlPathsToMatch(m_ControlPathsToMatch); 49 } 50 51 /// <summary> 52 /// Constrain the type of control layout that can be picked. 53 /// </summary> 54 /// <param name="expectedControlLayout">Name of the layout. This it the name as registered with 55 /// <see cref="InputSystem.RegisterLayout"/>.</param>. 56 /// <remarks> 57 /// <example> 58 /// <code> 59 /// // Pick only button controls. 60 /// editor.SetExpectedControlLayout("Button"); 61 /// </code> 62 /// </example> 63 /// </remarks> 64 public void SetExpectedControlLayout(string expectedControlLayout) 65 { 66 m_ExpectedControlLayout = expectedControlLayout; 67 m_PickerDropdown?.SetExpectedControlLayout(m_ExpectedControlLayout); 68 } 69 70 public void SetExpectedControlLayoutFromAttribute() 71 { 72 var field = pathProperty.GetField(); 73 if (field == null) 74 return; 75 76 var attribute = field.GetCustomAttribute<InputControlAttribute>(); 77 if (attribute != null) 78 SetExpectedControlLayout(attribute.layout); 79 } 80 81 public void OnGUI() 82 { 83 EditorGUILayout.BeginHorizontal(); 84 ////FIXME: for some reason, the left edge doesn't align properly in GetRect()'s result; indentation issue? 85 var rect = GUILayoutUtility.GetRect(0, EditorGUIUtility.singleLineHeight); 86 rect.x += EditorGUIUtility.standardVerticalSpacing + 2; 87 rect.width -= EditorGUIUtility.standardVerticalSpacing * 2 + 4; 88 OnGUI(rect); 89 EditorGUILayout.EndHorizontal(); 90 } 91 92 public void OnGUI(Rect rect, GUIContent label = null, SerializedProperty property = null, Action modifiedCallback = null) 93 { 94 var pathLabel = label ?? m_PathLabel; 95 var serializedProperty = property ?? pathProperty; 96 97 var lineRect = rect; 98 var labelRect = lineRect; 99 labelRect.width = EditorGUIUtility.labelWidth; 100 EditorGUI.LabelField(labelRect, pathLabel); 101 lineRect.x += labelRect.width; 102 lineRect.width -= labelRect.width; 103 104 var bindingTextRect = lineRect; 105 var editButtonRect = lineRect; 106 107 var bindingTextRectOffset = 80; 108 bindingTextRect.width += bindingTextRectOffset; 109 bindingTextRect.x -= bindingTextRectOffset + 20; 110 editButtonRect.x = bindingTextRect.x + bindingTextRect.width; // Place it directly after the textRect 111 editButtonRect.width = 20; 112 editButtonRect.height = 15; 113 114 var path = String.Empty; 115 try 116 { 117 path = serializedProperty.stringValue; 118 } 119 catch 120 { 121 // This try-catch block is a temporary fix for ISX-1436 122 // The plan is to convert InputControlPathEditor entirely to UITK and therefore this fix will 123 // no longer be required. 124 return; 125 } 126 127 ////TODO: this should be cached; generates needless GC churn 128 var displayName = InputControlPath.ToHumanReadableString(path); 129 130 // Either show dropdown control that opens path picker or show path directly as 131 // text, if manual path editing is toggled on. 132 if (m_PickerState.manualPathEditMode) 133 { 134 ////FIXME: for some reason the text field does not fill all the rect but rather adds large padding on the left 135 bindingTextRect.x -= 15; 136 bindingTextRect.width += 15; 137 138 EditorGUI.BeginChangeCheck(); 139 path = EditorGUI.DelayedTextField(bindingTextRect, path); 140 if (EditorGUI.EndChangeCheck()) 141 { 142 serializedProperty.stringValue = path; 143 serializedProperty.serializedObject.ApplyModifiedProperties(); 144 (modifiedCallback ?? onModified).Invoke(); 145 } 146 } 147 else 148 { 149 // Dropdown that shows binding text and allows opening control picker. 150 if (EditorGUI.DropdownButton(bindingTextRect, new GUIContent(displayName), FocusType.Keyboard)) 151 { 152 SetExpectedControlLayoutFromAttribute(serializedProperty); 153 ////TODO: for bindings that are part of composites, use the layout information from the [InputControl] attribute on the field 154 ShowDropdown(bindingTextRect, serializedProperty, modifiedCallback ?? onModified); 155 } 156 } 157 158 // Button to toggle between text edit mode. 159 m_PickerState.manualPathEditMode = GUI.Toggle(editButtonRect, m_PickerState.manualPathEditMode, "T", 160 EditorStyles.miniButton); 161 } 162 163 private void ShowDropdown(Rect rect, SerializedProperty serializedProperty, Action modifiedCallback) 164 { 165 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 166 InputActionsEditorSettingsProvider.SetIMGUIDropdownVisible(true, false); 167 #endif 168 if (m_PickerDropdown == null) 169 { 170 m_PickerDropdown = new InputControlPickerDropdown( 171 m_PickerState, 172 path => 173 { 174 serializedProperty.stringValue = path; 175 m_PickerState.manualPathEditMode = false; 176 modifiedCallback(); 177 }); 178 } 179 180 m_PickerDropdown.SetPickedCallback(path => 181 { 182 serializedProperty.stringValue = path; 183 m_PickerState.manualPathEditMode = false; 184 modifiedCallback(); 185 }); 186 187 m_PickerDropdown.SetControlPathsToMatch(m_ControlPathsToMatch); 188 m_PickerDropdown.SetExpectedControlLayout(m_ExpectedControlLayout); 189 190 m_PickerDropdown.Show(rect); 191 } 192 193 private void SetExpectedControlLayoutFromAttribute(SerializedProperty property) 194 { 195 var field = property.GetField(); 196 if (field == null) 197 return; 198 199 var attribute = field.GetCustomAttribute<InputControlAttribute>(); 200 if (attribute != null) 201 SetExpectedControlLayout(attribute.layout); 202 } 203 204 public SerializedProperty pathProperty { get; } 205 public Action onModified { get; } 206 207 private GUIContent m_PathLabel; 208 private string m_ExpectedControlLayout; 209 private string[] m_ControlPathsToMatch; 210 private InputControlScheme[] m_ControlSchemes; 211 private bool m_NeedToClearProgressBar; 212 213 private InputControlPickerDropdown m_PickerDropdown; 214 private readonly InputControlPickerState m_PickerState; 215 private InputActionRebindingExtensions.RebindingOperation m_RebindingOperation; 216 } 217} 218 #endif // UNITY_EDITOR