Initial commit
@@ -0,0 +1,249 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
|
||||
////TODO: ensure that GUIDs in the asset are unique
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Keeps a reference to the asset being edited and maintains a copy of the asset object
|
||||
/// around for editing.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class InputActionAssetManager : IDisposable
|
||||
{
|
||||
[SerializeField] private InputActionAsset m_AssetObjectForEditing;
|
||||
[SerializeField] private InputActionAsset m_ImportedAssetObject;
|
||||
[SerializeField] private string m_AssetGUID;
|
||||
[SerializeField] private string m_ImportedAssetJson;
|
||||
[SerializeField] private bool m_IsDirty;
|
||||
|
||||
private SerializedObject m_SerializedObject;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Asset GUID uniquely identifying the associated imported asset.
|
||||
/// </summary>
|
||||
public string guid => m_AssetGUID;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current Asset Path for the associated imported asset.
|
||||
/// If the asset have been deleted this will be <c>null</c>.
|
||||
/// </summary>
|
||||
public string path
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(m_AssetGUID), "Asset GUID is empty");
|
||||
return AssetDatabase.GUIDToAssetPath(m_AssetGUID);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the associated imported asset.
|
||||
/// </summary>
|
||||
public string name
|
||||
{
|
||||
get
|
||||
{
|
||||
var asset = importedAsset;
|
||||
if (asset != null)
|
||||
return asset.name;
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
return Path.GetFileNameWithoutExtension(path);
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private InputActionAsset importedAsset
|
||||
{
|
||||
get
|
||||
{
|
||||
// Note that this may be null after deserialization from domain reload
|
||||
if (m_ImportedAssetObject == null)
|
||||
LoadImportedObjectFromGuid();
|
||||
|
||||
return m_ImportedAssetObject;
|
||||
}
|
||||
}
|
||||
|
||||
public InputActionAsset editedAsset => m_AssetObjectForEditing; // TODO Remove if redundant
|
||||
|
||||
public Action<bool> onDirtyChanged { get; set; }
|
||||
|
||||
public InputActionAssetManager(InputActionAsset inputActionAsset)
|
||||
{
|
||||
if (inputActionAsset == null)
|
||||
throw new NullReferenceException(nameof(inputActionAsset));
|
||||
m_AssetGUID = EditorHelpers.GetAssetGUID(inputActionAsset);
|
||||
if (m_AssetGUID == null)
|
||||
throw new Exception($"Failed to get asset {inputActionAsset.name} GUID");
|
||||
|
||||
m_ImportedAssetObject = inputActionAsset;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public SerializedObject serializedObject => m_SerializedObject;
|
||||
|
||||
public bool dirty => m_IsDirty;
|
||||
|
||||
public bool Initialize()
|
||||
{
|
||||
if (m_AssetObjectForEditing == null)
|
||||
{
|
||||
if (importedAsset == null)
|
||||
{
|
||||
// The asset we want to edit no longer exists.
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateWorkingCopyAsset();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_SerializedObject = new SerializedObject(m_AssetObjectForEditing);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_SerializedObject == null)
|
||||
return;
|
||||
m_SerializedObject?.Dispose();
|
||||
m_SerializedObject = null;
|
||||
}
|
||||
|
||||
public bool ReInitializeIfAssetHasChanged()
|
||||
{
|
||||
var json = importedAsset.ToJson();
|
||||
if (m_ImportedAssetJson == json)
|
||||
return false;
|
||||
|
||||
CreateWorkingCopyAsset();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static InputActionAsset CreateWorkingCopy(InputActionAsset source)
|
||||
{
|
||||
var copy = Object.Instantiate(source);
|
||||
copy.hideFlags = HideFlags.HideAndDontSave;
|
||||
copy.name = source.name;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public static void CreateWorkingCopyAsset(ref InputActionAsset copy, InputActionAsset source)
|
||||
{
|
||||
if (copy != null)
|
||||
Cleanup(ref copy);
|
||||
|
||||
copy = CreateWorkingCopy(source);
|
||||
}
|
||||
|
||||
private void CreateWorkingCopyAsset() // TODO Can likely be removed if combined with Initialize
|
||||
{
|
||||
if (m_AssetObjectForEditing != null)
|
||||
Cleanup();
|
||||
|
||||
// Duplicate the asset along 1:1. Unlike calling Clone(), this will also preserve GUIDs.
|
||||
var asset = importedAsset;
|
||||
m_AssetObjectForEditing = CreateWorkingCopy(asset);
|
||||
m_ImportedAssetJson = asset.ToJson();
|
||||
m_SerializedObject = new SerializedObject(m_AssetObjectForEditing);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
Cleanup(ref m_AssetObjectForEditing);
|
||||
}
|
||||
|
||||
public static void Cleanup(ref InputActionAsset asset)
|
||||
{
|
||||
if (asset == null)
|
||||
return;
|
||||
|
||||
Object.DestroyImmediate(asset);
|
||||
asset = null;
|
||||
}
|
||||
|
||||
private void LoadImportedObjectFromGuid()
|
||||
{
|
||||
// https://fogbugz.unity3d.com/f/cases/1313185/
|
||||
// InputActionEditorWindow being an EditorWindow, it will be saved as part of the editor's
|
||||
// window layout. When a project is opened that has no Library/ folder, the layout from the
|
||||
// most recently opened project is used. Which means that when opening an .inputactions
|
||||
// asset in project A, then closing it, and then opening project B, restoring the window layout
|
||||
// also tries to restore the InputActionEditorWindow having that very same asset open -- which
|
||||
// will lead nowhere except there happens to be an InputActionAsset with the very same GUID in
|
||||
// the project.
|
||||
var assetPath = path;
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
m_ImportedAssetObject = AssetDatabase.LoadAssetAtPath<InputActionAsset>(assetPath);
|
||||
}
|
||||
|
||||
public void ApplyChanges()
|
||||
{
|
||||
m_SerializedObject.ApplyModifiedProperties();
|
||||
m_SerializedObject.Update();
|
||||
}
|
||||
|
||||
internal void SaveChangesToAsset()
|
||||
{
|
||||
// If this is invoked after a domain reload, importAsset will resolve itself.
|
||||
// However, if the asset do not exist importedAsset will be null and we cannot complete the operation.
|
||||
if (importedAsset == null)
|
||||
throw new Exception("Unable to save changes. Associated asset does not exist.");
|
||||
|
||||
SaveAsset(path, m_AssetObjectForEditing.ToJson());
|
||||
SetDirty(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves an asset to the given <c>assetPath</c> with file content corresponding to <c>assetJson</c>
|
||||
/// if the current content of the asset given by <c>assetPath</c> is different or the asset do not exist.
|
||||
/// </summary>
|
||||
/// <param name="assetPath">Destination asset path.</param>
|
||||
/// <param name="assetJson">The JSON file content to be written to the asset.</param>
|
||||
/// <returns><c>true</c> if the asset was successfully modified or created, else <c>false</c>.</returns>
|
||||
internal static bool SaveAsset(string assetPath, string assetJson)
|
||||
{
|
||||
var existingJson = File.Exists(assetPath) ? File.ReadAllText(assetPath) : string.Empty;
|
||||
|
||||
// Return immediately if file content has not changed, i.e. touching the file would not yield a difference.
|
||||
if (assetJson == existingJson)
|
||||
return false;
|
||||
|
||||
// Attempt to write asset to disc (including checkout the file) and inform the user if this fails.
|
||||
if (EditorHelpers.WriteAsset(assetPath, assetJson))
|
||||
return true;
|
||||
|
||||
Debug.LogError($"Unable save asset to \"{assetPath}\" since the asset-path could not be checked-out as editable in the underlying version-control system.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public void MarkDirty()
|
||||
{
|
||||
SetDirty(true);
|
||||
}
|
||||
|
||||
public void UpdateAssetDirtyState()
|
||||
{
|
||||
m_SerializedObject.Update();
|
||||
SetDirty(m_AssetObjectForEditing.ToJson() != importedAsset.ToJson()); // TODO Why not using cached version?
|
||||
}
|
||||
|
||||
private void SetDirty(bool newValue)
|
||||
{
|
||||
m_IsDirty = newValue;
|
||||
if (onDirtyChanged != null)
|
||||
onDirtyChanged(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5b4f9c3390c14106a5006fd5d3d3cbe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,674 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////TODO: better method for creating display names than InputControlPath.TryGetDeviceLayout
|
||||
|
||||
////FIXME: Device requirements list in control scheme popup must mention explicitly that that is what it is
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Toolbar in input action asset editor.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Allows editing and selecting from the set of control schemes as well as selecting from the
|
||||
/// set of device requirements within the currently selected control scheme.
|
||||
///
|
||||
/// Also controls saving and has the global search text field.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputActionEditorWindow"/>
|
||||
[Serializable]
|
||||
internal class InputActionEditorToolbar
|
||||
{
|
||||
public void OnGUI()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
DrawSchemeSelection();
|
||||
DrawDeviceFilterSelection();
|
||||
if (!InputEditorUserSettings.autoSaveInputActionAssets)
|
||||
DrawSaveButton();
|
||||
GUILayout.FlexibleSpace();
|
||||
DrawAutoSaveToggle();
|
||||
GUILayout.Space(5);
|
||||
DrawSearchField();
|
||||
GUILayout.Space(5);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawSchemeSelection()
|
||||
{
|
||||
var buttonGUI = m_ControlSchemes.LengthSafe() > 0
|
||||
? new GUIContent(selectedControlScheme?.name ?? "All Control Schemes")
|
||||
: new GUIContent("No Control Schemes");
|
||||
|
||||
var buttonRect = GUILayoutUtility.GetRect(buttonGUI, EditorStyles.toolbarPopup, GUILayout.MinWidth(k_MinimumButtonWidth));
|
||||
|
||||
if (GUI.Button(buttonRect, buttonGUI, EditorStyles.toolbarPopup))
|
||||
{
|
||||
// PopupWindow.Show already takes the current OnGUI EditorWindow context into account for window coordinates.
|
||||
// However, on macOS, menu commands are performed asynchronously, so we don't have a current OnGUI context.
|
||||
// So in that case, we need to translate the rect to screen coordinates. Don't do that on windows, as we will
|
||||
// overcompensate otherwise.
|
||||
if (Application.platform == RuntimePlatform.OSXEditor)
|
||||
buttonRect = new Rect(EditorGUIUtility.GUIToScreenPoint(new Vector2(buttonRect.x, buttonRect.y)), Vector2.zero);
|
||||
|
||||
var menu = new GenericMenu();
|
||||
|
||||
// Add entries to select control scheme, if we have some.
|
||||
if (m_ControlSchemes.LengthSafe() > 0)
|
||||
{
|
||||
menu.AddItem(s_AllControlSchemes, m_SelectedControlSchemeIndex == -1, OnControlSchemeSelected, null);
|
||||
var selectedControlSchemeName = m_SelectedControlSchemeIndex == -1
|
||||
? null : m_ControlSchemes[m_SelectedControlSchemeIndex].name;
|
||||
foreach (var controlScheme in m_ControlSchemes.OrderBy(x => x.name))
|
||||
menu.AddItem(new GUIContent(controlScheme.name),
|
||||
controlScheme.name == selectedControlSchemeName, OnControlSchemeSelected,
|
||||
controlScheme.name);
|
||||
|
||||
menu.AddSeparator(string.Empty);
|
||||
}
|
||||
|
||||
// Add entries to add/edit/duplicate/delete control schemes.
|
||||
menu.AddItem(s_AddControlSchemeLabel, false, OnAddControlScheme, buttonRect);
|
||||
if (m_SelectedControlSchemeIndex >= 0)
|
||||
{
|
||||
menu.AddItem(s_EditControlSchemeLabel, false, OnEditSelectedControlScheme, buttonRect);
|
||||
menu.AddItem(s_DuplicateControlSchemeLabel, false, OnDuplicateControlScheme, buttonRect);
|
||||
menu.AddItem(s_DeleteControlSchemeLabel, false, OnDeleteControlScheme);
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.AddDisabledItem(s_EditControlSchemeLabel, false);
|
||||
menu.AddDisabledItem(s_DuplicateControlSchemeLabel, false);
|
||||
menu.AddDisabledItem(s_DeleteControlSchemeLabel, false);
|
||||
}
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDeviceFilterSelection()
|
||||
{
|
||||
// Lazy-initialize list of GUIContents that represent each individual device requirement.
|
||||
if (m_SelectedSchemeDeviceRequirementNames == null && m_ControlSchemes.LengthSafe() > 0 && m_SelectedControlSchemeIndex >= 0)
|
||||
{
|
||||
m_SelectedSchemeDeviceRequirementNames = m_ControlSchemes[m_SelectedControlSchemeIndex]
|
||||
.deviceRequirements.Select(x => new GUIContent(DeviceRequirementToDisplayString(x)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
EditorGUI.BeginDisabledGroup(m_SelectedControlSchemeIndex < 0);
|
||||
if (m_SelectedSchemeDeviceRequirementNames.LengthSafe() == 0)
|
||||
{
|
||||
GUILayout.Button(s_AllDevicesLabel, EditorStyles.toolbarPopup, GUILayout.MinWidth(k_MinimumButtonWidth));
|
||||
}
|
||||
else if (GUILayout.Button(m_SelectedDeviceRequirementIndex < 0 ? s_AllDevicesLabel : m_SelectedSchemeDeviceRequirementNames[m_SelectedDeviceRequirementIndex],
|
||||
EditorStyles.toolbarPopup, GUILayout.MinWidth(k_MinimumButtonWidth)))
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
menu.AddItem(s_AllDevicesLabel, m_SelectedControlSchemeIndex == -1, OnSelectedDeviceChanged, -1);
|
||||
for (var i = 0; i < m_SelectedSchemeDeviceRequirementNames.Length; i++)
|
||||
menu.AddItem(m_SelectedSchemeDeviceRequirementNames[i], m_SelectedDeviceRequirementIndex == i, OnSelectedDeviceChanged, i);
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
private void DrawSaveButton()
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(!m_IsDirty);
|
||||
EditorGUILayout.Space();
|
||||
if (GUILayout.Button(s_SaveAssetLabel, EditorStyles.toolbarButton))
|
||||
onSave();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
private void DrawAutoSaveToggle()
|
||||
{
|
||||
////FIXME: Using a normal Toggle style with a miniFont, I can't get the "Auto-Save" label to align properly on the vertical.
|
||||
//// The workaround here splits it into a toggle with an empty label plus an extra label.
|
||||
//// Not using EditorStyles.toolbarButton here as it makes it hard to tell that it's a toggle.
|
||||
if (s_MiniToggleStyle == null)
|
||||
{
|
||||
s_MiniToggleStyle = new GUIStyle("Toggle")
|
||||
{
|
||||
font = EditorStyles.miniFont,
|
||||
margin = new RectOffset(0, 0, 1, 0),
|
||||
padding = new RectOffset(0, 16, 0, 0)
|
||||
};
|
||||
s_MiniLabelStyle = new GUIStyle("Label")
|
||||
{
|
||||
font = EditorStyles.miniFont,
|
||||
margin = new RectOffset(0, 0, 3, 0)
|
||||
};
|
||||
}
|
||||
|
||||
var autoSaveNew = GUILayout.Toggle(InputEditorUserSettings.autoSaveInputActionAssets, "",
|
||||
s_MiniToggleStyle);
|
||||
GUILayout.Label(s_AutoSaveLabel, s_MiniLabelStyle);
|
||||
if (autoSaveNew != InputEditorUserSettings.autoSaveInputActionAssets && autoSaveNew && m_IsDirty)
|
||||
{
|
||||
// If it changed from disabled to enabled, perform an initial save.
|
||||
onSave();
|
||||
}
|
||||
|
||||
InputEditorUserSettings.autoSaveInputActionAssets = autoSaveNew;
|
||||
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
private void DrawSearchField()
|
||||
{
|
||||
if (m_SearchField == null)
|
||||
m_SearchField = new SearchField();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
m_SearchText = m_SearchField.OnToolbarGUI(m_SearchText, GUILayout.MaxWidth(250));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
onSearchChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void OnControlSchemeSelected(object nameObj)
|
||||
{
|
||||
var index = -1;
|
||||
var name = (string)nameObj;
|
||||
if (name != null)
|
||||
{
|
||||
index = ArrayHelpers.IndexOf(m_ControlSchemes,
|
||||
x => x.name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
|
||||
Debug.Assert(index != -1, $"Cannot find control scheme {name}");
|
||||
}
|
||||
|
||||
m_SelectedControlSchemeIndex = index;
|
||||
m_SelectedDeviceRequirementIndex = -1;
|
||||
m_SelectedSchemeDeviceRequirementNames = null;
|
||||
|
||||
onSelectedSchemeChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void OnSelectedDeviceChanged(object indexObj)
|
||||
{
|
||||
Debug.Assert(m_SelectedControlSchemeIndex >= 0, "Control scheme must be selected");
|
||||
|
||||
m_SelectedDeviceRequirementIndex = (int)indexObj;
|
||||
onSelectedDeviceChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void OnAddControlScheme(object position)
|
||||
{
|
||||
var uniqueName = MakeUniqueControlSchemeName("New control scheme");
|
||||
ControlSchemePropertiesPopup.Show((Rect)position,
|
||||
new InputControlScheme(uniqueName),
|
||||
(s, _) => AddAndSelectControlScheme(s));
|
||||
}
|
||||
|
||||
private void OnDeleteControlScheme()
|
||||
{
|
||||
Debug.Assert(m_SelectedControlSchemeIndex >= 0, "Control scheme must be selected");
|
||||
|
||||
var name = m_ControlSchemes[m_SelectedControlSchemeIndex].name;
|
||||
var bindingGroup = m_ControlSchemes[m_SelectedControlSchemeIndex].bindingGroup;
|
||||
|
||||
// Ask for confirmation.
|
||||
if (!EditorUtility.DisplayDialog("Delete scheme?", $"Do you want to delete control scheme '{name}'?",
|
||||
"Delete", "Cancel"))
|
||||
return;
|
||||
|
||||
ArrayHelpers.EraseAt(ref m_ControlSchemes, m_SelectedControlSchemeIndex);
|
||||
m_SelectedControlSchemeIndex = -1;
|
||||
m_SelectedSchemeDeviceRequirementNames = null;
|
||||
|
||||
if (m_SelectedDeviceRequirementIndex >= 0)
|
||||
{
|
||||
m_SelectedDeviceRequirementIndex = -1;
|
||||
onSelectedDeviceChanged?.Invoke();
|
||||
}
|
||||
|
||||
onControlSchemesChanged?.Invoke();
|
||||
onSelectedSchemeChanged?.Invoke();
|
||||
onControlSchemeDeleted?.Invoke(name, bindingGroup);
|
||||
}
|
||||
|
||||
////REVIEW: this does nothing to bindings; should this ask to duplicate bindings as well?
|
||||
private void OnDuplicateControlScheme(object position)
|
||||
{
|
||||
Debug.Assert(m_SelectedControlSchemeIndex >= 0, "Control scheme must be selected");
|
||||
|
||||
var scheme = m_ControlSchemes[m_SelectedControlSchemeIndex];
|
||||
scheme = new InputControlScheme(MakeUniqueControlSchemeName(scheme.name),
|
||||
devices: scheme.deviceRequirements);
|
||||
|
||||
ControlSchemePropertiesPopup.Show((Rect)position, scheme,
|
||||
(s, _) => AddAndSelectControlScheme(s));
|
||||
}
|
||||
|
||||
private void OnEditSelectedControlScheme(object position)
|
||||
{
|
||||
Debug.Assert(m_SelectedControlSchemeIndex >= 0, "Control scheme must be selected");
|
||||
|
||||
ControlSchemePropertiesPopup.Show((Rect)position,
|
||||
m_ControlSchemes[m_SelectedControlSchemeIndex],
|
||||
UpdateControlScheme,
|
||||
m_SelectedControlSchemeIndex);
|
||||
}
|
||||
|
||||
private void AddAndSelectControlScheme(InputControlScheme scheme)
|
||||
{
|
||||
// Ensure scheme has a name.
|
||||
if (string.IsNullOrEmpty(scheme.name))
|
||||
scheme.m_Name = "New control scheme";
|
||||
|
||||
// Make sure name is unique.
|
||||
scheme.m_Name = MakeUniqueControlSchemeName(scheme.name);
|
||||
|
||||
var index = ArrayHelpers.Append(ref m_ControlSchemes, scheme);
|
||||
onControlSchemesChanged?.Invoke();
|
||||
|
||||
SelectControlScheme(index);
|
||||
}
|
||||
|
||||
private void UpdateControlScheme(InputControlScheme scheme, int index)
|
||||
{
|
||||
Debug.Assert(index >= 0 && index < m_ControlSchemes.LengthSafe(), "Control scheme index out of range");
|
||||
|
||||
var renamed = false;
|
||||
string oldBindingGroup = null;
|
||||
string newBindingGroup = null;
|
||||
|
||||
// If given scheme has no name, preserve the existing one on the control scheme.
|
||||
if (string.IsNullOrEmpty(scheme.name))
|
||||
scheme.m_Name = m_ControlSchemes[index].name;
|
||||
|
||||
// If name is changing, make sure it's unique.
|
||||
else if (scheme.name != m_ControlSchemes[index].name)
|
||||
{
|
||||
renamed = true;
|
||||
oldBindingGroup = m_ControlSchemes[index].bindingGroup;
|
||||
m_ControlSchemes[index].m_Name = ""; // Don't want this to interfere with finding a unique name.
|
||||
var newName = MakeUniqueControlSchemeName(scheme.name);
|
||||
m_ControlSchemes[index].SetNameAndBindingGroup(newName);
|
||||
newBindingGroup = m_ControlSchemes[index].bindingGroup;
|
||||
}
|
||||
|
||||
m_ControlSchemes[index] = scheme;
|
||||
onControlSchemesChanged?.Invoke();
|
||||
|
||||
if (renamed)
|
||||
onControlSchemeRenamed?.Invoke(oldBindingGroup, newBindingGroup);
|
||||
}
|
||||
|
||||
private void SelectControlScheme(int index)
|
||||
{
|
||||
Debug.Assert(index >= 0 && index < m_ControlSchemes.LengthSafe(), "Control scheme index out of range");
|
||||
|
||||
m_SelectedControlSchemeIndex = index;
|
||||
m_SelectedSchemeDeviceRequirementNames = null;
|
||||
onSelectedSchemeChanged?.Invoke();
|
||||
|
||||
// Reset device selection.
|
||||
if (m_SelectedDeviceRequirementIndex != -1)
|
||||
{
|
||||
m_SelectedDeviceRequirementIndex = -1;
|
||||
onSelectedDeviceChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private string MakeUniqueControlSchemeName(string name)
|
||||
{
|
||||
const string presetName = "All Control Schemes";
|
||||
if (m_ControlSchemes == null)
|
||||
return StringHelpers.MakeUniqueName(name, new[] {presetName}, x => x);
|
||||
return StringHelpers.MakeUniqueName(name, m_ControlSchemes.Select(x => x.name).Append(presetName), x => x);
|
||||
}
|
||||
|
||||
private static string DeviceRequirementToDisplayString(InputControlScheme.DeviceRequirement requirement)
|
||||
{
|
||||
////TODO: need something more flexible to produce correct results for more than the simple string we produce here
|
||||
var deviceLayout = InputControlPath.TryGetDeviceLayout(requirement.controlPath);
|
||||
var deviceLayoutText = !string.IsNullOrEmpty(deviceLayout)
|
||||
? EditorInputControlLayoutCache.GetDisplayName(deviceLayout)
|
||||
: string.Empty;
|
||||
var usages = InputControlPath.TryGetDeviceUsages(requirement.controlPath);
|
||||
|
||||
if (usages != null && usages.Length > 0)
|
||||
return $"{deviceLayoutText} {string.Join("}{", usages)}";
|
||||
|
||||
return deviceLayoutText;
|
||||
}
|
||||
|
||||
// Notifications.
|
||||
public Action onSearchChanged;
|
||||
public Action onSelectedSchemeChanged;
|
||||
public Action onSelectedDeviceChanged;
|
||||
public Action onControlSchemesChanged;
|
||||
public Action<string, string> onControlSchemeRenamed;
|
||||
public Action<string, string> onControlSchemeDeleted;
|
||||
public Action onSave;
|
||||
|
||||
[SerializeField] private bool m_IsDirty;
|
||||
[SerializeField] private int m_SelectedControlSchemeIndex = -1;
|
||||
[SerializeField] private int m_SelectedDeviceRequirementIndex = -1;
|
||||
[SerializeField] private InputControlScheme[] m_ControlSchemes;
|
||||
[SerializeField] private string m_SearchText;
|
||||
|
||||
private GUIContent[] m_SelectedSchemeDeviceRequirementNames;
|
||||
private SearchField m_SearchField;
|
||||
|
||||
private static readonly GUIContent s_AllControlSchemes = EditorGUIUtility.TrTextContent("All Control Schemes");
|
||||
private static readonly GUIContent s_AddControlSchemeLabel = new GUIContent("Add Control Scheme...");
|
||||
private static readonly GUIContent s_EditControlSchemeLabel = EditorGUIUtility.TrTextContent("Edit Control Scheme...");
|
||||
private static readonly GUIContent s_DuplicateControlSchemeLabel = EditorGUIUtility.TrTextContent("Duplicate Control Scheme...");
|
||||
private static readonly GUIContent s_DeleteControlSchemeLabel = EditorGUIUtility.TrTextContent("Delete Control Scheme...");
|
||||
private static readonly GUIContent s_SaveAssetLabel = EditorGUIUtility.TrTextContent("Save Asset");
|
||||
private static readonly GUIContent s_AutoSaveLabel = EditorGUIUtility.TrTextContent("Auto-Save");
|
||||
private static readonly GUIContent s_AllDevicesLabel = EditorGUIUtility.TrTextContent("All Devices");
|
||||
|
||||
private static GUIStyle s_MiniToggleStyle;
|
||||
private static GUIStyle s_MiniLabelStyle;
|
||||
|
||||
private const float k_MinimumButtonWidth = 110f;
|
||||
|
||||
public ReadOnlyArray<InputControlScheme> controlSchemes
|
||||
{
|
||||
get => m_ControlSchemes;
|
||||
set
|
||||
{
|
||||
m_ControlSchemes = value.ToArray();
|
||||
m_SelectedSchemeDeviceRequirementNames = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The control scheme currently selected in the toolbar or null if none is selected.
|
||||
/// </summary>
|
||||
public InputControlScheme? selectedControlScheme => m_SelectedControlSchemeIndex >= 0
|
||||
? new InputControlScheme ? (m_ControlSchemes[m_SelectedControlSchemeIndex])
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// The device requirement of the currently selected control scheme which is currently selected
|
||||
/// in the toolbar or null if none is selected.
|
||||
/// </summary>
|
||||
public InputControlScheme.DeviceRequirement? selectedDeviceRequirement => m_SelectedDeviceRequirementIndex >= 0
|
||||
? new InputControlScheme.DeviceRequirement ? (m_ControlSchemes[m_SelectedControlSchemeIndex]
|
||||
.deviceRequirements[m_SelectedDeviceRequirementIndex])
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// The search text currently entered in the toolbar or null.
|
||||
/// </summary>
|
||||
public string searchText => m_SearchText;
|
||||
|
||||
internal void ResetSearchFilters()
|
||||
{
|
||||
m_SearchText = null;
|
||||
m_SelectedControlSchemeIndex = -1;
|
||||
m_SelectedDeviceRequirementIndex = -1;
|
||||
}
|
||||
|
||||
public bool isDirty
|
||||
{
|
||||
get => m_IsDirty;
|
||||
set => m_IsDirty = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Popup window content for editing control schemes.
|
||||
/// </summary>
|
||||
private class ControlSchemePropertiesPopup : PopupWindowContent
|
||||
{
|
||||
public static void Show(Rect position, InputControlScheme controlScheme, Action<InputControlScheme, int> onApply,
|
||||
int controlSchemeIndex = -1)
|
||||
{
|
||||
var popup = new ControlSchemePropertiesPopup
|
||||
{
|
||||
m_ControlSchemeIndex = controlSchemeIndex,
|
||||
m_ControlScheme = controlScheme,
|
||||
m_OnApply = onApply,
|
||||
m_SetFocus = true,
|
||||
};
|
||||
|
||||
// We're calling here from a callback, so we need to manually handle ExitGUIException.
|
||||
try
|
||||
{
|
||||
PopupWindow.Show(position, popup);
|
||||
}
|
||||
catch (ExitGUIException) {}
|
||||
}
|
||||
|
||||
public override Vector2 GetWindowSize()
|
||||
{
|
||||
return m_ButtonsAndLabelsHeights > 0 ? new Vector2(300, m_ButtonsAndLabelsHeights) : s_DefaultSize;
|
||||
}
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
m_DeviceList = m_ControlScheme.deviceRequirements.Select(a => new DeviceEntry(a)).ToList();
|
||||
m_DeviceView = new ReorderableList(m_DeviceList, typeof(InputControlScheme.DeviceRequirement));
|
||||
m_DeviceView.headerHeight = 2;
|
||||
m_DeviceView.onAddCallback += list =>
|
||||
{
|
||||
var dropdown = new InputControlPickerDropdown(
|
||||
new InputControlPickerState(),
|
||||
path =>
|
||||
{
|
||||
var requirement = new InputControlScheme.DeviceRequirement
|
||||
{
|
||||
controlPath = path,
|
||||
isOptional = false
|
||||
};
|
||||
|
||||
AddDeviceRequirement(requirement);
|
||||
},
|
||||
mode: InputControlPicker.Mode.PickDevice);
|
||||
dropdown.Show(new Rect(Event.current.mousePosition, Vector2.zero));
|
||||
};
|
||||
m_DeviceView.onRemoveCallback += list =>
|
||||
{
|
||||
list.list.RemoveAt(list.index);
|
||||
list.index = -1;
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect rect)
|
||||
{
|
||||
if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape)
|
||||
{
|
||||
editorWindow.Close();
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
m_ButtonsAndLabelsHeights = 0;
|
||||
|
||||
GUILayout.BeginArea(rect);
|
||||
DrawTopBar();
|
||||
EditorGUILayout.BeginVertical(EditorStyles.label);
|
||||
DrawSpace();
|
||||
DrawNameEditTextField();
|
||||
DrawSpace();
|
||||
DrawDeviceList();
|
||||
DrawConfirmationButton();
|
||||
EditorGUILayout.EndVertical();
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
private void DrawConfirmationButton()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Cancel", GUILayout.ExpandWidth(true)))
|
||||
{
|
||||
editorWindow.Close();
|
||||
}
|
||||
if (GUILayout.Button("Save", GUILayout.ExpandWidth(true)))
|
||||
{
|
||||
// Don't allow control scheme name to be empty.
|
||||
if (string.IsNullOrEmpty(m_ControlScheme.name))
|
||||
{
|
||||
////FIXME: On 2019.1 this doesn't display properly in the window; check 2019.3
|
||||
editorWindow.ShowNotification(new GUIContent("Control scheme must have a name"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ControlScheme = new InputControlScheme(m_ControlScheme.name,
|
||||
devices: m_DeviceList.Select(a => a.deviceRequirement));
|
||||
|
||||
editorWindow.Close();
|
||||
m_OnApply(m_ControlScheme, m_ControlSchemeIndex);
|
||||
}
|
||||
}
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
m_ButtonsAndLabelsHeights += GUILayoutUtility.GetLastRect().height;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawDeviceList()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.label);
|
||||
var requirementsLabelSize = EditorStyles.label.CalcSize(s_RequirementsLabel);
|
||||
var deviceListRect = GUILayoutUtility.GetRect(GetWindowSize().x - requirementsLabelSize.x - 20, m_DeviceView.GetHeight());
|
||||
m_DeviceView.DoList(deviceListRect);
|
||||
var requirementsHeight = DrawRequirementsCheckboxes();
|
||||
var listHeight = m_DeviceView.GetHeight() + EditorGUIUtility.singleLineHeight * 3;
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
if (listHeight < requirementsHeight)
|
||||
{
|
||||
m_ButtonsAndLabelsHeights += requirementsHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ButtonsAndLabelsHeights += listHeight;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawSpace()
|
||||
{
|
||||
GUILayout.Space(6f);
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
m_ButtonsAndLabelsHeights += 6f;
|
||||
}
|
||||
|
||||
private void DrawTopBar()
|
||||
{
|
||||
EditorGUILayout.LabelField(s_AddControlSchemeLabel, Styles.headerLabel);
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
m_ButtonsAndLabelsHeights += GUILayoutUtility.GetLastRect().height;
|
||||
}
|
||||
|
||||
private void DrawNameEditTextField()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
var labelSize = EditorStyles.label.CalcSize(s_ControlSchemeNameLabel);
|
||||
EditorGUILayout.LabelField(s_ControlSchemeNameLabel, GUILayout.Width(labelSize.x));
|
||||
|
||||
GUI.SetNextControlName("ControlSchemeName");
|
||||
////FIXME: This should be a DelayedTextField but for some reason (maybe because it's in a popup?), this
|
||||
//// will lead to the text field not working correctly. Hitting enter on the keyboard will apply the
|
||||
//// change as expected but losing focus will *NOT*. In most cases, this makes the text field seem not
|
||||
//// to work at all so instead we use a normal text field here and then apply the name change as part
|
||||
//// of apply the control scheme changes as a whole. The only real downside is that if the name gets
|
||||
//// adjusted automatically because of a naming conflict, this will only become evident *after* hitting
|
||||
//// the "Save" button.
|
||||
m_ControlScheme.m_Name = EditorGUILayout.TextField(m_ControlScheme.m_Name);
|
||||
|
||||
if (m_SetFocus)
|
||||
{
|
||||
EditorGUI.FocusTextInControl("ControlSchemeName");
|
||||
m_SetFocus = false;
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private float DrawRequirementsCheckboxes()
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.LabelField(s_RequirementsLabel, GUILayout.Width(200));
|
||||
var requirementHeights = GUILayoutUtility.GetLastRect().y;
|
||||
EditorGUI.BeginDisabledGroup(m_DeviceView.index == -1);
|
||||
var requirementsOption = -1;
|
||||
if (m_DeviceView.index >= 0)
|
||||
{
|
||||
var deviceEntryForList = (DeviceEntry)m_DeviceView.list[m_DeviceView.index];
|
||||
requirementsOption = deviceEntryForList.deviceRequirement.isOptional ? 0 : 1;
|
||||
}
|
||||
EditorGUI.BeginChangeCheck();
|
||||
requirementsOption = GUILayout.SelectionGrid(requirementsOption, s_RequiredOptionalChoices, 1, EditorStyles.radioButton);
|
||||
requirementHeights += GUILayoutUtility.GetLastRect().y;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
m_DeviceList[m_DeviceView.index].deviceRequirement.isOptional = requirementsOption == 0;
|
||||
EditorGUI.EndDisabledGroup();
|
||||
EditorGUILayout.EndVertical();
|
||||
return requirementHeights;
|
||||
}
|
||||
|
||||
private void AddDeviceRequirement(InputControlScheme.DeviceRequirement requirement)
|
||||
{
|
||||
ArrayHelpers.Append(ref m_ControlScheme.m_DeviceRequirements, requirement);
|
||||
m_DeviceList.Add(new DeviceEntry(requirement));
|
||||
m_DeviceView.index = m_DeviceView.list.Count - 1;
|
||||
|
||||
editorWindow.Repaint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The control scheme edited by the popup.
|
||||
/// </summary>
|
||||
public InputControlScheme controlScheme => m_ControlScheme;
|
||||
|
||||
private int m_ControlSchemeIndex;
|
||||
private InputControlScheme m_ControlScheme;
|
||||
private Action<InputControlScheme, int> m_OnApply;
|
||||
|
||||
private ReorderableList m_DeviceView;
|
||||
private List<DeviceEntry> m_DeviceList = new List<DeviceEntry>();
|
||||
private int m_RequirementsOptionsChoice;
|
||||
|
||||
private bool m_SetFocus;
|
||||
private float m_ButtonsAndLabelsHeights;
|
||||
|
||||
private static Vector2 s_DefaultSize => new Vector2(300, 200);
|
||||
private static readonly GUIContent s_RequirementsLabel = EditorGUIUtility.TrTextContent("Requirements:");
|
||||
private static readonly GUIContent s_AddControlSchemeLabel = EditorGUIUtility.TrTextContent("Add control scheme");
|
||||
private static readonly GUIContent s_ControlSchemeNameLabel = EditorGUIUtility.TrTextContent("Scheme Name");
|
||||
private static readonly string[] s_RequiredOptionalChoices = { "Optional", "Required" };
|
||||
|
||||
private static class Styles
|
||||
{
|
||||
public static readonly GUIStyle headerLabel = new GUIStyle(EditorStyles.toolbar)
|
||||
.WithAlignment(TextAnchor.MiddleCenter)
|
||||
.WithFontStyle(FontStyle.Bold)
|
||||
.WithPadding(new RectOffset(10, 6, 0, 0));
|
||||
}
|
||||
|
||||
private class DeviceEntry
|
||||
{
|
||||
public string displayText;
|
||||
public InputControlScheme.DeviceRequirement deviceRequirement;
|
||||
|
||||
public DeviceEntry(InputControlScheme.DeviceRequirement requirement)
|
||||
{
|
||||
displayText = DeviceRequirementToDisplayString(requirement);
|
||||
deviceRequirement = requirement;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return displayText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e1e1ebbe5ff04f03bf06ab5398454cf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,859 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.PackageManager.UI;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
|
||||
////TODO: Add "Revert" button
|
||||
|
||||
////TODO: add helpers to very quickly set up certain common configs (e.g. "FPS Controls" in add-action context menu;
|
||||
//// "WASD Control" in add-binding context menu)
|
||||
|
||||
////REVIEW: should we listen for Unity project saves and save dirty .inputactions assets along with it?
|
||||
|
||||
////FIXME: when saving, processor/interaction selection is cleared
|
||||
|
||||
////TODO: persist view state of asset in Library/ folder
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// An editor window to edit .inputactions assets.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The .inputactions editor code does not really separate between model and view. Selection state is contained
|
||||
/// in the tree views and persistent across domain reloads via <see cref="TreeViewState"/>.
|
||||
/// </remarks>
|
||||
internal class InputActionEditorWindow : EditorWindow, IDisposable, IInputActionAssetEditor
|
||||
{
|
||||
// Register editor type via static constructor to enable asset monitoring
|
||||
static InputActionEditorWindow()
|
||||
{
|
||||
InputActionAssetEditor.RegisterType<InputActionEditorWindow>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open window if someone clicks on an .inputactions asset or an action inside of it.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "line", Justification = "line parameter required by OnOpenAsset attribute")]
|
||||
[OnOpenAsset]
|
||||
public static bool OnOpenAsset(int instanceId, int line)
|
||||
{
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
if (!InputSystem.settings.useIMGUIEditorForAssets)
|
||||
return false;
|
||||
#endif
|
||||
var path = AssetDatabase.GetAssetPath(instanceId);
|
||||
if (!InputActionImporter.IsInputActionAssetPath(path))
|
||||
return false;
|
||||
|
||||
string mapToSelect = null;
|
||||
string actionToSelect = null;
|
||||
|
||||
// Grab InputActionAsset.
|
||||
// NOTE: We defer checking out an asset until we save it. This allows a user to open an .inputactions asset and look at it
|
||||
// without forcing a checkout.
|
||||
var obj = EditorUtility.InstanceIDToObject(instanceId);
|
||||
var asset = obj as InputActionAsset;
|
||||
if (asset == null)
|
||||
{
|
||||
// Check if the user clicked on an action inside the asset.
|
||||
var actionReference = obj as InputActionReference;
|
||||
if (actionReference != null)
|
||||
{
|
||||
asset = actionReference.asset;
|
||||
mapToSelect = actionReference.action.actionMap.name;
|
||||
actionToSelect = actionReference.action.name;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
var window = OpenEditor(asset);
|
||||
|
||||
// If user clicked on an action inside the asset, focus on that action (if we can find it).
|
||||
if (actionToSelect != null && window.m_ActionMapsTree.TrySelectItem(mapToSelect))
|
||||
{
|
||||
window.OnActionMapTreeSelectionChanged();
|
||||
window.m_ActionsTree.SelectItem(actionToSelect);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open the specified <paramref name="asset"/> in an editor window. Used when someone hits the "Edit Asset" button in the
|
||||
/// importer inspector.
|
||||
/// </summary>
|
||||
/// <param name="asset">The InputActionAsset to open.</param>
|
||||
/// <returns>The editor window.</returns>
|
||||
public static InputActionEditorWindow OpenEditor(InputActionAsset asset)
|
||||
{
|
||||
////REVIEW: It'd be great if the window got docked by default but the public EditorWindow API doesn't allow that
|
||||
//// to be done for windows that aren't singletons (GetWindow<T>() will only create one window and it's the
|
||||
//// only way to get programmatic docking with the current API).
|
||||
// See if we have an existing editor window that has the asset open.
|
||||
var window = FindEditorForAsset(asset);
|
||||
if (window == null)
|
||||
{
|
||||
// No, so create a new window.
|
||||
window = CreateInstance<InputActionEditorWindow>();
|
||||
window.SetAsset(asset);
|
||||
}
|
||||
window.Show();
|
||||
window.Focus();
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
private static InputActionEditorWindow FindEditorForAsset(InputActionAsset asset)
|
||||
{
|
||||
var guid = EditorHelpers.GetAssetGUID(asset);
|
||||
return guid == null ? null : FindEditorForAssetWithGUID(guid);
|
||||
}
|
||||
|
||||
public static InputActionEditorWindow FindEditorForAssetWithGUID(string guid)
|
||||
{
|
||||
var windows = Resources.FindObjectsOfTypeAll<InputActionEditorWindow>();
|
||||
return windows.FirstOrDefault(w => w.m_ActionAssetManager.guid == guid);
|
||||
}
|
||||
|
||||
public void SaveChangesToAsset()
|
||||
{
|
||||
m_ActionAssetManager.SaveChangesToAsset();
|
||||
}
|
||||
|
||||
public void AddNewActionMap()
|
||||
{
|
||||
m_ActionMapsTree.AddNewActionMap();
|
||||
}
|
||||
|
||||
public void AddNewAction()
|
||||
{
|
||||
// Make sure we have an action map. If we don't have an action map selected,
|
||||
// refuse the operation.
|
||||
var actionMapItem = m_ActionMapsTree.GetSelectedItems().OfType<ActionMapTreeItem>().FirstOrDefault();
|
||||
if (actionMapItem == null)
|
||||
{
|
||||
EditorApplication.Beep();
|
||||
return;
|
||||
}
|
||||
|
||||
m_ActionsTree.AddNewAction(actionMapItem.property);
|
||||
}
|
||||
|
||||
public void AddNewBinding()
|
||||
{
|
||||
// Make sure we have an action selected.
|
||||
var actionItems = m_ActionsTree.GetSelectedItems().OfType<ActionTreeItem>();
|
||||
if (!actionItems.Any())
|
||||
{
|
||||
EditorApplication.Beep();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in actionItems)
|
||||
m_ActionsTree.AddNewBinding(item.property, item.actionMapProperty);
|
||||
}
|
||||
|
||||
private bool ConfirmSaveChangesIfNeeded()
|
||||
{
|
||||
// Ask for confirmation if we have unsaved changes.
|
||||
if (!m_ForceQuit && m_ActionAssetManager.dirty)
|
||||
{
|
||||
var result = Dialog.InputActionAsset.ShowSaveChanges(m_ActionAssetManager.path);
|
||||
switch (result)
|
||||
{
|
||||
case Dialog.Result.Save:
|
||||
m_ActionAssetManager.SaveChangesToAsset();
|
||||
m_ActionAssetManager.Cleanup();
|
||||
break;
|
||||
case Dialog.Result.Cancel:
|
||||
Instantiate(this).Show();
|
||||
// Cancel editor quit.
|
||||
return false;
|
||||
case Dialog.Result.Discard:
|
||||
// Don't save, don't ask again.
|
||||
m_ForceQuit = true;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(result));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EditorWantsToQuit()
|
||||
{
|
||||
return ConfirmSaveChangesIfNeeded();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
minSize = new Vector2(600, 300);
|
||||
|
||||
// Initialize toolbar. We keep the toolbar across domain reloads but we
|
||||
// will lose the delegates.
|
||||
if (m_Toolbar == null)
|
||||
m_Toolbar = new InputActionEditorToolbar();
|
||||
m_Toolbar.onSearchChanged = OnToolbarSearchChanged;
|
||||
m_Toolbar.onSelectedSchemeChanged = OnControlSchemeSelectionChanged;
|
||||
m_Toolbar.onSelectedDeviceChanged = OnControlSchemeSelectionChanged;
|
||||
m_Toolbar.onSave = SaveChangesToAsset;
|
||||
m_Toolbar.onControlSchemesChanged = OnControlSchemesModified;
|
||||
m_Toolbar.onControlSchemeRenamed = OnControlSchemeRenamed;
|
||||
m_Toolbar.onControlSchemeDeleted = OnControlSchemeDeleted;
|
||||
EditorApplication.wantsToQuit += EditorWantsToQuit;
|
||||
|
||||
// Initialize after assembly reload.
|
||||
if (m_ActionAssetManager != null)
|
||||
{
|
||||
if (!m_ActionAssetManager.Initialize())
|
||||
{
|
||||
// The asset we want to edit no longer exists.
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
m_ActionAssetManager.onDirtyChanged = OnDirtyChanged;
|
||||
|
||||
InitializeTrees();
|
||||
}
|
||||
|
||||
InputSystem.onSettingsChange += OnInputSettingsChanged;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ConfirmSaveChangesIfNeeded();
|
||||
EditorApplication.wantsToQuit -= EditorWantsToQuit;
|
||||
InputSystem.onSettingsChange -= OnInputSettingsChanged;
|
||||
}
|
||||
|
||||
private void OnInputSettingsChanged()
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
// Set asset would usually only be called when the window is open
|
||||
private void SetAsset(InputActionAsset asset)
|
||||
{
|
||||
if (asset == null)
|
||||
return;
|
||||
|
||||
m_ActionAssetManager = new InputActionAssetManager(asset) {onDirtyChanged = OnDirtyChanged};
|
||||
//m_ActionAssetManager.Initialize(); // TODO No longer needed when using constructor
|
||||
|
||||
InitializeTrees();
|
||||
LoadControlSchemes();
|
||||
|
||||
// Select first action map in asset.
|
||||
m_ActionMapsTree.SelectFirstToplevelItem();
|
||||
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
private void UpdateWindowTitle()
|
||||
{
|
||||
var title = m_ActionAssetManager.name + " (Input Actions)";
|
||||
m_Title = new GUIContent(title);
|
||||
m_DirtyTitle = new GUIContent("(*) " + m_Title.text);
|
||||
titleContent = m_Title;
|
||||
}
|
||||
|
||||
private void LoadControlSchemes()
|
||||
{
|
||||
TransferControlSchemes(save: false);
|
||||
}
|
||||
|
||||
private void TransferControlSchemes(bool save)
|
||||
{
|
||||
// The easiest way to load and save control schemes is using SerializedProperties to just transfer the data
|
||||
// between the InputControlScheme array in the toolbar and the one in the asset. Doing it this way rather than
|
||||
// just overwriting the array in m_AssetManager.m_AssetObjectForEditing directly will make undo work.
|
||||
using (var editorWindowObject = new SerializedObject(this))
|
||||
using (var controlSchemesArrayPropertyInWindow = editorWindowObject.FindProperty("m_Toolbar.m_ControlSchemes"))
|
||||
using (var controlSchemesArrayPropertyInAsset = m_ActionAssetManager.serializedObject.FindProperty("m_ControlSchemes"))
|
||||
{
|
||||
Debug.Assert(controlSchemesArrayPropertyInWindow != null, $"Cannot find m_ControlSchemes in window");
|
||||
Debug.Assert(controlSchemesArrayPropertyInAsset != null, $"Cannot find m_ControlSchemes in asset");
|
||||
|
||||
if (save)
|
||||
{
|
||||
var json = controlSchemesArrayPropertyInWindow.CopyToJson();
|
||||
controlSchemesArrayPropertyInAsset.RestoreFromJson(json);
|
||||
editorWindowObject.ApplyModifiedProperties();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load.
|
||||
var json = controlSchemesArrayPropertyInAsset.CopyToJson();
|
||||
controlSchemesArrayPropertyInWindow.RestoreFromJson(json);
|
||||
editorWindowObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnControlSchemeSelectionChanged()
|
||||
{
|
||||
OnToolbarSearchChanged();
|
||||
LoadPropertiesForSelection();
|
||||
}
|
||||
|
||||
private void OnControlSchemesModified()
|
||||
{
|
||||
TransferControlSchemes(save: true);
|
||||
|
||||
// Control scheme changes may affect the search filter.
|
||||
OnToolbarSearchChanged();
|
||||
|
||||
ApplyAndReloadTrees();
|
||||
}
|
||||
|
||||
private void OnControlSchemeRenamed(string oldBindingGroup, string newBindingGroup)
|
||||
{
|
||||
InputActionSerializationHelpers.ReplaceBindingGroup(m_ActionAssetManager.serializedObject,
|
||||
oldBindingGroup, newBindingGroup);
|
||||
ApplyAndReloadTrees();
|
||||
}
|
||||
|
||||
private void OnControlSchemeDeleted(string name, string bindingGroup)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(name), "Control scheme name should not be empty");
|
||||
Debug.Assert(!string.IsNullOrEmpty(bindingGroup), "Binding group should not be empty");
|
||||
|
||||
var asset = m_ActionAssetManager.editedAsset;
|
||||
|
||||
var bindingMask = InputBinding.MaskByGroup(bindingGroup);
|
||||
var schemeHasBindings = asset.actionMaps.Any(m => m.bindings.Any(b => bindingMask.Matches(ref b)));
|
||||
if (!schemeHasBindings)
|
||||
return;
|
||||
|
||||
////FIXME: this does not delete composites that have bindings in only one control scheme
|
||||
////REVIEW: offer to do nothing and leave all bindings as is?
|
||||
var deleteBindings =
|
||||
EditorUtility.DisplayDialog("Delete Bindings?",
|
||||
$"Delete bindings for '{name}' as well? If you select 'No', the bindings will only "
|
||||
+ $"be unassigned from the '{name}' control scheme but otherwise left as is. Note that bindings "
|
||||
+ $"that are assigned to '{name}' but also to other control schemes will be left in place either way.",
|
||||
"Yes", "No");
|
||||
|
||||
InputActionSerializationHelpers.ReplaceBindingGroup(m_ActionAssetManager.serializedObject, bindingGroup, "",
|
||||
deleteOrphanedBindings: deleteBindings);
|
||||
|
||||
ApplyAndReloadTrees();
|
||||
}
|
||||
|
||||
private void InitializeTrees()
|
||||
{
|
||||
// We persist tree view states (most importantly, they contain our selection states),
|
||||
// so only create those if we don't have any yet.
|
||||
if (m_ActionMapsTreeState == null)
|
||||
m_ActionMapsTreeState = new TreeViewState();
|
||||
if (m_ActionsTreeState == null)
|
||||
m_ActionsTreeState = new TreeViewState();
|
||||
|
||||
// Create tree in middle pane showing actions and bindings. We initially
|
||||
// leave this tree empty and populate it by selecting an action map in the
|
||||
// left pane tree.
|
||||
m_ActionsTree = new InputActionTreeView(m_ActionAssetManager.serializedObject, m_ActionsTreeState)
|
||||
{
|
||||
onSelectionChanged = OnActionTreeSelectionChanged,
|
||||
onSerializedObjectModified = ApplyAndReloadTrees,
|
||||
onBindingAdded = p => InputActionSerializationHelpers.RemoveUnusedBindingGroups(p, m_Toolbar.controlSchemes),
|
||||
drawMinusButton = false,
|
||||
title = ("Actions", "A list of InputActions in the InputActionMap selected in the left pane. Also, for each InputAction, the list "
|
||||
+ "of bindings that determine the controls that can trigger the action.\n\nThe name of each action must be unique within its InputActionMap."),
|
||||
};
|
||||
|
||||
// Create tree in left pane showing action maps.
|
||||
m_ActionMapsTree = new InputActionTreeView(m_ActionAssetManager.serializedObject, m_ActionMapsTreeState)
|
||||
{
|
||||
onBuildTree = () =>
|
||||
InputActionTreeView.BuildWithJustActionMapsFromAsset(m_ActionAssetManager.serializedObject),
|
||||
onSelectionChanged = OnActionMapTreeSelectionChanged,
|
||||
onSerializedObjectModified = ApplyAndReloadTrees,
|
||||
onHandleAddNewAction = m_ActionsTree.AddNewAction,
|
||||
drawMinusButton = false,
|
||||
title = ("Action Maps", "A list of InputActionMaps in the asset. Each map can be enabled and disabled separately at runtime and holds "
|
||||
+ "its own collection of InputActions which are listed in the middle pane (along with their InputBindings).")
|
||||
};
|
||||
m_ActionMapsTree.Reload();
|
||||
m_ActionMapsTree.ExpandAll();
|
||||
|
||||
RebuildActionTree();
|
||||
LoadPropertiesForSelection();
|
||||
|
||||
// Sync current search status in toolbar.
|
||||
OnToolbarSearchChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize the search filter applied to the trees.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that only filter the action tree. The action map tree remains unfiltered.
|
||||
/// </remarks>
|
||||
private void OnToolbarSearchChanged()
|
||||
{
|
||||
// Rather than adding FilterCriterion instances directly, we go through the
|
||||
// string-based format here. This allows typing queries directly into the search bar.
|
||||
|
||||
var searchStringBuffer = new StringBuilder();
|
||||
|
||||
// Plain-text search.
|
||||
if (!string.IsNullOrEmpty(m_Toolbar.searchText))
|
||||
searchStringBuffer.Append(m_Toolbar.searchText);
|
||||
|
||||
// Filter by binding group of selected control scheme.
|
||||
if (m_Toolbar.selectedControlScheme != null)
|
||||
{
|
||||
searchStringBuffer.Append(" \"");
|
||||
searchStringBuffer.Append(InputActionTreeView.FilterCriterion.k_BindingGroupTag);
|
||||
searchStringBuffer.Append(m_Toolbar.selectedControlScheme.Value.bindingGroup);
|
||||
searchStringBuffer.Append('\"');
|
||||
}
|
||||
|
||||
// Filter by device layout.
|
||||
if (m_Toolbar.selectedDeviceRequirement != null)
|
||||
{
|
||||
searchStringBuffer.Append(" \"");
|
||||
searchStringBuffer.Append(InputActionTreeView.FilterCriterion.k_DeviceLayoutTag);
|
||||
searchStringBuffer.Append(InputControlPath.TryGetDeviceLayout(m_Toolbar.selectedDeviceRequirement.Value.controlPath));
|
||||
searchStringBuffer.Append('\"');
|
||||
}
|
||||
|
||||
var searchString = searchStringBuffer.ToString();
|
||||
if (string.IsNullOrEmpty(searchString))
|
||||
m_ActionsTree.ClearItemSearchFilterAndReload();
|
||||
else
|
||||
m_ActionsTree.SetItemSearchFilterAndReload(searchStringBuffer.ToString());
|
||||
|
||||
// Have trees create new bindings with the right binding group.
|
||||
var currentBindingGroup = m_Toolbar.selectedControlScheme?.bindingGroup;
|
||||
m_ActionsTree.bindingGroupForNewBindings = currentBindingGroup;
|
||||
m_ActionMapsTree.bindingGroupForNewBindings = currentBindingGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize the display state to the currently selected action map.
|
||||
/// </summary>
|
||||
private void OnActionMapTreeSelectionChanged()
|
||||
{
|
||||
// Re-configure action tree (middle pane) for currently select action map.
|
||||
RebuildActionTree();
|
||||
|
||||
// If there's no actions in the selected action map or if there is no action map
|
||||
// selected, make sure we wipe the property pane.
|
||||
if (!m_ActionMapsTree.HasSelection() || !m_ActionsTree.rootItem.hasChildren)
|
||||
{
|
||||
LoadPropertiesForSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise select first action in map.
|
||||
m_ActionsTree.SelectFirstToplevelItem();
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildActionTree()
|
||||
{
|
||||
var selectedActionMapItem =
|
||||
m_ActionMapsTree.GetSelectedItems().OfType<ActionMapTreeItem>().FirstOrDefault();
|
||||
if (selectedActionMapItem == null)
|
||||
{
|
||||
// Nothing selected. Wipe middle and right pane.
|
||||
m_ActionsTree.onBuildTree = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ActionsTree.onBuildTree = () =>
|
||||
InputActionTreeView.BuildWithJustActionsAndBindingsFromMap(selectedActionMapItem.property);
|
||||
}
|
||||
|
||||
// Rebuild tree.
|
||||
m_ActionsTree.Reload();
|
||||
}
|
||||
|
||||
private void OnActionTreeSelectionChanged()
|
||||
{
|
||||
LoadPropertiesForSelection();
|
||||
}
|
||||
|
||||
private void LoadPropertiesForSelection()
|
||||
{
|
||||
m_BindingPropertyView = null;
|
||||
m_ActionPropertyView = null;
|
||||
|
||||
////TODO: preserve interaction/processor selection when reloading
|
||||
|
||||
// Nothing else to do if we don't have a selection in the middle pane or if
|
||||
// multiple items are selected (we don't currently have the ability to multi-edit).
|
||||
if (!m_ActionsTree.HasSelection() || m_ActionsTree.GetSelection().Count != 1)
|
||||
return;
|
||||
|
||||
var item = m_ActionsTree.GetSelectedItems().FirstOrDefault();
|
||||
if (item is BindingTreeItem)
|
||||
{
|
||||
// Grab the action for the binding and see if we have an expected control layout
|
||||
// set on it. Pass that on to the control picking machinery.
|
||||
var isCompositePartBinding = item is PartOfCompositeBindingTreeItem;
|
||||
var actionItem = (isCompositePartBinding ? item.parent.parent : item.parent) as ActionTreeItem;
|
||||
Debug.Assert(actionItem != null);
|
||||
|
||||
if (m_ControlPickerViewState == null)
|
||||
m_ControlPickerViewState = new InputControlPickerState();
|
||||
|
||||
// The toolbar may constrain the set of devices we're currently interested in by either
|
||||
// having one specific device selected from the current scheme or having at least a control
|
||||
// scheme selected.
|
||||
IEnumerable<string> controlPathsToMatch;
|
||||
if (m_Toolbar.selectedDeviceRequirement != null)
|
||||
{
|
||||
// Single device selected from set of devices in control scheme.
|
||||
controlPathsToMatch = new[] {m_Toolbar.selectedDeviceRequirement.Value.controlPath};
|
||||
}
|
||||
else if (m_Toolbar.selectedControlScheme != null)
|
||||
{
|
||||
// Constrain to devices from current control scheme.
|
||||
controlPathsToMatch =
|
||||
m_Toolbar.selectedControlScheme.Value.deviceRequirements.Select(x => x.controlPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there's no device filter coming from a control scheme, filter by supported
|
||||
// devices as given by settings.
|
||||
controlPathsToMatch = InputSystem.settings.supportedDevices.Select(x => $"<{x}>");
|
||||
}
|
||||
|
||||
// Show properties for binding.
|
||||
m_BindingPropertyView =
|
||||
new InputBindingPropertiesView(
|
||||
item.property,
|
||||
change =>
|
||||
{
|
||||
if (change == InputBindingPropertiesView.k_PathChanged ||
|
||||
change == InputBindingPropertiesView.k_CompositePartAssignmentChanged ||
|
||||
change == InputBindingPropertiesView.k_CompositeTypeChanged ||
|
||||
change == InputBindingPropertiesView.k_GroupsChanged)
|
||||
{
|
||||
ApplyAndReloadTrees();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simple property change that doesn't affect the rest of the UI.
|
||||
Apply();
|
||||
}
|
||||
},
|
||||
m_ControlPickerViewState,
|
||||
expectedControlLayout: item.expectedControlLayout,
|
||||
controlSchemes: m_Toolbar.controlSchemes,
|
||||
controlPathsToMatch: controlPathsToMatch);
|
||||
}
|
||||
else if (item is ActionTreeItem actionItem)
|
||||
{
|
||||
// Show properties for action.
|
||||
m_ActionPropertyView =
|
||||
new InputActionPropertiesView(
|
||||
actionItem.property,
|
||||
// Apply without reload is enough here as modifying the properties of an action will
|
||||
// never change the structure of the data.
|
||||
change => Apply());
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyAndReloadTrees()
|
||||
{
|
||||
Apply();
|
||||
|
||||
// This path here is meant to catch *any* edits made to the serialized data. I.e. also
|
||||
// any arbitrary undo that may have changed some misc bit not visible in the trees.
|
||||
|
||||
m_ActionMapsTree.Reload();
|
||||
RebuildActionTree();
|
||||
m_ActionAssetManager.UpdateAssetDirtyState();
|
||||
LoadControlSchemes();
|
||||
|
||||
LoadPropertiesForSelection();
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_INPUT_ACTIONS_EDITOR_AUTO_SAVE_ON_FOCUS_LOST
|
||||
private void OnLostFocus()
|
||||
{
|
||||
if (InputEditorUserSettings.autoSaveInputActionAssets)
|
||||
m_ActionAssetManager.SaveChangesToAsset();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private void Apply()
|
||||
{
|
||||
m_ActionAssetManager.ApplyChanges();
|
||||
|
||||
// Update dirty count, otherwise ReloadIfSerializedObjectHasBeenChanged will trigger a full ApplyAndReloadTrees
|
||||
m_ActionMapsTree.UpdateSerializedObjectDirtyCount();
|
||||
m_ActionsTree.UpdateSerializedObjectDirtyCount();
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_INPUT_ACTIONS_EDITOR_AUTO_SAVE_ON_FOCUS_LOST
|
||||
// If auto-save should be triggered on focus lost, only mark asset as dirty
|
||||
m_ActionAssetManager.MarkDirty();
|
||||
titleContent = m_DirtyTitle;
|
||||
#else
|
||||
// If auto-save is active, immediately flush out the changes to disk. Otherwise just
|
||||
// put us into dirty state.
|
||||
if (InputEditorUserSettings.autoSaveInputActionAssets)
|
||||
{
|
||||
m_ActionAssetManager.SaveChangesToAsset();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ActionAssetManager.MarkDirty();
|
||||
titleContent = m_DirtyTitle;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
// If the actions tree has lost the filters (because they would not match an item it tried to highlight),
|
||||
// update the Toolbar UI to remove them.
|
||||
if (!m_ActionsTree.hasFilter)
|
||||
m_Toolbar.ResetSearchFilters();
|
||||
|
||||
// Allow switching between action map tree and action tree using arrow keys.
|
||||
ToggleFocusUsingKeyboard(KeyCode.RightArrow, m_ActionMapsTree, m_ActionsTree);
|
||||
ToggleFocusUsingKeyboard(KeyCode.LeftArrow, m_ActionsTree, m_ActionMapsTree);
|
||||
|
||||
// Route copy-paste events to tree views if they have focus.
|
||||
if (m_ActionsTree.HasFocus())
|
||||
m_ActionsTree.HandleCopyPasteCommandEvent(Event.current);
|
||||
else if (m_ActionMapsTree.HasFocus())
|
||||
m_ActionMapsTree.HandleCopyPasteCommandEvent(Event.current);
|
||||
|
||||
// Draw toolbar.
|
||||
EditorGUILayout.BeginVertical();
|
||||
m_Toolbar.OnGUI();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Draw columns.
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
var columnAreaWidth = position.width - InputActionTreeView.Styles.backgroundWithBorder.margin.left -
|
||||
InputActionTreeView.Styles.backgroundWithBorder.margin.left -
|
||||
InputActionTreeView.Styles.backgroundWithBorder.margin.right;
|
||||
|
||||
var oldType = Event.current.type;
|
||||
DrawActionMapsColumn(columnAreaWidth * 0.22f);
|
||||
if (Event.current.type == EventType.Used && oldType != Event.current.type)
|
||||
{
|
||||
// When renaming an item, TreeViews will capture all mouse Events, and process any clicks outside the item
|
||||
// being renamed to end the renaming process. However, since we have two TreeViews, if the action column is
|
||||
// renaming an item, and then you double click on an item in the action map column, the action map column will
|
||||
// get to use the mouse event before the action collumn gets to see it, which would cause the action map column
|
||||
// to enter rename mode and use the event, before the action column gets a chance to see it and exit rename mode.
|
||||
// Then we end up with two active renaming sessions, which does not work correctly.
|
||||
// (See https://fogbugz.unity3d.com/f/cases/1140869/).
|
||||
// Now, our fix to this problem is to force-end and accept any renaming session on the action column if we see
|
||||
// that the action map column had processed the current event. This is not particularly elegant, but I cannot think
|
||||
// of a better solution as we are limited by the public APIs exposed by TreeView.
|
||||
m_ActionsTree.EndRename(forceAccept: true);
|
||||
}
|
||||
DrawActionsColumn(columnAreaWidth * 0.38f);
|
||||
DrawPropertiesColumn(columnAreaWidth * 0.40f);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// Bottom margin.
|
||||
GUILayout.Space(3);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private static void ToggleFocusUsingKeyboard(KeyCode key, InputActionTreeView fromTree,
|
||||
InputActionTreeView toTree)
|
||||
{
|
||||
var uiEvent = Event.current;
|
||||
if (uiEvent.type == EventType.KeyDown && uiEvent.keyCode == key && fromTree.HasFocus())
|
||||
{
|
||||
if (!toTree.HasSelection())
|
||||
toTree.SelectFirstToplevelItem();
|
||||
toTree.SetFocus();
|
||||
uiEvent.Use();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawActionMapsColumn(float width)
|
||||
{
|
||||
DrawColumnWithTreeView(m_ActionMapsTree, width, true);
|
||||
}
|
||||
|
||||
private void DrawActionsColumn(float width)
|
||||
{
|
||||
DrawColumnWithTreeView(m_ActionsTree, width, false);
|
||||
}
|
||||
|
||||
private static void DrawColumnWithTreeView(TreeView treeView, float width, bool fixedWidth)
|
||||
{
|
||||
EditorGUILayout.BeginVertical(InputActionTreeView.Styles.backgroundWithBorder,
|
||||
fixedWidth ? GUILayout.MaxWidth(width) : GUILayout.MinWidth(width),
|
||||
GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
var columnRect = GUILayoutUtility.GetLastRect();
|
||||
|
||||
treeView.OnGUI(columnRect);
|
||||
}
|
||||
|
||||
private void DrawPropertiesColumn(float width)
|
||||
{
|
||||
EditorGUILayout.BeginVertical(InputActionTreeView.Styles.backgroundWithBorder, GUILayout.Width(width));
|
||||
|
||||
var rect = GUILayoutUtility.GetRect(0,
|
||||
EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 2,
|
||||
GUILayout.ExpandWidth(true));
|
||||
rect.x -= 2;
|
||||
rect.y -= 1;
|
||||
rect.width += 4;
|
||||
|
||||
EditorGUI.LabelField(rect, GUIContent.none, InputActionTreeView.Styles.backgroundWithBorder);
|
||||
var headerRect = new Rect(rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2);
|
||||
|
||||
if (m_BindingPropertyView != null)
|
||||
{
|
||||
if (m_BindingPropertiesTitle == null)
|
||||
m_BindingPropertiesTitle = new GUIContent("Binding Properties", "The properties for the InputBinding selected in the "
|
||||
+ "'Actions' pane on the left.");
|
||||
EditorGUI.LabelField(headerRect, m_BindingPropertiesTitle, InputActionTreeView.Styles.columnHeaderLabel);
|
||||
m_PropertiesScroll = EditorGUILayout.BeginScrollView(m_PropertiesScroll);
|
||||
m_BindingPropertyView.OnGUI();
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
else if (m_ActionPropertyView != null)
|
||||
{
|
||||
if (m_ActionPropertiesTitle == null)
|
||||
m_ActionPropertiesTitle = new GUIContent("Action Properties", "The properties for the InputAction selected in the "
|
||||
+ "'Actions' pane on the left.");
|
||||
EditorGUI.LabelField(headerRect, m_ActionPropertiesTitle, InputActionTreeView.Styles.columnHeaderLabel);
|
||||
m_PropertiesScroll = EditorGUILayout.BeginScrollView(m_PropertiesScroll);
|
||||
m_ActionPropertyView.OnGUI();
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void ReloadAssetFromFileIfNotDirty()
|
||||
{
|
||||
if (m_ActionAssetManager.dirty)
|
||||
return;
|
||||
|
||||
// If our asset has disappeared from disk, just close the window.
|
||||
if (string.IsNullOrEmpty(AssetDatabase.GUIDToAssetPath(m_ActionAssetManager.guid)))
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't touch the UI state if the serialized data is still the same.
|
||||
if (!m_ActionAssetManager.ReInitializeIfAssetHasChanged())
|
||||
return;
|
||||
|
||||
// Unfortunately, on this path we lose the selection state of the interactions and processors lists
|
||||
// in the properties view.
|
||||
|
||||
InitializeTrees();
|
||||
LoadPropertiesForSelection();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
#if !UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
////TODO: add shortcut to focus search box
|
||||
|
||||
////TODO: show shortcuts in tooltips
|
||||
////FIXME: the shortcuts seem to have focus problems; often requires clicking away and then back to the window
|
||||
[Shortcut("Input Action Editor/Save", typeof(InputActionEditorWindow), KeyCode.S, ShortcutModifiers.Alt)]
|
||||
private static void SaveShortcut(ShortcutArguments arguments)
|
||||
{
|
||||
var window = (InputActionEditorWindow)arguments.context;
|
||||
window.SaveChangesToAsset();
|
||||
}
|
||||
|
||||
[Shortcut("Input Action Editor/Add Action Map", typeof(InputActionEditorWindow), KeyCode.M, ShortcutModifiers.Alt)]
|
||||
private static void AddActionMapShortcut(ShortcutArguments arguments)
|
||||
{
|
||||
var window = (InputActionEditorWindow)arguments.context;
|
||||
window.AddNewActionMap();
|
||||
}
|
||||
|
||||
[Shortcut("Input Action Editor/Add Action", typeof(InputActionEditorWindow), KeyCode.A, ShortcutModifiers.Alt)]
|
||||
private static void AddActionShortcut(ShortcutArguments arguments)
|
||||
{
|
||||
var window = (InputActionEditorWindow)arguments.context;
|
||||
window.AddNewAction();
|
||||
}
|
||||
|
||||
[Shortcut("Input Action Editor/Add Binding", typeof(InputActionEditorWindow), KeyCode.B, ShortcutModifiers.Alt)]
|
||||
private static void AddBindingShortcut(ShortcutArguments arguments)
|
||||
{
|
||||
var window = (InputActionEditorWindow)arguments.context;
|
||||
window.AddNewBinding();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private void OnDirtyChanged(bool dirty)
|
||||
{
|
||||
titleContent = dirty ? m_DirtyTitle : m_Title;
|
||||
m_Toolbar.isDirty = dirty;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_BindingPropertyView?.Dispose();
|
||||
}
|
||||
|
||||
[SerializeField] private TreeViewState m_ActionMapsTreeState;
|
||||
[SerializeField] private TreeViewState m_ActionsTreeState;
|
||||
[SerializeField] private InputControlPickerState m_ControlPickerViewState;
|
||||
[SerializeField] private InputActionAssetManager m_ActionAssetManager;
|
||||
[SerializeField] private InputActionEditorToolbar m_Toolbar;
|
||||
[SerializeField] private GUIContent m_DirtyTitle;
|
||||
[SerializeField] private GUIContent m_Title;
|
||||
[NonSerialized] private GUIContent m_ActionPropertiesTitle;
|
||||
[NonSerialized] private GUIContent m_BindingPropertiesTitle;
|
||||
|
||||
private InputBindingPropertiesView m_BindingPropertyView;
|
||||
private InputActionPropertiesView m_ActionPropertyView;
|
||||
private InputActionTreeView m_ActionMapsTree;
|
||||
private InputActionTreeView m_ActionsTree;
|
||||
|
||||
private static bool s_RefreshPending;
|
||||
|
||||
private Vector2 m_PropertiesScroll;
|
||||
private bool m_ForceQuit;
|
||||
|
||||
#region IInputActionAssetEditor
|
||||
|
||||
public void OnAssetImported() => ReloadAssetFromFileIfNotDirty();
|
||||
|
||||
public void OnAssetMoved() => UpdateWindowTitle();
|
||||
|
||||
public void OnAssetDeleted()
|
||||
{
|
||||
m_ForceQuit = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
public string assetGUID => m_ActionAssetManager.guid;
|
||||
public bool isDirty => m_ActionAssetManager.dirty;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb55afc2c109f42f2af37941a8c9300e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,130 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// UI that edits the properties of an <see cref="InputAction"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Right-most pane in <see cref="InputActionEditorWindow"/> when an action is selected.
|
||||
/// </remarks>
|
||||
internal class InputActionPropertiesView : PropertiesViewBase
|
||||
{
|
||||
public static FourCC k_PropertiesChanged => new FourCC("PROP");
|
||||
|
||||
public InputActionPropertiesView(SerializedProperty actionProperty, Action<FourCC> onChange = null)
|
||||
: base("Action", actionProperty, onChange, actionProperty.FindPropertyRelative("m_ExpectedControlType").stringValue)
|
||||
{
|
||||
m_ExpectedControlTypeProperty = actionProperty.FindPropertyRelative(nameof(InputAction.m_ExpectedControlType));
|
||||
m_ActionTypeProperty = actionProperty.FindPropertyRelative(nameof(InputAction.m_Type));
|
||||
m_ActionFlagsProperty = actionProperty.FindPropertyRelative(nameof(InputAction.m_Flags));
|
||||
|
||||
m_SelectedActionType = (InputActionType)m_ActionTypeProperty.intValue;
|
||||
m_WantsInitialStateCheck = (m_ActionFlagsProperty.intValue & (int)InputAction.ActionFlags.WantsInitialStateCheck) != 0;
|
||||
|
||||
BuildControlTypeList();
|
||||
m_SelectedControlType = Array.IndexOf(m_ControlTypeList, m_ExpectedControlTypeProperty.stringValue);
|
||||
if (m_SelectedControlType == -1)
|
||||
m_SelectedControlType = 0;
|
||||
|
||||
if (s_ControlTypeLabel == null)
|
||||
s_ControlTypeLabel = EditorGUIUtility.TrTextContent("Control Type", m_ExpectedControlTypeProperty.GetTooltip());
|
||||
if (s_ActionTypeLabel == null)
|
||||
s_ActionTypeLabel = EditorGUIUtility.TrTextContent("Action Type", m_ActionTypeProperty.GetTooltip());
|
||||
if (s_WantsInitialStateCheckLabel == null)
|
||||
s_WantsInitialStateCheckLabel = EditorGUIUtility.TrTextContent("Initial State Check",
|
||||
"Whether in the next input update after the action was enabled, the action should "
|
||||
+ "immediately trigger if any of its bound controls are currently in a non-default state. "
|
||||
+ "This check happens implicitly for Value actions but can be explicitly enabled for Button and Pass-Through actions.");
|
||||
}
|
||||
|
||||
protected override void DrawGeneralProperties()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
m_SelectedActionType = EditorGUILayout.EnumPopup(s_ActionTypeLabel, m_SelectedActionType);
|
||||
if ((InputActionType)m_SelectedActionType != InputActionType.Button)
|
||||
m_SelectedControlType = EditorGUILayout.Popup(s_ControlTypeLabel, m_SelectedControlType, m_ControlTypeOptions);
|
||||
|
||||
if ((InputActionType)m_SelectedActionType != InputActionType.Value)
|
||||
m_WantsInitialStateCheck = EditorGUILayout.Toggle(s_WantsInitialStateCheckLabel, m_WantsInitialStateCheck);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if ((InputActionType)m_SelectedActionType == InputActionType.Button)
|
||||
m_ExpectedControlTypeProperty.stringValue = "Button";
|
||||
else if (m_SelectedControlType == 0)
|
||||
m_ExpectedControlTypeProperty.stringValue = string.Empty;
|
||||
else
|
||||
m_ExpectedControlTypeProperty.stringValue = m_ControlTypeList[m_SelectedControlType];
|
||||
|
||||
m_ActionTypeProperty.intValue = (int)(InputActionType)m_SelectedActionType;
|
||||
|
||||
if (m_WantsInitialStateCheck)
|
||||
m_ActionFlagsProperty.intValue |= (int)InputAction.ActionFlags.WantsInitialStateCheck;
|
||||
else
|
||||
m_ActionFlagsProperty.intValue &= ~(int)InputAction.ActionFlags.WantsInitialStateCheck;
|
||||
|
||||
m_ActionTypeProperty.serializedObject.ApplyModifiedProperties();
|
||||
UpdateProcessors(m_ExpectedControlTypeProperty.stringValue);
|
||||
|
||||
onChange(k_PropertiesChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildControlTypeList()
|
||||
{
|
||||
var types = new List<string>();
|
||||
var allLayouts = InputSystem.s_Manager.m_Layouts;
|
||||
foreach (var layoutName in allLayouts.layoutTypes.Keys)
|
||||
{
|
||||
if (EditorInputControlLayoutCache.TryGetLayout(layoutName).hideInUI)
|
||||
continue;
|
||||
|
||||
// If the action type is InputActionType.Value, skip button controls.
|
||||
var type = allLayouts.layoutTypes[layoutName];
|
||||
if ((InputActionType)m_SelectedActionType == InputActionType.Value &&
|
||||
typeof(ButtonControl).IsAssignableFrom(type))
|
||||
continue;
|
||||
|
||||
////TODO: skip aliases
|
||||
|
||||
if (typeof(InputControl).IsAssignableFrom(type) &&
|
||||
!typeof(InputDevice).IsAssignableFrom(type))
|
||||
{
|
||||
types.Add(layoutName);
|
||||
}
|
||||
}
|
||||
// Sort alphabetically.
|
||||
types.Sort((a, b) => string.Compare(a, b, StringComparison.OrdinalIgnoreCase));
|
||||
// Make sure "Any" is always topmost entry.
|
||||
types.Insert(0, "Any");
|
||||
|
||||
m_ControlTypeList = types.ToArray();
|
||||
m_ControlTypeOptions = m_ControlTypeList.Select(x => new GUIContent(ObjectNames.NicifyVariableName(x)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private readonly SerializedProperty m_ExpectedControlTypeProperty;
|
||||
private readonly SerializedProperty m_ActionTypeProperty;
|
||||
private readonly SerializedProperty m_ActionFlagsProperty;
|
||||
|
||||
private string m_ExpectedControlLayout;
|
||||
private string[] m_ControlTypeList;
|
||||
private GUIContent[] m_ControlTypeOptions;
|
||||
private int m_SelectedControlType;
|
||||
private Enum m_SelectedActionType;
|
||||
private bool m_WantsInitialStateCheck;
|
||||
|
||||
private static GUIContent s_ActionTypeLabel;
|
||||
private static GUIContent s_ControlTypeLabel;
|
||||
private static GUIContent s_WantsInitialStateCheckLabel;
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d295e36fd3cd747f4a2123088241bd6a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 713b035f669b942adbd1cc05c4fb6e1d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,565 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////TODO: sync expanded state of SerializedProperties to expanded state of tree (will help preserving expansion in inspector)
|
||||
|
||||
////REVIEW: would be great to align all "[device]" parts of binding strings neatly in a column
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
internal abstract class ActionTreeItemBase : TreeViewItem
|
||||
{
|
||||
public SerializedProperty property { get; }
|
||||
public virtual string expectedControlLayout => string.Empty;
|
||||
public virtual bool canRename => true;
|
||||
public virtual bool serializedDataIncludesChildren => false;
|
||||
public abstract GUIStyle colorTagStyle { get; }
|
||||
public string name { get; }
|
||||
public Guid guid { get; }
|
||||
public virtual bool showWarningIcon => false;
|
||||
|
||||
// For some operations (like copy-paste), we want to include information that we have filtered out.
|
||||
internal List<ActionTreeItemBase> m_HiddenChildren;
|
||||
public bool hasChildrenIncludingHidden => hasChildren || (m_HiddenChildren != null && m_HiddenChildren.Count > 0);
|
||||
public IEnumerable<ActionTreeItemBase> hiddenChildren => m_HiddenChildren ?? Enumerable.Empty<ActionTreeItemBase>();
|
||||
public IEnumerable<ActionTreeItemBase> childrenIncludingHidden
|
||||
{
|
||||
get
|
||||
{
|
||||
if (hasChildren)
|
||||
foreach (var child in children)
|
||||
if (child is ActionTreeItemBase item)
|
||||
yield return item;
|
||||
if (m_HiddenChildren != null)
|
||||
foreach (var child in m_HiddenChildren)
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
|
||||
// Action data is generally stored in arrays. Action maps are stored in m_ActionMaps arrays in assets,
|
||||
// actions are stored in m_Actions arrays on maps and bindings are stored in m_Bindings arrays on maps.
|
||||
public SerializedProperty arrayProperty => property.GetArrayPropertyFromElement();
|
||||
|
||||
// Dynamically look up the array index instead of just taking it from `property`.
|
||||
// This makes sure whatever insertions or deletions we perform on the serialized data,
|
||||
// we get the right array index from an item.
|
||||
public int arrayIndex => InputActionSerializationHelpers.GetIndex(arrayProperty, guid);
|
||||
|
||||
protected ActionTreeItemBase(SerializedProperty property)
|
||||
{
|
||||
this.property = property;
|
||||
|
||||
// Look up name.
|
||||
var nameProperty = property.FindPropertyRelative("m_Name");
|
||||
Debug.Assert(nameProperty != null, $"Cannot find m_Name property on {property.propertyPath}");
|
||||
name = nameProperty.stringValue;
|
||||
|
||||
// Look up ID.
|
||||
var idProperty = property.FindPropertyRelative("m_Id");
|
||||
Debug.Assert(idProperty != null, $"Cannot find m_Id property on {property.propertyPath}");
|
||||
var idPropertyString = idProperty.stringValue;
|
||||
if (string.IsNullOrEmpty(idPropertyString))
|
||||
{
|
||||
// This is somewhat questionable but we can't operate if we don't have IDs on the data used in the tree.
|
||||
// Rather than requiring users of the tree to set this up consistently, we assign IDs
|
||||
// on the fly, if necessary.
|
||||
guid = Guid.NewGuid();
|
||||
idPropertyString = guid.ToString();
|
||||
idProperty.stringValue = idPropertyString;
|
||||
idProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
else
|
||||
{
|
||||
guid = new Guid(idPropertyString);
|
||||
}
|
||||
|
||||
// All our elements (maps, actions, bindings) carry unique IDs. We use their hash
|
||||
// codes as item IDs in the tree. This should result in stable item IDs that keep
|
||||
// identifying the right item across all reloads and tree mutations.
|
||||
id = guid.GetHashCode();
|
||||
}
|
||||
|
||||
public virtual void Rename(string newName)
|
||||
{
|
||||
Debug.Assert(!canRename, "Item is marked as allowing renames yet does not implement Rename()");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete serialized data for the tree item and its children.
|
||||
/// </summary>
|
||||
public abstract void DeleteData();
|
||||
|
||||
public abstract bool AcceptsDrop(ActionTreeItemBase item);
|
||||
|
||||
/// <summary>
|
||||
/// Get information about where to drop an item of the given type and (optionally) the given index.
|
||||
/// </summary>
|
||||
public abstract bool GetDropLocation(Type itemType, int? childIndex, ref SerializedProperty array, ref int arrayIndex);
|
||||
|
||||
protected static class Styles
|
||||
{
|
||||
private static GUIStyle StyleWithBackground(string fileName)
|
||||
{
|
||||
return new GUIStyle("Label").WithNormalBackground(AssetDatabase.LoadAssetAtPath<Texture2D>($"{InputActionTreeView.SharedResourcesPath}{fileName}.png"));
|
||||
}
|
||||
|
||||
public static readonly GUIStyle yellowRect = StyleWithBackground("yellow");
|
||||
public static readonly GUIStyle greenRect = StyleWithBackground("green");
|
||||
public static readonly GUIStyle blueRect = StyleWithBackground("blue");
|
||||
public static readonly GUIStyle pinkRect = StyleWithBackground("pink");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tree view item for an action map.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputActionMap"/>
|
||||
internal class ActionMapTreeItem : ActionTreeItemBase
|
||||
{
|
||||
public ActionMapTreeItem(SerializedProperty actionMapProperty)
|
||||
: base(actionMapProperty)
|
||||
{
|
||||
}
|
||||
|
||||
public override GUIStyle colorTagStyle => Styles.yellowRect;
|
||||
public SerializedProperty bindingsArrayProperty => property.FindPropertyRelative("m_Bindings");
|
||||
public SerializedProperty actionsArrayProperty => property.FindPropertyRelative("m_Actions");
|
||||
public override bool serializedDataIncludesChildren => true;
|
||||
|
||||
public override void Rename(string newName)
|
||||
{
|
||||
InputActionSerializationHelpers.RenameActionMap(property, newName);
|
||||
}
|
||||
|
||||
public override void DeleteData()
|
||||
{
|
||||
var assetObject = property.serializedObject;
|
||||
if (!(assetObject.targetObject is InputActionAsset))
|
||||
throw new InvalidOperationException(
|
||||
$"Action map must be part of InputActionAsset but is in {assetObject.targetObject} instead");
|
||||
|
||||
InputActionSerializationHelpers.DeleteActionMap(assetObject, guid);
|
||||
}
|
||||
|
||||
public override bool AcceptsDrop(ActionTreeItemBase item)
|
||||
{
|
||||
return item is ActionTreeItem;
|
||||
}
|
||||
|
||||
public override bool GetDropLocation(Type itemType, int? childIndex, ref SerializedProperty array, ref int arrayIndex)
|
||||
{
|
||||
// Drop actions into action array.
|
||||
if (itemType == typeof(ActionTreeItem))
|
||||
{
|
||||
array = actionsArrayProperty;
|
||||
arrayIndex = childIndex ?? -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// For action maps in assets, drop other action maps next to them.
|
||||
if (itemType == typeof(ActionMapTreeItem) && property.serializedObject.targetObject is InputActionAsset)
|
||||
{
|
||||
array = property.GetArrayPropertyFromElement();
|
||||
arrayIndex = this.arrayIndex + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
////REVIEW: would be nice to be able to replace the entire contents of a map in the inspector by dropping in another map
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ActionMapTreeItem AddTo(TreeViewItem parent, SerializedProperty actionMapProperty)
|
||||
{
|
||||
var item = new ActionMapTreeItem(actionMapProperty);
|
||||
|
||||
item.depth = parent.depth + 1;
|
||||
item.displayName = item.name;
|
||||
parent.AddChild(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public void AddActionsTo(TreeViewItem parent)
|
||||
{
|
||||
AddActionsTo(parent, addBindings: false);
|
||||
}
|
||||
|
||||
public void AddActionsAndBindingsTo(TreeViewItem parent)
|
||||
{
|
||||
AddActionsTo(parent, addBindings: true);
|
||||
}
|
||||
|
||||
private void AddActionsTo(TreeViewItem parent, bool addBindings)
|
||||
{
|
||||
var actionsArrayProperty = this.actionsArrayProperty;
|
||||
Debug.Assert(actionsArrayProperty != null, $"Cannot find m_Actions in {property}");
|
||||
|
||||
for (var i = 0; i < actionsArrayProperty.arraySize; i++)
|
||||
{
|
||||
var actionProperty = actionsArrayProperty.GetArrayElementAtIndex(i);
|
||||
var actionItem = ActionTreeItem.AddTo(parent, property, actionProperty);
|
||||
|
||||
if (addBindings)
|
||||
actionItem.AddBindingsTo(actionItem);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddActionMapsFromAssetTo(TreeViewItem parent, SerializedObject assetObject)
|
||||
{
|
||||
var actionMapsArrayProperty = assetObject.FindProperty("m_ActionMaps");
|
||||
Debug.Assert(actionMapsArrayProperty != null, $"Cannot find m_ActionMaps in {assetObject}");
|
||||
Debug.Assert(actionMapsArrayProperty.isArray, $"m_ActionMaps in {assetObject} is not an array");
|
||||
|
||||
var mapCount = actionMapsArrayProperty.arraySize;
|
||||
for (var i = 0; i < mapCount; ++i)
|
||||
{
|
||||
var mapProperty = actionMapsArrayProperty.GetArrayElementAtIndex(i);
|
||||
AddTo(parent, mapProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tree view item for an action.
|
||||
/// </summary>
|
||||
/// <see cref="InputAction"/>
|
||||
internal class ActionTreeItem : ActionTreeItemBase
|
||||
{
|
||||
public ActionTreeItem(SerializedProperty actionMapProperty, SerializedProperty actionProperty)
|
||||
: base(actionProperty)
|
||||
{
|
||||
this.actionMapProperty = actionMapProperty;
|
||||
}
|
||||
|
||||
public SerializedProperty actionMapProperty { get; }
|
||||
public override GUIStyle colorTagStyle => Styles.greenRect;
|
||||
public bool isSingletonAction => actionMapProperty == null;
|
||||
|
||||
public override string expectedControlLayout
|
||||
{
|
||||
get
|
||||
{
|
||||
var expectedControlType = property.FindPropertyRelative("m_ExpectedControlType").stringValue;
|
||||
if (!string.IsNullOrEmpty(expectedControlType))
|
||||
return expectedControlType;
|
||||
|
||||
var type = property.FindPropertyRelative("m_Type").intValue;
|
||||
if (type == (int)InputActionType.Button)
|
||||
return "Button";
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public SerializedProperty bindingsArrayProperty => isSingletonAction
|
||||
? property.FindPropertyRelative("m_SingletonActionBindings")
|
||||
: actionMapProperty.FindPropertyRelative("m_Bindings");
|
||||
|
||||
// If we're a singleton action (no associated action map property), we include all our bindings in the
|
||||
// serialized data.
|
||||
public override bool serializedDataIncludesChildren => actionMapProperty == null;
|
||||
|
||||
public override void Rename(string newName)
|
||||
{
|
||||
InputActionSerializationHelpers.RenameAction(property, actionMapProperty, newName);
|
||||
}
|
||||
|
||||
public override void DeleteData()
|
||||
{
|
||||
InputActionSerializationHelpers.DeleteActionAndBindings(actionMapProperty, guid);
|
||||
}
|
||||
|
||||
public override bool AcceptsDrop(ActionTreeItemBase item)
|
||||
{
|
||||
return item is BindingTreeItem && !(item is PartOfCompositeBindingTreeItem);
|
||||
}
|
||||
|
||||
public override bool GetDropLocation(Type itemType, int? childIndex, ref SerializedProperty array, ref int arrayIndex)
|
||||
{
|
||||
// Drop bindings into binding array.
|
||||
if (typeof(BindingTreeItem).IsAssignableFrom(itemType))
|
||||
{
|
||||
array = bindingsArrayProperty;
|
||||
|
||||
// Indexing by tree items is relative to each action but indexing in
|
||||
// binding array is global for all actions in a map. Adjust index accordingly.
|
||||
// NOTE: Bindings for any one action need not be stored contiguously in the binding array
|
||||
// so we can't just add something to the index of the first binding to the action.
|
||||
arrayIndex =
|
||||
InputActionSerializationHelpers.ConvertBindingIndexOnActionToBindingIndexInArray(
|
||||
array, name, childIndex ?? -1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Drop other actions next to us.
|
||||
if (itemType == typeof(ActionTreeItem))
|
||||
{
|
||||
array = arrayProperty;
|
||||
arrayIndex = this.arrayIndex + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ActionTreeItem AddTo(TreeViewItem parent, SerializedProperty actionMapProperty, SerializedProperty actionProperty)
|
||||
{
|
||||
var item = new ActionTreeItem(actionMapProperty, actionProperty);
|
||||
|
||||
item.depth = parent.depth + 1;
|
||||
item.displayName = item.name;
|
||||
parent.AddChild(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add items for the bindings of just this action to the given parent tree item.
|
||||
/// </summary>
|
||||
public void AddBindingsTo(TreeViewItem parent)
|
||||
{
|
||||
var isSingleton = actionMapProperty == null;
|
||||
var bindingsArrayProperty = isSingleton
|
||||
? property.FindPropertyRelative("m_SingletonActionBindings")
|
||||
: actionMapProperty.FindPropertyRelative("m_Bindings");
|
||||
|
||||
var bindingsCountInMap = bindingsArrayProperty.arraySize;
|
||||
var currentComposite = (CompositeBindingTreeItem)null;
|
||||
for (var i = 0; i < bindingsCountInMap; ++i)
|
||||
{
|
||||
var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i);
|
||||
|
||||
// Skip if binding is not for action.
|
||||
var actionProperty = bindingProperty.FindPropertyRelative("m_Action");
|
||||
Debug.Assert(actionProperty != null, $"Could not find m_Action in {bindingProperty}");
|
||||
if (!actionProperty.stringValue.Equals(name, StringComparison.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
// See what kind of binding we have.
|
||||
var flagsProperty = bindingProperty.FindPropertyRelative("m_Flags");
|
||||
Debug.Assert(actionProperty != null, $"Could not find m_Flags in {bindingProperty}");
|
||||
var flags = (InputBinding.Flags)flagsProperty.intValue;
|
||||
if ((flags & InputBinding.Flags.PartOfComposite) != 0 && currentComposite != null)
|
||||
{
|
||||
// Composite part binding.
|
||||
PartOfCompositeBindingTreeItem.AddTo(currentComposite, bindingProperty);
|
||||
}
|
||||
else if ((flags & InputBinding.Flags.Composite) != 0)
|
||||
{
|
||||
// Composite binding.
|
||||
currentComposite = CompositeBindingTreeItem.AddTo(parent, bindingProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
// "Normal" binding.
|
||||
BindingTreeItem.AddTo(parent, bindingProperty);
|
||||
currentComposite = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tree view item for a binding.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputBinding"/>
|
||||
internal class BindingTreeItem : ActionTreeItemBase
|
||||
{
|
||||
public BindingTreeItem(SerializedProperty bindingProperty)
|
||||
: base(bindingProperty)
|
||||
{
|
||||
path = property.FindPropertyRelative("m_Path").stringValue;
|
||||
groups = property.FindPropertyRelative("m_Groups").stringValue;
|
||||
action = property.FindPropertyRelative("m_Action").stringValue;
|
||||
}
|
||||
|
||||
public string path { get; }
|
||||
public string groups { get; }
|
||||
public string action { get; }
|
||||
public override bool showWarningIcon => InputSystem.ShouldDrawWarningIconForBinding(path);
|
||||
|
||||
public override bool canRename => false;
|
||||
public override GUIStyle colorTagStyle => Styles.blueRect;
|
||||
|
||||
public string displayPath =>
|
||||
!string.IsNullOrEmpty(path) ? InputControlPath.ToHumanReadableString(path) : "<No Binding>";
|
||||
|
||||
private ActionTreeItem actionItem
|
||||
{
|
||||
get
|
||||
{
|
||||
// Find the action we're under.
|
||||
for (var node = parent; node != null; node = node.parent)
|
||||
if (node is ActionTreeItem item)
|
||||
return item;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string expectedControlLayout
|
||||
{
|
||||
get
|
||||
{
|
||||
var currentActionItem = actionItem;
|
||||
return currentActionItem != null ? currentActionItem.expectedControlLayout : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public override void DeleteData()
|
||||
{
|
||||
var currentActionItem = actionItem;
|
||||
Debug.Assert(currentActionItem != null, "BindingTreeItem should always have a parent action");
|
||||
var bindingsArrayProperty = currentActionItem.bindingsArrayProperty;
|
||||
InputActionSerializationHelpers.DeleteBinding(bindingsArrayProperty, guid);
|
||||
}
|
||||
|
||||
public override bool AcceptsDrop(ActionTreeItemBase item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool GetDropLocation(Type itemType, int? childIndex, ref SerializedProperty array, ref int arrayIndex)
|
||||
{
|
||||
// Drop bindings next to us.
|
||||
if (typeof(BindingTreeItem).IsAssignableFrom(itemType))
|
||||
{
|
||||
array = arrayProperty;
|
||||
arrayIndex = this.arrayIndex + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static BindingTreeItem AddTo(TreeViewItem parent, SerializedProperty bindingProperty)
|
||||
{
|
||||
var item = new BindingTreeItem(bindingProperty);
|
||||
|
||||
item.depth = parent.depth + 1;
|
||||
item.displayName = item.displayPath;
|
||||
parent.AddChild(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tree view item for a composite binding.
|
||||
/// </summary>
|
||||
/// <seealso cref="InputBinding.isComposite"/>
|
||||
internal class CompositeBindingTreeItem : BindingTreeItem
|
||||
{
|
||||
public CompositeBindingTreeItem(SerializedProperty bindingProperty)
|
||||
: base(bindingProperty)
|
||||
{
|
||||
}
|
||||
|
||||
public override GUIStyle colorTagStyle => Styles.blueRect;
|
||||
public override bool canRename => true;
|
||||
|
||||
public string compositeName => NameAndParameters.ParseName(path);
|
||||
|
||||
public override void Rename(string newName)
|
||||
{
|
||||
InputActionSerializationHelpers.RenameComposite(property, newName);
|
||||
}
|
||||
|
||||
public override bool AcceptsDrop(ActionTreeItemBase item)
|
||||
{
|
||||
return item is PartOfCompositeBindingTreeItem;
|
||||
}
|
||||
|
||||
public override bool GetDropLocation(Type itemType, int? childIndex, ref SerializedProperty array, ref int arrayIndex)
|
||||
{
|
||||
// Drop part binding into composite.
|
||||
if (itemType == typeof(PartOfCompositeBindingTreeItem))
|
||||
{
|
||||
array = arrayProperty;
|
||||
|
||||
// Adjust child index by index of composite item itself.
|
||||
arrayIndex = childIndex != null
|
||||
? this.arrayIndex + 1 + childIndex.Value // Dropping at #0 should put as our index plus one.
|
||||
: this.arrayIndex + 1 + InputActionSerializationHelpers.GetCompositePartCount(array, this.arrayIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Drop other bindings next to us.
|
||||
if (typeof(BindingTreeItem).IsAssignableFrom(itemType))
|
||||
{
|
||||
array = arrayProperty;
|
||||
arrayIndex = this.arrayIndex + 1 +
|
||||
InputActionSerializationHelpers.GetCompositePartCount(array, this.arrayIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public new static CompositeBindingTreeItem AddTo(TreeViewItem parent, SerializedProperty bindingProperty)
|
||||
{
|
||||
var item = new CompositeBindingTreeItem(bindingProperty);
|
||||
|
||||
item.depth = parent.depth + 1;
|
||||
item.displayName = !string.IsNullOrEmpty(item.name)
|
||||
? item.name
|
||||
: ObjectNames.NicifyVariableName(NameAndParameters.ParseName(item.path));
|
||||
|
||||
parent.AddChild(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tree view item for bindings that are parts of composites.
|
||||
/// </summary>
|
||||
/// <see cref="InputBinding.isPartOfComposite"/>
|
||||
internal class PartOfCompositeBindingTreeItem : BindingTreeItem
|
||||
{
|
||||
public PartOfCompositeBindingTreeItem(SerializedProperty bindingProperty)
|
||||
: base(bindingProperty)
|
||||
{
|
||||
}
|
||||
|
||||
public override GUIStyle colorTagStyle => Styles.pinkRect;
|
||||
public override bool canRename => false;
|
||||
|
||||
public override string expectedControlLayout
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ExpectedControlLayout == null)
|
||||
{
|
||||
var partName = name;
|
||||
var compositeName = ((CompositeBindingTreeItem)parent).compositeName;
|
||||
var layoutName = InputBindingComposite.GetExpectedControlLayoutName(compositeName, partName);
|
||||
m_ExpectedControlLayout = layoutName ?? "";
|
||||
}
|
||||
|
||||
return m_ExpectedControlLayout;
|
||||
}
|
||||
}
|
||||
|
||||
private string m_ExpectedControlLayout;
|
||||
|
||||
public new static PartOfCompositeBindingTreeItem AddTo(TreeViewItem parent, SerializedProperty bindingProperty)
|
||||
{
|
||||
var item = new PartOfCompositeBindingTreeItem(bindingProperty);
|
||||
|
||||
item.depth = parent.depth + 1;
|
||||
item.displayName = $"{ObjectNames.NicifyVariableName(item.name)}: {item.displayPath}";
|
||||
parent.AddChild(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76d591911f4ae4f62953b13f6ff50429
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,393 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor.Lists;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////REVIEW: when we start with a blank tree view state, we should initialize the control picker to select the control currently
|
||||
//// selected by the path property
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// UI for editing properties of an <see cref="InputBinding"/>. Right-most pane in action editor when
|
||||
/// binding is selected in middle pane.
|
||||
/// </summary>
|
||||
internal class InputBindingPropertiesView : PropertiesViewBase, IDisposable
|
||||
{
|
||||
public static FourCC k_GroupsChanged => new FourCC("GRPS");
|
||||
public static FourCC k_PathChanged => new FourCC("PATH");
|
||||
public static FourCC k_CompositeTypeChanged => new FourCC("COMP");
|
||||
public static FourCC k_CompositePartAssignmentChanged => new FourCC("PART");
|
||||
|
||||
public InputBindingPropertiesView(
|
||||
SerializedProperty bindingProperty,
|
||||
Action<FourCC> onChange = null,
|
||||
InputControlPickerState controlPickerState = null,
|
||||
string expectedControlLayout = null,
|
||||
ReadOnlyArray<InputControlScheme> controlSchemes = new ReadOnlyArray<InputControlScheme>(),
|
||||
IEnumerable<string> controlPathsToMatch = null)
|
||||
: base(InputActionSerializationHelpers.IsCompositeBinding(bindingProperty) ? "Composite" : "Binding",
|
||||
bindingProperty, onChange, expectedControlLayout)
|
||||
{
|
||||
m_BindingProperty = bindingProperty;
|
||||
m_GroupsProperty = bindingProperty.FindPropertyRelative("m_Groups");
|
||||
m_PathProperty = bindingProperty.FindPropertyRelative("m_Path");
|
||||
m_BindingGroups = m_GroupsProperty.stringValue
|
||||
.Split(new[] {InputBinding.Separator}, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
m_ExpectedControlLayout = expectedControlLayout;
|
||||
m_ControlSchemes = controlSchemes;
|
||||
|
||||
var flags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
|
||||
m_IsPartOfComposite = (flags & InputBinding.Flags.PartOfComposite) != 0;
|
||||
m_IsComposite = (flags & InputBinding.Flags.Composite) != 0;
|
||||
|
||||
// Set up control picker for m_Path. Not needed if the binding is a composite.
|
||||
if (!m_IsComposite)
|
||||
{
|
||||
m_ControlPickerState = controlPickerState ?? new InputControlPickerState();
|
||||
m_ControlPathEditor = new InputControlPathEditor(m_PathProperty, m_ControlPickerState, OnPathChanged);
|
||||
m_ControlPathEditor.SetExpectedControlLayout(m_ExpectedControlLayout);
|
||||
if (controlPathsToMatch != null)
|
||||
m_ControlPathEditor.SetControlPathsToMatch(controlPathsToMatch);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_ControlPathEditor?.Dispose();
|
||||
}
|
||||
|
||||
protected override void DrawGeneralProperties()
|
||||
{
|
||||
var currentPath = m_PathProperty.stringValue;
|
||||
InputSystem.OnDrawCustomWarningForBindingPath(currentPath);
|
||||
|
||||
if (m_IsComposite)
|
||||
{
|
||||
if (m_CompositeParameters == null)
|
||||
InitializeCompositeProperties();
|
||||
|
||||
// Composite type dropdown.
|
||||
var selectedCompositeType = EditorGUILayout.Popup(s_CompositeTypeLabel, m_SelectedCompositeType, m_CompositeTypeOptions);
|
||||
if (selectedCompositeType != m_SelectedCompositeType)
|
||||
{
|
||||
m_SelectedCompositeType = selectedCompositeType;
|
||||
OnCompositeTypeChanged();
|
||||
}
|
||||
|
||||
// Composite parameters.
|
||||
m_CompositeParameters.OnGUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Path.
|
||||
m_ControlPathEditor.OnGUI();
|
||||
|
||||
// Composite part.
|
||||
if (m_IsPartOfComposite)
|
||||
{
|
||||
if (m_CompositeParts == null)
|
||||
InitializeCompositePartProperties();
|
||||
|
||||
var selectedPart = EditorGUILayout.Popup(s_CompositePartAssignmentLabel, m_SelectedCompositePart,
|
||||
m_CompositePartOptions);
|
||||
if (selectedPart != m_SelectedCompositePart)
|
||||
{
|
||||
m_SelectedCompositePart = selectedPart;
|
||||
OnCompositePartAssignmentChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// Show the specific controls which match the current path
|
||||
DrawMatchingControlPaths();
|
||||
|
||||
// Control scheme matrix.
|
||||
DrawUseInControlSchemes();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to keep track of which foldouts are expanded.
|
||||
/// </summary>
|
||||
private static bool showMatchingLayouts = false;
|
||||
private static Dictionary<string, bool> showMatchingChildLayouts = new Dictionary<string, bool>();
|
||||
|
||||
private static void DrawMatchingControlPaths(List<MatchingControlPath> matchingControlPaths)
|
||||
{
|
||||
foreach (var matchingControlPath in matchingControlPaths)
|
||||
{
|
||||
bool showLayout = false;
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
var text = $"{matchingControlPath.deviceName} > {matchingControlPath.controlName}";
|
||||
if (matchingControlPath.children.Count() > 0 && !matchingControlPath.isRoot)
|
||||
{
|
||||
showMatchingChildLayouts.TryGetValue(matchingControlPath.deviceName, out showLayout);
|
||||
showMatchingChildLayouts[matchingControlPath.deviceName] = EditorGUILayout.Foldout(showLayout, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField(text);
|
||||
}
|
||||
|
||||
showLayout |= matchingControlPath.isRoot;
|
||||
if (showLayout)
|
||||
DrawMatchingControlPaths(matchingControlPath.children);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all registered control paths implemented by concrete classes which match the current binding path and renders it.
|
||||
/// </summary>
|
||||
private void DrawMatchingControlPaths()
|
||||
{
|
||||
bool controlPathUsagePresent = false;
|
||||
List<MatchingControlPath> matchingControlPaths = MatchingControlPath.CollectMatchingControlPaths(m_ControlPathEditor.pathProperty.stringValue, showMatchingLayouts, ref controlPathUsagePresent);
|
||||
if (matchingControlPaths == null || matchingControlPaths.Count != 0)
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
showMatchingLayouts = EditorGUILayout.Foldout(showMatchingLayouts, "Derived Bindings");
|
||||
|
||||
if (showMatchingLayouts)
|
||||
{
|
||||
if (matchingControlPaths == null)
|
||||
{
|
||||
if (controlPathUsagePresent)
|
||||
EditorGUILayout.HelpBox("No registered controls match this current binding. Some controls are only registered at runtime.", MessageType.Warning);
|
||||
else
|
||||
EditorGUILayout.HelpBox("No other registered controls match this current binding. Some controls are only registered at runtime.", MessageType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawMatchingControlPaths(matchingControlPaths);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw control scheme matrix that allows selecting which control schemes a particular
|
||||
/// binding appears in.
|
||||
/// </summary>
|
||||
private void DrawUseInControlSchemes()
|
||||
{
|
||||
if (m_ControlSchemes.Count <= 0)
|
||||
return;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(s_UseInControlSchemesLAbel, EditorStyles.boldLabel);
|
||||
EditorGUILayout.BeginVertical();
|
||||
|
||||
foreach (var scheme in m_ControlSchemes)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var result = EditorGUILayout.Toggle(scheme.name, m_BindingGroups.Contains(scheme.bindingGroup));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
m_BindingGroups.Add(scheme.bindingGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_BindingGroups.Remove(scheme.bindingGroup);
|
||||
}
|
||||
OnBindingGroupsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void InitializeCompositeProperties()
|
||||
{
|
||||
// Find name of current composite.
|
||||
var path = m_PathProperty.stringValue;
|
||||
var compositeNameAndParameters = NameAndParameters.Parse(path);
|
||||
var compositeName = compositeNameAndParameters.name;
|
||||
var compositeType = InputBindingComposite.s_Composites.LookupTypeRegistration(compositeName);
|
||||
|
||||
// Collect all possible composite types.
|
||||
var selectedCompositeIndex = -1;
|
||||
var compositeTypeOptionsList = new List<GUIContent>();
|
||||
var compositeTypeList = new List<string>();
|
||||
var currentIndex = 0;
|
||||
foreach (var composite in InputBindingComposite.s_Composites.internedNames.Where(x =>
|
||||
!InputBindingComposite.s_Composites.aliases.Contains(x)).OrderBy(x => x))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(m_ExpectedControlLayout))
|
||||
{
|
||||
var valueType = InputBindingComposite.GetValueType(composite);
|
||||
if (valueType != null &&
|
||||
!InputControlLayout.s_Layouts.ValueTypeIsAssignableFrom(
|
||||
new InternedString(m_ExpectedControlLayout), valueType))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (InputBindingComposite.s_Composites.LookupTypeRegistration(composite) == compositeType)
|
||||
selectedCompositeIndex = currentIndex;
|
||||
var name = ObjectNames.NicifyVariableName(composite);
|
||||
compositeTypeOptionsList.Add(new GUIContent(name));
|
||||
compositeTypeList.Add(composite);
|
||||
++currentIndex;
|
||||
}
|
||||
|
||||
// If the current composite type isn't a registered type, add it to the list as
|
||||
// an extra option.
|
||||
if (selectedCompositeIndex == -1)
|
||||
{
|
||||
selectedCompositeIndex = compositeTypeList.Count;
|
||||
compositeTypeOptionsList.Add(new GUIContent(ObjectNames.NicifyVariableName(compositeName)));
|
||||
compositeTypeList.Add(compositeName);
|
||||
}
|
||||
|
||||
m_CompositeTypes = compositeTypeList.ToArray();
|
||||
m_CompositeTypeOptions = compositeTypeOptionsList.ToArray();
|
||||
m_SelectedCompositeType = selectedCompositeIndex;
|
||||
|
||||
// Initialize parameters.
|
||||
m_CompositeParameters = new ParameterListView
|
||||
{
|
||||
onChange = OnCompositeParametersModified
|
||||
};
|
||||
if (compositeType != null)
|
||||
m_CompositeParameters.Initialize(compositeType, compositeNameAndParameters.parameters);
|
||||
}
|
||||
|
||||
private void InitializeCompositePartProperties()
|
||||
{
|
||||
var currentCompositePart = m_BindingProperty.FindPropertyRelative("m_Name").stringValue;
|
||||
|
||||
////REVIEW: this makes a lot of assumptions about the serialized data based on the one property we've been given in the ctor
|
||||
// Determine the name of the current composite type that the part belongs to.
|
||||
var bindingArrayProperty = m_BindingProperty.GetArrayPropertyFromElement();
|
||||
var partBindingIndex = InputActionSerializationHelpers.GetIndex(bindingArrayProperty, m_BindingProperty);
|
||||
var compositeBindingIndex =
|
||||
InputActionSerializationHelpers.GetCompositeStartIndex(bindingArrayProperty, partBindingIndex);
|
||||
if (compositeBindingIndex == -1)
|
||||
return;
|
||||
var compositeBindingProperty = bindingArrayProperty.GetArrayElementAtIndex(compositeBindingIndex);
|
||||
var compositePath = compositeBindingProperty.FindPropertyRelative("m_Path").stringValue;
|
||||
var compositeNameAndParameters = NameAndParameters.Parse(compositePath);
|
||||
|
||||
// Initialize option list from all parts available for the composite.
|
||||
var optionList = new List<GUIContent>();
|
||||
var nameList = new List<string>();
|
||||
var currentIndex = 0;
|
||||
var selectedPartNameIndex = -1;
|
||||
foreach (var partName in InputBindingComposite.GetPartNames(compositeNameAndParameters.name))
|
||||
{
|
||||
if (partName.Equals(currentCompositePart, StringComparison.InvariantCultureIgnoreCase))
|
||||
selectedPartNameIndex = currentIndex;
|
||||
var niceName = ObjectNames.NicifyVariableName(partName);
|
||||
optionList.Add(new GUIContent(niceName));
|
||||
nameList.Add(partName);
|
||||
++currentIndex;
|
||||
}
|
||||
|
||||
// If currently selected part is not in list, add it as an option.
|
||||
if (selectedPartNameIndex == -1)
|
||||
{
|
||||
selectedPartNameIndex = nameList.Count;
|
||||
optionList.Add(new GUIContent(ObjectNames.NicifyVariableName(currentCompositePart)));
|
||||
nameList.Add(currentCompositePart);
|
||||
}
|
||||
|
||||
m_CompositeParts = nameList.ToArray();
|
||||
m_CompositePartOptions = optionList.ToArray();
|
||||
m_SelectedCompositePart = selectedPartNameIndex;
|
||||
}
|
||||
|
||||
private void OnCompositeParametersModified()
|
||||
{
|
||||
Debug.Assert(m_CompositeParameters != null);
|
||||
|
||||
var path = m_PathProperty.stringValue;
|
||||
var nameAndParameters = NameAndParameters.Parse(path);
|
||||
nameAndParameters.parameters = m_CompositeParameters.GetParameters();
|
||||
|
||||
m_PathProperty.stringValue = nameAndParameters.ToString();
|
||||
m_PathProperty.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
OnPathChanged();
|
||||
}
|
||||
|
||||
private void OnBindingGroupsChanged()
|
||||
{
|
||||
m_GroupsProperty.stringValue = string.Join(InputBinding.kSeparatorString, m_BindingGroups.ToArray());
|
||||
m_GroupsProperty.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
onChange?.Invoke(k_GroupsChanged);
|
||||
}
|
||||
|
||||
private void OnPathChanged()
|
||||
{
|
||||
m_BindingProperty.serializedObject.ApplyModifiedProperties();
|
||||
onChange?.Invoke(k_PathChanged);
|
||||
}
|
||||
|
||||
private void OnCompositeTypeChanged()
|
||||
{
|
||||
var nameAndParameters = new NameAndParameters
|
||||
{
|
||||
name = m_CompositeTypes[m_SelectedCompositeType],
|
||||
parameters = m_CompositeParameters.GetParameters()
|
||||
};
|
||||
|
||||
InputActionSerializationHelpers.ChangeCompositeBindingType(m_BindingProperty, nameAndParameters);
|
||||
m_PathProperty.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
onChange?.Invoke(k_CompositeTypeChanged);
|
||||
}
|
||||
|
||||
private void OnCompositePartAssignmentChanged()
|
||||
{
|
||||
m_BindingProperty.FindPropertyRelative("m_Name").stringValue = m_CompositeParts[m_SelectedCompositePart];
|
||||
m_BindingProperty.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
onChange?.Invoke(k_CompositePartAssignmentChanged);
|
||||
}
|
||||
|
||||
private readonly bool m_IsComposite;
|
||||
private ParameterListView m_CompositeParameters;
|
||||
private int m_SelectedCompositeType;
|
||||
private GUIContent[] m_CompositeTypeOptions;
|
||||
private string[] m_CompositeTypes;
|
||||
|
||||
private int m_SelectedCompositePart;
|
||||
private GUIContent[] m_CompositePartOptions;
|
||||
private string[] m_CompositeParts;
|
||||
|
||||
private readonly SerializedProperty m_GroupsProperty;
|
||||
private readonly SerializedProperty m_BindingProperty;
|
||||
private readonly SerializedProperty m_PathProperty;
|
||||
|
||||
private readonly InputControlPickerState m_ControlPickerState;
|
||||
private readonly InputControlPathEditor m_ControlPathEditor;
|
||||
|
||||
private static readonly GUIContent s_CompositeTypeLabel = EditorGUIUtility.TrTextContent("Composite Type",
|
||||
"Type of composite. Allows changing the composite type retroactively. Doing so will modify the bindings that are part of the composite.");
|
||||
private static readonly GUIContent s_UseInControlSchemesLAbel = EditorGUIUtility.TrTextContent("Use in control scheme",
|
||||
"In which control schemes the binding is active. A binding can be used by arbitrary many control schemes. If a binding is not "
|
||||
+ "assigned to a specific control schemes, it is active in all of them.");
|
||||
private static readonly GUIContent s_CompositePartAssignmentLabel = EditorGUIUtility.TrTextContent(
|
||||
"Composite Part",
|
||||
"The named part of the composite that the binding is assigned to. Multiple bindings may be assigned the same part. All controls from "
|
||||
+ "all bindings that are assigned the same part will collectively feed values into that part of the composite.");
|
||||
|
||||
private ReadOnlyArray<InputControlScheme> m_ControlSchemes;
|
||||
private readonly List<string> m_BindingGroups;
|
||||
private readonly string m_ExpectedControlLayout;
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8afcd24f0b647f4f9f8c539d613ee6d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,229 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor.Lists
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="ReorderableList"/> to manage a set of name-and-parameter pairs and a <see cref="ParameterListView"/>
|
||||
/// to edit the parameters of the currently selected pair.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Produces output that can be consumed by <see cref="NameAndParameters.ParseMultiple"/>.
|
||||
/// </remarks>
|
||||
internal abstract class NameAndParameterListView
|
||||
{
|
||||
protected NameAndParameterListView(SerializedProperty property, Action applyAction, string expectedControlLayout, TypeTable listOptions, Func<Type, Type> 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<Type, Type> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of processors and their parameters.
|
||||
/// </summary>
|
||||
internal class ProcessorsListView : NameAndParameterListView
|
||||
{
|
||||
public ProcessorsListView(SerializedProperty property, Action applyAction, string expectedControlLayout)
|
||||
: base(property, applyAction, expectedControlLayout, InputProcessor.s_Processors, InputProcessor.GetValueTypeFromType, "Processor")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list view of interactions and their parameters.
|
||||
/// </summary>
|
||||
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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da649da79e1b9455791f5ddd31355edc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad61ded360bb0784b9301188fc603d6d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,108 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33f32142c2c2d4662be5f5274db27158
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,108 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ffeba93637e654de6a5906b48520dc94
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95391b99439094c00a105cd26ba61b1b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,120 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce5f11cd993284c4f9c54f854bdc8c9c
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 1
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 1
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 9cfdf4253b3de4d389dff51ab21111ad
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,108 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf632ed80bf1f46c980de6b1b8b903ef
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,120 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ab69704aa4984b49bc426da72fabbed
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 1
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 1
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,120 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa4c2360f910d46f497f75b4b6677358
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 1
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 1
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,108 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7b144323984d4298924c65ed071dea1
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be2b3c041afc2477ea1f172b6d14c3dc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,120 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4b66e31395c54087a2fbc13ae7e0f3c
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 1
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,108 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 943deaaee9c9347e0a455aa208526c42
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,120 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0996bc290ff74bf4834f4a8b7dc8c3e
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 1
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,120 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b02e3f0c0798a4f1789a89a7697660be
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 1
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,108 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47cf506b4219343519083fe1c0b8fc9c
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,455 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
////TODO: show description of interaction or processor when selected
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor.Lists
|
||||
{
|
||||
/// <summary>
|
||||
/// Inspector-like functionality for editing parameter lists as used in <see cref="InputControlLayout"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used for parameters on interactions, processors, and composites.
|
||||
///
|
||||
/// Call <see cref="Initialize"/> to set up (can be done repeatedly on the same instance). Call
|
||||
/// <see cref="OnGUI"/> to render.
|
||||
///
|
||||
/// Custom parameter GUIs can be defined by deriving from <see cref="InputParameterEditor{TObject}"/>.
|
||||
/// This class will automatically incorporate custom GUIs and fall back to default GUIs where no custom
|
||||
/// ones are defined.
|
||||
/// </remarks>
|
||||
internal class ParameterListView
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked whenever a parameter is changed.
|
||||
/// </summary>
|
||||
public Action onChange { get; set; }
|
||||
|
||||
public bool hasUIToShow => (m_Parameters != null && m_Parameters.Length > 0) || m_ParameterEditor != null;
|
||||
public bool visible { get; set; }
|
||||
public string name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the current parameter values according to the editor state.
|
||||
/// </summary>
|
||||
/// <returns>An array of parameter values.</returns>
|
||||
public NamedValue[] GetParameters()
|
||||
{
|
||||
if (m_Parameters == null)
|
||||
return null;
|
||||
|
||||
// See if we have parameters that aren't at their default value.
|
||||
var countOfParametersNotAtDefaultValue = 0;
|
||||
for (var i = 0; i < m_Parameters.Length; ++i)
|
||||
{
|
||||
if (!m_Parameters[i].isAtDefault)
|
||||
++countOfParametersNotAtDefaultValue;
|
||||
}
|
||||
|
||||
// If not, we return null.
|
||||
if (countOfParametersNotAtDefaultValue == 0)
|
||||
return null;
|
||||
|
||||
// Collect non-default parameter values.
|
||||
var result = new NamedValue[countOfParametersNotAtDefaultValue];
|
||||
var index = 0;
|
||||
for (var i = 0; i < m_Parameters.Length; ++i)
|
||||
{
|
||||
var parameter = m_Parameters[i];
|
||||
if (parameter.isAtDefault)
|
||||
continue;
|
||||
|
||||
result[index++] = parameter.value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the parameter list view based on the given registered type that has parameters to edit. This can be
|
||||
/// things such as interactions, processors, or composites.
|
||||
/// </summary>
|
||||
/// <param name="registeredType">Type of object that the parameters will be passed to at runtime.
|
||||
/// We need this to be able to determine the possible set of parameters and their possible values. This
|
||||
/// can be a class implementing <see cref="IInputInteraction"/>, for example.</param>
|
||||
/// <param name="existingParameters">List of existing parameters. Can be empty.</param>
|
||||
public void Initialize(Type registeredType, ReadOnlyArray<NamedValue> existingParameters)
|
||||
{
|
||||
if (registeredType == null)
|
||||
{
|
||||
// No registered type. This usually happens when data references a registration that has
|
||||
// been removed in the meantime (e.g. an interaction that is no longer supported). We want
|
||||
// to accept this case and simply pretend that the given type has no parameters.
|
||||
|
||||
Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
visible = true;
|
||||
|
||||
// Try to instantiate object so that we can determine defaults.
|
||||
object instance = null;
|
||||
try
|
||||
{
|
||||
instance = Activator.CreateInstance(registeredType);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Swallow. If we can't create an instance, we simply assume no defaults.
|
||||
}
|
||||
|
||||
var parameters = new List<EditableParameterValue>();
|
||||
|
||||
////REVIEW: support properties here?
|
||||
// Go through public instance fields and add every parameter found on the registered
|
||||
// type.
|
||||
var fields = registeredType.GetFields(BindingFlags.Public | BindingFlags.Instance);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
// Skip all fields that have an [InputControl] attribute. This is relevant
|
||||
// only for composites, but we just always do it here.
|
||||
if (field.GetCustomAttribute<InputControlAttribute>(false) != null)
|
||||
continue;
|
||||
|
||||
// Determine parameter name from field.
|
||||
var parameter = new EditableParameterValue {field = field};
|
||||
var name = field.Name;
|
||||
parameter.value.name = name;
|
||||
|
||||
// Determine parameter type from field.
|
||||
var fieldType = field.FieldType;
|
||||
if (fieldType.IsEnum)
|
||||
{
|
||||
// For enums, we want the underlying integer type.
|
||||
var underlyingType = fieldType.GetEnumUnderlyingType();
|
||||
var underlyingTypeCode = Type.GetTypeCode(underlyingType);
|
||||
|
||||
parameter.value = parameter.value.ConvertTo(underlyingTypeCode);
|
||||
|
||||
// Read enum names and values.
|
||||
parameter.enumNames = Enum.GetNames(fieldType).Select(x => new GUIContent(x)).ToArray();
|
||||
////REVIEW: this probably falls apart if multiple members have the same value
|
||||
var list = new List<int>();
|
||||
foreach (var value in Enum.GetValues(fieldType))
|
||||
list.Add((int)value);
|
||||
parameter.enumValues = list.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
var typeCode = Type.GetTypeCode(fieldType);
|
||||
parameter.value = parameter.value.ConvertTo(typeCode);
|
||||
}
|
||||
|
||||
// Determine default value.
|
||||
if (instance != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = field.GetValue(instance);
|
||||
parameter.defaultValue = new NamedValue
|
||||
{
|
||||
name = name,
|
||||
value = PrimitiveValue.FromObject(value)
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If the getter throws, ignore. All we lose is the actual default value from
|
||||
// the field.
|
||||
}
|
||||
}
|
||||
|
||||
// If the parameter already exists in the given list, maintain its value.
|
||||
var existingParameterIndex = existingParameters.IndexOf(x => x.name == field.Name);
|
||||
if (existingParameterIndex >= 0)
|
||||
{
|
||||
// Make sure we're preserving the right type.
|
||||
parameter.value = existingParameters[existingParameterIndex].ConvertTo(parameter.value.type);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not assigned. Set to default.
|
||||
if (parameter.defaultValue != null)
|
||||
parameter.value = parameter.defaultValue.Value;
|
||||
}
|
||||
|
||||
// Add.
|
||||
parameters.Add(parameter);
|
||||
}
|
||||
|
||||
m_Parameters = parameters.ToArray();
|
||||
|
||||
// See if we have a dedicated parameter editor.
|
||||
var parameterEditorType = InputParameterEditor.LookupEditorForType(registeredType);
|
||||
if (parameterEditorType != null)
|
||||
{
|
||||
// Create an editor instance and hand it the instance we created. Unlike our default
|
||||
// editing logic, on this path we will be operating on an object instance that contains
|
||||
// the parameter values. So on this path, we actually need to update the object to reflect
|
||||
// the current parameter values.
|
||||
|
||||
NamedValue.ApplyAllToObject(instance, m_Parameters.Select(x => x.value));
|
||||
|
||||
m_ParameterEditor = (InputParameterEditor)Activator.CreateInstance(parameterEditorType);
|
||||
|
||||
// We have to jump through some hoops here to create instances of any CustomOrDefaultSetting fields on the
|
||||
// parameter editor. This is because those types changed from structs to classes when UIToolkit was
|
||||
// introduced, and we don't want to force users to have to create those instances manually on any of their
|
||||
// own editors.
|
||||
var genericArgumentType = TypeHelpers.GetGenericTypeArgumentFromHierarchy(parameterEditorType,
|
||||
typeof(InputParameterEditor<>), 0);
|
||||
if (genericArgumentType != null)
|
||||
{
|
||||
var fieldInfos = parameterEditorType
|
||||
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var customOrDefaultGenericType = typeof(InputParameterEditor<>.CustomOrDefaultSetting);
|
||||
var customOrDefaultType = customOrDefaultGenericType.MakeGenericType(genericArgumentType);
|
||||
foreach (var customOrDefaultEditorField in fieldInfos.Where(f => f.FieldType == customOrDefaultType))
|
||||
{
|
||||
customOrDefaultEditorField.SetValue(m_ParameterEditor, Activator.CreateInstance(customOrDefaultEditorField.FieldType));
|
||||
}
|
||||
}
|
||||
m_ParameterEditor.SetTarget(instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ParameterEditor = null;
|
||||
|
||||
// Create parameter labels.
|
||||
m_ParameterLabels = new GUIContent[m_Parameters.Length];
|
||||
for (var i = 0; i < m_Parameters.Length; ++i)
|
||||
{
|
||||
// Look up tooltip from field.
|
||||
var tooltip = string.Empty;
|
||||
var field = m_Parameters[i].field;
|
||||
var tooltipAttribute = field.GetCustomAttribute<TooltipAttribute>();
|
||||
if (tooltipAttribute != null)
|
||||
tooltip = tooltipAttribute.tooltip;
|
||||
|
||||
// Create label.
|
||||
var niceName = ObjectNames.NicifyVariableName(m_Parameters[i].value.name);
|
||||
m_ParameterLabels[i] = new GUIContent(niceName, tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_Parameters = null;
|
||||
m_ParameterEditor = null;
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
public void OnDrawVisualElements(VisualElement root)
|
||||
{
|
||||
if (m_ParameterEditor != null)
|
||||
{
|
||||
m_ParameterEditor.OnDrawVisualElements(root, OnValuesChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_Parameters == null)
|
||||
return;
|
||||
|
||||
void OnValueChanged(ref EditableParameterValue parameter, object result, int i)
|
||||
{
|
||||
parameter.value.value = PrimitiveValue.FromObject(result).ConvertTo(parameter.value.type);
|
||||
m_Parameters[i] = parameter;
|
||||
}
|
||||
|
||||
void OnEditEnd()
|
||||
{
|
||||
onChange?.Invoke();
|
||||
}
|
||||
|
||||
for (var i = 0; i < m_Parameters.Length; i++)
|
||||
{
|
||||
var parameter = m_Parameters[i];
|
||||
var label = m_ParameterLabels[i];
|
||||
var closedIndex = i;
|
||||
|
||||
if (parameter.isEnum)
|
||||
{
|
||||
var intValue = parameter.value.value.ToInt32();
|
||||
var field = new DropdownField(label.text, parameter.enumNames.Select(x => x.text).ToList(), intValue);
|
||||
field.tooltip = label.tooltip;
|
||||
field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, field.index, closedIndex));
|
||||
field.RegisterCallback<BlurEvent>(_ => OnEditEnd());
|
||||
root.Add(field);
|
||||
}
|
||||
else if (parameter.value.type == TypeCode.Int64 || parameter.value.type == TypeCode.UInt64)
|
||||
{
|
||||
var longValue = parameter.value.value.ToInt64();
|
||||
var field = new LongField(label.text) { value = longValue, tooltip = label.tooltip };
|
||||
field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, evt.newValue, closedIndex));
|
||||
field.RegisterCallback<BlurEvent>(_ => OnEditEnd());
|
||||
root.Add(field);
|
||||
}
|
||||
else if (parameter.value.type.IsInt())
|
||||
{
|
||||
var intValue = parameter.value.value.ToInt32();
|
||||
var field = new IntegerField(label.text) { value = intValue, tooltip = label.tooltip };
|
||||
field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, evt.newValue, closedIndex));
|
||||
field.RegisterCallback<BlurEvent>(_ => OnEditEnd());
|
||||
root.Add(field);
|
||||
}
|
||||
else if (parameter.value.type == TypeCode.Single)
|
||||
{
|
||||
var floatValue = parameter.value.value.ToSingle();
|
||||
var field = new FloatField(label.text) { value = floatValue, tooltip = label.tooltip };
|
||||
field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, evt.newValue, closedIndex));
|
||||
field.RegisterCallback<BlurEvent>(_ => OnEditEnd());
|
||||
root.Add(field);
|
||||
}
|
||||
else if (parameter.value.type == TypeCode.Double)
|
||||
{
|
||||
var floatValue = parameter.value.value.ToDouble();
|
||||
var field = new DoubleField(label.text) { value = floatValue, tooltip = label.tooltip };
|
||||
field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, evt.newValue, closedIndex));
|
||||
field.RegisterCallback<BlurEvent>(_ => OnEditEnd());
|
||||
root.Add(field);
|
||||
}
|
||||
else if (parameter.value.type == TypeCode.Boolean)
|
||||
{
|
||||
var boolValue = parameter.value.value.ToBoolean();
|
||||
var field = new Toggle(label.text) { value = boolValue, tooltip = label.tooltip };
|
||||
field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, evt.newValue, closedIndex));
|
||||
field.RegisterValueChangedCallback(_ => OnEditEnd());
|
||||
root.Add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private void OnValuesChanged()
|
||||
{
|
||||
ReadParameterValuesFrom(m_ParameterEditor.target);
|
||||
onChange?.Invoke();
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
// If we have a dedicated parameter editor, let it do all the work.
|
||||
if (m_ParameterEditor != null)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
m_ParameterEditor.OnGUI();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
ReadParameterValuesFrom(m_ParameterEditor.target);
|
||||
onChange?.Invoke();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
|
||||
// handled by OnDrawVisualElements with UI Toolkit
|
||||
if (!InputSystem.settings.useIMGUIEditorForAssets) return;
|
||||
#endif
|
||||
// Otherwise, fall back to our default logic.
|
||||
if (m_Parameters == null)
|
||||
return;
|
||||
for (var i = 0; i < m_Parameters.Length; i++)
|
||||
{
|
||||
var parameter = m_Parameters[i];
|
||||
var label = m_ParameterLabels[i];
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
object result = null;
|
||||
if (parameter.isEnum)
|
||||
{
|
||||
var intValue = parameter.value.value.ToInt32();
|
||||
result = EditorGUILayout.IntPopup(label, intValue, parameter.enumNames, parameter.enumValues);
|
||||
}
|
||||
else if (parameter.value.type == TypeCode.Int64 || parameter.value.type == TypeCode.UInt64)
|
||||
{
|
||||
var longValue = parameter.value.value.ToInt64();
|
||||
result = EditorGUILayout.LongField(label, longValue);
|
||||
}
|
||||
else if (parameter.value.type.IsInt())
|
||||
{
|
||||
var intValue = parameter.value.value.ToInt32();
|
||||
result = EditorGUILayout.IntField(label, intValue);
|
||||
}
|
||||
else if (parameter.value.type == TypeCode.Single)
|
||||
{
|
||||
var floatValue = parameter.value.value.ToSingle();
|
||||
result = EditorGUILayout.FloatField(label, floatValue);
|
||||
}
|
||||
else if (parameter.value.type == TypeCode.Double)
|
||||
{
|
||||
var floatValue = parameter.value.value.ToDouble();
|
||||
result = EditorGUILayout.DoubleField(label, floatValue);
|
||||
}
|
||||
else if (parameter.value.type == TypeCode.Boolean)
|
||||
{
|
||||
var boolValue = parameter.value.value.ToBoolean();
|
||||
result = EditorGUILayout.Toggle(label, boolValue);
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
parameter.value.value = PrimitiveValue.FromObject(result).ConvertTo(parameter.value.type);
|
||||
m_Parameters[i] = parameter;
|
||||
onChange?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////REVIEW: check whether parameters have *actually* changed?
|
||||
/// <summary>
|
||||
/// Refresh <see cref="m_Parameters"/> from the current parameter values in <paramref name="target"/>.
|
||||
/// </summary>
|
||||
/// <param name="target">An instance of the current type we are editing parameters on.</param>
|
||||
private void ReadParameterValuesFrom(object target)
|
||||
{
|
||||
if (m_Parameters == null)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < m_Parameters.Length; ++i)
|
||||
{
|
||||
var parameter = m_Parameters[i];
|
||||
|
||||
object value = null;
|
||||
try
|
||||
{
|
||||
value = parameter.field.GetValue(target);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore exceptions from getters.
|
||||
}
|
||||
|
||||
m_Parameters[i].value.value = PrimitiveValue.FromObject(value).ConvertTo(parameter.value.type);
|
||||
}
|
||||
}
|
||||
|
||||
private InputParameterEditor m_ParameterEditor;
|
||||
private EditableParameterValue[] m_Parameters;
|
||||
private GUIContent[] m_ParameterLabels;
|
||||
|
||||
private struct EditableParameterValue
|
||||
{
|
||||
public NamedValue value;
|
||||
public NamedValue? defaultValue;
|
||||
public int[] enumValues;
|
||||
public GUIContent[] enumNames;
|
||||
public FieldInfo field;
|
||||
|
||||
public bool isEnum => enumValues != null;
|
||||
public bool isAtDefault => defaultValue != null && value == defaultValue.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb0e54f1ead384cd191254077544faf0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,146 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor.Lists;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////TODO: show parameters for selected interaction or processor inline in list rather than separately underneath list
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for views that show the properties of actions or bindings.
|
||||
/// </summary>
|
||||
internal abstract class PropertiesViewBase
|
||||
{
|
||||
protected PropertiesViewBase(string label, SerializedProperty bindingOrAction, Action<FourCC> onChange, string expectedControlLayout = null)
|
||||
{
|
||||
if (bindingOrAction == null)
|
||||
throw new ArgumentNullException(nameof(bindingOrAction));
|
||||
|
||||
m_InteractionsProperty = bindingOrAction.FindPropertyRelative("m_Interactions");
|
||||
m_ProcessorsProperty = bindingOrAction.FindPropertyRelative("m_Processors");
|
||||
|
||||
m_InteractionsList = new InteractionsListView(m_InteractionsProperty, OnInteractionsModified, expectedControlLayout);
|
||||
UpdateProcessors(expectedControlLayout);
|
||||
|
||||
m_OnChange = onChange;
|
||||
m_GeneralFoldoutLabel = EditorGUIUtility.TrTextContent(label);
|
||||
}
|
||||
|
||||
protected void UpdateProcessors(string expectedControlLayout)
|
||||
{
|
||||
m_ProcessorsList = new ProcessorsListView(m_ProcessorsProperty, OnProcessorsModified, expectedControlLayout);
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
DrawGeneralGroup();
|
||||
if (!m_IsPartOfComposite)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
DrawInteractionsGroup();
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
DrawProcessorsGroup();
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
protected abstract void DrawGeneralProperties();
|
||||
|
||||
private void DrawGeneralGroup()
|
||||
{
|
||||
m_GeneralFoldout = DrawFoldout(m_GeneralFoldoutLabel, m_GeneralFoldout);
|
||||
if (m_GeneralFoldout)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
DrawGeneralProperties();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawProcessorsGroup()
|
||||
{
|
||||
m_ProcessorsFoldout = DrawFoldout(s_ProcessorsFoldoutLabel, m_ProcessorsFoldout, s_ProcessorsAddButton, m_ProcessorsList.OnAddDropdown);
|
||||
if (m_ProcessorsFoldout)
|
||||
m_ProcessorsList.OnGUI();
|
||||
}
|
||||
|
||||
private void DrawInteractionsGroup()
|
||||
{
|
||||
m_InteractionsFoldout = DrawFoldout(s_InteractionsFoldoutLabel, m_InteractionsFoldout, s_InteractionsAddButton, m_InteractionsList.OnAddDropdown);
|
||||
if (m_InteractionsFoldout)
|
||||
m_InteractionsList.OnGUI();
|
||||
}
|
||||
|
||||
private static bool DrawFoldout(GUIContent content, bool folded, GUIContent addButton = null, Action<Rect> addDropDown = null)
|
||||
{
|
||||
const int k_PopupSize = 20;
|
||||
var bgRect = GUILayoutUtility.GetRect(content, Styles.s_FoldoutBackgroundStyle);
|
||||
EditorGUI.LabelField(bgRect, GUIContent.none, Styles.s_FoldoutBackgroundStyle);
|
||||
var foldoutRect = bgRect;
|
||||
foldoutRect.xMax -= k_PopupSize;
|
||||
var retval = EditorGUI.Foldout(foldoutRect, folded, content, true, Styles.s_FoldoutStyle);
|
||||
if (addButton != null)
|
||||
{
|
||||
var popupRect = bgRect;
|
||||
popupRect.xMin = popupRect.xMax - k_PopupSize;
|
||||
if (GUI.Button(popupRect, addButton, EditorStyles.label))
|
||||
addDropDown(popupRect);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
private void OnProcessorsModified()
|
||||
{
|
||||
m_ProcessorsProperty.stringValue = m_ProcessorsList.ToSerializableString();
|
||||
m_ProcessorsProperty.serializedObject.ApplyModifiedProperties();
|
||||
m_OnChange(k_ProcessorsChanged);
|
||||
}
|
||||
|
||||
private void OnInteractionsModified()
|
||||
{
|
||||
m_InteractionsProperty.stringValue = m_InteractionsList.ToSerializableString();
|
||||
m_InteractionsProperty.serializedObject.ApplyModifiedProperties();
|
||||
m_OnChange(k_InteractionsChanged);
|
||||
}
|
||||
|
||||
public Action<FourCC> onChange => m_OnChange;
|
||||
|
||||
private bool m_GeneralFoldout = true;
|
||||
private bool m_InteractionsFoldout = true;
|
||||
private bool m_ProcessorsFoldout = true;
|
||||
protected bool m_IsPartOfComposite;
|
||||
|
||||
private readonly Action<FourCC> m_OnChange;
|
||||
|
||||
private readonly InteractionsListView m_InteractionsList;
|
||||
private ProcessorsListView m_ProcessorsList;
|
||||
|
||||
private readonly SerializedProperty m_InteractionsProperty;
|
||||
private readonly SerializedProperty m_ProcessorsProperty;
|
||||
|
||||
private readonly GUIContent m_GeneralFoldoutLabel;
|
||||
|
||||
////TODO: tooltips
|
||||
private static readonly GUIContent s_ProcessorsFoldoutLabel = EditorGUIUtility.TrTextContent("Processors");
|
||||
public static readonly GUIContent s_ProcessorsAddButton = EditorGUIUtility.TrIconContent("Toolbar Plus More", "Add Processor");
|
||||
private static readonly GUIContent s_InteractionsFoldoutLabel = EditorGUIUtility.TrTextContent("Interactions");
|
||||
public static readonly GUIContent s_InteractionsAddButton = EditorGUIUtility.TrIconContent("Toolbar Plus More", "Add Interaction");
|
||||
|
||||
public static FourCC k_InteractionsChanged => new FourCC("IACT");
|
||||
public static FourCC k_ProcessorsChanged => new FourCC("PROC");
|
||||
|
||||
private static class Styles
|
||||
{
|
||||
public static readonly GUIStyle s_FoldoutBackgroundStyle = new GUIStyle("Label")
|
||||
.WithNormalBackground(AssetDatabase.LoadAssetAtPath<Texture2D>(InputActionTreeView.ResourcesPath + "foldoutBackground.png"))
|
||||
.WithBorder(new RectOffset(3, 3, 3, 3))
|
||||
.WithMargin(new RectOffset(1, 1, 3, 3));
|
||||
public static readonly GUIStyle s_FoldoutStyle = new GUIStyle("foldout");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 060ae4265f580481f9bc2f2e7ab367ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||