Aucune description
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

InputControlPickerDropdown.cs 29KB

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