diff --git a/Editor/DKEditorUtils.cs b/Editor/DKEditorUtils.cs index ce662fe..4e10f24 100644 --- a/Editor/DKEditorUtils.cs +++ b/Editor/DKEditorUtils.cs @@ -17,6 +17,7 @@ using Chocopoi.DressingFramework.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using UnityEditor; using UnityEngine; namespace Chocopoi.DressingFramework @@ -85,6 +86,14 @@ public static string RandomString(int length) // referenced from: http://answers.unity3d.com/questions/458207/copy-a-component-at-runtime.html public static Component CopyComponent(Component originalComponent, GameObject destGameObject) { + if (destGameObject.scene == null) + { + throw new Exception("Report this to the DressingTools developer! Destination GameObject does not contain a scene!"); + } + if (PrefabUtility.IsPartOfAnyPrefab(destGameObject)) + { + throw new Exception("Report this to the DressingTools developer! Destination GameObject is part of a prefab!"); + } System.Type type = originalComponent.GetType(); // get the destination component or add new diff --git a/Editor/Detail/DK/DKMAMenuStore.cs b/Editor/Detail/DK/DKMAMenuStore.cs index c2e5104..a717b3c 100644 --- a/Editor/Detail/DK/DKMAMenuStore.cs +++ b/Editor/Detail/DK/DKMAMenuStore.cs @@ -11,13 +11,16 @@ */ #if DK_MA && DK_VRCSDK3A +using System; using System.Collections.Generic; +using System.Linq; using Chocopoi.DressingFramework.Menu; using Chocopoi.DressingFramework.Menu.VRChat; using nadena.dev.modular_avatar.core; using UnityEngine; using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.ScriptableObjects; +using Object = UnityEngine.Object; namespace Chocopoi.DressingFramework.Detail.DK { @@ -30,29 +33,51 @@ namespace Chocopoi.DressingFramework.Detail.DK internal class DKMAMenuStore : MenuStore { private readonly Context _ctx; - private readonly Dictionary _buffer; - private readonly HashSet _clonedVrcMenus; + private readonly MenuGroup _buffer; + private readonly Dictionary> _vrcMenuAppends; public DKMAMenuStore(Context ctx) { _ctx = ctx; - _buffer = new Dictionary(); - _clonedVrcMenus = new HashSet(); + _buffer = new MenuGroup(); + _vrcMenuAppends = new Dictionary>(); } - public override void Append(MenuItem menuItem, string path = null) + public IMenuRepository FindMAInstallTarget(IMenuRepository rootMenu, string path) { - if (path == null) + var installTarget = rootMenu; + if (!string.IsNullOrEmpty(path)) { - path = ""; + var paths = path.Trim().Split('/'); + installTarget = MenuUtils.GenericFindInstallTarget(rootMenu, paths, 0, (item, idx) => + { + if (item is VRCSubMenuItem vrcSubMenuItem) + { + var newPath = $"{path}/{item.Name}"; + if (vrcSubMenuItem.SubMenu == null) + { + var newVrcMenu = Object.Instantiate(VRCMenuUtils.GetDefaultExpressionsMenu()); + _ctx.CreateUniqueAsset(newVrcMenu, newPath.Replace('/', '_')); + vrcSubMenuItem.SubMenu = newVrcMenu; + } + if (!_vrcMenuAppends.TryGetValue(vrcSubMenuItem.SubMenu, out var menuGroup)) + { + menuGroup = _vrcMenuAppends[vrcSubMenuItem.SubMenu] = new Tuple(newPath, new MenuGroup()); + } + return menuGroup.Item2; + } + return null; + }); } - path = path.Trim(); + return installTarget; + } - if (!_buffer.TryGetValue(path, out var menuItems)) - { - menuItems = _buffer[path] = new MenuGroup(); - } - menuItems.Add(menuItem); + public override void Append(MenuItem menuItem, string path = null) + { + // append the item to our buffer + // in cases the path falls on a VRC menu, we install using MA installer instead + var target = FindMAInstallTarget(_buffer, path); + target.Add(menuItem); } private static VRCExpressionsMenu.Control MakeSubMenuControl(string name, Texture2D icon, VRCExpressionsMenu subMenu) @@ -70,19 +95,7 @@ private static VRCExpressionsMenu.Control MakeSubMenuControl(string name, Textur }; } - private VRCExpressionsMenu MakeDownwardsMenuGroups(string[] paths, int index) - { - var menu = Object.Instantiate(VRCMenuUtils.GetDefaultExpressionsMenu()); - _ctx.CreateUniqueAsset(menu, string.Join("_", paths, 0, index)); - if (index < paths.Length) - { - var newMenuItem = MakeSubMenuControl(paths[index], null, MakeDownwardsMenuGroups(paths, index + 1)); - menu.controls.Add(newMenuItem); - } - return menu; - } - - private VRCExpressionsMenu FindInstallTarget(VRCExpressionsMenu parent, string[] paths, int index) + private VRCExpressionsMenu FindExistingVRCMenu(VRCExpressionsMenu parent, string[] paths, int index) { if (index >= paths.Length) { @@ -101,36 +114,44 @@ private VRCExpressionsMenu FindInstallTarget(VRCExpressionsMenu parent, string[] if (item.subMenu == null) { var newVrcMenu = Object.Instantiate(VRCMenuUtils.GetDefaultExpressionsMenu()); - _ctx.CreateUniqueAsset(newVrcMenu, string.Join("_", paths, 0, index + 1)); + _ctx.CreateUniqueAsset(newVrcMenu, $"{string.Join("_", paths, 0, index + 1)}_{item.name}"); item.subMenu = newVrcMenu; - - _clonedVrcMenus.Add(item.subMenu); } - else if (!_clonedVrcMenus.Contains(item.subMenu)) - { - var menuCopy = Object.Instantiate(item.subMenu); - _ctx.CreateUniqueAsset(menuCopy, string.Join("_", paths, 0, index + 1)); - item.subMenu = menuCopy; - - _clonedVrcMenus.Add(item.subMenu); - } - - return FindInstallTarget(item.subMenu, paths, index + 1); + return FindExistingVRCMenu(item.subMenu, paths, index + 1); } } - // if not found, we create empty menu groups recursively downwards - var newMenuItem = MakeSubMenuControl(paths[index], null, MakeDownwardsMenuGroups(paths, index + 1)); - parent.controls.Add(newMenuItem); - - // find again - return FindInstallTarget(parent, paths, index); + // not found + return null; } - private static void DKToMAMenuItem(GameObject parent, MenuItem menuItem) + private void DKToMAMenuItem(Transform dkMaRoot, VRCExpressionsMenu avatarRootMenu, Transform parent, MenuItem menuItem, string absolutePathPrefix) { + var newPath = string.IsNullOrEmpty(absolutePathPrefix) ? + menuItem.Name : + $"{absolutePathPrefix}/{menuItem.Name}"; + + // prefer using MA installer on existing menus for supporting path install + if (menuItem is SubMenuItem preCheckSubMenuItem) + { + var newPaths = newPath.Split('/'); + var vrcMenu = FindExistingVRCMenu(avatarRootMenu, newPaths, 0); + if (vrcMenu != null) + { + // make a ma installer to that menu + var menuObj = new GameObject(string.Join("_", newPaths)); + menuObj.transform.SetParent(dkMaRoot); + var maInstaller = menuObj.AddComponent(); + maInstaller.installTargetMenu = vrcMenu; + var maGroup = menuObj.AddComponent(); + maGroup.targetObject = menuObj; + DKGroupToMAItems(dkMaRoot, avatarRootMenu, menuObj.transform, preCheckSubMenuItem.SubMenu, newPath); + return; + } + } + var maItemObj = new GameObject(menuItem.Name); - maItemObj.transform.SetParent(parent.transform); + maItemObj.transform.SetParent(parent); var maItem = maItemObj.AddComponent(); @@ -140,7 +161,7 @@ private static void DKToMAMenuItem(GameObject parent, MenuItem menuItem) maItem.MenuSource = SubmenuSource.Children; if (subMenuItem.SubMenu != null) { - DKGroupToMAItems(maItemObj, subMenuItem.SubMenu); + DKGroupToMAItems(dkMaRoot, avatarRootMenu, maItemObj.transform, subMenuItem.SubMenu, newPath); } } else if (menuItem is VRCSubMenuItem vrcSubMenuItem) @@ -154,11 +175,11 @@ private static void DKToMAMenuItem(GameObject parent, MenuItem menuItem) } } - private static void DKGroupToMAItems(GameObject parent, MenuGroup menuGroup) + private void DKGroupToMAItems(Transform dkMaRoot, VRCExpressionsMenu avatarRootMenu, Transform parent, MenuGroup menuGroup, string absolutePath) { foreach (var item in menuGroup) { - DKToMAMenuItem(parent, item); + DKToMAMenuItem(dkMaRoot, avatarRootMenu, parent, item, absolutePath); } } @@ -173,40 +194,27 @@ public override void Flush() var dkMaRootObj = new GameObject("DKMAMenu"); dkMaRootObj.transform.SetParent(_ctx.AvatarGameObject.transform); - foreach (var kvp in _buffer) - { - var path = kvp.Key; - var items = kvp.Value; - - // find and create the install target and pass to MA - string menuObjName; - VRCExpressionsMenu installTarget; - if (string.IsNullOrEmpty(path)) - { - menuObjName = "Root"; - installTarget = avatarDesc.expressionsMenu; - } - else - { - var paths = path.Trim().Split('/'); - menuObjName = string.Join("_", paths); - installTarget = FindInstallTarget(avatarDesc.expressionsMenu, paths, 0); - } + var rootMenuObj = new GameObject("Root"); + rootMenuObj.transform.SetParent(dkMaRootObj.transform); + var rootMaInstaller = rootMenuObj.AddComponent(); + rootMaInstaller.installTargetMenu = avatarDesc.expressionsMenu; + var rootMaGroup = rootMenuObj.AddComponent(); + rootMaGroup.targetObject = rootMenuObj; - var menuObj = new GameObject(menuObjName); + // for paths pointing to existing vrc menus, we use the ma installer + foreach (var kvp in _vrcMenuAppends) + { + var menuObj = new GameObject(kvp.Key.name); menuObj.transform.SetParent(dkMaRootObj.transform); - - // add installer var maInstaller = menuObj.AddComponent(); - maInstaller.installTargetMenu = installTarget; - - // add menu group + maInstaller.installTargetMenu = kvp.Key; var maGroup = menuObj.AddComponent(); maGroup.targetObject = menuObj; - - // add menu items - DKGroupToMAItems(menuObj, items); + DKGroupToMAItems(dkMaRootObj.transform, avatarDesc.expressionsMenu, menuObj.transform, kvp.Value.Item2, kvp.Value.Item1); } + + // normal appends + DKGroupToMAItems(dkMaRootObj.transform, avatarDesc.expressionsMenu, rootMenuObj.transform, _buffer, ""); } internal override void OnEnable() { } diff --git a/Editor/Menu/MenuRepositoryStore.cs b/Editor/Menu/MenuRepositoryStore.cs index f304753..9a5d3fd 100644 --- a/Editor/Menu/MenuRepositoryStore.cs +++ b/Editor/Menu/MenuRepositoryStore.cs @@ -47,89 +47,13 @@ public override void Append(MenuItem menuItem, string path = null) _buffer[menuItem] = path; } - private IMenuRepository FindInstallTarget(IMenuRepository parent, string[] paths, int index) + private void InstallMenuItem(IMenuRepository rootMenu, MenuItem item, string path) { - if (index >= paths.Length) - { - return parent; - } - - foreach (var item in parent) - { - if (item.Name != paths[index]) - { - continue; - } - - if (item is SubMenuItem subMenuItem) - { - if (subMenuItem.SubMenu == null) - { - subMenuItem.SubMenu = new MenuGroup(); - } - return FindInstallTarget(subMenuItem.SubMenu, paths, index + 1); - } #if DK_VRCSDK3A - else if (item is VRCSubMenuItem vrcSubMenuItem) - { - if (vrcSubMenuItem.SubMenu == null) - { - var newVrcMenu = Object.Instantiate(VRCMenuUtils.GetDefaultExpressionsMenu()); - _ctx.CreateUniqueAsset(newVrcMenu, string.Join("_", paths, 0, index + 1)); - vrcSubMenuItem.SubMenu = newVrcMenu; - - _clonedVrcMenus.Add(vrcSubMenuItem.SubMenu); - } - else if (!_clonedVrcMenus.Contains(vrcSubMenuItem.SubMenu)) - { - var menuCopy = Object.Instantiate(vrcSubMenuItem.SubMenu); - _ctx.CreateUniqueAsset(menuCopy, string.Join("_", paths, 0, index + 1)); - vrcSubMenuItem.SubMenu = menuCopy; - _clonedVrcMenus.Add(vrcSubMenuItem.SubMenu); - } - - return FindInstallTarget(new VRCMenuWrapper(vrcSubMenuItem.SubMenu, _ctx), paths, index + 1); - } + var installTarget = VRCMenuUtils.FindInstallTarget(rootMenu, path, _ctx, _clonedVrcMenus); +#else + var installTarget = MenuUtils.FindInstallTarget(rootMenu, path); #endif - } - - // if not found, we create empty menu groups recursively downwards - var newMenuItem = new SubMenuItem() - { - Name = paths[index], - Icon = null, - SubMenu = MakeDownwardsMenuGroups(paths, index + 1) - }; - parent.Add(newMenuItem); - - // find again, the menu group pointers above cannot be used after the CRUD operation - return FindInstallTarget(parent, paths, index); - } - - private MenuGroup MakeDownwardsMenuGroups(string[] paths, int index) - { - var mg = new MenuGroup(); - if (index < paths.Length) - { - var newMenuItem = new SubMenuItem() - { - Name = paths[index], - Icon = null, - SubMenu = MakeDownwardsMenuGroups(paths, index + 1) - }; - mg.Add(newMenuItem); - } - return mg; - } - - private void InstallMenuItem(IMenuRepository rootMenu, MenuItem item, string path) - { - var installTarget = rootMenu; - if (!string.IsNullOrEmpty(path)) - { - var paths = path.Trim().Split('/'); - installTarget = FindInstallTarget(rootMenu, paths, 0); - } installTarget.Add(item); } diff --git a/Editor/Menu/MenuUtils.cs b/Editor/Menu/MenuUtils.cs new file mode 100644 index 0000000..3a5ad56 --- /dev/null +++ b/Editor/Menu/MenuUtils.cs @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024 chocopoi + * + * This file is part of DressingFramework. + * + * DressingFramework is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * DressingFramework is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see . + */ + +using System; + +namespace Chocopoi.DressingFramework.Menu +{ + internal static class MenuUtils + { + private static MenuGroup MakeDownwardsMenuGroups(string[] paths, int index) + { + var mg = new MenuGroup(); + if (index < paths.Length) + { + var newMenuItem = new SubMenuItem() + { + Name = paths[index], + Icon = null, + SubMenu = MakeDownwardsMenuGroups(paths, index + 1) + }; + mg.Add(newMenuItem); + } + return mg; + } + + internal static IMenuRepository GenericFindInstallTarget(IMenuRepository parent, string[] paths, int index, Func extraFindFunc = null) + { + if (index >= paths.Length) + { + return parent; + } + + foreach (var item in parent) + { + if (item.Name != paths[index]) + { + continue; + } + + if (item is SubMenuItem subMenuItem) + { + if (subMenuItem.SubMenu == null) + { + subMenuItem.SubMenu = new MenuGroup(); + } + return GenericFindInstallTarget(subMenuItem.SubMenu, paths, index + 1, extraFindFunc); + } + else if (extraFindFunc != null) + { + var repo = extraFindFunc(item, index); + if (repo != null) + { + return repo; + } + } + } + + // if not found, we create empty menu groups recursively downwards + var newMenuItem = new SubMenuItem() + { + Name = paths[index], + Icon = null, + SubMenu = MakeDownwardsMenuGroups(paths, index + 1) + }; + parent.Add(newMenuItem); + + // find again, the menu group pointers above cannot be used after the CRUD operation + return GenericFindInstallTarget(parent, paths, index, extraFindFunc); + } + + public static IMenuRepository FindInstallTarget(IMenuRepository rootMenu, string path) + { + var installTarget = rootMenu; + if (!string.IsNullOrEmpty(path)) + { + var paths = path.Trim().Split('/'); + installTarget = GenericFindInstallTarget(rootMenu, paths, 0); + } + return installTarget; + } + } +} diff --git a/Editor/Menu/MenuUtils.cs.meta b/Editor/Menu/MenuUtils.cs.meta new file mode 100644 index 0000000..ef5bbbd --- /dev/null +++ b/Editor/Menu/MenuUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3f03ac9fc8a59ba42837769c33ed8a6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Menu/VRChat/VRCMenuUtils.cs b/Editor/Menu/VRChat/VRCMenuUtils.cs index 7768064..3687590 100644 --- a/Editor/Menu/VRChat/VRCMenuUtils.cs +++ b/Editor/Menu/VRChat/VRCMenuUtils.cs @@ -14,13 +14,54 @@ using System; using System.Collections.Generic; using UnityEditor; +using UnityEngine; using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.ScriptableObjects; +using Object = UnityEngine.Object; namespace Chocopoi.DressingFramework.Menu.VRChat { internal static class VRCMenuUtils { + private static IMenuRepository ProcessVRCMenuItem(MenuItem item, int index, Context ctx, string[] paths, HashSet clonedVrcMenus) + { + if (item is VRCSubMenuItem vrcSubMenuItem) + { + if (vrcSubMenuItem.SubMenu == null) + { + var newVrcMenu = Object.Instantiate(GetDefaultExpressionsMenu()); + ctx.CreateUniqueAsset(newVrcMenu, string.Join("_", paths, 0, index + 1)); + vrcSubMenuItem.SubMenu = newVrcMenu; + + clonedVrcMenus.Add(vrcSubMenuItem.SubMenu); + } + else if (!clonedVrcMenus.Contains(vrcSubMenuItem.SubMenu)) + { + var menuCopy = Object.Instantiate(vrcSubMenuItem.SubMenu); + ctx.CreateUniqueAsset(menuCopy, string.Join("_", paths, 0, index + 1)); + vrcSubMenuItem.SubMenu = menuCopy; + clonedVrcMenus.Add(vrcSubMenuItem.SubMenu); + } + + return MenuUtils.GenericFindInstallTarget(new VRCMenuWrapper(vrcSubMenuItem.SubMenu, ctx), paths, index + 1, (anotherItem, anotherIndex) => + ProcessVRCMenuItem(anotherItem, anotherIndex, ctx, paths, clonedVrcMenus)); + } + return null; + + } + + public static IMenuRepository FindInstallTarget(IMenuRepository rootMenu, string path, Context ctx, HashSet clonedVrcMenus) + { + var installTarget = rootMenu; + if (!string.IsNullOrEmpty(path)) + { + var paths = path.Trim().Split('/'); + installTarget = MenuUtils.GenericFindInstallTarget(rootMenu, paths, 0, (anotherItem, anotherIndex) => + ProcessVRCMenuItem(anotherItem, anotherIndex, ctx, paths, clonedVrcMenus)); + } + return installTarget; + } + public static VRCExpressionParameters GetDefaultExpressionsParameters() { var expressionParameters = AssetDatabase.LoadAssetAtPath("Packages/com.vrchat.avatars/Samples/AV3 Demo Assets/Expressions Menu/DefaultExpressionParameters.asset"); diff --git a/Runtime/Detail/DK/Logging/DKReport.cs b/Runtime/Detail/DK/Logging/DKReport.cs index 381b38a..dfe7daa 100644 --- a/Runtime/Detail/DK/Logging/DKReport.cs +++ b/Runtime/Detail/DK/Logging/DKReport.cs @@ -21,6 +21,8 @@ using Chocopoi.DressingFramework.Logging; using Chocopoi.DressingFramework.Serialization; using Newtonsoft.Json; +using UnityEngine; +using LogType = Chocopoi.DressingFramework.Logging.LogType; namespace Chocopoi.DressingFramework.Detail.DK.Logging { diff --git a/Tests~/Editor/Details/DK/DKMAMenuStoreTest.cs b/Tests~/Editor/Details/DK/DKMAMenuStoreTest.cs index a9d20fb..1a23085 100644 --- a/Tests~/Editor/Details/DK/DKMAMenuStoreTest.cs +++ b/Tests~/Editor/Details/DK/DKMAMenuStoreTest.cs @@ -87,25 +87,42 @@ private static VRCExpressionsMenu FindMenuThroughPath(VRCExpressionsMenu menu, s } } - private static void AssertMAItems(GameObject avatar, VRCExpressionsMenu rootMenu, MenuItem dkItem1, MenuItem dkItem2, MenuItem dkItem3) + [Test] + public void InstallToPathNoExistingSubMenuTest() { - var vrcMenu1 = FindMenuThroughPath(rootMenu, new string[] { "A" }, 0); - var vrcMenu2 = FindMenuThroughPath(rootMenu, new string[] { "B", "C" }, 0); - var vrcMenu3 = FindMenuThroughPath(rootMenu, new string[] { "C", "D", "E" }, 0); + var avatar = CreateGameObject("Avatar"); + var avatarDesc = avatar.AddComponent(); + var rootMenu = ScriptableObject.CreateInstance(); + avatarDesc.expressionsMenu = rootMenu; - var menu1 = avatar.transform.Find("DKMAMenu/A"); + var ctx = new DKNativeContext(avatar); + var store = new DKMAMenuStore(ctx); + + var dkItem1 = new ToggleItem() { Name = "1" }; + store.Append(dkItem1, "A"); + + var dkItem2 = new ToggleItem() { Name = "2" }; + store.Append(dkItem2, "B/C"); + + var dkItem3 = new ToggleItem() { Name = "3" }; + store.Append(dkItem3, "C/D/E"); + + store.Flush(); + + var menu1 = avatar.transform.Find("DKMAMenu/Root/A"); Assert.NotNull(menu1); - var menu2 = avatar.transform.Find("DKMAMenu/B_C"); + var menu2 = avatar.transform.Find("DKMAMenu/Root/B/C"); Assert.NotNull(menu2); - var menu3 = avatar.transform.Find("DKMAMenu/C_D_E"); + var menu3 = avatar.transform.Find("DKMAMenu/Root/C/D/E"); Assert.NotNull(menu3); - Assert.True(menu1.TryGetComponent(out var installer1)); - Assert.AreEqual(vrcMenu1, installer1.installTargetMenu); - Assert.True(menu2.TryGetComponent(out var installer2)); - Assert.AreEqual(vrcMenu2, installer2.installTargetMenu); - Assert.True(menu3.TryGetComponent(out var installer3)); - Assert.AreEqual(vrcMenu3, installer3.installTargetMenu); + // they will be installed to the root instead + Assert.True(menu1.TryGetComponent(out var item1)); + Assert.AreEqual(VRCExpressionsMenu.Control.ControlType.SubMenu, item1.Control.type); + Assert.True(menu2.TryGetComponent(out var item2)); + Assert.AreEqual(VRCExpressionsMenu.Control.ControlType.SubMenu, item2.Control.type); + Assert.True(menu3.TryGetComponent(out var item3)); + Assert.AreEqual(VRCExpressionsMenu.Control.ControlType.SubMenu, item3.Control.type); Assert.AreEqual(1, menu1.childCount); Assert.True(menu1.GetChild(0).TryGetComponent(out var maItem1)); @@ -126,31 +143,6 @@ private static void AssertMAItems(GameObject avatar, VRCExpressionsMenu rootMenu Assert.AreEqual(VRCExpressionsMenu.Control.ControlType.Toggle, maItem3.Control.type); } - [Test] - public void InstallToPathNoExistingSubMenuTest() - { - var avatar = CreateGameObject("Avatar"); - var avatarDesc = avatar.AddComponent(); - var rootMenu = ScriptableObject.CreateInstance(); - avatarDesc.expressionsMenu = rootMenu; - - var ctx = new DKNativeContext(avatar); - var store = new DKMAMenuStore(ctx); - - var dkItem1 = new ToggleItem() { Name = "1" }; - store.Append(dkItem1, "A"); - - var dkItem2 = new ToggleItem() { Name = "2" }; - store.Append(dkItem2, "B/C"); - - var dkItem3 = new ToggleItem() { Name = "3" }; - store.Append(dkItem3, "C/D/E"); - - store.Flush(); - - AssertMAItems(avatar, rootMenu, dkItem1, dkItem2, dkItem3); - } - private static void AddMenusThroughPath(VRCExpressionsMenu menu, string[] paths, int index) { if (index < paths.Length) @@ -166,6 +158,21 @@ private static void AddMenusThroughPath(VRCExpressionsMenu menu, string[] paths, } } + private static void PrintHierarchy(Transform root, int hyphens) + { + for (var i = 0; i < root.childCount; i++) + { + var trans = root.GetChild(i); + var pre = ""; + for (var j = 0; j < hyphens; j++) + { + pre += "-"; + } + Debug.Log(pre + trans.name); + PrintHierarchy(trans, hyphens + 1); + } + } + [Test] public void InstallToPathExistingSubMenuTest() { @@ -191,9 +198,44 @@ public void InstallToPathExistingSubMenuTest() store.Append(dkItem3, "C/D/E"); store.Flush(); + PrintHierarchy(avatar.transform, 0); // the store will clone a copy of it, so the original cannot be used for asserts - AssertMAItems(avatar, rootMenu, dkItem1, dkItem2, dkItem3); + var vrcMenu1 = FindMenuThroughPath(rootMenu, new string[] { "A" }, 0); + var vrcMenu2 = FindMenuThroughPath(rootMenu, new string[] { "B", "C" }, 0); + var vrcMenu3 = FindMenuThroughPath(rootMenu, new string[] { "C", "D", "E" }, 0); + + var menu1 = avatar.transform.Find("DKMAMenu/A"); + Assert.NotNull(menu1); + var menu2 = avatar.transform.Find("DKMAMenu/B_C"); + Assert.NotNull(menu2); + var menu3 = avatar.transform.Find("DKMAMenu/C_D_E"); + Assert.NotNull(menu3); + + Assert.True(menu1.TryGetComponent(out var installer1)); + Assert.AreEqual(vrcMenu1, installer1.installTargetMenu); + Assert.True(menu2.TryGetComponent(out var installer2)); + Assert.AreEqual(vrcMenu2, installer2.installTargetMenu); + Assert.True(menu3.TryGetComponent(out var installer3)); + Assert.AreEqual(vrcMenu3, installer3.installTargetMenu); + + Assert.AreEqual(1, menu1.childCount); + Assert.True(menu1.GetChild(0).TryGetComponent(out var maItem1)); + Assert.NotNull(maItem1.Control); + Assert.AreEqual(dkItem1.Name, maItem1.Control.name); + Assert.AreEqual(VRCExpressionsMenu.Control.ControlType.Toggle, maItem1.Control.type); + + Assert.AreEqual(1, menu2.childCount); + Assert.True(menu2.GetChild(0).TryGetComponent(out var maItem2)); + Assert.NotNull(maItem2.Control); + Assert.AreEqual(dkItem2.Name, maItem2.Control.name); + Assert.AreEqual(VRCExpressionsMenu.Control.ControlType.Toggle, maItem2.Control.type); + + Assert.AreEqual(1, menu3.childCount); + Assert.True(menu3.GetChild(0).TryGetComponent(out var maItem3)); + Assert.NotNull(maItem3.Control); + Assert.AreEqual(dkItem3.Name, maItem3.Control.name); + Assert.AreEqual(VRCExpressionsMenu.Control.ControlType.Toggle, maItem3.Control.type); } } } diff --git a/package.json b/package.json index 4cef2b3..9146f3f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.chocopoi.vrc.dressingframework", "displayName": "DressingFramework", - "version": "2.1.0", + "version": "2.1.1", "unity": "2019.4", "description": "A framework that assembles DressingTools and provides interfaces for third-party developers.", "author": {