Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Editor /Internal /InputControlTreeView.cs
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using UnityEditor.IMGUI.Controls; | |
| using UnityEngine.InputSystem.LowLevel; | |
| using UnityEngine.Profiling; | |
| ////TODO: make control values editable (create state events from UI and pump them into the system) | |
| ////TODO: show processors attached to controls | |
| ////TODO: make controls that have different `value` and `previous` in bold | |
| namespace UnityEngine.InputSystem.Editor | |
| { | |
| // Multi-column TreeView that shows control tree of device. | |
| internal class InputControlTreeView : TreeView | |
| { | |
| // If this is set, the controls won't display their current value but we'll | |
| // show their state data from this buffer instead. | |
| public byte[] stateBuffer; | |
| public byte[][] multipleStateBuffers; | |
| public bool showDifferentOnly; | |
| public static InputControlTreeView Create(InputControl rootControl, int numValueColumns, ref TreeViewState treeState, ref MultiColumnHeaderState headerState) | |
| { | |
| if (treeState == null) | |
| treeState = new TreeViewState(); | |
| var newHeaderState = CreateHeaderState(numValueColumns); | |
| if (headerState != null) | |
| MultiColumnHeaderState.OverwriteSerializedFields(headerState, newHeaderState); | |
| headerState = newHeaderState; | |
| var header = new MultiColumnHeader(headerState); | |
| return new InputControlTreeView(rootControl, treeState, header); | |
| } | |
| public void RefreshControlValues() | |
| { | |
| foreach (var item in GetRows()) | |
| if (item is ControlItem controlItem) | |
| ReadState(controlItem.control, ref controlItem); | |
| } | |
| private const float kRowHeight = 20f; | |
| private enum ColumnId | |
| { | |
| Name, | |
| DisplayName, | |
| Layout, | |
| Type, | |
| Format, | |
| Offset, | |
| Bit, | |
| Size, | |
| Optimized, | |
| Value, | |
| COUNT | |
| } | |
| private InputControl m_RootControl; | |
| private static MultiColumnHeaderState CreateHeaderState(int numValueColumns) | |
| { | |
| var columns = new MultiColumnHeaderState.Column[(int)ColumnId.COUNT + numValueColumns - 1]; | |
| columns[(int)ColumnId.Name] = | |
| new MultiColumnHeaderState.Column | |
| { | |
| width = 180, | |
| minWidth = 60, | |
| headerContent = new GUIContent("Name") | |
| }; | |
| columns[(int)ColumnId.DisplayName] = | |
| new MultiColumnHeaderState.Column | |
| { | |
| width = 160, | |
| minWidth = 60, | |
| headerContent = new GUIContent("Display Name") | |
| }; | |
| columns[(int)ColumnId.Layout] = | |
| new MultiColumnHeaderState.Column | |
| { | |
| width = 100, | |
| minWidth = 60, | |
| headerContent = new GUIContent("Layout") | |
| }; | |
| columns[(int)ColumnId.Type] = | |
| new MultiColumnHeaderState.Column | |
| { | |
| width = 100, | |
| minWidth = 60, | |
| headerContent = new GUIContent("Type") | |
| }; | |
| columns[(int)ColumnId.Format] = | |
| new MultiColumnHeaderState.Column {headerContent = new GUIContent("Format")}; | |
| columns[(int)ColumnId.Offset] = | |
| new MultiColumnHeaderState.Column {headerContent = new GUIContent("Offset")}; | |
| columns[(int)ColumnId.Bit] = | |
| new MultiColumnHeaderState.Column {width = 40, headerContent = new GUIContent("Bit")}; | |
| columns[(int)ColumnId.Size] = | |
| new MultiColumnHeaderState.Column {headerContent = new GUIContent("Size (Bits)")}; | |
| columns[(int)ColumnId.Optimized] = | |
| new MultiColumnHeaderState.Column {headerContent = new GUIContent("Optimized")}; | |
| if (numValueColumns == 1) | |
| { | |
| columns[(int)ColumnId.Value] = | |
| new MultiColumnHeaderState.Column {width = 120, headerContent = new GUIContent("Value")}; | |
| } | |
| else | |
| { | |
| for (var i = 0; i < numValueColumns; ++i) | |
| columns[(int)ColumnId.Value + i] = | |
| new MultiColumnHeaderState.Column | |
| { | |
| width = 100, | |
| headerContent = new GUIContent("Value " + (char)('A' + i)) | |
| }; | |
| } | |
| return new MultiColumnHeaderState(columns); | |
| } | |
| private InputControlTreeView(InputControl root, TreeViewState state, MultiColumnHeader header) | |
| : base(state, header) | |
| { | |
| m_RootControl = root; | |
| showBorder = false; | |
| rowHeight = kRowHeight; | |
| } | |
| protected override TreeViewItem BuildRoot() | |
| { | |
| Profiler.BeginSample("BuildControlTree"); | |
| var id = 1; | |
| // Build tree from control down the control hierarchy. | |
| var rootItem = BuildControlTreeRecursive(m_RootControl, 0, ref id); | |
| Profiler.EndSample(); | |
| // Wrap root control in invisible item required by TreeView. | |
| return new TreeViewItem | |
| { | |
| id = 0, | |
| children = new List<TreeViewItem> {rootItem}, | |
| depth = -1 | |
| }; | |
| } | |
| private ControlItem BuildControlTreeRecursive(InputControl control, int depth, ref int id) | |
| { | |
| // Build children. | |
| List<TreeViewItem> children = null; | |
| var isLeaf = control.children.Count == 0; | |
| if (!isLeaf) | |
| { | |
| children = new List<TreeViewItem>(); | |
| foreach (var child in control.children) | |
| { | |
| var childItem = BuildControlTreeRecursive(child, depth + 1, ref id); | |
| if (childItem != null) | |
| children.Add(childItem); | |
| } | |
| // If none of our children returned an item, none of their data is different, | |
| // so if we are supposed to show only controls that differ in value, we're sitting | |
| // on a branch that has no changes. Cull the branch except if we're all the way | |
| // at the root (we want to have at least one item). | |
| if (children.Count == 0 && showDifferentOnly && depth != 0) | |
| return null; | |
| // Sort children by name. | |
| children.Sort((a, b) => string.Compare(a.displayName, b.displayName)); | |
| } | |
| // Compute offset. Offsets on the controls are absolute. Make them relative to the | |
| // root control. | |
| var controlOffset = control.stateBlock.byteOffset; | |
| var rootOffset = m_RootControl.stateBlock.byteOffset; | |
| var offset = controlOffset - rootOffset; | |
| // Read state. | |
| var item = new ControlItem | |
| { | |
| id = id++, | |
| control = control, | |
| depth = depth, | |
| children = children | |
| }; | |
| ////TODO: come up with nice icons depicting different control types | |
| if (!ReadState(control, ref item)) | |
| return null; | |
| if (children != null) | |
| { | |
| foreach (var child in children) | |
| child.parent = item; | |
| } | |
| return item; | |
| } | |
| private bool ReadState(InputControl control, ref ControlItem item) | |
| { | |
| // Compute offset. Offsets on the controls are absolute. Make them relative to the | |
| // root control. | |
| var controlOffset = control.stateBlock.byteOffset; | |
| var rootOffset = m_RootControl.stateBlock.byteOffset; | |
| var offset = controlOffset - rootOffset; | |
| item.displayName = control.name; | |
| item.layout = new GUIContent(control.layout); | |
| item.format = new GUIContent(control.stateBlock.format.ToString()); | |
| item.offset = new GUIContent(offset.ToString()); | |
| item.bit = new GUIContent(control.stateBlock.bitOffset.ToString()); | |
| item.sizeInBits = new GUIContent(control.stateBlock.sizeInBits.ToString()); | |
| item.type = new GUIContent(control.GetType().Name); | |
| item.optimized = new GUIContent(control.optimizedControlDataType != InputStateBlock.kFormatInvalid ? "+" : "-"); | |
| try | |
| { | |
| if (stateBuffer != null) | |
| { | |
| var text = ReadRawValueAsString(control, stateBuffer); | |
| if (text != null) | |
| item.value = new GUIContent(text); | |
| } | |
| else if (multipleStateBuffers != null) | |
| { | |
| var valueStrings = multipleStateBuffers.Select(x => ReadRawValueAsString(control, x)); | |
| if (showDifferentOnly && control.children.Count == 0 && valueStrings.Distinct().Count() == 1) | |
| return false; | |
| item.values = valueStrings.Select(x => x != null ? new GUIContent(x) : null).ToArray(); | |
| } | |
| else | |
| { | |
| var valueObject = control.ReadValueAsObject(); | |
| if (valueObject != null) | |
| item.value = new GUIContent(valueObject.ToString()); | |
| } | |
| } | |
| catch (Exception exception) | |
| { | |
| // If we fail to read a value, swallow it so we don't fail completely | |
| // showing anything from the device. | |
| item.value = new GUIContent(exception.ToString()); | |
| } | |
| return true; | |
| } | |
| protected override void RowGUI(RowGUIArgs args) | |
| { | |
| var item = (ControlItem)args.item; | |
| var columnCount = args.GetNumVisibleColumns(); | |
| for (var i = 0; i < columnCount; ++i) | |
| { | |
| ColumnGUI(args.GetCellRect(i), item, args.GetColumn(i), ref args); | |
| } | |
| } | |
| private void ColumnGUI(Rect cellRect, ControlItem item, int column, ref RowGUIArgs args) | |
| { | |
| CenterRectUsingSingleLineHeight(ref cellRect); | |
| switch (column) | |
| { | |
| case (int)ColumnId.Name: | |
| args.rowRect = cellRect; | |
| base.RowGUI(args); | |
| break; | |
| case (int)ColumnId.DisplayName: | |
| GUI.Label(cellRect, item.control.displayName); | |
| break; | |
| case (int)ColumnId.Layout: | |
| GUI.Label(cellRect, item.layout); | |
| break; | |
| case (int)ColumnId.Format: | |
| GUI.Label(cellRect, item.format); | |
| break; | |
| case (int)ColumnId.Offset: | |
| GUI.Label(cellRect, item.offset); | |
| break; | |
| case (int)ColumnId.Bit: | |
| GUI.Label(cellRect, item.bit); | |
| break; | |
| case (int)ColumnId.Size: | |
| GUI.Label(cellRect, item.sizeInBits); | |
| break; | |
| case (int)ColumnId.Type: | |
| GUI.Label(cellRect, item.type); | |
| break; | |
| case (int)ColumnId.Optimized: | |
| GUI.Label(cellRect, item.optimized); | |
| break; | |
| case (int)ColumnId.Value: | |
| if (item.value != null) | |
| GUI.Label(cellRect, item.value); | |
| else if (item.values != null && item.values[0] != null) | |
| GUI.Label(cellRect, item.values[0]); | |
| break; | |
| default: | |
| var valueIndex = column - (int)ColumnId.Value; | |
| if (item.values != null && item.values[valueIndex] != null) | |
| GUI.Label(cellRect, item.values[valueIndex]); | |
| break; | |
| } | |
| } | |
| private unsafe string ReadRawValueAsString(InputControl control, byte[] state) | |
| { | |
| fixed(byte* statePtr = state) | |
| { | |
| var ptr = statePtr - m_RootControl.m_StateBlock.byteOffset; | |
| return control.ReadValueFromStateAsObject(ptr).ToString(); | |
| } | |
| } | |
| private class ControlItem : TreeViewItem | |
| { | |
| public InputControl control; | |
| public GUIContent layout; | |
| public GUIContent format; | |
| public GUIContent offset; | |
| public GUIContent bit; | |
| public GUIContent sizeInBits; | |
| public GUIContent type; | |
| public GUIContent optimized; | |
| public GUIContent value; | |
| public GUIContent[] values; | |
| } | |
| } | |
| } | |