Skip to content

Commit 6db0cc5

Browse files
UITK drag and drop (ISX-1141) (#1831)
* DROP Enable UITK Asset Editor * WIP Add Drag And Drop UI functionality * SQUASH * Add drag and drop respective manipulators to deal with UI events * WIP Add data manipulation logic based on UI drag and drop actions * fixed visualizations * fixed reordering and drag between lists * fixed focusing MapListItems during drag * clean up branch * clean up drag/drop * implemented Drag & Drop between action tree view and action map list view * cleared focus during drag/drop between lists * fixed freeze after drag * drag and drop of action maps to reorder ActionMap list * WIP drag & drop of actions, bindings & composites * fix after merge * added moving of bindings and move composite (WIP) * implemented move part of composites and fixed move binding to empty actions * fixed moving bindings * added discard drag on invalid * fixed selection after drag & dragging bindings into actionMapView * refactor & fixed not allow drag of composites to action maps * fixed moving of composites * fixed discard of drag between items * change action name for composites * fixed bindings are moved down one index to far * added action item * added changelog * fix formatting & adjusted changelog * refactor commands & fixed analyzer * added comments and fixed accessibility * added comments & named parameters to ActionsTreeView * fixed formatting * removed plus sign for dragging action to action map * added safety check - fixed nullpointer for adding actions in empty action map * fix picking of elements for dropping on ActionMapView * fixed naming and enhanced break conditions for TreeViewDrag * added safety assert & debug pasting bindings * changed dropManipulator to be internal --------- Co-authored-by: João Freire <[email protected]>
1 parent d46ba1a commit 6db0cc5

File tree

10 files changed

+367
-38
lines changed

10 files changed

+367
-38
lines changed

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ however, it has to be formatted properly to pass verification tests.
2121
- [`InputAction.WasCompletedThisFrame`](xref:UnityEngine.InputSystem.InputAction.WasCompletedThisFrame) returns `true` on the frame that the action stopped being in the performed phase. This allows for similar functionality to [`WasPressedThisFrame`](xref:UnityEngine.InputSystem.InputAction.WasPressedThisFrame)/[`WasReleasedThisFrame`](xref:UnityEngine.InputSystem.InputAction.WasReleasedThisFrame) when paired with [`WasPerformedThisFrame`](xref:UnityEngine.InputSystem.InputAction.WasPerformedThisFrame) except it is directly based on the interactions driving the action. For example, you can use it to distinguish between the button being released or whether it was released after being held for long enough to perform when using the Hold interaction.
2222
- Added Copy, Paste and Cut support for Action Maps, Actions and Bindings via context menu and key command shortcuts.
2323
- Added Dual Sense Edge controller to be mapped to the same layout as the Dual Sense controller
24+
- Added drag and drop support in the Input Action Asset Editor for Action Maps, Actions and Bindings.
2425
- UI Toolkit input action editor now supports showing the derived bindings.
2526

2627
### Fixed

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,24 @@ public static void DeleteActionMap(SerializedObject asset, Guid id)
148148
mapArrayProperty.DeleteArrayElementAtIndex(mapIndex);
149149
}
150150

151+
public static void MoveActionMap(SerializedObject asset, int fromIndex, int toIndex)
152+
{
153+
var mapArrayProperty = asset.FindProperty("m_ActionMaps");
154+
mapArrayProperty.MoveArrayElement(fromIndex, toIndex);
155+
}
156+
157+
public static void MoveAction(SerializedProperty actionMap, int fromIndex, int toIndex)
158+
{
159+
var actionArrayProperty = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Actions));
160+
actionArrayProperty.MoveArrayElement(fromIndex, toIndex);
161+
}
162+
163+
public static void MoveBinding(SerializedProperty actionMap, int fromIndex, int toIndex)
164+
{
165+
var arrayProperty = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
166+
arrayProperty.MoveArrayElement(fromIndex, toIndex);
167+
}
168+
151169
// Append a new action to the end of the set.
152170
public static SerializedProperty AddAction(SerializedProperty actionMap, int index = -1)
153171
{

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

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,17 @@ public static Command PasteActionMaps()
149149
};
150150
}
151151

