#if UNITY_EDITOR || PACKAGE_DOCS_GENERATION using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.UIElements; using UnityEngine.InputSystem.Utilities; using UnityEngine.UIElements; ////REVIEW: generalize this to something beyond just parameters? namespace UnityEngine.InputSystem.Editor { /// /// A custom UI for editing parameter values on a , , /// or . /// /// /// When implementing a custom parameter editor, use instead. /// /// /// public abstract class InputParameterEditor { /// /// The , , or /// being edited. /// public object target { get; internal set; } /// /// Callback for implementing a custom UI. /// public abstract void OnGUI(); #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS /// /// Add visual elements for this parameter editor to a root VisualElement. /// /// The VisualElement that parameter editor elements should be added to. /// A callback that will be called when any of the parameter editors /// changes value. public abstract void OnDrawVisualElements(VisualElement root, Action onChangedCallback); #endif internal abstract void SetTarget(object target); internal static Type LookupEditorForType(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); if (s_TypeLookupCache == null) { s_TypeLookupCache = new Dictionary(); foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var typeInfo in assembly.DefinedTypes) { // Only looking for classes. if (!typeInfo.IsClass) continue; var definedType = typeInfo.AsType(); if (definedType == null) continue; // Only looking for InputParameterEditors. if (!typeof(InputParameterEditor).IsAssignableFrom(definedType)) continue; // Grab parameter from InputParameterEditor<>. var objectType = TypeHelpers.GetGenericTypeArgumentFromHierarchy(definedType, typeof(InputParameterEditor<>), 0); if (objectType == null) continue; s_TypeLookupCache[objectType] = definedType; } } } s_TypeLookupCache.TryGetValue(type, out var editorType); return editorType; } private static Dictionary s_TypeLookupCache; } /// /// A custom UI for editing parameter values on a , /// , or . /// /// /// Custom parameter editors do not need to be registered explicitly. Say you have a custom /// called QuantizeProcessor. To define a custom editor /// UI for it, simply define a new class based on InputParameterEditor<QuantizeProcessor>. /// /// /// /// public class QuantizeProcessorEditor : InputParameterEditor<QuantizeProcessor> /// { /// // You can put initialization logic in OnEnable, if you need it. /// public override void OnEnable() /// { /// // Use the 'target' property to access the QuantizeProcessor instance. /// } /// /// // In OnGUI, you can define custom UI elements. Use EditorGUILayout to lay /// // out the controls. /// public override void OnGUI() /// { /// // Say that QuantizeProcessor has a "stepping" property that determines /// // the stepping distance for discrete values returned by the processor. /// // We can expose it here as a float field. To apply the modification to /// // processor object, we just assign the value back to the field on it. /// target.stepping = EditorGUILayout.FloatField( /// m_SteppingLabel, target.stepping); /// } /// /// private GUIContent m_SteppingLabel = new GUIContent("Stepping", /// "Discrete stepping with which input values will be quantized."); /// } /// /// /// /// Note that a parameter editor takes over the entire editing UI for the object and /// not just the editing of specific parameters. /// /// The default parameter editor will derive names from the names of the respective /// fields just like the Unity inspector does. Also, it will respect tooltips applied /// to these fields with Unity's TooltipAttribute. /// /// So, let's say that QuantizeProcessor from our example was defined like /// below. In that case, the result would be equivalent to the custom parameter editor /// UI defined above. /// /// /// /// public class QuantizeProcessor : InputProcessor<float> /// { /// [Tooltip("Discrete stepping with which input values will be quantized.")] /// public float stepping; /// /// public override float Process(float value, InputControl control) /// { /// return value - value % stepping; /// } /// } /// /// /// public abstract class InputParameterEditor : InputParameterEditor where TObject : class { /// /// The , , or /// being edited. /// public new TObject target { get; private set; } /// /// Called after the parameter editor has been initialized. /// protected virtual void OnEnable() { } internal override void SetTarget(object target) { if (target == null) throw new ArgumentNullException(nameof(target)); if (!(target is TObject targetOfType)) throw new ArgumentException( $"Expecting object of type '{typeof(TObject).Name}' but got object of type '{target.GetType().Name}' instead", nameof(target)); this.target = targetOfType; base.target = targetOfType; OnEnable(); } #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS /// /// Default stub implementation of . /// Should be overridden to create the desired UI. /// public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback) { } #endif /// /// Helper for parameters that have defaults (usually from ). /// /// /// Has a bool toggle to switch between default and custom value. /// internal class CustomOrDefaultSetting { public void Initialize(string label, string tooltip, string defaultName, Func getValue, Action setValue, Func getDefaultValue, bool defaultComesFromInputSettings = true, float defaultInitializedValue = default) { m_GetValue = getValue; m_SetValue = setValue; m_GetDefaultValue = getDefaultValue; m_ToggleLabel = EditorGUIUtility.TrTextContent("Default", defaultComesFromInputSettings ? $"If enabled, the default {label.ToLower()} configured globally in the input settings is used. See Edit >> Project Settings... >> Input (NEW)." : "If enabled, the default value is used."); m_ValueLabel = EditorGUIUtility.TrTextContent(label, tooltip); if (defaultComesFromInputSettings) m_OpenInputSettingsLabel = EditorGUIUtility.TrTextContent("Open Input Settings"); m_DefaultInitializedValue = defaultInitializedValue; m_UseDefaultValue = Mathf.Approximately(getValue(), defaultInitializedValue); m_DefaultComesFromInputSettings = defaultComesFromInputSettings; m_HelpBoxText = EditorGUIUtility.TrTextContent( $"Uses \"{defaultName}\" set in project-wide input settings."); } #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS public void OnDrawVisualElements(VisualElement root, Action onChangedCallback) { var value = m_GetValue(); if (m_UseDefaultValue) value = m_GetDefaultValue(); // 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. // Revert it back to default to show a nice readable value in UI. // ReSharper disable once CompareOfFloatsByEqualityOperator if ((value - float.Epsilon) == m_DefaultInitializedValue) value = m_DefaultInitializedValue; var container = new VisualElement(); var settingsContainer = new VisualElement { style = { flexDirection = FlexDirection.Row } }; m_FloatField = new FloatField(m_ValueLabel.text) { value = value }; m_FloatField.Q("unity-text-input").AddToClassList("float-field"); m_FloatField.RegisterValueChangedCallback(ChangeSettingValue); m_FloatField.RegisterCallback(_ => OnEditEnd(onChangedCallback)); m_FloatField.SetEnabled(!m_UseDefaultValue); m_HelpBox = new HelpBox(m_HelpBoxText.text, HelpBoxMessageType.None); m_DefaultToggle = new Toggle("Default") { value = m_UseDefaultValue, style = { flexDirection = FlexDirection.RowReverse } }; m_DefaultToggle.RegisterValueChangedCallback(evt => ToggleUseDefaultValue(evt, onChangedCallback)); m_DefaultToggle.Q