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.Linq; 5using UnityEditor; 6using UnityEngine.UIElements; 7using PopupWindow = UnityEngine.UIElements.PopupWindow; 8 9namespace UnityEngine.InputSystem.Editor 10{ 11 internal class ControlSchemesView : ViewBase<InputControlScheme> 12 { 13 //is used to save the new name of the control scheme when renaming 14 private string m_NewName; 15 public event Action<ViewBase<InputControlScheme>> OnClosing; 16 17 public ControlSchemesView(VisualElement root, StateContainer stateContainer, bool updateExisting = false) 18 : base(root, stateContainer) 19 { 20 m_UpdateExisting = updateExisting; 21 22 var controlSchemeEditor = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>( 23 InputActionsEditorConstants.PackagePath + 24 InputActionsEditorConstants.ResourcesPath + 25 InputActionsEditorConstants.ControlSchemeEditorViewUxml); 26 27 var controlSchemeVisualElement = controlSchemeEditor.CloneTree(); 28 controlSchemeVisualElement.Q<Button>(kCancelButton).clicked += Cancel; 29 controlSchemeVisualElement.Q<Button>(kSaveButton).clicked += SaveAndClose; 30 controlSchemeVisualElement.Q<TextField>(kControlSchemeNameTextField).RegisterCallback<FocusOutEvent>(evt => 31 { 32 Dispatch((in InputActionsEditorState state) => 33 { 34 // If the name is the same as the current name, don't change it 35 var newName = ((TextField)evt.currentTarget).value.Trim(); 36 if (string.IsNullOrEmpty(newName) || String.Compare(newName, state.selectedControlScheme.name) == 0) 37 { 38 m_NewName = String.Empty; 39 // write back the value to the text field if the name was empty 40 ((TextField)evt.currentTarget).value = state.selectedControlScheme.name; 41 } 42 else 43 { 44 m_NewName = ControlSchemeCommands.MakeUniqueControlSchemeName(state, newName); 45 // write back the value to the text field if the name was not unique 46 ((TextField)evt.currentTarget).value = m_NewName; 47 } 48 49 return state.With(selectedControlScheme: state.selectedControlScheme); 50 }); 51 }); 52 53 m_ModalWindow = new VisualElement 54 { 55 style = { position = new StyleEnum<Position>(Position.Absolute) } 56 }; 57 var popupWindow = new PopupWindow 58 { 59 text = "Add Control Scheme", 60 style = { position = new StyleEnum<Position>(Position.Absolute) } 61 }; 62 popupWindow.contentContainer.Add(controlSchemeVisualElement); 63 m_ModalWindow.Add(popupWindow); 64 root.Add(m_ModalWindow); 65 m_ModalWindow.StretchToParentSize(); 66 m_ModalWindow.RegisterCallback<ClickEvent>(evt => CloseView()); 67 popupWindow.RegisterCallback<ClickEvent>(evt => evt.StopPropagation()); 68 69 m_ListView = controlSchemeVisualElement.Q<MultiColumnListView>(kControlSchemesListView); 70 m_ListView.columns[kDeviceTypeColumnName].makeCell = () => new Label(); 71 m_ListView.columns[kDeviceTypeColumnName].bindCell = BindDeviceTypeCell; 72 73 m_ListView.columns[kRequiredColumnName].makeCell = MakeRequiredCell; 74 m_ListView.columns[kRequiredColumnName].bindCell = BindDeviceRequiredCell; 75 m_ListView.columns[kRequiredColumnName].unbindCell = UnbindDeviceRequiredCell; 76 77 m_ListView.Q<Button>(kUnityListViewAddButton).clickable = new Clickable(AddDeviceRequirement); 78 m_ListView.Q<Button>(kUnityListViewRemoveButton).clickable = new Clickable(RemoveDeviceRequirement); 79 80 m_ListView.itemIndexChanged += (oldPosition, newPosition) => 81 { 82 Dispatch(ControlSchemeCommands.ReorderDeviceRequirements(oldPosition, newPosition)); 83 }; 84 85 m_ListView.itemsSource = new List<string>(); 86 87 CreateSelector(s => s.selectedControlScheme, 88 (_, s) => s.selectedControlScheme); 89 } 90 91 private void AddDeviceRequirement() 92 { 93 var dropdown = new InputControlPickerDropdown(new InputControlPickerState(), path => 94 { 95 var requirement = new InputControlScheme.DeviceRequirement { controlPath = path, isOptional = false }; 96 Dispatch(ControlSchemeCommands.AddDeviceRequirement(requirement)); 97 }, mode: InputControlPicker.Mode.PickDevice); 98 dropdown.Show(new Rect(Event.current.mousePosition, Vector2.zero)); 99 } 100 101 private void RemoveDeviceRequirement() 102 { 103 if (m_ListView.selectedIndex == -1) 104 return; 105 106 Dispatch(ControlSchemeCommands.RemoveDeviceRequirement(m_ListView.selectedIndex)); 107 } 108 109 public override void RedrawUI(InputControlScheme viewState) 110 { 111 rootElement.Q<TextField>(kControlSchemeNameTextField).value = string.IsNullOrEmpty(m_NewName) ? viewState.name : m_NewName; 112 113 m_ListView.itemsSource?.Clear(); 114 m_ListView.itemsSource = viewState.deviceRequirements.Count > 0 ? 115 viewState.deviceRequirements.Select(r => (r.controlPath, r.isOptional)).ToList() : 116 new List<(string, bool)>(); 117 m_ListView.Rebuild(); 118 } 119 120 public override void DestroyView() 121 { 122 m_ModalWindow.RemoveFromHierarchy(); 123 } 124 125 private void SaveAndClose() 126 { 127 // Persist the current ControlScheme values to the SerializedProperty 128 Dispatch(ControlSchemeCommands.SaveControlScheme(m_NewName, m_UpdateExisting)); 129 CloseView(); 130 } 131 132 private void Cancel() 133 { 134 // Reload the selected ControlScheme values from the SerilaizedProperty and throw away any changes 135 Dispatch(ControlSchemeCommands.ResetSelectedControlScheme()); 136 CloseView(); 137 } 138 139 private void CloseView() 140 { 141 // Closing the View without explicitly selecting "Save" or "Cancel" holds the values in the 142 // current UI state but won't persist them; the Asset Editor state isn't dirtied. 143 // 144 // This means accidentally clicking outside of the View (closes the dialog) won't immediately 145 // cause loss of work: the "Edit Control Scheme" option can be selected to re-open the View 146 // the changes retained. However, if a different ControlScheme is selected or the Asset 147 // Editor window is closed, then the changes are lost. 148 149 m_NewName = string.Empty; 150 OnClosing?.Invoke(this); 151 } 152 153 private VisualElement MakeRequiredCell() 154 { 155 var ve = new VisualElement 156 { 157 style = 158 { 159 flexDirection = FlexDirection.Column, 160 flexGrow = 1, 161 alignContent = new StyleEnum<Align>(Align.Center) 162 } 163 }; 164 ve.Add(new Toggle()); 165 return ve; 166 } 167 168 private void BindDeviceRequiredCell(VisualElement visualElement, int rowIndex) 169 { 170 var toggle = visualElement.Q<Toggle>(); 171 var rowItem = ((string path, bool optional))m_ListView.itemsSource[rowIndex]; 172 173 toggle.value = !rowItem.optional; 174 var eventCallback = (EventCallback<ChangeEvent<bool>>)(evt => 175 Dispatch(ControlSchemeCommands.ChangeDeviceRequirement(rowIndex, evt.newValue))); 176 toggle.userData = eventCallback; 177 178 toggle.RegisterValueChangedCallback(eventCallback); 179 } 180 181 private void UnbindDeviceRequiredCell(VisualElement visualElement, int rowIndex) 182 { 183 var toggle = visualElement.Q<Toggle>(); 184 toggle.UnregisterValueChangedCallback((EventCallback<ChangeEvent<bool>>)toggle.userData); 185 } 186 187 private void BindDeviceTypeCell(VisualElement visualElement, int rowIndex) 188 { 189 ((Label)visualElement).text = (((string, bool))m_ListView.itemsSource[rowIndex]).Item1; 190 } 191 192 private readonly bool m_UpdateExisting; 193 private MultiColumnListView m_ListView; 194 private VisualElement m_ModalWindow; 195 196 private const string kControlSchemeNameTextField = "control-scheme-name"; 197 private const string kCancelButton = "cancel-button"; 198 private const string kSaveButton = "save-button"; 199 private const string kControlSchemesListView = "control-schemes-list-view"; 200 private const string kDeviceTypeColumnName = "device-type"; 201 private const string kRequiredColumnName = "required"; 202 private const string kUnityListViewAddButton = "unity-list-view__add-button"; 203 private const string kUnityListViewRemoveButton = "unity-list-view__remove-button"; 204 } 205} 206 207#endif