mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-05-10 21:29:00 +08:00
integrate with menu generation pass and menu target UI
This commit is contained in:
parent
19e42f3422
commit
52dd314f8a
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using nadena.dev.modular_avatar.core.editor.menu;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.IMGUI.Controls;
|
using UnityEditor.IMGUI.Controls;
|
||||||
@ -28,7 +29,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
set => _treeView.TargetInstaller = value;
|
set => _treeView.TargetInstaller = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action<VRCExpressionsMenu> OnMenuSelected = (menu) => { };
|
public Action<object> OnMenuSelected = (menu) => { };
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
@ -58,7 +59,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
_treeView.OnGUI(new Rect(0, 0, position.width, position.height));
|
_treeView.OnGUI(new Rect(0, 0, position.width, position.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void Show(VRCAvatarDescriptor Avatar, ModularAvatarMenuInstaller Installer, Action<VRCExpressionsMenu> OnSelect)
|
internal static void Show(VRCAvatarDescriptor Avatar, ModularAvatarMenuInstaller Installer,
|
||||||
|
Action<object> OnSelect)
|
||||||
{
|
{
|
||||||
var window = GetWindow<AvMenuTreeViewWindow>();
|
var window = GetWindow<AvMenuTreeViewWindow>();
|
||||||
window.titleContent = new GUIContent("Select menu");
|
window.titleContent = new GUIContent("Select menu");
|
||||||
@ -97,14 +99,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Action<VRCExpressionsMenu> OnSelect = (menu) => { };
|
internal Action<object> OnSelect = (menu) => { };
|
||||||
internal Action OnDoubleclickSelect = () => { };
|
internal Action OnDoubleclickSelect = () => { };
|
||||||
|
|
||||||
private List<VRCExpressionsMenu> _menuItems = new List<VRCExpressionsMenu>();
|
private List<object> _nodeKeys = new List<object>();
|
||||||
private HashSet<VRCExpressionsMenu> _visitedMenus = new HashSet<VRCExpressionsMenu>();
|
private HashSet<object> _visitedMenus = new HashSet<object>();
|
||||||
|
|
||||||
private MenuTree _menuTree;
|
private VirtualMenu _menuTree;
|
||||||
private Stack<VRCExpressionsMenu> _visitedMenuStack = new Stack<VRCExpressionsMenu>();
|
private Stack<object> _visitedMenuStack = new Stack<object>();
|
||||||
|
|
||||||
public AvMenuTreeView(TreeViewState state) : base(state)
|
public AvMenuTreeView(TreeViewState state) : base(state)
|
||||||
{
|
{
|
||||||
@ -112,27 +114,21 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
protected override void SelectionChanged(IList<int> selectedIds)
|
protected override void SelectionChanged(IList<int> selectedIds)
|
||||||
{
|
{
|
||||||
OnSelect.Invoke(_menuItems[selectedIds[0]]);
|
OnSelect.Invoke(_nodeKeys[selectedIds[0]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DoubleClickedItem(int id)
|
protected override void DoubleClickedItem(int id)
|
||||||
{
|
{
|
||||||
OnSelect.Invoke(_menuItems[id]);
|
OnSelect.Invoke(_nodeKeys[id]);
|
||||||
OnDoubleclickSelect.Invoke();
|
OnDoubleclickSelect.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TreeViewItem BuildRoot()
|
protected override TreeViewItem BuildRoot()
|
||||||
{
|
{
|
||||||
_menuItems.Clear();
|
_nodeKeys.Clear();
|
||||||
_visitedMenuStack.Clear();
|
_visitedMenuStack.Clear();
|
||||||
|
|
||||||
_menuTree = new MenuTree(Avatar);
|
_menuTree = VirtualMenu.ForAvatar(_avatar);
|
||||||
_menuTree.TraverseAvatarMenu();
|
|
||||||
foreach (ModularAvatarMenuInstaller installer in Avatar.gameObject.GetComponentsInChildren<ModularAvatarMenuInstaller>(true))
|
|
||||||
{
|
|
||||||
if (installer == TargetInstaller) continue;
|
|
||||||
_menuTree.TraverseMenuInstaller(installer);
|
|
||||||
}
|
|
||||||
|
|
||||||
var root = new TreeViewItem(-1, -1, "<root>");
|
var root = new TreeViewItem(-1, -1, "<root>");
|
||||||
List<TreeViewItem> treeItems = new List<TreeViewItem>
|
List<TreeViewItem> treeItems = new List<TreeViewItem>
|
||||||
@ -141,27 +137,27 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
id = 0,
|
id = 0,
|
||||||
depth = 0,
|
depth = 0,
|
||||||
displayName = $"{Avatar.gameObject.name} ({(Avatar.expressionsMenu == null ? "None" : Avatar.expressionsMenu.name)})"
|
displayName =
|
||||||
|
$"{Avatar.gameObject.name} ({(Avatar.expressionsMenu == null ? "None" : Avatar.expressionsMenu.name)})"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_menuItems.Add(Avatar.expressionsMenu);
|
_nodeKeys.Add(_menuTree.RootMenuKey);
|
||||||
_visitedMenuStack.Push(Avatar.expressionsMenu);
|
_visitedMenuStack.Push(_menuTree.RootMenuKey);
|
||||||
|
TraverseMenu(1, treeItems, _menuTree.RootMenuNode);
|
||||||
TraverseMenu(1, treeItems, Avatar.expressionsMenu);
|
|
||||||
SetupParentsAndChildrenFromDepths(root, treeItems);
|
SetupParentsAndChildrenFromDepths(root, treeItems);
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TraverseMenu(int depth, List<TreeViewItem> items, VRCExpressionsMenu menu)
|
private void TraverseMenu(int depth, List<TreeViewItem> items, MenuNode node)
|
||||||
{
|
{
|
||||||
IEnumerable<MenuTree.ChildElement> children = _menuTree.GetChildren(menu)
|
IEnumerable<VirtualControl> children = node.Controls
|
||||||
.Where(child => !_visitedMenuStack.Contains(child.menu));
|
.Where(control => control.type == VRCExpressionsMenu.Control.ControlType.SubMenu &&
|
||||||
foreach (MenuTree.ChildElement child in children)
|
control.SubmenuNode != null &&
|
||||||
|
!_visitedMenuStack.Contains(control.SubmenuNode));
|
||||||
|
foreach (var child in children)
|
||||||
{
|
{
|
||||||
if (child.menu == null) continue;
|
string displayName = child.name;
|
||||||
string displayName = child.installer == null ?
|
|
||||||
$"{child.menuName} ({child.menu.name})" :
|
|
||||||
$"{child.menuName} ({child.menu.name}) InstallerObject : {child.installer.name}";
|
|
||||||
items.Add(
|
items.Add(
|
||||||
new TreeViewItem
|
new TreeViewItem
|
||||||
{
|
{
|
||||||
@ -170,9 +166,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
displayName = displayName
|
displayName = displayName
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
_menuItems.Add(child.menu);
|
_nodeKeys.Add(child.SubmenuNode.NodeKey);
|
||||||
_visitedMenuStack.Push(child.menu);
|
_visitedMenuStack.Push(child.SubmenuNode);
|
||||||
TraverseMenu(depth + 1, items, child.menu);
|
TraverseMenu(depth + 1, items, child.SubmenuNode);
|
||||||
_visitedMenuStack.Pop();
|
_visitedMenuStack.Pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
if (name != null)
|
if (name != null)
|
||||||
{
|
{
|
||||||
EditorGUI.BeginChangeCheck();
|
EditorGUI.BeginChangeCheck();
|
||||||
var targetGameObject = ((ModularAvatarMenuItem) target).gameObject;
|
var newName = EditorGUILayout.TextField("Name", name);
|
||||||
var newName = EditorGUILayout.TextField("Name", targetGameObject.name);
|
|
||||||
if (EditorGUI.EndChangeCheck() && commitName != null)
|
if (EditorGUI.EndChangeCheck() && commitName != null)
|
||||||
{
|
{
|
||||||
commitName(newName);
|
commitName(newName);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using nadena.dev.modular_avatar.core.editor.menu;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VRC.SDK3.Avatars.Components;
|
using VRC.SDK3.Avatars.Components;
|
||||||
@ -101,7 +102,20 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
AvMenuTreeViewWindow.Show(avatar, _installer, menu =>
|
AvMenuTreeViewWindow.Show(avatar, _installer, menu =>
|
||||||
{
|
{
|
||||||
installTo.objectReferenceValue = menu;
|
if (menu is VRCExpressionsMenu expMenu)
|
||||||
|
{
|
||||||
|
if (expMenu == avatar.expressionsMenu) installTo.objectReferenceValue = null;
|
||||||
|
else installTo.objectReferenceValue = expMenu;
|
||||||
|
}
|
||||||
|
else if (menu is RootMenu)
|
||||||
|
{
|
||||||
|
installTo.objectReferenceValue = null;
|
||||||
|
}
|
||||||
|
else if (menu is ModularAvatarMenuItem item)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using VRC.SDK3.Avatars.Components;
|
||||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor.menu
|
namespace nadena.dev.modular_avatar.core.editor.menu
|
||||||
@ -71,7 +72,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
|||||||
*/
|
*/
|
||||||
internal class VirtualMenu
|
internal class VirtualMenu
|
||||||
{
|
{
|
||||||
private readonly object RootMenuKey;
|
internal readonly object RootMenuKey;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indexes which menu installers are contributing to which VRCExpressionMenu assets.
|
/// Indexes which menu installers are contributing to which VRCExpressionMenu assets.
|
||||||
@ -92,6 +93,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
|||||||
|
|
||||||
// TODO: immutable?
|
// TODO: immutable?
|
||||||
public Dictionary<object, MenuNode> ResolvedMenu => _resolvedMenu;
|
public Dictionary<object, MenuNode> ResolvedMenu => _resolvedMenu;
|
||||||
|
public MenuNode RootMenuNode => ResolvedMenu[RootMenuKey];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the VirtualMenu.
|
/// Initializes the VirtualMenu.
|
||||||
@ -111,6 +113,24 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static VirtualMenu ForAvatar(VRCAvatarDescriptor avatar)
|
||||||
|
{
|
||||||
|
var menu = new VirtualMenu(avatar.expressionsMenu);
|
||||||
|
foreach (var installer in avatar.GetComponentsInChildren<ModularAvatarMenuInstaller>(true))
|
||||||
|
{
|
||||||
|
menu.RegisterMenuInstaller(installer);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var target in avatar.GetComponentsInChildren<ModularAvatarMenuInstallTarget>(true))
|
||||||
|
{
|
||||||
|
menu.RegisterMenuInstallTarget(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.FreezeMenu();
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
private MenuNode ImportMenu(VRCExpressionsMenu menu, object menuKey = null)
|
private MenuNode ImportMenu(VRCExpressionsMenu menu, object menuKey = null)
|
||||||
{
|
{
|
||||||
if (menuKey == null) menuKey = menu;
|
if (menuKey == null) menuKey = menu;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using nadena.dev.modular_avatar.core.editor.menu;
|
||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -22,7 +23,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
private VRCExpressionsMenu _rootMenu;
|
private VRCExpressionsMenu _rootMenu;
|
||||||
|
|
||||||
private MenuTree _menuTree;
|
|
||||||
private Stack<ModularAvatarMenuInstaller> _visitedInstallerStack;
|
private Stack<ModularAvatarMenuInstaller> _visitedInstallerStack;
|
||||||
|
|
||||||
public void OnPreprocessAvatar(GameObject avatarRoot, BuildContext context)
|
public void OnPreprocessAvatar(GameObject avatarRoot, BuildContext context)
|
||||||
@ -48,55 +48,15 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
_rootMenu = avatar.expressionsMenu;
|
_rootMenu = avatar.expressionsMenu;
|
||||||
_menuTree = new MenuTree(avatar);
|
var virtualMenu = VirtualMenu.ForAvatar(avatar);
|
||||||
_menuTree.TraverseAvatarMenu();
|
avatar.expressionsMenu = virtualMenu.SerializeMenu(asset =>
|
||||||
|
|
||||||
avatar.expressionsMenu = _context.CloneMenu(avatar.expressionsMenu);
|
|
||||||
|
|
||||||
foreach (ModularAvatarMenuInstaller installer in menuInstallers)
|
|
||||||
{
|
{
|
||||||
BuildReport.ReportingObject(installer, () => _menuTree.TraverseMenuInstaller(installer));
|
context.SaveAsset(asset);
|
||||||
}
|
if (asset is VRCExpressionsMenu menu) SplitMenu(menu);
|
||||||
|
});
|
||||||
foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(null))
|
|
||||||
{
|
|
||||||
BuildReport.ReportingObject(childElement.installer, () => InstallMenu(childElement.installer));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InstallMenu(ModularAvatarMenuInstaller installer, VRCExpressionsMenu installTarget = null)
|
private void SplitMenu(VRCExpressionsMenu targetMenu)
|
||||||
{
|
|
||||||
if (!installer.enabled) return;
|
|
||||||
|
|
||||||
if (installer.installTargetMenu == null)
|
|
||||||
{
|
|
||||||
installer.installTargetMenu = _rootMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (installTarget == null)
|
|
||||||
{
|
|
||||||
installTarget = installer.installTargetMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (installer.installTargetMenu == null || installer.menuToAppend == null) return;
|
|
||||||
if (!_context.ClonedMenus.TryGetValue(installTarget, out var targetMenu)) return;
|
|
||||||
|
|
||||||
// Clone before appending to sanitize menu icons
|
|
||||||
targetMenu.controls.AddRange(_context.CloneMenu(installer.menuToAppend).controls);
|
|
||||||
|
|
||||||
SplitMenu(installer, targetMenu);
|
|
||||||
|
|
||||||
if (_visitedInstallerStack.Contains(installer)) return;
|
|
||||||
_visitedInstallerStack.Push(installer);
|
|
||||||
foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(installer))
|
|
||||||
{
|
|
||||||
InstallMenu(childElement.installer, childElement.parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
_visitedInstallerStack.Pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SplitMenu(ModularAvatarMenuInstaller installer, VRCExpressionsMenu targetMenu)
|
|
||||||
{
|
{
|
||||||
while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS)
|
while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS)
|
||||||
{
|
{
|
||||||
@ -123,7 +83,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
labels = Array.Empty<VRCExpressionsMenu.Control.Label>()
|
labels = Array.Empty<VRCExpressionsMenu.Control.Label>()
|
||||||
});
|
});
|
||||||
|
|
||||||
_context.ClonedMenus[installer.installTargetMenu] = newMenu;
|
|
||||||
targetMenu = newMenu;
|
targetMenu = newMenu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,209 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using UnityEngine;
|
|
||||||
using VRC.SDK3.Avatars.Components;
|
|
||||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
|
||||||
using static VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control;
|
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
|
||||||
{
|
|
||||||
internal class MenuTree
|
|
||||||
{
|
|
||||||
|
|
||||||
public struct ChildElement
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Parent menu control name
|
|
||||||
/// </summary>
|
|
||||||
public string menuName;
|
|
||||||
public VRCExpressionsMenu menu;
|
|
||||||
public VRCExpressionsMenu parent;
|
|
||||||
/// <summary>
|
|
||||||
/// Installer to install this menu. Is null if the this menu is not installed by the installer.
|
|
||||||
/// </summary>
|
|
||||||
public ModularAvatarMenuInstaller installer;
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the this submenu is added directly by the installer
|
|
||||||
/// </summary>
|
|
||||||
public bool isInstallerRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly HashSet<VRCExpressionsMenu> _included;
|
|
||||||
|
|
||||||
private readonly VRCExpressionsMenu _rootMenu;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Map to link child menus from parent menu
|
|
||||||
/// </summary>
|
|
||||||
private readonly Dictionary<VRCExpressionsMenu, ImmutableList<ChildElement>> _menuChildrenMap;
|
|
||||||
|
|
||||||
public MenuTree(VRCAvatarDescriptor descriptor)
|
|
||||||
{
|
|
||||||
_rootMenu = descriptor.expressionsMenu;
|
|
||||||
_included = new HashSet<VRCExpressionsMenu>();
|
|
||||||
_menuChildrenMap = new Dictionary<VRCExpressionsMenu, ImmutableList<ChildElement>>();
|
|
||||||
|
|
||||||
if (_rootMenu == null)
|
|
||||||
{
|
|
||||||
// If the route menu is null, create a temporary menu indicating the route
|
|
||||||
_rootMenu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
|
|
||||||
}
|
|
||||||
|
|
||||||
_included.Add(_rootMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TraverseAvatarMenu()
|
|
||||||
{
|
|
||||||
if (_rootMenu == null) return;
|
|
||||||
TraverseMenu(_rootMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TraverseMenuInstaller(ModularAvatarMenuInstaller installer)
|
|
||||||
{
|
|
||||||
if (!installer.enabled) return;
|
|
||||||
if (installer.menuToAppend == null) return;
|
|
||||||
TraverseMenu(installer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImmutableList<ChildElement> GetChildren(VRCExpressionsMenu parent)
|
|
||||||
{
|
|
||||||
if (parent == null) parent = _rootMenu;
|
|
||||||
return !_menuChildrenMap.TryGetValue(parent, out ImmutableList<ChildElement> immutableList) ? ImmutableList<ChildElement>.Empty : immutableList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<ChildElement> GetChildInstallers(ModularAvatarMenuInstaller parentInstaller)
|
|
||||||
{
|
|
||||||
HashSet<VRCExpressionsMenu> visitedMenus = new HashSet<VRCExpressionsMenu>();
|
|
||||||
Queue<VRCExpressionsMenu> queue = new Queue<VRCExpressionsMenu>();
|
|
||||||
if (parentInstaller != null && parentInstaller.menuToAppend == null) yield break;
|
|
||||||
if (parentInstaller == null)
|
|
||||||
{
|
|
||||||
queue.Enqueue(_rootMenu);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (parentInstaller.menuToAppend == null) yield break;
|
|
||||||
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(parentInstaller.menuToAppend))
|
|
||||||
{
|
|
||||||
queue.Enqueue(childMenu.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (queue.Count > 0)
|
|
||||||
{
|
|
||||||
VRCExpressionsMenu parentMenu = queue.Dequeue();
|
|
||||||
if (visitedMenus.Contains(parentMenu)) continue;
|
|
||||||
visitedMenus.Add(parentMenu);
|
|
||||||
HashSet<ModularAvatarMenuInstaller> returnedInstallers = new HashSet<ModularAvatarMenuInstaller>();
|
|
||||||
foreach (ChildElement childElement in GetChildren(parentMenu))
|
|
||||||
{
|
|
||||||
if (!childElement.isInstallerRoot)
|
|
||||||
{
|
|
||||||
queue.Enqueue(childElement.menu);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// One installer may add multiple children, so filter to return only one.
|
|
||||||
if (returnedInstallers.Contains(childElement.installer)) continue;
|
|
||||||
returnedInstallers.Add(childElement.installer);
|
|
||||||
yield return childElement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void TraverseMenu(VRCExpressionsMenu root)
|
|
||||||
{
|
|
||||||
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(root))
|
|
||||||
{
|
|
||||||
TraverseMenu(root, new ChildElement
|
|
||||||
{
|
|
||||||
menuName = childMenu.Key,
|
|
||||||
menu = childMenu.Value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TraverseMenu(ModularAvatarMenuInstaller installer)
|
|
||||||
{
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
parents = parentMenus;
|
|
||||||
}
|
|
||||||
|
|
||||||
VRCExpressionsMenu[] parentsMenus = parents.DefaultIfEmpty(installer.installTargetMenu).ToArray();
|
|
||||||
bool hasChildMenu = false;
|
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in childMenus)
|
|
||||||
{
|
|
||||||
hasChildMenu = true;
|
|
||||||
ChildElement childElement = new ChildElement
|
|
||||||
{
|
|
||||||
menuName = childMenu.Key,
|
|
||||||
menu = childMenu.Value,
|
|
||||||
installer = installer,
|
|
||||||
isInstallerRoot = true
|
|
||||||
};
|
|
||||||
foreach (VRCExpressionsMenu parentMenu in parentsMenus)
|
|
||||||
{
|
|
||||||
TraverseMenu(parentMenu, childElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasChildMenu) return;
|
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
{
|
|
||||||
TraverseMenu(parentMenu, new ChildElement
|
|
||||||
{
|
|
||||||
installer = installer,
|
|
||||||
isInstallerRoot = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TraverseMenu(VRCExpressionsMenu parent, ChildElement childElement)
|
|
||||||
{
|
|
||||||
if (parent == null) parent = _rootMenu;
|
|
||||||
childElement.parent = parent;
|
|
||||||
if (!_menuChildrenMap.TryGetValue(parent, out ImmutableList<ChildElement> children))
|
|
||||||
{
|
|
||||||
children = ImmutableList<ChildElement>.Empty;
|
|
||||||
_menuChildrenMap[parent] = children;
|
|
||||||
}
|
|
||||||
|
|
||||||
_menuChildrenMap[parent] = children.Add(childElement);
|
|
||||||
if (childElement.menu == null) return;
|
|
||||||
if (_included.Contains(childElement.menu)) return;
|
|
||||||
_included.Add(childElement.menu);
|
|
||||||
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(childElement.menu))
|
|
||||||
{
|
|
||||||
TraverseMenu(childElement.menu, new ChildElement
|
|
||||||
{
|
|
||||||
menuName = childMenu.Key,
|
|
||||||
menu = childMenu.Value,
|
|
||||||
installer = childElement.installer
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<KeyValuePair<string, VRCExpressionsMenu>> GetChildMenus(VRCExpressionsMenu expressionsMenu)
|
|
||||||
{
|
|
||||||
return expressionsMenu.controls
|
|
||||||
.Where(control => control.type == ControlType.SubMenu && control.subMenu != null)
|
|
||||||
.Select(control => new KeyValuePair<string, VRCExpressionsMenu>(control.name, control.subMenu));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: effd4557902f4578af42d3bdfb7f876d
|
|
||||||
timeCreated: 1670746991
|
|
@ -178,6 +178,26 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ModularAvatarMenuItem menuItem:
|
||||||
|
{
|
||||||
|
if (menuItem.Control.parameter?.name != null &&
|
||||||
|
remaps.TryGetValue(menuItem.Control.parameter.name, out var newVal))
|
||||||
|
{
|
||||||
|
menuItem.Control.parameter.name = newVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var subParam in menuItem.Control.subParameters ??
|
||||||
|
Array.Empty<VRCExpressionsMenu.Control.Parameter>())
|
||||||
|
{
|
||||||
|
if (subParam?.name != null && remaps.TryGetValue(subParam.name, out var subNewVal))
|
||||||
|
{
|
||||||
|
subParam.name = subNewVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user