152+
public static Command PasteActionIntoActionMap(int actionMapIndex)
153+
{
154+
return (in InputActionsEditorState state) =>
155+
{
156+
var lastPastedElement = CopyPasteHelper.PasteActionsOrBindingsFromClipboard(state, true, actionMapIndex);
157+
if (lastPastedElement != null)
158+
state.serializedObject.ApplyModifiedProperties();
159+
return state;
160+
};
161+
}
162+
152163
public static Command PasteActionFromActionMap()
153164
{
154165
return (in InputActionsEditorState state) =>
@@ -230,6 +241,117 @@ private static InputActionsEditorState SelectPrevActionMap(InputActionsEditorSta
230241
return state.SelectActionMap(index);
231242
}
232243

244+
public static Command ReorderActionMap(int oldIndex, int newIndex)
245+
{
246+
return (in InputActionsEditorState state) =>
247+
{
248+
InputActionSerializationHelpers.MoveActionMap(state.serializedObject, oldIndex, newIndex);
249+
state.serializedObject.ApplyModifiedProperties();
250+
return state.SelectActionMap(newIndex);
251+
};
252+
}
253+
254+
public static Command MoveAction(int oldIndex, int newIndex)
255+
{
256+
return (in InputActionsEditorState state) =>
257+
{
258+
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
259+
InputActionSerializationHelpers.MoveAction(actionMap, oldIndex, newIndex);
260+
state.serializedObject.ApplyModifiedProperties();
261+
return state.SelectAction(newIndex);
262+
};
263+
}
264+
265+
public static Command MoveBinding(int oldIndex, int actionIndex, int childIndex)
266+
{
267+
return (in InputActionsEditorState state) =>
268+
{
269+
var newBindingIndex = MoveBindingOrComposite(state, oldIndex, actionIndex, childIndex);
270+
state.serializedObject.ApplyModifiedProperties();
271+
return state.SelectBinding(newBindingIndex);
272+
};
273+
}
274+
275+
public static Command MoveComposite(int oldIndex, int actionIndex, int childIndex)
276+
{
277+
return (in InputActionsEditorState state) =>
278+
{
279+
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
280+
var compositeBindings = CopyPasteHelper.GetBindingsForComposite(actionMap?.FindPropertyRelative(nameof(InputActionMap.m_Bindings)), oldIndex);
281+
//move the composite element
282+
var newBindingIndex = MoveBindingOrComposite(state, oldIndex, actionIndex, childIndex);
283+
var actionTo = Selectors.GetActionForIndex(actionMap, actionIndex).FindPropertyRelative(nameof(InputAction.m_Name)).stringValue;
284+
var toIndex = newBindingIndex;
285+
foreach (var compositePart in compositeBindings)
286+
{
287+
// the index of the composite part stays the same if composite was moved down as previous elements are shifted down (the index seems to update async so it's safer to use the oldIndex)
288+
// if the composite was moved up, the index of the composite part is not changing so we are safe to use it
289+
var from = oldIndex < newBindingIndex ? oldIndex : compositePart.GetIndexOfArrayElement();
290+
// if added below the old position the array changes as composite parts are added on top (increase the index)
291+
// if added above the oldIndex, the index does not change
292+
var to = oldIndex < newBindingIndex ? newBindingIndex : ++toIndex;
293+
InputActionSerializationHelpers.MoveBinding(actionMap, from, to);
294+
Selectors.GetCompositeOrBindingInMap(actionMap, to).wrappedProperty.FindPropertyRelative("m_Action").stringValue = actionTo;
295+
}
296+
state.serializedObject.ApplyModifiedProperties();
297+
return state.SelectBinding(newBindingIndex);
298+
};
299+
}
300+
301+
private static int MoveBindingOrComposite(InputActionsEditorState state, int oldIndex, int actionIndex, int childIndex)
302+
{
303+
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
304+
var bindingsForAction = Selectors.GetBindingsForAction(state, actionMap, actionIndex);
305+
var allBindings = actionMap?.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
306+
var actionTo = Selectors.GetActionForIndex(actionMap, actionIndex).FindPropertyRelative(nameof(InputAction.m_Name)).stringValue;
307+
var actionFrom = Selectors.GetCompositeOrBindingInMap(actionMap, oldIndex).wrappedProperty.FindPropertyRelative("m_Action");
308+
int newBindingIndex;
309+
if (bindingsForAction.Count == 0) //if there are no bindings for an action retrieve the first binding index of a binding before (iterate previous actions)
310+
newBindingIndex = Selectors.GetBindingIndexBeforeAction(allBindings, actionIndex, allBindings);
311+
else
312+
{
313+
var toSkip = GetNumberOfCompositePartItemsToSkip(bindingsForAction, childIndex, oldIndex); //skip composite parts if there are - avoid moving into a composite
314+
newBindingIndex = bindingsForAction[0].GetIndexOfArrayElement() + Math.Clamp(childIndex + toSkip, 0, bindingsForAction.Count);
315+
newBindingIndex -= newBindingIndex > oldIndex && !actionTo.Equals(actionFrom.stringValue) ? 1 : 0; // reduce index by one in case the moved binding will be shifted underneath to another action
316+
}
317+
318+
actionFrom.stringValue = actionTo;
319+
InputActionSerializationHelpers.MoveBinding(actionMap, oldIndex, newBindingIndex);
320+
return newBindingIndex;
321+
}
322+
323+
private static int GetNumberOfCompositePartItemsToSkip(List<SerializedProperty> bindings, int childIndex, int oldIndex)
324+
{
325+
var toSkip = 0;
326+
var normalBindings = 0;
327+
foreach (var binding in bindings)
328+
{
329+
if (binding.GetIndexOfArrayElement() == oldIndex)
330+
continue;
331+
if (normalBindings > childIndex)
332+
break;
333+
if (binding.FindPropertyRelative(nameof(InputBinding.m_Flags)).intValue ==
334+
(int)InputBinding.Flags.PartOfComposite)
335+
toSkip++;
336+
else
337+
normalBindings++;
338+
}
339+
return toSkip;
340+
}
341+
342+
public static Command MovePartOfComposite(int oldIndex, int newIndex, int compositeIndex)
343+
{
344+
return (in InputActionsEditorState state) =>
345+
{
346+
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
347+
var actionTo = actionMap?.FindPropertyRelative(nameof(InputActionMap.m_Bindings)).GetArrayElementAtIndex(compositeIndex).FindPropertyRelative("m_Action").stringValue;
348+
InputActionSerializationHelpers.MoveBinding(actionMap, oldIndex, newIndex);
349+
Selectors.GetCompositeOrBindingInMap(actionMap, newIndex).wrappedProperty.FindPropertyRelative("m_Action").stringValue = actionTo;
350+
state.serializedObject.ApplyModifiedProperties();
351+
return state.SelectBinding(newIndex);
352+
};
353+
}
354+
233355
public static Command DeleteAction(int actionMapIndex, string actionName)
234356
{
235357
return (in InputActionsEditorState state) =>

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public ActionMapsView(VisualElement root, StateContainer stateContainer)
1717
{
1818
m_ListView = root.Q<ListView>("action-maps-list-view");
1919
m_ListView.selectionType = UIElements.SelectionType.Single;
20-
20+
m_ListView.reorderable = true;
2121
m_ListViewSelectionChangeFilter = new CollectionViewSelectionChangeFilter(m_ListView);
2222
m_ListViewSelectionChangeFilter.selectedIndicesChanged += (selectedIndices) =>
2323
{
@@ -34,6 +34,7 @@ public ActionMapsView(VisualElement root, StateContainer stateContainer)
3434
treeViewItem.DuplicateCallback = _ => DuplicateActionMap(i);
3535
treeViewItem.OnDeleteItem += treeViewItem.DeleteCallback;
3636
treeViewItem.OnDuplicateItem += treeViewItem.DuplicateCallback;
37+
treeViewItem.userData = i;
3738

3839
ContextMenu.GetContextMenuForActionMapItem(treeViewItem);
3940
};
@@ -55,6 +56,10 @@ public ActionMapsView(VisualElement root, StateContainer stateContainer)
5556

5657
m_ListView.RegisterCallback<ExecuteCommandEvent>(OnExecuteCommand);
5758
m_ListView.RegisterCallback<ValidateCommandEvent>(OnValidateCommand);
59+
var treeView = root.Q<TreeView>("actions-tree-view");
60+
m_ListView.AddManipulator(new DropManipulator(OnDroppedHandler, treeView));
61+
m_ListView.itemIndexChanged += OnReorder;
62+
5863

5964
CreateSelector(s => new ViewStateCollection<string>(Selectors.GetActionMapNames(s)),
6065
(actionMapNames, state) => new ViewState(Selectors.GetSelectedActionMap(state), actionMapNames));
@@ -64,6 +69,17 @@ public ActionMapsView(VisualElement root, StateContainer stateContainer)
6469
ContextMenu.GetContextMenuForActionMapListView(this, m_ListView.parent);
6570
}
6671

72+
void OnDroppedHandler(int mapIndex)
73+
{
74+
Dispatch(Commands.CutActionsOrBindings());
75+
Dispatch(Commands.PasteActionIntoActionMap(mapIndex));
76+
}
77+
78+
void OnReorder(int oldIndex, int newIndex)
79+
{
80+
Dispatch(Commands.ReorderActionMap(oldIndex, newIndex));
81+
}
82+
6783
public override void RedrawUI(ViewState viewState)
6884
{
6985
m_ListView.itemsSource = viewState.actionMapNames?.ToList() ?? new List<string>();

0 commit comments

Comments
 (0)