diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Settings/InputSettingsProvider.cs index 1ac676a022..3835d9b582 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; } @@ -89,10 +90,29 @@ public override void OnTitleBarGUI() } public override void OnGUI(string searchContext) + { + if (m_RootElement != null) + return; + + DrawSettingsGUI(includeUIToolkitHeader: false); + } + + private void DrawSupportedDevicesGUI() + { + InitializeWithCurrentSettingsIfNecessary(); + + using (new EditorGUI.DisabledScope(m_AvailableInputSettingsAssets.Length == 0)) + { + Debug.Assert(m_Settings != null); + m_SupportedDevices.DoLayoutList(); + } + } + + private void DrawSettingsGUI(bool includeUIToolkitHeader) { InitializeWithCurrentSettingsIfNecessary(); - if (m_AvailableInputSettingsAssets.Length == 0) + if (!includeUIToolkitHeader && 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.", @@ -112,96 +132,460 @@ public override void OnGUI(string searchContext) EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(m_UpdateMode, m_UpdateModeContent); - if (InputSystem.settings?.updateMode == InputSettings.UpdateMode.ProcessEventsManually) - CustomUpdateModeHelpBox(); + if (!includeUIToolkitHeader) + { + EditorGUILayout.PropertyField(m_UpdateMode, m_UpdateModeContent); + if (InputSystem.settings?.updateMode == InputSettings.UpdateMode.ProcessEventsManually) + CustomUpdateModeHelpBox(); - 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); + 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); #if UNITY_INPUT_SYSTEM_PLATFORM_SCROLL_DELTA - EditorGUILayout.PropertyField(m_ScrollDeltaBehavior, m_ScrollDeltaBehaviorContent); + EditorGUILayout.PropertyField(m_ScrollDeltaBehavior, m_ScrollDeltaBehaviorContent); #endif - EditorGUILayout.Space(); - EditorGUILayout.PropertyField(m_CompensateForScreenOrientation, m_CompensateForScreenOrientationContent); + 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); + } - EditorGUILayout.Space(); - EditorGUILayout.Separator(); - EditorGUILayout.Space(); + if (EditorGUI.EndChangeCheck()) + Apply(); + } + } - 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); + private void BuildUI() + { + if (m_RootElement == null) + return; - EditorGUILayout.Space(); - EditorGUILayout.Separator(); - EditorGUILayout.Space(); + InitializeWithCurrentSettingsIfNecessary(); + m_RootElement.Clear(); - 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_CreateSettingsAssetContainer = new VisualElement(); + m_CreateSettingsAssetContainer.style.marginBottom = 12; + m_RootElement.Add(m_CreateSettingsAssetContainer); - m_SupportedDevices.DoLayoutList(); + 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); - EditorGUILayout.LabelField("iOS", EditorStyles.boldLabel); - EditorGUILayout.Space(); - m_iOSProvider.OnGUI(); + m_CreateSettingsAssetButton = new Button(() => CreateNewSettingsAsset("Assets/InputSystem.inputsettings.asset")) + { + text = "Create settings asset" + }; + m_CreateSettingsAssetButton.style.marginTop = 6; + m_CreateSettingsAssetButton.style.height = 30; + m_CreateSettingsAssetContainer.Add(m_CreateSettingsAssetButton); - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Editor", EditorStyles.boldLabel); - EditorGUILayout.Space(); - EditorGUILayout.PropertyField(m_EditorInputBehaviorInPlayMode, m_EditorInputBehaviorInPlayModeContent); + var titleLabel = new Label("Settings"); + titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold; + titleLabel.style.fontSize = 19; + titleLabel.style.marginBottom = 12; + m_RootElement.Add(titleLabel); - 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); + m_HeaderContainer = new VisualElement(); + m_RootElement.Add(m_HeaderContainer); - if (EditorGUI.EndChangeCheck()) - Apply(); + m_UpdateModeDropdown = CreateEnumDropdown( + () => m_UpdateMode, + m_UpdateModeContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_UpdateModeDropdown); + + m_UpdateModeHelpContainer = new VisualElement(); + + 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); + + 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 + m_ScrollDeltaBehaviorDropdown = CreateEnumDropdown( + () => m_ScrollDeltaBehavior, + m_ScrollDeltaBehaviorContent, + RefreshUIToolkitHeaderState); + m_HeaderContainer.Add(m_ScrollDeltaBehaviorDropdown); +#endif + + 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); + + m_RootElement.Add(new IMGUIContainer(DrawSupportedDevicesGUI)); + + var lowerSectionsContainer = new VisualElement(); + m_RootElement.Add(lowerSectionsContainer); + + m_iOSProvider.CreateGUI(lowerSectionsContainer, () => + { + Apply(); + RefreshUIToolkitHeaderState(); + }); + + var editorTitleLabel = new Label("Editor"); + editorTitleLabel.style.unityFontStyleAndWeight = FontStyle.Bold; + editorTitleLabel.style.marginTop = 12; + lowerSectionsContainer.Add(editorTitleLabel); + + m_EditorInputBehaviorInPlayModeDropdown = CreateEnumDropdown( + () => m_EditorInputBehaviorInPlayMode, + m_EditorInputBehaviorInPlayModeContent, + RefreshUIToolkitHeaderState); + lowerSectionsContainer.Add(m_EditorInputBehaviorInPlayModeDropdown); + + var shortcutSupportTitleLabel = new Label("Improved Shortcut Support"); + shortcutSupportTitleLabel.style.unityFontStyleAndWeight = FontStyle.Bold; + shortcutSupportTitleLabel.style.marginTop = 12; + lowerSectionsContainer.Add(shortcutSupportTitleLabel); + + m_ShortcutKeysConsumeInputsToggle = CreateToggle( + () => m_ShortcutKeysConsumeInputs, + m_ShortcutKeysConsumeInputsContent, + RefreshUIToolkitHeaderState); + lowerSectionsContainer.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); + lowerSectionsContainer.Add(m_ShortcutKeysConsumeInputsHelpBox); + + m_IMGUIContainer = new IMGUIContainer(() => DrawSettingsGUI(includeUIToolkitHeader: true)); + m_RootElement.Add(m_IMGUIContainer); + + 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 void RefreshUIToolkitHeaderState() + { + if (m_HeaderContainer == null) + return; + + var hasSettings = m_SettingsObject != null; + var hasSettingsAsset = m_AvailableInputSettingsAssets != null && m_AvailableInputSettingsAssets.Length != 0; + var canEditSettings = hasSettings && hasSettingsAsset; + + if (m_CreateSettingsAssetContainer != null) + m_CreateSettingsAssetContainer.style.display = hasSettingsAsset ? DisplayStyle.None : DisplayStyle.Flex; + + UpdateDropdownChoices(m_UpdateModeDropdown, m_UpdateMode); + if (m_UpdateModeDropdown != null) + m_UpdateModeDropdown.SetEnabled(canEditSettings && m_UpdateMode != null); + + var showManualUpdateModeHelp = hasSettings && + m_UpdateMode != null && + m_UpdateMode.intValue == (int)InputSettings.UpdateMode.ProcessEventsManually; + if (m_UpdateModeHelpContainer != null) + m_UpdateModeHelpContainer.style.display = showManualUpdateModeHelp ? DisplayStyle.Flex : DisplayStyle.None; + + UpdateDropdownChoices(m_BackgroundBehaviorDropdown, m_BackgroundBehavior); + if (m_BackgroundBehaviorDropdown != null) + m_BackgroundBehaviorDropdown.SetEnabled(canEditSettings && Application.runInBackground && m_BackgroundBehavior != null); + + if (m_BackgroundBehaviorHelpBox != null) + { + var showRunInBackgroundHelp = hasSettings && !Application.runInBackground; + m_BackgroundBehaviorHelpBox.style.display = showRunInBackgroundHelp ? DisplayStyle.Flex : DisplayStyle.None; + } + +#if UNITY_INPUT_SYSTEM_PLATFORM_SCROLL_DELTA + UpdateDropdownChoices(m_ScrollDeltaBehaviorDropdown, m_ScrollDeltaBehavior); + if (m_ScrollDeltaBehaviorDropdown != null) + m_ScrollDeltaBehaviorDropdown.SetEnabled(canEditSettings && m_ScrollDeltaBehavior != null); +#endif + + if (m_CompensateForScreenOrientationToggle != null) + { + m_CompensateForScreenOrientationToggle.SetEnabled(canEditSettings && m_CompensateForScreenOrientation != null); + m_CompensateForScreenOrientationToggle.SetValueWithoutNotify(m_CompensateForScreenOrientation?.boolValue ?? false); + } + + UpdateFloatField(m_DefaultDeadzoneMinField, m_DefaultDeadzoneMin, canEditSettings); + UpdateFloatField(m_DefaultDeadzoneMaxField, m_DefaultDeadzoneMax, canEditSettings); + UpdateFloatField(m_DefaultButtonPressPointField, m_DefaultButtonPressPoint, canEditSettings); + UpdateFloatField(m_ButtonReleaseThresholdField, m_ButtonReleaseThreshold, canEditSettings); + UpdateFloatField(m_DefaultTapTimeField, m_DefaultTapTime, canEditSettings); + UpdateFloatField(m_DefaultSlowTapTimeField, m_DefaultSlowTapTime, canEditSettings); + UpdateFloatField(m_DefaultHoldTimeField, m_DefaultHoldTime, canEditSettings); + UpdateFloatField(m_TapRadiusField, m_TapRadius, canEditSettings); + UpdateFloatField(m_MultiTapDelayTimeField, m_MultiTapDelayTime, canEditSettings); + + UpdateDropdownChoices(m_EditorInputBehaviorInPlayModeDropdown, m_EditorInputBehaviorInPlayMode); + if (m_EditorInputBehaviorInPlayModeDropdown != null) + m_EditorInputBehaviorInPlayModeDropdown.SetEnabled(canEditSettings && m_EditorInputBehaviorInPlayMode != null); + + if (m_ShortcutKeysConsumeInputsToggle != null) + { + m_ShortcutKeysConsumeInputsToggle.SetEnabled(canEditSettings && m_ShortcutKeysConsumeInputs != null); + m_ShortcutKeysConsumeInputsToggle.SetValueWithoutNotify(m_ShortcutKeysConsumeInputs?.boolValue ?? false); + } + + if (m_ShortcutKeysConsumeInputsHelpBox != null) + m_ShortcutKeysConsumeInputsHelpBox.style.display = + m_ShortcutKeysConsumeInputs != null && m_ShortcutKeysConsumeInputs.boolValue + ? DisplayStyle.Flex + : DisplayStyle.None; + + m_iOSProvider?.RefreshUIToolkitState(canEditSettings); + } + + private static void UpdateDropdownChoices(DropdownField dropdown, SerializedProperty property) + { + if (dropdown == null) + return; + + if (property == null) + { + dropdown.choices = Array.Empty().ToList(); + dropdown.SetValueWithoutNotify(string.Empty); + return; } + + dropdown.choices = property.enumDisplayNames.ToList(); + if (property.enumValueIndex >= 0 && property.enumValueIndex < dropdown.choices.Count) + dropdown.SetValueWithoutNotify(dropdown.choices[property.enumValueIndex]); + } + + private static void UpdateFloatField(FloatField field, SerializedProperty property, bool canEditSettings) + { + if (field == null) + return; + + field.SetEnabled(canEditSettings && property != null); + field.SetValueWithoutNotify(property?.floatValue ?? 0f); } private void CustomUpdateModeHelpBox() { var message = - "This is not recommended, the default update mode is dynamic update and should only be changed for compelling reasons.\nPlease refer to the documentation."; - Uri link = new Uri(InputSystem.kDocUrl + "/manual/Settings.html#update-mode"); + "This is not recommended, the default update mode is dynamic update and should only be changed for compelling reasons.\nPlease refer to the documentation."; GUILayout.BeginHorizontal(EditorStyles.helpBox); GUILayout.Label(EditorGUIUtility.IconContent("console.warnicon"), GUILayout.ExpandWidth(false)); GUILayout.BeginVertical(); GUILayout.Label(message, EditorStyles.label); if (GUILayout.Button("Read more", EditorStyles.linkLabel)) - System.Diagnostics.Process.Start(link.AbsoluteUri); + OpenUpdateModeDocumentation(); EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link); GUILayout.EndVertical(); GUILayout.EndHorizontal(); } + private static void OpenUpdateModeDocumentation() + { + var link = new Uri(InputSystem.kDocUrl + "/manual/Settings.html#update-mode"); + System.Diagnostics.Process.Start(link.AbsoluteUri); + } + private static void ShowPlatformSettings() { // Would be nice to get BuildTargetDiscovery.GetBuildTargetInfoList since that contains information about icons etc @@ -385,7 +769,10 @@ private void InitializeWithCurrentSettings() } }; - m_iOSProvider = new InputSettingsiOSProvider(m_SettingsObject); + if (m_iOSProvider == null) + m_iOSProvider = new InputSettingsiOSProvider(m_SettingsObject); + else + m_iOSProvider.Update(m_SettingsObject); } private void Apply() @@ -402,11 +789,13 @@ private void OnUndoRedo() if (m_Settings != null && EditorUtility.GetDirtyCount(m_Settings) != m_SettingsDirtyCount) m_Settings.OnChange(); InitializeWithCurrentSettingsIfNecessary(); + RefreshUIToolkitHeaderState(); } private void OnSettingsChange() { InitializeWithCurrentSettingsIfNecessary(); + RefreshUIToolkitHeaderState(); ////REVIEW: leads to double-repaint when the settings change is initiated by us; problem? Repaint(); @@ -470,6 +859,35 @@ private static string[] FindInputSettingsInProject() private GUIContent m_ShortcutKeysConsumeInputsContent; [NonSerialized] private InputSettingsiOSProvider m_iOSProvider; + [NonSerialized] private VisualElement m_RootElement; + [NonSerialized] private VisualElement m_CreateSettingsAssetContainer; + [NonSerialized] private VisualElement m_HeaderContainer; + [NonSerialized] private VisualElement m_UpdateModeHelpContainer; + [NonSerialized] private IMGUIContainer m_IMGUIContainer; + [NonSerialized] private DropdownField m_UpdateModeDropdown; + [NonSerialized] private DropdownField m_BackgroundBehaviorDropdown; +#if UNITY_INPUT_SYSTEM_PLATFORM_SCROLL_DELTA + [NonSerialized] private DropdownField m_ScrollDeltaBehaviorDropdown; +#endif + [NonSerialized] private DropdownField m_EditorInputBehaviorInPlayModeDropdown; + [NonSerialized] private Toggle m_CompensateForScreenOrientationToggle; + [NonSerialized] private Toggle m_ShortcutKeysConsumeInputsToggle; + [NonSerialized] private FloatField m_DefaultDeadzoneMinField; + [NonSerialized] private FloatField m_DefaultDeadzoneMaxField; + [NonSerialized] private FloatField m_DefaultButtonPressPointField; + [NonSerialized] private FloatField m_ButtonReleaseThresholdField; + [NonSerialized] private FloatField m_DefaultTapTimeField; + [NonSerialized] private FloatField m_DefaultSlowTapTimeField; + [NonSerialized] private FloatField m_DefaultHoldTimeField; + [NonSerialized] private FloatField m_TapRadiusField; + [NonSerialized] private FloatField m_MultiTapDelayTimeField; + [NonSerialized] private HelpBox m_CreateSettingsAssetHelpBox; + [NonSerialized] private Button m_CreateSettingsAssetButton; + [NonSerialized] private HelpBox m_UpdateModeHelpBox; + [NonSerialized] private Button m_UpdateModeReadMoreButton; + [NonSerialized] private HelpBox m_BackgroundBehaviorHelpBox; + [NonSerialized] private HelpBox m_ShortcutKeysConsumeInputsHelpBox; + [NonSerialized] private HelpBox m_SupportedDevicesHelpBox; private static InputSettingsProvider s_Instance; diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/InputSettingsiOSProvider.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/InputSettingsiOSProvider.cs index 36eb6f92f9..c33d651bff 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/InputSettingsiOSProvider.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/iOS/InputSettingsiOSProvider.cs @@ -1,6 +1,7 @@ #if UNITY_EDITOR using System; using UnityEditor; +using UnityEngine.UIElements; namespace UnityEngine.InputSystem { @@ -8,20 +9,28 @@ internal class InputSettingsiOSProvider { [NonSerialized] private SerializedProperty m_MotionUsageEnabled; [NonSerialized] private SerializedProperty m_MotionUsageDescription; + [NonSerialized] private VisualElement m_Container; + [NonSerialized] private Toggle m_MotionUsageToggle; + [NonSerialized] private TextField m_MotionUsageDescriptionField; private GUIContent m_MotionUsageContent; private GUIContent m_MotionUsageDescriptionContent; public InputSettingsiOSProvider(SerializedObject parent) { - var prefix = "m_iOSSettings.m_MotionUsage"; - m_MotionUsageEnabled = parent.FindProperty(prefix + ".m_Enabled"); - m_MotionUsageDescription = parent.FindProperty(prefix + ".m_Description"); + Update(parent); m_MotionUsageContent = new GUIContent("Motion Usage", "Enables Motion Usage for the app, required for sensors like Step Counter. This also adds 'Privacy - Motion Usage Description' entry to Info.plist"); m_MotionUsageDescriptionContent = new GUIContent(" Description", "Describe why the app wants to access the device's Motion Usage sensor."); } + public void Update(SerializedObject parent) + { + var prefix = "m_iOSSettings.m_MotionUsage"; + m_MotionUsageEnabled = parent.FindProperty(prefix + ".m_Enabled"); + m_MotionUsageDescription = parent.FindProperty(prefix + ".m_Description"); + } + public void OnGUI() { EditorGUILayout.PropertyField(m_MotionUsageEnabled, m_MotionUsageContent); @@ -29,6 +38,66 @@ public void OnGUI() EditorGUILayout.PropertyField(m_MotionUsageDescription, m_MotionUsageDescriptionContent); EditorGUI.EndDisabledGroup(); } + + public void CreateGUI(VisualElement parent, Action onValueChanged) + { + if (parent == null) + return; + + m_Container = new VisualElement(); + var titleLabel = new Label("iOS"); + titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold; + titleLabel.style.marginTop = 12; + m_Container.Add(titleLabel); + + m_MotionUsageToggle = new Toggle(m_MotionUsageContent.text) + { + tooltip = m_MotionUsageContent.tooltip + }; + m_MotionUsageToggle.RegisterValueChangedCallback(evt => + { + if (m_MotionUsageEnabled == null || m_MotionUsageEnabled.boolValue == evt.newValue) + return; + + m_MotionUsageEnabled.boolValue = evt.newValue; + onValueChanged?.Invoke(); + }); + m_Container.Add(m_MotionUsageToggle); + + m_MotionUsageDescriptionField = new TextField("Description") + { + tooltip = m_MotionUsageDescriptionContent.tooltip + }; + m_MotionUsageDescriptionField.RegisterValueChangedCallback(evt => + { + if (m_MotionUsageDescription == null || m_MotionUsageDescription.stringValue == evt.newValue) + return; + + m_MotionUsageDescription.stringValue = evt.newValue; + onValueChanged?.Invoke(); + }); + m_Container.Add(m_MotionUsageDescriptionField); + + parent.Add(m_Container); + } + + public void RefreshUIToolkitState(bool canEditSettings) + { + if (m_Container == null) + return; + + if (m_MotionUsageToggle != null) + { + m_MotionUsageToggle.SetEnabled(canEditSettings && m_MotionUsageEnabled != null); + m_MotionUsageToggle.SetValueWithoutNotify(m_MotionUsageEnabled?.boolValue ?? false); + } + + if (m_MotionUsageDescriptionField != null) + { + m_MotionUsageDescriptionField.SetEnabled(canEditSettings && m_MotionUsageEnabled != null && m_MotionUsageEnabled.boolValue); + m_MotionUsageDescriptionField.SetValueWithoutNotify(m_MotionUsageDescription?.stringValue ?? string.Empty); + } + } } }