using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using ExpressionsMenu = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu; using ExpressionControl = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control; using ExpressionParameters = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters; using VRC.SDK3.Avatars.ScriptableObjects; using System.Reflection.Emit; [CustomEditor(typeof(VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu))] public class VRCExpressionsMenuEditor : Editor { static string[] ToggleStyles = { "Pip-Slot", "Animation" }; List foldoutList = new List(); public void Start() { } public void OnDisable() { SelectAvatarDescriptor(null); } public override void OnInspectorGUI() { serializedObject.Update(); SelectAvatarDescriptor(); if(activeDescriptor == null) { EditorGUILayout.HelpBox("No active avatar descriptor found in scene.", MessageType.Error); } EditorGUILayout.Space(); //Controls EditorGUI.BeginDisabledGroup(activeDescriptor == null); EditorGUILayout.LabelField("Controls"); EditorGUI.indentLevel += 1; { var controls = serializedObject.FindProperty("controls"); for (int i = 0; i < controls.arraySize; i++) { var control = controls.GetArrayElementAtIndex(i); DrawControl(controls, control as SerializedProperty, i); } //Add EditorGUI.BeginDisabledGroup(controls.arraySize >= ExpressionsMenu.MAX_CONTROLS); if (GUILayout.Button("Add Control")) { var menu = serializedObject.targetObject as ExpressionsMenu; var control = new ExpressionControl(); control.name = "New Control"; menu.controls.Add(control); } EditorGUI.EndDisabledGroup(); } EditorGUI.indentLevel -= 1; EditorGUI.EndDisabledGroup(); serializedObject.ApplyModifiedProperties(); } void DrawControl(SerializedProperty controls, SerializedProperty control, int index) { var name = control.FindPropertyRelative("name"); var icon = control.FindPropertyRelative("icon"); var type = control.FindPropertyRelative("type"); var parameter = control.FindPropertyRelative("parameter"); var value = control.FindPropertyRelative("value"); var subMenu = control.FindPropertyRelative("subMenu"); var subParameters = control.FindPropertyRelative("subParameters"); var labels = control.FindPropertyRelative("labels"); //Foldout EditorGUI.BeginChangeCheck(); control.isExpanded = EditorGUILayout.Foldout(control.isExpanded, name.stringValue); if (!control.isExpanded) return; //Box GUILayout.BeginVertical(GUI.skin.box); { //Up, Down, Delete GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (GUILayout.Button("Up", GUILayout.Width(64))) { if (index > 0) controls.MoveArrayElement(index, index - 1); } if (GUILayout.Button("Down", GUILayout.Width(64))) { if (index < controls.arraySize - 1) controls.MoveArrayElement(index, index + 1); } if (GUILayout.Button("Delete", GUILayout.Width(64))) { controls.DeleteArrayElementAtIndex(index); return; } GUILayout.EndHorizontal(); //Generic params EditorGUI.indentLevel += 1; { EditorGUILayout.PropertyField(name); EditorGUILayout.PropertyField(icon); EditorGUILayout.PropertyField(type); //Type Info var controlType = (ExpressionControl.ControlType)type.intValue; switch (controlType) { case ExpressionControl.ControlType.Button: EditorGUILayout.HelpBox("Click or hold to activate. The button remains active for a minimum 0.2s.\nWhile active the (Parameter) is set to (Value).\nWhen inactive the (Parameter) is reset to zero.", MessageType.Info); break; case ExpressionControl.ControlType.Toggle: EditorGUILayout.HelpBox("Click to toggle on or off.\nWhen turned on the (Parameter) is set to (Value).\nWhen turned off the (Parameter) is reset to zero.", MessageType.Info); break; case ExpressionControl.ControlType.SubMenu: EditorGUILayout.HelpBox("Opens another expression menu.\nWhen opened the (Parameter) is set to (Value).\nWhen closed (Parameter) is reset to zero.", MessageType.Info); break; case ExpressionControl.ControlType.TwoAxisPuppet: EditorGUILayout.HelpBox("Puppet menu that maps the joystick to two parameters (-1 to +1).\nWhen opened the (Parameter) is set to (Value).\nWhen closed (Parameter) is reset to zero.", MessageType.Info); break; case ExpressionControl.ControlType.FourAxisPuppet: EditorGUILayout.HelpBox("Puppet menu that maps the joystick to four parameters (0 to 1).\nWhen opened the (Parameter) is set to (Value).\nWhen closed (Parameter) is reset to zero.", MessageType.Info); break; case ExpressionControl.ControlType.RadialPuppet: EditorGUILayout.HelpBox("Puppet menu that sets a value based on joystick rotation. (0 to 1)\nWhen opened the (Parameter) is set to (Value).\nWhen closed (Parameter) is reset to zero.", MessageType.Info); break; } //Param switch (controlType) { case ExpressionControl.ControlType.Button: case ExpressionControl.ControlType.Toggle: case ExpressionControl.ControlType.SubMenu: case ExpressionControl.ControlType.TwoAxisPuppet: case ExpressionControl.ControlType.FourAxisPuppet: case ExpressionControl.ControlType.RadialPuppet: DrawParameterDropDown(parameter, "Parameter"); DrawParameterValue(parameter, value); break; } EditorGUILayout.LabelField("", GUI.skin.horizontalSlider); //Style /*if (controlType == ExpressionsControl.ControlType.Toggle) { style.intValue = EditorGUILayout.Popup("Visual Style", style.intValue, ToggleStyles); }*/ //Sub menu if (controlType == ExpressionControl.ControlType.SubMenu) { EditorGUILayout.PropertyField(subMenu); } //Puppet Parameter Set switch (controlType) { case ExpressionControl.ControlType.TwoAxisPuppet: subParameters.arraySize = 2; labels.arraySize = 4; DrawParameterDropDown(subParameters.GetArrayElementAtIndex(0), "Parameter Horizontal", false); DrawParameterDropDown(subParameters.GetArrayElementAtIndex(1), "Parameter Vertical", false); DrawLabel(labels.GetArrayElementAtIndex(0), "Label Up"); DrawLabel(labels.GetArrayElementAtIndex(1), "Label Right"); DrawLabel(labels.GetArrayElementAtIndex(2), "Label Down"); DrawLabel(labels.GetArrayElementAtIndex(3), "Label Left"); break; case ExpressionControl.ControlType.FourAxisPuppet: subParameters.arraySize = 4; labels.arraySize = 4; DrawParameterDropDown(subParameters.GetArrayElementAtIndex(0), "Parameter Up", false); DrawParameterDropDown(subParameters.GetArrayElementAtIndex(1), "Parameter Right", false); DrawParameterDropDown(subParameters.GetArrayElementAtIndex(2), "Parameter Down", false); DrawParameterDropDown(subParameters.GetArrayElementAtIndex(3), "Parameter Left", false); DrawLabel(labels.GetArrayElementAtIndex(0), "Label Up"); DrawLabel(labels.GetArrayElementAtIndex(1), "Label Right"); DrawLabel(labels.GetArrayElementAtIndex(2), "Label Down"); DrawLabel(labels.GetArrayElementAtIndex(3), "Label Left"); break; case ExpressionControl.ControlType.RadialPuppet: subParameters.arraySize = 1; labels.arraySize = 0; DrawParameterDropDown(subParameters.GetArrayElementAtIndex(0), "Paramater Rotation", false); break; default: subParameters.arraySize = 0; labels.arraySize = 0; break; } } EditorGUI.indentLevel -= 1; } GUILayout.EndVertical(); } void DrawLabel(SerializedProperty subControl, string name) { var nameProp = subControl.FindPropertyRelative("name"); var icon = subControl.FindPropertyRelative("icon"); EditorGUILayout.LabelField(name); EditorGUI.indentLevel += 2; EditorGUILayout.PropertyField(nameProp); EditorGUILayout.PropertyField(icon); EditorGUI.indentLevel -= 2; } void DrawInfoHover(string text) { GUILayout.Button(new GUIContent("?", text), GUILayout.MaxWidth(32)); } void DrawInfo(string text) { GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUILayout.Label(text, GUI.skin.textArea, GUILayout.MaxWidth(400)); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } VRC.SDK3.Avatars.Components.VRCAvatarDescriptor activeDescriptor = null; string[] parameterNames; void SelectAvatarDescriptor() { var descriptors = GameObject.FindObjectsOfType(); if (descriptors.Length > 0) { //Compile list of names string[] names = new string[descriptors.Length]; for(int i=0; i