暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

InputControlTreeView.cs 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. #if UNITY_EDITOR
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEditor.IMGUI.Controls;
  6. using UnityEngine.InputSystem.LowLevel;
  7. using UnityEngine.Profiling;
  8. ////TODO: make control values editable (create state events from UI and pump them into the system)
  9. ////TODO: show processors attached to controls
  10. ////TODO: make controls that have different `value` and `previous` in bold
  11. namespace UnityEngine.InputSystem.Editor
  12. {
  13. // Multi-column TreeView that shows control tree of device.
  14. internal class InputControlTreeView : TreeView
  15. {
  16. // If this is set, the controls won't display their current value but we'll
  17. // show their state data from this buffer instead.
  18. public byte[] stateBuffer;
  19. public byte[][] multipleStateBuffers;
  20. public bool showDifferentOnly;
  21. public static InputControlTreeView Create(InputControl rootControl, int numValueColumns, ref TreeViewState treeState, ref MultiColumnHeaderState headerState)
  22. {
  23. if (treeState == null)
  24. treeState = new TreeViewState();
  25. var newHeaderState = CreateHeaderState(numValueColumns);
  26. if (headerState != null)
  27. MultiColumnHeaderState.OverwriteSerializedFields(headerState, newHeaderState);
  28. headerState = newHeaderState;
  29. var header = new MultiColumnHeader(headerState);
  30. return new InputControlTreeView(rootControl, treeState, header);
  31. }
  32. public void RefreshControlValues()
  33. {
  34. foreach (var item in GetRows())
  35. if (item is ControlItem controlItem)
  36. ReadState(controlItem.control, ref controlItem);
  37. }
  38. private const float kRowHeight = 20f;
  39. private enum ColumnId
  40. {
  41. Name,
  42. DisplayName,
  43. Layout,
  44. Type,
  45. Format,
  46. Offset,
  47. Bit,
  48. Size,
  49. Optimized,
  50. Value,
  51. COUNT
  52. }
  53. private InputControl m_RootControl;
  54. private static MultiColumnHeaderState CreateHeaderState(int numValueColumns)
  55. {
  56. var columns = new MultiColumnHeaderState.Column[(int)ColumnId.COUNT + numValueColumns - 1];
  57. columns[(int)ColumnId.Name] =
  58. new MultiColumnHeaderState.Column
  59. {
  60. width = 180,
  61. minWidth = 60,
  62. headerContent = new GUIContent("Name")
  63. };
  64. columns[(int)ColumnId.DisplayName] =
  65. new MultiColumnHeaderState.Column
  66. {
  67. width = 160,
  68. minWidth = 60,
  69. headerContent = new GUIContent("Display Name")
  70. };
  71. columns[(int)ColumnId.Layout] =
  72. new MultiColumnHeaderState.Column
  73. {
  74. width = 100,
  75. minWidth = 60,
  76. headerContent = new GUIContent("Layout")
  77. };
  78. columns[(int)ColumnId.Type] =
  79. new MultiColumnHeaderState.Column
  80. {
  81. width = 100,
  82. minWidth = 60,
  83. headerContent = new GUIContent("Type")
  84. };
  85. columns[(int)ColumnId.Format] =
  86. new MultiColumnHeaderState.Column {headerContent = new GUIContent("Format")};
  87. columns[(int)ColumnId.Offset] =
  88. new MultiColumnHeaderState.Column {headerContent = new GUIContent("Offset")};
  89. columns[(int)ColumnId.Bit] =
  90. new MultiColumnHeaderState.Column {width = 40, headerContent = new GUIContent("Bit")};
  91. columns[(int)ColumnId.Size] =
  92. new MultiColumnHeaderState.Column {headerContent = new GUIContent("Size (Bits)")};
  93. columns[(int)ColumnId.Optimized] =
  94. new MultiColumnHeaderState.Column {headerContent = new GUIContent("Optimized")};
  95. if (numValueColumns == 1)
  96. {
  97. columns[(int)ColumnId.Value] =
  98. new MultiColumnHeaderState.Column {width = 120, headerContent = new GUIContent("Value")};
  99. }
  100. else
  101. {
  102. for (var i = 0; i < numValueColumns; ++i)
  103. columns[(int)ColumnId.Value + i] =
  104. new MultiColumnHeaderState.Column
  105. {
  106. width = 100,
  107. headerContent = new GUIContent("Value " + (char)('A' + i))
  108. };
  109. }
  110. return new MultiColumnHeaderState(columns);
  111. }
  112. private InputControlTreeView(InputControl root, TreeViewState state, MultiColumnHeader header)
  113. : base(state, header)
  114. {
  115. m_RootControl = root;
  116. showBorder = false;
  117. rowHeight = kRowHeight;
  118. }
  119. protected override TreeViewItem BuildRoot()
  120. {
  121. Profiler.BeginSample("BuildControlTree");
  122. var id = 1;
  123. // Build tree from control down the control hierarchy.
  124. var rootItem = BuildControlTreeRecursive(m_RootControl, 0, ref id);
  125. Profiler.EndSample();
  126. // Wrap root control in invisible item required by TreeView.
  127. return new TreeViewItem
  128. {
  129. id = 0,
  130. children = new List<TreeViewItem> {rootItem},
  131. depth = -1
  132. };
  133. }
  134. private ControlItem BuildControlTreeRecursive(InputControl control, int depth, ref int id)
  135. {
  136. // Build children.
  137. List<TreeViewItem> children = null;
  138. var isLeaf = control.children.Count == 0;
  139. if (!isLeaf)
  140. {
  141. children = new List<TreeViewItem>();
  142. foreach (var child in control.children)
  143. {
  144. var childItem = BuildControlTreeRecursive(child, depth + 1, ref id);
  145. if (childItem != null)
  146. children.Add(childItem);
  147. }
  148. // If none of our children returned an item, none of their data is different,
  149. // so if we are supposed to show only controls that differ in value, we're sitting
  150. // on a branch that has no changes. Cull the branch except if we're all the way
  151. // at the root (we want to have at least one item).
  152. if (children.Count == 0 && showDifferentOnly && depth != 0)
  153. return null;
  154. // Sort children by name.
  155. children.Sort((a, b) => string.Compare(a.displayName, b.displayName));
  156. }
  157. // Compute offset. Offsets on the controls are absolute. Make them relative to the
  158. // root control.
  159. var controlOffset = control.stateBlock.byteOffset;
  160. var rootOffset = m_RootControl.stateBlock.byteOffset;
  161. var offset = controlOffset - rootOffset;
  162. // Read state.
  163. var item = new ControlItem
  164. {
  165. id = id++,
  166. control = control,
  167. depth = depth,
  168. children = children
  169. };
  170. ////TODO: come up with nice icons depicting different control types
  171. if (!ReadState(control, ref item))
  172. return null;
  173. if (children != null)
  174. {
  175. foreach (var child in children)
  176. child.parent = item;
  177. }
  178. return item;
  179. }
  180. private bool ReadState(InputControl control, ref ControlItem item)
  181. {
  182. // Compute offset. Offsets on the controls are absolute. Make them relative to the
  183. // root control.
  184. var controlOffset = control.stateBlock.byteOffset;
  185. var rootOffset = m_RootControl.stateBlock.byteOffset;
  186. var offset = controlOffset - rootOffset;
  187. item.displayName = control.name;
  188. item.layout = new GUIContent(control.layout);
  189. item.format = new GUIContent(control.stateBlock.format.ToString());
  190. item.offset = new GUIContent(offset.ToString());
  191. item.bit = new GUIContent(control.stateBlock.bitOffset.ToString());
  192. item.sizeInBits = new GUIContent(control.stateBlock.sizeInBits.ToString());
  193. item.type = new GUIContent(control.GetType().Name);
  194. item.optimized = new GUIContent(control.optimizedControlDataType != InputStateBlock.kFormatInvalid ? "+" : "-");
  195. try
  196. {
  197. if (stateBuffer != null)
  198. {
  199. var text = ReadRawValueAsString(control, stateBuffer);
  200. if (text != null)
  201. item.value = new GUIContent(text);
  202. }
  203. else if (multipleStateBuffers != null)
  204. {
  205. var valueStrings = multipleStateBuffers.Select(x => ReadRawValueAsString(control, x));
  206. if (showDifferentOnly && control.children.Count == 0 && valueStrings.Distinct().Count() == 1)
  207. return false;
  208. item.values = valueStrings.Select(x => x != null ? new GUIContent(x) : null).ToArray();
  209. }
  210. else
  211. {
  212. var valueObject = control.ReadValueAsObject();
  213. if (valueObject != null)
  214. item.value = new GUIContent(valueObject.ToString());
  215. }
  216. }
  217. catch (Exception exception)
  218. {
  219. // If we fail to read a value, swallow it so we don't fail completely
  220. // showing anything from the device.
  221. item.value = new GUIContent(exception.ToString());
  222. }
  223. return true;
  224. }
  225. protected override void RowGUI(RowGUIArgs args)
  226. {
  227. var item = (ControlItem)args.item;
  228. var columnCount = args.GetNumVisibleColumns();
  229. for (var i = 0; i < columnCount; ++i)
  230. {
  231. ColumnGUI(args.GetCellRect(i), item, args.GetColumn(i), ref args);
  232. }
  233. }
  234. private void ColumnGUI(Rect cellRect, ControlItem item, int column, ref RowGUIArgs args)
  235. {
  236. CenterRectUsingSingleLineHeight(ref cellRect);
  237. switch (column)
  238. {
  239. case (int)ColumnId.Name:
  240. args.rowRect = cellRect;
  241. base.RowGUI(args);
  242. break;
  243. case (int)ColumnId.DisplayName:
  244. GUI.Label(cellRect, item.control.displayName);
  245. break;
  246. case (int)ColumnId.Layout:
  247. GUI.Label(cellRect, item.layout);
  248. break;
  249. case (int)ColumnId.Format:
  250. GUI.Label(cellRect, item.format);
  251. break;
  252. case (int)ColumnId.Offset:
  253. GUI.Label(cellRect, item.offset);
  254. break;
  255. case (int)ColumnId.Bit:
  256. GUI.Label(cellRect, item.bit);
  257. break;
  258. case (int)ColumnId.Size:
  259. GUI.Label(cellRect, item.sizeInBits);
  260. break;
  261. case (int)ColumnId.Type:
  262. GUI.Label(cellRect, item.type);
  263. break;
  264. case (int)ColumnId.Optimized:
  265. GUI.Label(cellRect, item.optimized);
  266. break;
  267. case (int)ColumnId.Value:
  268. if (item.value != null)
  269. GUI.Label(cellRect, item.value);
  270. else if (item.values != null && item.values[0] != null)
  271. GUI.Label(cellRect, item.values[0]);
  272. break;
  273. default:
  274. var valueIndex = column - (int)ColumnId.Value;
  275. if (item.values != null && item.values[valueIndex] != null)
  276. GUI.Label(cellRect, item.values[valueIndex]);
  277. break;
  278. }
  279. }
  280. private unsafe string ReadRawValueAsString(InputControl control, byte[] state)
  281. {
  282. fixed(byte* statePtr = state)
  283. {
  284. var ptr = statePtr - m_RootControl.m_StateBlock.byteOffset;
  285. return control.ReadValueFromStateAsObject(ptr).ToString();
  286. }
  287. }
  288. private class ControlItem : TreeViewItem
  289. {
  290. public InputControl control;
  291. public GUIContent layout;
  292. public GUIContent format;
  293. public GUIContent offset;
  294. public GUIContent bit;
  295. public GUIContent sizeInBits;
  296. public GUIContent type;
  297. public GUIContent optimized;
  298. public GUIContent value;
  299. public GUIContent[] values;
  300. }
  301. }
  302. }
  303. #endif // UNITY_EDITOR