209 lines
6.7 KiB
C#
Raw Normal View History

2022-12-11 22:08:41 +09:00
using System.Collections.Generic;
2022-12-12 18:19:50 +09:00
using System.Collections.Immutable;
2022-12-11 22:08:41 +09:00
using System.Linq;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using static VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control;
2022-12-17 16:51:34 +09:00
namespace nadena.dev.modular_avatar.core.editor
{
2022-12-18 18:55:27 +09:00
internal class MenuTree
2022-12-17 16:51:34 +09:00
{
2022-12-17 16:51:34 +09:00
public struct ChildElement
{
2022-12-18 19:03:13 +09:00
/// <summary>
/// Parent menu control name
/// </summary>
public string menuName;
public VRCExpressionsMenu menu;
2022-12-17 00:06:34 +09:00
public VRCExpressionsMenu parent;
2022-12-18 19:03:13 +09:00
/// <summary>
/// Installer to install this menu. Is null if the this menu is not installed by the installer.
/// </summary>
public ModularAvatarMenuInstaller installer;
2022-12-18 19:03:13 +09:00
/// <summary>
/// Whether the this submenu is added directly by the installer
/// </summary>
2022-12-17 00:06:34 +09:00
public bool isInstallerRoot;
}
2022-12-11 22:08:41 +09:00
private readonly HashSet<VRCExpressionsMenu> _included;
2022-12-17 00:06:34 +09:00
private readonly VRCExpressionsMenu _rootMenu;
2022-12-18 19:03:13 +09:00
/// <summary>
/// Map to link child menus from parent menu
/// </summary>
private readonly Dictionary<VRCExpressionsMenu, ImmutableList<ChildElement>> _menuChildrenMap;
2022-12-12 18:19:50 +09:00
2022-12-17 16:51:34 +09:00
public MenuTree(VRCAvatarDescriptor descriptor)
{
2022-12-18 17:01:46 +09:00
_rootMenu = descriptor.expressionsMenu;
_included = new HashSet<VRCExpressionsMenu>();
_menuChildrenMap = new Dictionary<VRCExpressionsMenu, ImmutableList<ChildElement>>();
2022-12-11 22:08:41 +09:00
2022-12-18 17:01:46 +09:00
if (_rootMenu == null)
2022-12-17 16:51:34 +09:00
{
2022-12-18 19:03:13 +09:00
// If the route menu is null, create a temporary menu indicating the route
2022-12-18 17:01:46 +09:00
_rootMenu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
2022-12-11 22:08:41 +09:00
}
2022-12-18 17:01:46 +09:00
_included.Add(_rootMenu);
2022-12-11 22:08:41 +09:00
}
2022-12-18 17:01:46 +09:00
public void TraverseAvatarMenu()
2022-12-17 16:51:34 +09:00
{
2022-12-18 17:01:46 +09:00
if (_rootMenu == null) return;
TraverseMenu(_rootMenu);
2022-12-11 22:08:41 +09:00
}
2022-12-18 17:01:46 +09:00
public void TraverseMenuInstaller(ModularAvatarMenuInstaller installer)
2022-12-17 16:51:34 +09:00
{
if (!installer.enabled) return;
2022-12-11 22:08:41 +09:00
if (installer.menuToAppend == null) return;
2022-12-18 17:01:46 +09:00
TraverseMenu(installer);
2022-12-11 22:08:41 +09:00
}
public ImmutableList<ChildElement> GetChildren(VRCExpressionsMenu parent)
2022-12-17 16:51:34 +09:00
{
2022-12-18 17:01:46 +09:00
if (parent == null) parent = _rootMenu;
return !_menuChildrenMap.TryGetValue(parent, out ImmutableList<ChildElement> immutableList) ? ImmutableList<ChildElement>.Empty : immutableList;
2022-12-12 18:19:50 +09:00
}
2022-12-17 16:51:34 +09:00
public IEnumerable<ChildElement> GetChildInstallers(ModularAvatarMenuInstaller parentInstaller)
{
2022-12-17 00:06:34 +09:00
HashSet<VRCExpressionsMenu> visitedMenus = new HashSet<VRCExpressionsMenu>();
2022-12-11 22:08:41 +09:00
Queue<VRCExpressionsMenu> queue = new Queue<VRCExpressionsMenu>();
2022-12-17 00:06:34 +09:00
if (parentInstaller != null && parentInstaller.menuToAppend == null) yield break;
2022-12-17 16:51:34 +09:00
if (parentInstaller == null)
{
2022-12-18 17:01:46 +09:00
queue.Enqueue(_rootMenu);
2022-12-17 16:51:34 +09:00
}
else
{
2022-12-17 00:06:34 +09:00
if (parentInstaller.menuToAppend == null) yield break;
2022-12-17 16:51:34 +09:00
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(parentInstaller.menuToAppend))
{
2022-12-17 00:06:34 +09:00
queue.Enqueue(childMenu.Value);
}
}
2022-12-17 16:51:34 +09:00
while (queue.Count > 0)
{
2022-12-17 00:06:34 +09:00
VRCExpressionsMenu parentMenu = queue.Dequeue();
if (visitedMenus.Contains(parentMenu)) continue;
visitedMenus.Add(parentMenu);
HashSet<ModularAvatarMenuInstaller> returnedInstallers = new HashSet<ModularAvatarMenuInstaller>();
2022-12-18 17:01:46 +09:00
foreach (ChildElement childElement in GetChildren(parentMenu))
2022-12-17 16:51:34 +09:00
{
if (!childElement.isInstallerRoot)
{
2022-12-17 00:06:34 +09:00
queue.Enqueue(childElement.menu);
continue;
}
2022-12-18 19:03:13 +09:00
// One installer may add multiple children, so filter to return only one.
2022-12-17 00:06:34 +09:00
if (returnedInstallers.Contains(childElement.installer)) continue;
returnedInstallers.Add(childElement.installer);
yield return childElement;
2022-12-11 22:08:41 +09:00
}
2022-12-17 00:06:34 +09:00
}
}
2022-12-11 22:08:41 +09:00
2022-12-18 17:01:46 +09:00
private void TraverseMenu(VRCExpressionsMenu root)
2022-12-17 16:51:34 +09:00
{
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(root))
{
2022-12-18 17:01:46 +09:00
TraverseMenu(root, new ChildElement
2022-12-17 16:51:34 +09:00
{
2022-12-17 00:06:34 +09:00
menuName = childMenu.Key,
menu = childMenu.Value
});
}
}
2022-12-18 17:01:46 +09:00
private void TraverseMenu(ModularAvatarMenuInstaller installer)
2022-12-17 16:51:34 +09:00
{
2022-12-17 00:06:34 +09:00
IEnumerable<KeyValuePair<string, VRCExpressionsMenu>> childMenus = GetChildMenus(installer.menuToAppend);
IEnumerable<VRCExpressionsMenu> parents = Enumerable.Empty<VRCExpressionsMenu>();
if (installer.installTargetMenu != null &&
ClonedMenuMappings.TryGetClonedMenus(installer.installTargetMenu, out ImmutableList<VRCExpressionsMenu> parentMenus))
2022-12-17 16:51:34 +09:00
{
2022-12-17 00:06:34 +09:00
parents = parentMenus;
}
VRCExpressionsMenu[] parentsMenus = parents.DefaultIfEmpty(installer.installTargetMenu).ToArray();
bool hasChildMenu = false;
2022-12-18 19:03:13 +09:00
/*
* Installer adds the controls in specified menu to the installation destination.
* So, since the specified menu itself does not exist as a child menu,
* and the child menus of the specified menu are the actual child menus, a single installer may add multiple child menus.
*/
2022-12-17 16:51:34 +09:00
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in childMenus)
{
hasChildMenu = true;
2022-12-17 16:51:34 +09:00
ChildElement childElement = new ChildElement
{
2022-12-17 00:06:34 +09:00
menuName = childMenu.Key,
menu = childMenu.Value,
installer = installer,
isInstallerRoot = true
};
2022-12-17 16:51:34 +09:00
foreach (VRCExpressionsMenu parentMenu in parentsMenus)
{
2022-12-18 17:01:46 +09:00
TraverseMenu(parentMenu, childElement);
2022-12-11 22:08:41 +09:00
}
2022-12-17 00:06:34 +09:00
}
if (hasChildMenu) return;
2022-12-18 19:03:13 +09:00
/*
* If the specified menu does not have any submenus, it is not mapped as a child menu and the Installer information itself is not registered.
* Therefore, register elements that do not have child menus themselves, but only have information about the installer.
*/
foreach (VRCExpressionsMenu parentMenu in parentsMenus)
2022-12-17 16:51:34 +09:00
{
TraverseMenu(parentMenu, new ChildElement
2022-12-17 16:51:34 +09:00
{
installer = installer,
isInstallerRoot = true
});
2022-12-17 16:51:34 +09:00
}
2022-12-17 00:06:34 +09:00
}
2022-12-18 17:01:46 +09:00
private void TraverseMenu(VRCExpressionsMenu parent, ChildElement childElement)
2022-12-17 16:51:34 +09:00
{
2022-12-18 17:01:46 +09:00
if (parent == null) parent = _rootMenu;
2022-12-17 00:06:34 +09:00
childElement.parent = parent;
if (!_menuChildrenMap.TryGetValue(parent, out ImmutableList<ChildElement> children))
2022-12-17 16:51:34 +09:00
{
children = ImmutableList<ChildElement>.Empty;
2022-12-18 17:01:46 +09:00
_menuChildrenMap[parent] = children;
2022-12-17 00:06:34 +09:00
}
_menuChildrenMap[parent] = children.Add(childElement);
2022-12-17 16:51:34 +09:00
if (childElement.menu == null) return;
2022-12-18 17:01:46 +09:00
if (_included.Contains(childElement.menu)) return;
_included.Add(childElement.menu);
2022-12-17 16:51:34 +09:00
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(childElement.menu))
{
2022-12-18 17:01:46 +09:00
TraverseMenu(childElement.menu, new ChildElement
2022-12-17 16:51:34 +09:00
{
2022-12-17 00:06:34 +09:00
menuName = childMenu.Key,
menu = childMenu.Value,
installer = childElement.installer
});
2022-12-11 22:08:41 +09:00
}
}
2022-12-17 16:51:34 +09:00
private static IEnumerable<KeyValuePair<string, VRCExpressionsMenu>> GetChildMenus(VRCExpressionsMenu expressionsMenu)
{
2022-12-11 22:08:41 +09:00
return expressionsMenu.controls
.Where(control => control.type == ControlType.SubMenu && control.subMenu != null)
.Select(control => new KeyValuePair<string, VRCExpressionsMenu>(control.name, control.subMenu));
2022-12-11 22:08:41 +09:00
}
}
}