Skip to content

Commit d1c66c3

Browse files
authored
NEW: duplicate action maps, actions and bindings (ISX-1375) (#1719)
* implemented basic duplication WIP * WIP added duplicate actions&bindings * index fix * fixed duplicating actions with composites * fixed bindings duplicated twice bug * fix for duplicating bindings * fixed duplicating bindings and composites * undo public modifier * formatting fix
1 parent c8c9e8a commit d1c66c3

7 files changed

Lines changed: 173 additions & 0 deletions

File tree

Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputActionSerializationHelpers.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#if UNITY_EDITOR
22
using System;
3+
using System.Collections.Generic;
34
using System.Linq;
45
using System.Reflection;
56
using UnityEditor;
@@ -110,6 +111,98 @@ public static int ConvertBindingIndexOnActionToBindingIndexInArray(SerializedPro
110111
return indexInArray;
111112
}
112113

114+
public static SerializedProperty DuplicateElement(SerializedProperty arrayProperty, SerializedProperty toDuplicate, string name, int index, bool changeName = true)
115+
{
116+
var json = toDuplicate.CopyToJson(true);
117+
var duplicatedProperty = AddElement(arrayProperty, name, index);
118+
duplicatedProperty.RestoreFromJson(json);
119+
if (changeName)
120+
EnsureUniqueName(duplicatedProperty);
121+
AssignUniqueIDs(duplicatedProperty);
122+
return duplicatedProperty;
123+
}
124+
125+
public static void DuplicateAction(SerializedProperty actionMap, SerializedProperty arrayProperty, SerializedProperty toDuplicate, string name)
126+
{
127+
var property = DuplicateElement(arrayProperty, toDuplicate, name, toDuplicate.GetIndexOfArrayElement() + 1);
128+
var newName = property.FindPropertyRelative("m_Name").stringValue;
129+
var bindingsArray = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
130+
var bindings = bindingsArray.Where(binding => binding.FindPropertyRelative("m_Action").stringValue.Equals(name)).ToList();
131+
var index = bindings.Select(b => b.GetIndexOfArrayElement()).Max() + 1;
132+
foreach (var binding in bindings)
133+
{
134+
var newIndex = DuplicateBindingAsPartOfAction(bindingsArray, binding, newName, index);
135+
index = newIndex;
136+
}
137+
}
138+
139+
private static SerializedProperty DuplicateComposite(SerializedProperty bindingsArray, SerializedProperty compositeToDuplicate, string name, string actionName, int index, out int newIndex, bool increaseIndex = true)
140+
{
141+
newIndex = index;
142+
var bindings = GetBindingsForComposite(bindingsArray, compositeToDuplicate);
143+
if (increaseIndex)
144+
newIndex += GetCompositePartCount(bindingsArray, compositeToDuplicate.GetIndexOfArrayElement());
145+
var newComposite = DuplicateElement(bindingsArray, compositeToDuplicate, name, newIndex++, false);
146+
newComposite.FindPropertyRelative("m_Action").stringValue = actionName;
147+
foreach (var binding in bindings)
148+
{
149+
var newBinding = DuplicateElement(bindingsArray, binding, binding.FindPropertyRelative("m_Name").stringValue, newIndex++, false);
150+
newBinding.FindPropertyRelative("m_Action").stringValue = actionName;
151+
}
152+
return newComposite;
153+
}
154+
155+
private static List<SerializedProperty> GetBindingsForComposite(SerializedProperty bindingsArray, SerializedProperty compositeToDuplicate)
156+
{
157+
var compositeBindings = new List<SerializedProperty>();
158+
var compositeStartIndex = GetCompositeStartIndex(bindingsArray, compositeToDuplicate.GetIndexOfArrayElement());
159+
if (compositeStartIndex == -1)
160+
return compositeBindings;
161+
162+
for (var i = compositeStartIndex + 1; i < bindingsArray.arraySize; ++i)
163+
{
164+
var bindingProperty = bindingsArray.GetArrayElementAtIndex(i);
165+
var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
166+
if ((bindingFlags & InputBinding.Flags.PartOfComposite) == 0)
167+
break;
168+
compositeBindings.Add(bindingProperty);
169+
}
170+
return compositeBindings;
171+
}
172+
173+
private static bool IsComposite(SerializedProperty property) => property.FindPropertyRelative("m_Flags").intValue == (int)InputBinding.Flags.Composite;
174+
private static bool IsPartComposite(SerializedProperty property) => property.FindPropertyRelative("m_Flags").intValue == (int)InputBinding.Flags.PartOfComposite;
175+
private static string PropertyName(SerializedProperty property) => property.FindPropertyRelative("m_Name").stringValue;
176+
177+
private static int DuplicateBindingAsPartOfAction(SerializedProperty arrayProperty, SerializedProperty toDuplicate, string newActionName, int index)
178+
{
179+
if (IsComposite(toDuplicate))
180+
{
181+
DuplicateComposite(arrayProperty, toDuplicate, PropertyName(toDuplicate), newActionName, index, out var newIndex, false);
182+
return newIndex;
183+
}
184+
if (IsPartComposite(toDuplicate))
185+
return index;
186+
var duplicatedBinding = DuplicateElement(arrayProperty, toDuplicate, PropertyName(toDuplicate), index++, false);
187+
duplicatedBinding.FindPropertyRelative("m_Action").stringValue = newActionName;
188+
return index;
189+
}
190+
191+
public static int DuplicateBinding(SerializedProperty arrayProperty, SerializedProperty toDuplicate, string newActionName, int index)
192+
{
193+
if (IsComposite(toDuplicate))
194+
{
195+
var newComposite = DuplicateComposite(arrayProperty, toDuplicate, PropertyName(toDuplicate), newActionName, index, out _);
196+
index = newComposite.GetIndexOfArrayElement();
197+
}
198+
else
199+
{
200+
var duplicatedBinding = DuplicateElement(arrayProperty, toDuplicate, PropertyName(toDuplicate), index, false);
201+
duplicatedBinding.FindPropertyRelative("m_Action").stringValue = newActionName;
202+
}
203+
return index;
204+
}
205+
113206
public static SerializedProperty AddElement(SerializedProperty arrayProperty, string name, int index = -1)
114207
{
115208
var uniqueName = FindUniqueName(arrayProperty, name);

Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Commands/Commands.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,47 @@ public static Command DeleteActionMap(int actionMapIndex)
9797
};
9898
}
9999

