mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-05-10 13:19:01 +08:00
feat: add the MAMenuItem component
This commit is contained in:
parent
d385eb8800
commit
d212dabc27
@ -172,6 +172,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
var context = new BuildContext(vrcAvatarDescriptor);
|
||||
|
||||
new ReifyMenuPass().OnPreprocessAvatar(vrcAvatarDescriptor, context);
|
||||
new RenameParametersHook().OnPreprocessAvatar(avatarGameObject, context);
|
||||
new MergeAnimatorProcessor().OnPreprocessAvatar(avatarGameObject, context);
|
||||
context.AnimationDatabase.Bootstrap(vrcAvatarDescriptor);
|
||||
@ -212,6 +213,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
ErrorReportUI.MaybeOpenErrorReportUI();
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
Resources.UnloadUnusedAssets();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
@ -13,6 +15,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
internal readonly AnimationDatabase AnimationDatabase = new AnimationDatabase();
|
||||
internal readonly AnimatorController AssetContainer;
|
||||
|
||||
internal readonly Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> ClonedMenus
|
||||
= new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>();
|
||||
|
||||
|
||||
public BuildContext(VRCAvatarDescriptor avatarDescriptor)
|
||||
{
|
||||
AvatarDescriptor = avatarDescriptor;
|
||||
@ -72,5 +78,38 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
merger.AddOverrideController("", overrideController, null);
|
||||
return merger.Finish();
|
||||
}
|
||||
|
||||
public VRCExpressionsMenu CloneMenu(VRCExpressionsMenu menu)
|
||||
{
|
||||
if (menu == null) return null;
|
||||
if (ClonedMenus.TryGetValue(menu, out var newMenu)) return newMenu;
|
||||
newMenu = Object.Instantiate(menu);
|
||||
this.SaveAsset(newMenu);
|
||||
ClonedMenus[menu] = newMenu;
|
||||
|
||||
foreach (var control in newMenu.controls)
|
||||
{
|
||||
if (Util.ValidateExpressionMenuIcon(control.icon) != Util.ValidateExpressionMenuIconResult.Success)
|
||||
control.icon = null;
|
||||
|
||||
for (int i = 0; i < control.labels.Length; i++)
|
||||
{
|
||||
var label = control.labels[i];
|
||||
var labelResult = Util.ValidateExpressionMenuIcon(label.icon);
|
||||
if (labelResult != Util.ValidateExpressionMenuIconResult.Success)
|
||||
{
|
||||
label.icon = null;
|
||||
control.labels[i] = label;
|
||||
}
|
||||
}
|
||||
|
||||
if (control.type == VRCExpressionsMenu.Control.ControlType.SubMenu)
|
||||
{
|
||||
control.subMenu = CloneMenu(control.subMenu);
|
||||
}
|
||||
}
|
||||
|
||||
return newMenu;
|
||||
}
|
||||
}
|
||||
}
|
@ -137,7 +137,7 @@ namespace nadena.dev.modular_avatar.editor.ErrorReporting
|
||||
private static List<ErrorLog> CheckInternal(ModularAvatarMenuInstaller mi)
|
||||
{
|
||||
// TODO - check that target menu is in the avatar
|
||||
if (mi.menuToAppend == null)
|
||||
if (mi.menuToAppend == null && mi.GetComponent<MenuSource>() == null)
|
||||
{
|
||||
return new List<ErrorLog>()
|
||||
{
|
||||
|
@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
[CustomEditor(typeof(MAMenuItem))]
|
||||
internal class MAMenuItemInspector : MAEditorBase
|
||||
{
|
||||
private SerializedProperty prop_submenu_source;
|
||||
private SerializedProperty prop_control;
|
||||
private SerializedProperty prop_otherObjChildren;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
prop_control = serializedObject.FindProperty(nameof(MAMenuItem.Control));
|
||||
prop_submenu_source = serializedObject.FindProperty(nameof(MAMenuItem.MenuSource));
|
||||
prop_otherObjChildren = serializedObject.FindProperty(nameof(MAMenuItem.menuSource_otherObjectChildren));
|
||||
}
|
||||
|
||||
private void DrawControlSettings(SerializedProperty control, string name = null,
|
||||
Action<string> commitName = null)
|
||||
{
|
||||
if (name != null)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var targetGameObject = ((MAMenuItem) target).gameObject;
|
||||
var newName = EditorGUILayout.TextField("Name", targetGameObject.name);
|
||||
if (EditorGUI.EndChangeCheck() && commitName != null)
|
||||
{
|
||||
commitName(newName);
|
||||
}
|
||||
}
|
||||
|
||||
var prop_type = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.type));
|
||||
var prop_parameter = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter))
|
||||
.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter.name));
|
||||
var prop_value = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.value));
|
||||
|
||||
EditorGUILayout.PropertyField(prop_type);
|
||||
EditorGUILayout.PropertyField(prop_parameter, new GUIContent("Parameter"));
|
||||
EditorGUILayout.PropertyField(prop_value);
|
||||
}
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
bool multiEdit = targets.Length != 1;
|
||||
string name = null;
|
||||
Action<string> commitName = null;
|
||||
if (!multiEdit)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var targetGameObject = ((MAMenuItem) target).gameObject;
|
||||
name = targetGameObject.name;
|
||||
commitName = newName =>
|
||||
{
|
||||
Undo.RecordObject(targetGameObject, "Rename object");
|
||||
targetGameObject.name = newName;
|
||||
};
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
DrawControlSettings(prop_control, name, commitName);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (multiEdit) return;
|
||||
|
||||
var menuItem = (MAMenuItem) target;
|
||||
if (menuItem.Control.type == VRCExpressionsMenu.Control.ControlType.SubMenu)
|
||||
{
|
||||
GUILayout.Space(EditorStyles.label.lineHeight);
|
||||
EditorGUILayout.LabelField("Sub Menu", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(prop_submenu_source);
|
||||
|
||||
if (prop_submenu_source.enumValueIndex == (int) SubmenuSource.Children)
|
||||
{
|
||||
EditorGUILayout.PropertyField(prop_otherObjChildren);
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
switch (menuItem.MenuSource)
|
||||
{
|
||||
default: break;
|
||||
case SubmenuSource.Children:
|
||||
{
|
||||
var source = menuItem.menuSource_otherObjectChildren != null
|
||||
? menuItem.menuSource_otherObjectChildren
|
||||
: menuItem.gameObject;
|
||||
foreach (Transform t in source.transform)
|
||||
{
|
||||
var child = t.GetComponent<MAMenuItem>();
|
||||
if (child == null) continue;
|
||||
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUILayout.ObjectField(new GUIContent(), child, typeof(MAMenuItem), true,
|
||||
GUILayout.ExpandWidth(true));
|
||||
}
|
||||
|
||||
GUILayout.Space(20);
|
||||
GUILayout.Label("Enabled", GUILayout.Width(50));
|
||||
var childObject = t.gameObject;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var active = GUILayout.Toggle(childObject.activeSelf, new GUIContent(),
|
||||
GUILayout.Width(EditorGUIUtility.singleLineHeight));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
childObject.SetActive(active);
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
name = t.gameObject.name;
|
||||
commitName = newName =>
|
||||
{
|
||||
Undo.RecordObject(t.gameObject, "Rename object");
|
||||
t.gameObject.name = newName;
|
||||
};
|
||||
|
||||
var childSO = new SerializedObject(child);
|
||||
var childControl = childSO.FindProperty(nameof(MAMenuItem.Control));
|
||||
DrawControlSettings(childControl, name, commitName);
|
||||
childSO.ApplyModifiedProperties();
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b674c72186c4e6884b0cd05098f11b6
|
||||
timeCreated: 1676791017
|
@ -51,6 +51,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
_menuToAppend = _installer.menuToAppend;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
SetupMenuEditor();
|
||||
@ -107,6 +108,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
if (targets.Length == 1)
|
||||
{
|
||||
/* TODO
|
||||
_menuFoldout = EditorGUILayout.Foldout(_menuFoldout, G("menuinstall.showcontents"));
|
||||
if (_menuFoldout)
|
||||
{
|
||||
@ -119,13 +121,47 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
bool inconsistentSources = false;
|
||||
MenuSource menuSource = null;
|
||||
bool first = true;
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var component = (ModularAvatarMenuInstaller) target;
|
||||
var componentSource = component.GetComponent<MenuSource>();
|
||||
if (componentSource != null)
|
||||
{
|
||||
if (menuSource == null && first)
|
||||
{
|
||||
menuSource = componentSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
inconsistentSources = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (menuSource != null)
|
||||
{
|
||||
// TODO localize
|
||||
EditorGUILayout.HelpBox("Menu contents provided by " + menuSource.GetType() + " component",
|
||||
MessageType.Info);
|
||||
}
|
||||
|
||||
if (!inconsistentSources)
|
||||
{
|
||||
_devFoldout = EditorGUILayout.Foldout(_devFoldout, G("menuinstall.devoptions"));
|
||||
if (_devFoldout)
|
||||
{
|
||||
SerializedProperty menuToAppendProperty = serializedObject.FindProperty(nameof(ModularAvatarMenuInstaller.menuToAppend));
|
||||
switch (ValidateExpressionMenuIcon((VRCExpressionsMenu)menuToAppendProperty.objectReferenceValue))
|
||||
SerializedProperty menuToAppendProperty =
|
||||
serializedObject.FindProperty(nameof(ModularAvatarMenuInstaller.menuToAppend));
|
||||
if (!menuToAppendProperty.hasMultipleDifferentValues)
|
||||
{
|
||||
switch (ValidateExpressionMenuIcon(
|
||||
(VRCExpressionsMenu) menuToAppendProperty.objectReferenceValue))
|
||||
{
|
||||
case ValidateExpressionMenuIconResult.Success:
|
||||
break;
|
||||
@ -138,12 +174,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(
|
||||
menuToAppendProperty, new GUIContent(G("menuinstall.srcmenu")));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
@ -226,12 +264,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
VRCExpressionsMenu parent = queue.Dequeue();
|
||||
var controls = parent.controls.Where(control => control.type == VRCExpressionsMenu.Control.ControlType.SubMenu && control.subMenu != null);
|
||||
var controls = parent.controls.Where(control =>
|
||||
control.type == VRCExpressionsMenu.Control.ControlType.SubMenu && control.subMenu != null);
|
||||
foreach (VRCExpressionsMenu.Control control in controls)
|
||||
{
|
||||
// Do not filter in LINQ to avoid closure allocation
|
||||
if (visitedMenus.Contains(control.subMenu)) continue;
|
||||
if (!_menuInstallersMap.TryGetValue(control.subMenu, out List<ModularAvatarMenuInstaller> fromInstallers))
|
||||
if (!_menuInstallersMap.TryGetValue(control.subMenu,
|
||||
out List<ModularAvatarMenuInstaller> fromInstallers))
|
||||
{
|
||||
fromInstallers = new List<ModularAvatarMenuInstaller>();
|
||||
_menuInstallersMap[control.subMenu] = fromInstallers;
|
||||
@ -245,12 +285,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMenuReachable(VRCAvatarDescriptor avatar, VRCExpressionsMenu menu, HashSet<ModularAvatarMenuInstaller> visitedInstaller = null)
|
||||
private bool IsMenuReachable(VRCAvatarDescriptor avatar, VRCExpressionsMenu menu,
|
||||
HashSet<ModularAvatarMenuInstaller> visitedInstaller = null)
|
||||
{
|
||||
if (_avatarMenus == null || _avatarMenus.Contains(menu)) return true;
|
||||
|
||||
if (_menuInstallersMap == null) return true;
|
||||
if (visitedInstaller == null) visitedInstaller = new HashSet<ModularAvatarMenuInstaller> { (ModularAvatarMenuInstaller)target };
|
||||
if (visitedInstaller == null)
|
||||
visitedInstaller = new HashSet<ModularAvatarMenuInstaller> {(ModularAvatarMenuInstaller) target};
|
||||
|
||||
if (!_menuInstallersMap.TryGetValue(menu, out List<ModularAvatarMenuInstaller> installers)) return false;
|
||||
foreach (ModularAvatarMenuInstaller installer in installers)
|
||||
@ -269,14 +311,16 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ValidateExpressionMenuIconResult ValidateExpressionMenuIcon(VRCExpressionsMenu menu, HashSet<VRCExpressionsMenu> visitedMenus = null)
|
||||
private static ValidateExpressionMenuIconResult ValidateExpressionMenuIcon(VRCExpressionsMenu menu,
|
||||
HashSet<VRCExpressionsMenu> visitedMenus = null)
|
||||
{
|
||||
if (menu == null) return ValidateExpressionMenuIconResult.Success;
|
||||
if (visitedMenus == null) visitedMenus = new HashSet<VRCExpressionsMenu>();
|
||||
if (visitedMenus.Contains(menu)) return ValidateExpressionMenuIconResult.Success;
|
||||
visitedMenus.Add(menu);
|
||||
|
||||
foreach (VRCExpressionsMenu.Control control in menu.controls) {
|
||||
foreach (VRCExpressionsMenu.Control control in menu.controls)
|
||||
{
|
||||
// Control
|
||||
ValidateExpressionMenuIconResult result = Util.ValidateExpressionMenuIcon(control.icon);
|
||||
if (result != ValidateExpressionMenuIconResult.Success) return result;
|
||||
@ -293,12 +337,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
// SubMenu
|
||||
if (control.type != VRCExpressionsMenu.Control.ControlType.SubMenu) continue;
|
||||
ValidateExpressionMenuIconResult subMenuResult = ValidateExpressionMenuIcon(control.subMenu, visitedMenus);
|
||||
ValidateExpressionMenuIconResult subMenuResult =
|
||||
ValidateExpressionMenuIcon(control.subMenu, visitedMenus);
|
||||
if (subMenuResult != ValidateExpressionMenuIconResult.Success) return subMenuResult;
|
||||
}
|
||||
|
||||
return ValidateExpressionMenuIconResult.Success;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
76
Packages/nadena.dev.modular-avatar/Editor/MenuExtractor.cs
Normal file
76
Packages/nadena.dev.modular-avatar/Editor/MenuExtractor.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
internal class MenuExtractor
|
||||
{
|
||||
private const int PRIORITY = 49;
|
||||
|
||||
[MenuItem("GameObject/[Modular Avatar] Extract menu", false, PRIORITY)]
|
||||
static void ExtractMenu(MenuCommand menuCommand)
|
||||
{
|
||||
if (!(menuCommand.context is GameObject gameObj)) return;
|
||||
var avatar = gameObj.GetComponent<VRCAvatarDescriptor>();
|
||||
if (avatar == null || avatar.expressionsMenu == null) return;
|
||||
|
||||
VRCExpressionsMenu.Control fakeControl = new VRCExpressionsMenu.Control()
|
||||
{
|
||||
subMenu = avatar.expressionsMenu,
|
||||
type = VRCExpressionsMenu.Control.ControlType.SubMenu,
|
||||
name = "Avatar Menu"
|
||||
};
|
||||
var rootMenu = ConvertSubmenu(gameObj, fakeControl, new Dictionary<VRCExpressionsMenu, MenuSource>());
|
||||
Undo.RecordObject(avatar, "Convert menu");
|
||||
avatar.expressionsMenu = null;
|
||||
|
||||
rootMenu.gameObject.AddComponent<ModularAvatarMenuInstaller>();
|
||||
}
|
||||
|
||||
private static MenuSource ConvertSubmenu(
|
||||
GameObject parentObj,
|
||||
VRCExpressionsMenu.Control sourceControl,
|
||||
Dictionary<VRCExpressionsMenu, MenuSource> convertedMenus
|
||||
)
|
||||
{
|
||||
var itemObj = new GameObject();
|
||||
itemObj.name = string.IsNullOrEmpty(sourceControl.name) ? " " : sourceControl.name;
|
||||
Undo.RegisterCreatedObjectUndo(itemObj, "Convert menu");
|
||||
itemObj.transform.SetParent(parentObj.transform);
|
||||
itemObj.transform.localPosition = Vector3.zero;
|
||||
itemObj.transform.localRotation = Quaternion.identity;
|
||||
itemObj.transform.localScale = Vector3.one;
|
||||
|
||||
var menuItem = itemObj.AddComponent<MAMenuItem>();
|
||||
menuItem.Control = sourceControl;
|
||||
|
||||
if (menuItem.Control.type == VRCExpressionsMenu.Control.ControlType.SubMenu)
|
||||
{
|
||||
if (convertedMenus.TryGetValue(sourceControl.subMenu, out var otherSource))
|
||||
{
|
||||
menuItem.MenuSource = SubmenuSource.OtherMenuItem;
|
||||
menuItem.menuSource_otherSource = otherSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
convertedMenus[sourceControl.subMenu] = menuItem;
|
||||
|
||||
menuItem.MenuSource = SubmenuSource.Children;
|
||||
|
||||
if (sourceControl.subMenu.controls != null)
|
||||
{
|
||||
foreach (var childControl in sourceControl.subMenu.controls)
|
||||
{
|
||||
ConvertSubmenu(itemObj, childControl, convertedMenus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return menuItem;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de7ea831512b4d9c9e92985ab6fd5f17
|
||||
timeCreated: 1676896326
|
@ -20,9 +20,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
private BuildContext _context;
|
||||
|
||||
private Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> _clonedMenus;
|
||||
|
||||
|
||||
private VRCExpressionsMenu _rootMenu;
|
||||
|
||||
private MenuTree _menuTree;
|
||||
@ -38,8 +35,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
.ToArray();
|
||||
if (menuInstallers.Length == 0) return;
|
||||
|
||||
|
||||
_clonedMenus = new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>();
|
||||
_visitedInstallerStack = new Stack<ModularAvatarMenuInstaller>();
|
||||
|
||||
VRCAvatarDescriptor avatar = avatarRoot.GetComponent<VRCAvatarDescriptor>();
|
||||
@ -49,14 +44,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var menu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
|
||||
_context.SaveAsset(menu);
|
||||
avatar.expressionsMenu = menu;
|
||||
_clonedMenus[menu] = menu;
|
||||
context.ClonedMenus[menu] = menu;
|
||||
}
|
||||
|
||||
_rootMenu = avatar.expressionsMenu;
|
||||
_menuTree = new MenuTree(avatar);
|
||||
_menuTree.TraverseAvatarMenu();
|
||||
|
||||
avatar.expressionsMenu = CloneMenu(avatar.expressionsMenu);
|
||||
avatar.expressionsMenu = _context.CloneMenu(avatar.expressionsMenu);
|
||||
|
||||
foreach (ModularAvatarMenuInstaller installer in menuInstallers)
|
||||
{
|
||||
@ -84,10 +79,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
|
||||
if (installer.installTargetMenu == null || installer.menuToAppend == null) return;
|
||||
if (!_clonedMenus.TryGetValue(installTarget, out var targetMenu)) return;
|
||||
if (!_context.ClonedMenus.TryGetValue(installTarget, out var targetMenu)) return;
|
||||
|
||||
// Clone before appending to sanitize menu icons
|
||||
targetMenu.controls.AddRange(CloneMenu(installer.menuToAppend).controls);
|
||||
targetMenu.controls.AddRange(_context.CloneMenu(installer.menuToAppend).controls);
|
||||
|
||||
SplitMenu(installer, targetMenu);
|
||||
|
||||
@ -128,42 +123,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
labels = Array.Empty<VRCExpressionsMenu.Control.Label>()
|
||||
});
|
||||
|
||||
_clonedMenus[installer.installTargetMenu] = newMenu;
|
||||
_context.ClonedMenus[installer.installTargetMenu] = newMenu;
|
||||
targetMenu = newMenu;
|
||||
}
|
||||
}
|
||||
|
||||
private VRCExpressionsMenu CloneMenu(VRCExpressionsMenu menu)
|
||||
{
|
||||
if (menu == null) return null;
|
||||
if (_clonedMenus.TryGetValue(menu, out var newMenu)) return newMenu;
|
||||
newMenu = Object.Instantiate(menu);
|
||||
_context.SaveAsset(newMenu);
|
||||
_clonedMenus[menu] = newMenu;
|
||||
|
||||
foreach (var control in newMenu.controls)
|
||||
{
|
||||
if (Util.ValidateExpressionMenuIcon(control.icon) != Util.ValidateExpressionMenuIconResult.Success)
|
||||
control.icon = null;
|
||||
|
||||
for (int i = 0; i < control.labels.Length; i++)
|
||||
{
|
||||
var label = control.labels[i];
|
||||
var labelResult = Util.ValidateExpressionMenuIcon(label.icon);
|
||||
if (labelResult != Util.ValidateExpressionMenuIconResult.Success)
|
||||
{
|
||||
label.icon = null;
|
||||
control.labels[i] = label;
|
||||
}
|
||||
}
|
||||
|
||||
if (control.type == VRCExpressionsMenu.Control.ControlType.SubMenu)
|
||||
{
|
||||
control.subMenu = CloneMenu(control.subMenu);
|
||||
}
|
||||
}
|
||||
|
||||
return newMenu;
|
||||
}
|
||||
}
|
||||
}
|
32
Packages/nadena.dev.modular-avatar/Editor/ReifyMenuPass.cs
Normal file
32
Packages/nadena.dev.modular-avatar/Editor/ReifyMenuPass.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
internal class ReifyMenuPass
|
||||
{
|
||||
public void OnPreprocessAvatar(VRCAvatarDescriptor root, BuildContext context)
|
||||
{
|
||||
foreach (ModularAvatarMenuInstaller installer in
|
||||
root.GetComponentsInChildren<ModularAvatarMenuInstaller>(true))
|
||||
{
|
||||
BuildReport.ReportingObject(installer, () => ReifyMenu(context, installer));
|
||||
}
|
||||
}
|
||||
|
||||
private void ReifyMenu(BuildContext context, ModularAvatarMenuInstaller installer)
|
||||
{
|
||||
var source = installer.GetComponent<MenuSource>();
|
||||
if (source == null) return;
|
||||
|
||||
var controls = source.GenerateMenu();
|
||||
var menu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
|
||||
menu.controls = controls.ToList();
|
||||
|
||||
installer.menuToAppend = context.CloneMenu(menu);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e50c6e0b2ad64fc0b1b00cb45117f023
|
||||
timeCreated: 1676895255
|
130
Packages/nadena.dev.modular-avatar/Runtime/MAMenuItem.cs
Normal file
130
Packages/nadena.dev.modular-avatar/Runtime/MAMenuItem.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public abstract class MenuSource : AvatarTagComponent
|
||||
{
|
||||
/**
|
||||
* Generates the menu items for this menu source object. Submenus are not required to be persisted as assets;
|
||||
* this will be handled by the caller if necessary.
|
||||
*
|
||||
* Note that this method might be called outside of a build context (e.g. from custom inspectors).
|
||||
*/
|
||||
internal abstract VRCExpressionsMenu.Control[] GenerateMenu();
|
||||
}
|
||||
|
||||
|
||||
public enum SubmenuSource
|
||||
{
|
||||
External,
|
||||
Children,
|
||||
MenuInstaller,
|
||||
OtherMenuItem,
|
||||
}
|
||||
|
||||
public class MAMenuItem : MenuSource
|
||||
{
|
||||
public VRCExpressionsMenu.Control Control;
|
||||
public SubmenuSource MenuSource;
|
||||
|
||||
public ModularAvatarMenuInstaller menuSource_installer;
|
||||
public MenuSource menuSource_otherSource;
|
||||
public GameObject menuSource_otherObjectChildren;
|
||||
|
||||
internal override VRCExpressionsMenu.Control[] GenerateMenu()
|
||||
{
|
||||
switch (Control.type)
|
||||
{
|
||||
case VRCExpressionsMenu.Control.ControlType.SubMenu:
|
||||
return GenerateSubmenu();
|
||||
default:
|
||||
return new[]
|
||||
{Control};
|
||||
}
|
||||
}
|
||||
|
||||
private bool _recursing = false;
|
||||
private VRCExpressionsMenu _cachedMenu;
|
||||
|
||||
private VRCExpressionsMenu.Control[] GenerateSubmenu()
|
||||
{
|
||||
List<VRCExpressionsMenu.Control> controls = null;
|
||||
switch (MenuSource)
|
||||
{
|
||||
case SubmenuSource.External:
|
||||
controls = Control.subMenu?.controls?.ToList();
|
||||
break;
|
||||
case SubmenuSource.Children:
|
||||
{
|
||||
var menuRoot = menuSource_otherObjectChildren == null
|
||||
? gameObject
|
||||
: menuSource_otherObjectChildren;
|
||||
controls = new List<VRCExpressionsMenu.Control>();
|
||||
foreach (Transform child in menuRoot.transform)
|
||||
{
|
||||
var menuSource = child.GetComponent<MenuSource>();
|
||||
if (menuSource != null && child.gameObject.activeSelf && menuSource.enabled)
|
||||
{
|
||||
controls.AddRange(menuSource.GenerateMenu());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SubmenuSource.MenuInstaller:
|
||||
controls = menuSource_installer.installTargetMenu?.controls?.ToList();
|
||||
break;
|
||||
case SubmenuSource.OtherMenuItem:
|
||||
if (_recursing || menuSource_otherSource == null)
|
||||
{
|
||||
return new VRCExpressionsMenu.Control[] { };
|
||||
}
|
||||
else
|
||||
{
|
||||
_recursing = true;
|
||||
try
|
||||
{
|
||||
return menuSource_otherSource.GenerateMenu();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_recursing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (controls == null)
|
||||
{
|
||||
return new VRCExpressionsMenu.Control[] { };
|
||||
}
|
||||
|
||||
if (_cachedMenu == null) _cachedMenu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
|
||||
_cachedMenu.controls = controls;
|
||||
|
||||
var control = CloneControl(Control);
|
||||
control.name = gameObject.name;
|
||||
control.subMenu = _cachedMenu;
|
||||
|
||||
return new[] {control};
|
||||
}
|
||||
|
||||
private static VRCExpressionsMenu.Control CloneControl(VRCExpressionsMenu.Control control)
|
||||
{
|
||||
return new VRCExpressionsMenu.Control()
|
||||
{
|
||||
type = control.type,
|
||||
parameter = control.parameter,
|
||||
labels = control.labels.ToArray(),
|
||||
subParameters = control.subParameters.ToArray(),
|
||||
icon = control.icon,
|
||||
name = control.name,
|
||||
value = control.value,
|
||||
subMenu = control.subMenu
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b29d45007c5493d926d2cd45a489529
|
||||
timeCreated: 1676787152
|
Loading…
x
Reference in New Issue
Block a user