fix: internal parameters not renamed on installed VRCExpressionMenu submenus (#314)

Thanks to suzuryg for finding the cause of the bug and suggesting an initial fix.

Reported-By: 33linn
Fixes: #305
This commit is contained in:
bd_ 2023-05-23 21:10:22 +09:00 committed by GitHub
parent 023e206d1c
commit a4c3e1d0f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 620 additions and 18 deletions

View File

@ -0,0 +1,25 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -340790334, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3}
m_Name: InstalledMenu 1
m_EditorClassIdentifier:
controls:
- name: test
icon: {fileID: 0}
type: 101
parameter:
name: test
value: 1
style: 0
subMenu: {fileID: 0}
subParameters: []
labels: []

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 614a7f73ba0dcf549ba9c5d5f5ca08c5
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,35 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -340790334, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3}
m_Name: InstalledMenu
m_EditorClassIdentifier:
controls:
- name: test
icon: {fileID: 0}
type: 101
parameter:
name: test
value: 1
style: 0
subMenu: {fileID: 0}
subParameters: []
labels: []
- name: submenu
icon: {fileID: 0}
type: 103
parameter:
name:
value: 1
style: 0
subMenu: {fileID: 11400000, guid: 614a7f73ba0dcf549ba9c5d5f5ca08c5, type: 2}
subParameters: []
labels: []

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2c0378f39ab75fa49a4389865f2cacd0
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,389 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &60508717702498984
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5510421323487623105}
- component: {fileID: 4185130606118511228}
- component: {fileID: 5128436694278922395}
- component: {fileID: 7568325595845725080}
m_Layer: 0
m_Name: RenameInstalledMenu
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &5510421323487623105
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 60508717702498984}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -0.26738983, y: 0.9100384, z: -0.11232555}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 915584570472609813}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &4185130606118511228
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 60508717702498984}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 542108242, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3}
m_Name:
m_EditorClassIdentifier:
Name:
ViewPosition: {x: 0, y: 1.6, z: 0.2}
Animations: 0
ScaleIPD: 1
lipSync: 0
lipSyncJawBone: {fileID: 0}
lipSyncJawClosed: {x: 0, y: 0, z: 0, w: 1}
lipSyncJawOpen: {x: 0, y: 0, z: 0, w: 1}
VisemeSkinnedMesh: {fileID: 0}
MouthOpenBlendShapeName: Facial_Blends.Jaw_Down
VisemeBlendShapes: []
unityVersion:
portraitCameraPositionOffset: {x: 0, y: 0, z: 0}
portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139}
networkIDs: []
customExpressions: 1
expressionsMenu: {fileID: 0}
expressionParameters: {fileID: 0}
enableEyeLook: 0
customEyeLookSettings:
eyeMovement:
confidence: 0.5
excitement: 0.5
leftEye: {fileID: 0}
rightEye: {fileID: 0}
eyesLookingStraight:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyesLookingUp:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyesLookingDown:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyesLookingLeft:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyesLookingRight:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyelidType: 0
upperLeftEyelid: {fileID: 0}
upperRightEyelid: {fileID: 0}
lowerLeftEyelid: {fileID: 0}
lowerRightEyelid: {fileID: 0}
eyelidsDefault:
upper:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
lower:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyelidsClosed:
upper:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
lower:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyelidsLookingUp:
upper:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
lower:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyelidsLookingDown:
upper:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
lower:
linked: 1
left: {x: 0, y: 0, z: 0, w: 0}
right: {x: 0, y: 0, z: 0, w: 0}
eyelidsSkinnedMesh: {fileID: 0}
eyelidsBlendshapes:
customizeAnimationLayers: 1
baseAnimationLayers:
- isEnabled: 0
type: 0
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
- isEnabled: 0
type: 4
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
- isEnabled: 0
type: 5
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
specialAnimationLayers:
- isEnabled: 0
type: 6
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
- isEnabled: 0
type: 7
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
- isEnabled: 0
type: 8
animatorController: {fileID: 0}
mask: {fileID: 0}
isDefault: 1
AnimationPreset: {fileID: 0}
animationHashSet: []
autoFootsteps: 1
autoLocomotion: 1
collider_head:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_torso:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_footR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_footL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_handR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_handL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerIndexL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerMiddleL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerRingL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerLittleL:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerIndexR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerMiddleR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerRingR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
collider_fingerLittleR:
isMirrored: 1
state: 0
transform: {fileID: 0}
radius: 0
height: 0
position: {x: 0, y: 0, z: 0}
rotation: {x: 0, y: 0, z: 0, w: 1}
--- !u!114 &5128436694278922395
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 60508717702498984}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3}
m_Name:
m_EditorClassIdentifier:
launchedFromSDKPipeline: 0
completedSDKPipeline: 0
blueprintId:
contentType: 0
assetBundleUnityVersion:
fallbackStatus: 0
--- !u!95 &7568325595845725080
Animator:
serializedVersion: 3
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 60508717702498984}
m_Enabled: 1
m_Avatar: {fileID: 0}
m_Controller: {fileID: 0}
m_CullingMode: 0
m_UpdateMode: 0
m_ApplyRootMotion: 0
m_LinearVelocityBlending: 0
m_WarningMessage:
m_HasTransformHierarchy: 1
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorControllerStateOnDisable: 0
--- !u!1 &4981990376963646429
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 915584570472609813}
- component: {fileID: 400234402456169398}
- component: {fileID: 2535240168338781792}
m_Layer: 0
m_Name: inner
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &915584570472609813
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4981990376963646429}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 5510421323487623105}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &400234402456169398
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4981990376963646429}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 71a96d4ea0c344f39e277d82035bf9bd, type: 3}
m_Name:
m_EditorClassIdentifier:
parameters:
- nameOrPrefix: test
remapTo:
internalParameter: 1
isPrefix: 0
syncType: 3
localOnly: 0
defaultValue: 0
saved: 0
--- !u!114 &2535240168338781792
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4981990376963646429}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7ef83cb0c23d4d7c9d41021e544a1978, type: 3}
m_Name:
m_EditorClassIdentifier:
menuToAppend: {fileID: 11400000, guid: 2c0378f39ab75fa49a4389865f2cacd0, type: 2}
installTargetMenu: {fileID: 0}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a530c77b9f332ec46828790c0ca65568
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -4,6 +4,7 @@ using NUnit.Framework;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
namespace modular_avatar_tests.RenameParametersTests namespace modular_avatar_tests.RenameParametersTests
{ {
@ -32,5 +33,34 @@ namespace modular_avatar_tests.RenameParametersTests
Assert.AreEqual("param_new", driver.parameters[0].name); Assert.AreEqual("param_new", driver.parameters[0].name);
} }
} }
[Test]
public void RenameInstalledMenu()
{
var prefab = CreatePrefab("RenameInstalledMenu.prefab");
AvatarProcessor.ProcessAvatar(prefab);
var menu = prefab.GetComponent<VRCAvatarDescriptor>().expressionsMenu;
Assert.AreEqual("test$$Internal_0", menu.controls[0].parameter.name);
Assert.AreEqual("test$$Internal_0", menu.controls[1].subMenu.controls[0].parameter.name);
}
[Test]
public void TestRecursiveMenu()
{
var menu = LoadAsset<VRCExpressionsMenu>("m1.asset");
var avatar = CreateRoot("root");
var child = CreateChild(avatar, "child");
var param = child.AddComponent<ModularAvatarParameters>();
param.parameters.Add(new ParameterConfig()
{
nameOrPrefix = "test",
internalParameter = true,
});
var installer = child.AddComponent<ModularAvatarMenuInstaller>();
installer.menuToAppend = menu;
AvatarProcessor.ProcessAvatar(avatar);
}
} }
} }

