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