Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Editor /ControlPicker /InputControlPickerDropdown.cs
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Text; | |
| using UnityEditor; | |
| using UnityEngine.InputSystem.Controls; | |
| using UnityEngine.InputSystem.Layouts; | |
| using UnityEngine.InputSystem.Utilities; | |
| ////TODO: have tooltips on each entry in the picker | |
| ////TODO: find better way to present controls when filtering to specific devices | |
| ////REVIEW: if there's only a single device in the picker, automatically go into it? | |
| namespace UnityEngine.InputSystem.Editor | |
| { | |
| internal class InputControlPickerDropdown : AdvancedDropdown, IDisposable | |
| { | |
| public InputControlPickerDropdown( | |
| InputControlPickerState state, | |
| Action<string> onPickCallback, | |
| InputControlPicker.Mode mode = InputControlPicker.Mode.PickControl) | |
| : base(state.advancedDropdownState) | |
| { | |
| m_Gui = new InputControlPickerGUI(this); | |
| minimumSize = new Vector2(275, 300); | |
| maximumSize = new Vector2(0, 300); | |
| m_OnPickCallback = onPickCallback; | |
| m_Mode = mode; | |
| } | |
| public void SetControlPathsToMatch(string[] controlPathsToMatch) | |
| { | |
| m_ControlPathsToMatch = controlPathsToMatch; | |
| Reload(); | |
| } | |
| public void SetExpectedControlLayout(string expectedControlLayout) | |
| { | |
| m_ExpectedControlLayout = expectedControlLayout; | |
| if (string.Equals(expectedControlLayout, "InputDevice", StringComparison.InvariantCultureIgnoreCase)) | |
| m_ExpectedControlType = typeof(InputDevice); | |
| else | |
| m_ExpectedControlType = !string.IsNullOrEmpty(expectedControlLayout) | |
| ? InputSystem.s_Manager.m_Layouts.GetControlTypeForLayout(new InternedString(expectedControlLayout)) | |
| : null; | |
| // If the layout is for a device, automatically switch to device | |
| // picking mode. | |
| if (m_ExpectedControlType != null && typeof(InputDevice).IsAssignableFrom(m_ExpectedControlType)) | |
| m_Mode = InputControlPicker.Mode.PickDevice; | |
| Reload(); | |
| } | |
| public void SetPickedCallback(Action<string> action) | |
| { | |
| m_OnPickCallback = action; | |
| } | |
| protected override void OnDestroy() | |
| { | |
| m_RebindingOperation?.Dispose(); | |
| m_RebindingOperation = null; | |
| } | |
| public void Dispose() | |
| { | |
| m_RebindingOperation?.Dispose(); | |
| } | |
| protected override AdvancedDropdownItem BuildRoot() | |
| { | |
| var root = new AdvancedDropdownItem(string.Empty); | |
| // Usages. | |
| if (m_Mode != InputControlPicker.Mode.PickDevice) | |
| { | |
| var usages = BuildTreeForUsages(); | |
| if (usages.children.Any()) | |
| { | |
| root.AddChild(usages); | |
| root.AddSeparator(); | |
| } | |
| } | |
| // Devices. | |
| AddItemsForDevices(root); | |
| return root; | |
| } | |
| protected override AdvancedDropdownItem BuildCustomSearch(string searchString, | |
| IEnumerable<AdvancedDropdownItem> elements) | |
| { | |
| if (!isListening) | |
| return null; | |
| var root = new AdvancedDropdownItem(!string.IsNullOrEmpty(m_ExpectedControlLayout) | |
| ? $"Listening for {m_ExpectedControlLayout}..." | |
| : "Listening for input..."); | |
| if (searchString == "\u0017") | |
| return root; | |
| var paths = searchString.Substring(1).Split('\u0017'); | |
| foreach (var element in elements) | |
| { | |
| if (element is ControlDropdownItem controlItem && paths.Any(x => controlItem.controlPathWithDevice == x)) | |
| root.AddChild(element); | |
| } | |
| return root; | |
| } | |
| protected override void ItemSelected(AdvancedDropdownItem item) | |
| { | |
| var path = ((InputControlDropdownItem)item).controlPathWithDevice; | |
| m_OnPickCallback(path); | |
| } | |
| private AdvancedDropdownItem BuildTreeForUsages() | |
| { | |
| var usageRoot = new AdvancedDropdownItem("Usages"); | |
| foreach (var usageAndLayouts in EditorInputControlLayoutCache.allUsages) | |
| { | |
| if (usageAndLayouts.Item2.Any(LayoutMatchesExpectedControlLayoutFilter)) | |
| { | |
| var child = new UsageDropdownItem(usageAndLayouts.Item1); | |
| usageRoot.AddChild(child); | |
| } | |
| } | |
| return usageRoot; | |
| } | |
| private void AddItemsForDevices(AdvancedDropdownItem parent) | |
| { | |
| // Add devices that are marked as generic types of devices directly to the parent. | |
| // E.g. adds "Gamepad" and then underneath all the more specific types of gamepads. | |
| foreach (var deviceLayout in EditorInputControlLayoutCache.allLayouts | |
| .Where(x => x.isDeviceLayout && !x.isOverride && x.isGenericTypeOfDevice && !x.hideInUI) | |
| .OrderBy(a => a.displayName)) | |
| { | |
| AddDeviceTreeItemRecursive(deviceLayout, parent); | |
| } | |
| // We have devices that are based directly on InputDevice but are not marked as generic types | |
| // of devices (e.g. Vive Lighthouses). We do not want them to clutter the list at the root so we | |
| // put all of them in a group called "Other" at the end of the list. | |
| var otherGroup = new AdvancedDropdownItem("Other"); | |
| foreach (var deviceLayout in EditorInputControlLayoutCache.allLayouts | |
| .Where(x => x.isDeviceLayout && !x.isOverride && !x.isGenericTypeOfDevice && | |
| (x.type.BaseType == typeof(InputDevice) || x.type == typeof(InputDevice)) && | |
| !x.hideInUI && !x.baseLayouts.Any()).OrderBy(a => a.displayName)) | |
| { | |
| AddDeviceTreeItemRecursive(deviceLayout, otherGroup); | |
| } | |
| if (otherGroup.children.Any()) | |
| parent.AddChild(otherGroup); | |
| } | |
| private void AddDeviceTreeItemRecursive(InputControlLayout layout, AdvancedDropdownItem parent, bool searchable = true) | |
| { | |
| // Find all layouts directly based on this one (ignoring overrides). | |
| var childLayouts = EditorInputControlLayoutCache.allLayouts | |
| .Where(x => x.isDeviceLayout && !x.isOverride && !x.hideInUI && x.baseLayouts.Contains(layout.name)).OrderBy(x => x.displayName); | |
| // See if the entire tree should be excluded. | |
| var shouldIncludeDeviceLayout = ShouldIncludeDeviceLayout(layout); | |
| var shouldIncludeAtLeastOneChildLayout = childLayouts.Any(ShouldIncludeDeviceLayout); | |
| if (!shouldIncludeDeviceLayout && !shouldIncludeAtLeastOneChildLayout) | |
| return; | |
| // Add toplevel item for device. | |
| var deviceItem = new DeviceDropdownItem(layout, searchable: searchable); | |
| var defaultControlPickerLayout = new DefaultInputControlPickerLayout(); | |
| // Add common usage variants. | |
| if (layout.commonUsages.Count > 0) | |
| { | |
| foreach (var usage in layout.commonUsages) | |
| { | |
| var usageItem = new DeviceDropdownItem(layout, usage); | |
| if (m_Mode == InputControlPicker.Mode.PickControl) | |
| AddControlTreeItemsRecursive(defaultControlPickerLayout, layout, usageItem, layout.name, usage, searchable); | |
| deviceItem.AddChild(usageItem); | |
| } | |
| deviceItem.AddSeparator(); | |
| } | |
| // Add controls. | |
| if (m_Mode != InputControlPicker.Mode.PickDevice) | |
| { | |
| // The keyboard is special in that we want to allow binding by display name (i.e. character | |
| // generated by a key) instead of only by physical key location. Also, we want to give an indication | |
| // of which specific key an entry refers to by taking the current keyboard layout into account. | |
| // | |
| // So what we do is add an extra level to the keyboard where key's can be bound by character | |
| // according to the current layout. And in the top level of the keyboard we display keys with | |
| // both physical and logical names. | |
| if (layout.type == typeof(Keyboard) && InputSystem.GetDevice<Keyboard>() != null) | |
| { | |
| var byLocationGroup = new AdvancedDropdownItem("By Location of Key (Using US Layout)"); | |
| var byCharacterGroup = new AdvancedDropdownItem("By Character Mapped to Key"); | |
| deviceItem.AddChild(byLocationGroup); | |
| deviceItem.AddChild(byCharacterGroup); | |
| var keyboard = InputSystem.GetDevice<Keyboard>(); | |
| AddCharacterKeyBindingsTo(byCharacterGroup, keyboard); | |
| AddPhysicalKeyBindingsTo(byLocationGroup, keyboard, searchable); | |
| // AnyKey won't appear in either group. Add it explicitly. | |
| AddControlItem(defaultControlPickerLayout, deviceItem, null, | |
| layout.FindControl(new InternedString("anyKey")).Value, layout.name, null, searchable); | |
| } | |
| else if (layout.type == typeof(Touchscreen)) | |
| { | |
| AddControlTreeItemsRecursive(new TouchscreenControlPickerLayout(), layout, deviceItem, layout.name, null, searchable); | |
| } | |
| else | |
| { | |
| AddControlTreeItemsRecursive(defaultControlPickerLayout, layout, deviceItem, layout.name, null, searchable); | |
| } | |
| } | |
| // Add child items. | |
| var isFirstChild = true; | |
| foreach (var childLayout in childLayouts) | |
| { | |
| if (!ShouldIncludeDeviceLayout(childLayout)) | |
| continue; | |
| if (isFirstChild) | |
| deviceItem.AddSeparator("More Specific " + deviceItem.name.GetPlural()); | |
| isFirstChild = false; | |
| AddDeviceTreeItemRecursive(childLayout, deviceItem, searchable && !childLayout.isGenericTypeOfDevice); | |
| } | |
| // When picking devices, it must be possible to select a device that itself has more specific types | |
| // of devices underneath it. However in the dropdown, such a device will be a foldout and not itself | |
| // be selectable. We solve this problem by adding an entry for the device underneath the device | |
| // itself (e.g. "Gamepad >> Gamepad"). | |
| if (m_Mode == InputControlPicker.Mode.PickDevice && deviceItem.m_Children.Count > 0) | |
| { | |
| var item = new DeviceDropdownItem(layout); | |
| deviceItem.m_Children.Insert(0, item); | |
| } | |
| if (deviceItem.m_Children.Count > 0 || m_Mode == InputControlPicker.Mode.PickDevice) | |
| parent.AddChild(deviceItem); | |
| } | |
| private void AddControlTreeItemsRecursive(IInputControlPickerLayout controlPickerLayout, InputControlLayout layout, | |
| DeviceDropdownItem parent, string device, string usage, bool searchable, ControlDropdownItem parentControl = null) | |
| { | |
| foreach (var control in layout.controls.OrderBy(a => a.name)) | |
| { | |
| if (control.isModifyingExistingControl) | |
| continue; | |
| // Skip variants except the default variant and variants dictated by the layout itself. | |
| if (!control.variants.IsEmpty() && control.variants != InputControlLayout.DefaultVariant | |
| && (layout.variants.IsEmpty() || !InputControlLayout.VariantsMatch(layout.variants, control.variants))) | |
| { | |
| continue; | |
| } | |
| controlPickerLayout.AddControlItem(this, parent, parentControl, control, device, usage, searchable); | |
| } | |
| // Add optional controls for devices. | |
| var optionalControls = EditorInputControlLayoutCache.GetOptionalControlsForLayout(layout.name); | |
| if (optionalControls.Any() && layout.isDeviceLayout) | |
| { | |
| var optionalGroup = new AdvancedDropdownItem("Optional Controls"); | |
| foreach (var optionalControl in optionalControls) | |
| { | |
| ////FIXME: this should list children, too | |
| ////FIXME: this should handle arrays, too | |
| if (LayoutMatchesExpectedControlLayoutFilter(optionalControl.layout)) | |
| { | |
| var child = new OptionalControlDropdownItem(optionalControl, device, usage); | |
| child.icon = EditorInputControlLayoutCache.GetIconForLayout(optionalControl.layout); | |
| optionalGroup.AddChild(child); | |
| } | |
| } | |
| if (optionalGroup.children.Any()) | |
| { | |
| var deviceName = EditorInputControlLayoutCache.TryGetLayout(device).m_DisplayName ?? | |
| ObjectNames.NicifyVariableName(device); | |
| parent.AddSeparator("Controls Present on More Specific " + deviceName.GetPlural()); | |
| parent.AddChild(optionalGroup); | |
| } | |
| } | |
| } | |
| internal void AddControlItem(IInputControlPickerLayout controlPickerLayout, | |
| DeviceDropdownItem parent, ControlDropdownItem parentControl, | |
| InputControlLayout.ControlItem control, string device, string usage, bool searchable, | |
| string controlNameOverride = default) | |
| { | |
| var controlName = controlNameOverride ?? control.name; | |
| // If it's an array, generate a control entry for each array element. | |
| for (var i = 0; i < (control.isArray ? control.arraySize : 1); ++i) | |
| { | |
| var name = control.isArray ? controlName + i : controlName; | |
| var displayName = !string.IsNullOrEmpty(control.displayName) | |
| ? (control.isArray ? $"{control.displayName} #{i}" : control.displayName) | |
| : name; | |
| var child = new ControlDropdownItem(parentControl, name, displayName, | |
| device, usage, searchable); | |
| child.icon = EditorInputControlLayoutCache.GetIconForLayout(control.layout); | |
| var controlLayout = EditorInputControlLayoutCache.TryGetLayout(control.layout); | |
| if (LayoutMatchesExpectedControlLayoutFilter(control.layout)) | |
| parent.AddChild(child); | |
| else if (controlLayout.controls.Any(x => LayoutMatchesExpectedControlLayoutFilter(x.layout))) | |
| { | |
| child.enabled = false; | |
| parent.AddChild(child); | |
| } | |
| // Add children. | |
| if (controlLayout != null) | |
| AddControlTreeItemsRecursive(controlPickerLayout, controlLayout, parent, device, usage, | |
| searchable, child); | |
| } | |
| } | |
| private static void AddPhysicalKeyBindingsTo(AdvancedDropdownItem parent, Keyboard keyboard, bool searchable) | |
| { | |
| foreach (var key in keyboard.children.OfType<KeyControl>()) | |
| { | |
| // If the key has a display name that differs from the key name, show it in the UI. | |
| var displayName = key.m_DisplayNameFromLayout; | |
| var keyDisplayName = key.displayName; | |
| if (keyDisplayName.All(x => x.IsPrintable()) && string.Compare(keyDisplayName, displayName, | |
| StringComparison.InvariantCultureIgnoreCase) != 0) | |
| displayName = $"{displayName} (Current Layout: {key.displayName})"; | |
| // For left/right modifier keys, prepend artificial combined version. | |
| ButtonControl combinedVersion = null; | |
| if (key == keyboard.leftShiftKey) | |
| combinedVersion = keyboard.shiftKey; | |
| else if (key == keyboard.leftAltKey) | |
| combinedVersion = keyboard.altKey; | |
| else if (key == keyboard.leftCtrlKey) | |
| combinedVersion = keyboard.ctrlKey; | |
| if (combinedVersion != null) | |
| parent.AddChild(new ControlDropdownItem(null, combinedVersion.name, combinedVersion.displayName, keyboard.layout, | |
| "", searchable)); | |
| var item = new ControlDropdownItem(null, key.name, displayName, | |
| keyboard.layout, "", searchable); | |
| parent.AddChild(item); | |
| } | |
| } | |
| private static void AddCharacterKeyBindingsTo(AdvancedDropdownItem parent, Keyboard keyboard) | |
| { | |
| foreach (var key in keyboard.children.OfType<KeyControl>()) | |
| { | |
| if (!key.keyCode.IsTextInputKey()) | |
| continue; | |
| // We can only bind to characters that can be printed. | |
| var displayName = key.displayName; | |
| if (!displayName.All(x => x.IsPrintable())) | |
| continue; | |
| if (displayName.Contains(')')) | |
| displayName = string.Join("", displayName.Select(x => "\\" + x)); | |
| ////TODO: should be searchable; when searching, needs different display name | |
| var item = new ControlDropdownItem(null, $"#({displayName})", "", keyboard.layout, "", false); | |
| item.name = key.displayName; | |
| parent.AddChild(item); | |
| } | |
| } | |
| private bool LayoutMatchesExpectedControlLayoutFilter(string layout) | |
| { | |
| if (m_ExpectedControlType == null) | |
| return true; | |
| var layoutType = InputSystem.s_Manager.m_Layouts.GetControlTypeForLayout(new InternedString(layout)); | |
| return m_ExpectedControlType.IsAssignableFrom(layoutType); | |
| } | |
| private bool ShouldIncludeDeviceLayout(InputControlLayout layout) | |
| { | |
| if (layout.hideInUI) | |
| return false; | |
| // By default, if a device has no (usable) controls, we don't want it listed in the control picker | |
| // except if we're picking devices. | |
| if (!layout.controls.Any(x => LayoutMatchesExpectedControlLayoutFilter(x.layout)) && layout.controls.Any(x => true) && | |
| m_Mode != InputControlPicker.Mode.PickDevice) | |
| return false; | |
| // If we have a device filter, see if we should ignore the device. | |
| if (m_ControlPathsToMatch != null && m_ControlPathsToMatch.Length > 0) | |
| { | |
| var matchesAnyInDeviceFilter = false; | |
| foreach (var entry in m_ControlPathsToMatch) | |
| { | |
| // Include the layout if it's in the inheritance hierarchy of the layout we expect (either below | |
| // or above it or, well, just right on it). | |
| var expectedLayout = InputControlPath.TryGetDeviceLayout(entry); | |
| if (!string.IsNullOrEmpty(expectedLayout) && | |
| (expectedLayout == layout.name || | |
| InputControlLayout.s_Layouts.IsBasedOn(layout.name, new InternedString(expectedLayout)) || | |
| InputControlLayout.s_Layouts.IsBasedOn(new InternedString(expectedLayout), layout.name))) | |
| { | |
| matchesAnyInDeviceFilter = true; | |
| break; | |
| } | |
| } | |
| if (!matchesAnyInDeviceFilter) | |
| return false; | |
| } | |
| return true; | |
| } | |
| private void StartListening() | |
| { | |
| if (m_RebindingOperation == null) | |
| m_RebindingOperation = new InputActionRebindingExtensions.RebindingOperation(); | |
| ////TODO: for keyboard, generate both possible paths (physical and by display name) | |
| m_RebindingOperation.Reset(); | |
| m_RebindingOperation | |
| .WithExpectedControlType(m_ExpectedControlLayout) | |
| // Require minimum actuation of 0.15f. This is after deadzoning has been applied. | |
| .WithMagnitudeHavingToBeGreaterThan(0.15f) | |
| ////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 and clicks. | |
| // NOTE: We go for all types of pointers here, not just mice. | |
| .WithControlsExcluding("<Pointer>/position") | |
| .WithControlsExcluding("<Pointer>/delta") | |
| .WithControlsExcluding("<Pointer>/press") | |
| .WithControlsExcluding("<Pointer>/clickCount") | |
| .WithControlsExcluding("<Pointer>/{PrimaryAction}") | |
| .WithControlsExcluding("<Mouse>/scroll") | |
| .OnPotentialMatch( | |
| operation => | |
| { | |
| // We never really complete the pick but keep listening for as long as the "Interactive" | |
| // button is toggled on. | |
| Repaint(); | |
| }) | |
| .OnCancel( | |
| operation => | |
| { | |
| Repaint(); | |
| }) | |
| .OnApplyBinding( | |
| (operation, newPath) => | |
| { | |
| // This is never invoked (because we don't complete the pick) but we need it nevertheless | |
| // as RebindingOperation requires the callback if we don't supply an action to apply the binding to. | |
| }); | |
| // If we have control paths to match, pass them on. | |
| if (m_ControlPathsToMatch.LengthSafe() > 0) | |
| m_ControlPathsToMatch.Select(x => m_RebindingOperation.WithControlsHavingToMatchPath(x)); | |
| m_RebindingOperation.Start(); | |
| } | |
| private void StopListening() | |
| { | |
| m_RebindingOperation?.Cancel(); | |
| } | |
| // This differs from RebindingOperation.GeneratePathForControl in that it cycles through all | |
| // layouts in the inheritance chain and generates a path for each one that contains the given control. | |
| private static IEnumerable<string> GeneratePossiblePathsForControl(InputControl control) | |
| { | |
| var builder = new StringBuilder(); | |
| var deviceLayoutName = control.device.m_Layout; | |
| do | |
| { | |
| // Skip layout if it is supposed to be hidden in the UI. | |
| var layout = EditorInputControlLayoutCache.TryGetLayout(deviceLayoutName); | |
| if (layout.hideInUI) | |
| continue; | |
| builder.Length = 0; | |
| yield return control.BuildPath(deviceLayoutName, builder); | |
| } | |
| while (InputControlLayout.s_Layouts.baseLayoutTable.TryGetValue(deviceLayoutName, out deviceLayoutName)); | |
| } | |
| private Action<string> m_OnPickCallback; | |
| private InputControlPicker.Mode m_Mode; | |
| private string[] m_ControlPathsToMatch; | |
| private string m_ExpectedControlLayout; | |
| private Type m_ExpectedControlType; | |
| private InputActionRebindingExtensions.RebindingOperation m_RebindingOperation; | |
| private bool isListening => m_RebindingOperation != null && m_RebindingOperation.started; | |
| private class InputControlPickerGUI : AdvancedDropdownGUI | |
| { | |
| private readonly InputControlPickerDropdown m_Owner; | |
| public InputControlPickerGUI(InputControlPickerDropdown owner) | |
| { | |
| m_Owner = owner; | |
| } | |
| internal override void BeginDraw(EditorWindow window) | |
| { | |
| if (Event.current.isKey && Event.current.keyCode == KeyCode.Escape) | |
| { | |
| window.Close(); | |
| return; | |
| } | |
| if (m_Owner.isListening) | |
| { | |
| // Eat key events to suppress the editor from passing them to the OS | |
| // (causing beeps or menu commands being triggered). | |
| if (Event.current.isKey) | |
| Event.current.Use(); | |
| } | |
| } | |
| internal override string DrawSearchFieldControl(string searchString) | |
| { | |
| using (new EditorGUILayout.HorizontalScope()) | |
| { | |
| var isListening = false; | |
| // When picking controls, have a "Listen" button that allows listening for input. | |
| if (m_Owner.m_Mode == InputControlPicker.Mode.PickControl) | |
| { | |
| using (new EditorGUILayout.VerticalScope(GUILayout.MaxWidth(50))) | |
| { | |
| GUILayout.Space(4); | |
| var isListeningOld = m_Owner.isListening; | |
| var isListeningNew = GUILayout.Toggle(isListeningOld, "Listen", | |
| EditorStyles.miniButton, GUILayout.MaxWidth(50)); | |
| if (isListeningOld != isListeningNew) | |
| { | |
| if (isListeningNew) | |
| { | |
| m_Owner.StartListening(); | |
| } | |
| else | |
| { | |
| m_Owner.StopListening(); | |
| searchString = string.Empty; | |
| } | |
| } | |
| isListening = isListeningNew; | |
| } | |
| } | |
| ////FIXME: the search box doesn't clear out when listening; no idea why the new string isn't taking effect | |
| EditorGUI.BeginDisabledGroup(isListening); | |
| var newSearchString = base.DrawSearchFieldControl(isListening ? string.Empty : searchString); | |
| EditorGUI.EndDisabledGroup(); | |
| if (isListening) | |
| { | |
| var rebind = m_Owner.m_RebindingOperation; | |
| return "\u0017" + string.Join("\u0017", | |
| rebind.candidates.SelectMany(x => GeneratePossiblePathsForControl(x).Reverse())); | |
| } | |
| return newSearchString; | |
| } | |
| } | |
| internal override void DrawItem(AdvancedDropdownItem item, string name, Texture2D icon, bool enabled, | |
| bool drawArrow, bool selected, bool hasSearch, bool richText = false) | |
| { | |
| if (hasSearch && item is InputControlDropdownItem viewItem) | |
| name = viewItem.searchableName; | |
| base.DrawItem(item, name, icon, enabled, drawArrow, selected, hasSearch); | |
| } | |
| internal override void DrawFooter(AdvancedDropdownItem selectedItem) | |
| { | |
| //dun work because there is no selection | |
| if (selectedItem is ControlDropdownItem controlItem) | |
| { | |
| var content = new GUIContent(controlItem.controlPath); | |
| var rect = GUILayoutUtility.GetRect(content, headerStyle, GUILayout.ExpandWidth(true)); | |
| EditorGUI.TextField(rect, controlItem.controlPath, headerStyle); | |
| } | |
| } | |
| } | |
| private static class Styles | |
| { | |
| public static readonly GUIStyle waitingForInputLabel = new GUIStyle("WhiteBoldLabel").WithFontSize(22); | |
| } | |
| } | |
| } | |