A game about forced loneliness, made by TACStudios
1#if UNITY_EDITOR
2using System;
3using System.Collections.Generic;
4using System.Linq;
5using System.Text;
6using UnityEditor;
7using UnityEngine.InputSystem.Controls;
8using UnityEngine.InputSystem.Layouts;
9using UnityEngine.InputSystem.Utilities;
10
11////TODO: have tooltips on each entry in the picker
12
13////TODO: find better way to present controls when filtering to specific devices
14
15////REVIEW: if there's only a single device in the picker, automatically go into it?
16
17namespace UnityEngine.InputSystem.Editor
18{
19 internal class InputControlPickerDropdown : AdvancedDropdown, IDisposable
20 {
21 public InputControlPickerDropdown(
22 InputControlPickerState state,
23 Action<string> onPickCallback,
24 InputControlPicker.Mode mode = InputControlPicker.Mode.PickControl)
25 : base(state.advancedDropdownState)
26 {
27 m_Gui = new InputControlPickerGUI(this);
28
29 minimumSize = new Vector2(275, 300);
30 maximumSize = new Vector2(0, 300);
31
32 m_OnPickCallback = onPickCallback;
33 m_Mode = mode;
34 }
35
36 public void SetControlPathsToMatch(string[] controlPathsToMatch)
37 {
38 m_ControlPathsToMatch = controlPathsToMatch;
39 Reload();
40 }
41
42 public void SetExpectedControlLayout(string expectedControlLayout)
43 {
44 m_ExpectedControlLayout = expectedControlLayout;
45
46 if (string.Equals(expectedControlLayout, "InputDevice", StringComparison.InvariantCultureIgnoreCase))
47 m_ExpectedControlType = typeof(InputDevice);
48 else
49 m_ExpectedControlType = !string.IsNullOrEmpty(expectedControlLayout)
50 ? InputSystem.s_Manager.m_Layouts.GetControlTypeForLayout(new InternedString(expectedControlLayout))
51 : null;
52
53 // If the layout is for a device, automatically switch to device
54 // picking mode.
55 if (m_ExpectedControlType != null && typeof(InputDevice).IsAssignableFrom(m_ExpectedControlType))
56 m_Mode = InputControlPicker.Mode.PickDevice;
57
58 Reload();
59 }
60
61 public void SetPickedCallback(Action<string> action)
62 {
63 m_OnPickCallback = action;
64 }
65
66 protected override void OnDestroy()
67 {
68 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
69 InputActionsEditorSettingsProvider.SetIMGUIDropdownVisible(false, false);
70 #endif
71 m_RebindingOperation?.Dispose();
72 m_RebindingOperation = null;
73 }
74
75 public void Dispose()
76 {
77 m_RebindingOperation?.Dispose();
78 }
79
80 protected override AdvancedDropdownItem BuildRoot()
81 {
82 var root = new AdvancedDropdownItem(string.Empty);
83
84 // Usages.
85 if (m_Mode != InputControlPicker.Mode.PickDevice)
86 {
87 var usages = BuildTreeForControlUsages();
88 if (usages.children.Any())
89 {
90 root.AddChild(usages);
91 root.AddSeparator();
92 }
93 }
94
95 // Devices.
96 AddItemsForDevices(root);
97
98 return root;
99 }
100
101 protected override AdvancedDropdownItem BuildCustomSearch(string searchString,
102 IEnumerable<AdvancedDropdownItem> elements)
103 {
104 if (!isListening)
105 return null;
106
107 var root = new AdvancedDropdownItem(!string.IsNullOrEmpty(m_ExpectedControlLayout)
108 ? $"Listening for {m_ExpectedControlLayout}..."
109 : "Listening for input...");
110
111 if (searchString == "\u0017")
112 return root;
113
114 var paths = searchString.Substring(1).Split('\u0017');
115 foreach (var element in elements)
116 {
117 if (element is ControlDropdownItem controlItem && paths.Any(x => controlItem.controlPathWithDevice == x))
118 root.AddChild(element);
119 }
120
121 return root;
122 }
123
124 protected override void ItemSelected(AdvancedDropdownItem item)
125 {
126 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
127 InputActionsEditorSettingsProvider.SetIMGUIDropdownVisible(false, true);
128 #endif
129 var path = ((InputControlDropdownItem)item).controlPathWithDevice;
130 m_OnPickCallback(path);
131 }
132
133 private AdvancedDropdownItem BuildTreeForControlUsages(string device = "", string usage = "")
134 {
135 var usageRoot = new AdvancedDropdownItem("Usages");
136 foreach (var usageAndLayouts in EditorInputControlLayoutCache.allUsages)
137 {
138 if (usageAndLayouts.Item2.Any(LayoutMatchesExpectedControlLayoutFilter))
139 {
140 var child = new ControlUsageDropdownItem(device, usage, usageAndLayouts.Item1);
141 usageRoot.AddChild(child);
142 }
143 }
144 return usageRoot;
145 }
146
147 private void AddItemsForDevices(AdvancedDropdownItem parent)
148 {
149 // Add devices that are marked as generic types of devices directly to the parent.
150 // E.g. adds "Gamepad" and then underneath all the more specific types of gamepads.
151 foreach (var deviceLayout in EditorInputControlLayoutCache.allLayouts
152 .Where(x => x.isDeviceLayout && !x.isOverride && x.isGenericTypeOfDevice && !x.hideInUI)
153 .OrderBy(a => a.displayName))
154 {
155 AddDeviceTreeItemRecursive(deviceLayout, parent);
156 }
157
158 // We have devices that are based directly on InputDevice but are not marked as generic types
159 // of devices (e.g. Vive Lighthouses). We do not want them to clutter the list at the root so we
160 // put all of them in a group called "Other" at the end of the list.
161 var otherGroup = new AdvancedDropdownItem("Other");
162 foreach (var deviceLayout in EditorInputControlLayoutCache.allLayouts
163 .Where(x => x.isDeviceLayout && !x.isOverride && !x.isGenericTypeOfDevice &&
164 (x.type.BaseType == typeof(InputDevice) || x.type == typeof(InputDevice)) &&
165 !x.hideInUI && !x.baseLayouts.Any()).OrderBy(a => a.displayName))
166 {
167 AddDeviceTreeItemRecursive(deviceLayout, otherGroup);
168 }
169
170 if (otherGroup.children.Any())
171 parent.AddChild(otherGroup);
172 }
173
174 private void AddDeviceTreeItemRecursive(InputControlLayout layout, AdvancedDropdownItem parent, bool searchable = true)
175 {
176 // Find all layouts directly based on this one (ignoring overrides).
177 var childLayouts = EditorInputControlLayoutCache.allLayouts
178 .Where(x => x.isDeviceLayout && !x.isOverride && !x.hideInUI && x.baseLayouts.Contains(layout.name)).OrderBy(x => x.displayName);
179
180 // See if the entire tree should be excluded.
181 var shouldIncludeDeviceLayout = ShouldIncludeDeviceLayout(layout);
182 var shouldIncludeAtLeastOneChildLayout = childLayouts.Any(ShouldIncludeDeviceLayout);
183
184 if (!shouldIncludeDeviceLayout && !shouldIncludeAtLeastOneChildLayout)
185 return;
186
187 // Add toplevel item for device.
188 var deviceItem = new DeviceDropdownItem(layout, searchable: searchable);
189
190 var defaultControlPickerLayout = new DefaultInputControlPickerLayout();
191
192 // Add common usage variants of the device
193 if (layout.commonUsages.Count > 0)
194 {
195 foreach (var usage in layout.commonUsages)
196 {
197 var usageItem = new DeviceDropdownItem(layout, usage);
198
199 // Add control usages to the device variants
200 var deviceVariantControlUsages = BuildTreeForControlUsages(layout.name, usage);
201 if (deviceVariantControlUsages.children.Any())
202 {
203 usageItem.AddChild(deviceVariantControlUsages);
204 usageItem.AddSeparator();
205 }
206
207 if (m_Mode == InputControlPicker.Mode.PickControl)
208 AddControlTreeItemsRecursive(defaultControlPickerLayout, layout, usageItem, layout.name, usage, searchable);
209 deviceItem.AddChild(usageItem);
210 }
211 deviceItem.AddSeparator();
212 }
213
214 // Add control usages
215 var deviceControlUsages = BuildTreeForControlUsages(layout.name);
216 if (deviceControlUsages.children.Any())
217 {
218 deviceItem.AddChild(deviceControlUsages);
219 deviceItem.AddSeparator();
220 }
221
222 // Add controls.
223 if (m_Mode != InputControlPicker.Mode.PickDevice)
224 {
225 // The keyboard is special in that we want to allow binding by display name (i.e. character
226 // generated by a key) instead of only by physical key location. Also, we want to give an indication
227 // of which specific key an entry refers to by taking the current keyboard layout into account.
228 //
229 // So what we do is add an extra level to the keyboard where key's can be bound by character
230 // according to the current layout. And in the top level of the keyboard we display keys with
231 // both physical and logical names.
232 if (layout.type == typeof(Keyboard) && InputSystem.GetDevice<Keyboard>() != null)
233 {
234 var byLocationGroup = new AdvancedDropdownItem("By Location of Key (Using US Layout)");
235 var byCharacterGroup = new AdvancedDropdownItem("By Character Mapped to Key");
236
237 deviceItem.AddChild(byLocationGroup);
238 deviceItem.AddChild(byCharacterGroup);
239
240 var keyboard = InputSystem.GetDevice<Keyboard>();
241
242 AddCharacterKeyBindingsTo(byCharacterGroup, keyboard);
243 AddPhysicalKeyBindingsTo(byLocationGroup, keyboard, searchable);
244
245 // AnyKey won't appear in either group. Add it explicitly.
246 AddControlItem(defaultControlPickerLayout, deviceItem, null,
247 layout.FindControl(new InternedString("anyKey")).Value, layout.name, null, searchable);
248 }
249 else if (layout.type == typeof(Touchscreen))
250 {
251 AddControlTreeItemsRecursive(new TouchscreenControlPickerLayout(), layout, deviceItem, layout.name, null, searchable);
252 }
253 else
254 {
255 AddControlTreeItemsRecursive(defaultControlPickerLayout, layout, deviceItem, layout.name, null, searchable);
256 }
257 }
258
259 // Add child items.
260 var isFirstChild = true;
261 foreach (var childLayout in childLayouts)
262 {
263 if (!ShouldIncludeDeviceLayout(childLayout))
264 continue;
265
266 if (isFirstChild)
267 deviceItem.AddSeparator("More Specific " + deviceItem.name.GetPlural());
268 isFirstChild = false;
269
270 AddDeviceTreeItemRecursive(childLayout, deviceItem, searchable && !childLayout.isGenericTypeOfDevice);
271 }
272
273 // When picking devices, it must be possible to select a device that itself has more specific types
274 // of devices underneath it. However in the dropdown, such a device will be a foldout and not itself
275 // be selectable. We solve this problem by adding an entry for the device underneath the device
276 // itself (e.g. "Gamepad >> Gamepad").
277 if (m_Mode == InputControlPicker.Mode.PickDevice && deviceItem.m_Children.Count > 0)
278 {
279 var item = new DeviceDropdownItem(layout);
280 deviceItem.m_Children.Insert(0, item);
281 }
282
283 if (deviceItem.m_Children.Count > 0 || m_Mode == InputControlPicker.Mode.PickDevice)
284 parent.AddChild(deviceItem);
285 }
286
287 private void AddControlTreeItemsRecursive(IInputControlPickerLayout controlPickerLayout, InputControlLayout layout,
288 DeviceDropdownItem parent, string device, string usage, bool searchable, ControlDropdownItem parentControl = null)
289 {
290 foreach (var control in layout.controls.OrderBy(a => a.name))
291 {
292 if (control.isModifyingExistingControl)
293 continue;
294
295 // Skip variants except the default variant and variants dictated by the layout itself.
296 if (!control.variants.IsEmpty() && control.variants != InputControlLayout.DefaultVariant
297 && (layout.variants.IsEmpty() || !InputControlLayout.VariantsMatch(layout.variants, control.variants)))
298 {
299 continue;
300 }
301
302 controlPickerLayout.AddControlItem(this, parent, parentControl, control, device, usage, searchable);
303 }
304
305 // Add optional controls for devices.
306 var optionalControls = EditorInputControlLayoutCache.GetOptionalControlsForLayout(layout.name);
307 if (optionalControls.Any() && layout.isDeviceLayout)
308 {
309 var optionalGroup = new AdvancedDropdownItem("Optional Controls");
310 foreach (var optionalControl in optionalControls)
311 {
312 ////FIXME: this should list children, too
313 ////FIXME: this should handle arrays, too
314 if (LayoutMatchesExpectedControlLayoutFilter(optionalControl.layout))
315 {
316 var child = new OptionalControlDropdownItem(optionalControl, device, usage);
317 child.icon = EditorInputControlLayoutCache.GetIconForLayout(optionalControl.layout);
318 optionalGroup.AddChild(child);
319 }
320 }
321
322 if (optionalGroup.children.Any())
323 {
324 var deviceName = EditorInputControlLayoutCache.TryGetLayout(device).m_DisplayName ??
325 ObjectNames.NicifyVariableName(device);
326 parent.AddSeparator("Controls Present on More Specific " + deviceName.GetPlural());
327 parent.AddChild(optionalGroup);
328 }
329 }
330 }
331
332 internal void AddControlItem(IInputControlPickerLayout controlPickerLayout,
333 DeviceDropdownItem parent, ControlDropdownItem parentControl,
334 InputControlLayout.ControlItem control, string device, string usage, bool searchable,
335 string controlNameOverride = default)
336 {
337 var controlName = controlNameOverride ?? control.name;
338
339 // If it's an array, generate a control entry for each array element.
340 for (var i = 0; i < (control.isArray ? control.arraySize : 1); ++i)
341 {
342 var name = control.isArray ? controlName + i : controlName;
343 var displayName = !string.IsNullOrEmpty(control.displayName)
344 ? (control.isArray ? $"{control.displayName} #{i}" : control.displayName)
345 : name;
346
347 var child = new ControlDropdownItem(parentControl, name, displayName,
348 device, usage, searchable);
349 child.icon = EditorInputControlLayoutCache.GetIconForLayout(control.layout);
350 var controlLayout = EditorInputControlLayoutCache.TryGetLayout(control.layout);
351
352 if (LayoutMatchesExpectedControlLayoutFilter(control.layout))
353 parent.AddChild(child);
354 else if (controlLayout.controls.Any(x => LayoutMatchesExpectedControlLayoutFilter(x.layout)))
355 {
356 child.enabled = false;
357 parent.AddChild(child);
358 }
359 // Add children.
360 if (controlLayout != null)
361 AddControlTreeItemsRecursive(controlPickerLayout, controlLayout, parent, device, usage,
362 searchable, child);
363 }
364 }
365
366 private static void AddPhysicalKeyBindingsTo(AdvancedDropdownItem parent, Keyboard keyboard, bool searchable)
367 {
368 foreach (var key in keyboard.children.OfType<KeyControl>())
369 {
370 // If the key has a display name that differs from the key name, show it in the UI.
371 var displayName = key.m_DisplayNameFromLayout;
372 var keyDisplayName = key.displayName;
373 if (keyDisplayName.All(x => x.IsPrintable()) && string.Compare(keyDisplayName, displayName,
374 StringComparison.InvariantCultureIgnoreCase) != 0)
375 displayName = $"{displayName} (Current Layout: {key.displayName})";
376
377 // For left/right modifier keys, prepend artificial combined version.
378 ButtonControl combinedVersion = null;
379 if (key == keyboard.leftShiftKey)
380 combinedVersion = keyboard.shiftKey;
381 else if (key == keyboard.leftAltKey)
382 combinedVersion = keyboard.altKey;
383 else if (key == keyboard.leftCtrlKey)
384 combinedVersion = keyboard.ctrlKey;
385 if (combinedVersion != null)
386 parent.AddChild(new ControlDropdownItem(null, combinedVersion.name, combinedVersion.displayName, keyboard.layout,
387 "", searchable));
388
389 var item = new ControlDropdownItem(null, key.name, displayName,
390 keyboard.layout, "", searchable);
391
392 parent.AddChild(item);
393 }
394 }
395
396 private static void AddCharacterKeyBindingsTo(AdvancedDropdownItem parent, Keyboard keyboard)
397 {
398 foreach (var key in keyboard.children.OfType<KeyControl>())
399 {
400 if (!key.keyCode.IsTextInputKey())
401 continue;
402
403 // We can only bind to characters that can be printed.
404 var displayName = key.displayName;
405 if (!displayName.All(x => x.IsPrintable()))
406 continue;
407
408 if (displayName.Contains(')'))
409 displayName = string.Join("", displayName.Select(x => "\\" + x));
410
411 ////TODO: should be searchable; when searching, needs different display name
412 var item = new ControlDropdownItem(null, $"#({displayName})", "", keyboard.layout, "", false);
413 item.name = key.displayName;
414
415 parent.AddChild(item);
416 }
417 }
418
419 private bool LayoutMatchesExpectedControlLayoutFilter(string layout)
420 {
421 if (m_ExpectedControlType == null)
422 return true;
423
424 var layoutType = InputSystem.s_Manager.m_Layouts.GetControlTypeForLayout(new InternedString(layout));
425 return m_ExpectedControlType.IsAssignableFrom(layoutType);
426 }
427
428 private bool ShouldIncludeDeviceLayout(InputControlLayout layout)
429 {
430 if (layout.hideInUI)
431 return false;
432
433 // By default, if a device has no (usable) controls, we don't want it listed in the control picker
434 // except if we're picking devices.
435 if (!layout.controls.Any(x => LayoutMatchesExpectedControlLayoutFilter(x.layout)) && layout.controls.Any(x => true) &&
436 m_Mode != InputControlPicker.Mode.PickDevice)
437 return false;
438
439 // If we have a device filter, see if we should ignore the device.
440 if (m_ControlPathsToMatch != null && m_ControlPathsToMatch.Length > 0)
441 {
442 var matchesAnyInDeviceFilter = false;
443 foreach (var entry in m_ControlPathsToMatch)
444 {
445 // Include the layout if it's in the inheritance hierarchy of the layout we expect (either below
446 // or above it or, well, just right on it).
447 var expectedLayout = InputControlPath.TryGetDeviceLayout(entry);
448 if (!string.IsNullOrEmpty(expectedLayout) &&
449 (expectedLayout == layout.name ||
450 InputControlLayout.s_Layouts.IsBasedOn(layout.name, new InternedString(expectedLayout)) ||
451 InputControlLayout.s_Layouts.IsBasedOn(new InternedString(expectedLayout), layout.name)))
452 {
453 matchesAnyInDeviceFilter = true;
454 break;
455 }
456 }
457
458 if (!matchesAnyInDeviceFilter)
459 return false;
460 }
461
462 return true;
463 }
464
465 private void StartListening()
466 {
467 if (m_RebindingOperation == null)
468 m_RebindingOperation = new InputActionRebindingExtensions.RebindingOperation();
469
470 ////TODO: for keyboard, generate both possible paths (physical and by display name)
471
472 m_RebindingOperation.Reset();
473 m_RebindingOperation
474 .WithExpectedControlType(m_ExpectedControlLayout)
475 // Require minimum actuation of 0.15f. This is after deadzoning has been applied.
476 .WithMagnitudeHavingToBeGreaterThan(0.15f)
477 ////REVIEW: should we exclude only the system's active pointing device?
478 // With the mouse operating the UI, its cursor control is too fickle a thing to
479 // bind to. Ignore mouse position and delta and clicks.
480 // NOTE: We go for all types of pointers here, not just mice.
481 .WithControlsExcluding("<Pointer>/position")
482 .WithControlsExcluding("<Pointer>/delta")
483 .WithControlsExcluding("<Pointer>/press")
484 .WithControlsExcluding("<Pointer>/clickCount")
485 .WithControlsExcluding("<Pointer>/{PrimaryAction}")
486 .WithControlsExcluding("<Mouse>/scroll")
487 .OnPotentialMatch(
488 operation =>
489 {
490 // We never really complete the pick but keep listening for as long as the "Interactive"
491 // button is toggled on.
492
493 Repaint();
494 })
495 .OnCancel(
496 operation =>
497 {
498 Repaint();
499 })
500 .OnApplyBinding(
501 (operation, newPath) =>
502 {
503 // This is never invoked (because we don't complete the pick) but we need it nevertheless
504 // as RebindingOperation requires the callback if we don't supply an action to apply the binding to.
505 });
506
507 // If we have control paths to match, pass them on.
508 if (m_ControlPathsToMatch.LengthSafe() > 0)
509 m_ControlPathsToMatch.Select(x => m_RebindingOperation.WithControlsHavingToMatchPath(x));
510
511 m_RebindingOperation.Start();
512 }
513
514 private void StopListening()
515 {
516 m_RebindingOperation?.Cancel();
517 }
518
519 // This differs from RebindingOperation.GeneratePathForControl in that it cycles through all
520 // layouts in the inheritance chain and generates a path for each one that contains the given control.
521 private static IEnumerable<string> GeneratePossiblePathsForControl(InputControl control)
522 {
523 var builder = new StringBuilder();
524 var deviceLayoutName = control.device.m_Layout;
525 do
526 {
527 // Skip layout if it is supposed to be hidden in the UI.
528 var layout = EditorInputControlLayoutCache.TryGetLayout(deviceLayoutName);
529 if (layout.hideInUI)
530 continue;
531
532 builder.Length = 0;
533 yield return control.BuildPath(deviceLayoutName, builder);
534 }
535 while (InputControlLayout.s_Layouts.baseLayoutTable.TryGetValue(deviceLayoutName, out deviceLayoutName));
536 }
537
538 private Action<string> m_OnPickCallback;
539 private InputControlPicker.Mode m_Mode;
540 private string[] m_ControlPathsToMatch;
541 private string m_ExpectedControlLayout;
542 private Type m_ExpectedControlType;
543 private InputActionRebindingExtensions.RebindingOperation m_RebindingOperation;
544
545 private bool isListening => m_RebindingOperation != null && m_RebindingOperation.started;
546
547 private class InputControlPickerGUI : AdvancedDropdownGUI
548 {
549 private readonly InputControlPickerDropdown m_Owner;
550
551 public InputControlPickerGUI(InputControlPickerDropdown owner)
552 {
553 m_Owner = owner;
554 }
555
556 internal override void BeginDraw(EditorWindow window)
557 {
558 if (Event.current.isKey && Event.current.keyCode == KeyCode.Escape)
559 {
560 window.Close();
561 return;
562 }
563
564 if (m_Owner.isListening)
565 {
566 // Eat key events to suppress the editor from passing them to the OS
567 // (causing beeps or menu commands being triggered).
568 if (Event.current.isKey)
569 Event.current.Use();
570 }
571 }
572
573 internal override string DrawSearchFieldControl(string searchString)
574 {
575 using (new EditorGUILayout.HorizontalScope())
576 {
577 var isListening = false;
578
579 // When picking controls, have a "Listen" button that allows listening for input.
580 if (m_Owner.m_Mode == InputControlPicker.Mode.PickControl)
581 {
582 using (new EditorGUILayout.VerticalScope(GUILayout.MaxWidth(50)))
583 {
584 GUILayout.Space(4);
585 var isListeningOld = m_Owner.isListening;
586 var isListeningNew = GUILayout.Toggle(isListeningOld, "Listen",
587 EditorStyles.miniButton, GUILayout.MaxWidth(50));
588
589 if (isListeningOld != isListeningNew)
590 {
591 if (isListeningNew)
592 {
593 m_Owner.StartListening();
594 }
595 else
596 {
597 m_Owner.StopListening();
598 searchString = string.Empty;
599 }
600 }
601
602 isListening = isListeningNew;
603 }
604 }
605
606 ////FIXME: the search box doesn't clear out when listening; no idea why the new string isn't taking effect
607 EditorGUI.BeginDisabledGroup(isListening);
608 var newSearchString = base.DrawSearchFieldControl(isListening ? string.Empty : searchString);
609 EditorGUI.EndDisabledGroup();
610
611 if (isListening)
612 {
613 var rebind = m_Owner.m_RebindingOperation;
614 return "\u0017" + string.Join("\u0017",
615 rebind.candidates.SelectMany(x => GeneratePossiblePathsForControl(x).Reverse()));
616 }
617
618 return newSearchString;
619 }
620 }
621
622 internal override void DrawItem(AdvancedDropdownItem item, string name, Texture2D icon, bool enabled,
623 bool drawArrow, bool selected, bool hasSearch, bool richText = false)
624 {
625 if (hasSearch && item is InputControlDropdownItem viewItem)
626 name = viewItem.searchableName;
627
628 base.DrawItem(item, name, icon, enabled, drawArrow, selected, hasSearch);
629 }
630
631 internal override void DrawFooter(AdvancedDropdownItem selectedItem)
632 {
633 //dun work because there is no selection
634 if (selectedItem is ControlDropdownItem controlItem)
635 {
636 var content = new GUIContent(controlItem.controlPath);
637 var rect = GUILayoutUtility.GetRect(content, headerStyle, GUILayout.ExpandWidth(true));
638 EditorGUI.TextField(rect, controlItem.controlPath, headerStyle);
639 }
640 }
641 }
642
643 private static class Styles
644 {
645 public static readonly GUIStyle waitingForInputLabel = new GUIStyle("WhiteBoldLabel").WithFontSize(22);
646 }
647 }
648}
649#endif // UNITY_EDITOR