コード例 #1
0
    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();
    }
コード例 #2
0
 internal void ExpressionParameterReset(VRCExpressionsMenu.Control control)
 {
     if (!string.IsNullOrEmpty(control.parameter.name))
     {
         SetExpressionParameter(control.parameter.name, 0);
     }
 }
コード例 #3
0
        public RadialButton CreateButton(VRCExpressionsMenu.Control control, float xpos, float ypos)
        {
            RadialButton newButton  = Instantiate(RadialMenuController.current.ButtonPrefab) as RadialButton;
            Button       selectable = newButton.GetComponent <Button>();

            if (null == control)
            {
                if (null == Parent)
                {
                    selectable.interactable = false;
                    control = new VRCExpressionsMenu.Control()
                    {
                        icon = RadialMenuController.current.HomeIcon,
                        name = "home"
                    };
                }
                else
                {
                    selectable.onClick.AddListener(delegate { RadialMenuController.current.SwapMenu(this, Parent); });
                    control = new VRCExpressionsMenu.Control()
                    {
                        icon = RadialMenuController.current.BackIcon,
                        name = "back"
                    };
                }
            }

            newButton.Initialize(this, control, xpos, ypos);
            newButton.gameObject.name = control.name;

            return(newButton);
        }
コード例 #4
0
        private static Emh.EmhType TypeOf(VRCExpressionsMenu.Control control)
        {
            switch (control.type)
            {
            case VRCExpressionsMenu.Control.ControlType.Button:
                return(Emh.EmhType.Button);

            case VRCExpressionsMenu.Control.ControlType.Toggle:
                return(Emh.EmhType.Toggle);

            case VRCExpressionsMenu.Control.ControlType.SubMenu:
                return(Emh.EmhType.SubMenu);

            case VRCExpressionsMenu.Control.ControlType.TwoAxisPuppet:
                return(Emh.EmhType.TwoAxisPuppet);

            case VRCExpressionsMenu.Control.ControlType.FourAxisPuppet:
                return(Emh.EmhType.FourAxisPuppet);

            case VRCExpressionsMenu.Control.ControlType.RadialPuppet:
                return(Emh.EmhType.RadialPuppet);

            default:
                throw new ArgumentOutOfRangeException();
            }
        }
コード例 #5
0
 public RadialMenuControl(RadialMenu menu, VRCExpressionsMenu.Control control) : base(control.name, control.icon, control.type, control.value)
 {
     _menu          = menu;
     _subMenu       = control.subMenu;
     _subLabels     = control.labels;
     _parameter     = menu.GetParam(control.parameter.name);
     _subParameters = control.subParameters.Select(parameter => menu.GetParam(parameter.name)).ToArray();
 }
コード例 #6
0
        public static VRCExpressionsMenu.Control AddVRCExpressionsMenuControl(VRCExpressionsMenu menu, ControlType controlType, string parameterName, List <Object> dirtyAssets)
        {
            var control = new VRCExpressionsMenu.Control
            {
                name          = ObjectNames.NicifyVariableName(parameterName),
                type          = controlType,
                subParameters = new Parameter[1],
                parameter     = new Parameter(),
            };

            switch (controlType)
            {
            case ControlType.Button:
            case ControlType.Toggle:
                control.parameter = new Parameter {
                    name = parameterName
                };
                break;

            case ControlType.RadialPuppet:
                control.subParameters[0] = new Parameter {
                    name = parameterName
                };
                break;

            default:
                throw new ArgumentException(nameof(controlType), $"{controlType}", null);
            }

            void FixAv3Emulator()
            {
                try
                {
                    // Hacky hack to avoid a bug in Lyuma.Av3Emulator
                    control.icon = Texture2D.blackTexture;
                    var m = new SerializedObject(menu);
                    m.Update();
                    var c       = m.FindProperty("controls");
                    var element = c.GetArrayElementAtIndex(Mathf.Min(0, c.arraySize - 1));
                    var icon    = element.FindPropertyRelative("icon");
                    icon.objectReferenceInstanceIDValue = 0;
                    m.ApplyModifiedProperties();
                }
                catch (Exception)
                {
                    // ignored
                }
            }

            if (menu != null)
            {
                menu.controls.Add(control);
                dirtyAssets.Add(menu);
                FixAv3Emulator();
            }

            return(control);
        }
コード例 #7
0
    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));
                }
            }
        }
    }
