Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Editor /PropertyDrawers /InputActionDrawerBase.cs
| using System.Collections.Generic; | |
| using UnityEditor; | |
| using UnityEditor.IMGUI.Controls; | |
| namespace UnityEngine.InputSystem.Editor | |
| { | |
| /// <summary> | |
| /// Base class for property drawers that display input actions. | |
| /// </summary> | |
| internal abstract class InputActionDrawerBase : PropertyDrawer | |
| { | |
| public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | |
| { | |
| InitTreeIfNeeded(property); | |
| return GetOrCreateViewData(property).TreeView.totalHeight; | |
| } | |
| public override bool CanCacheInspectorGUI(SerializedProperty property) | |
| { | |
| return false; | |
| } | |
| public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | |
| { | |
| InitTreeIfNeeded(property); | |
| EditorGUI.BeginProperty(position, label, property); | |
| SetNameIfNotSet(property); | |
| GetOrCreateViewData(property).TreeView.OnGUI(position); | |
| EditorGUI.EndProperty(); | |
| } | |
| private void InitTreeIfNeeded(SerializedProperty property) | |
| { | |
| // NOTE: Unlike InputActionEditorWindow, we do not need to protect against the SerializedObject | |
| // changing behind our backs by undo/redo here. Being a PropertyDrawer, we will automatically | |
| // get recreated by Unity when it touches our serialized data. | |
| var viewData = GetOrCreateViewData(property); | |
| var propertyIsClone = IsPropertyAClone(property); | |
| if (!propertyIsClone && viewData.TreeView != null && viewData.TreeView.serializedObject == property.serializedObject) | |
| return; | |
| if (propertyIsClone) | |
| ResetProperty(property); | |
| viewData.TreeView = new InputActionTreeView(property.serializedObject) | |
| { | |
| onBuildTree = () => BuildTree(property), | |
| onDoubleClick = item => OnItemDoubleClicked(item, property), | |
| drawActionPropertiesButton = true, | |
| title = (GetPropertyTitle(property), property.GetTooltip()) | |
| }; | |
| viewData.TreeView.Reload(); | |
| } | |
| private void SetNameIfNotSet(SerializedProperty actionProperty) | |
| { | |
| var nameProperty = actionProperty.FindPropertyRelative("m_Name"); | |
| if (!string.IsNullOrEmpty(nameProperty.stringValue)) | |
| return; | |
| // Special case for InputActionProperty where we want to take the name not from | |
| // the m_Action property embedded in it but rather from the InputActionProperty field | |
| // itself. | |
| var name = actionProperty.displayName; | |
| var parent = actionProperty.GetParentProperty(); | |
| if (parent != null && parent.type == "InputActionProperty") | |
| name = parent.displayName; | |
| var suffix = GetSuffixToRemoveFromPropertyDisplayName(); | |
| if (name.EndsWith(suffix)) | |
| name = name.Substring(0, name.Length - suffix.Length); | |
| // If it's a singleton action, we also need to adjust the InputBinding.action | |
| // property values in its binding list. | |
| var singleActionBindings = actionProperty.FindPropertyRelative("m_SingletonActionBindings"); | |
| if (singleActionBindings != null) | |
| { | |
| var bindingCount = singleActionBindings.arraySize; | |
| for (var i = 0; i < bindingCount; ++i) | |
| { | |
| var binding = singleActionBindings.GetArrayElementAtIndex(i); | |
| var actionNameProperty = binding.FindPropertyRelative("m_Action"); | |
| actionNameProperty.stringValue = name; | |
| } | |
| } | |
| nameProperty.stringValue = name; | |
| actionProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo(); | |
| EditorUtility.SetDirty(actionProperty.serializedObject.targetObject); | |
| } | |
| private static string GetPropertyTitle(SerializedProperty property) | |
| { | |
| var propertyTitleNumeral = string.Empty; | |
| if (property.GetParentProperty() != null && property.GetParentProperty().isArray) | |
| propertyTitleNumeral = $" {property.GetIndexOfArrayElement()}"; | |
| if (property.displayName != null && | |
| property.displayName.Length > 0 && | |
| (property.type == nameof(InputAction) || property.type == nameof(InputActionMap))) | |
| { | |
| return $"{property.displayName}{propertyTitleNumeral}"; | |
| } | |
| return property.type == nameof(InputActionMap) ? $"Input Action Map{propertyTitleNumeral}" : $"Input Action{propertyTitleNumeral}"; | |
| } | |
| private void OnItemDoubleClicked(ActionTreeItemBase item, SerializedProperty property) | |
| { | |
| var viewData = GetOrCreateViewData(property); | |
| // Double-clicking on binding or action item opens property popup. | |
| PropertiesViewBase propertyView = null; | |
| if (item is BindingTreeItem) | |
| { | |
| if (viewData.ControlPickerState == null) | |
| viewData.ControlPickerState = new InputControlPickerState(); | |
| propertyView = new InputBindingPropertiesView(item.property, | |
| controlPickerState: viewData.ControlPickerState, | |
| expectedControlLayout: item.expectedControlLayout, | |
| onChange: | |
| change => viewData.TreeView.Reload()); | |
| } | |
| else if (item is ActionTreeItem) | |
| { | |
| propertyView = new InputActionPropertiesView(item.property, | |
| onChange: change => viewData.TreeView.Reload()); | |
| } | |
| if (propertyView != null) | |
| { | |
| var rect = new Rect(GUIUtility.GUIToScreenPoint(Event.current.mousePosition), Vector2.zero); | |
| PropertiesViewPopup.Show(rect, propertyView); | |
| } | |
| } | |
| private InputActionDrawerViewData GetOrCreateViewData(SerializedProperty property) | |
| { | |
| if (m_PerPropertyViewData == null) | |
| m_PerPropertyViewData = new Dictionary<string, InputActionDrawerViewData>(); | |
| if (m_PerPropertyViewData.TryGetValue(property.propertyPath, out var data)) return data; | |
| data = new InputActionDrawerViewData(); | |
| m_PerPropertyViewData.Add(property.propertyPath, data); | |
| return data; | |
| } | |
| protected abstract TreeViewItem BuildTree(SerializedProperty property); | |
| protected abstract string GetSuffixToRemoveFromPropertyDisplayName(); | |
| protected abstract bool IsPropertyAClone(SerializedProperty property); | |
| protected abstract void ResetProperty(SerializedProperty property); | |
| // Unity creates a single instance of a property drawer to draw multiple instances of the property drawer type, | |
| // so we can't store state in the property drawer for each item. We do need that though, because each InputAction | |
| // needs to have it's own instance of the InputActionTreeView to correctly draw it's own bindings. So what we do | |
| // is keep this array around that stores a tree view instance for each unique property path that the property | |
| // drawer encounters. The tree view will be recreated if we detect that the property being drawn has changed. | |
| private Dictionary<string, InputActionDrawerViewData> m_PerPropertyViewData; | |
| internal class PropertiesViewPopup : EditorWindow | |
| { | |
| public static void Show(Rect btnRect, PropertiesViewBase view) | |
| { | |
| var window = CreateInstance<PropertiesViewPopup>(); | |
| window.m_PropertyView = view; | |
| window.ShowPopup(); | |
| window.ShowAsDropDown(btnRect, new Vector2(300, 350)); | |
| } | |
| private void OnGUI() | |
| { | |
| m_PropertyView.OnGUI(); | |
| } | |
| private PropertiesViewBase m_PropertyView; | |
| } | |
| private class InputActionDrawerViewData | |
| { | |
| public InputActionTreeView TreeView; | |
| public InputControlPickerState ControlPickerState; | |
| } | |
| } | |
| } | |