100+
public static Command DuplicateActionMap(int actionMapIndex)
101+
{
102+
return (in InputActionsEditorState state) =>
103+
{
104+
var actionMapArray = state.serializedObject.FindProperty(nameof(InputActionAsset.m_ActionMaps));
105+
var actionMap = Selectors.GetActionMapAtIndex(state, actionMapIndex)?.wrappedProperty;
106+
var name = actionMap?.FindPropertyRelative(nameof(InputAction.m_Name)).stringValue;
107+
var newMap = InputActionSerializationHelpers.DuplicateElement(actionMapArray, actionMap, name, actionMap.GetIndexOfArrayElement() + 1);
108+
state.serializedObject.ApplyModifiedProperties();
109+
return state.SelectActionMap(newMap.FindPropertyRelative(nameof(InputAction.m_Name)).stringValue);
110+
};
111+
}
112+
113+
public static Command DuplicateAction()
114+
{
115+
return (in InputActionsEditorState state) =>
116+
{
117+
var action = Selectors.GetSelectedAction(state)?.wrappedProperty;
118+
var actionName = action?.FindPropertyRelative(nameof(InputAction.m_Name)).stringValue;
119+
var actionMap = Selectors.GetActionMapAtIndex(state, state.selectedActionMapIndex)?.wrappedProperty;
120+
var actionArray = actionMap?.FindPropertyRelative(nameof(InputActionMap.m_Actions));
121+
InputActionSerializationHelpers.DuplicateAction(actionMap, actionArray, action, actionName);
122+
state.serializedObject.ApplyModifiedProperties();
123+
return state.SelectAction(state.selectedActionIndex + 1);
124+
};
125+
}
126+
127+
public static Command DuplicateBinding()
128+
{
129+
return (in InputActionsEditorState state) =>
130+
{
131+
var binding = Selectors.GetSelectedBinding(state)?.wrappedProperty;
132+
var actionName = binding?.FindPropertyRelative("m_Action").stringValue;
133+
var actionMap = Selectors.GetActionMapAtIndex(state, state.selectedActionMapIndex)?.wrappedProperty;
134+
var bindingsArray = actionMap?.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
135+
var newIndex = InputActionSerializationHelpers.DuplicateBinding(bindingsArray, binding, actionName, binding.GetIndexOfArrayElement() + 1);
136+
state.serializedObject.ApplyModifiedProperties();
137+
return state.SelectBinding(newIndex);
138+
};
139+
}
140+
100141
private static InputActionsEditorState SelectPrevActionMap(InputActionsEditorState state)
101142
{
102143
var count = Selectors.GetActionMapCount(state);

Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionMapsView.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ public ActionMapsView(VisualElement root, StateContainer stateContainer)
2727
treeViewItem.EditTextFinishedCallback = newName => ChangeActionMapName(i, newName);
2828
treeViewItem.EditTextFinished += treeViewItem.EditTextFinishedCallback;
2929
treeViewItem.DeleteCallback = _ => DeleteActionMap(i);
30+
treeViewItem.DuplicateCallback = _ => DuplicateActionMap(i);
3031
treeViewItem.OnDeleteItem += treeViewItem.DeleteCallback;
32+
treeViewItem.OnDuplicateItem += treeViewItem.DuplicateCallback;
3133

3234
ContextMenu.GetContextMenuForActionMapItem(treeViewItem);
3335
};
@@ -37,6 +39,7 @@ public ActionMapsView(VisualElement root, StateContainer stateContainer)
3739
var treeViewElement = (InputActionsTreeViewItem)element;
3840
treeViewElement.Reset();
3941
treeViewElement.OnDeleteItem -= treeViewElement.DeleteCallback;
42+
treeViewElement.OnDuplicateItem -= treeViewElement.DuplicateCallback;
4043
treeViewElement.EditTextFinished -= treeViewElement.EditTextFinishedCallback;
4144
};
4245