コード例 #8
0
        public RadialMenu CreateMenu(RadialMenu parent, VRCExpressionsMenu.Control control)
        {
            RadialMenu rMenu = Instantiate(RadialMenuPrefab) as RadialMenu;

            rMenu.gameObject.name = control.subMenu.name;
            rMenu.Initialize(parent, control.subMenu, parameters);
            rMenu.transform.SetParent(transform, false);
            rMenu.gameObject.SetActive(false);

            subMenus.Add(rMenu.gameObject);
            return(rMenu);
        }
コード例 #9
0
    private void AddMarkerToMenu(ref VRCExpressionsMenu menu)
    {
        var control = new VRCExpressionsMenu.Control();

        control.type           = ControlType.Toggle;
        control.name           = "Toggle Marker";
        control.parameter      = new VRCExpressionsMenu.Control.Parameter();
        control.parameter.name = "ToggleMarker";
        control.value          = 1;
        // control.icon // TODO
        menu.controls.Add(control);
        //avatarDescriptor.expressionsMenu
        EditorUtility.SetDirty(avatarDescriptor.expressionsMenu);
    }
コード例 #10
0
    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);
            }
        }
    }
コード例 #11
0
        internal void ExpressionParameterSet(VRCExpressionsMenu.Control control, params float[] values)
        {
            //Debug.Log($"setting [{control.name}]: {control.type}, sub params {values.Length}");

            if (!string.IsNullOrEmpty(control.parameter.name))
            {
                SetExpressionParameter(control.parameter.name, control.value);
            }

            for (int i = 0; i < control.subParameters.Length; i++)
            {
                if (i < values.Length && !string.IsNullOrEmpty(control.subParameters[i].name))
                {
                    SetExpressionParameter(control.subParameters[i].name, values[i]);
                }
            }
        }
コード例 #12
0
 //Default Constructor
 public PageItem()
 {
     Type               = ItemType.Toggle;
     InitialState       = false;
     ObjectReference    = "";
     UseAnimations      = false;
     TransitionType     = true;
     TransitionDuration = 0f;
     TransitionOffset   = false;
     EnableClip         = null;
     DisableClip        = null;
     Sync               = SyncMode.Auto;
     Saved              = true;
     EnableGroup        = new GroupItem[0];
     DisableGroup       = new GroupItem[0];
     ButtonGroup        = new GroupItem[0];
     Control            = new VRCExpressionsMenu.Control();
 }
コード例 #13
0
        public MenuControlDefinition(IAnimationDefinition parent, VRCExpressionsMenu.Control control)
        {
            Name    = control.name;
            Parent  = parent;
            Control = control;
            Type    = control.type;

            if (Type == VRCExpressionsMenu.Control.ControlType.SubMenu)
            {
                AddMenu(control.subMenu);
                return;
            }

            MainParameter = AddParameter(control.parameter);
            foreach (var controlSubParameter in control.subParameters)
            {
                SubParameters.Add(AddParameter(controlSubParameter));
            }
        }
コード例 #14
0
    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();
    }
コード例 #15
0
    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);
                }
            }
        }
    }
コード例 #16
0
    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();
    }
コード例 #17
0
    private bool ParameterizedButton(VRCExpressionsMenu.Control control, string parameterName, float wantedValue)
    {
        var hasParameter = IsValidParameterName(parameterName);

        return(GUILayout.Button(new GUIContent(control.name + (hasParameter ? " (" + parameterName + " = " + wantedValue + ")" : ""), ResizedIcon(control.icon))));
    }
