diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs index 1ac676a022..a5c1ce7d12 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs @@ -6,8 +6,6 @@ using UnityEngine.InputSystem.Utilities; using UnityEngine.UIElements; -////TODO: detect if new input backends are enabled and put UI in here to enable them if needed - #pragma warning disable CS0414 namespace UnityEngine.InputSystem.Editor { @@ -54,13 +52,16 @@ private InputSettingsProvider(string path, SettingsScope scopes) public override void OnActivate(string searchContext, VisualElement rootElement) { base.OnActivate(searchContext, rootElement); + m_RootElement = rootElement; InputSystem.onSettingsChange += OnSettingsChange; Undo.undoRedoPerformed += OnUndoRedo; + BuildUI(); } public override void OnDeactivate() { base.OnDeactivate(); + m_RootElement = null; InputSystem.onSettingsChange -= OnSettingsChange; Undo.undoRedoPerformed -= OnUndoRedo; } @@ -88,118 +89,527 @@ public override void OnTitleBarGUI() } } - public override void OnGUI(string searchContext) + private void BuildUI() { + if (m_RootElement == null) + return; + InitializeWithCurrentSettingsIfNecessary(); + m_RootElement.Clear(); - if (m_AvailableInputSettingsAssets.Length == 0) - { - EditorGUILayout.HelpBox( - "Settings for the new input system are stored in an asset. Click the button below to create a settings asset you can edit.", - MessageType.Info); - if (GUILayout.Button("Create settings asset", GUILayout.Height(30))) - CreateNewSettingsAsset("Assets/InputSystem.inputsettings.asset"); - GUILayout.Space(20); - } + m_CreateSettingsAssetContainer = new VisualElement(); + m_CreateSettingsAssetContainer.style.marginBottom = 12; + m_RootElement.Add(m_CreateSettingsAssetContainer); - using (new EditorGUI.DisabledScope(m_AvailableInputSettingsAssets.Length == 0)) + m_CreateSettingsAssetHelpBox = new HelpBox( + "Settings for the new input system are stored in an asset. Click the button below to create a settings asset you can edit.", + HelpBoxMessageType.Info); + m_CreateSettingsAssetContainer.Add(m_CreateSettingsAssetHelpBox); + + m_CreateSettingsAssetButton = new Button(() => CreateNewSettingsAsset("Assets/InputSystem.inputsettings.asset")) { - EditorGUILayout.Space(); - EditorGUILayout.Separator(); - EditorGUILayout.Space(); + text = "Create settings asset" + }; + m_CreateSettingsAssetButton.style.marginTop = 6; + m_CreateSettingsAssetButton.style.height = 30; + m_CreateSettingsAssetContainer.Add(m_CreateSettingsAssetButton); + + var titleLabel = new Label("Settings"); + titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold; + titleLabel.style.fontSize = 19; + titleLabel.style.marginBottom = 12; + m_RootElement.Add(titleLabel); + + m_HeaderContainer = new VisualElement(); + m_RootElement.Add(m_HeaderContainer); - Debug.Assert(m_Settings != null); + m_UpdateModeDropdown = CreateEnumDropdown( + () => m_UpdateMode, + m_UpdateModeContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_UpdateModeDropdown); - EditorGUI.BeginChangeCheck(); + m_UpdateModeHelpContainer = new VisualElement(); - EditorGUILayout.PropertyField(m_UpdateMode, m_UpdateModeContent); - if (InputSystem.settings?.updateMode == InputSettings.UpdateMode.ProcessEventsManually) - CustomUpdateModeHelpBox(); + m_UpdateModeHelpBox = new HelpBox( + "This is not recommended, the default update mode is dynamic update and should only be changed for compelling reasons. Please refer to the documentation.", + HelpBoxMessageType.Warning); + m_UpdateModeHelpContainer.Add(m_UpdateModeHelpBox); - var runInBackground = Application.runInBackground; - using (new EditorGUI.DisabledScope(!runInBackground)) - EditorGUILayout.PropertyField(m_BackgroundBehavior, m_BackgroundBehaviorContent); - if (!runInBackground) - EditorGUILayout.HelpBox("Focus change behavior can only be changed if 'Run In Background' is enabled in Player Settings.", MessageType.Info); + m_UpdateModeReadMoreButton = new Button(OpenUpdateModeDocumentation) + { + text = "Read more" + }; + m_UpdateModeHelpContainer.Add(m_UpdateModeReadMoreButton); + m_HeaderContainer.Add(m_UpdateModeHelpContainer); + + m_BackgroundBehaviorDropdown = CreateEnumDropdown( + () => m_BackgroundBehavior, + m_BackgroundBehaviorContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_BackgroundBehaviorDropdown); + + m_BackgroundBehaviorHelpBox = new HelpBox( + "Focus change behavior can only be changed if 'Run In Background' is enabled in Player Settings.", + HelpBoxMessageType.Info); + m_HeaderContainer.Add(m_BackgroundBehaviorHelpBox); #if UNITY_INPUT_SYSTEM_PLATFORM_SCROLL_DELTA - EditorGUILayout.PropertyField(m_ScrollDeltaBehavior, m_ScrollDeltaBehaviorContent); + m_ScrollDeltaBehaviorDropdown = CreateEnumDropdown( + () => m_ScrollDeltaBehavior, + m_ScrollDeltaBehaviorContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_ScrollDeltaBehaviorDropdown); #endif - EditorGUILayout.Space(); - EditorGUILayout.PropertyField(m_CompensateForScreenOrientation, m_CompensateForScreenOrientationContent); - - // NOTE: We do NOT make showing this one conditional on whether runInBackground is actually set in the - // player settings as regardless of whether it's on or not, Unity will force it on in standalone - // development players. - - EditorGUILayout.Space(); - EditorGUILayout.Separator(); - EditorGUILayout.Space(); - - EditorGUILayout.PropertyField(m_DefaultDeadzoneMin, m_DefaultDeadzoneMinContent); - EditorGUILayout.PropertyField(m_DefaultDeadzoneMax, m_DefaultDeadzoneMaxContent); - EditorGUILayout.PropertyField(m_DefaultButtonPressPoint, m_DefaultButtonPressPointContent); - EditorGUILayout.PropertyField(m_ButtonReleaseThreshold, m_ButtonReleaseThresholdContent); - EditorGUILayout.PropertyField(m_DefaultTapTime, m_DefaultTapTimeContent); - EditorGUILayout.PropertyField(m_DefaultSlowTapTime, m_DefaultSlowTapTimeContent); - EditorGUILayout.PropertyField(m_DefaultHoldTime, m_DefaultHoldTimeContent); - EditorGUILayout.PropertyField(m_TapRadius, m_TapRadiusContent); - EditorGUILayout.PropertyField(m_MultiTapDelayTime, m_MultiTapDelayTimeContent); - - EditorGUILayout.Space(); - EditorGUILayout.Separator(); - EditorGUILayout.Space(); - - EditorGUILayout.HelpBox("Leave 'Supported Devices' empty if you want the input system to support all input devices it can recognize. If, however, " - + "you are only interested in a certain set of devices, adding them here will narrow the scope of what's presented in the editor " - + "and avoid picking up input from devices not relevant to the project. When you add devices here, any device that will not be classified " - + "as supported will appear under 'Unsupported Devices' in the input debugger.", MessageType.None); - - m_SupportedDevices.DoLayoutList(); - - EditorGUILayout.LabelField("iOS", EditorStyles.boldLabel); - EditorGUILayout.Space(); - m_iOSProvider.OnGUI(); - - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Editor", EditorStyles.boldLabel); - EditorGUILayout.Space(); - EditorGUILayout.PropertyField(m_EditorInputBehaviorInPlayMode, m_EditorInputBehaviorInPlayModeContent); - - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Improved Shortcut Support", EditorStyles.boldLabel); - EditorGUILayout.Space(); - EditorGUILayout.PropertyField(m_ShortcutKeysConsumeInputs, m_ShortcutKeysConsumeInputsContent); - if (m_ShortcutKeysConsumeInputs.boolValue) - EditorGUILayout.HelpBox("Please note that enabling Improved Shortcut Support will cause actions with composite bindings to consume input and block any other actions which are enabled and sharing the same controls. " - + "Input consumption is performed in priority order, with the action containing the greatest number of bindings checked first. " - + "Therefore actions requiring fewer keypresses will not be triggered if an action using more keypresses is triggered and has overlapping controls. " - + "This works for shortcut keys, however in other cases this might not give the desired result, especially where there are actions with the exact same number of composite controls, in which case it is non-deterministic which action will be triggered. " - + "These conflicts may occur even between actions which belong to different Action Maps e.g. if using an UIInputModule with the Arrow Keys bound to the Navigate Action in the UI Action Map, this would interfere with other Action Maps using those keys. " - + "However conflicts would not occur between actions which belong to different Action Assets. " - + "Since event consumption only occurs for enabled actions, you can resolve unexpected issues by ensuring that only those Actions or Action Maps that are relevant to your game's current context are enabled. Enabling or disabling actions as your game or application moves between different contexts. " - , MessageType.None); - - if (EditorGUI.EndChangeCheck()) + m_CompensateForScreenOrientationToggle = CreateToggle( + () => m_CompensateForScreenOrientation, + m_CompensateForScreenOrientationContent, + RefreshUIToolkitHeaderState); + m_CompensateForScreenOrientationToggle.style.marginTop = 12; + m_CompensateForScreenOrientationToggle.style.marginBottom = 12; + m_HeaderContainer.Add(m_CompensateForScreenOrientationToggle); + + m_DefaultDeadzoneMinField = CreateFloatField( + () => m_DefaultDeadzoneMin, + m_DefaultDeadzoneMinContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_DefaultDeadzoneMinField); + + m_DefaultDeadzoneMaxField = CreateFloatField( + () => m_DefaultDeadzoneMax, + m_DefaultDeadzoneMaxContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_DefaultDeadzoneMaxField); + + m_DefaultButtonPressPointField = CreateFloatField( + () => m_DefaultButtonPressPoint, + m_DefaultButtonPressPointContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_DefaultButtonPressPointField); + + m_ButtonReleaseThresholdField = CreateFloatField( + () => m_ButtonReleaseThreshold, + m_ButtonReleaseThresholdContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_ButtonReleaseThresholdField); + + m_DefaultTapTimeField = CreateFloatField( + () => m_DefaultTapTime, + m_DefaultTapTimeContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_DefaultTapTimeField); + + m_DefaultSlowTapTimeField = CreateFloatField( + () => m_DefaultSlowTapTime, + m_DefaultSlowTapTimeContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_DefaultSlowTapTimeField); + + m_DefaultHoldTimeField = CreateFloatField( + () => m_DefaultHoldTime, + m_DefaultHoldTimeContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_DefaultHoldTimeField); + + m_TapRadiusField = CreateFloatField( + () => m_TapRadius, + m_TapRadiusContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_TapRadiusField); + + m_MultiTapDelayTimeField = CreateFloatField( + () => m_MultiTapDelayTime, + m_MultiTapDelayTimeContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_MultiTapDelayTimeField); + + m_SupportedDevicesHelpBox = new HelpBox( + "Leave 'Supported Devices' empty if you want the input system to support all input devices it can recognize. If, however, " + + "you are only interested in a certain set of devices, adding them here will narrow the scope of what's presented in the editor " + + "and avoid picking up input from devices not relevant to the project. When you add devices here, any device that will not be classified " + + "as supported will appear under 'Unsupported Devices' in the input debugger.", + HelpBoxMessageType.None); + m_SupportedDevicesHelpBox.style.marginTop = 48; + m_HeaderContainer.Add(m_SupportedDevicesHelpBox); + + var supportedDevicesTitleLabel = new Label("Supported Devices"); + supportedDevicesTitleLabel.style.unityFontStyleAndWeight = FontStyle.Bold; + supportedDevicesTitleLabel.style.marginTop = 6; + m_HeaderContainer.Add(supportedDevicesTitleLabel); + + m_SupportedDevicesListView = new ListView + { + selectionType = UIElements.SelectionType.Single, + reorderable = true, + showBorder = true, + fixedItemHeight = 22 + }; + m_SupportedDevicesListView.makeItem = MakeSupportedDevicesItem; + m_SupportedDevicesListView.bindItem = BindSupportedDevicesItem; + m_SupportedDevicesListView.itemIndexChanged += OnSupportedDevicesReordered; + m_SupportedDevicesListView.selectionChanged += _ => RefreshSupportedDevicesButtonsState(); + m_HeaderContainer.Add(m_SupportedDevicesListView); + + var supportedDevicesButtonsContainer = new VisualElement(); + supportedDevicesButtonsContainer.style.flexDirection = FlexDirection.Row; + supportedDevicesButtonsContainer.style.justifyContent = Justify.FlexEnd; + supportedDevicesButtonsContainer.style.marginTop = 4; + m_HeaderContainer.Add(supportedDevicesButtonsContainer); + + m_AddSupportedDeviceButton = new Button(AddSupportedDevice) + { + text = "Add" + }; + supportedDevicesButtonsContainer.Add(m_AddSupportedDeviceButton); + + m_RemoveSupportedDeviceButton = new Button(RemoveSupportedDevice) + { + text = "Remove" + }; + m_RemoveSupportedDeviceButton.style.marginLeft = 4; + supportedDevicesButtonsContainer.Add(m_RemoveSupportedDeviceButton); + + m_iOSProvider.CreateGUI(m_HeaderContainer, () => + { + Apply(); + RefreshUIToolkitHeaderState(); + }); + + var editorTitleLabel = new Label("Editor"); + editorTitleLabel.style.unityFontStyleAndWeight = FontStyle.Bold; + editorTitleLabel.style.marginTop = 12; + m_HeaderContainer.Add(editorTitleLabel); + + m_EditorInputBehaviorInPlayModeDropdown = CreateEnumDropdown( + () => m_EditorInputBehaviorInPlayMode, + m_EditorInputBehaviorInPlayModeContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_EditorInputBehaviorInPlayModeDropdown); + + var shortcutSupportTitleLabel = new Label("Improved Shortcut Support"); + shortcutSupportTitleLabel.style.unityFontStyleAndWeight = FontStyle.Bold; + shortcutSupportTitleLabel.style.marginTop = 12; + m_HeaderContainer.Add(shortcutSupportTitleLabel); + + m_ShortcutKeysConsumeInputsToggle = CreateToggle( + () => m_ShortcutKeysConsumeInputs, + m_ShortcutKeysConsumeInputsContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_ShortcutKeysConsumeInputsToggle); + + m_ShortcutKeysConsumeInputsHelpBox = new HelpBox( + "Please note that enabling Improved Shortcut Support will cause actions with composite bindings to consume input and block any other actions which are enabled and sharing the same controls. " + + "Input consumption is performed in priority order, with the action containing the greatest number of bindings checked first. " + + "Therefore actions requiring fewer keypresses will not be triggered if an action using more keypresses is triggered and has overlapping controls. " + + "This works for shortcut keys, however in other cases this might not give the desired result, especially where there are actions with the exact same number of composite controls, in which case it is non-deterministic which action will be triggered. " + + "These conflicts may occur even between actions which belong to different Action Maps e.g. if using an UIInputModule with the Arrow Keys bound to the Navigate Action in the UI Action Map, this would interfere with other Action Maps using those keys. " + + "However conflicts would not occur between actions which belong to different Action Assets. " + + "Since event consumption only occurs for enabled actions, you can resolve unexpected issues by ensuring that only those Actions or Action Maps that are relevant to your game's current context are enabled. Enabling or disabling actions as your game or application moves between different contexts. ", + HelpBoxMessageType.None); + m_HeaderContainer.Add(m_ShortcutKeysConsumeInputsHelpBox); + + RefreshUIToolkitHeaderState(); + } + + private DropdownField CreateEnumDropdown(Func propertyAccessor, GUIContent content, Action onValueChanged) + { + var dropdown = new DropdownField(content.text) + { + tooltip = content.tooltip + }; + dropdown.RegisterValueChangedCallback(evt => + { + var property = propertyAccessor(); + if (property == null) + return; + + var newIndex = dropdown.choices?.IndexOf(evt.newValue) ?? -1; + if (newIndex == -1 || property.enumValueIndex == newIndex) + return; + + property.enumValueIndex = newIndex; + Apply(); + onValueChanged?.Invoke(); + }); + + return dropdown; + } + + private Toggle CreateToggle(Func propertyAccessor, GUIContent content, Action onValueChanged) + { + var toggle = new Toggle(content.text) + { + tooltip = content.tooltip + }; + toggle.RegisterValueChangedCallback(evt => + { + var property = propertyAccessor(); + if (property == null || property.boolValue == evt.newValue) + return; + + property.boolValue = evt.newValue; + Apply(); + onValueChanged?.Invoke(); + }); + + return toggle; + } + + private FloatField CreateFloatField(Func propertyAccessor, GUIContent content, Action onValueChanged) + { + var field = new FloatField(content.text) + { + tooltip = content.tooltip + }; + field.RegisterValueChangedCallback(evt => + { + var property = propertyAccessor(); + if (property == null || Mathf.Approximately(property.floatValue, evt.newValue)) + return; + + property.floatValue = evt.newValue; + Apply(); + onValueChanged?.Invoke(); + }); + + return field; + } + + private VisualElement MakeSupportedDevicesItem() + { + var row = new VisualElement(); + row.style.flexDirection = FlexDirection.Row; + row.style.alignItems = Align.Center; + + var icon = new Image + { + name = "icon", + scaleMode = ScaleMode.ScaleToFit + }; + icon.style.width = 20; + icon.style.height = 20; + icon.style.marginRight = 4; + row.Add(icon); + + var label = new Label + { + name = "label" + }; + label.style.flexGrow = 1; + row.Add(label); + + return row; + } + + private void BindSupportedDevicesItem(VisualElement element, int index) + { + var icon = element.Q("icon"); + var label = element.Q