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.

InputControlPathEditor.cs 9.1KB

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