Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. #if UNITY_EDITOR || PACKAGE_DOCS_GENERATION
  2. using System;
  3. using System.Collections.Generic;
  4. using UnityEditor;
  5. using UnityEditor.UIElements;
  6. using UnityEngine.InputSystem.Utilities;
  7. using UnityEngine.UIElements;
  8. ////REVIEW: generalize this to something beyond just parameters?
  9. namespace UnityEngine.InputSystem.Editor
  10. {
  11. /// <summary>
  12. /// A custom UI for editing parameter values on a <see cref="InputProcessor"/>, <see cref="InputBindingComposite"/>,
  13. /// or <see cref="IInputInteraction"/>.
  14. /// </summary>
  15. /// <remarks>
  16. /// When implementing a custom parameter editor, use <see cref="InputParameterEditor{TObject}"/> instead.
  17. /// </remarks>
  18. /// <seealso cref="InputActionRebindingExtensions.GetParameterValue(InputAction,string,InputBinding)"/>
  19. /// <seealso cref="InputActionRebindingExtensions.ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
  20. public abstract class InputParameterEditor
  21. {
  22. /// <summary>
  23. /// The <see cref="InputProcessor"/>, <see cref="InputBindingComposite"/>, or <see cref="IInputInteraction"/>
  24. /// being edited.
  25. /// </summary>
  26. public object target { get; internal set; }
  27. /// <summary>
  28. /// Callback for implementing a custom UI.
  29. /// </summary>
  30. public abstract void OnGUI();
  31. #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
  32. /// <summary>
  33. /// Add visual elements for this parameter editor to a root VisualElement.
  34. /// </summary>
  35. /// <param name="root">The VisualElement that parameter editor elements should be added to.</param>
  36. /// <param name="onChangedCallback">A callback that will be called when any of the parameter editors
  37. /// changes value.</param>
  38. public abstract void OnDrawVisualElements(VisualElement root, Action onChangedCallback);
  39. #endif
  40. internal abstract void SetTarget(object target);
  41. internal static Type LookupEditorForType(Type type)
  42. {
  43. if (type == null)
  44. throw new ArgumentNullException(nameof(type));
  45. if (s_TypeLookupCache == null)
  46. {
  47. s_TypeLookupCache = new Dictionary<Type, Type>();
  48. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
  49. {
  50. foreach (var typeInfo in assembly.DefinedTypes)
  51. {
  52. // Only looking for classes.
  53. if (!typeInfo.IsClass)
  54. continue;
  55. var definedType = typeInfo.AsType();
  56. if (definedType == null)
  57. continue;
  58. // Only looking for InputParameterEditors.
  59. if (!typeof(InputParameterEditor).IsAssignableFrom(definedType))
  60. continue;
  61. // Grab <TValue> parameter from InputParameterEditor<>.
  62. var objectType =
  63. TypeHelpers.GetGenericTypeArgumentFromHierarchy(definedType, typeof(InputParameterEditor<>),
  64. 0);
  65. if (objectType == null)
  66. continue;
  67. s_TypeLookupCache[objectType] = definedType;
  68. }
  69. }
  70. }
  71. s_TypeLookupCache.TryGetValue(type, out var editorType);
  72. return editorType;
  73. }
  74. private static Dictionary<Type, Type> s_TypeLookupCache;
  75. }
  76. /// <summary>
  77. /// A custom UI for editing parameter values on a <see cref="InputProcessor"/>,
  78. /// <see cref="InputBindingComposite"/>, or <see cref="IInputInteraction"/>.
  79. /// </summary>
  80. /// <remarks>
  81. /// Custom parameter editors do not need to be registered explicitly. Say you have a custom
  82. /// <see cref="InputProcessor"/> called <c>QuantizeProcessor</c>. To define a custom editor
  83. /// UI for it, simply define a new class based on <c>InputParameterEditor&lt;QuantizeProcessor&gt;</c>.
  84. ///
  85. /// <example>
  86. /// <code>
  87. /// public class QuantizeProcessorEditor : InputParameterEditor&lt;QuantizeProcessor&gt;
  88. /// {
  89. /// // You can put initialization logic in OnEnable, if you need it.
  90. /// public override void OnEnable()
  91. /// {
  92. /// // Use the 'target' property to access the QuantizeProcessor instance.
  93. /// }
  94. ///
  95. /// // In OnGUI, you can define custom UI elements. Use EditorGUILayout to lay
  96. /// // out the controls.
  97. /// public override void OnGUI()
  98. /// {
  99. /// // Say that QuantizeProcessor has a "stepping" property that determines
  100. /// // the stepping distance for discrete values returned by the processor.
  101. /// // We can expose it here as a float field. To apply the modification to
  102. /// // processor object, we just assign the value back to the field on it.
  103. /// target.stepping = EditorGUILayout.FloatField(
  104. /// m_SteppingLabel, target.stepping);
  105. /// }
  106. ///
  107. /// private GUIContent m_SteppingLabel = new GUIContent("Stepping",
  108. /// "Discrete stepping with which input values will be quantized.");
  109. /// }
  110. /// </code>
  111. /// </example>
  112. ///
  113. /// Note that a parameter editor takes over the entire editing UI for the object and
  114. /// not just the editing of specific parameters.
  115. ///
  116. /// The default parameter editor will derive names from the names of the respective
  117. /// fields just like the Unity inspector does. Also, it will respect tooltips applied
  118. /// to these fields with Unity's <c>TooltipAttribute</c>.
  119. ///
  120. /// So, let's say that <c>QuantizeProcessor</c> from our example was defined like
  121. /// below. In that case, the result would be equivalent to the custom parameter editor
  122. /// UI defined above.
  123. ///
  124. /// <example>
  125. /// <code>
  126. /// public class QuantizeProcessor : InputProcessor&lt;float&gt;
  127. /// {
  128. /// [Tooltip("Discrete stepping with which input values will be quantized.")]
  129. /// public float stepping;
  130. ///
  131. /// public override float Process(float value, InputControl control)
  132. /// {
  133. /// return value - value % stepping;
  134. /// }
  135. /// }
  136. /// </code>
  137. /// </example>
  138. /// </remarks>
  139. public abstract class InputParameterEditor<TObject> : InputParameterEditor
  140. where TObject : class
  141. {
  142. /// <summary>
  143. /// The <see cref="InputProcessor"/>, <see cref="InputBindingComposite"/>, or <see cref="IInputInteraction"/>
  144. /// being edited.
  145. /// </summary>
  146. public new TObject target { get; private set; }
  147. /// <summary>
  148. /// Called after the parameter editor has been initialized.
  149. /// </summary>
  150. protected virtual void OnEnable()
  151. {
  152. }
  153. internal override void SetTarget(object target)
  154. {
  155. if (target == null)
  156. throw new ArgumentNullException(nameof(target));
  157. if (!(target is TObject targetOfType))
  158. throw new ArgumentException(
  159. $"Expecting object of type '{typeof(TObject).Name}' but got object of type '{target.GetType().Name}' instead",
  160. nameof(target));
  161. this.target = targetOfType;
  162. base.target = targetOfType;
  163. OnEnable();
  164. }
  165. #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
  166. /// <summary>
  167. /// Default stub implementation of <see cref="InputParameterEditor.OnDrawVisualElements"/>.
  168. /// Should be overridden to create the desired UI.
  169. /// </summary>
  170. public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
  171. {
  172. }
  173. #endif
  174. /// <summary>
  175. /// Helper for parameters that have defaults (usually from <see cref="InputSettings"/>).
  176. /// </summary>
  177. /// <remarks>
  178. /// Has a bool toggle to switch between default and custom value.
  179. /// </remarks>
  180. internal class CustomOrDefaultSetting
  181. {
  182. public void Initialize(string label, string tooltip, string defaultName, Func<float> getValue,
  183. Action<float> setValue, Func<float> getDefaultValue, bool defaultComesFromInputSettings = true,
  184. float defaultInitializedValue = default)
  185. {
  186. m_GetValue = getValue;
  187. m_SetValue = setValue;
  188. m_GetDefaultValue = getDefaultValue;
  189. m_ToggleLabel = EditorGUIUtility.TrTextContent("Default",
  190. defaultComesFromInputSettings
  191. ? $"If enabled, the default {label.ToLower()} configured globally in the input settings is used. See Edit >> Project Settings... >> Input (NEW)."
  192. : "If enabled, the default value is used.");
  193. m_ValueLabel = EditorGUIUtility.TrTextContent(label, tooltip);
  194. if (defaultComesFromInputSettings)
  195. m_OpenInputSettingsLabel = EditorGUIUtility.TrTextContent("Open Input Settings");
  196. m_DefaultInitializedValue = defaultInitializedValue;
  197. m_UseDefaultValue = Mathf.Approximately(getValue(), defaultInitializedValue);
  198. m_DefaultComesFromInputSettings = defaultComesFromInputSettings;
  199. m_HelpBoxText =
  200. EditorGUIUtility.TrTextContent(
  201. $"Uses \"{defaultName}\" set in project-wide input settings.");
  202. }
  203. #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
  204. public void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
  205. {
  206. var value = m_GetValue();
  207. if (m_UseDefaultValue)
  208. value = m_GetDefaultValue();
  209. // If previous value was an epsilon away from default value, it most likely means that value was set by our own code down in this method.
  210. // Revert it back to default to show a nice readable value in UI.
  211. // ReSharper disable once CompareOfFloatsByEqualityOperator
  212. if ((value - float.Epsilon) == m_DefaultInitializedValue)
  213. value = m_DefaultInitializedValue;
  214. var container = new VisualElement();
  215. var settingsContainer = new VisualElement { style = { flexDirection = FlexDirection.Row } };
  216. m_FloatField = new FloatField(m_ValueLabel.text) { value = value };
  217. m_FloatField.Q("unity-text-input").AddToClassList("float-field");
  218. m_FloatField.RegisterValueChangedCallback(ChangeSettingValue);
  219. m_FloatField.RegisterCallback<BlurEvent>(_ => OnEditEnd(onChangedCallback));
  220. m_FloatField.SetEnabled(!m_UseDefaultValue);
  221. m_HelpBox = new HelpBox(m_HelpBoxText.text, HelpBoxMessageType.None);
  222. m_DefaultToggle = new Toggle("Default") { value = m_UseDefaultValue };
  223. m_DefaultToggle.RegisterValueChangedCallback(evt => ToggleUseDefaultValue(evt, onChangedCallback));
  224. var buttonContainer = new VisualElement
  225. {
  226. style =
  227. {
  228. flexDirection = FlexDirection.RowReverse
  229. }
  230. };
  231. m_OpenInputSettingsButton = new Button(InputSettingsProvider.Open){text = m_OpenInputSettingsLabel.text};
  232. m_OpenInputSettingsButton.AddToClassList("open-settings-button");
  233. settingsContainer.Add(m_FloatField);
  234. settingsContainer.Add(m_DefaultToggle);
  235. container.Add(settingsContainer);
  236. if (m_UseDefaultValue)
  237. {
  238. buttonContainer.Add(m_OpenInputSettingsButton);
  239. container.Add(m_HelpBox);
  240. }
  241. container.Add(buttonContainer);
  242. root.Add(container);
  243. }
  244. private void ChangeSettingValue(ChangeEvent<float> evt)
  245. {
  246. if (m_UseDefaultValue) return;
  247. // ReSharper disable once CompareOfFloatsByEqualityOperator
  248. if (evt.newValue == m_DefaultInitializedValue)
  249. {
  250. // If user sets a value that is equal to default initialized, change value slightly so it doesn't pass potential default checks.
  251. ////TODO: refactor all of this to use tri-state values instead, there is no obvious float value that we can use as default (well maybe NaN),
  252. ////so instead it would be better to have a separate bool to show if value is present or not.
  253. m_SetValue(evt.newValue + float.Epsilon);
  254. }
  255. else
  256. {
  257. m_SetValue(evt.newValue);
  258. }
  259. }
  260. private void OnEditEnd(Action onChangedCallback)
  261. {
  262. onChangedCallback.Invoke();
  263. }
  264. private void ToggleUseDefaultValue(ChangeEvent<bool> evt, Action onChangedCallback)
  265. {
  266. if (evt.newValue != m_UseDefaultValue)
  267. {
  268. m_SetValue(!evt.newValue ? m_GetDefaultValue() : m_DefaultInitializedValue);
  269. onChangedCallback.Invoke();
  270. }
  271. m_UseDefaultValue = evt.newValue;
  272. m_FloatField?.SetEnabled(!m_UseDefaultValue);
  273. }
  274. #endif
  275. public void OnGUI()
  276. {
  277. EditorGUILayout.BeginHorizontal();
  278. EditorGUI.BeginDisabledGroup(m_UseDefaultValue);
  279. var value = m_GetValue();
  280. if (m_UseDefaultValue)
  281. value = m_GetDefaultValue();
  282. // If previous value was an epsilon away from default value, it most likely means that value was set by our own code down in this method.
  283. // Revert it back to default to show a nice readable value in UI.
  284. // ReSharper disable once CompareOfFloatsByEqualityOperator
  285. if ((value - float.Epsilon) == m_DefaultInitializedValue)
  286. value = m_DefaultInitializedValue;
  287. ////TODO: use slider rather than float field
  288. var newValue = EditorGUILayout.FloatField(m_ValueLabel, value, GUILayout.ExpandWidth(false));
  289. if (!m_UseDefaultValue)
  290. {
  291. // ReSharper disable once CompareOfFloatsByEqualityOperator
  292. if (newValue == m_DefaultInitializedValue)
  293. // If user sets a value that is equal to default initialized, change value slightly so it doesn't pass potential default checks.
  294. ////TODO: refactor all of this to use tri-state values instead, there is no obvious float value that we can use as default (well maybe NaN),
  295. ////so instead it would be better to have a separate bool to show if value is present or not.
  296. m_SetValue(newValue + float.Epsilon);
  297. else
  298. m_SetValue(newValue);
  299. }
  300. EditorGUI.EndDisabledGroup();
  301. var newUseDefault = GUILayout.Toggle(m_UseDefaultValue, m_ToggleLabel, GUILayout.ExpandWidth(false));
  302. if (newUseDefault != m_UseDefaultValue)
  303. {
  304. if (!newUseDefault)
  305. m_SetValue(m_GetDefaultValue());
  306. else
  307. m_SetValue(m_DefaultInitializedValue);
  308. }
  309. m_UseDefaultValue = newUseDefault;
  310. EditorGUILayout.EndHorizontal();
  311. // If we're using a default from global InputSettings, show info text for that and provide
  312. // button to open input settings.
  313. if (m_UseDefaultValue && m_DefaultComesFromInputSettings)
  314. {
  315. EditorGUILayout.HelpBox(m_HelpBoxText);
  316. EditorGUILayout.BeginHorizontal();
  317. GUILayout.FlexibleSpace();
  318. if (GUILayout.Button(m_OpenInputSettingsLabel, EditorStyles.miniButton))
  319. InputSettingsProvider.Open();
  320. EditorGUILayout.EndHorizontal();
  321. }
  322. }
  323. private Func<float> m_GetValue;
  324. private Action<float> m_SetValue;
  325. private Func<float> m_GetDefaultValue;
  326. private bool m_UseDefaultValue;
  327. private bool m_DefaultComesFromInputSettings;
  328. private float m_DefaultInitializedValue;
  329. private GUIContent m_ToggleLabel;
  330. private GUIContent m_ValueLabel;
  331. private GUIContent m_OpenInputSettingsLabel;
  332. private GUIContent m_HelpBoxText;
  333. private FloatField m_FloatField;
  334. private Button m_OpenInputSettingsButton;
  335. private Toggle m_DefaultToggle;
  336. #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
  337. private HelpBox m_HelpBox;
  338. #endif
  339. }
  340. }
  341. }
  342. #endif // UNITY_EDITOR