Skip to content

Commit

Permalink
UITK drag and drop (ISX-1141) (#1831)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
ritamerkl and jfreire-unity authored Feb 14, 2024
1 parent d46ba1a commit 6db0cc5
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 38 deletions.
1 change: 1 addition & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ however, it has to be formatted properly to pass verification tests.
- [`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.
- Added Copy, Paste and Cut support for Action Maps, Actions and Bindings via context menu and key command shortcuts.
- Added Dual Sense Edge controller to be mapped to the same layout as the Dual Sense controller
- Added drag and drop support in the Input Action Asset Editor for Action Maps, Actions and Bindings.
- UI Toolkit input action editor now supports showing the derived bindings.

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,24 @@ public static void DeleteActionMap(SerializedObject asset, Guid id)
mapArrayProperty.DeleteArrayElementAtIndex(mapIndex);
}

public static void MoveActionMap(SerializedObject asset, int fromIndex, int toIndex)
{
var mapArrayProperty = asset.FindProperty("m_ActionMaps");
mapArrayProperty.MoveArrayElement(fromIndex, toIndex);
}

public static void MoveAction(SerializedProperty actionMap, int fromIndex, int toIndex)
{
var actionArrayProperty = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Actions));
actionArrayProperty.MoveArrayElement(fromIndex, toIndex);
}

public static void MoveBinding(SerializedProperty actionMap, int fromIndex, int toIndex)
{
var arrayProperty = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
arrayProperty.MoveArrayElement(fromIndex, toIndex);
}

// Append a new action to the end of the set.
public static SerializedProperty AddAction(SerializedProperty actionMap, int index = -1)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ public static Command PasteActionMaps()
};
}

public static Command PasteActionIntoActionMap(int actionMapIndex)
{
return (in InputActionsEditorState state) =>
{
var lastPastedElement = CopyPasteHelper.PasteActionsOrBindingsFromClipboard(state, true, actionMapIndex);
if (lastPastedElement != null)
state.serializedObject.ApplyModifiedProperties();
return state;
};
}

public static Command PasteActionFromActionMap()
{
return (in InputActionsEditorState state) =>
Expand Down Expand Up @@ -230,6 +241,117 @@ private static InputActionsEditorState SelectPrevActionMap(InputActionsEditorSta
return state.SelectActionMap(index);
}

public static Command ReorderActionMap(int oldIndex, int newIndex)
{
return (in InputActionsEditorState state) =>
{
InputActionSerializationHelpers.MoveActionMap(state.serializedObject, oldIndex, newIndex);
state.serializedObject.ApplyModifiedProperties();
return state.SelectActionMap(newIndex);
};
}

public static Command MoveAction(int oldIndex, int newIndex)
{
return (in InputActionsEditorState state) =>
{
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
InputActionSerializationHelpers.MoveAction(actionMap, oldIndex, newIndex);
state.serializedObject.ApplyModifiedProperties();
return state.SelectAction(newIndex);
};
}

public static Command MoveBinding(int oldIndex, int actionIndex, int childIndex)
{
return (in InputActionsEditorState state) =>
{
var newBindingIndex = MoveBindingOrComposite(state, oldIndex, actionIndex, childIndex);
state.serializedObject.ApplyModifiedProperties();
return state.SelectBinding(newBindingIndex);
};
}

public static Command MoveComposite(int oldIndex, int actionIndex, int childIndex)
{
return (in InputActionsEditorState state) =>
{
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
var compositeBindings = CopyPasteHelper.GetBindingsForComposite(actionMap?.FindPropertyRelative(nameof(InputActionMap.m_Bindings)), oldIndex);
//move the composite element
var newBindingIndex = MoveBindingOrComposite(state, oldIndex, actionIndex, childIndex);
var actionTo = Selectors.GetActionForIndex(actionMap, actionIndex).FindPropertyRelative(nameof(InputAction.m_Name)).stringValue;
var toIndex = newBindingIndex;
foreach (var compositePart in compositeBindings)
{
// 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)
// if the composite was moved up, the index of the composite part is not changing so we are safe to use it
var from = oldIndex < newBindingIndex ? oldIndex : compositePart.GetIndexOfArrayElement();
// if added below the old position the array changes as composite parts are added on top (increase the index)
// if added above the oldIndex, the index does not change
var to = oldIndex < newBindingIndex ? newBindingIndex : ++toIndex;
InputActionSerializationHelpers.MoveBinding(actionMap, from, to);
Selectors.GetCompositeOrBindingInMap(actionMap, to).wrappedProperty.FindPropertyRelative("m_Action").stringValue = actionTo;
}
state.serializedObject.ApplyModifiedProperties();
return state.SelectBinding(newBindingIndex);
};
}

private static int MoveBindingOrComposite(InputActionsEditorState state, int oldIndex, int actionIndex, int childIndex)
{
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
var bindingsForAction = Selectors.GetBindingsForAction(state, actionMap, actionIndex);
var allBindings = actionMap?.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
var actionTo = Selectors.GetActionForIndex(actionMap, actionIndex).FindPropertyRelative(nameof(InputAction.m_Name)).stringValue;
var actionFrom = Selectors.GetCompositeOrBindingInMap(actionMap, oldIndex).wrappedProperty.FindPropertyRelative("m_Action");
int newBindingIndex;
if (bindingsForAction.Count == 0) //if there are no bindings for an action retrieve the first binding index of a binding before (iterate previous actions)
newBindingIndex = Selectors.GetBindingIndexBeforeAction(allBindings, actionIndex, allBindings);
else
{
var toSkip = GetNumberOfCompositePartItemsToSkip(bindingsForAction, childIndex, oldIndex); //skip composite parts if there are - avoid moving into a composite
newBindingIndex = bindingsForAction[0].GetIndexOfArrayElement() + Math.Clamp(childIndex + toSkip, 0, bindingsForAction.Count);
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
}

actionFrom.stringValue = actionTo;
InputActionSerializationHelpers.MoveBinding(actionMap, oldIndex, newBindingIndex);
return newBindingIndex;
}

private static int GetNumberOfCompositePartItemsToSkip(List<SerializedProperty> bindings, int childIndex, int oldIndex)
{
var toSkip = 0;
var normalBindings = 0;
foreach (var binding in bindings)
{
if (binding.GetIndexOfArrayElement() == oldIndex)
continue;
if (normalBindings > childIndex)
break;
if (binding.FindPropertyRelative(nameof(InputBinding.m_Flags)).intValue ==
(int)InputBinding.Flags.PartOfComposite)
toSkip++;
else
normalBindings++;
}
return toSkip;
}

public static Command MovePartOfComposite(int oldIndex, int newIndex, int compositeIndex)
{
return (in InputActionsEditorState state) =>
{
var actionMap = Selectors.GetSelectedActionMap(state)?.wrappedProperty;
var actionTo = actionMap?.FindPropertyRelative(nameof(InputActionMap.m_Bindings)).GetArrayElementAtIndex(compositeIndex).FindPropertyRelative("m_Action").stringValue;
InputActionSerializationHelpers.MoveBinding(actionMap, oldIndex, newIndex);
Selectors.GetCompositeOrBindingInMap(actionMap, newIndex).wrappedProperty.FindPropertyRelative("m_Action").stringValue = actionTo;
state.serializedObject.ApplyModifiedProperties();
return state.SelectBinding(newIndex);
};
}

public static Command DeleteAction(int actionMapIndex, string actionName)
{
return (in InputActionsEditorState state) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public ActionMapsView(VisualElement root, StateContainer stateContainer)
{
m_ListView = root.Q<ListView>("action-maps-list-view");
m_ListView.selectionType = UIElements.SelectionType.Single;

m_ListView.reorderable = true;
m_ListViewSelectionChangeFilter = new CollectionViewSelectionChangeFilter(m_ListView);
m_ListViewSelectionChangeFilter.selectedIndicesChanged += (selectedIndices) =>
{
Expand All @@ -34,6 +34,7 @@ public ActionMapsView(VisualElement root, StateContainer stateContainer)
treeViewItem.DuplicateCallback = _ => DuplicateActionMap(i);
treeViewItem.OnDeleteItem += treeViewItem.DeleteCallback;
treeViewItem.OnDuplicateItem += treeViewItem.DuplicateCallback;
treeViewItem.userData = i;

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

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


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

void OnDroppedHandler(int mapIndex)
{
Dispatch(Commands.CutActionsOrBindings());
Dispatch(Commands.PasteActionIntoActionMap(mapIndex));
}

void OnReorder(int oldIndex, int newIndex)
{
Dispatch(Commands.ReorderActionMap(oldIndex, newIndex));
}

public override void RedrawUI(ViewState viewState)
{
m_ListView.itemsSource = viewState.actionMapNames?.ToList() ?? new List<string>();
Expand Down
Loading

0 comments on commit 6db0cc5

Please sign in to comment.