#if UNITY_EDITOR
using System;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.Editor.Lists
{
///
/// A to manage a set of name-and-parameter pairs and a
/// to edit the parameters of the currently selected pair.
///
///
/// Produces output that can be consumed by .
///
internal abstract class NameAndParameterListView
{
protected NameAndParameterListView(SerializedProperty property, Action applyAction, string expectedControlLayout, TypeTable listOptions, Func getValueType, string itemName)
{
m_ItemName = itemName;
m_GetValueType = getValueType;
m_DeleteButton = EditorGUIUtility.TrIconContent("Toolbar Minus", $"Delete {itemName}");
m_UpButton = EditorGUIUtility.TrIconContent(GUIHelpers.LoadIcon("ChevronUp"), $"Move {itemName} up");
m_DownButton = EditorGUIUtility.TrIconContent(GUIHelpers.LoadIcon("ChevronDown"), $"Move {itemName} down");
m_Property = property;
m_Apply = applyAction;
m_ListOptions = listOptions;
m_ExpectedControlLayout = expectedControlLayout;
if (!string.IsNullOrEmpty(m_ExpectedControlLayout))
m_ExpectedValueType = EditorInputControlLayoutCache.GetValueType(m_ExpectedControlLayout);
m_ParametersForEachListItem = NameAndParameters.ParseMultiple(m_Property.stringValue).ToArray();
m_EditableParametersForEachListItem = new ParameterListView[m_ParametersForEachListItem.Length];
for (var i = 0; i < m_ParametersForEachListItem.Length; i++)
{
m_EditableParametersForEachListItem[i] = new ParameterListView { onChange = OnParametersChanged };
var typeName = m_ParametersForEachListItem[i].name;
var rowType = m_ListOptions.LookupTypeRegistration(typeName);
m_EditableParametersForEachListItem[i].Initialize(rowType, m_ParametersForEachListItem[i].parameters);
var name = ObjectNames.NicifyVariableName(typeName);
////REVIEW: finding this kind of stuff should probably have better support globally on the asset; e.g. some
//// notification that pops up and allows fixing all occurrences in one click
// Find out if we still support this option and indicate it in the list, if we don't.
if (rowType == null)
name += " (Obsolete)";
else if (m_ExpectedValueType != null)
{
var valueType = getValueType(rowType);
if (valueType != null && !m_ExpectedValueType.IsAssignableFrom(valueType))
name += " (Incompatible Value Type)";
}
m_EditableParametersForEachListItem[i].name = name;
}
}
public void OnAddDropdown(Rect r)
{
// Add only original names to the menu and not aliases.
var menu = new GenericMenu();
foreach (var name in m_ListOptions.internedNames.Where(x => !m_ListOptions.ShouldHideInUI(x)).OrderBy(x => x.ToString()))
{
// Skip if not compatible with value type.
if (m_ExpectedValueType != null)
{
var type = m_ListOptions.LookupTypeRegistration(name);
var valueType = m_GetValueType(type);
if (valueType != null && !m_ExpectedValueType.IsAssignableFrom(valueType))
continue;
}
var niceName = ObjectNames.NicifyVariableName(name);
menu.AddItem(new GUIContent(niceName), false, OnAddElement, name.ToString());
}
menu.ShowAsContext();
}
private void OnAddElement(object data)
{
var name = (string)data;
ArrayHelpers.Append(ref m_ParametersForEachListItem,
new NameAndParameters {name = name});
ArrayHelpers.Append(ref m_EditableParametersForEachListItem,
new ParameterListView { onChange = OnParametersChanged });
var index = m_EditableParametersForEachListItem.Length - 1;
var typeName = m_ParametersForEachListItem[index].name;
var rowType = m_ListOptions.LookupTypeRegistration(typeName);
m_EditableParametersForEachListItem[index].Initialize(rowType, m_ParametersForEachListItem[index].parameters);
m_EditableParametersForEachListItem[index].name = ObjectNames.NicifyVariableName(name);
m_Apply();
}
private void OnParametersChanged()
{
for (var i = 0; i < m_ParametersForEachListItem.Length; i++)
{
m_ParametersForEachListItem[i] = new NameAndParameters
{
name = m_ParametersForEachListItem[i].name,
parameters = m_EditableParametersForEachListItem[i].GetParameters(),
};
}
m_Apply();
}
private static class Styles
{
public static readonly GUIStyle s_FoldoutStyle = new GUIStyle("foldout").WithFontStyle(FontStyle.Bold);
public static readonly GUIStyle s_UpDownButtonStyle = new GUIStyle("label").WithFixedWidth(12).WithFixedHeight(12).WithPadding(new RectOffset());
}
private void SwapEntry(int oldIndex, int newIndex)
{
MemoryHelpers.Swap(ref m_ParametersForEachListItem[oldIndex], ref m_ParametersForEachListItem[newIndex]);
MemoryHelpers.Swap(ref m_EditableParametersForEachListItem[oldIndex], ref m_EditableParametersForEachListItem[newIndex]);
m_Apply();
}
public void OnGUI()
{
if (m_EditableParametersForEachListItem == null || m_EditableParametersForEachListItem.Length == 0)
{
using (new EditorGUI.DisabledScope(true))
{
EditorGUI.indentLevel++;
EditorGUILayout.LabelField($"No {m_ItemName}s have been added.");
EditorGUI.indentLevel--;
}
}
else
for (var i = 0; i < m_EditableParametersForEachListItem.Length; i++)
{
var editableParams = m_EditableParametersForEachListItem[i];
EditorGUILayout.BeginHorizontal();
if (editableParams.hasUIToShow)
editableParams.visible = EditorGUILayout.Foldout(editableParams.visible, editableParams.name, true, Styles.s_FoldoutStyle);
else
{
GUILayout.Space(16);
EditorGUILayout.LabelField(editableParams.name, EditorStyles.boldLabel);
}
GUILayout.FlexibleSpace();
using (new EditorGUI.DisabledScope(i == 0))
{
if (GUILayout.Button(m_UpButton, Styles.s_UpDownButtonStyle))
SwapEntry(i, i - 1);
}
using (new EditorGUI.DisabledScope(i == m_EditableParametersForEachListItem.Length - 1))
{
if (GUILayout.Button(m_DownButton, Styles.s_UpDownButtonStyle))
SwapEntry(i, i + 1);
}
if (GUILayout.Button(m_DeleteButton, EditorStyles.label))
{
// Unfocus controls, because otherwise, the editor can get confused and have text from a text field
// on the deleted item leak to a different field.
GUI.FocusControl(null);
ArrayHelpers.EraseAt(ref m_ParametersForEachListItem, i);
ArrayHelpers.EraseAt(ref m_EditableParametersForEachListItem, i);
m_Apply();
GUIUtility.ExitGUI();
}
EditorGUILayout.EndHorizontal();
if (editableParams.visible)
{
EditorGUI.indentLevel++;
editableParams.OnGUI();
EditorGUI.indentLevel--;
}
GUIHelpers.DrawLineSeparator();
}
}
public string ToSerializableString()
{
if (m_ParametersForEachListItem == null)
return string.Empty;
return string.Join(NamedValue.Separator,
m_ParametersForEachListItem.Select(x => x.ToString()).ToArray());
}
private Func m_GetValueType;
private SerializedProperty m_Property;
private readonly TypeTable m_ListOptions;
private readonly string m_ExpectedControlLayout;
private readonly Type m_ExpectedValueType;
private readonly GUIContent m_DeleteButton;
private readonly GUIContent m_UpButton;
private readonly GUIContent m_DownButton;
private NameAndParameters[] m_ParametersForEachListItem;
private ParameterListView[] m_EditableParametersForEachListItem;
private readonly Action m_Apply;
private string m_ItemName;
}
///
/// A list of processors and their parameters.
///
internal class ProcessorsListView : NameAndParameterListView
{
public ProcessorsListView(SerializedProperty property, Action applyAction, string expectedControlLayout)
: base(property, applyAction, expectedControlLayout, InputProcessor.s_Processors, InputProcessor.GetValueTypeFromType, "Processor")
{
}
}
///
/// A list view of interactions and their parameters.
///
internal class InteractionsListView : NameAndParameterListView
{
public InteractionsListView(SerializedProperty property, Action applyAction, string expectedControlLayout)
: base(property, applyAction, expectedControlLayout, InputInteraction.s_Interactions, InputInteraction.GetValueType, "Interaction")
{
}
}
}
#endif // UNITY_EDITOR