123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- #if UNITY_EDITOR
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using UnityEngine.InputSystem.Layouts;
- using UnityEngine.InputSystem.DualShock;
- using UnityEngine.InputSystem.Utilities;
-
- namespace UnityEngine.InputSystem.Editor
- {
- /// <summary>
- /// Caches <see cref="InputControlLayout"/> instances.
- /// </summary>
- /// <remarks>
- /// In the editor we need access to the <see cref="InputControlLayout">InputControlLayouts</see>
- /// registered with the system in order to facilitate various UI features. Instead of
- /// constructing layout instances over and over, we keep them around in here.
- ///
- /// This class is only available in the editor (when <c>UNITY_EDITOR</c> is true).
- /// </remarks>
- internal static class EditorInputControlLayoutCache
- {
- /// <summary>
- /// Iterate over all control layouts in the system.
- /// </summary>
- public static IEnumerable<InputControlLayout> allLayouts
- {
- get
- {
- Refresh();
- return InputControlLayout.cache.table.Values;
- }
- }
-
- /// <summary>
- /// Iterate over all unique usages and their respective lists of layouts that use them.
- /// </summary>
- public static IEnumerable<Tuple<string, IEnumerable<string>>> allUsages
- {
- get
- {
- Refresh();
- return s_Usages.Select(pair => new Tuple<string, IEnumerable<string>>(pair.Key, pair.Value.Select(x => x.ToString())));
- }
- }
-
- public static IEnumerable<InputControlLayout> allControlLayouts
- {
- get
- {
- Refresh();
- foreach (var name in s_ControlLayouts)
- yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString());
- }
- }
-
- public static IEnumerable<InputControlLayout> allDeviceLayouts
- {
- get
- {
- Refresh();
- foreach (var name in s_DeviceLayouts)
- yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString());
- }
- }
-
- public static IEnumerable<InputControlLayout> allProductLayouts
- {
- get
- {
- Refresh();
- foreach (var name in s_ProductLayouts)
- yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString());
- }
- }
-
- public static bool HasChildLayouts(string layoutName)
- {
- if (string.IsNullOrEmpty(layoutName))
- throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
-
- Refresh();
-
- var internedLayout = new InternedString(layoutName);
- // return nothing is the layout does not have any derivations
- return s_DeviceChildLayouts.TryGetValue(internedLayout, out var derivations) && derivations.Count > 0;
- }
-
- public static IEnumerable<InputControlLayout> TryGetChildLayouts(string layoutName)
- {
- if (string.IsNullOrEmpty(layoutName))
- throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
-
- Refresh();
-
- var internedLayout = new InternedString(layoutName);
- // return nothing is the layout does not have any derivations
- if (!s_DeviceChildLayouts.TryGetValue(internedLayout, out var derivations))
- yield break;
- else
- {
- foreach (var name in derivations)
- yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString());
- }
- }
-
- public static InputControlLayout TryGetLayout(string layoutName)
- {
- if (string.IsNullOrEmpty(layoutName))
- throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
-
- Refresh();
- return InputControlLayout.cache.FindOrLoadLayout(layoutName, throwIfNotFound: false);
- }
-
- public static Type GetValueType(string layoutName)
- {
- if (string.IsNullOrEmpty(layoutName))
- throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
-
- // Load layout.
- var layout = TryGetLayout(layoutName);
- if (layout == null)
- return null;
-
- // Grab type.
- var type = layout.type;
- Debug.Assert(type != null, "Layout should have associated type");
- Debug.Assert(typeof(InputControl).IsAssignableFrom(type),
- "Layout's associated type should be derived from InputControl");
-
- return layout.GetValueType();
- }
-
- public static IEnumerable<InputDeviceMatcher> GetDeviceMatchers(string layoutName)
- {
- if (string.IsNullOrEmpty(layoutName))
- throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
-
- Refresh();
- s_DeviceMatchers.TryGetValue(new InternedString(layoutName), out var matchers);
- return matchers;
- }
-
- public static string GetDisplayName(string layoutName)
- {
- if (string.IsNullOrEmpty(layoutName))
- throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
-
- var layout = TryGetLayout(layoutName);
- if (layout == null)
- return layoutName;
-
- if (!string.IsNullOrEmpty(layout.displayName))
- return layout.displayName;
- return layout.name;
- }
-
- /// <summary>
- /// List the controls that may be present on controls or devices of the given layout by virtue
- /// of being defined in other layouts based on it.
- /// </summary>
- /// <param name="layoutName"></param>
- /// <returns></returns>
- public static IEnumerable<OptionalControl> GetOptionalControlsForLayout(string layoutName)
- {
- if (string.IsNullOrEmpty(layoutName))
- throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
-
- Refresh();
-
- if (!s_OptionalControls.TryGetValue(new InternedString(layoutName), out var list))
- return Enumerable.Empty<OptionalControl>();
-
- return list;
- }
-
- public static Texture2D GetIconForLayout(string layoutName)
- {
- if (string.IsNullOrEmpty(layoutName))
- throw new ArgumentNullException(nameof(layoutName));
-
- Refresh();
-
- // See if we already have it in the cache.
- var internedName = new InternedString(layoutName);
- if (s_Icons.TryGetValue(internedName, out var icon))
- return icon;
-
- // No, so see if we have an icon on disk for exactly the layout
- // we're looking at (i.e. with the same name).
- icon = GUIHelpers.LoadIcon(layoutName);
- if (icon != null)
- {
- s_Icons.Add(internedName, icon);
- return icon;
- }
-
- // No, not that either so start walking up the inheritance chain
- // until we either bump against the ceiling or find an icon.
- var layout = TryGetLayout(layoutName);
- if (layout != null)
- {
- foreach (var baseLayoutName in layout.baseLayouts)
- {
- icon = GetIconForLayout(baseLayoutName);
- if (icon != null)
- return icon;
- }
-
- // If it's a control and there's no specific icon, return a generic one.
- if (layout.isControlLayout)
- {
- var genericIcon = GUIHelpers.LoadIcon("InputControl");
- if (genericIcon != null)
- {
- s_Icons.Add(internedName, genericIcon);
- return genericIcon;
- }
- }
- }
-
- // No icon for anything in this layout's chain.
- return null;
- }
-
- public struct ControlSearchResult
- {
- public string controlPath;
- public InputControlLayout layout;
- public InputControlLayout.ControlItem item;
- }
-
- internal static void Clear()
- {
- s_LayoutRegistrationVersion = 0;
- s_LayoutCacheRef.Dispose();
- s_Usages.Clear();
- s_ControlLayouts.Clear();
- s_DeviceLayouts.Clear();
- s_ProductLayouts.Clear();
- s_DeviceMatchers.Clear();
- s_Icons.Clear();
- }
-
- // If our layout data is outdated, rescan all the layouts in the system.
- private static void Refresh()
- {
- var manager = InputSystem.s_Manager;
- if (manager.m_LayoutRegistrationVersion == s_LayoutRegistrationVersion)
- return;
-
- Clear();
-
- if (!s_LayoutCacheRef.valid)
- {
- // In the editor, we keep a permanent reference on the global layout
- // cache. Means that in the editor, we always have all layouts loaded in full
- // at all times whereas in the player, we load layouts only while we need
- // them and then release them again.
- s_LayoutCacheRef = InputControlLayout.CacheRef();
- }
-
- var layoutNames = manager.ListControlLayouts().ToArray();
-
- // Remember which layout maps to which device matchers.
- var layoutMatchers = InputControlLayout.s_Layouts.layoutMatchers;
- foreach (var entry in layoutMatchers)
- {
- s_DeviceMatchers.TryGetValue(entry.layoutName, out var matchers);
-
- matchers.Append(entry.deviceMatcher);
- s_DeviceMatchers[entry.layoutName] = matchers;
- }
-
- // Load and store all layouts.
- foreach (var layoutName in layoutNames)
- {
- ////FIXME: does not protect against exceptions
- var layout = InputControlLayout.cache.FindOrLoadLayout(layoutName, throwIfNotFound: false);
- if (layout == null)
- continue;
-
- ScanLayout(layout);
-
- if (layout.isOverride)
- continue;
-
- if (layout.isControlLayout)
- s_ControlLayouts.Add(layout.name);
- else if (s_DeviceMatchers.ContainsKey(layout.name))
- s_ProductLayouts.Add(layout.name);
- else
- s_DeviceLayouts.Add(layout.name);
- }
-
- // Move all device layouts without a device description but derived from
- // a layout that has one over to the product list.
- foreach (var name in s_DeviceLayouts)
- {
- var layout = InputControlLayout.cache.FindOrLoadLayout(name);
-
- if (layout.m_BaseLayouts.length > 1)
- throw new NotImplementedException();
-
- for (var baseLayoutName = layout.baseLayouts.FirstOrDefault(); !baseLayoutName.IsEmpty();)
- {
- if (s_ProductLayouts.Contains(baseLayoutName))
- {
- // Defer removing from s_DeviceLayouts to keep iteration stable.
- s_ProductLayouts.Add(name);
- break;
- }
-
- var baseLayout = InputControlLayout.cache.FindOrLoadLayout(baseLayoutName, throwIfNotFound: false);
- if (baseLayout == null)
- continue;
- if (baseLayout.m_BaseLayouts.length > 1)
- throw new NotImplementedException();
- baseLayoutName = baseLayout.baseLayouts.FirstOrDefault();
- }
- }
-
- // Remove every product device layout now.
- s_DeviceLayouts.ExceptWith(s_ProductLayouts);
-
- s_LayoutRegistrationVersion = manager.m_LayoutRegistrationVersion;
- }
-
- private static int s_LayoutRegistrationVersion;
- private static InputControlLayout.CacheRefInstance s_LayoutCacheRef;
-
- private static readonly HashSet<InternedString> s_ControlLayouts = new HashSet<InternedString>();
- private static readonly HashSet<InternedString> s_DeviceLayouts = new HashSet<InternedString>();
- private static readonly HashSet<InternedString> s_ProductLayouts = new HashSet<InternedString>();
- private static readonly Dictionary<InternedString, List<OptionalControl>> s_OptionalControls =
- new Dictionary<InternedString, List<OptionalControl>>();
- private static readonly Dictionary<InternedString, InlinedArray<InputDeviceMatcher>> s_DeviceMatchers =
- new Dictionary<InternedString, InlinedArray<InputDeviceMatcher>>();
- private static Dictionary<InternedString, Texture2D> s_Icons =
- new Dictionary<InternedString, Texture2D>();
-
- // We keep a map of the devices which a derived from a base device.
- private static readonly Dictionary<InternedString, HashSet<InternedString>> s_DeviceChildLayouts =
- new Dictionary<InternedString, HashSet<InternedString>>();
-
-
- // We keep a map of all unique usages we find in layouts and also
- // retain a list of the layouts they are used with.
- private static readonly SortedDictionary<InternedString, HashSet<InternedString>> s_Usages =
- new SortedDictionary<InternedString, HashSet<InternedString>>();
-
- private static void ScanLayout(InputControlLayout layout)
- {
- var controls = layout.controls;
- for (var i = 0; i < controls.Count; ++i)
- {
- var control = controls[i];
-
- // If it's not just a control modifying some inner child control, add control to all base
- // layouts as an optional control.
- //
- // NOTE: We're looking at layouts post-merging here. Means we have already picked up all the
- // controls present on the base.
- // Only controls which belong to UI-facing layouts are included, as optional controls are used solely by
- // the InputControlPickerDropdown UI
- if (control.isFirstDefinedInThisLayout && !control.isModifyingExistingControl && !control.layout.IsEmpty() && !layout.hideInUI)
- {
- foreach (var baseLayout in layout.baseLayouts)
- AddOptionalControlRecursive(baseLayout, ref control);
- }
-
- // Collect unique usages and the layouts used with them.
- foreach (var usage in control.usages)
- {
- // Empty usages can occur for controls that want to reset inherited usages.
- if (string.IsNullOrEmpty(usage))
- continue;
-
- var internedUsage = new InternedString(usage);
- var internedLayout = new InternedString(control.layout);
-
- if (!s_Usages.TryGetValue(internedUsage, out var layoutSet))
- {
- layoutSet = new HashSet<InternedString> { internedLayout };
- s_Usages[internedUsage] = layoutSet;
- }
- else
- {
- layoutSet.Add(internedLayout);
- }
- }
-
- // Create a dependency tree matching each concrete device layout exposed in the UI
- // to all of the layouts that are directly derived from it.
- if (layout.isDeviceLayout && !layout.hideInUI)
- {
- foreach (var baseLayoutName in layout.baseLayouts)
- {
- if (!s_DeviceChildLayouts.TryGetValue(baseLayoutName, out var derivedSet))
- {
- derivedSet = new HashSet<InternedString> { layout.name };
- s_DeviceChildLayouts[baseLayoutName] = derivedSet;
- }
- else
- {
- derivedSet.Add(layout.name);
- }
- }
- }
- }
- }
-
- private static void AddOptionalControlRecursive(InternedString layoutName, ref InputControlLayout.ControlItem controlItem)
- {
- Debug.Assert(!controlItem.isModifyingExistingControl);
- Debug.Assert(!controlItem.layout.IsEmpty());
-
- // Recurse into base.
- if (InputControlLayout.s_Layouts.baseLayoutTable.TryGetValue(layoutName, out var baseLayoutName))
- AddOptionalControlRecursive(baseLayoutName, ref controlItem);
-
- // See if we already have this optional control.
- var alreadyPresent = false;
- if (!s_OptionalControls.TryGetValue(layoutName, out var list))
- {
- list = new List<OptionalControl>();
- s_OptionalControls[layoutName] = list;
- }
- else
- {
- // See if we already have this control.
- foreach (var item in list)
- {
- if (item.name == controlItem.name && item.layout == controlItem.layout)
- {
- alreadyPresent = true;
- break;
- }
- }
- }
- if (!alreadyPresent)
- list.Add(new OptionalControl {name = controlItem.name, layout = controlItem.layout});
- }
-
- /// <summary>
- /// An optional control is a control that is not defined on a layout but which is defined
- /// on a derived layout.
- /// </summary>
- /// <remarks>
- /// An example is the "acceleration" control defined by some layouts based on <see cref="Gamepad"/> (e.g.
- /// <see cref="DualShockGamepad.acceleration"/>. This means gamepads
- /// MAY have a gyro and thus MAY have an "acceleration" control.
- ///
- /// In bindings (<see cref="InputBinding"/>), it is perfectly valid to deal with this opportunistically
- /// and create a binding to <c>"<Gamepad>/acceleration"</c> which will bind correctly IF the gamepad has
- /// an acceleration control but will do nothing if it doesn't.
- ///
- /// The concept of optional controls permits setting up such bindings in the UI by making controls that
- /// are present on more specific layouts than the one currently looked at available directly on the
- /// base layout.
- /// </remarks>
- public struct OptionalControl
- {
- public InternedString name;
- public InternedString layout;
- ////REVIEW: do we want to have the list of layouts that define the control?
- }
- }
- }
- #endif // UNITY_EDITOR
|