This commit is contained in:
2025-01-17 13:10:42 +01:00
commit 4536213c91
15115 changed files with 1442174 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.InputSystem.LowLevel;
using UnityEditor;
using UnityEngine.UIElements;
namespace UnityEngine.InputSystem.Editor
{
/// <summary>
/// Property drawer for <see cref = "GamepadButton" />
/// </summary >
[CustomPropertyDrawer(typeof(GamepadButton))]
internal class GamepadButtonPropertyDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
CreateEnumList();
return base.CreatePropertyGUI(property);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
if (m_EnumDisplayNames == null)
{
CreateEnumList();
}
if (property.propertyType == SerializedPropertyType.Enum)
{
property.intValue = EditorGUI.Popup(position, label.text, property.intValue, m_EnumDisplayNames);
}
EditorGUI.EndProperty();
}
private void CreateEnumList()
{
var enumNamesAndValues = new Dictionary<string, int>();
var enumDisplayNames = Enum.GetNames(typeof(GamepadButton));
var enumValues = Enum.GetValues(typeof(GamepadButton)).Cast<GamepadButton>().ToArray();
for (var i = 0; i < enumDisplayNames.Length; ++i)
{
string enumName;
switch (enumDisplayNames[i])
{
case nameof(GamepadButton.Y):
case nameof(GamepadButton.Triangle):
case nameof(GamepadButton.A):
case nameof(GamepadButton.Cross):
case nameof(GamepadButton.B):
case nameof(GamepadButton.Circle):
case nameof(GamepadButton.X):
case nameof(GamepadButton.Square):
continue;
case nameof(GamepadButton.North):
enumName = "North, Y, Triangle, X";
break;
case nameof(GamepadButton.South):
enumName = "South, A, Cross, B";
break;
case nameof(GamepadButton.East):
enumName = "East, B, Circle, A";
break;
case nameof(GamepadButton.West):
enumName = "West, X, Square, Y";
break;
default:
enumName = enumDisplayNames[i];
break;
}
enumNamesAndValues.Add(enumName, (int)enumValues.GetValue(i));
}
var sortedEntries = enumNamesAndValues.OrderBy(x => x.Value);
m_EnumDisplayNames = sortedEntries.Select(x => x.Key).ToArray();
}
private string[] m_EnumDisplayNames;
}
}
#endif // UNITY_EDITOR

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e64ea03a5bf05845977ae0cd2e1dd4f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
// Note: If not UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS we do not use a custom property drawer and
// picker for InputActionAsset but rather rely on default (classic) object picker.
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
using UnityEditor;
using UnityEditor.Search;
namespace UnityEngine.InputSystem.Editor
{
/// <summary>
/// Custom property drawer in order to use the "Advanced Picker" from UnityEditor.Search.
/// </summary>
[CustomPropertyDrawer(typeof(InputActionAsset))]
internal sealed class InputActionAssetDrawer : PropertyDrawer
{
private readonly SearchContext m_Context = UnityEditor.Search.SearchService.CreateContext(new[]
{
InputActionAssetSearchProviders.CreateInputActionAssetSearchProvider(),
InputActionAssetSearchProviders.CreateInputActionAssetSearchProviderForProjectWideActions(),
}, string.Empty, SearchConstants.PickerSearchFlags);
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
ObjectField.DoObjectField(position, property, typeof(InputActionAsset), label,
m_Context, SearchConstants.PickerViewFlags);
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9e0a885665a44db1adfaf0e3b9e9cda6
timeCreated: 1693838957

View File

@@ -0,0 +1,123 @@
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Search;
using UnityEngine.Search;
namespace UnityEngine.InputSystem.Editor
{
internal static class InputActionAssetSearchProviders
{
const string k_AssetFolderSearchProviderId = "AssetsInputActionAssetSearchProvider";
const string k_ProjectWideActionsSearchProviderId = "ProjectWideInputActionAssetSearchProvider";
const string k_ProjectWideAssetIdentificationString = " [Project Wide Input Actions]";
internal static SearchProvider CreateInputActionAssetSearchProvider()
{
return CreateInputActionAssetSearchProvider(k_AssetFolderSearchProviderId,
"Asset Input Action Assets",
(obj) => { return obj != null ? AssetDatabase.GetAssetPath(obj) : "Null"; },
() => LoadInputActionAssetsFromAssetDatabase(skipProjectWide: true));
}
internal static SearchProvider CreateInputActionAssetSearchProviderForProjectWideActions()
{
return CreateInputActionAssetSearchProvider(k_ProjectWideActionsSearchProviderId,
"Project-Wide Input Action Asset",
(obj) => { return obj != null ? AssetDatabase.GetAssetPath(obj) : "Null"; },
() => LoadInputActionReferencesFromAsset());
}
private static IEnumerable<Object> LoadInputActionReferencesFromAsset()
{
var asset = InputSystem.actions;
if (asset == null)
return Array.Empty<Object>();
return new List<InputActionAsset>() { asset };
}
private static IEnumerable<Object> LoadInputActionAssetsFromAssetDatabase(bool skipProjectWide)
{
string[] searchFolders = new string[] { "Assets" };
var inputActionAssetGUIDs = AssetDatabase.FindAssets($"t:{typeof(InputActionAsset).Name}", searchFolders);
var inputActionAssetList = new List<InputActionAsset>();
foreach (var guid in inputActionAssetGUIDs)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var assetInputActionAsset = AssetDatabase.LoadAssetAtPath<InputActionAsset>(assetPath);
if (skipProjectWide)
{
if (assetInputActionAsset == InputSystem.actions)
continue;
}
inputActionAssetList.Add(assetInputActionAsset);
}
return inputActionAssetList;
}
private static SearchProvider CreateInputActionAssetSearchProvider(string id, string displayName,
Func<Object, string> createItemFetchDescription, Func<IEnumerable<Object>> fetchAssets)
{
// We assign description+label in FilteredSearch but also provide a fetchDescription+fetchLabel below.
// This is needed to support all zoom-modes for an unknown reason.
// Also, fetchLabel/fetchDescription and what is provided to CreateItem is playing different
// roles at different zoom levels.
var inputActionAssetIcon = InputActionAssetIconLoader.LoadAssetIcon();
return new SearchProvider(id, displayName)
{
priority = 25,
fetchDescription = FetchLabel,
fetchItems = (context, items, provider) => FilteredSearch(context, provider, FetchLabel, createItemFetchDescription,
fetchAssets),
fetchLabel = FetchLabel,
fetchPreview = (item, context, size, options) => inputActionAssetIcon,
fetchThumbnail = (item, context) => inputActionAssetIcon,
toObject = ToObject,
};
}
private static Object ToObject(SearchItem item, Type type)
{
return item.data as Object;
}
// Custom search function with label matching filtering.
private static IEnumerable<SearchItem> FilteredSearch(SearchContext context, SearchProvider provider,
Func<Object, string> fetchObjectLabel, Func<Object, string> createItemFetchDescription, Func<IEnumerable<Object>> fetchAssets)
{
foreach (var asset in fetchAssets())
{
var label = fetchObjectLabel(asset);
Texture2D thumbnail = null; // filled in later
if (!label.Contains(context.searchText, System.StringComparison.InvariantCultureIgnoreCase))
continue; // Ignore due to filtering
yield return provider.CreateItem(context, asset.GetInstanceID().ToString(), label, createItemFetchDescription(asset),
thumbnail, asset);
}
}
// Note that this is overloaded to allow utilizing FetchLabel inside fetchItems to keep label formatting
// consistent between CreateItem and additional fetchLabel calls.
private static string FetchLabel(Object obj)
{
// if (obj == InputSystem.actions) return $"{obj.name}{k_ProjectWideAssetIdentificationString}";
return obj.name;
}
private static string FetchLabel(SearchItem item, SearchContext context)
{
return FetchLabel((item.data as Object) !);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15ce6d517ffb81e44bc72545abacdc9c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,63 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
namespace UnityEngine.InputSystem.Editor
{
/// <summary>
/// Property drawer for <see cref="InputAction"/>.
/// </summary>
[CustomPropertyDrawer(typeof(InputAction))]
internal class InputActionDrawer : InputActionDrawerBase
{
protected override TreeViewItem BuildTree(SerializedProperty property)
{
return InputActionTreeView.BuildWithJustBindingsFromAction(property);
}
protected override string GetSuffixToRemoveFromPropertyDisplayName()
{
return " Action";
}
protected override bool IsPropertyAClone(SerializedProperty property)
{
// When a new item is added to a collection through the inspector, the default behaviour is
// to create a clone of the previous item. Here we look at all InputActions that appear before
// the current one and compare their Ids to determine if we have a clone. We don't look past
// the current item because Unity will be calling this property drawer for each input action
// in the collection in turn. If the user just added a new input action, and it's a clone, as
// we work our way down the list, we'd end up thinking that an existing input action was a clone
// of the newly added one, instead of the other way around. If we do have a clone, we need to
// clear out some properties of the InputAction (id, name, and singleton action bindings) and
// recreate the tree view.
if (property?.GetParentProperty() == null || property.GetParentProperty().isArray == false)
return false;
var array = property.GetArrayPropertyFromElement();
var index = property.GetIndexOfArrayElement();
for (var i = 0; i < index; i++)
{
if (property.FindPropertyRelative(nameof(InputAction.m_Id))?.stringValue ==
array.GetArrayElementAtIndex(i)?.FindPropertyRelative(nameof(InputAction.m_Id))?.stringValue)
return true;
}
return false;
}
protected override void ResetProperty(SerializedProperty property)
{
if (property == null) return;
property.SetStringValue(nameof(InputAction.m_Id), Guid.NewGuid().ToString());
property.SetStringValue(nameof(InputAction.m_Name), "Input Action");
property.FindPropertyRelative(nameof(InputAction.m_SingletonActionBindings))?.ClearArray();
property.serializedObject?.ApplyModifiedPropertiesWithoutUndo();
}
}
}
#endif // UNITY_EDITOR

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2539bd7bca18c47e5a76ad1471ed6829
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,198 @@
#if UNITY_EDITOR
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;
}
#if UNITY_2023_2_OR_NEWER
[System.Obsolete("CanCacheInspectorGUI has been deprecated and is no longer used.", false)]
#endif
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;
}
}
}
#endif // UNITY_EDITOR

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba2c9cc57fa154716b6f077bf9c1c193
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,64 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
namespace UnityEngine.InputSystem.Editor
{
/// <summary>
/// Property drawer for <see cref="InputActionMap"/>.
/// </summary>
[CustomPropertyDrawer(typeof(InputActionMap))]
internal class InputActionMapDrawer : InputActionDrawerBase
{
protected override TreeViewItem BuildTree(SerializedProperty property)
{
return InputActionTreeView.BuildWithJustActionsAndBindingsFromMap(property);
}
protected override string GetSuffixToRemoveFromPropertyDisplayName()
{
return " Action Map";
}
protected override bool IsPropertyAClone(SerializedProperty property)
{
// When a new item is added to a collection through the inspector, the default behaviour is
// to create a clone of the previous item. Here we look at all InputActionMaps that appear before
// the current one and compare their Ids to determine if we have a clone. We don't look past
// the current item because Unity will be calling this property drawer for each input action map
// in the collection in turn. If the user just added a new input action map, and it's a clone, as
// we work our way down the list, we'd end up thinking that an existing input action map was a clone
// of the newly added one, instead of the other way around. If we do have a clone, we need to
// clear out some properties of the InputActionMap (id, name, input actions, and bindings) and
// recreate the tree view.
if (property?.GetParentProperty() == null || property.GetParentProperty().isArray == false)
return false;
var array = property.GetArrayPropertyFromElement();
var index = property.GetIndexOfArrayElement();
for (var i = 0; i < index; i++)
{
if (property.FindPropertyRelative(nameof(InputActionMap.m_Id))?.stringValue ==
array.GetArrayElementAtIndex(i)?.FindPropertyRelative(nameof(InputActionMap.m_Id))?.stringValue)
return true;
}
return false;
}
protected override void ResetProperty(SerializedProperty property)
{
if (property == null) return;
property.SetStringValue(nameof(InputActionMap.m_Id), Guid.NewGuid().ToString());
property.SetStringValue(nameof(InputActionMap.m_Name), "Input Action Map");
property.FindPropertyRelative(nameof(InputActionMap.m_Actions))?.ClearArray();
property.FindPropertyRelative(nameof(InputActionMap.m_Bindings))?.ClearArray();
property.serializedObject?.ApplyModifiedPropertiesWithoutUndo();
}
}
}
#endif // UNITY_EDITOR

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e1ad7c41319cc470d8ae996688c56e79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,198 @@
#if UNITY_EDITOR
using UnityEditor;
namespace UnityEngine.InputSystem.Editor
{
/// <summary>
/// A custom property drawer for <see cref="InputActionProperty"/>.
/// </summary>
/// <remarks>
/// Selectively draws the <see cref="InputActionReference"/> and/or the <see cref="InputAction"/>
/// underlying the property, based on whether the property is set to use a reference or not.
/// The drawer also allows for drawing in different layouts, chosen through Project Settings.
/// </remarks>
/// <seealso cref="InputSettings.InputActionPropertyDrawerMode"/>
[CustomPropertyDrawer(typeof(InputActionProperty))]
internal class InputActionPropertyDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (property == null)
throw new System.ArgumentNullException(nameof(property));
var drawerMode = InputSystem.settings.inputActionPropertyDrawerMode;
switch (drawerMode)
{
case InputSettings.InputActionPropertyDrawerMode.Compact:
default:
return GetCompactHeight(property);
case InputSettings.InputActionPropertyDrawerMode.MultilineEffective:
case InputSettings.InputActionPropertyDrawerMode.MultilineBoth:
return GetMultilineHeight(property, drawerMode == InputSettings.InputActionPropertyDrawerMode.MultilineEffective);
}
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property == null)
throw new System.ArgumentNullException(nameof(property));
// Using BeginProperty / EndProperty on the parent property means that
// prefab override logic works on the entire property.
label = EditorGUI.BeginProperty(position, label, property);
var drawerMode = InputSystem.settings.inputActionPropertyDrawerMode;
switch (drawerMode)
{
case InputSettings.InputActionPropertyDrawerMode.Compact:
default:
DrawCompactGUI(position, property, label);
break;
case InputSettings.InputActionPropertyDrawerMode.MultilineEffective:
case InputSettings.InputActionPropertyDrawerMode.MultilineBoth:
DrawMultilineGUI(position, property, label, drawerMode == InputSettings.InputActionPropertyDrawerMode.MultilineEffective);
break;
}
EditorGUI.EndProperty();
}
static float GetCompactHeight(SerializedProperty property)
{
var useReference = property.FindPropertyRelative("m_UseReference");
var effectiveProperty = useReference.boolValue ? property.FindPropertyRelative("m_Reference") : property.FindPropertyRelative("m_Action");
return EditorGUI.GetPropertyHeight(effectiveProperty);
}
static float GetMultilineHeight(SerializedProperty property, bool showEffectiveOnly)
{
var height = 0f;
// Field label.
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
// "Use Reference" toggle.
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
// We show either the InputAction property drawer or InputActionReference drawer (default object field).
var useReference = property.FindPropertyRelative("m_UseReference");
var reference = property.FindPropertyRelative("m_Reference");
var action = property.FindPropertyRelative("m_Action");
if (showEffectiveOnly)
{
var effectiveProperty = useReference.boolValue ? reference : action;
height += EditorGUI.GetPropertyHeight(effectiveProperty);
}
else
{
height += EditorGUI.GetPropertyHeight(reference) + EditorGUI.GetPropertyHeight(action);
}
return height;
}
static void DrawCompactGUI(Rect position, SerializedProperty property, GUIContent label)
{
position = EditorGUI.PrefixLabel(position, label);
var useReference = property.FindPropertyRelative("m_UseReference");
var effectiveProperty = useReference.boolValue ? property.FindPropertyRelative("m_Reference") : property.FindPropertyRelative("m_Action");
// Calculate rect for configuration button
var buttonRect = position;
var popupStyle = Styles.popup;
buttonRect.yMin += popupStyle.margin.top + 1f;
buttonRect.width = popupStyle.fixedWidth + popupStyle.margin.right;
buttonRect.height = EditorGUIUtility.singleLineHeight;
position.xMin = buttonRect.xMax;
// Don't make child fields be indented
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
// Using BeginProperty / EndProperty on the popup button allows the user to
// revert prefab overrides to Use Reference by right-clicking the configuration button.
EditorGUI.BeginProperty(buttonRect, GUIContent.none, useReference);
using (var check = new EditorGUI.ChangeCheckScope())
{
var newPopupIndex = EditorGUI.Popup(buttonRect, GetCompactPopupIndex(useReference), Contents.compactPopupOptions, popupStyle);
if (check.changed)
useReference.boolValue = IsUseReference(newPopupIndex);
}
EditorGUI.EndProperty();
EditorGUI.PropertyField(position, effectiveProperty, GUIContent.none);
// Set indent back to what it was
EditorGUI.indentLevel = indent;
}
static void DrawMultilineGUI(Rect position, SerializedProperty property, GUIContent label, bool showEffectiveOnly)
{
const float kIndent = 15f;
var titleRect = position;
titleRect.height = EditorGUIUtility.singleLineHeight;
var useReference = property.FindPropertyRelative("m_UseReference");
var useReferenceToggleRect = position;
useReferenceToggleRect.x += kIndent;
useReferenceToggleRect.width -= kIndent;
useReferenceToggleRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
useReferenceToggleRect.height = EditorGUI.GetPropertyHeight(useReference);
var firstValueRect = position;
firstValueRect.x += kIndent;
firstValueRect.width -= kIndent;
firstValueRect.y += (titleRect.height + useReferenceToggleRect.height) + (EditorGUIUtility.standardVerticalSpacing * 2f);
var reference = property.FindPropertyRelative("m_Reference");
var action = property.FindPropertyRelative("m_Action");
// Draw the Use Reference toggle.
EditorGUI.LabelField(titleRect, label);
EditorGUI.PropertyField(useReferenceToggleRect, useReference);
if (showEffectiveOnly)
{
// Draw only the Reference or Action underlying the property, whichever is the effective one.
var effectiveProperty = useReference.boolValue ? reference : action;
firstValueRect.height = EditorGUI.GetPropertyHeight(effectiveProperty);
EditorGUI.PropertyField(firstValueRect, effectiveProperty);
}
else
{
// Draw the Action followed by the Reference.
firstValueRect.height = EditorGUI.GetPropertyHeight(action);
var referenceRect = firstValueRect;
referenceRect.y += firstValueRect.height + EditorGUIUtility.standardVerticalSpacing;
referenceRect.height = EditorGUI.GetPropertyHeight(reference);
EditorGUI.PropertyField(firstValueRect, action);
EditorGUI.PropertyField(referenceRect, reference);
}
}
// 0 == Use Reference, 1 == Use Action
// Keep synced with Contents.compactPopupOptions.
static int GetCompactPopupIndex(SerializedProperty useReference) => useReference.boolValue ? 0 : 1;
static bool IsUseReference(int index) => index == 0;
static class Contents
{
static readonly GUIContent s_UseReference = EditorGUIUtility.TrTextContent("Use Reference");
static readonly GUIContent s_UseAction = EditorGUIUtility.TrTextContent("Use Action");
public static readonly GUIContent[] compactPopupOptions = { s_UseReference, s_UseAction };
}
static class Styles
{
public static readonly GUIStyle popup = new GUIStyle("PaneOptions") { imagePosition = ImagePosition.ImageOnly };
}
}
}
#endif // UNITY_EDITOR

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 550c4f473d16e49cf9f2e5c45be4490d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,50 @@
// Note: If not UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS we do not use a custom property drawer and
// picker for InputActionReferences but rather rely on default (classic) object picker.
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
using UnityEditor;
using UnityEditor.Search;
namespace UnityEngine.InputSystem.Editor
{
/// <summary>
/// Custom property drawer in order to use the "Advanced Picker" from UnityEditor.Search.
/// </summary>
[CustomPropertyDrawer(typeof(InputActionReference))]
internal sealed class InputActionReferencePropertyDrawer : PropertyDrawer
{
private readonly SearchContext m_Context = UnityEditor.Search.SearchService.CreateContext(new[]
{
InputActionReferenceSearchProviders.CreateInputActionReferenceSearchProviderForAssets(),
InputActionReferenceSearchProviders.CreateInputActionReferenceSearchProviderForProjectWideActions(),
}, string.Empty, SearchConstants.PickerSearchFlags);
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// Sets the property to null if the action is not found in the asset.
ValidatePropertyWithDanglingInputActionReferences(property);
ObjectField.DoObjectField(position, property, typeof(InputActionReference), label,
m_Context, SearchConstants.PickerViewFlags);
}
static void ValidatePropertyWithDanglingInputActionReferences(SerializedProperty property)
{
if (property?.objectReferenceValue is InputActionReference reference)
{
// Check only if the reference is a project-wide action.
if (reference?.asset == InputSystem.actions)
{
var action = reference?.asset?.FindAction(reference.action.id);
if (action is null)
{
property.objectReferenceValue = null;
property.serializedObject.ApplyModifiedProperties();
}
}
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 30bd04fe045c46918b5fe7bc6efc4ce2
timeCreated: 1698143402

View File

@@ -0,0 +1,105 @@
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Search;
using UnityEngine.Search;
namespace UnityEngine.InputSystem.Editor
{
internal static class SearchConstants
{
// SearchFlags: these flags are used to customize how search is performed and how search
// results are displayed in the advanced object picker.
// Note: SearchFlags.Packages is not currently used and hides all results from packages.
internal static readonly SearchFlags PickerSearchFlags = SearchFlags.Sorted | SearchFlags.OpenPicker;
// Search.SearchViewFlags : these flags are used to customize the appearance of the PickerWindow.
internal static readonly Search.SearchViewFlags PickerViewFlags = SearchViewFlags.DisableBuilderModeToggle
| SearchViewFlags.DisableInspectorPreview
| SearchViewFlags.ListView
| SearchViewFlags.DisableSavedSearchQuery;
}
internal static class InputActionReferenceSearchProviders
{
const string k_AssetFolderSearchProviderId = "AssetsInputActionReferenceSearchProvider";
const string k_ProjectWideActionsSearchProviderId = "ProjectWideInputActionReferenceSearchProvider";
// Search provider for InputActionReferences for all assets in the project, without project-wide actions.
internal static SearchProvider CreateInputActionReferenceSearchProviderForAssets()
{
return CreateInputActionReferenceSearchProvider(k_AssetFolderSearchProviderId,
"Asset Input Actions",
// Show the asset path in the description.
(obj) => AssetDatabase.GetAssetPath((obj as InputActionReference).asset),
() => InputActionImporter.LoadInputActionReferencesFromAssetDatabase(skipProjectWide: true));
}
// Search provider for InputActionReferences for project-wide actions
internal static SearchProvider CreateInputActionReferenceSearchProviderForProjectWideActions()
{
return CreateInputActionReferenceSearchProvider(k_ProjectWideActionsSearchProviderId,
"Project-Wide Input Actions",
(obj) => "(Project-Wide Input Actions)",
() =>
{
var asset = InputSystem.actions;
if (asset == null)
return Array.Empty<Object>();
var assetPath = AssetDatabase.GetAssetPath(asset);
return InputActionImporter.LoadInputActionReferencesFromAsset(assetPath);
});
}
private static SearchProvider CreateInputActionReferenceSearchProvider(string id, string displayName,
Func<Object, string> createItemFetchDescription, Func<IEnumerable<Object>> fetchAssets)
{
// Match icon used for sub-assets from importer for InputActionReferences.
// We assign description+label in FilteredSearch but also provide a fetchDescription+fetchLabel below.
// This is needed to support all zoom-modes for an unknown reason.
// Also, fetchLabel/fetchDescription and what is provided to CreateItem is playing different
// roles at different zoom levels.
var inputActionReferenceIcon = InputActionAssetIconLoader.LoadActionIcon();
return new SearchProvider(id, displayName)
{
priority = 25,
fetchDescription = FetchLabel,
fetchItems = (context, items, provider) => FilteredSearch(context, provider, FetchLabel, createItemFetchDescription,
fetchAssets, "(Project-Wide Input Actions)"),
fetchLabel = FetchLabel,
fetchPreview = (item, context, size, options) => inputActionReferenceIcon,
fetchThumbnail = (item, context) => inputActionReferenceIcon,
toObject = (item, type) => item.data as Object,
};
}
// Custom search function with label matching filtering.
private static IEnumerable<SearchItem> FilteredSearch(SearchContext context, SearchProvider provider,
Func<Object, string> fetchObjectLabel, Func<Object, string> createItemFetchDescription, Func<IEnumerable<Object>> fetchAssets, string description)
{
foreach (var asset in fetchAssets())
{
var label = fetchObjectLabel(asset);
if (!label.Contains(context.searchText, System.StringComparison.InvariantCultureIgnoreCase))
continue; // Ignore due to filtering
yield return provider.CreateItem(context, asset.GetInstanceID().ToString(), label, createItemFetchDescription(asset),
null, asset);
}
}
// Note that this is overloaded to allow utilizing FetchLabel inside fetchItems to keep label formatting
// consistent between CreateItem and additional fetchLabel calls.
private static string FetchLabel(Object obj)
{
return obj.name;
}
private static string FetchLabel(SearchItem item, SearchContext context)
{
return FetchLabel((item.data as Object) !);
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: baaf1bd005134bc6825c06f3510c6643
timeCreated: 1698141997

View File

@@ -0,0 +1,52 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine.InputSystem.Layouts;
namespace UnityEngine.InputSystem.Editor
{
/// <summary>
/// Custom property drawer for string type fields that represent input control paths.
/// </summary>
/// <remarks>
/// To use this drawer on a property, apply <see cref="InputControlAttribute"/> on it. To constrain
/// what type of control is picked, set the <see cref="InputControlAttribute.layout"/> field on the
/// attribute.
///
/// <example>
/// <code>
/// // A string representing a control path. Constrain it to picking Button-type controls.
/// [InputControl(layout = "Button")]
/// [SerializeField] private string m_ControlPath;
/// </code>
/// </example>
/// </remarks>
[CustomPropertyDrawer(typeof(InputControlAttribute))]
internal sealed class InputControlPathDrawer : PropertyDrawer, IDisposable
{
private InputControlPickerState m_PickerState;
private InputControlPathEditor m_Editor;
public void Dispose()
{
m_Editor?.Dispose();
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (m_PickerState == null)
m_PickerState = new InputControlPickerState();
if (m_Editor == null)
{
m_Editor = new InputControlPathEditor(property, m_PickerState,
() => property.serializedObject.ApplyModifiedProperties(),
label: label);
}
EditorGUI.BeginProperty(position, label, property);
m_Editor.OnGUI(position, label, property, () => property.serializedObject.ApplyModifiedProperties());
EditorGUI.EndProperty();
}
}
}
#endif // UNITY_EDITOR

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4075725b42343494dae837a8144df3b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: