From 14e814d2d8371ee2a036c129545c436605b23955 Mon Sep 17 00:00:00 2001 From: bd_ Date: Sat, 16 Nov 2024 19:00:35 -0800 Subject: [PATCH] feat: add API to open Menu Installer's selection UI Closes: #1327 --- Editor/Inspector/Menu/MenuInstallerEditor.cs | 164 +++++++++++-------- Runtime/Menu/ModularAvatarMenuInstaller.cs | 12 ++ 2 files changed, 108 insertions(+), 68 deletions(-) diff --git a/Editor/Inspector/Menu/MenuInstallerEditor.cs b/Editor/Inspector/Menu/MenuInstallerEditor.cs index f32efe0f..79dbb02f 100644 --- a/Editor/Inspector/Menu/MenuInstallerEditor.cs +++ b/Editor/Inspector/Menu/MenuInstallerEditor.cs @@ -33,6 +33,29 @@ internal class MenuInstallerEditor : MAEditorBase private Dictionary> _menuInstallersMap; + private static Editor _cachedEditor; + + [InitializeOnLoadMethod] + private static void Init() + { + ModularAvatarMenuInstaller._openSelectMenu = OpenSelectInstallTargetMenu; + } + + private static void OpenSelectInstallTargetMenu(ModularAvatarMenuInstaller installer) + { + CreateCachedEditor(installer, typeof(MenuInstallerEditor), ref _cachedEditor); + + var editor = (MenuInstallerEditor)_cachedEditor; + editor.OnEnable(); + + var serializedObject = editor.serializedObject; + var installTo = serializedObject.FindProperty(nameof(ModularAvatarMenuInstaller.installTargetMenu)); + + var root = editor.FindCommonAvatar(); + + editor.OpenSelectMenu(root, installTo); + } + private void OnEnable() { _installer = (ModularAvatarMenuInstaller) target; @@ -215,74 +238,7 @@ protected override void OnInnerInspectorGUI() var avatar = commonAvatar; if (avatar != null && InstallTargets.Count == 1 && GUILayout.Button(G("menuinstall.selectmenu"))) { - AvMenuTreeViewWindow.Show(avatar, _installer, menu => - { - if (InstallTargets.Count != 1 || menu == InstallTargets[0]) return; - - if (InstallTargets[0] is ModularAvatarMenuInstallTarget oldTarget && oldTarget != null) - { - DestroyInstallTargets(); - } - - if (menu is ValueTuple vt) // TODO: This should be a named type... - { - // Menu, ContextCallback - menu = vt.Item1; - } - - if (menu is ModularAvatarMenuItem item) - { - if (item.MenuSource == SubmenuSource.MenuAsset) - { - menu = item.Control.subMenu; - } - else - { - var menuParent = item.menuSource_otherObjectChildren != null - ? item.menuSource_otherObjectChildren - : item.gameObject; - - menu = new MenuNodesUnder(menuParent); - } - } - else if (menu is ModularAvatarMenuGroup group) - { - if (group.targetObject != null) menu = new MenuNodesUnder(group.targetObject); - else menu = new MenuNodesUnder(group.gameObject); - } - - 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 MenuNodesUnder nodesUnder) - { - installTo.objectReferenceValue = null; - - foreach (var target in targets.Cast().OrderBy(ObjectHierarchyOrder)) - { - var installer = (ModularAvatarMenuInstaller) target; - var child = new GameObject(); - Undo.RegisterCreatedObjectUndo(child, "Set install target"); - child.transform.SetParent(nodesUnder.root.transform, false); - child.name = installer.gameObject.name; - - var targetComponent = child.AddComponent(); - targetComponent.installer = installer; - - EditorGUIUtility.PingObject(child); - } - } - - serializedObject.ApplyModifiedProperties(); - VirtualMenu.InvalidateCaches(); - Repaint(); - }); + OpenSelectMenu(avatar, installTo); } } @@ -371,6 +327,78 @@ protected override void OnInnerInspectorGUI() ShowLanguageUI(); } + private void OpenSelectMenu(VRCAvatarDescriptor avatar, SerializedProperty installTo) + { + AvMenuTreeViewWindow.Show(avatar, _installer, menu => + { + if (InstallTargets.Count != 1 || menu == InstallTargets[0]) return; + + if (InstallTargets[0] is ModularAvatarMenuInstallTarget oldTarget && oldTarget != null) + { + DestroyInstallTargets(); + } + + if (menu is ValueTuple vt) // TODO: This should be a named type... + { + // Menu, ContextCallback + menu = vt.Item1; + } + + if (menu is ModularAvatarMenuItem item) + { + if (item.MenuSource == SubmenuSource.MenuAsset) + { + menu = item.Control.subMenu; + } + else + { + var menuParent = item.menuSource_otherObjectChildren != null + ? item.menuSource_otherObjectChildren + : item.gameObject; + + menu = new MenuNodesUnder(menuParent); + } + } + else if (menu is ModularAvatarMenuGroup group) + { + if (group.targetObject != null) menu = new MenuNodesUnder(group.targetObject); + else menu = new MenuNodesUnder(group.gameObject); + } + + 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 MenuNodesUnder nodesUnder) + { + installTo.objectReferenceValue = null; + + foreach (var target in targets.Cast().OrderBy(ObjectHierarchyOrder)) + { + var installer = (ModularAvatarMenuInstaller)target; + var child = new GameObject(); + Undo.RegisterCreatedObjectUndo(child, "Set install target"); + child.transform.SetParent(nodesUnder.root.transform, false); + child.name = installer.gameObject.name; + + var targetComponent = child.AddComponent(); + targetComponent.installer = installer; + + EditorGUIUtility.PingObject(child); + } + } + + serializedObject.ApplyModifiedProperties(); + VirtualMenu.InvalidateCaches(); + Repaint(); + }); + } + private string ObjectHierarchyOrder(Component arg) { var list = new List(); diff --git a/Runtime/Menu/ModularAvatarMenuInstaller.cs b/Runtime/Menu/ModularAvatarMenuInstaller.cs index 96a78cc5..75ea8f02 100644 --- a/Runtime/Menu/ModularAvatarMenuInstaller.cs +++ b/Runtime/Menu/ModularAvatarMenuInstaller.cs @@ -1,5 +1,7 @@ #if MA_VRCSDK3_AVATARS +using System; +using JetBrains.Annotations; using UnityEngine; using VRC.SDK3.Avatars.ScriptableObjects; @@ -12,6 +14,16 @@ public class ModularAvatarMenuInstaller : AvatarTagComponent public VRCExpressionsMenu menuToAppend; public VRCExpressionsMenu installTargetMenu; + internal static Action _openSelectMenu = _ => { }; + + /// + /// Opens the "Select Menu" window, as if the user had clicked this button in the inspector. + /// + [PublicAPI] + public void OpenSelectMenu() + { + _openSelectMenu(this); + } // ReSharper disable once Unity.RedundantEventFunction void Start()