123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- #if UNITY_EDITOR
- 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;
- }
- }
- }
- #endif // UNITY_EDITOR
|