From f2ef8099b09627ed3f4163a950cffa5532d9f44d Mon Sep 17 00:00:00 2001 From: bd_ Date: Wed, 1 Mar 2023 00:26:45 +0900 Subject: [PATCH] ToggleGroup UI --- .../Editor/Inspector/MAEditorBase.cs | 16 +- .../Editor/Inspector/Menu/MenuPreviewGUI.cs | 146 +++++++++--------- .../Inspector/Menu/ToggleGroupInspector.cs | 108 +++++++++++++ .../Menu/ToggleGroupInspector.cs.meta | 3 + 4 files changed, 199 insertions(+), 74 deletions(-) create mode 100644 Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/ToggleGroupInspector.cs create mode 100644 Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/ToggleGroupInspector.cs.meta diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAEditorBase.cs b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAEditorBase.cs index 3d9cd96e..b962b83d 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAEditorBase.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAEditorBase.cs @@ -81,10 +81,7 @@ namespace nadena.dev.modular_avatar.core.editor protected virtual VisualElement CreateInnerInspectorGUI() { - var throwaway = new InspectorElement(); - MethodInfo m = typeof(InspectorElement).GetMethod("CreateIMGUIInspectorFromEditor", - BindingFlags.NonPublic | BindingFlags.Instance); - return m.Invoke(throwaway, new object[] {serializedObject, this, false}) as VisualElement; + return null; } public sealed override VisualElement CreateInspectorGUI() @@ -94,10 +91,19 @@ namespace nadena.dev.modular_avatar.core.editor var inner = CreateInnerInspectorGUI(); + bool innerIsImgui = (inner == null); + if (innerIsImgui) + { + var throwaway = new InspectorElement(); + MethodInfo m = typeof(InspectorElement).GetMethod("CreateIMGUIInspectorFromEditor", + BindingFlags.NonPublic | BindingFlags.Instance); + inner = m.Invoke(throwaway, new object[] {serializedObject, this, false}) as VisualElement; + } + _visualElement = new MAVisualElement(); _visualElement.Add(inner); - _suppressOnceDefaultMargins = true; + _suppressOnceDefaultMargins = innerIsImgui; return _visualElement; } diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/MenuPreviewGUI.cs b/Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/MenuPreviewGUI.cs index 5ca1f0c0..6dcb485d 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/MenuPreviewGUI.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/MenuPreviewGUI.cs @@ -7,69 +7,38 @@ using VRC.SDK3.Avatars.ScriptableObjects; namespace nadena.dev.modular_avatar.core.editor { - internal class MenuPreviewGUI + internal class MenuObjectHeader { private const float INDENT_PER_LEVEL = 2; - private Action _redraw; - private float _indentLevel = 0; - private readonly Dictionary _guiNodes = new Dictionary(); + private static float _indentLevel = 0; - public MenuPreviewGUI(Action redraw) + private UnityEngine.Object _headerObj; + private SerializedProperty _disableProp; + + public MenuObjectHeader(UnityEngine.Object headerObj, SerializedProperty disableProp = null) { - _redraw = redraw; + _headerObj = headerObj; + _disableProp = disableProp; } - public void DoGUI(MenuSource root) + public static void ClearIndent() { _indentLevel = 0; - new VisitorContext(this).PushNode(root); } - public void DoGUI(ModularAvatarMenuInstaller root) + public IDisposable Scope() { - _indentLevel = 0; - new VisitorContext(this).PushMenuInstaller(root); - } + GUILayout.BeginHorizontal(); + GUILayout.Space(_indentLevel); + _indentLevel += INDENT_PER_LEVEL; - public void DoGUI(VRCExpressionsMenu menu, GameObject parameterReference = null) - { - _indentLevel = 0; - new VisitorContext(this).PushNode(menu); - } + EditorGUILayout.BeginVertical(EditorStyles.helpBox); - private void PushGuiNode(object key, Func guiBuilder) - { - if (!_guiNodes.TryGetValue(key, out var gui)) + if (_headerObj != null) { - gui = guiBuilder(); - _guiNodes.Add(key, gui); - } - - gui(); - } - - private class Header - { - private MenuPreviewGUI _gui; - private UnityEngine.Object _headerObj; - private SerializedProperty _disableProp; - - public Header(MenuPreviewGUI gui, UnityEngine.Object headerObj, SerializedProperty disableProp = null) - { - _gui = gui; - _headerObj = headerObj; - _disableProp = disableProp; - } - - public IDisposable Scope() - { - GUILayout.BeginHorizontal(); - GUILayout.Space(_gui._indentLevel); - _gui._indentLevel += INDENT_PER_LEVEL; - - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - - if (_headerObj != null) + var oldIndent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + try { GUILayout.BeginHorizontal(); using (new EditorGUI.DisabledScope(true)) @@ -91,27 +60,66 @@ namespace nadena.dev.modular_avatar.core.editor GUILayout.EndHorizontal(); } - - return new ScopeSentinel(_gui); + finally + { + EditorGUI.indentLevel = oldIndent; + } } - private class ScopeSentinel : IDisposable + return new ScopeSentinel(); + } + + private class ScopeSentinel : IDisposable + { + public ScopeSentinel() { - private readonly MenuPreviewGUI _gui; + } - public ScopeSentinel(MenuPreviewGUI gui) - { - _gui = gui; - } - - public void Dispose() - { - GUILayout.EndVertical(); - _gui._indentLevel -= INDENT_PER_LEVEL; - GUILayout.EndHorizontal(); - } + public void Dispose() + { + GUILayout.EndVertical(); + _indentLevel -= INDENT_PER_LEVEL; + GUILayout.EndHorizontal(); } } + } + + internal class MenuPreviewGUI + { + private Action _redraw; + private readonly Dictionary _guiNodes = new Dictionary(); + + public MenuPreviewGUI(Action redraw) + { + _redraw = redraw; + } + + public void DoGUI(MenuSource root) + { + new VisitorContext(this).PushNode(root); + } + + public void DoGUI(ModularAvatarMenuInstaller root) + { + new VisitorContext(this).PushMenuInstaller(root); + } + + public void DoGUI(VRCExpressionsMenu menu, GameObject parameterReference = null) + { + new VisitorContext(this).PushNode(menu); + } + + private void PushGuiNode(object key, Func guiBuilder) + { + if (!_guiNodes.TryGetValue(key, out var gui)) + { + gui = guiBuilder(); + _guiNodes.Add(key, gui); + } + + gui(); + } + private class VisitorContext : NodeContext { @@ -127,7 +135,7 @@ namespace nadena.dev.modular_avatar.core.editor { _gui.PushGuiNode((expMenu, parameterReference), () => { - var header = new Header(_gui, expMenu); + var header = new MenuObjectHeader(expMenu); var obj = new SerializedObject(expMenu); var controls = obj.FindProperty(nameof(expMenu.controls)); var subGui = new List(); @@ -143,7 +151,7 @@ namespace nadena.dev.modular_avatar.core.editor { foreach (var gui in subGui) { - using (new Header(_gui, null).Scope()) + using (new MenuObjectHeader(null).Scope()) { gui.DoGUI(); } @@ -164,7 +172,7 @@ namespace nadena.dev.modular_avatar.core.editor { _gui.PushGuiNode(item, () => { - var header = new Header(_gui, item, + var header = new MenuObjectHeader(item, new SerializedObject(item.gameObject).FindProperty("m_IsActive")); var gui = new MenuItemCoreGUI(new SerializedObject(item), _gui._redraw); return () => @@ -178,7 +186,7 @@ namespace nadena.dev.modular_avatar.core.editor } else { - using (new Header(_gui, source as UnityEngine.Object).Scope()) + using (new MenuObjectHeader(source as UnityEngine.Object).Scope()) { if (_visited.Contains(source)) return; _visited.Add(source); @@ -190,7 +198,7 @@ namespace nadena.dev.modular_avatar.core.editor public void PushNode(ModularAvatarMenuInstaller installer) { - using (new Header(_gui, installer).Scope()) + using (new MenuObjectHeader(installer).Scope()) { PushMenuInstaller(installer); } diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/ToggleGroupInspector.cs b/Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/ToggleGroupInspector.cs new file mode 100644 index 00000000..601909fc --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/ToggleGroupInspector.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine; +using UnityEngine.UIElements; + +namespace nadena.dev.modular_avatar.core.editor +{ + [CustomEditor(typeof(ToggleGroup))] + internal class ToggleGroupInspector : MAEditorBase + { + private void OnEnable() + { + EditorApplication.hierarchyChanged += Invalidate; + } + + private void OnDisable() + { + EditorApplication.hierarchyChanged -= Invalidate; + } + + private List _menuItemActions = null; + + private void Invalidate() + { + var target = (ToggleGroup) this.target; + var avatar = RuntimeUtil.FindAvatarInParents(target.transform); + var menuItems = avatar.GetComponentsInChildren(true); + + _menuItemActions = new List(); + foreach (var menuItem in menuItems.Where(item => item.toggleGroup == target)) + { + var node = CreateMenuItemNode(menuItem); + _menuItemActions.Add(node); + } + } + + private Action CreateMenuItemNode(ModularAvatarMenuItem menuItem) + { + bool foldout = false; + + var coreUI = new MenuItemCoreGUI(new SerializedObject(menuItem), Repaint); + var enableMenuItem = new SerializedObject(menuItem.gameObject).FindProperty("m_IsActive"); + + List foldoutInspectors = null; + + return () => + { + using (new MenuObjectHeader(menuItem, enableMenuItem).Scope()) + { + coreUI.DoGUI(); + + foldout = EditorGUILayout.Foldout(foldout, "Actions"); + if (foldout) + { + if (foldoutInspectors == null) + { + foldoutInspectors = menuItem.GetComponents() + .Select(action => + { + var component = (Component) action; + var editor = CreateEditor(component); + var enabled_prop = new SerializedObject(component).FindProperty("m_Enabled"); + + return (Action) (() => + { + using (new MenuObjectHeader(component, enabled_prop).Scope()) + { + editor.OnInspectorGUI(); + } + }); + }) + .ToList(); + } + + foreach (var inspector in foldoutInspectors) + { + inspector(); + } + } + } + }; + } + + protected override void OnInnerInspectorGUI() + { + if (_menuItemActions == null) Invalidate(); + + EditorGUILayout.LabelField("Bound menu items", EditorStyles.boldLabel); + foreach (var action in _menuItemActions) + { + try + { + EditorGUI.indentLevel++; + action(); + } + finally + { + EditorGUI.indentLevel--; + } + + EditorGUILayout.Space(4); + } + } + } +} \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/ToggleGroupInspector.cs.meta b/Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/ToggleGroupInspector.cs.meta new file mode 100644 index 00000000..8fbebf1d --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/Menu/ToggleGroupInspector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 357527439f084cd6812d6e5dcd9692f8 +timeCreated: 1677595893 \ No newline at end of file