Files
MMORPG/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/MatchingControlPaths.cs
cooney ce83f21c93 UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
2026-01-25 01:31:34 +09:00

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