From 52dd314f8a16feb23ddbb0230098eb9816dc14c6 Mon Sep 17 00:00:00 2001 From: bd_ Date: Wed, 22 Feb 2023 21:54:09 +0900 Subject: [PATCH] integrate with menu generation pass and menu target UI --- .../Editor/Inspector/AvMenuTreeView.cs | 80 ++++--- .../Editor/Inspector/MAMenuItemInspector.cs | 3 +- .../Editor/Inspector/MenuInstallerEditor.cs | 16 +- .../Editor/MenuGeneration/VirtualMenu.cs | 22 +- .../Editor/MenuInstallHook.cs | 55 +---- .../Editor/MenuTree.cs | 209 ------------------ .../Editor/MenuTree.cs.meta | 3 - .../Editor/RenameParametersHook.cs | 20 ++ 8 files changed, 102 insertions(+), 306 deletions(-) delete mode 100644 Packages/nadena.dev.modular-avatar/Editor/MenuTree.cs delete mode 100644 Packages/nadena.dev.modular-avatar/Editor/MenuTree.cs.meta diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/AvMenuTreeView.cs b/Packages/nadena.dev.modular-avatar/Editor/Inspector/AvMenuTreeView.cs index 5f73a280..3595ae08 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Inspector/AvMenuTreeView.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/AvMenuTreeView.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using nadena.dev.modular_avatar.core.editor.menu; using NUnit.Framework; using UnityEditor; using UnityEditor.IMGUI.Controls; @@ -22,13 +23,13 @@ namespace nadena.dev.modular_avatar.core.editor set => _treeView.Avatar = value; } - public ModularAvatarMenuInstaller TargetInstaller + public ModularAvatarMenuInstaller TargetInstaller { get => _treeView.TargetInstaller; set => _treeView.TargetInstaller = value; } - public Action OnMenuSelected = (menu) => { }; + public Action OnMenuSelected = (menu) => { }; private void Awake() { @@ -37,7 +38,7 @@ namespace nadena.dev.modular_avatar.core.editor _treeView.OnDoubleclickSelect = Close; } - private void OnLostFocus() + private void OnLostFocus() { Close(); } @@ -58,7 +59,8 @@ namespace nadena.dev.modular_avatar.core.editor _treeView.OnGUI(new Rect(0, 0, position.width, position.height)); } - internal static void Show(VRCAvatarDescriptor Avatar, ModularAvatarMenuInstaller Installer, Action OnSelect) + internal static void Show(VRCAvatarDescriptor Avatar, ModularAvatarMenuInstaller Installer, + Action OnSelect) { var window = GetWindow(); window.titleContent = new GUIContent("Select menu"); @@ -87,24 +89,24 @@ namespace nadena.dev.modular_avatar.core.editor private ModularAvatarMenuInstaller _targetInstaller; - public ModularAvatarMenuInstaller TargetInstaller + public ModularAvatarMenuInstaller TargetInstaller { get => _targetInstaller; - set + set { _targetInstaller = value; Reload(); } } - internal Action OnSelect = (menu) => { }; + internal Action OnSelect = (menu) => { }; internal Action OnDoubleclickSelect = () => { }; - private List _menuItems = new List(); - private HashSet _visitedMenus = new HashSet(); + private List _nodeKeys = new List(); + private HashSet _visitedMenus = new HashSet(); - private MenuTree _menuTree; - private Stack _visitedMenuStack = new Stack(); + private VirtualMenu _menuTree; + private Stack _visitedMenuStack = new Stack(); public AvMenuTreeView(TreeViewState state) : base(state) { @@ -112,67 +114,61 @@ namespace nadena.dev.modular_avatar.core.editor protected override void SelectionChanged(IList selectedIds) { - OnSelect.Invoke(_menuItems[selectedIds[0]]); + OnSelect.Invoke(_nodeKeys[selectedIds[0]]); } protected override void DoubleClickedItem(int id) { - OnSelect.Invoke(_menuItems[id]); + OnSelect.Invoke(_nodeKeys[id]); OnDoubleclickSelect.Invoke(); } - protected override TreeViewItem BuildRoot() + protected override TreeViewItem BuildRoot() { - _menuItems.Clear(); + _nodeKeys.Clear(); _visitedMenuStack.Clear(); - _menuTree = new MenuTree(Avatar); - _menuTree.TraverseAvatarMenu(); - foreach (ModularAvatarMenuInstaller installer in Avatar.gameObject.GetComponentsInChildren(true)) - { - if (installer == TargetInstaller) continue; - _menuTree.TraverseMenuInstaller(installer); - } - + _menuTree = VirtualMenu.ForAvatar(_avatar); + var root = new TreeViewItem(-1, -1, ""); - List treeItems = new List + List treeItems = new List { - new TreeViewItem + new TreeViewItem { id = 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); - _visitedMenuStack.Push(Avatar.expressionsMenu); - - TraverseMenu(1, treeItems, Avatar.expressionsMenu); + _nodeKeys.Add(_menuTree.RootMenuKey); + _visitedMenuStack.Push(_menuTree.RootMenuKey); + TraverseMenu(1, treeItems, _menuTree.RootMenuNode); SetupParentsAndChildrenFromDepths(root, treeItems); return root; } - private void TraverseMenu(int depth, List items, VRCExpressionsMenu menu) + private void TraverseMenu(int depth, List items, MenuNode node) { - IEnumerable children = _menuTree.GetChildren(menu) - .Where(child => !_visitedMenuStack.Contains(child.menu)); - foreach (MenuTree.ChildElement child in children) + IEnumerable children = node.Controls + .Where(control => control.type == VRCExpressionsMenu.Control.ControlType.SubMenu && + control.SubmenuNode != null && + !_visitedMenuStack.Contains(control.SubmenuNode)); + foreach (var child in children) { - if (child.menu == null) continue; - string displayName = child.installer == null ? - $"{child.menuName} ({child.menu.name})" : - $"{child.menuName} ({child.menu.name}) InstallerObject : {child.installer.name}"; + string displayName = child.name; + items.Add( - new TreeViewItem + new TreeViewItem { id = items.Count, depth = depth, displayName = displayName } ); - _menuItems.Add(child.menu); - _visitedMenuStack.Push(child.menu); - TraverseMenu(depth + 1, items, child.menu); + _nodeKeys.Add(child.SubmenuNode.NodeKey); + _visitedMenuStack.Push(child.SubmenuNode); + TraverseMenu(depth + 1, items, child.SubmenuNode); _visitedMenuStack.Pop(); } } diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAMenuItemInspector.cs b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAMenuItemInspector.cs index 91015f2e..fdeda2ce 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAMenuItemInspector.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAMenuItemInspector.cs @@ -26,8 +26,7 @@ namespace nadena.dev.modular_avatar.core.editor if (name != null) { EditorGUI.BeginChangeCheck(); - var targetGameObject = ((ModularAvatarMenuItem) target).gameObject; - var newName = EditorGUILayout.TextField("Name", targetGameObject.name); + var newName = EditorGUILayout.TextField("Name", name); if (EditorGUI.EndChangeCheck() && commitName != null) { commitName(newName); diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MenuInstallerEditor.cs b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MenuInstallerEditor.cs index 563389cc..aae58086 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MenuInstallerEditor.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MenuInstallerEditor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using nadena.dev.modular_avatar.core.editor.menu; using UnityEditor; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -101,7 +102,20 @@ namespace nadena.dev.modular_avatar.core.editor { 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(); }); } diff --git a/Packages/nadena.dev.modular-avatar/Editor/MenuGeneration/VirtualMenu.cs b/Packages/nadena.dev.modular-avatar/Editor/MenuGeneration/VirtualMenu.cs index 22a427f5..d8790909 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/MenuGeneration/VirtualMenu.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/MenuGeneration/VirtualMenu.cs @@ -4,6 +4,7 @@ using System.Linq; using nadena.dev.modular_avatar.editor.ErrorReporting; using UnityEditor; using UnityEngine; +using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.ScriptableObjects; namespace nadena.dev.modular_avatar.core.editor.menu @@ -71,7 +72,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu */ internal class VirtualMenu { - private readonly object RootMenuKey; + internal readonly object RootMenuKey; /// /// Indexes which menu installers are contributing to which VRCExpressionMenu assets. @@ -92,6 +93,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu // TODO: immutable? public Dictionary ResolvedMenu => _resolvedMenu; + public MenuNode RootMenuNode => ResolvedMenu[RootMenuKey]; /// /// 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(true)) + { + menu.RegisterMenuInstaller(installer); + } + + foreach (var target in avatar.GetComponentsInChildren(true)) + { + menu.RegisterMenuInstallTarget(target); + } + + menu.FreezeMenu(); + + return menu; + } + private MenuNode ImportMenu(VRCExpressionsMenu menu, object menuKey = null) { if (menuKey == null) menuKey = menu; diff --git a/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs b/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs index 2e8a4692..ba6c9f9b 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using nadena.dev.modular_avatar.core.editor.menu; using nadena.dev.modular_avatar.editor.ErrorReporting; using UnityEditor; using UnityEngine; @@ -22,7 +23,6 @@ namespace nadena.dev.modular_avatar.core.editor private VRCExpressionsMenu _rootMenu; - private MenuTree _menuTree; private Stack _visitedInstallerStack; public void OnPreprocessAvatar(GameObject avatarRoot, BuildContext context) @@ -48,55 +48,15 @@ namespace nadena.dev.modular_avatar.core.editor } _rootMenu = avatar.expressionsMenu; - _menuTree = new MenuTree(avatar); - _menuTree.TraverseAvatarMenu(); - - avatar.expressionsMenu = _context.CloneMenu(avatar.expressionsMenu); - - foreach (ModularAvatarMenuInstaller installer in menuInstallers) + var virtualMenu = VirtualMenu.ForAvatar(avatar); + avatar.expressionsMenu = virtualMenu.SerializeMenu(asset => { - BuildReport.ReportingObject(installer, () => _menuTree.TraverseMenuInstaller(installer)); - } - - foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(null)) - { - BuildReport.ReportingObject(childElement.installer, () => InstallMenu(childElement.installer)); - } + context.SaveAsset(asset); + if (asset is VRCExpressionsMenu menu) SplitMenu(menu); + }); } - private void InstallMenu(ModularAvatarMenuInstaller installer, VRCExpressionsMenu installTarget = null) - { - 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) + private void SplitMenu(VRCExpressionsMenu targetMenu) { while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS) { @@ -123,7 +83,6 @@ namespace nadena.dev.modular_avatar.core.editor labels = Array.Empty() }); - _context.ClonedMenus[installer.installTargetMenu] = newMenu; targetMenu = newMenu; } } diff --git a/Packages/nadena.dev.modular-avatar/Editor/MenuTree.cs b/Packages/nadena.dev.modular-avatar/Editor/MenuTree.cs deleted file mode 100644 index cde6ab69..00000000 --- a/Packages/nadena.dev.modular-avatar/Editor/MenuTree.cs +++ /dev/null @@ -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 - { - /// - /// Parent menu control name - /// - public string menuName; - public VRCExpressionsMenu menu; - public VRCExpressionsMenu parent; - /// - /// Installer to install this menu. Is null if the this menu is not installed by the installer. - /// - public ModularAvatarMenuInstaller installer; - /// - /// Whether the this submenu is added directly by the installer - /// - public bool isInstallerRoot; - } - - private readonly HashSet _included; - - private readonly VRCExpressionsMenu _rootMenu; - - /// - /// Map to link child menus from parent menu - /// - private readonly Dictionary> _menuChildrenMap; - - public MenuTree(VRCAvatarDescriptor descriptor) - { - _rootMenu = descriptor.expressionsMenu; - _included = new HashSet(); - _menuChildrenMap = new Dictionary>(); - - if (_rootMenu == null) - { - // If the route menu is null, create a temporary menu indicating the route - _rootMenu = ScriptableObject.CreateInstance(); - } - - _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 GetChildren(VRCExpressionsMenu parent) - { - if (parent == null) parent = _rootMenu; - return !_menuChildrenMap.TryGetValue(parent, out ImmutableList immutableList) ? ImmutableList.Empty : immutableList; - } - - public IEnumerable GetChildInstallers(ModularAvatarMenuInstaller parentInstaller) - { - HashSet visitedMenus = new HashSet(); - Queue queue = new Queue(); - if (parentInstaller != null && parentInstaller.menuToAppend == null) yield break; - if (parentInstaller == null) - { - queue.Enqueue(_rootMenu); - } - else - { - if (parentInstaller.menuToAppend == null) yield break; - foreach (KeyValuePair 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 returnedInstallers = new HashSet(); - 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 childMenu in GetChildMenus(root)) - { - TraverseMenu(root, new ChildElement - { - menuName = childMenu.Key, - menu = childMenu.Value - }); - } - } - - private void TraverseMenu(ModularAvatarMenuInstaller installer) - { - IEnumerable> childMenus = GetChildMenus(installer.menuToAppend); - IEnumerable parents = Enumerable.Empty(); - if (installer.installTargetMenu != null && - ClonedMenuMappings.TryGetClonedMenus(installer.installTargetMenu, out ImmutableList 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 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 children)) - { - children = ImmutableList.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 childMenu in GetChildMenus(childElement.menu)) - { - TraverseMenu(childElement.menu, new ChildElement - { - menuName = childMenu.Key, - menu = childMenu.Value, - installer = childElement.installer - }); - } - } - - private static IEnumerable> GetChildMenus(VRCExpressionsMenu expressionsMenu) - { - return expressionsMenu.controls - .Where(control => control.type == ControlType.SubMenu && control.subMenu != null) - .Select(control => new KeyValuePair(control.name, control.subMenu)); - } - } -} \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/MenuTree.cs.meta b/Packages/nadena.dev.modular-avatar/Editor/MenuTree.cs.meta deleted file mode 100644 index dc5993a1..00000000 --- a/Packages/nadena.dev.modular-avatar/Editor/MenuTree.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: effd4557902f4578af42d3bdfb7f876d -timeCreated: 1670746991 \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs b/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs index 4eb1abe6..6abb0850 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs @@ -178,6 +178,26 @@ namespace nadena.dev.modular_avatar.core.editor 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()) + { + if (subParam?.name != null && remaps.TryGetValue(subParam.name, out var subNewVal)) + { + subParam.name = subNewVal; + } + } + + break; + } } }); }