A game about forced loneliness, made by TACStudios
1#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
2using CmdEvents = UnityEngine.InputSystem.Editor.InputActionsEditorConstants.CommandEvents;
3using System.Collections.Generic;
4using System.Linq;
5using UnityEngine.UIElements;
6
7namespace UnityEngine.InputSystem.Editor
8{
9 /// <summary>
10 /// A list view to display the action maps of the currently opened input actions asset.
11 /// </summary>
12 internal class ActionMapsView : ViewBase<ActionMapsView.ViewState>
13 {
14 public ActionMapsView(VisualElement root, StateContainer stateContainer)
15 : base(root, stateContainer)
16 {
17 m_ListView = root.Q<ListView>("action-maps-list-view");
18 m_ListView.selectionType = UIElements.SelectionType.Single;
19 m_ListView.reorderable = true;
20 m_ListViewSelectionChangeFilter = new CollectionViewSelectionChangeFilter(m_ListView);
21 m_ListViewSelectionChangeFilter.selectedIndicesChanged += (selectedIndices) =>
22 {
23 Dispatch(Commands.SelectActionMap(((ActionMapData)m_ListView.selectedItem).mapName));
24 };
25
26 m_ListView.bindItem = (element, i) =>
27 {
28 var treeViewItem = (InputActionMapsTreeViewItem)element;
29 var mapData = (ActionMapData)m_ListView.itemsSource[i];
30 treeViewItem.label.text = mapData.mapName;
31 treeViewItem.EditTextFinishedCallback = newName => ChangeActionMapName(i, newName);
32 treeViewItem.EditTextFinished += treeViewItem.EditTextFinishedCallback;
33 treeViewItem.userData = i;
34 element.SetEnabled(!mapData.isDisabled);
35
36 ContextMenu.GetContextMenuForActionMapItem(this, treeViewItem, i);
37 };
38 m_ListView.makeItem = () => new InputActionMapsTreeViewItem();
39 m_ListView.unbindItem = (element, i) =>
40 {
41 var treeViewElement = (InputActionMapsTreeViewItem)element;
42 treeViewElement.Reset();
43 treeViewElement.EditTextFinished -= treeViewElement.EditTextFinishedCallback;
44 };
45
46 m_ListView.itemsChosen += objects =>
47 {
48 var item = m_ListView.GetRootElementForIndex(m_ListView.selectedIndex).Q<InputActionMapsTreeViewItem>();
49 item.FocusOnRenameTextField();
50 };
51
52 m_ListView.RegisterCallback<ExecuteCommandEvent>(OnExecuteCommand);
53 m_ListView.RegisterCallback<ValidateCommandEvent>(OnValidateCommand);
54 m_ListView.RegisterCallback<PointerDownEvent>(OnPointerDown, TrickleDown.TrickleDown);
55
56 // ISXB-748 - Scrolling the view causes a visual glitch with the rename TextField. As a work-around we
57 // need to cancel the rename operation in this scenario.
58 m_ListView.RegisterCallback<WheelEvent>(e => InputActionMapsTreeViewItem.CancelRename(), TrickleDown.TrickleDown);
59
60 var treeView = root.Q<TreeView>("actions-tree-view");
61 m_ListView.AddManipulator(new DropManipulator(OnDroppedHandler, treeView));
62 m_ListView.itemIndexChanged += OnReorder;
63
64 CreateSelector(Selectors.GetActionMapNames, Selectors.GetSelectedActionMap, (actionMapNames, actionMap, state) => new ViewState(actionMap, actionMapNames, state.GetDisabledActionMaps(actionMapNames.ToList())));
65
66 m_AddActionMapButton = root.Q<Button>("add-new-action-map-button");
67 m_AddActionMapButton.clicked += AddActionMap;
68
69 ContextMenu.GetContextMenuForActionMapsEmptySpace(this, root.Q<VisualElement>("rclick-area-to-add-new-action-map"));
70 // Only bring up this context menu for the List when it's empty, so we can treat it like right-clicking the empty space:
71 ContextMenu.GetContextMenuForActionMapsEmptySpace(this, m_ListView, onlyShowIfListIsEmpty: true);
72 }
73
74 void OnDroppedHandler(int mapIndex)
75 {
76 Dispatch(Commands.PasteActionIntoActionMap(mapIndex));
77 }
78
79 void OnReorder(int oldIndex, int newIndex)
80 {
81 Dispatch(Commands.ReorderActionMap(oldIndex, newIndex));
82 }
83
84 public override void RedrawUI(ViewState viewState)
85 {
86 m_ListView.itemsSource = viewState.actionMapData?.ToList() ?? new List<ActionMapData>();
87 if (viewState.selectedActionMap.HasValue)
88 {
89 var actionMapData = viewState.actionMapData?.Find(map => map.mapName.Equals(viewState.selectedActionMap.Value.name));
90 if (actionMapData.HasValue)
91 m_ListView.SetSelection(viewState.actionMapData.IndexOf(actionMapData.Value));
92 }
93 m_ListView.RefreshItems();
94 RenameNewActionMaps();
95 }
96
97 public override void DestroyView()
98 {
99 m_AddActionMapButton.clicked -= AddActionMap;
100 }
101
102 private void RenameNewActionMaps()
103 {
104 if (!m_EnterRenamingMode)
105 return;
106 m_ListView.ScrollToItem(m_ListView.selectedIndex);
107 var element = m_ListView.GetRootElementForIndex(m_ListView.selectedIndex);
108 if (element == null)
109 return;
110 ((InputActionMapsTreeViewItem)element).FocusOnRenameTextField();
111 }
112
113 internal void RenameActionMap(int index)
114 {
115 m_ListView.ScrollToItem(index);
116 var element = m_ListView.GetRootElementForIndex(index);
117 if (element == null)
118 return;
119 ((InputActionMapsTreeViewItem)element).FocusOnRenameTextField();
120 }
121
122 internal void DeleteActionMap(int index)
123 {
124 Dispatch(Commands.DeleteActionMap(index));
125 }
126
127 internal void DuplicateActionMap(int index)
128 {
129 Dispatch(Commands.DuplicateActionMap(index));
130 }
131
132 internal void CopyItems()
133 {
134 Dispatch(Commands.CopyActionMapSelection());
135 }
136
137 internal void CutItems()
138 {
139 Dispatch(Commands.CutActionMapSelection());
140 }
141
142 internal void PasteItems(bool copiedAction)
143 {
144 Dispatch(copiedAction ? Commands.PasteActionFromActionMap(InputActionsEditorView.s_OnPasteCutElements) : Commands.PasteActionMaps(InputActionsEditorView.s_OnPasteCutElements));
145 }
146
147 private void ChangeActionMapName(int index, string newName)
148 {
149 m_EnterRenamingMode = false;
150 Dispatch(Commands.ChangeActionMapName(index, newName));
151 }
152
153 internal void AddActionMap()
154 {
155 Dispatch(Commands.AddActionMap());
156 m_EnterRenamingMode = true;
157 }
158
159 internal int GetMapCount()
160 {
161 return m_ListView.itemsSource.Count;
162 }
163
164 private void OnExecuteCommand(ExecuteCommandEvent evt)
165 {
166 var selectedItem = m_ListView.GetRootElementForIndex(m_ListView.selectedIndex);
167 if (selectedItem == null)
168 return;
169
170 if (allowUICommandExecution)
171 {
172 switch (evt.commandName)
173 {
174 case CmdEvents.Rename:
175 ((InputActionMapsTreeViewItem)selectedItem).FocusOnRenameTextField();
176 break;
177 case CmdEvents.Delete:
178 case CmdEvents.SoftDelete:
179 DeleteActionMap(m_ListView.selectedIndex);
180 break;
181 case CmdEvents.Duplicate:
182 DuplicateActionMap(m_ListView.selectedIndex);
183 break;
184 case CmdEvents.Copy:
185 CopyItems();
186 break;
187 case CmdEvents.Cut:
188 CutItems();
189 break;
190 case CmdEvents.Paste:
191 var isActionCopied = CopyPasteHelper.GetCopiedClipboardType() == typeof(InputAction);
192 if (CopyPasteHelper.HasPastableClipboardData(typeof(InputActionMap)))
193 PasteItems(isActionCopied);
194 break;
195 default:
196 return; // Skip StopPropagation if we didn't execute anything
197 }
198
199 // Prevent any UI commands from executing until after UI has been updated
200 allowUICommandExecution = false;
201 }
202 evt.StopPropagation();
203 }
204
205 private void OnValidateCommand(ValidateCommandEvent evt)
206 {
207 // Mark commands as supported for Execute by stopping propagation of the event
208 switch (evt.commandName)
209 {
210 case CmdEvents.Rename:
211 case CmdEvents.Delete:
212 case CmdEvents.SoftDelete:
213 case CmdEvents.Duplicate:
214 case CmdEvents.Copy:
215 case CmdEvents.Cut:
216 case CmdEvents.Paste:
217 evt.StopPropagation();
218 break;
219 }
220 }
221
222 private void OnPointerDown(PointerDownEvent evt)
223 {
224 // Allow right clicks to select an item before we bring up the matching context menu.
225 if (evt.button == (int)MouseButton.RightMouse && evt.clickCount == 1)
226 {
227 var actionMap = (evt.target as VisualElement).GetFirstAncestorOfType<InputActionMapsTreeViewItem>();
228 if (actionMap != null)
229 m_ListView.SetSelection(actionMap.parent.IndexOf(actionMap));
230 }
231 }
232
233 private readonly CollectionViewSelectionChangeFilter m_ListViewSelectionChangeFilter;
234 private bool m_EnterRenamingMode;
235 private readonly ListView m_ListView;
236 private readonly Button m_AddActionMapButton;
237
238 internal struct ActionMapData
239 {
240 internal string mapName;
241 internal bool isDisabled;
242
243 public ActionMapData(string mapName, bool isDisabled)
244 {
245 this.mapName = mapName;
246 this.isDisabled = isDisabled;
247 }
248 }
249
250 internal class ViewState
251 {
252 public SerializedInputActionMap? selectedActionMap;
253 public List<ActionMapData> actionMapData;
254
255 public ViewState(SerializedInputActionMap? selectedActionMap, IEnumerable<string> actionMapNames, IEnumerable<string> disabledActionMapNames)
256 {
257 this.selectedActionMap = selectedActionMap;
258 actionMapData = new List<ActionMapData>();
259 foreach (var name in actionMapNames)
260 {
261 actionMapData.Add(new ActionMapData(name, disabledActionMapNames.Contains(name)));
262 }
263 }
264 }
265 }
266}
267
268#endif