コード例 #18
0
        public void Initialize(RadialMenu parent, VRCExpressionsMenu.Control control, float xpos, float ypos)
        {
            this.RadialMenu = parent;
            this.Control    = control;
            this.xpos       = xpos;
            this.ypos       = ypos;
            Texture2D tex = Control.icon ?? null;

            this.Selectable = GetComponent <Button>();

            RadialMenuController.current.MenuActivationEvent += MenuActivationEvent;

            string error = "";

            if (VRCExpressionsMenu.Control.ControlType.SubMenu == Control.type)
            {
                if (null == tex)
                {
                    tex = RadialMenuController.current.SubMenuIcon;
                }

                if (null == Control.subMenu)
                {
                    Error = true;
                    error = $"Sub Menu [{control.name}] is not set";
                    TEA_Manager.SDKError(error);
                }
                else
                {
                    SubMenu = RadialMenuController.current.CreateMenu(parent, Control);
                }
            }
            if (VRCExpressionsMenu.Control.ControlType.RadialPuppet == Control.type)
            {
                if (null == tex)
                {
                    tex = RadialMenuController.current.RadialPuppetIcon;
                }

                if (null == Control.subParameters[0] || string.IsNullOrEmpty(Control.subParameters[0].name))
                {
                    error = $"Radial Puppet [{control.name}] does not have a Rotation Parameter";
                    TEA_Manager.SDKError(error);
                    Error = true;
                }
            }
            if (VRCExpressionsMenu.Control.ControlType.TwoAxisPuppet == Control.type)
            {
                if (null == tex)
                {
                    tex = RadialMenuController.current.TwoAxisIcon;
                }
                if ((null == Control.subParameters[0] || string.IsNullOrEmpty(Control.subParameters[0].name)) && (null == Control.subParameters[1] || string.IsNullOrEmpty(Control.subParameters[1].name)))
                {
                    Error = true;
                    error = $"Two Axis Radial Puppet [{control.name}] does not have a either Horizontal or Vertical Parameters";
                    TEA_Manager.SDKError(error);
                }
            }
            if (VRCExpressionsMenu.Control.ControlType.Toggle == Control.type)
            {
                if (null == tex)
                {
                    tex = RadialMenuController.current.ToggleIcon;
                }
            }
            if (VRCExpressionsMenu.Control.ControlType.FourAxisPuppet == Control.type)
            {
                if (null == tex)
                {
                    tex = RadialMenuController.current.FourAxisIcon;
                }
                if ((null == Control.subParameters[0] || string.IsNullOrEmpty(Control.subParameters[0].name)) &&
                    (null == Control.subParameters[1] || string.IsNullOrEmpty(Control.subParameters[1].name)) &&
                    (null == Control.subParameters[2] || string.IsNullOrEmpty(Control.subParameters[2].name)) &&
                    (null == Control.subParameters[3] || string.IsNullOrEmpty(Control.subParameters[3].name)))
                {
                    Error = true;
                    error = $"Four Axis Radial Puppet [{control.name}] is missing a parameter";
                    TEA_Manager.SDKError(error);
                }
            }
            if (VRCExpressionsMenu.Control.ControlType.Button == Control.type)
            {
                if (null == tex)
                {
                    tex = RadialMenuController.current.ButtonIcon;
                }
            }

            if (!Error)
            {
                transform.Find("Text").GetComponent <Text>().text = Control.name;
                if (null != tex)
                {
                    sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), new Vector2(0.5f, 0.5f));
                    SetImageSprite(image, sprite);
                }
            }
            else
            {
                sprite = RadialMenuController.current.ErrorSprite;
                SetImageSprite(image, sprite);
                transform.Find("Text").GetComponent <Text>().text = error;
            }

            transform.SetParent(RadialMenu.transform);
            SetScale();
            SetOnClick();
        }
コード例 #19
0
    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.MinHeight(36), GUILayout.MinWidth(40), GUILayout.ExpandHeight(true)));
    }