View File

@ -0,0 +1,35 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -340790334, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3}
m_Name: m1
m_EditorClassIdentifier:
controls:
- name: New Control
icon: {fileID: 0}
type: 103
parameter:
name:
value: 1
style: 0
subMenu: {fileID: 11400000, guid: 9480c3cf97dd60f4dbb9ee2cd1e6b5d3, type: 2}
subParameters: []
labels: []
- name: New Control
icon: {fileID: 0}
type: 101
parameter:
name: test
value: 1
style: 0
subMenu: {fileID: 0}
subParameters: []
labels: []

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 414ea3241357b4f4d90073ec98345186
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,35 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -340790334, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3}
m_Name: m2
m_EditorClassIdentifier:
controls:
- name: New Control
icon: {fileID: 0}
type: 103
parameter:
name:
value: 1
style: 0
subMenu: {fileID: 11400000, guid: 414ea3241357b4f4d90073ec98345186, type: 2}
subParameters: []
labels: []
- name: New Control
icon: {fileID: 0}
type: 101
parameter:
name: test
value: 1
style: 0
subMenu: {fileID: 0}
subParameters: []
labels: []

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9480c3cf97dd60f4dbb9ee2cd1e6b5d3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -50,17 +50,24 @@ namespace modular_avatar_tests
protected GameObject CreatePrefab(string relPath) protected GameObject CreatePrefab(string relPath)
{ {
var prefabRoot = "Assets/_ModularAvatar/EditModeTests/" + GetType().Name + "/"; var prefab = LoadAsset<GameObject>(relPath);
var prefabPath = prefabRoot + relPath;
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
Assert.NotNull(prefab, "Missing test prefab {0}", prefabPath);
var go = Object.Instantiate(prefab); var go = Object.Instantiate(prefab);
objects.Add(go); objects.Add(go);
return go; return go;
} }
protected T LoadAsset<T>(string relPath) where T : UnityEngine.Object
{
var root = "Assets/_ModularAvatar/EditModeTests/" + GetType().Name + "/";
var path = root + relPath;
var obj = AssetDatabase.LoadAssetAtPath<T>(path);
Assert.NotNull(obj, "Missing test asset {0}", path);
return obj;
}
protected static AnimationClip findFxClip(GameObject prefab, string layerName) protected static AnimationClip findFxClip(GameObject prefab, string layerName)
{ {

View File

@ -33,7 +33,8 @@ namespace nadena.dev.modular_avatar.core.editor.menu
class NodeContextImpl : NodeContext class NodeContextImpl : NodeContext
{ {
[CanBeNull] [CanBeNull]
internal delegate VirtualMenuNode NodeForDelegate(object menu); internal delegate VirtualMenuNode
NodeForDelegate(object menu, Action<VRCExpressionsMenu.Control> postprocessor);
private readonly ImmutableDictionary<object, ImmutableList<ModularAvatarMenuInstaller>> private readonly ImmutableDictionary<object, ImmutableList<ModularAvatarMenuInstaller>>
_menuToInstallerMap; _menuToInstallerMap;
@ -47,25 +48,25 @@ namespace nadena.dev.modular_avatar.core.editor.menu
private readonly Action<VRCExpressionsMenu> _visitedMenu; private readonly Action<VRCExpressionsMenu> _visitedMenu;
private readonly HashSet<object> _visited = new HashSet<object>(); private readonly HashSet<object> _visited = new HashSet<object>();
private Action<VRCExpressionsMenu.Control> _currentPostprocessor = _control => { }; private Action<VRCExpressionsMenu.Control> _currentPostprocessor;
internal ImmutableHashSet<object> Visited => _visited.ToImmutableHashSet(); internal ImmutableHashSet<object> Visited => _visited.ToImmutableHashSet();
private class PostprocessorContext : IDisposable private class PostprocessorContext : IDisposable
{ {
private NodeContextImpl _context; private NodeContextImpl _context;
private Action<VRCExpressionsMenu.Control> _priorPreprocessor; private Action<VRCExpressionsMenu.Control> _priorPostprocessor;
public PostprocessorContext(NodeContextImpl context, Action<VRCExpressionsMenu.Control> preprocessor) public PostprocessorContext(NodeContextImpl context, Action<VRCExpressionsMenu.Control> postprocessor)
{ {
this._context = context; this._context = context;
this._priorPreprocessor = context._currentPostprocessor; this._priorPostprocessor = context._currentPostprocessor;
context._currentPostprocessor = preprocessor; context._currentPostprocessor = postprocessor ?? context._currentPostprocessor;
} }
public void Dispose() public void Dispose()
{ {
_context._currentPostprocessor = _priorPreprocessor; _context._currentPostprocessor = _priorPostprocessor;
} }
} }
@ -74,7 +75,8 @@ namespace nadena.dev.modular_avatar.core.editor.menu
NodeForDelegate nodeFor, NodeForDelegate nodeFor,
ImmutableDictionary<object, ImmutableList<ModularAvatarMenuInstaller>> menuToInstallerMap, ImmutableDictionary<object, ImmutableList<ModularAvatarMenuInstaller>> menuToInstallerMap,
ImmutableDictionary<ModularAvatarMenuInstaller, Action<VRCExpressionsMenu.Control>> postProcessControls, ImmutableDictionary<ModularAvatarMenuInstaller, Action<VRCExpressionsMenu.Control>> postProcessControls,
Action<VRCExpressionsMenu> visitedMenu Action<VRCExpressionsMenu> visitedMenu,
Action<VRCExpressionsMenu.Control> postprocessor
) )
{ {
_node = node; _node = node;
@ -82,6 +84,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu
_menuToInstallerMap = menuToInstallerMap; _menuToInstallerMap = menuToInstallerMap;
_postProcessControls = postProcessControls; _postProcessControls = postProcessControls;
_visitedMenu = visitedMenu; _visitedMenu = visitedMenu;
_currentPostprocessor = postprocessor;
} }
public void PushNode(VRCExpressionsMenu expMenu) public void PushNode(VRCExpressionsMenu expMenu)
@ -146,6 +149,8 @@ namespace nadena.dev.modular_avatar.core.editor.menu
virtualControl.SubmenuNode = NodeFor(control.subMenu); virtualControl.SubmenuNode = NodeFor(control.subMenu);
_currentPostprocessor(virtualControl);
PushControl(virtualControl); PushControl(virtualControl);
} }
@ -157,13 +162,13 @@ namespace nadena.dev.modular_avatar.core.editor.menu
public VirtualMenuNode NodeFor(VRCExpressionsMenu menu) public VirtualMenuNode NodeFor(VRCExpressionsMenu menu)
{ {
if (menu == null) return null; if (menu == null) return null;
return _nodeFor(menu); return _nodeFor(menu, _currentPostprocessor);
} }
public VirtualMenuNode NodeFor(MenuSource source) public VirtualMenuNode NodeFor(MenuSource source)
{ {
if (source == null) return null; if (source == null) return null;
return _nodeFor(source); return _nodeFor(source, _currentPostprocessor);
} }
} }
@ -329,7 +334,8 @@ namespace nadena.dev.modular_avatar.core.editor.menu
var rootContext = var rootContext =
new NodeContextImpl(RootNode, NodeFor, menuToInstallerFiltered, _postprocessControlsHooks, new NodeContextImpl(RootNode, NodeFor, menuToInstallerFiltered, _postprocessControlsHooks,
m => _visitedMenus.Add(m)); m => _visitedMenus.Add(m),
_control => { });
if (RootMenuKey is VRCExpressionsMenu menu) if (RootMenuKey is VRCExpressionsMenu menu)
{ {
foreach (var control in menu.controls) foreach (var control in menu.controls)
@ -353,7 +359,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu
_visitedNodes = rootContext.Visited; _visitedNodes = rootContext.Visited;
VirtualMenuNode NodeFor(object key) VirtualMenuNode NodeFor(object key, Action<VRCExpressionsMenu.Control> postprocessContext)
{ {
if (_resolvedMenu.TryGetValue(key, out var node)) return node; if (_resolvedMenu.TryGetValue(key, out var node)) return node;
node = new VirtualMenuNode(key); node = new VirtualMenuNode(key);
@ -365,7 +371,8 @@ namespace nadena.dev.modular_avatar.core.editor.menu
{ {
var context = new NodeContextImpl(node, NodeFor, menuToInstallerFiltered, var context = new NodeContextImpl(node, NodeFor, menuToInstallerFiltered,
_postprocessControlsHooks, _postprocessControlsHooks,
m => _visitedMenus.Add(m)); m => _visitedMenus.Add(m),
postprocessContext);
if (key is VRCExpressionsMenu expMenu) if (key is VRCExpressionsMenu expMenu)
{ {
context.PushNode(expMenu); context.PushNode(expMenu);