using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.IMGUI.Controls; using UnityEngine; using VRC.SDK3.Avatars.Components; using Object = UnityEngine.Object; namespace nadena.dev.modular_avatar.core.editor { internal class ParameterGUI { private readonly SerializedProperty _property; private readonly Action _redraw; private Rect fieldRect; internal ParameterGUI(SerializedProperty property, Action redraw) { _property = property; _redraw = redraw; } public void DoGUI() { GUILayout.Space(-2); GUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(_property, new GUIContent("Parameter")); Rect propField = new Rect(); if (Event.current.type == EventType.Repaint) { propField = GUILayoutUtility.GetLastRect(); } if (_property.serializedObject.targetObjects.Length == 1) { var target = (_property.serializedObject.targetObject as Component)?.gameObject; if (target != null && EditorGUILayout.DropdownButton(new GUIContent(), FocusType.Keyboard, "IN DropDown")) { PopupWindow.Show(fieldRect, new ParameterWindow(target, _property, fieldRect.width, _redraw)); } } if (Event.current.type == EventType.Repaint) { var buttonRect = GUILayoutUtility.GetLastRect(); fieldRect = propField; fieldRect.x += EditorGUIUtility.labelWidth + 2; fieldRect.width = buttonRect.xMax - propField.x - EditorGUIUtility.labelWidth; } GUILayout.EndHorizontal(); GUILayout.Space(2); } private class ParameterWindow : PopupWindowContent { private readonly GameObject _target; private readonly SerializedProperty _prop; private readonly float _width; private readonly Action _redraw; private SearchField _searchField; private ParameterTree _tree; private string _searchString; public ParameterWindow(GameObject target, SerializedProperty prop, float width, Action redraw) { _target = target; _prop = prop; _width = width; _redraw = redraw; } public override void OnGUI(Rect rect) { var sfRect = rect; sfRect.height = EditorGUIUtility.singleLineHeight; rect.y += EditorGUIUtility.singleLineHeight; rect.height -= EditorGUIUtility.singleLineHeight; if (_searchField == null) { _searchField = new SearchField(); } _searchString = _searchField.OnGUI(sfRect, _searchString); if (_tree == null) { _tree = new ParameterTree(new TreeViewState(), _target); _tree.OnSelect = (s) => { _prop.stringValue = s; _prop.serializedObject.ApplyModifiedProperties(); _redraw(); }; _tree.OnCommit = (s) => { _prop.stringValue = s; _prop.serializedObject.ApplyModifiedProperties(); editorWindow.Close(); _redraw(); }; _tree.Reload(); } _tree.searchString = _searchString; _tree.OnGUI(rect); } public override Vector2 GetWindowSize() { return new Vector2(_width, 150); } public void SetContext(Object serializedObjectTargetObject) { //throw new NotImplementedException(); } } private class ParameterTree : TreeView { private List _items; public Action OnSelect, OnCommit; private GameObject _obj; private class SourceItem : TreeViewItem { public GameObject source; } private class ParamItem : TreeViewItem { public GameObject source; } public ParameterTree(TreeViewState state, GameObject obj) : base(state) { _obj = obj; } protected override void SelectionChanged(IList selectedIds) { var item = _items[selectedIds[0]]; if (item != null) OnSelect(item); } protected override void DoubleClickedItem(int id) { var item = _items[id]; if (item != null) OnCommit(item); } protected override void RowGUI(RowGUIArgs args) { if (!string.IsNullOrEmpty(searchString) && args.item is ParamItem offer) { var rect = args.rowRect; var objName = offer.source.name + " / "; var content = new GUIContent(objName); var width = EditorStyles.label.CalcSize(content).x; var color = GUI.color; var grey = color; grey.a *= 0.7f; GUI.color = grey; EditorGUI.LabelField(rect, content); GUI.color = color; rect.x += width; rect.width -= width; if (rect.width >= 0) { EditorGUI.LabelField(rect, offer.displayName); } } else if (args.item is SourceItem source) { var rect = args.rowRect; rect.xMin += this.GetContentIndent(args.item) + this.extraSpaceBeforeIconAndLabel; EditorGUI.LabelField(rect, source.source.name); } else { base.RowGUI(args); } } protected override TreeViewItem BuildRoot() { List treeItems = new List(); _items = new List(); _items.Add(""); var root = new TreeViewItem {id = 0, depth = -1, displayName = "Root"}; GameObject priorNode = null; foreach ((GameObject node, string param) in FindParameters()) { if (node != priorNode) { _items.Add(null); treeItems.Add(new SourceItem() {id = _items.Count - 1, depth = 0, displayName = "", source = node}); priorNode = node; } _items.Add(param); treeItems.Add(new ParamItem {id = _items.Count - 1, depth = 1, displayName = param, source = node}); } SetupParentsAndChildrenFromDepths(root, treeItems); return root; } private IEnumerable<(GameObject, string)> FindParameters() { HashSet emitted = new HashSet(); GameObject node = _obj; while (node != null && node.GetComponent() == null) { var paramComp = node.GetComponent(); if (paramComp != null) { foreach (var param in paramComp.parameters) { if (!param.isPrefix) { if (emitted.Add(param.nameOrPrefix)) yield return (node, param.nameOrPrefix); } } } node = node.transform.parent?.gameObject; } var desc = node?.GetComponent(); if (desc != null) { foreach (var param in desc.expressionParameters.parameters) { if (emitted.Add(param.name)) yield return (node, param.name); } } } } } }