private void DrawInteractivePickButton(Rect rect) { if (s_PickButtonIcon == null) { s_PickButtonIcon = EditorInputControlLayoutCache.GetIconForLayout("Button"); } var toggleRebind = GUI.Toggle(rect, m_RebindingOperation != null && m_RebindingOperation.started, s_PickButtonIcon, EditorStyles.miniButton); if (toggleRebind && (m_RebindingOperation == null || !m_RebindingOperation.started)) { // Start rebind. if (m_RebindingOperation == null) { m_RebindingOperation = new InputActionRebindingExtensions.RebindingOperation(); } ////TODO: if we have multiple candidates that we can't trivially decide between, let user choose m_RebindingOperation .WithExpectedControlLayout(m_ExpectedControlLayout) // Require minimum actuation of 0.15f. This is after deadzoning has been applied. .WithMagnitudeHavingToBeGreaterThan(0.15f) // After 4 seconds, cancel the operation. .WithTimeout(4) ////REVIEW: the delay makes it more robust but doesn't feel good // Give us a buffer of 0.25 seconds to see if a better match comes along. .OnMatchWaitForAnother(0.25f) ////REVIEW: should we exclude only the system's active pointing device? // With the mouse operating the UI, its cursor control is too fickle a thing to // bind to. Ignore mouse position and delta. // NOTE: We go for all types of pointers here, not just mice. .WithControlsExcluding("<Pointer>/position") .WithControlsExcluding("<Pointer>/delta") .OnCancel( operation => { ////REVIEW: Is there a better way to do this? All we want is for the *current* UI to repaint but Unity's //// editor API seems to have no way to retrieve the current EditorWindow from inside an OnGUI callback. //// So we'd have to pass the EditorWindow in here all the way from the EditorWindow.OnGUI() callback //// itself. InternalEditorUtility.RepaintAllViews(); if (m_NeedToClearProgressBar) { EditorUtility.ClearProgressBar(); } }) .OnComplete( operation => { if (m_NeedToClearProgressBar) { EditorUtility.ClearProgressBar(); } }) .OnApplyBinding( (operation, newPath) => { pathProperty.stringValue = newPath; pathProperty.serializedObject.ApplyModifiedProperties(); onModified(); }); // If we have control paths to match, pass them on. m_RebindingOperation.WithoutControlsHavingToMatchPath(); if (m_ControlPathsToMatch.LengthSafe() > 0) { m_ControlPathsToMatch.Select(x => m_RebindingOperation.WithControlsHavingToMatchPath(x)); } m_RebindingOperation.Start(); } else if (!toggleRebind && m_RebindingOperation != null && m_RebindingOperation.started) { m_RebindingOperation.Cancel(); } }
private void AddControlItem(InputControlLayout.ControlItem control, TreeViewItem parent, ref int id) { var item = AddChild(parent, control.variants.IsEmpty() ? control.name : string.Format("{0} ({1})", control.name, control.variants), ref id); if (!control.layout.IsEmpty()) { item.icon = EditorInputControlLayoutCache.GetIconForLayout(control.layout); } ////TODO: fully merge TreeViewItems from isModifyingChildControlByPath control layouts into the control they modify ////TODO: allow clicking this field to jump to the layout if (!control.layout.IsEmpty()) { AddChild(item, $"Layout: {control.layout}", ref id); } if (!control.variants.IsEmpty()) { AddChild(item, $"Variant: {control.variants}", ref id); } if (!string.IsNullOrEmpty(control.displayName)) { AddChild(item, $"Display Name: {control.displayName}", ref id); } if (!string.IsNullOrEmpty(control.shortDisplayName)) { AddChild(item, $"Short Display Name: {control.shortDisplayName}", ref id); } if (control.format != 0) { AddChild(item, $"Format: {control.format}", ref id); } if (control.offset != InputStateBlock.kInvalidOffset) { AddChild(item, $"Offset: {control.offset}", ref id); } if (control.bit != InputStateBlock.kInvalidOffset) { AddChild(item, $"Bit: {control.bit}", ref id); } if (control.sizeInBits != 0) { AddChild(item, $"Size In Bits: {control.sizeInBits}", ref id); } if (control.isArray) { AddChild(item, $"Array Size: {control.arraySize}", ref id); } if (!string.IsNullOrEmpty(control.useStateFrom)) { AddChild(item, $"Use State From: {control.useStateFrom}", ref id); } if (!control.defaultState.isEmpty) { AddChild(item, $"Default State: {control.defaultState.ToString()}", ref id); } if (!control.minValue.isEmpty) { AddChild(item, $"Min Value: {control.minValue.ToString()}", ref id); } if (!control.maxValue.isEmpty) { AddChild(item, $"Max Value: {control.maxValue.ToString()}", ref id); } if (control.usages.Count > 0) { AddChild(item, "Usages: " + string.Join(", ", control.usages.Select(x => x.ToString()).ToArray()), ref id); } if (control.aliases.Count > 0) { AddChild(item, "Aliases: " + string.Join(", ", control.aliases.Select(x => x.ToString()).ToArray()), ref id); } if (control.isNoisy || control.isSynthetic) { var flags = "Flags: "; if (control.isNoisy) { flags += "Noisy"; } if (control.isSynthetic) { if (control.isNoisy) { flags += ", Synthetic"; } else { flags += "Synthetic"; } } AddChild(item, flags, ref id); } if (control.parameters.Count > 0) { var parameters = AddChild(item, "Parameters", ref id); foreach (var parameter in control.parameters) { AddChild(parameters, parameter.ToString(), ref id); } } if (control.processors.Count > 0) { var processors = AddChild(item, "Processors", ref id); foreach (var processor in control.processors) { var processorItem = AddChild(processors, processor.name, ref id); foreach (var parameter in processor.parameters) { AddChild(processorItem, parameter.ToString(), ref id); } } } }
/// <summary> /// Grab <see cref="InputSystem.settings"/> and set it up for editing. /// </summary> private void InitializeWithCurrentSettings() { // Find the set of available assets in the project. m_AvailableInputSettingsAssets = FindInputSettingsInProject(); // See which is the active one. m_Settings = InputSystem.settings; var currentSettingsPath = AssetDatabase.GetAssetPath(m_Settings); if (string.IsNullOrEmpty(currentSettingsPath)) { if (m_AvailableInputSettingsAssets.Length != 0) { m_CurrentSelectedInputSettingsAsset = 0; m_Settings = AssetDatabase.LoadAssetAtPath <InputSettings>(m_AvailableInputSettingsAssets[0]); InputSystem.settings = m_Settings; } } else { m_CurrentSelectedInputSettingsAsset = ArrayHelpers.IndexOf(m_AvailableInputSettingsAssets, currentSettingsPath); if (m_CurrentSelectedInputSettingsAsset == -1) { // This is odd and shouldn't happen. Solve by just adding the path to the list. m_CurrentSelectedInputSettingsAsset = ArrayHelpers.Append(ref m_AvailableInputSettingsAssets, currentSettingsPath); } ////REVIEW: should we store this by platform? EditorBuildSettings.AddConfigObject(kEditorBuildSettingsConfigKey, m_Settings, true); } // Refresh the list of assets we display in the UI. m_AvailableSettingsAssetsOptions = new GUIContent[m_AvailableInputSettingsAssets.Length]; for (var i = 0; i < m_AvailableInputSettingsAssets.Length; ++i) { var name = m_AvailableInputSettingsAssets[i]; if (name.StartsWith("Assets/")) { name = name.Substring("Assets/".Length); } if (name.EndsWith(".asset")) { name = name.Substring(0, name.Length - ".asset".Length); } if (name.EndsWith(".inputsettings")) { name = name.Substring(0, name.Length - ".inputsettings".Length); } // Ugly hack: GenericMenu iterprets "/" as a submenu path. But luckily, "/" is not the only slash we have in Unicode. m_AvailableSettingsAssetsOptions[i] = new GUIContent(name.Replace("/", "\u29f8")); } // Look up properties. m_SettingsObject = new SerializedObject(m_Settings); m_UpdateMode = m_SettingsObject.FindProperty("m_UpdateMode"); m_ActionUpdateMode = m_SettingsObject.FindProperty("m_ActionUpdateMode"); m_TimesliceEvents = m_SettingsObject.FindProperty("m_TimesliceEvents"); m_CompensateForScreenOrientation = m_SettingsObject.FindProperty("m_CompensateForScreenOrientation"); m_FilterNoiseOnCurrent = m_SettingsObject.FindProperty("m_FilterNoiseOnCurrent"); m_DefaultDeadzoneMin = m_SettingsObject.FindProperty("m_DefaultDeadzoneMin"); m_DefaultDeadzoneMax = m_SettingsObject.FindProperty("m_DefaultDeadzoneMax"); m_DefaultButtonPressPoint = m_SettingsObject.FindProperty("m_DefaultButtonPressPoint"); m_DefaultTapTime = m_SettingsObject.FindProperty("m_DefaultTapTime"); m_DefaultSlowTapTime = m_SettingsObject.FindProperty("m_DefaultSlowTapTime"); m_DefaultHoldTime = m_SettingsObject.FindProperty("m_DefaultHoldTime"); // Initialize ReorderableList for list of supported devices. var supportedDevicesProperty = m_SettingsObject.FindProperty("m_SupportedDevices"); m_SupportedDevices = new ReorderableList(m_SettingsObject, supportedDevicesProperty) { drawHeaderCallback = rect => { EditorGUI.LabelField(rect, m_SupportedDevicesText); }, onChangedCallback = list => { Apply(); }, onAddDropdownCallback = (rect, list) => { var dropdown = new InputControlPickerDropdown( new InputControlPickerState(), path => { var layoutName = InputControlPath.TryGetDeviceLayout(path) ?? path; var numDevices = supportedDevicesProperty.arraySize; supportedDevicesProperty.InsertArrayElementAtIndex(numDevices); supportedDevicesProperty.GetArrayElementAtIndex(numDevices) .stringValue = layoutName; Apply(); }, mode: InputControlPicker.Mode.PickDevice); dropdown.Show(rect); }, drawElementCallback = (rect, index, isActive, isFocused) => { var layoutName = m_Settings.supportedDevices[index]; var icon = EditorInputControlLayoutCache.GetIconForLayout(layoutName); if (icon != null) { var iconRect = rect; iconRect.width = 20; rect.x += 20; rect.width -= 20; GUI.Label(iconRect, icon); } EditorGUI.LabelField(rect, m_Settings.supportedDevices[index]); } }; }
protected override TreeViewItem BuildRoot() { var id = 0; var root = new TreeViewItem { id = id++, depth = -1 }; ////TODO: this will need to be improved for multi-user scenarios // Actions. m_EnabledActions.Clear(); InputSystem.ListEnabledActions(m_EnabledActions); if (m_EnabledActions.Count > 0) { actionsItem = AddChild(root, $"Actions ({m_EnabledActions.Count})", ref id); AddEnabledActions(actionsItem, ref id); } // Users. var userCount = InputUser.all.Count; if (userCount > 0) { usersItem = AddChild(root, $"Users ({userCount})", ref id); foreach (var user in InputUser.all) { AddUser(usersItem, user, ref id); } } // Devices. var devices = InputSystem.devices; devicesItem = AddChild(root, $"Devices ({devices.Count})", ref id); var haveRemotes = devices.Any(x => x.remote); TreeViewItem localDevicesNode = null; if (haveRemotes) { // Split local and remote devices into groups. localDevicesNode = AddChild(devicesItem, "Local", ref id); AddDevices(localDevicesNode, devices, ref id); var remoteDevicesNode = AddChild(devicesItem, "Remote", ref id); foreach (var player in EditorConnection.instance.ConnectedPlayers) { var playerNode = AddChild(remoteDevicesNode, player.name, ref id); AddDevices(playerNode, devices, ref id, player.playerId); } } else { // We don't have remote devices so don't add an extra group for local devices. // Put them all directly underneath the "Devices" node. AddDevices(devicesItem, devices, ref id); } ////TDO: unsupported and disconnected devices should also be shown for remotes if (m_UnsupportedDevices == null) { m_UnsupportedDevices = new List <InputDeviceDescription>(); } m_UnsupportedDevices.Clear(); InputSystem.GetUnsupportedDevices(m_UnsupportedDevices); if (m_UnsupportedDevices.Count > 0) { var parent = haveRemotes ? localDevicesNode : devicesItem; var unsupportedDevicesNode = AddChild(parent, $"Unsupported ({m_UnsupportedDevices.Count})", ref id); foreach (var device in m_UnsupportedDevices) { AddChild(unsupportedDevicesNode, device.ToString(), ref id); } unsupportedDevicesNode.children.Sort((a, b) => string.Compare(a.displayName, b.displayName)); } var disconnectedDevices = InputSystem.disconnectedDevices; if (disconnectedDevices.Count > 0) { var parent = haveRemotes ? localDevicesNode : devicesItem; var disconnectedDevicesNode = AddChild(parent, $"Disconnected ({disconnectedDevices.Count})", ref id); foreach (var device in disconnectedDevices) { AddChild(disconnectedDevicesNode, device.ToString(), ref id); } disconnectedDevicesNode.children.Sort((a, b) => string.Compare(a.displayName, b.displayName)); } // Layouts. layoutsItem = AddChild(root, "Layouts", ref id); AddControlLayouts(layoutsItem, ref id); ////FIXME: this shows local configuration only // Settings. var settings = InputSystem.settings; var settingsAssetPath = AssetDatabase.GetAssetPath(settings); var settingsLabel = "Settings"; if (!string.IsNullOrEmpty(settingsAssetPath)) { settingsLabel = $"Settings ({Path.GetFileName(settingsAssetPath)})"; } settingsItem = AddChild(root, settingsLabel, ref id); AddValueItem(settingsItem, "Update Mode", settings.updateMode, ref id); AddValueItem(settingsItem, "Timeslice Events", settings.timesliceEvents, ref id); AddValueItem(settingsItem, "Compensate For Screen Orientation", settings.compensateForScreenOrientation, ref id); AddValueItem(settingsItem, "Filter Noise On .current", settings.filterNoiseOnCurrent, ref id); AddValueItem(settingsItem, "Default Button Press Point", settings.defaultButtonPressPoint, ref id); AddValueItem(settingsItem, "Default Deadzone Min", settings.defaultDeadzoneMin, ref id); AddValueItem(settingsItem, "Default Deadzone Max", settings.defaultDeadzoneMax, ref id); AddValueItem(settingsItem, "Default Tap Time", settings.defaultTapTime, ref id); AddValueItem(settingsItem, "Default Slow Tap Time", settings.defaultSlowTapTime, ref id); AddValueItem(settingsItem, "Default Hold Time", settings.defaultHoldTime, ref id); AddValueItem(settingsItem, "Lock Input To Game View", InputEditorUserSettings.lockInputToGameView, ref id); if (settings.supportedDevices.Count > 0) { var supportedDevices = AddChild(settingsItem, "Supported Devices", ref id); foreach (var item in settings.supportedDevices) { var icon = EditorInputControlLayoutCache.GetIconForLayout(item); AddChild(supportedDevices, item, ref id, icon); } } settingsItem.children.Sort((a, b) => string.Compare(a.displayName, b.displayName)); // Metrics. var metrics = InputSystem.GetMetrics(); metricsItem = AddChild(root, "Metrics", ref id); AddChild(metricsItem, "Current State Size in Bytes: " + StringHelpers.NicifyMemorySize(metrics.currentStateSizeInBytes), ref id); AddValueItem(metricsItem, "Current Control Count", metrics.currentControlCount, ref id); AddValueItem(metricsItem, "Current Layout Count", metrics.currentLayoutCount, ref id); return(root); }