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