diff --git a/Assets/Tests/InputSystem.Editor/SelectorsTests.cs b/Assets/Tests/InputSystem.Editor/SelectorsTests.cs index 5cca0aa08f..978fc55b2b 100644 --- a/Assets/Tests/InputSystem.Editor/SelectorsTests.cs +++ b/Assets/Tests/InputSystem.Editor/SelectorsTests.cs @@ -24,7 +24,7 @@ public void GetActionsAsTreeViewData_ReturnsActionsAndBindingsAsTreeViewData() var actionTwo = actionMap.AddAction("Action2", binding: "/d"); - var treeViewData = Selectors.GetActionsAsTreeViewData(TestData.EditorStateWithAsset(asset).Generate()); + var treeViewData = Selectors.GetActionsAsTreeViewData(TestData.EditorStateWithAsset(asset).Generate(), new Dictionary()); Assert.That(treeViewData.Count, Is.EqualTo(2)); diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionsTreeView.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionsTreeView.cs index 89600e1007..eb9d58e0eb 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionsTreeView.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ActionsTreeView.cs @@ -26,6 +26,9 @@ internal class ActionsTreeView : ViewBase private bool m_RenameOnActionAdded; private readonly CollectionViewSelectionChangeFilter m_ActionsTreeViewSelectionChangeFilter; + //save TreeView element id's of individual input actions and bindings to ensure saving of expanded state + private Dictionary m_GuidToTreeViewId; + public ActionsTreeView(VisualElement root, StateContainer stateContainer) : base(root, stateContainer) { @@ -35,6 +38,7 @@ public ActionsTreeView(VisualElement root, StateContainer stateContainer) m_ActionsTreeView = root.Q("actions-tree-view"); //assign unique viewDataKey to store treeView states like expanded/collapsed items - make it unique to avoid conflicts with other TreeViews m_ActionsTreeView.viewDataKey = $"InputActionTreeView_{stateContainer.assetGUID}"; + m_GuidToTreeViewId = new Dictionary(); m_ActionsTreeView.selectionType = UIElements.SelectionType.Single; m_ActionsTreeView.makeItem = () => new InputActionsTreeViewItem(); m_ActionsTreeView.reorderable = true; @@ -143,7 +147,7 @@ public ActionsTreeView(VisualElement root, StateContainer stateContainer) CreateSelector(Selectors.GetActionsForSelectedActionMap, Selectors.GetActionMapCount, (_, count, state) => { - var treeData = Selectors.GetActionsAsTreeViewData(state); + var treeData = Selectors.GetActionsAsTreeViewData(state, m_GuidToTreeViewId); return new ViewState { treeViewData = treeData, @@ -568,7 +572,7 @@ public ActionOrBindingData(bool isAction, string name, int actionMapIndex, bool internal static partial class Selectors { - public static List> GetActionsAsTreeViewData(InputActionsEditorState state) + public static List> GetActionsAsTreeViewData(InputActionsEditorState state, Dictionary idDictionary) { var actionMapIndex = state.selectedActionMapIndex; var controlSchemes = state.serializedObject.FindProperty(nameof(InputActionAsset.m_ControlSchemes)); @@ -587,13 +591,12 @@ public static List> GetActionsAsTreeViewDa .ToList(); var actionItems = new List>(); - var treeviewItemIDCounter = 0; foreach (var action in actions) { var actionBindings = bindings.Where(spb => spb.action == action.name).ToList(); var bindingItems = new List>(); var actionId = new Guid(action.id); - + for (var i = 0; i < actionBindings.Count; i++) { var serializedInputBinding = actionBindings[i]; @@ -616,7 +619,7 @@ public static List> GetActionsAsTreeViewDa if (isVisible) { var name = GetHumanReadableCompositeName(nextBinding, state.selectedControlScheme, controlSchemes); - compositeItems.Add(new TreeViewItemData(treeviewItemIDCounter++, + compositeItems.Add(new TreeViewItemData(GetIdForGuid(new Guid(nextBinding.id), idDictionary), new ActionOrBindingData(isAction: false, name, actionMapIndex, isComposite: false, isPartOfComposite: true, GetControlLayout(nextBinding.path), bindingIndex: nextBinding.indexOfBinding, isCut: state.IsBindingCut(actionMapIndex, nextBinding.indexOfBinding)))); } @@ -634,7 +637,7 @@ public static List> GetActionsAsTreeViewDa var shouldCompositeBeVisible = !(compositeItems.Count == 0 && hasHiddenCompositeParts); //hide composite if all parts are hidden if (shouldCompositeBeVisible) - bindingItems.Add(new TreeViewItemData(treeviewItemIDCounter++, + bindingItems.Add(new TreeViewItemData(GetIdForGuid(inputBindingId, idDictionary), new ActionOrBindingData(isAction: false, serializedInputBinding.name, actionMapIndex, isComposite: true, isPartOfComposite: false, action.expectedControlType, bindingIndex: serializedInputBinding.indexOfBinding, isCut: state.IsBindingCut(actionMapIndex, serializedInputBinding.indexOfBinding)), compositeItems.Count > 0 ? compositeItems : null)); } @@ -642,18 +645,31 @@ public static List> GetActionsAsTreeViewDa { var isVisible = ShouldBindingBeVisible(serializedInputBinding, state.selectedControlScheme, state.selectedDeviceRequirementIndex); if (isVisible) - bindingItems.Add(new TreeViewItemData(treeviewItemIDCounter++, + bindingItems.Add(new TreeViewItemData(GetIdForGuid(inputBindingId, idDictionary), new ActionOrBindingData(isAction: false, GetHumanReadableBindingName(serializedInputBinding, state.selectedControlScheme, controlSchemes), actionMapIndex, isComposite: false, isPartOfComposite: false, GetControlLayout(serializedInputBinding.path), bindingIndex: serializedInputBinding.indexOfBinding, isCut: state.IsBindingCut(actionMapIndex, serializedInputBinding.indexOfBinding)))); } } var actionIndex = action.wrappedProperty.GetIndexOfArrayElement(); - actionItems.Add(new TreeViewItemData(treeviewItemIDCounter++, + actionItems.Add(new TreeViewItemData(GetIdForGuid(actionId, idDictionary), new ActionOrBindingData(isAction: true, action.name, actionMapIndex, isComposite: false, isPartOfComposite: false, action.expectedControlType, actionIndex: actionIndex, isCut: state.IsActionCut(actionMapIndex, actionIndex)), bindingItems.Count > 0 ? bindingItems : null)); } return actionItems; } + private static int GetIdForGuid(Guid guid, Dictionary idDictionary) + { + //This method is used to ensure that the same Guid always gets the same id + //We use getHashCode instead of a counter, as we cannot guarantee that the same Guid will always be added in the same order + //There is a tiny chance of a collision, but it is it does happen it will only affect the expanded state of the tree view + if (!idDictionary.TryGetValue(guid, out var id)) + { + id = guid.GetHashCode(); //idDictionary.Values.Count > 0 ? idDictionary.Values.Max() + 1 : 0; + idDictionary.Add(guid, id); + } + return id; + } + private static string GetHumanReadableBindingName(SerializedInputBinding serializedInputBinding, InputControlScheme? currentControlScheme, SerializedProperty allControlSchemes) { var name = InputControlPath.ToHumanReadableString(serializedInputBinding.path);