暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InputEventTreeView.cs 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. #if UNITY_EDITOR
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEditor.IMGUI.Controls;
  5. using UnityEngine.InputSystem.LowLevel;
  6. using UnityEditor;
  7. using UnityEngine.Profiling;
  8. ////FIXME: this performs horribly; the constant rebuilding on every single event makes the debug view super slow when device is noisy
  9. ////TODO: add information about which update type + update count an event came through in
  10. ////TODO: add more information for each event (ideally, dump deltas that highlight control values that have changed)
  11. ////TODO: add diagnostics to immediately highlight problems with events (e.g. events getting ignored because of incorrect type codes)
  12. ////TODO: implement support for sorting data by different property columns (we currently always sort events by ID)
  13. namespace UnityEngine.InputSystem.Editor
  14. {
  15. // Multi-column TreeView that shows the events in a trace.
  16. internal class InputEventTreeView : TreeView
  17. {
  18. private readonly InputEventTrace m_EventTrace;
  19. private readonly InputControl m_RootControl;
  20. private enum ColumnId
  21. {
  22. Id,
  23. Type,
  24. Device,
  25. Size,
  26. Time,
  27. Details,
  28. COUNT
  29. }
  30. public static InputEventTreeView Create(InputDevice device, InputEventTrace eventTrace, ref TreeViewState treeState, ref MultiColumnHeaderState headerState)
  31. {
  32. if (treeState == null)
  33. treeState = new TreeViewState();
  34. var newHeaderState = CreateHeaderState();
  35. if (headerState != null)
  36. MultiColumnHeaderState.OverwriteSerializedFields(headerState, newHeaderState);
  37. headerState = newHeaderState;
  38. var header = new MultiColumnHeader(headerState);
  39. return new InputEventTreeView(treeState, header, eventTrace, device);
  40. }
  41. private static MultiColumnHeaderState CreateHeaderState()
  42. {
  43. var columns = new MultiColumnHeaderState.Column[(int)ColumnId.COUNT];
  44. columns[(int)ColumnId.Id] =
  45. new MultiColumnHeaderState.Column
  46. {
  47. width = 80,
  48. minWidth = 60,
  49. headerContent = new GUIContent("Id"),
  50. canSort = false
  51. };
  52. columns[(int)ColumnId.Type] =
  53. new MultiColumnHeaderState.Column
  54. {
  55. width = 60,
  56. minWidth = 60,
  57. headerContent = new GUIContent("Type"),
  58. canSort = false
  59. };
  60. columns[(int)ColumnId.Device] =
  61. new MultiColumnHeaderState.Column
  62. {
  63. width = 80,
  64. minWidth = 60,
  65. headerContent = new GUIContent("Device"),
  66. canSort = false
  67. };
  68. columns[(int)ColumnId.Size] =
  69. new MultiColumnHeaderState.Column
  70. {
  71. width = 50,
  72. minWidth = 50,
  73. headerContent = new GUIContent("Size"),
  74. canSort = false
  75. };
  76. columns[(int)ColumnId.Time] =
  77. new MultiColumnHeaderState.Column
  78. {
  79. width = 100,
  80. minWidth = 80,
  81. headerContent = new GUIContent("Time"),
  82. canSort = false
  83. };
  84. columns[(int)ColumnId.Details] =
  85. new MultiColumnHeaderState.Column
  86. {
  87. width = 250,
  88. minWidth = 100,
  89. headerContent = new GUIContent("Details"),
  90. canSort = false
  91. };
  92. return new MultiColumnHeaderState(columns);
  93. }
  94. private InputEventTreeView(TreeViewState state, MultiColumnHeader multiColumnHeader, InputEventTrace eventTrace, InputControl rootControl)
  95. : base(state, multiColumnHeader)
  96. {
  97. m_EventTrace = eventTrace;
  98. m_RootControl = rootControl;
  99. Reload();
  100. }
  101. protected override void DoubleClickedItem(int id)
  102. {
  103. var item = FindItem(id, rootItem) as EventItem;
  104. if (item == null)
  105. return;
  106. // We can only inspect state events so ignore double-clicks on other
  107. // types of events.
  108. var eventPtr = item.eventPtr;
  109. if (!eventPtr.IsA<StateEvent>() && !eventPtr.IsA<DeltaStateEvent>())
  110. return;
  111. PopUpStateWindow(eventPtr);
  112. }
  113. ////TODO: move inspect and compare from a context menu to the toolbar of the event view
  114. protected override void ContextClickedItem(int id)
  115. {
  116. var item = FindItem(id, rootItem) as EventItem;
  117. if (item == null)
  118. return;
  119. var menu = new GenericMenu();
  120. var selection = GetSelection();
  121. if (selection.Count == 1)
  122. {
  123. menu.AddItem(new GUIContent("Inspect"), false, OnInspectMenuItem, id);
  124. }
  125. else if (selection.Count > 1)
  126. {
  127. menu.AddItem(new GUIContent("Compare"), false, OnCompareMenuItem, selection);
  128. }
  129. menu.ShowAsContext();
  130. }
  131. private void OnCompareMenuItem(object userData)
  132. {
  133. var selection = (IList<int>)userData;
  134. var window = ScriptableObject.CreateInstance<InputStateWindow>();
  135. window.InitializeWithEvents(selection.Select(id => ((EventItem)FindItem(id, rootItem)).eventPtr).ToArray(), m_RootControl);
  136. window.Show();
  137. }
  138. private void OnInspectMenuItem(object userData)
  139. {
  140. var itemId = (int)userData;
  141. var item = FindItem(itemId, rootItem) as EventItem;
  142. if (item == null)
  143. return;
  144. PopUpStateWindow(item.eventPtr);
  145. }
  146. private void PopUpStateWindow(InputEventPtr eventPtr)
  147. {
  148. var window = ScriptableObject.CreateInstance<InputStateWindow>();
  149. window.InitializeWithEvent(eventPtr, m_RootControl);
  150. window.Show();
  151. }
  152. protected override TreeViewItem BuildRoot()
  153. {
  154. Profiler.BeginSample("InputEventTreeView.BuildRoot");
  155. var root = new TreeViewItem
  156. {
  157. id = 0,
  158. depth = -1,
  159. displayName = "Root"
  160. };
  161. var eventCount = m_EventTrace.eventCount;
  162. if (eventCount == 0)
  163. {
  164. // TreeView doesn't allow having empty trees. Put a dummy item in here that we
  165. // render without contents.
  166. root.AddChild(new TreeViewItem(1));
  167. }
  168. else
  169. {
  170. var current = new InputEventPtr();
  171. // Can't set List to a fixed size and then fill it from the back. So we do it
  172. // the worse way... fill it in inverse order first, then reverse it :(
  173. root.children = new List<TreeViewItem>((int)eventCount);
  174. for (var i = 0; i < eventCount; ++i)
  175. {
  176. if (!m_EventTrace.GetNextEvent(ref current))
  177. break;
  178. var item = new EventItem
  179. {
  180. id = i + 1,
  181. depth = 1,
  182. displayName = current.id.ToString(),
  183. eventPtr = current
  184. };
  185. root.AddChild(item);
  186. }
  187. root.children.Reverse();
  188. }
  189. Profiler.EndSample();
  190. return root;
  191. }
  192. protected override void RowGUI(RowGUIArgs args)
  193. {
  194. // Render nothing if event list is empty.
  195. if (m_EventTrace.eventCount == 0)
  196. return;
  197. var columnCount = args.GetNumVisibleColumns();
  198. for (var i = 0; i < columnCount; ++i)
  199. {
  200. var item = (EventItem)args.item;
  201. ColumnGUI(args.GetCellRect(i), item.eventPtr, args.GetColumn(i));
  202. }
  203. }
  204. private unsafe void ColumnGUI(Rect cellRect, InputEventPtr eventPtr, int column)
  205. {
  206. CenterRectUsingSingleLineHeight(ref cellRect);
  207. switch (column)
  208. {
  209. case (int)ColumnId.Id:
  210. GUI.Label(cellRect, eventPtr.id.ToString());
  211. break;
  212. case (int)ColumnId.Type:
  213. GUI.Label(cellRect, eventPtr.type.ToString());
  214. break;
  215. case (int)ColumnId.Device:
  216. GUI.Label(cellRect, eventPtr.deviceId.ToString());
  217. break;
  218. case (int)ColumnId.Size:
  219. GUI.Label(cellRect, eventPtr.sizeInBytes.ToString());
  220. break;
  221. case (int)ColumnId.Time:
  222. GUI.Label(cellRect, eventPtr.time.ToString("0.0000s"));
  223. break;
  224. case (int)ColumnId.Details:
  225. if (eventPtr.IsA<DeltaStateEvent>())
  226. {
  227. var deltaEventPtr = DeltaStateEvent.From(eventPtr);
  228. GUI.Label(cellRect, $"Format={deltaEventPtr->stateFormat}, Offset={deltaEventPtr->stateOffset}");
  229. }
  230. else if (eventPtr.IsA<StateEvent>())
  231. {
  232. var stateEventPtr = StateEvent.From(eventPtr);
  233. GUI.Label(cellRect, $"Format={stateEventPtr->stateFormat}");
  234. }
  235. else if (eventPtr.IsA<TextEvent>())
  236. {
  237. var textEventPtr = TextEvent.From(eventPtr);
  238. GUI.Label(cellRect, $"Character='{(char) textEventPtr->character}'");
  239. }
  240. break;
  241. }
  242. }
  243. private class EventItem : TreeViewItem
  244. {
  245. public InputEventPtr eventPtr;
  246. }
  247. }
  248. }
  249. #endif // UNITY_EDITOR