コード例 #20
0
        /// <summary>
        /// Creates a copy of the avatar descriptor's topmost menu asset or creates one if it doesn't exist, adds the provided menu as a submenu,
        /// assigns the new topmost menu asset, and stores it in the specified directory.
        /// </summary>
        /// <param name="descriptor">The avatar descriptor to add the submenu to.</param>
        /// <param name="menuToAdd">The menu to add, which will become a submenu of the topmost menu.</param>
        /// <param name="controlName">The name of the submenu control for the menu to add.</param>
        /// <param name="directory">The unique directory to store the new topmost menu asset, ex. "Assets/MyCoolScript/GeneratedAssets/725638/".</param>
        /// <param name="controlParameter">Optionally, the parameter to trigger when the submenu is opened.</param>
        /// <param name="icon"> Optionally, the icon to display on this submenu. </param>
        /// <param name="overwrite">Optionally, choose to not overwrite an asset of the same name in directory. See class for more info.</param>
        public static void AddSubMenu(VRCAvatarDescriptor descriptor, VRCExpressionsMenu menuToAdd, string controlName, string directory, VRCExpressionsMenu.Control.Parameter controlParameter = null, Texture2D icon = null, bool overwrite = true)
        {
            if (descriptor == null)
            {
                Debug.LogError("Couldn't add the menu, the avatar descriptor is null!");
                return;
            }
            else if ((menuToAdd == null) || (controlName == null) || (controlName == ""))
            {
                Debug.LogError("Couldn't add the menu, it or the name of its control is null!");
                return;
            }
            else if ((directory == null) || (directory == ""))
            {
                Debug.Log("Directory was not specified, storing new topmost menu in " + defaultDirectory);
                directory = defaultDirectory;
            }

            descriptor.customExpressions = true;
            VRCExpressionsMenu topMenu = ScriptableObject.CreateInstance <VRCExpressionsMenu>();

            if (descriptor.expressionsMenu == null)
            {
                AssetDatabase.CreateAsset(topMenu, directory + "Menu Topmost.asset");
            }
            else
            {
                if (descriptor.expressionsMenu.controls.Count == 8)
                {
                    Debug.LogWarning("Couldn't add menu. Please have an available slot in your avatar's topmost Expression Menu.");
                    return;
                }

                string path = (directory + descriptor.expressionsMenu.name + ".asset");
                path = (overwrite) ? path : AssetDatabase.GenerateUniqueAssetPath(path);
                if (AssetDatabase.GetAssetPath(descriptor.expressionsMenu) != path) // if we have not made a copy yet
                {                                                                   // CopyAsset with two identical strings yields exception
                    AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(descriptor.expressionsMenu), path);
                }
                topMenu = AssetDatabase.LoadAssetAtPath(path, typeof(VRCExpressionsMenu)) as VRCExpressionsMenu;
            }

            List <VRCExpressionsMenu.Control> controlList = topMenu.controls;

            for (int i = 0; i < controlList.Count; i++)
            {
                if (controlList[i].name.Equals(controlName) && controlList[i].type.Equals(VRCExpressionsMenu.Control.ControlType.SubMenu))
                { // if a control for a submenu exists with the same name, replace the submenu
                    controlList[i].subMenu   = menuToAdd;
                    controlList[i].parameter = controlParameter;
                    controlList[i].icon      = icon;
                    EditorUtility.SetDirty(topMenu);
                    AssetDatabase.SaveAssets();
                    AssetDatabase.Refresh();
                    descriptor.expressionsMenu = topMenu;
                    return;
                }
            }

            VRCExpressionsMenu.Control control = new VRCExpressionsMenu.Control
            {
                name = controlName, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = menuToAdd, parameter = controlParameter, icon = icon
            };
            controlList.Add(control);
            topMenu.controls = controlList;
            EditorUtility.SetDirty(topMenu);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            descriptor.expressionsMenu = topMenu;
        }
コード例 #21
0
 public RadialButton(RadialMenu radialMenu, VRCExpressionsMenu.Control control)
 {
     this.RadialMenu = radialMenu;
     this.Control    = control;
     this.Selectable = GetComponent <Button>();
 }
コード例 #22
0
 public MenuControlDefinition AddControl(VRCExpressionsMenu.Control control)
 {
     return(Children.AddChild(new MenuControlDefinition(this, control)));
 }