@@ -88,6 +91,11 @@ private void DeleteActionMap(int index)
8891
Dispatch(Commands.DeleteActionMap(index));
8992
}
9093

94+
private void DuplicateActionMap(int index)
95+
{
96+
Dispatch(Commands.DuplicateActionMap(index));
97+
}
98+
9199
private void ChangeActionMapName(int index, string newName)
92100
{
93101
Dispatch(Commands.ChangeActionMapName(index, newName));

Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionsTreeView.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ public ActionsTreeView(VisualElement root, StateContainer stateContainer)
3636
var addBindingButton = e.Q<Button>("add-new-binding-button");
3737
var treeViewItem = (InputActionsTreeViewItem)e;
3838
treeViewItem.DeleteCallback = _ => DeleteItem(item);
39+
treeViewItem.DuplicateCallback = _ => DuplicateItem(item);
3940
treeViewItem.OnDeleteItem += treeViewItem.DeleteCallback;
41+
treeViewItem.OnDuplicateItem += treeViewItem.DuplicateCallback;
4042
if (item.isComposite)
4143
ContextMenu.GetContextMenuForCompositeItem(treeViewItem, i);
4244
else if (item.isAction)
@@ -99,6 +101,7 @@ public ActionsTreeView(VisualElement root, StateContainer stateContainer)
99101
treeViewItem.Reset();
100102

101103
treeViewItem.OnDeleteItem -= treeViewItem.DeleteCallback;
104+
treeViewItem.OnDuplicateItem -= treeViewItem.DuplicateCallback;
102105
treeViewItem.EditTextFinished -= treeViewItem.EditTextFinishedCallback;
103106
};
104107

@@ -214,6 +217,11 @@ private void DeleteItem(ActionOrBindingData data)
214217
Dispatch(Commands.DeleteBinding(data.actionMapIndex, data.bindingIndex));
215218
}
216219

