mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 18:55:06 +08:00
fix: issues with menu generation (#371)
* chore: adding unit tests for #366 and #326 * fix: duplicate submenu controls not generated for multiple installers When multiple installers referenced the same expressions menu asset, only one submenu control would be generated. * fix: submenus incorrectly deduping across different postprocessing contexts Fixes: #366, #326 * fix: postprocess context not being inherited into submenus (#326) This caused issues where parameter mappings were not being applied to submenus.
This commit is contained in:
parent
333d4e8a95
commit
3044969454
@ -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: InternalParamTestMenu
|
||||
m_EditorClassIdentifier:
|
||||
controls:
|
||||
- name: item
|
||||
icon: {fileID: 0}
|
||||
type: 102
|
||||
parameter:
|
||||
name: x
|
||||
value: 1
|
||||
style: 0
|
||||
subMenu: {fileID: 0}
|
||||
subParameters: []
|
||||
labels: []
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09a6ec3c61c7db840b3cd958099c5798
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,450 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &2860324038786842778
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2860324038786842773}
|
||||
- component: {fileID: 2860324038786842772}
|
||||
- component: {fileID: 1761937879440307670}
|
||||
- component: {fileID: 3202484316900348122}
|
||||
- component: {fileID: 2669963860607714920}
|
||||
- component: {fileID: 5085570563562387984}
|
||||
m_Layer: 0
|
||||
m_Name: InternalParameterTest
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &2860324038786842773
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2860324038786842778}
|
||||
m_LocalRotation: {x: 0.07681167, y: 0.62035006, z: 0.20221491, w: 0.7539065}
|
||||
m_LocalPosition: {x: -0.0534066, y: 0.9950551, z: 0.14142857}
|
||||
m_LocalScale: {x: 1.098901, y: 1.0989009, z: 1.098901}
|
||||
m_Children:
|
||||
- {fileID: 8910535925068494434}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &2860324038786842772
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2860324038786842778}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 71a96d4ea0c344f39e277d82035bf9bd, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
parameters:
|
||||
- nameOrPrefix: x
|
||||
remapTo:
|
||||
internalParameter: 1
|
||||
isPrefix: 0
|
||||
syncType: 3
|
||||
localOnly: 0
|
||||
defaultValue: 0
|
||||
saved: 1
|
||||
--- !u!114 &1761937879440307670
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2860324038786842778}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 3b29d45007c5493d926d2cd45a489529, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Control:
|
||||
name:
|
||||
icon: {fileID: 0}
|
||||
type: 103
|
||||
parameter:
|
||||
name:
|
||||
value: 1
|
||||
style: 0
|
||||
subMenu: {fileID: 11400000, guid: 09a6ec3c61c7db840b3cd958099c5798, type: 2}
|
||||
subParameters: []
|
||||
labels: []
|
||||
MenuSource: 0
|
||||
menuSource_otherObjectChildren: {fileID: 0}
|
||||
isSynced: 1
|
||||
isSaved: 1
|
||||
--- !u!114 &3202484316900348122
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2860324038786842778}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7ef83cb0c23d4d7c9d41021e544a1978, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
menuToAppend: {fileID: 11400000, guid: 09a6ec3c61c7db840b3cd958099c5798, type: 2}
|
||||
installTargetMenu: {fileID: 0}
|
||||
--- !u!114 &2669963860607714920
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2860324038786842778}
|
||||
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: 0
|
||||
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: 0
|
||||
baseAnimationLayers:
|
||||
- isEnabled: 0
|
||||
type: 0
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
- isEnabled: 0
|
||||
type: 2
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
- isEnabled: 0
|
||||
type: 3
|
||||
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 &5085570563562387984
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2860324038786842778}
|
||||
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!1 &7624042284818776979
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8910535925068494434}
|
||||
- component: {fileID: 2708762870899574912}
|
||||
- component: {fileID: 216941358938488416}
|
||||
m_Layer: 0
|
||||
m_Name: Menu
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &8910535925068494434
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7624042284818776979}
|
||||
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: 2860324038786842773}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &2708762870899574912
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7624042284818776979}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 3b29d45007c5493d926d2cd45a489529, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Control:
|
||||
name:
|
||||
icon: {fileID: 0}
|
||||
type: 103
|
||||
parameter:
|
||||
name:
|
||||
value: 1
|
||||
style: 0
|
||||
subMenu: {fileID: 0}
|
||||
subParameters: []
|
||||
labels: []
|
||||
MenuSource: 0
|
||||
menuSource_otherObjectChildren: {fileID: 0}
|
||||
isSynced: 1
|
||||
isSaved: 1
|
||||
--- !u!114 &216941358938488416
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7624042284818776979}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7ef83cb0c23d4d7c9d41021e544a1978, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
menuToAppend: {fileID: 0}
|
||||
installTargetMenu: {fileID: 0}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15cecd8ca5178eb40953a581734e61f6
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,12 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.core;
|
||||
using nadena.dev.modular_avatar.core.editor;
|
||||
using nadena.dev.modular_avatar.core.editor.menu;
|
||||
using nadena.dev.modular_avatar.core.menu;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEditor.VersionControl;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
using Object = UnityEngine.Object;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace modular_avatar_tests.VirtualMenuTests
|
||||
{
|
||||
@ -73,9 +79,9 @@ namespace modular_avatar_tests.VirtualMenuTests
|
||||
virtualMenu.FreezeMenu();
|
||||
|
||||
Assert.AreEqual(1, virtualMenu.ResolvedMenu.Count);
|
||||
var root = virtualMenu.ResolvedMenu[rootMenu];
|
||||
var root = virtualMenu.NodeForMenuAsset(rootMenu);
|
||||
Assert.AreEqual(2, root.Controls.Count);
|
||||
Assert.AreSame(rootMenu, root.NodeKey);
|
||||
Assert.AreSame(rootMenu, root.SourceMenu());
|
||||
AssertControlEquals(rootMenu.controls[0], root.Controls[0]);
|
||||
AssertControlEquals(rootMenu.controls[1], root.Controls[1]);
|
||||
}
|
||||
@ -106,22 +112,22 @@ namespace modular_avatar_tests.VirtualMenuTests
|
||||
virtualMenu.FreezeMenu();
|
||||
|
||||
Assert.AreEqual(3, virtualMenu.ResolvedMenu.Count);
|
||||
var rootNode = virtualMenu.ResolvedMenu[rootMenu];
|
||||
var sub1Node = virtualMenu.ResolvedMenu[sub1];
|
||||
var sub2Node = virtualMenu.ResolvedMenu[sub2];
|
||||
var rootNode = virtualMenu.ResolvedMenu[virtualMenu.RootMenuKey];
|
||||
var sub1Node = virtualMenu.NodeForMenuAsset(sub1);
|
||||
var sub2Node = virtualMenu.NodeForMenuAsset(sub2);
|
||||
|
||||
Assert.AreEqual(1, rootNode.Controls.Count);
|
||||
Assert.AreSame(rootMenu, rootNode.NodeKey);
|
||||
Assert.AreSame(virtualMenu.RootMenuKey, rootNode.NodeKey);
|
||||
Assert.AreSame(sub1Node, rootNode.Controls[0].SubmenuNode);
|
||||
Assert.IsNull(rootNode.Controls[0].subMenu);
|
||||
|
||||
Assert.AreEqual(1, sub1Node.Controls.Count);
|
||||
Assert.AreSame(sub1, sub1Node.NodeKey);
|
||||
Assert.AreSame(sub1, sub1Node.SourceMenu());
|
||||
Assert.AreSame(sub2Node, sub1Node.Controls[0].SubmenuNode);
|
||||
Assert.IsNull(sub1Node.Controls[0].subMenu);
|
||||
|
||||
Assert.AreEqual(1, sub2Node.Controls.Count);
|
||||
Assert.AreSame(sub2, sub2Node.NodeKey);
|
||||
Assert.AreSame(sub2, sub2Node.SourceMenu());
|
||||
Assert.AreSame(rootNode, sub2Node.Controls[0].SubmenuNode);
|
||||
Assert.IsNull(sub2Node.Controls[0].subMenu);
|
||||
}
|
||||
@ -243,10 +249,10 @@ namespace modular_avatar_tests.VirtualMenuTests
|
||||
|
||||
Assert.AreEqual(2, virtualMenu.ResolvedMenu.Count);
|
||||
var rootMenu = virtualMenu.ResolvedMenu[RootMenu.Instance];
|
||||
var subMenu = virtualMenu.ResolvedMenu[menu_b];
|
||||
var subMenu = virtualMenu.NodeForMenuAsset(menu_b);
|
||||
Assert.AreSame(subMenu, rootMenu.Controls[0].SubmenuNode);
|
||||
Assert.AreSame(RootMenu.Instance, rootMenu.NodeKey);
|
||||
Assert.AreSame(menu_b, subMenu.NodeKey);
|
||||
Assert.AreSame(menu_b, ((ValueTuple<object, object>) subMenu.NodeKey).Item1);
|
||||
Assert.AreEqual(1, subMenu.Controls.Count);
|
||||
AssertControlEquals(menu_b.controls[0], subMenu.Controls[0]);
|
||||
}
|
||||
@ -324,8 +330,7 @@ namespace modular_avatar_tests.VirtualMenuTests
|
||||
virtualMenu.FreezeMenu();
|
||||
|
||||
Assert.AreEqual(1, virtualMenu.ResolvedMenu.Count);
|
||||
var rootMenu = virtualMenu.ResolvedMenu[menu_a];
|
||||
var menu_a_node = virtualMenu.ResolvedMenu[menu_a];
|
||||
var rootMenu = virtualMenu.NodeForMenuAsset(menu_a);
|
||||
Assert.AreEqual(3, rootMenu.Controls.Count);
|
||||
}
|
||||
|
||||
@ -557,10 +562,113 @@ namespace modular_avatar_tests.VirtualMenuTests
|
||||
|
||||
virtualMenu.FreezeMenu();
|
||||
|
||||
var root = virtualMenu.ResolvedMenu[menu_a];
|
||||
var root = virtualMenu.NodeForMenuAsset(menu_a);
|
||||
Assert.AreEqual(1, root.Controls.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void multipleMenuAssets_areInstalledMultipleTimes()
|
||||
{
|
||||
var menu_a = Create<VRCExpressionsMenu>();
|
||||
menu_a.controls.Add(new VRCExpressionsMenu.Control()
|
||||
{
|
||||
name = "control",
|
||||
parameter = new VRCExpressionsMenu.Control.Parameter()
|
||||
{
|
||||
name = "p"
|
||||
},
|
||||
type = VRCExpressionsMenu.Control.ControlType.Toggle
|
||||
});
|
||||
|
||||
var av_root = CreateRoot("avatar");
|
||||
|
||||
var node_a = CreateInstaller("menu_a");
|
||||
node_a.transform.SetParent(av_root.transform);
|
||||
node_a.menuToAppend = menu_a;
|
||||
|
||||
var node_b = CreateInstaller("menu_b");
|
||||
node_b.transform.SetParent(av_root.transform);
|
||||
node_b.menuToAppend = menu_a;
|
||||
|
||||
var virtualMenu = VirtualMenu.ForAvatar(av_root.GetComponent<VRCAvatarDescriptor>());
|
||||
virtualMenu.FreezeMenu();
|
||||
Assert.AreEqual(2, virtualMenu.RootMenuNode.Controls.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void remapParams_isAppliedSeparatelyForEachDedup()
|
||||
{
|
||||
var menu_a = Create<VRCExpressionsMenu>();
|
||||
menu_a.controls.Add(new VRCExpressionsMenu.Control()
|
||||
{
|
||||
name = "control",
|
||||
parameter = new VRCExpressionsMenu.Control.Parameter()
|
||||
{
|
||||
name = "p"
|
||||
},
|
||||
type = VRCExpressionsMenu.Control.ControlType.Toggle
|
||||
});
|
||||
var menu_outer = Create<VRCExpressionsMenu>();
|
||||
menu_outer.controls.Add(new VRCExpressionsMenu.Control()
|
||||
{
|
||||
name = "control",
|
||||
type = VRCExpressionsMenu.Control.ControlType.SubMenu,
|
||||
subMenu = menu_a
|
||||
});
|
||||
|
||||
var av_root = CreateRoot("avatar");
|
||||
|
||||
var node_a = CreateInstaller("menu_a");
|
||||
node_a.transform.SetParent(av_root.transform);
|
||||
node_a.menuToAppend = menu_outer;
|
||||
node_a.gameObject.AddComponent<ModularAvatarParameters>().parameters = new List<ParameterConfig>()
|
||||
{
|
||||
new ParameterConfig()
|
||||
{
|
||||
nameOrPrefix = "p",
|
||||
remapTo = "a",
|
||||
}
|
||||
};
|
||||
|
||||
var node_b = CreateInstaller("menu_b");
|
||||
node_b.transform.SetParent(av_root.transform);
|
||||
node_b.menuToAppend = menu_outer;
|
||||
node_b.gameObject.AddComponent<ModularAvatarParameters>().parameters = new List<ParameterConfig>()
|
||||
{
|
||||
new ParameterConfig()
|
||||
{
|
||||
nameOrPrefix = "p",
|
||||
remapTo = "b",
|
||||
}
|
||||
};
|
||||
|
||||
var buildContext = new BuildContext(av_root.GetComponent<VRCAvatarDescriptor>());
|
||||
new RenameParametersHook().OnPreprocessAvatar(av_root, buildContext);
|
||||
|
||||
var virtualMenu = VirtualMenu.ForAvatar(av_root.GetComponent<VRCAvatarDescriptor>(), buildContext);
|
||||
virtualMenu.FreezeMenu();
|
||||
|
||||
Assert.IsTrue(virtualMenu.RootMenuNode.Controls.Any(c =>
|
||||
c.SubmenuNode.Controls[0].parameter.name == "a"
|
||||
));
|
||||
Assert.IsTrue(virtualMenu.RootMenuNode.Controls.Any(c =>
|
||||
c.SubmenuNode.Controls[0].parameter.name == "b"
|
||||
));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void internalParameterTest()
|
||||
{
|
||||
var root = CreatePrefab("InternalParameterTest.prefab");
|
||||
|
||||
BuildContext buildContext = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
|
||||
new RenameParametersHook().OnPreprocessAvatar(root, buildContext);
|
||||
var virtualMenu = VirtualMenu.ForAvatar(root.GetComponent<VRCAvatarDescriptor>(), buildContext);
|
||||
|
||||
Assert.AreNotEqual("x", virtualMenu.RootMenuNode.Controls[0]
|
||||
.SubmenuNode.Controls[0].parameter.name);
|
||||
}
|
||||
|
||||
ModularAvatarMenuInstaller CreateInstaller(string name)
|
||||
{
|
||||
GameObject obj = new GameObject();
|
||||
@ -640,4 +748,24 @@ namespace modular_avatar_tests.VirtualMenuTests
|
||||
Assert.AreEqual(expected.style, actual.style);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class TestHelpers
|
||||
{
|
||||
internal static VirtualMenuNode NodeForMenuAsset(this VirtualMenu menu, VRCExpressionsMenu asset)
|
||||
{
|
||||
return menu.ResolvedMenu.FirstOrDefault(
|
||||
kvp => kvp.Key is ValueTuple<object, object> tuple && ReferenceEquals(tuple.Item1, asset)
|
||||
).Value;
|
||||
}
|
||||
|
||||
internal static VRCExpressionsMenu SourceMenu(this VirtualMenuNode node)
|
||||
{
|
||||
if (node.NodeKey is ValueTuple<object, object> tuple && tuple.Item1 is VRCExpressionsMenu menu)
|
||||
{
|
||||
return menu;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -107,7 +107,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
public void DoGUI(VRCExpressionsMenu menu, GameObject parameterReference = null)
|
||||
{
|
||||
new VisitorContext(this).PushNode(menu);
|
||||
new VisitorContext(this).PushMenuContents(menu);
|
||||
}
|
||||
|
||||
private void PushGuiNode(object key, Func<Action> guiBuilder)
|
||||
@ -162,7 +162,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
});
|
||||
}
|
||||
|
||||
public void PushNode(VRCExpressionsMenu expMenu)
|
||||
public void PushMenuContents(VRCExpressionsMenu expMenu)
|
||||
{
|
||||
PushMenu(expMenu, null);
|
||||
}
|
||||
|
@ -87,13 +87,15 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
_currentPostprocessor = postprocessor;
|
||||
}
|
||||
|
||||
public void PushNode(VRCExpressionsMenu expMenu)
|
||||
public void PushMenuContents(VRCExpressionsMenu expMenu)
|
||||
{
|
||||
if (expMenu == null) return;
|
||||
if (_visited.Contains(expMenu)) return;
|
||||
_visited.Add(expMenu);
|
||||
_visitedMenu(expMenu);
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var control in expMenu.controls)
|
||||
{
|
||||
PushControl(control);
|
||||
@ -110,6 +112,13 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// We can visit the same expMenu multiple times, with different visit contexts (owing to having
|
||||
// different source installers, with different postprocessing configurations).
|
||||
_visited.Remove(expMenu);
|
||||
}
|
||||
}
|
||||
|
||||
public void PushNode(MenuSource source)
|
||||
{
|
||||
@ -118,6 +127,8 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
_visited.Add(source);
|
||||
|
||||
BuildReport.ReportingObject(source as UnityEngine.Object, () => source.Visit(this));
|
||||
|
||||
_visited.Remove(source);
|
||||
}
|
||||
|
||||
public void PushNode(ModularAvatarMenuInstaller installer)
|
||||
@ -127,6 +138,8 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
_visited.Add(installer);
|
||||
|
||||
BuildReport.ReportingObject(installer, () =>
|
||||
{
|
||||
using (new PostprocessorContext(this, _postProcessControls.GetValueOrDefault(installer)))
|
||||
{
|
||||
var menuSourceComp = installer.GetComponent<MenuSource>();
|
||||
if (menuSourceComp != null)
|
||||
@ -135,19 +148,25 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
}
|
||||
else if (installer.menuToAppend != null)
|
||||
{
|
||||
using (new PostprocessorContext(this, _postProcessControls.GetValueOrDefault(installer)))
|
||||
{
|
||||
PushNode(installer.menuToAppend);
|
||||
PushMenuContents(installer.menuToAppend);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_visited.Remove(installer);
|
||||
}
|
||||
|
||||
public void PushControl(VRCExpressionsMenu.Control control)
|
||||
{
|
||||
// XXX: When we invoke NodeFor on the subMenu, we need to ensure we dedup considering the parameter context
|
||||
// of the source control. This is because the same subMenu can be used in multiple places, with different
|
||||
// parameter replacements. (FIXME)
|
||||
var virtualControl = new VirtualControl(control);
|
||||
|
||||
if (control.subMenu != null)
|
||||
{
|
||||
virtualControl.SubmenuNode = NodeFor(control.subMenu);
|
||||
}
|
||||
|
||||
_currentPostprocessor(virtualControl);
|
||||
|
||||
@ -178,6 +197,8 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
*/
|
||||
internal class VirtualMenu
|
||||
{
|
||||
private static readonly Action<VRCExpressionsMenu.Control> NoopPostprocessor = control => { };
|
||||
|
||||
internal readonly object RootMenuKey;
|
||||
|
||||
private static long _cacheSeq = 0;
|
||||
@ -236,7 +257,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
|
||||
if (rootMenu != null)
|
||||
{
|
||||
RootMenuKey = rootMenu;
|
||||
RootMenuKey = (ValueTuple<object, object>) (rootMenu, NoopPostprocessor);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -335,22 +356,32 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
var rootContext =
|
||||
new NodeContextImpl(RootNode, NodeFor, menuToInstallerFiltered, _postprocessControlsHooks,
|
||||
m => _visitedMenus.Add(m),
|
||||
_control => { });
|
||||
if (RootMenuKey is VRCExpressionsMenu menu)
|
||||
NoopPostprocessor);
|
||||
if (RootMenuKey is ValueTuple<object, object> tuple && tuple.Item1 is VRCExpressionsMenu menu)
|
||||
{
|
||||
foreach (var control in menu.controls)
|
||||
{
|
||||
rootContext.PushControl(control);
|
||||
}
|
||||
}
|
||||
|
||||
if (menuToInstallerFiltered.TryGetValue(RootMenuKey, out var installers))
|
||||
// Some menu installers may be bound to the root menu _asset_ directly.
|
||||
if (menuToInstallerFiltered.TryGetValue(menu, out var installers))
|
||||
{
|
||||
foreach (var installer in installers)
|
||||
{
|
||||
rootContext.PushNode(installer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Untargeted installers are bound to the RootMenuKey, rather than the menu asset itself.
|
||||
if (menuToInstallerFiltered.TryGetValue(RootMenuKey, out var installers2))
|
||||
{
|
||||
foreach (var installer in installers2)
|
||||
{
|
||||
rootContext.PushNode(installer);
|
||||
}
|
||||
}
|
||||
|
||||
while (_pendingGeneration.Count > 0)
|
||||
{
|
||||
@ -361,9 +392,15 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
|
||||
VirtualMenuNode NodeFor(object key, Action<VRCExpressionsMenu.Control> postprocessContext)
|
||||
{
|
||||
if (_resolvedMenu.TryGetValue(key, out var node)) return node;
|
||||
node = new VirtualMenuNode(key);
|
||||
_resolvedMenu[key] = node;
|
||||
var lookupKey = key;
|
||||
if (key is VRCExpressionsMenu)
|
||||
{
|
||||
lookupKey = (ValueTuple<object, object>) (key, postprocessContext);
|
||||
}
|
||||
|
||||
if (_resolvedMenu.TryGetValue(lookupKey, out var node)) return node;
|
||||
node = new VirtualMenuNode(lookupKey);
|
||||
_resolvedMenu[lookupKey] = node;
|
||||
|
||||
_pendingGeneration.Enqueue(() =>
|
||||
{
|
||||
@ -375,7 +412,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
postprocessContext);
|
||||
if (key is VRCExpressionsMenu expMenu)
|
||||
{
|
||||
context.PushNode(expMenu);
|
||||
context.PushMenuContents(expMenu);
|
||||
}
|
||||
else if (key is MenuSource source)
|
||||
{
|
||||
|
@ -106,7 +106,7 @@ namespace nadena.dev.modular_avatar.core.menu
|
||||
/// installer invocations.
|
||||
/// </summary>
|
||||
/// <param name="expMenu"></param>
|
||||
void PushNode(VRCExpressionsMenu expMenu);
|
||||
void PushMenuContents(VRCExpressionsMenu expMenu);
|
||||
|
||||
/// <summary>
|
||||
/// Pushes the contents of this menu source onto the current menu node.
|
||||
|
Loading…
Reference in New Issue
Block a user