コード例 #23
0
        private bool VRC_Actions(string songLibrary, List <AudioClip> libAssets, Avatar8TrackEditor _8Track)
        {
            //--- --- VRC Expression Parameters --- ---
            if (null != expressionParameters)
            {
                VRCExpressionParameters.Parameter volumeParam = new VRCExpressionParameters.Parameter()
                {
                    name = _8Track.VolumeEPName, valueType = VRCExpressionParameters.ValueType.Float
                };
                VRCExpressionParameters.Parameter trackParam = new VRCExpressionParameters.Parameter()
                {
                    name = _8Track.TrackEPName, valueType = VRCExpressionParameters.ValueType.Int
                };

                VRCExpressionParameters.Parameter[] parameters = expressionParameters.parameters;
                bool hasTrack  = false;
                bool hasVolume = false;
                foreach (VRCExpressionParameters.Parameter parameter in parameters)
                {
                    if (parameter.name == _8TrackEditor.VolumeEPName)
                    {
                        hasVolume = true;
                    }
                    else if (parameter.name == _8TrackEditor.TrackEPName)
                    {
                        hasTrack = true;
                    }
                }

                if (!hasVolume)
                {
                    ArrayUtility.Add <VRCExpressionParameters.Parameter>(ref expressionParameters.parameters, volumeParam);
                }
                if (!hasTrack)
                {
                    ArrayUtility.Add <VRCExpressionParameters.Parameter>(ref expressionParameters.parameters, trackParam);
                }

                UnityEditor.EditorUtility.SetDirty(expressionParameters);
            }

            //--- --- VRC Expression Menu --- ---
            VRCExpressionsMenu mainMenu = new VRCExpressionsMenu()
            {
                name = _8Track._8TrackObject.name + " Menu"
            };

            mainMenu.controls.Add(new VRCExpressionsMenu.Control()
            {
                icon = _8Track.StopIcon, name = "Stop", parameter = new VRCExpressionsMenu.Control.Parameter()
                {
                    name = _8Track.TrackEPName
                }, type = VRCExpressionsMenu.Control.ControlType.Toggle, value = 0
            });
            mainMenu.controls.Add(new VRCExpressionsMenu.Control()
            {
                icon = _8Track.VolumeIcon, name = "Volume", subParameters = new VRCExpressionsMenu.Control.Parameter[] { new VRCExpressionsMenu.Control.Parameter()
                                                                                                                         {
                                                                                                                             name = _8Track.VolumeEPName
                                                                                                                         } }, type = VRCExpressionsMenu.Control.ControlType.RadialPuppet
            });

            VRCExpressionsMenu disk = new VRCExpressionsMenu()
            {
                name = "Disk " + 1
            };

            mainMenu.controls.Add(new VRCExpressionsMenu.Control()
            {
                icon = _8Track.DiskIcon, name = disk.name, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = disk
            });

            int menuCount = 3;
            VRCExpressionsMenu        menu  = mainMenu;
            List <VRCExpressionsMenu> menus = new List <VRCExpressionsMenu>();

            int diskCount = 0;
            List <VRCExpressionsMenu> disks = new List <VRCExpressionsMenu>
            {
                disk
            };

            int trackNumber = 0;

            do
            {
                if (libAssets.Count <= trackNumber)
                {
                    break;
                }

                string trackName = libAssets[trackNumber].name;
                trackNumber++;

                if (diskCount == 8)
                {
                    VRCExpressionsMenu newDisk = new VRCExpressionsMenu()
                    {
                        name = "Disk " + (disks.Count + 1)
                    };

                    if (7 == menuCount && 0 < libAssets.Count - (trackNumber))
                    {
                        VRCExpressionsMenu newMenu = new VRCExpressionsMenu()
                        {
                            name = "More..."
                        };
                        menu.controls.Add(new VRCExpressionsMenu.Control()
                        {
                            icon = _8Track.FolderIcon, name = newMenu.name, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = newMenu
                        });
                        menus.Add(newMenu);
                        menu      = newMenu;
                        menuCount = 0;
                    }
                    menu.controls.Add(new VRCExpressionsMenu.Control()
                    {
                        icon = _8Track.DiskIcon, name = newDisk.name, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = newDisk
                    });
                    menuCount++;
                    disks.Add(newDisk);
                    disk      = newDisk;
                    diskCount = 0;
                }

                disk.controls.Add(new VRCExpressionsMenu.Control()
                {
                    icon = _8Track.TrackIcon, name = trackName, parameter = new VRCExpressionsMenu.Control.Parameter()
                    {
                        name = _8Track.TrackEPName
                    }, type = VRCExpressionsMenu.Control.ControlType.Toggle, value = trackNumber
                });
                diskCount++;
            } while(true);

            foreach (VRCExpressionsMenu d in disks)
            {
                CreateAsset(_8Track.AudioClip, d, d.name + ".asset");
                EditorUtility.SetDirty(d);
            }

            CreateAsset(_8Track.AudioClip, mainMenu, mainMenu.name + ".asset");
            EditorUtility.SetDirty(mainMenu);
            for (int i = 0; i < menus.Count; i++)
            {
                CreateAsset(_8TrackEditor.AudioClip, menus[i], _8Track._8TrackObject.name + " Menu " + i + ".asset");
            }

            if (null != expressionsMenu)
            {
                VRCExpressionsMenu.Control toggle = GetMenuControl();
                expressionsMenu.controls.Remove(toggle);
                expressionsMenu.controls.Add(new VRCExpressionsMenu.Control()
                {
                    icon = _8Track.FolderIcon, name = _8TrackEditor._8TrackObject.name, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = mainMenu
                });
                EditorUtility.SetDirty(expressionsMenu);
            }
            EditorUtility.SetDirty(expressionParameters);
            AssetDatabase.SaveAssets();
            return(true);
        }