220+
private void DuplicateItem(ActionOrBindingData data)
221+
{
222+
Dispatch(data.isAction ? Commands.DuplicateAction() : Commands.DuplicateBinding());
223+
}
224+
217225
private void ChangeActionName(ActionOrBindingData data, string newName)
218226
{
219227
m_RenameOnActionAdded = false;

Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ContextMenu.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace UnityEngine.InputSystem.Editor
66
internal static class ContextMenu
77
{
88
private static readonly string rename_String = "Rename";
9+
private static readonly string duplicate_String = "Duplicate";
910
private static readonly string delete_String = "Delete";
1011

1112
private static readonly string add_Action_String = "Add Action";
@@ -20,6 +21,7 @@ public static void GetContextMenuForActionMapItem(InputActionsTreeViewItem treeV
2021
menuEvent.menu.AppendAction(add_Action_String, _ => InputActionViewsControlsHolder.CreateAction.Invoke(treeViewItem));
2122
menuEvent.menu.AppendSeparator();
2223
menuEvent.menu.AppendAction(rename_String, _ => InputActionViewsControlsHolder.RenameActionMap.Invoke(treeViewItem));
24+
AppendDuplicateAction(menuEvent, treeViewItem);
2325
AppendDeleteAction(menuEvent, treeViewItem);
2426
}) { target = treeViewItem };
2527
}
@@ -34,6 +36,7 @@ public static void GetContextMenuForActionItem(InputActionsTreeViewItem treeView
3436
menuEvent.menu.AppendAction(add_twoModifier_Binding_String, _ => InputActionViewsControlsHolder.AddCompositeTwoModifier.Invoke(treeViewItem));
3537
menuEvent.menu.AppendSeparator();
3638
AppendRenameAction(menuEvent, index, treeViewItem);
39+
AppendDuplicateAction(menuEvent, treeViewItem);
3740
AppendDeleteAction(menuEvent, treeViewItem);
3841
}) { target = treeViewItem };
3942
}
@@ -43,6 +46,7 @@ public static void GetContextMenuForCompositeItem(InputActionsTreeViewItem treeV
4346
var _ = new ContextualMenuManipulator(menuEvent =>
4447
{
4548
AppendRenameAction(menuEvent, index, treeViewItem);
49+
AppendDuplicateAction(menuEvent, treeViewItem);
4650
AppendDeleteAction(menuEvent, treeViewItem);
4751
}) { target = treeViewItem };
4852
}
@@ -51,6 +55,7 @@ public static void GetContextMenuForBindingItem(InputActionsTreeViewItem treeVie
5155
{
5256
var _ = new ContextualMenuManipulator(menuEvent =>
5357
{
58+
AppendDuplicateAction(menuEvent, treeViewItem);
5459
AppendDeleteAction(menuEvent, treeViewItem);
5560
}) { target = treeViewItem };
5661
}
@@ -60,6 +65,11 @@ private static void AppendDeleteAction(ContextualMenuPopulateEvent menuEvent, In
6065
menuEvent.menu.AppendAction(delete_String, _ => {InputActionViewsControlsHolder.DeleteAction.Invoke(treeViewItem);});
6166
}
6267

68+
private static void AppendDuplicateAction(ContextualMenuPopulateEvent menuEvent, InputActionsTreeViewItem treeViewItem)
69+
{
70+
menuEvent.menu.AppendAction(duplicate_String, _ => {InputActionViewsControlsHolder.DuplicateAction.Invoke(treeViewItem);});
71+
}
72+
6373
private static void AppendRenameAction(ContextualMenuPopulateEvent menuEvent, int index, InputActionsTreeViewItem treeViewItem)
6474
{
6575
menuEvent.menu.AppendAction(rename_String, _ => {InputActionViewsControlsHolder.RenameAction.Invoke(index, treeViewItem);});

Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/InputActionViewsControlsHolder.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ internal static class InputActionViewsControlsHolder
1717
internal static Action<InputActionsTreeViewItem> AddCompositeOneModifier => AddNewOneModifierComposite;
1818
internal static Action<InputActionsTreeViewItem> AddCompositeTwoModifier => AddNewTwoModifierComposite;
1919
internal static Action<InputActionsTreeViewItem> CreateAction => CreateNewAction;
20+
internal static Action<InputActionsTreeViewItem> DuplicateAction => Duplicate;
2021

2122
internal static void Initialize(VisualElement root, ActionsTreeView actionsTreeView)
2223
{
@@ -77,6 +78,11 @@ private static void AddNewTwoModifierComposite(InputActionsTreeViewItem inputAct
7778
var action = inputActionsTreeViewItem.label.text;
7879
m_ActionsTreeView.AddComposite(action, "TwoModifiers");
7980
}
81+
82+
private static void Duplicate(InputActionsTreeViewItem inputActionsTreeViewItem)
83+
{
84+
inputActionsTreeViewItem.DuplicateItem();
85+
}
8086
}
8187
}
8288
#endif

Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/InputActionsTreeViewItem.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ internal class InputActionsTreeViewItem : VisualElement
1515
{
1616
public EventCallback<string> EditTextFinishedCallback;
1717
public EventCallback<int> DeleteCallback;
18+
public EventCallback<int> DuplicateCallback;
1819

1920
private const string kRenameTextField = "rename-text-field";
2021
public event EventCallback<string> EditTextFinished;
2122
public event EventCallback<int> OnDeleteItem;
23+
public event EventCallback<int> OnDuplicateItem;
2224

2325
private bool m_IsEditing;
2426

@@ -105,6 +107,11 @@ public void DeleteItem()
105107
OnDeleteItem?.Invoke(0);
106108
}
107109

110+
public void DuplicateItem()
111+
{
112+
OnDuplicateItem?.Invoke(0);
113+
}
114+
108115
private void OnEditTextFinished()
109116
{
110117
if (!m_IsEditing)

0 commit comments

Comments
 (0)