207 lines
9.8 KiB
C#
207 lines
9.8 KiB
C#
#if UNITY_EDITOR
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine.UIElements;
|
|
using UnityEngine.InputSystem.Layouts;
|
|
using UnityEngine.InputSystem.Utilities;
|
|
using System.Collections.Generic;
|
|
|
|
namespace UnityEngine.InputSystem.Editor
|
|
{
|
|
internal class MatchingControlPath
|
|
{
|
|
public string deviceName
|
|
{
|
|
get;
|
|
}
|
|
public string controlName
|
|
{
|
|
get;
|
|
}
|
|
|
|
public bool isRoot
|
|
{
|
|
get;
|
|
}
|
|
public List<MatchingControlPath> children
|
|
{
|
|
get;
|
|
}
|
|
|
|
|
|
public MatchingControlPath(string deviceName, string controlName, bool isRoot)
|
|
{
|
|
this.deviceName = deviceName;
|
|
this.controlName = controlName;
|
|
this.isRoot = isRoot;
|
|
this.children = new List<MatchingControlPath>();
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
public static List<TreeViewItemData<MatchingControlPath>> BuildMatchingControlPathsTreeData(List<MatchingControlPath> matchingControlPaths)
|
|
{
|
|
int id = 0;
|
|
return BuildMatchingControlPathsTreeDataRecursive(ref id, matchingControlPaths);
|
|
}
|
|
|
|
private static List<TreeViewItemData<MatchingControlPath>> BuildMatchingControlPathsTreeDataRecursive(ref int id, List<MatchingControlPath> matchingControlPaths)
|
|
{
|
|
var treeViewList = new List<TreeViewItemData<MatchingControlPath>>(matchingControlPaths.Count);
|
|
foreach (var matchingControlPath in matchingControlPaths)
|
|
{
|
|
var childTreeViewList = BuildMatchingControlPathsTreeDataRecursive(ref id, matchingControlPath.children);
|
|
|
|
var treeViewItem = new TreeViewItemData<MatchingControlPath>(id++, matchingControlPath, childTreeViewList);
|
|
treeViewList.Add(treeViewItem);
|
|
}
|
|
|
|
return treeViewList;
|
|
}
|
|
|
|
#endif
|
|
|
|
public static List<MatchingControlPath> CollectMatchingControlPaths(string path, bool showPaths, ref bool controlPathUsagePresent)
|
|
{
|
|
var matchingControlPaths = new List<MatchingControlPath>();
|
|
|
|
if (path == string.Empty)
|
|
return matchingControlPaths;
|
|
|
|
var deviceLayoutPath = InputControlPath.TryGetDeviceLayout(path);
|
|
var parsedPath = InputControlPath.Parse(path).ToArray();
|
|
|
|
// If the provided path is parseable into device and control components, draw UI which shows control layouts that match the path.
|
|
if (parsedPath.Length >= 2 && !string.IsNullOrEmpty(deviceLayoutPath))
|
|
{
|
|
bool matchExists = false;
|
|
|
|
var rootDeviceLayout = EditorInputControlLayoutCache.TryGetLayout(deviceLayoutPath);
|
|
bool isValidDeviceLayout = deviceLayoutPath == InputControlPath.Wildcard || (rootDeviceLayout != null && !rootDeviceLayout.isOverride && !rootDeviceLayout.hideInUI);
|
|
// Exit early if a malformed device layout was provided,
|
|
if (!isValidDeviceLayout)
|
|
return matchingControlPaths;
|
|
|
|
controlPathUsagePresent = parsedPath[1].usages.Count() > 0;
|
|
bool hasChildDeviceLayouts = deviceLayoutPath == InputControlPath.Wildcard || EditorInputControlLayoutCache.HasChildLayouts(rootDeviceLayout.name);
|
|
|
|
// If the path provided matches exactly one control path (i.e. has no ui-facing child device layouts or uses control usages), then exit early
|
|
if (!controlPathUsagePresent && !hasChildDeviceLayouts)
|
|
return matchingControlPaths;
|
|
|
|
// Otherwise, we will show either all controls that match the current binding (if control usages are used)
|
|
// or all controls in derived device layouts (if a no control usages are used).
|
|
|
|
// If our control path contains a usage, make sure we render the binding that belongs to the root device layout first
|
|
if (deviceLayoutPath != InputControlPath.Wildcard && controlPathUsagePresent)
|
|
{
|
|
matchExists |= CollectMatchingControlPathsForLayout(rootDeviceLayout, in parsedPath, true, matchingControlPaths);
|
|
}
|
|
// Otherwise, just render the bindings that belong to child device layouts. The binding that matches the root layout is
|
|
// already represented by the user generated control path itself.
|
|
else
|
|
{
|
|
IEnumerable<InputControlLayout> matchedChildLayouts = Enumerable.Empty<InputControlLayout>();
|
|
if (deviceLayoutPath == InputControlPath.Wildcard)
|
|
{
|
|
matchedChildLayouts = EditorInputControlLayoutCache.allLayouts
|
|
.Where(x => x.isDeviceLayout && !x.hideInUI && !x.isOverride && x.isGenericTypeOfDevice && x.baseLayouts.Count() == 0).OrderBy(x => x.displayName);
|
|
}
|
|
else
|
|
{
|
|
matchedChildLayouts = EditorInputControlLayoutCache.TryGetChildLayouts(rootDeviceLayout.name);
|
|
}
|
|
|
|
foreach (var childLayout in matchedChildLayouts)
|
|
{
|
|
matchExists |= CollectMatchingControlPathsForLayout(childLayout, in parsedPath, false, matchingControlPaths);
|
|
}
|
|
}
|
|
|
|
// Otherwise, indicate that no layouts match the current path.
|
|
if (!matchExists)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return matchingControlPaths;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the deviceLayout or any of its children has controls which match the provided parsed path. exist matching registered control paths.
|
|
/// </summary>
|
|
/// <param name="deviceLayout">The device layout to draw control paths for</param>
|
|
/// <param name="parsedPath">The parsed path containing details of the Input Controls that can be matched</param>
|
|
private static bool CollectMatchingControlPathsForLayout(InputControlLayout deviceLayout, in InputControlPath.ParsedPathComponent[] parsedPath, bool isRoot, List<MatchingControlPath> matchingControlPaths)
|
|
{
|
|
string deviceName = deviceLayout.displayName;
|
|
string controlName = string.Empty;
|
|
bool matchExists = false;
|
|
|
|
for (int i = 0; i < deviceLayout.m_Controls.Length; i++)
|
|
{
|
|
ref InputControlLayout.ControlItem controlItem = ref deviceLayout.m_Controls[i];
|
|
if (InputControlPath.MatchControlComponent(ref parsedPath[1], ref controlItem, true))
|
|
{
|
|
// If we've already located a match, append a ", " to the control name
|
|
// This is to accomodate cases where multiple control items match the same path within a single device layout
|
|
// Note, some controlItems have names but invalid displayNames (i.e. the Dualsense HID > leftTriggerButton)
|
|
// There are instance where there are 2 control items with the same name inside a layout definition, however they are not
|
|
// labeled significantly differently.
|
|
// The notable example is that the Android Xbox and Android Dualshock layouts have 2 d-pad definitions, one is a "button"
|
|
// while the other is an axis.
|
|
controlName += matchExists ? $", {controlItem.name}" : controlItem.name;
|
|
|
|
// if the parsePath has a 3rd component, try to match it with items in the controlItem's layout definition.
|
|
if (parsedPath.Length == 3)
|
|
{
|
|
var controlLayout = EditorInputControlLayoutCache.TryGetLayout(controlItem.layout);
|
|
if (controlLayout.isControlLayout && !controlLayout.hideInUI)
|
|
{
|
|
for (int j = 0; j < controlLayout.m_Controls.Count(); j++)
|
|
{
|
|
ref InputControlLayout.ControlItem controlLayoutItem = ref controlLayout.m_Controls[j];
|
|
if (InputControlPath.MatchControlComponent(ref parsedPath[2], ref controlLayoutItem))
|
|
{
|
|
controlName += $"/{controlLayoutItem.name}";
|
|
matchExists = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
matchExists = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
IEnumerable<InputControlLayout> matchedChildLayouts = EditorInputControlLayoutCache.TryGetChildLayouts(deviceLayout.name);
|
|
|
|
// If this layout does not have a match, or is the top level root layout,
|
|
// skip over trying to draw any items for it, and immediately try processing the child layouts
|
|
if (!matchExists)
|
|
{
|
|
foreach (var childLayout in matchedChildLayouts)
|
|
{
|
|
matchExists |= CollectMatchingControlPathsForLayout(childLayout, in parsedPath, false, matchingControlPaths);
|
|
}
|
|
}
|
|
// Otherwise, draw the items for it, and then only process the child layouts if the foldout is expanded.
|
|
else
|
|
{
|
|
var newMatchingControlPath = new MatchingControlPath(deviceName, controlName, isRoot);
|
|
matchingControlPaths.Add(newMatchingControlPath);
|
|
|
|
foreach (var childLayout in matchedChildLayouts)
|
|
{
|
|
CollectMatchingControlPathsForLayout(childLayout, in parsedPath, false, newMatchingControlPath.children);
|
|
}
|
|
}
|
|
|
|
return matchExists;
|
|
}
|
|
}
|
|
}
|
|
#endif
|