Skip to content

Commit

Permalink
fix(ma-store): appending menu items to previous added submenu
Browse files Browse the repository at this point in the history
  • Loading branch information
poi-vrc committed Apr 20, 2024
1 parent 4da8078 commit 3cb00a3
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 198 deletions.
9 changes: 9 additions & 0 deletions Editor/DKEditorUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Chocopoi.DressingFramework.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;

namespace Chocopoi.DressingFramework
Expand Down Expand Up @@ -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!");

Check warning on line 91 in Editor/DKEditorUtils.cs

View check run for this annotation

Codecov / codecov/patch

Editor/DKEditorUtils.cs#L90-L91

Added lines #L90 - L91 were not covered by tests
}
if (PrefabUtility.IsPartOfAnyPrefab(destGameObject))
{
throw new Exception("Report this to the DressingTools developer! Destination GameObject is part of a prefab!");

Check warning on line 95 in Editor/DKEditorUtils.cs

View check run for this annotation

Codecov / codecov/patch

Editor/DKEditorUtils.cs#L94-L95

Added lines #L94 - L95 were not covered by tests
}
System.Type type = originalComponent.GetType();

// get the destination component or add new
Expand Down
164 changes: 86 additions & 78 deletions Editor/Detail/DK/DKMAMenuStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -30,29 +33,51 @@ namespace Chocopoi.DressingFramework.Detail.DK
internal class DKMAMenuStore : MenuStore
{
private readonly Context _ctx;
private readonly Dictionary<string, MenuGroup> _buffer;
private readonly HashSet<VRCExpressionsMenu> _clonedVrcMenus;
private readonly MenuGroup _buffer;
private readonly Dictionary<VRCExpressionsMenu, Tuple<string, MenuGroup>> _vrcMenuAppends;

public DKMAMenuStore(Context ctx)
{
_ctx = ctx;
_buffer = new Dictionary<string, MenuGroup>();
_clonedVrcMenus = new HashSet<VRCExpressionsMenu>();
_buffer = new MenuGroup();
_vrcMenuAppends = new Dictionary<VRCExpressionsMenu, Tuple<string, MenuGroup>>();
}

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<string, MenuGroup>(newPath, new MenuGroup());
}
return menuGroup.Item2;

Check warning on line 67 in Editor/Detail/DK/DKMAMenuStore.cs

View check run for this annotation

Codecov / codecov/patch

Editor/Detail/DK/DKMAMenuStore.cs#L53-L67

Added lines #L53 - L67 were not covered by tests
}
return null;
});

Check warning on line 70 in Editor/Detail/DK/DKMAMenuStore.cs

View check run for this annotation

Codecov / codecov/patch

Editor/Detail/DK/DKMAMenuStore.cs#L69-L70

Added lines #L69 - L70 were not covered by tests
}
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)
Expand All @@ -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)
{
Expand All @@ -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}");

Check warning on line 117 in Editor/Detail/DK/DKMAMenuStore.cs

View check run for this annotation

Codecov / codecov/patch

Editor/Detail/DK/DKMAMenuStore.cs#L117

Added line #L117 was not covered by tests
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<ModularAvatarMenuInstaller>();
maInstaller.installTargetMenu = vrcMenu;
var maGroup = menuObj.AddComponent<ModularAvatarMenuGroup>();
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<ModularAvatarMenuItem>();

Expand All @@ -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)
Expand All @@ -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);
}
}

Expand All @@ -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<ModularAvatarMenuInstaller>();
rootMaInstaller.installTargetMenu = avatarDesc.expressionsMenu;
var rootMaGroup = rootMenuObj.AddComponent<ModularAvatarMenuGroup>();
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);

Check warning on line 207 in Editor/Detail/DK/DKMAMenuStore.cs

View check run for this annotation

Codecov / codecov/patch

Editor/Detail/DK/DKMAMenuStore.cs#L206-L207

Added lines #L206 - L207 were not covered by tests
menuObj.transform.SetParent(dkMaRootObj.transform);

// add installer
var maInstaller = menuObj.AddComponent<ModularAvatarMenuInstaller>();
maInstaller.installTargetMenu = installTarget;

// add menu group
maInstaller.installTargetMenu = kvp.Key;

Check warning on line 210 in Editor/Detail/DK/DKMAMenuStore.cs

View check run for this annotation

Codecov / codecov/patch

Editor/Detail/DK/DKMAMenuStore.cs#L210

Added line #L210 was not covered by tests
var maGroup = menuObj.AddComponent<ModularAvatarMenuGroup>();
maGroup.targetObject = menuObj;

// add menu items
DKGroupToMAItems(menuObj, items);
DKGroupToMAItems(dkMaRootObj.transform, avatarDesc.expressionsMenu, menuObj.transform, kvp.Value.Item2, kvp.Value.Item1);

Check warning on line 213 in Editor/Detail/DK/DKMAMenuStore.cs

View check run for this annotation

Codecov / codecov/patch

Editor/Detail/DK/DKMAMenuStore.cs#L213

Added line #L213 was not covered by tests
}

// normal appends
DKGroupToMAItems(dkMaRootObj.transform, avatarDesc.expressionsMenu, rootMenuObj.transform, _buffer, "");
}

internal override void OnEnable() { }
Expand Down
84 changes: 4 additions & 80 deletions Editor/Menu/MenuRepositoryStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Loading

0 comments on commit 3cb00a3

Please sign in to comment.