A game about forced loneliness, made by TACStudios
1#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 2using System; 3using System.Collections.Generic; 4using System.Collections.ObjectModel; 5using System.Linq; 6using UnityEditor; 7 8namespace UnityEngine.InputSystem.Editor 9{ 10 [System.Serializable] 11 12 internal class CutElement 13 { 14 private Guid id; 15 internal Type type; 16 17 public CutElement(Guid id, Type type) 18 { 19 this.id = id; 20 this.type = type; 21 } 22 23 public int GetIndexOfProperty(InputActionsEditorState state) 24 { 25 if (type == typeof(InputActionMap)) 26 { 27 var actionMap = state.serializedObject 28 ?.FindProperty(nameof(InputActionAsset.m_ActionMaps)) 29 ?.FirstOrDefault(s => InputActionSerializationHelpers.GetId(s).Equals(id)); 30 return actionMap.GetIndexOfArrayElement(); 31 } 32 33 if (type == typeof(InputAction)) 34 { 35 var action = Selectors.GetActionMapAtIndex(state, actionMapIndex(state))?.wrappedProperty.FindPropertyRelative("m_Actions").FirstOrDefault(a => InputActionSerializationHelpers.GetId(a).Equals(id)); 36 return action.GetIndexOfArrayElement(); 37 } 38 39 if (type == typeof(InputBinding)) 40 { 41 var binding = Selectors.GetBindingForId(state, id.ToString(), 42 out _); 43 return binding.GetIndexOfArrayElement(); 44 } 45 return -1; 46 } 47 48 public int actionMapIndex(InputActionsEditorState state) => type == typeof(InputActionMap) ? GetIndexOfProperty(state) : GetActionMapIndex(state); 49 50 private int GetActionMapIndex(InputActionsEditorState state) 51 { 52 var actionMaps = state.serializedObject?.FindProperty(nameof(InputActionAsset.m_ActionMaps)); 53 var cutActionMapIndex = state.serializedObject 54 ?.FindProperty(nameof(InputActionAsset.m_ActionMaps)) 55 ?.FirstOrDefault(s => s.FindPropertyRelative("m_Id").stringValue.Equals(id)).GetIndexOfArrayElement(); 56 if (type == typeof(InputBinding)) 57 cutActionMapIndex = actionMaps.FirstOrDefault(map => map.FindPropertyRelative("m_Bindings").Select(InputActionSerializationHelpers.GetId).Contains(id)).GetIndexOfArrayElement(); 58 else if (type == typeof(InputAction)) 59 cutActionMapIndex = actionMaps.FirstOrDefault(map => map.FindPropertyRelative("m_Actions").Select(InputActionSerializationHelpers.GetId).Contains(id)).GetIndexOfArrayElement(); 60 return cutActionMapIndex ?? -1; 61 } 62 } 63 internal struct InputActionsEditorState 64 { 65 public int selectedActionMapIndex { get {return m_selectedActionMapIndex; } } 66 public int selectedActionIndex { get {return m_selectedActionIndex; } } 67 public int selectedBindingIndex { get {return m_selectedBindingIndex; } } 68 public SelectionType selectionType { get {return m_selectionType; } } 69 public SerializedObject serializedObject { get; } // Note that state doesn't own this disposable object 70 private readonly List<CutElement> cutElements => m_CutElements; 71 72 // Control schemes 73 public int selectedControlSchemeIndex { get { return m_selectedControlSchemeIndex; } } 74 public int selectedDeviceRequirementIndex { get {return m_selectedDeviceRequirementIndex; } } 75 public InputControlScheme selectedControlScheme => m_ControlScheme; // TODO Bad this either po 76 77 internal InputActionsEditorSessionAnalytic m_Analytics; 78 79 [SerializeField] int m_selectedActionMapIndex; 80 [SerializeField] int m_selectedActionIndex; 81 [SerializeField] int m_selectedBindingIndex; 82 [SerializeField] SelectionType m_selectionType; 83 [SerializeField] int m_selectedControlSchemeIndex; 84 [SerializeField] int m_selectedDeviceRequirementIndex; 85 private List<CutElement> m_CutElements; 86 internal bool hasCutElements => m_CutElements != null && m_CutElements.Count > 0; 87 88 public InputActionsEditorState( 89 InputActionsEditorSessionAnalytic analytics, 90 SerializedObject inputActionAsset, 91 int selectedActionMapIndex = 0, 92 int selectedActionIndex = 0, 93 int selectedBindingIndex = 0, 94 SelectionType selectionType = SelectionType.Action, 95 Dictionary<(string, string), HashSet<int>> expandedBindingIndices = null, 96 InputControlScheme selectedControlScheme = default, 97 int selectedControlSchemeIndex = -1, 98 int selectedDeviceRequirementIndex = -1, 99 List<CutElement> cutElements = null) 100 { 101 Debug.Assert(inputActionAsset != null); 102 103 m_Analytics = analytics; 104 105 serializedObject = inputActionAsset; 106 107 m_selectedActionMapIndex = selectedActionMapIndex; 108 m_selectedActionIndex = selectedActionIndex; 109 m_selectedBindingIndex = selectedBindingIndex; 110 m_selectionType = selectionType; 111 m_ControlScheme = selectedControlScheme; 112 m_selectedControlSchemeIndex = selectedControlSchemeIndex; 113 m_selectedDeviceRequirementIndex = selectedDeviceRequirementIndex; 114 115 m_ExpandedCompositeBindings = expandedBindingIndices == null ? 116 new Dictionary<(string, string), HashSet<int>>() : 117 new Dictionary<(string, string), HashSet<int>>(expandedBindingIndices); 118 m_CutElements = cutElements; 119 } 120 121 public InputActionsEditorState(InputActionsEditorState other, SerializedObject asset) 122 { 123 m_Analytics = other.m_Analytics; 124 125 // Assign serialized object, not that this might be equal to other.serializedObject, 126 // a slight variation of it with any kind of changes or a completely different one. 127 // Hence, we do our best here to keep any selections consistent by remapping objects 128 // based on GUIDs (IDs) and when it fails, attempt to select first object and if that 129 // fails revert to not having a selection. This would even be true for domain reloads 130 // if the asset would be modified during domain reload. 131 serializedObject = asset; 132 133 if (other.Equals(default(InputActionsEditorState))) 134 { 135 // This instance was created by default constructor and thus is missing some appropriate defaults: 136 other.m_selectionType = SelectionType.Action; 137 other.m_selectedControlSchemeIndex = -1; 138 other.m_selectedDeviceRequirementIndex = -1; 139 } 140 141 // Attempt to preserve action map selection by GUID, otherwise select first or last resort none 142 var otherSelectedActionMap = other.GetSelectedActionMap(); 143 var actionMapCount = Selectors.GetActionMapCount(asset); 144 m_selectedActionMapIndex = otherSelectedActionMap != null 145 ? Selectors.GetActionMapIndexFromId(asset, 146 InputActionSerializationHelpers.GetId(otherSelectedActionMap)) 147 : actionMapCount > 0 ? 0 : -1; 148 var selectedActionMap = m_selectedActionMapIndex >= 0 149 ? Selectors.GetActionMapAtIndex(asset, m_selectedActionMapIndex)?.wrappedProperty : null; 150 151 // Attempt to preserve action selection by GUID, otherwise select first or last resort none 152 var otherSelectedAction = m_selectedActionMapIndex >= 0 ? 153 Selectors.GetSelectedAction(other) : null; 154 m_selectedActionIndex = selectedActionMap != null && otherSelectedAction.HasValue 155 ? Selectors.GetActionIndexFromId(selectedActionMap, 156 InputActionSerializationHelpers.GetId(otherSelectedAction.Value.wrappedProperty)) 157 : Selectors.GetActionCount(selectedActionMap) > 0 ? 0 : -1; 158 159 // Attempt to preserve binding selection by GUID, otherwise select first or none 160 m_selectedBindingIndex = -1; 161 if (m_selectedActionMapIndex >= 0) 162 { 163 var otherSelectedBinding = Selectors.GetSelectedBinding(other); 164 if (otherSelectedBinding != null) 165 { 166 var otherSelectedBindingId = 167 InputActionSerializationHelpers.GetId(otherSelectedBinding.Value.wrappedProperty); 168 var binding = Selectors.GetBindingForId(asset, otherSelectedBindingId.ToString(), out _); 169 if (binding != null) 170 m_selectedBindingIndex = binding.GetIndexOfArrayElement(); 171 } 172 } 173 174 // Sanity check selection type and override any previous selection if not valid given indices 175 // since we have remapped GUIDs to selection indices for another asset (SerializedObject) 176 if (other.m_selectionType == SelectionType.Binding && m_selectedBindingIndex < 0) 177 m_selectionType = SelectionType.Action; 178 else 179 m_selectionType = other.m_selectionType; 180 181 m_selectedControlSchemeIndex = other.m_selectedControlSchemeIndex; 182 m_selectedDeviceRequirementIndex = other.m_selectedDeviceRequirementIndex; 183 184 // Selected ControlScheme index is serialized but we have to recreated actual object after domain reload. 185 // In case asset is different from from others asset the index might not even be valid range so we need 186 // to reattempt to preserve selection but range adapt. 187 // Note that control schemes and device requirements currently lack any GUID/ID to be uniquely identified. 188 var controlSchemesArrayProperty = serializedObject.FindProperty(nameof(InputActionAsset.m_ControlSchemes)); 189 if (m_selectedControlSchemeIndex >= 0 && controlSchemesArrayProperty.arraySize > 0) 190 { 191 if (m_selectedControlSchemeIndex >= controlSchemesArrayProperty.arraySize) 192 m_selectedControlSchemeIndex = 0; 193 m_ControlScheme = new InputControlScheme( 194 controlSchemesArrayProperty.GetArrayElementAtIndex(other.m_selectedControlSchemeIndex)); 195 // TODO Preserve device requirement index 196 } 197 else 198 { 199 m_selectedControlSchemeIndex = -1; 200 m_selectedDeviceRequirementIndex = -1; 201 m_ControlScheme = new InputControlScheme(); 202 } 203 204 // Editor may leave these as null after domain reloads, so recreate them in that case. 205 // If they exist, we attempt to just preserve the same expanded items based on name for now for simplicity. 206 m_ExpandedCompositeBindings = other.m_ExpandedCompositeBindings == null ? 207 new Dictionary<(string, string), HashSet<int>>() : 208 new Dictionary<(string, string), HashSet<int>>(other.m_ExpandedCompositeBindings); 209 210 m_CutElements = other.cutElements; 211 } 212 213 public InputActionsEditorState With( 214 int? selectedActionMapIndex = null, 215 int? selectedActionIndex = null, 216 int? selectedBindingIndex = null, 217 SelectionType? selectionType = null, 218 InputControlScheme? selectedControlScheme = null, 219 int? selectedControlSchemeIndex = null, 220 int? selectedDeviceRequirementIndex = null, 221 Dictionary<(string, string), HashSet<int>> expandedBindingIndices = null, 222 List<CutElement> cutElements = null) 223 { 224 return new InputActionsEditorState( 225 m_Analytics, 226 serializedObject, 227 selectedActionMapIndex ?? this.selectedActionMapIndex, 228 selectedActionIndex ?? this.selectedActionIndex, 229 selectedBindingIndex ?? this.selectedBindingIndex, 230 selectionType ?? this.selectionType, 231 expandedBindingIndices ?? m_ExpandedCompositeBindings, 232 233 // Control schemes 234 selectedControlScheme ?? this.selectedControlScheme, 235 selectedControlSchemeIndex ?? this.selectedControlSchemeIndex, 236 selectedDeviceRequirementIndex ?? this.selectedDeviceRequirementIndex, 237 238 cutElements ?? m_CutElements 239 ); 240 } 241 242 public InputActionsEditorState ClearCutElements() 243 { 244 return new InputActionsEditorState( 245 m_Analytics, 246 serializedObject, 247 selectedActionMapIndex, 248 selectedActionIndex, 249 selectedBindingIndex, 250 selectionType, 251 m_ExpandedCompositeBindings, 252 selectedControlScheme, 253 selectedControlSchemeIndex, 254 selectedDeviceRequirementIndex, 255 cutElements: null); 256 } 257 258 public SerializedProperty GetActionMapByName(string actionMapName) 259 { 260 return serializedObject 261 .FindProperty(nameof(InputActionAsset.m_ActionMaps)) 262 .FirstOrDefault(p => p.FindPropertyRelative(nameof(InputActionMap.m_Name)).stringValue == actionMapName); 263 } 264 265 public InputActionsEditorState ExpandCompositeBinding(SerializedInputBinding binding) 266 { 267 var key = GetSelectedActionMapAndActionKey(); 268 269 var expandedCompositeBindings = new Dictionary<(string, string), HashSet<int>>(m_ExpandedCompositeBindings); 270 if (!expandedCompositeBindings.TryGetValue(key, out var expandedStates)) 271 { 272 expandedStates = new HashSet<int>(); 273 expandedCompositeBindings.Add(key, expandedStates); 274 } 275 276 expandedStates.Add(binding.indexOfBinding); 277 278 return With(expandedBindingIndices: expandedCompositeBindings); 279 } 280 281 public InputActionsEditorState CollapseCompositeBinding(SerializedInputBinding binding) 282 { 283 var key = GetSelectedActionMapAndActionKey(); 284 285 if (m_ExpandedCompositeBindings.ContainsKey(key) == false) 286 throw new InvalidOperationException("Trying to collapse a composite binding tree that was never expanded."); 287 288 // do the dance of C# immutability 289 var oldExpandedCompositeBindings = m_ExpandedCompositeBindings; 290 var expandedCompositeBindings = oldExpandedCompositeBindings.Keys.Where(dictKey => dictKey != key) 291 .ToDictionary(dictKey => dictKey, dictKey => oldExpandedCompositeBindings[dictKey]); 292 var newHashset = new HashSet<int>(m_ExpandedCompositeBindings[key].Where(index => index != binding.indexOfBinding)); 293 expandedCompositeBindings.Add(key, newHashset); 294 295 return With(expandedBindingIndices: expandedCompositeBindings); 296 } 297 298 public InputActionsEditorState SelectAction(string actionName) 299 { 300 var actionMap = GetSelectedActionMap(); 301 var actions = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Actions)); 302 303 for (var i = 0; i < actions.arraySize; i++) 304 { 305 if (actions.GetArrayElementAtIndex(i) 306 .FindPropertyRelative(nameof(InputAction.m_Name)).stringValue != actionName) continue; 307 308 return With(selectedActionIndex: i, selectionType: SelectionType.Action); 309 } 310 311 // If we cannot find the desired map we should return invalid index 312 return With(selectedActionIndex: -1, selectionType: SelectionType.Action); 313 } 314 315 public InputActionsEditorState SelectAction(SerializedProperty state) 316 { 317 var index = state.GetIndexOfArrayElement(); 318 return With(selectedActionIndex: index, selectionType: SelectionType.Action); 319 } 320 321 public InputActionsEditorState SelectActionMap(SerializedProperty actionMap) 322 { 323 var index = actionMap.GetIndexOfArrayElement(); 324 return With(selectedBindingIndex: 0, selectedActionMapIndex: index, selectedActionIndex: 0); 325 } 326 327 public InputActionsEditorState SelectActionMap(string actionMapName) 328 { 329 var actionMap = GetActionMapByName(actionMapName); 330 return With(selectedBindingIndex: 0, 331 selectedActionMapIndex: actionMap.GetIndexOfArrayElement(), 332 selectedActionIndex: 0, selectionType: SelectionType.Action); 333 } 334 335 public InputActionsEditorState SelectBinding(int index) 336 { 337 //if no binding selected (due to no bindings in list) set selection type to action 338 if (index == -1) 339 return With(selectedBindingIndex: index, selectionType: SelectionType.Action); 340 return With(selectedBindingIndex: index, selectionType: SelectionType.Binding); 341 } 342 343 public InputActionsEditorState SelectAction(int index) 344 { 345 //if no action selected (no actions available) set selection type to none 346 if (index == -1) 347 return With(selectedActionIndex: index, selectionType: SelectionType.None); 348 return With(selectedActionIndex: index, selectionType: SelectionType.Action); 349 } 350 351 public InputActionsEditorState SelectActionMap(int index) 352 { 353 if (index == -1) 354 return With(selectedActionMapIndex: index, selectionType: SelectionType.None); 355 return With(selectedBindingIndex: 0, 356 selectedActionMapIndex: index, 357 selectedActionIndex: 0, selectionType: SelectionType.Action); 358 } 359 360 public InputActionsEditorState CutActionOrBinding() 361 { 362 m_CutElements = new List<CutElement>(); 363 var type = selectionType == SelectionType.Action ? typeof(InputAction) : typeof(InputBinding); 364 var property = selectionType == SelectionType.Action ? Selectors.GetSelectedAction(this)?.wrappedProperty : Selectors.GetSelectedBinding(this)?.wrappedProperty; 365 cutElements.Add(new CutElement(InputActionSerializationHelpers.GetId(property), type)); 366 return With(cutElements: cutElements); 367 } 368 369 public InputActionsEditorState CutActionMaps() 370 { 371 m_CutElements = new List<CutElement> { new(InputActionSerializationHelpers.GetId(Selectors.GetSelectedActionMap(this)?.wrappedProperty), typeof(InputActionMap)) }; 372 return With(cutElements: cutElements); 373 } 374 375 public IEnumerable<string> GetDisabledActionMaps(List<string> allActionMaps) 376 { 377 if (cutElements == null || cutElements == null) 378 return Enumerable.Empty<string>(); 379 var cutActionMaps = cutElements.Where(cut => cut.type == typeof(InputActionMap)); 380 var state = this; 381 return allActionMaps.Where(actionMapName => 382 { 383 return cutActionMaps.Any(am => am.GetIndexOfProperty(state) == allActionMaps.IndexOf(actionMapName)); 384 }); 385 } 386 387 public readonly bool IsBindingCut(int actionMapIndex, int bindingIndex) 388 { 389 if (cutElements == null) 390 return false; 391 392 var state = this; 393 return cutElements.Any(cutElement => cutElement.actionMapIndex(state) == actionMapIndex && 394 cutElement.GetIndexOfProperty(state) == bindingIndex && 395 cutElement.type == typeof(InputBinding)); 396 } 397 398 public readonly bool IsActionCut(int actionMapIndex, int actionIndex) 399 { 400 if (cutElements == null) 401 return false; 402 403 var state = this; 404 return cutElements.Any(cutElement => cutElement.actionMapIndex(state) == actionMapIndex && 405 cutElement.GetIndexOfProperty(state) == actionIndex && 406 cutElement.type == typeof(InputAction)); 407 } 408 409 public readonly bool IsActionMapCut(int actionMapIndex) 410 { 411 if (cutElements == null) 412 return false; 413 var state = this; 414 return cutElements.Any(cutElement => cutElement.GetIndexOfProperty(state) == actionMapIndex && cutElement.type == typeof(InputActionMap)); 415 } 416 417 public readonly List<CutElement> GetCutElements() 418 { 419 return m_CutElements; 420 } 421 422 public ReadOnlyCollection<int> GetOrCreateExpandedState() 423 { 424 return new ReadOnlyCollection<int>(GetOrCreateExpandedStateInternal().ToList()); 425 } 426 427 private HashSet<int> GetOrCreateExpandedStateInternal() 428 { 429 var key = GetSelectedActionMapAndActionKey(); 430 431 if (m_ExpandedCompositeBindings.TryGetValue(key, out var expandedStates)) 432 return expandedStates; 433 434 expandedStates = new HashSet<int>(); 435 m_ExpandedCompositeBindings.Add(key, expandedStates); 436 return expandedStates; 437 } 438 439 internal (string, string) GetSelectedActionMapAndActionKey() 440 { 441 var selectedActionMap = GetSelectedActionMap(); 442 443 var selectedAction = selectedActionMap 444 .FindPropertyRelative(nameof(InputActionMap.m_Actions)) 445 .GetArrayElementAtIndex(selectedActionIndex); 446 447 var key = ( 448 selectedActionMap.FindPropertyRelative(nameof(InputActionMap.m_Name)).stringValue, 449 selectedAction.FindPropertyRelative(nameof(InputAction.m_Name)).stringValue 450 ); 451 return key; 452 } 453 454 private SerializedProperty GetSelectedActionMap() 455 { 456 return Selectors.GetActionMapAtIndex(serializedObject, selectedActionMapIndex)?.wrappedProperty; 457 } 458 459 /// <summary> 460 /// Expanded states for the actions tree view. These are stored per InputActionMap 461 /// </summary> 462 private readonly Dictionary<(string, string), HashSet<int>> m_ExpandedCompositeBindings; 463 464 private readonly InputControlScheme m_ControlScheme; 465 } 466 467 internal enum SelectionType 468 { 469 None, 470 Action, 471 Binding 472 } 473} 474 475#endif