diff options
Diffstat (limited to 'VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor')
4 files changed, 625 insertions, 0 deletions
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs new file mode 100644 index 00000000..66c73166 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs @@ -0,0 +1,268 @@ +/* Copyright (c) 2020-2022 Lyuma <xn.lyuma@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEngine.Animations; +using UnityEditor.Animations; +using UnityEditor.Compilation; +using UnityEditor.Playables; +using UnityEngine.Playables; +using VRC.SDK3.Avatars.Components; + +[InitializeOnLoadAttribute] +public static class LyumaAv3EditorSupport +{ + static Dictionary<VRCAvatarDescriptor.AnimLayerType, string> animLayerToDefaultFile = new Dictionary<VRCAvatarDescriptor.AnimLayerType, string> { + {VRCAvatarDescriptor.AnimLayerType.TPose, "vrc_AvatarV3UtilityTPose"}, + {VRCAvatarDescriptor.AnimLayerType.IKPose, "vrc_AvatarV3UtilityIKPose"}, + {VRCAvatarDescriptor.AnimLayerType.Base, "vrc_AvatarV3LocomotionLayer"}, + {VRCAvatarDescriptor.AnimLayerType.Sitting, "vrc_AvatarV3SittingLayer"}, + {VRCAvatarDescriptor.AnimLayerType.Additive, "vrc_AvatarV3IdleLayer"}, + {VRCAvatarDescriptor.AnimLayerType.FX, "vrc_AvatarV3FaceLayer"}, + {VRCAvatarDescriptor.AnimLayerType.Action, "vrc_AvatarV3ActionLayer"}, + {VRCAvatarDescriptor.AnimLayerType.Gesture, "vrc_AvatarV3HandsLayer"}, + }; + static Dictionary<VRCAvatarDescriptor.AnimLayerType, string> animLayerToDefaultAvaMaskFile = new Dictionary<VRCAvatarDescriptor.AnimLayerType, string> + { + {VRCAvatarDescriptor.AnimLayerType.TPose, "vrc_MusclesOnly"}, + {VRCAvatarDescriptor.AnimLayerType.IKPose, "vrc_MusclesOnly"}, + {VRCAvatarDescriptor.AnimLayerType.Base, null},//"LyumaFullMask"}, + {VRCAvatarDescriptor.AnimLayerType.Sitting, null},//"LyumaFullMask"}, + {VRCAvatarDescriptor.AnimLayerType.Additive, null},//"LyumaFullMask"}, + {VRCAvatarDescriptor.AnimLayerType.FX, "LyumaEmptyMask"}, // TODO + {VRCAvatarDescriptor.AnimLayerType.Action, null},//"vrc_MusclesOnly"}, + {VRCAvatarDescriptor.AnimLayerType.Gesture, "vrc_HandsOnly"}, + }; + + static void InitDefaults() { + foreach (var kv in animLayerToDefaultFile) { + if (kv.Value == null) { + LyumaAv3Runtime.animLayerToDefaultController[kv.Key] = null; + } else + { + AnimatorController ac = AssetDatabase.LoadAssetAtPath<AnimatorController>("Assets/VRCSDK/Examples3/Animation/Controllers/" + kv.Value + ".controller"); + if (ac == null) + { + Debug.LogWarning("Failed to resolve animator controller " + kv.Value + " for " + kv.Key); + foreach (var guid in AssetDatabase.FindAssets(kv.Value)) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + if (path.EndsWith("/" + kv.Value + ".controller")) { + ac = AssetDatabase.LoadAssetAtPath<AnimatorController>(path); + break; + } + } + } + LyumaAv3Runtime.animLayerToDefaultController[kv.Key] = ac; + } + } + foreach (var kv in animLayerToDefaultAvaMaskFile) { + if (kv.Value == null) { + LyumaAv3Runtime.animLayerToDefaultAvaMask[kv.Key] = null; + } else + { + AvatarMask mask = null; + foreach (var guid in AssetDatabase.FindAssets(kv.Value)) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + mask = AssetDatabase.LoadAssetAtPath<AvatarMask>(path); + } + if (mask == null) + { + Debug.LogWarning("Failed to resolve avatar mask " + kv.Value + " for " + kv.Key); + mask = new AvatarMask(); + } + LyumaAv3Runtime.animLayerToDefaultAvaMask[kv.Key] = mask; + } + } + foreach (string guid in AssetDatabase.FindAssets("EmptyController")) { + LyumaAv3Emulator.EmptyController = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(AssetDatabase.GUIDToAssetPath(guid)); + } + + LyumaAv3Runtime.updateSelectionDelegate = (obj) => { + if (obj == null && LyumaAv3Emulator.emulatorInstance != null) { + // Debug.Log("Resetting selected object: " + LyumaAv3Emulator.emulatorInstance); + obj = LyumaAv3Emulator.emulatorInstance.gameObject; + } + // Debug.Log("Setting selected object: " + go); + Selection.SetActiveObjectWithContext(obj, obj); + // Highlighter.Highlight("Inspector", "Animator To Debug"); + }; + + LyumaAv3Runtime.updateSceneLayersDelegate = (layers) => { + if (Tools.visibleLayers == layers) { + return; + } + // Debug.Log("Setting selected layers: " + layers); + Tools.visibleLayers = layers; + Camera[] cameras = new Camera[255]; + Camera.GetAllCameras(cameras); + foreach (Camera c in cameras) { + if (c != null && c.targetTexture == null && c.GetComponentInParent<LyumaAv3Runtime>() == null && c.gameObject.activeInHierarchy && c.isActiveAndEnabled) { + c.cullingMask = layers; + } + } + // Highlighter.Highlight("Inspector", "Animator To Debug"); + }; + + LyumaAv3Runtime.addRuntimeDelegate = (runtime) => { + MoveComponentToTop(runtime); + }; + + // Currently PhysBone and ContactManager cause exceptions if scripts reload during Play mode. + // This applies a workaround: disable the objects before compile; call RuntimeInit to recreate them after. + LyumaAv3Runtime.ApplyOnEnableWorkaroundDelegate = () => { + CompilationPipeline.assemblyCompilationStarted -= WorkaroundDestroyManagersBeforeCompile; + CompilationPipeline.assemblyCompilationStarted += WorkaroundDestroyManagersBeforeCompile; + GameObject gotmp = GameObject.Find("/TempReloadDontDestroy"); + if (gotmp != null) { + GameObject.DestroyImmediate(gotmp); + var avatarDynamicsSetup = typeof(VRCExpressionsMenuEditor).Assembly.GetType("VRC.SDK3.Avatars.AvatarDynamicsSetup"); + if (avatarDynamicsSetup != null) { + var RuntimeInit = avatarDynamicsSetup.GetMethod("RuntimeInit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + if (RuntimeInit != null) { + Debug.Log("Caling avatarDynamicsSetup.RuntimeInit(): " + RuntimeInit); + RuntimeInit.Invoke(null, new object[0]); + } + } + Debug.Log("DONE workaround"); + } + }; + + LyumaAv3Osc.GetEditorViewportDelegate = () => { + try { + Rect ret = UnityEditor.SceneView.currentDrawingSceneView.position; + // Gizmos are relative to the active window in terms of x and y. + ret.x = 1.0f; + ret.y = 1.0f; + ret.height -= 7.0f; + return ret; + } catch { + Vector2 gvsize = Handles.GetMainGameViewSize(); + return new Rect(0, -18, gvsize.x, gvsize.y); + } + }; + LyumaAv3Osc.DrawDebugRectDelegate = (Rect pos, Color col, Color outlineCol) => { + // Debug.Log("Debug raw rect " + pos); + Color origColor = GUI.color; + GUI.color = col; + UnityEditor.Handles.BeginGUI(); + UnityEditor.Handles.DrawSolidRectangleWithOutline(pos, col, outlineCol); + UnityEditor.Handles.EndGUI(); + GUI.color = origColor; + }; + LyumaAv3Osc.DrawDebugTextDelegate = (Rect pos, Color backgroundCol, Color outlineCol, Color textCol, string str, TextAnchor alignment) => { + // Debug.Log("Debug raw text " + str + " at " + pos); + Color origColor = GUI.color; + GUI.color = backgroundCol; + var view = UnityEditor.SceneView.currentDrawingSceneView; + // Vector2 size = GUI.skin.label.CalcSize(new GUIContent(str)); + // Rect pos = new Rect(location.x, location.y, size.x, size.y); + UnityEditor.Handles.BeginGUI(); + UnityEditor.Handles.DrawSolidRectangleWithOutline(pos, backgroundCol, outlineCol); + GUI.color = textCol.r + textCol.b + textCol.g > 0.5f ? new Color(0,0,0,textCol.a * 0.5f) : new Color(1,1,1,textCol.a * 0.5f);//new Color(1.0f, 1.0f, 1.0f, textCol.a * 0.25f); + var style = new GUIStyle(); + style.fontStyle = FontStyle.Bold; + style.alignment = alignment; + style.normal.textColor = GUI.color; + pos.y += 1; + GUI.Label(pos, str, style); + pos.x += 1; + GUI.Label(pos, str, style); + pos.y -= 1; + GUI.Label(pos, str, style); + pos.x -= 1; + GUI.Label(pos, str, style); + pos.x += 0.5f; + pos.y += 0.5f; + GUI.color = textCol; + style.normal.textColor = GUI.color; + GUI.Label(pos, str, style); + UnityEditor.Handles.EndGUI(); + GUI.color = origColor; + }; + } + + public static void OnPlayModeStateChange(UnityEditor.PlayModeStateChange pmsc) { + // We don't want any of our callbacks causing trouble outside of play mode. + if (pmsc != UnityEditor.PlayModeStateChange.EnteredPlayMode) { + CompilationPipeline.assemblyCompilationStarted -= WorkaroundDestroyManagersBeforeCompile; + } + } + + private static void WorkaroundDestroyManagersBeforeCompile(string obj) { + Debug.Log("Compile Started"); + GameObject gotmp = new GameObject("TempReloadDontDestroy"); + Object.DontDestroyOnLoad(gotmp); + GameObject go; + go = GameObject.Find("/TriggerManager"); + if (go != null) { + Object.DestroyImmediate(go); + } + go = GameObject.Find("/PhysBoneManager"); + if (go != null) { + Object.DestroyImmediate(go); + } + } + + static void MoveComponentToTop(Component c) { + GameObject go = c.gameObject; + Component[] components = go.GetComponents<Component>(); + for (int i = 0; i < components.Length; i++) { + if (components[i].GetType().Name.Contains("PipelineSaver")) { + return; + } + } + try { + if (PrefabUtility.IsPartOfAnyPrefab(go)) { + PrefabUtility.UnpackPrefabInstance(go, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); + } + } catch (System.Exception) {} + int moveUpCalls = components.Length - 2; + if (!PrefabUtility.IsPartOfAnyPrefab(go.GetComponents<Component>()[1])) { + for (int i = 0; i < moveUpCalls; i++) { + UnityEditorInternal.ComponentUtility.MoveComponentUp(c); + } + } + } + + // register an event handler when the class is initialized + static LyumaAv3EditorSupport() + { + InitDefaults(); + EditorApplication.playModeStateChanged += OnPlayModeStateChange; + } + + [MenuItem("Tools/Enable Avatars 3.0 Emulator")] + public static void EnableAv3Testing() { + GameObject go = GameObject.Find("/Avatars 3.0 Emulator Control"); + if (go != null) { + go.SetActive(true); + } else { + go = new GameObject("Avatars 3.0 Emulator Control"); + } + Selection.SetActiveObjectWithContext(go, go); + go.GetOrAddComponent<LyumaAv3Emulator>(); + go.GetOrAddComponent<LyumaAv3Osc>(); + EditorGUIUtility.PingObject(go); + } +} diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs.meta new file mode 100644 index 00000000..79bb5d5f --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3EditorSupport.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77ddade7c6475a242a8e34b2b7554adc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs new file mode 100644 index 00000000..4da25e95 --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs @@ -0,0 +1,343 @@ +/* Copyright (c) 2020-2022 Lyuma <xn.lyuma@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using VRC.SDK3.Avatars.ScriptableObjects; + +[CustomEditor(typeof(LyumaAv3Menu), true)] +public class LyumaAv3MenuEditor : Editor +{ + private VRCExpressionsMenu _currentMenu; + + public override void OnInspectorGUI() + { + var menu = (LyumaAv3Menu)target; + GUILayout.BeginHorizontal(); + if (GUILayout.Button(menu.IsMenuOpen ? "Close menu" : "Open menu")) + { + menu.ToggleMenu(); + } + + if (menu.gameObject.GetComponents<LyumaAv3Menu>().Length == 1) + { + if (GUILayout.Button("+", GUILayout.Width(20))) + { + OpenMenuForTwoHandedSupport(menu); + } + } + + GUILayout.EndHorizontal(); + + RenderButtonMenu(); + } + + protected void RenderButtonMenu() { + + var menu = (LyumaAv3Menu)target; + if (menu.Runtime == null) return; + if (menu.RootMenu == null) + { + menu.RootMenu = (VRCExpressionsMenu)EditorGUILayout.ObjectField(new GUIContent("Expressions Menu"), null, typeof(VRCExpressionsMenu), false); + return; + } + + var isInRootMenu = menu.MenuStack.Count == 0; + + GUILayout.Label( + (isInRootMenu ? "Expressions" : LabelizeMenu()) + + (menu.IsMenuOpen ? "" : " [Menu is closed]"), + EditorStyles.boldLabel); + + if (!menu.IsMenuOpen) { + return; + } + + _currentMenu = menu.MenuStack.Count == 0 ? menu.RootMenu : menu.MenuStack.Last().ExpressionsMenu; + + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.ObjectField(_currentMenu, typeof(VRCExpressionsMenu), false); + EditorGUI.EndDisabledGroup(); + + EditorGUI.BeginDisabledGroup(isInRootMenu || menu.HasActiveControl()); + if (GUILayout.Button("Back")) + { + menu.UserBack(); + } + EditorGUI.EndDisabledGroup(); + if (_currentMenu == null) { + EditorGUILayout.LabelField("(This submenu is null)"); + return; + } + for (var controlIndex = 0; controlIndex < _currentMenu.controls.Count; controlIndex++) + { + var control = _currentMenu.controls[controlIndex]; + switch (control.type) + { + case VRCExpressionsMenu.Control.ControlType.Button: + FromToggle(control, "Button"); + break; + case VRCExpressionsMenu.Control.ControlType.Toggle: + FromToggle(control, "Toggle"); + break; + case VRCExpressionsMenu.Control.ControlType.SubMenu: + FromSubMenu(control); + break; + case VRCExpressionsMenu.Control.ControlType.TwoAxisPuppet: + FromTwoAxis(control, controlIndex); + break; + case VRCExpressionsMenu.Control.ControlType.FourAxisPuppet: + FromFourAxis(control, controlIndex); + break; + case VRCExpressionsMenu.Control.ControlType.RadialPuppet: + FromRadial(control, controlIndex); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + if (_currentMenu.controls.Count == 0) + { + EditorGUILayout.LabelField("(This menu has no controls)"); + } + } + + private static void OpenMenuForTwoHandedSupport(LyumaAv3Menu menu) + { + var mainMenu = menu.Runtime.gameObject.AddComponent<LyumaAv3Menu>(); + mainMenu.useLegacyMenu = menu.useLegacyMenu; + mainMenu.Runtime = menu.Runtime; + mainMenu.RootMenu = menu.RootMenu; + } + + private string LabelizeMenu() + { + var menu = (LyumaAv3Menu)target; + + var lastMenu = menu.MenuStack.Last(); + if (lastMenu.MandatedParam == null) + { + if (lastMenu.ExpressionsMenu == null) { + return "SubMenu linked to null menu!"; + } + return lastMenu.ExpressionsMenu.name; + } + + return lastMenu.ExpressionsMenu.name + " (" + lastMenu.MandatedParam.name + " = " + lastMenu.MandatedParam.value + ")"; + } + + private void FromToggle(VRCExpressionsMenu.Control control, string labelType) + { + var menu = (LyumaAv3Menu)target; + + var parameterName = control.parameter.name; + var controlValue = control.value; + + var isActive = menu.IsVisualActive(parameterName, controlValue); + + EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginDisabledGroup(menu.HasActiveControl()); + if (GreenBackground(isActive, () => ParameterizedButton(control, parameterName, controlValue))) + { + menu.UserToggle(parameterName, controlValue); + } + EditorGUI.EndDisabledGroup(); + LabelType(labelType); + EditorGUILayout.EndHorizontal(); + } + + private void FromSubMenu(VRCExpressionsMenu.Control control) + { + var menu = (LyumaAv3Menu)target; + + var parameterName = control.parameter.name; + var wantedValue = control.value; + + EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginDisabledGroup(menu.HasActiveControl()); + if (ParameterizedButton(control, parameterName, wantedValue)) + { + if (IsValidParameterName(parameterName)) + { + menu.UserSubMenu(control.subMenu, parameterName, wantedValue); + } + else + { + menu.UserSubMenu(control.subMenu); + } + } + EditorGUI.EndDisabledGroup(); + LabelType("SubMenu"); + EditorGUILayout.EndHorizontal(); + } + + private void FromRadial(VRCExpressionsMenu.Control control, int controlIndex) + { + var menu = (LyumaAv3Menu)target; + + SubControl(control, controlIndex, menu, "Radial"); + + if (menu.IsActiveControl(controlIndex)) + { + if (control.subParameters.Length > 0) + { + SliderFloat(menu, control.subParameters[0], "Rotation", 0f, 1f); + } + } + } + + private void FromTwoAxis(VRCExpressionsMenu.Control control, int controlIndex) + { + var menu = (LyumaAv3Menu)target; + + SubControl(control, controlIndex, menu, "TwoAxis"); + + if (menu.IsActiveControl(controlIndex)) + { + var sanitySubParamLength = control.subParameters.Length; + if (sanitySubParamLength > 0) SliderFloat(menu, control.subParameters[0], "Horizontal", -1f, 1f); + if (sanitySubParamLength > 1) SliderFloat(menu, control.subParameters[1], "Vertical", -1f, 1f); + + var oldColor = Color.HSVToRGB( + 0, + sanitySubParamLength > 0 ? menu.FindFloat(control.subParameters[0].name) * 0.5f + 0.5f : 0, + sanitySubParamLength > 1 ? menu.FindFloat(control.subParameters[1].name) * 0.5f + 0.5f : 0); + var newColor = EditorGUILayout.ColorField(oldColor); + if (oldColor.r != newColor.r || oldColor.g != newColor.g || oldColor.b != newColor.b) + { + Color.RGBToHSV(newColor, out _, out var s, out var v); + if (sanitySubParamLength > 0) menu.UserFloat(control.subParameters[0].name, s * 2 - 1); + if (sanitySubParamLength > 1) menu.UserFloat(control.subParameters[1].name, v * 2 - 1); + } + } + } + + private void FromFourAxis(VRCExpressionsMenu.Control control, int controlIndex) + { + var menu = (LyumaAv3Menu)target; + + SubControl(control, controlIndex, menu, "FourAxis"); + + if (menu.IsActiveControl(controlIndex)) + { + var sanitySubParamLength = control.subParameters.Length; + if (sanitySubParamLength > 0) SliderFloat(menu, control.subParameters[0], "Up", 0f, 1f); + if (sanitySubParamLength > 1) SliderFloat(menu, control.subParameters[1], "Right", 0f, 1f); + if (sanitySubParamLength > 2) SliderFloat(menu, control.subParameters[2], "Down", 0f, 1f); + if (sanitySubParamLength > 3) SliderFloat(menu, control.subParameters[3], "Left", 0f, 1f); + + var oldColor = Color.HSVToRGB( + 0, + (sanitySubParamLength > 0 ? menu.FindFloat(control.subParameters[0].name) : 0) * 0.5f + 0.5f + -(sanitySubParamLength > 2 ? menu.FindFloat(control.subParameters[2].name) : 0) * 0.5f + 0.5f, + (sanitySubParamLength > 1 ? menu.FindFloat(control.subParameters[1].name) : 0) * 0.5f + 0.5f + -(sanitySubParamLength > 3 ? menu.FindFloat(control.subParameters[3].name) : 0) * 0.5f + 0.5f); + var newColor = EditorGUILayout.ColorField(oldColor); + if (oldColor.r != newColor.r || oldColor.g != newColor.g || oldColor.b != newColor.b) + { + Color.RGBToHSV(newColor, out _, out var s, out var v); + if (sanitySubParamLength > 0) menu.UserFloat(control.subParameters[0].name, Mathf.Clamp(v * 2 - 1, 0f, 1f)); + if (sanitySubParamLength > 1) menu.UserFloat(control.subParameters[1].name, Mathf.Clamp(s * 2 - 1, 0f, 1f)); + if (sanitySubParamLength > 2) menu.UserFloat(control.subParameters[2].name, -Mathf.Clamp(v * 2 - 1, -1f, 0f)); + if (sanitySubParamLength > 3) menu.UserFloat(control.subParameters[3].name, -Mathf.Clamp(s * 2 - 1, -1f, 0f)); + } + } + } + + private void SubControl(VRCExpressionsMenu.Control control, int controlIndex, LyumaAv3Menu menu, string labelType) + { + var parameterName = control.parameter.name; + var intValue = (int) control.value; + + var isActive = menu.IsVisualActive(parameterName, intValue); + + EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginDisabledGroup(menu.HasActiveControl() && !menu.IsActiveControl(controlIndex)); + if (GreenBackground(isActive || menu.IsActiveControl(controlIndex), () => ParameterizedButton(control, parameterName, intValue))) + { + if (!menu.IsActiveControl(controlIndex)) + { + if (IsValidParameterName(parameterName)) + { + menu.UserControlEnter(controlIndex, parameterName, intValue); + } + else + { + menu.UserControlEnter(controlIndex); + } + } + else + { + menu.UserControlExit(); + } + } + + EditorGUI.EndDisabledGroup(); + LabelType(labelType); + EditorGUILayout.EndHorizontal(); + } + + private static void SliderFloat(LyumaAv3Menu menu, VRCExpressionsMenu.Control.Parameter subParam, string intent, float left, float right) + { + if (subParam == null || subParam.name == "") + { + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.Slider(intent, 0, left, right); + EditorGUI.EndDisabledGroup(); + return; + } + + menu.UserFloat(subParam.name, EditorGUILayout.Slider(intent + " (" + subParam.name + ")", menu.FindFloat(subParam.name), left, right)); + } + + private bool ParameterizedButton(VRCExpressionsMenu.Control control, string parameterName, float wantedValue) + { + var hasParameter = IsValidParameterName(parameterName); + return GUILayout.Button(new GUIContent(control.name + (hasParameter ? " (" + parameterName + " = " + wantedValue + ")" : ""), control.icon), GUILayout.Height(36),GUILayout.MinWidth(40)); + } + + private static T GreenBackground<T>(bool isActive, Func<T> inside) + { + var col = GUI.color; + try + { + if (isActive) GUI.color = Color.green; + return inside(); + } + finally + { + GUI.color = col; + } + } + + private static void LabelType(string toggle) + { + EditorGUILayout.LabelField(toggle, GUILayout.Width(70), GUILayout.ExpandHeight(true)); + } + + private static bool IsValidParameterName(string parameterName) + { + return !string.IsNullOrEmpty(parameterName); + } +} diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs.meta new file mode 100644 index 00000000..f613af7d --- /dev/null +++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Editor/LyumaAv3MenuEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 187ea10b8ccd4441a6399698c23122e3 +timeCreated: 1604152513
\ No newline at end of file |