ToggleGroup UI

This commit is contained in:
bd_ 2023-03-01 00:26:45 +09:00
parent 64d831672a
commit f2ef8099b0
4 changed files with 199 additions and 74 deletions

View File

@ -81,10 +81,7 @@ namespace nadena.dev.modular_avatar.core.editor
protected virtual VisualElement CreateInnerInspectorGUI() protected virtual VisualElement CreateInnerInspectorGUI()
{ {
var throwaway = new InspectorElement(); return null;
MethodInfo m = typeof(InspectorElement).GetMethod("CreateIMGUIInspectorFromEditor",
BindingFlags.NonPublic | BindingFlags.Instance);
return m.Invoke(throwaway, new object[] {serializedObject, this, false}) as VisualElement;
} }
public sealed override VisualElement CreateInspectorGUI() public sealed override VisualElement CreateInspectorGUI()
@ -94,10 +91,19 @@ namespace nadena.dev.modular_avatar.core.editor
var inner = CreateInnerInspectorGUI(); 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 = new MAVisualElement();
_visualElement.Add(inner); _visualElement.Add(inner);
_suppressOnceDefaultMargins = true; _suppressOnceDefaultMargins = innerIsImgui;
return _visualElement; return _visualElement;
} }

View File

@ -7,69 +7,38 @@ using VRC.SDK3.Avatars.ScriptableObjects;
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
internal class MenuPreviewGUI internal class MenuObjectHeader
{ {
private const float INDENT_PER_LEVEL = 2; private const float INDENT_PER_LEVEL = 2;
private Action _redraw; private static float _indentLevel = 0;
private float _indentLevel = 0;
private readonly Dictionary<object, Action> _guiNodes = new Dictionary<object, Action>();
public MenuPreviewGUI(Action redraw)
{
_redraw = redraw;
}
public void DoGUI(MenuSource root)
{
_indentLevel = 0;
new VisitorContext(this).PushNode(root);
}
public void DoGUI(ModularAvatarMenuInstaller root)
{
_indentLevel = 0;
new VisitorContext(this).PushMenuInstaller(root);
}
public void DoGUI(VRCExpressionsMenu menu, GameObject parameterReference = null)
{
_indentLevel = 0;
new VisitorContext(this).PushNode(menu);
}
private void PushGuiNode(object key, Func<Action> guiBuilder)
{
if (!_guiNodes.TryGetValue(key, out var gui))
{
gui = guiBuilder();
_guiNodes.Add(key, gui);
}
gui();
}
private class Header
{
private MenuPreviewGUI _gui;
private UnityEngine.Object _headerObj; private UnityEngine.Object _headerObj;
private SerializedProperty _disableProp; private SerializedProperty _disableProp;
public Header(MenuPreviewGUI gui, UnityEngine.Object headerObj, SerializedProperty disableProp = null) public MenuObjectHeader(UnityEngine.Object headerObj, SerializedProperty disableProp = null)
{ {
_gui = gui;
_headerObj = headerObj; _headerObj = headerObj;
_disableProp = disableProp; _disableProp = disableProp;
} }
public static void ClearIndent()
{
_indentLevel = 0;
}
public IDisposable Scope() public IDisposable Scope()
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.Space(_gui._indentLevel); GUILayout.Space(_indentLevel);
_gui._indentLevel += INDENT_PER_LEVEL; _indentLevel += INDENT_PER_LEVEL;
EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.BeginVertical(EditorStyles.helpBox);
if (_headerObj != null) if (_headerObj != null)
{
var oldIndent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
try
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
using (new EditorGUI.DisabledScope(true)) using (new EditorGUI.DisabledScope(true))
@ -91,28 +60,67 @@ namespace nadena.dev.modular_avatar.core.editor
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
finally
{
EditorGUI.indentLevel = oldIndent;
}
}
return new ScopeSentinel(_gui); return new ScopeSentinel();
} }
private class ScopeSentinel : IDisposable private class ScopeSentinel : IDisposable
{ {
private readonly MenuPreviewGUI _gui; public ScopeSentinel()
public ScopeSentinel(MenuPreviewGUI gui)
{ {
_gui = gui;
} }
public void Dispose() public void Dispose()
{ {
GUILayout.EndVertical(); GUILayout.EndVertical();
_gui._indentLevel -= INDENT_PER_LEVEL; _indentLevel -= INDENT_PER_LEVEL;
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
} }
} }
internal class MenuPreviewGUI
{
private Action _redraw;
private readonly Dictionary<object, Action> _guiNodes = new Dictionary<object, Action>();
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<Action> guiBuilder)
{
if (!_guiNodes.TryGetValue(key, out var gui))
{
gui = guiBuilder();
_guiNodes.Add(key, gui);
}
gui();
}
private class VisitorContext : NodeContext private class VisitorContext : NodeContext
{ {
private readonly HashSet<object> _visited = new HashSet<object>(); private readonly HashSet<object> _visited = new HashSet<object>();
@ -127,7 +135,7 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
_gui.PushGuiNode((expMenu, parameterReference), () => _gui.PushGuiNode((expMenu, parameterReference), () =>
{ {
var header = new Header(_gui, expMenu); var header = new MenuObjectHeader(expMenu);
var obj = new SerializedObject(expMenu); var obj = new SerializedObject(expMenu);
var controls = obj.FindProperty(nameof(expMenu.controls)); var controls = obj.FindProperty(nameof(expMenu.controls));
var subGui = new List<MenuItemCoreGUI>(); var subGui = new List<MenuItemCoreGUI>();
@ -143,7 +151,7 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
foreach (var gui in subGui) foreach (var gui in subGui)
{ {
using (new Header(_gui, null).Scope()) using (new MenuObjectHeader(null).Scope())
{ {
gui.DoGUI(); gui.DoGUI();
} }
@ -164,7 +172,7 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
_gui.PushGuiNode(item, () => _gui.PushGuiNode(item, () =>
{ {
var header = new Header(_gui, item, var header = new MenuObjectHeader(item,
new SerializedObject(item.gameObject).FindProperty("m_IsActive")); new SerializedObject(item.gameObject).FindProperty("m_IsActive"));
var gui = new MenuItemCoreGUI(new SerializedObject(item), _gui._redraw); var gui = new MenuItemCoreGUI(new SerializedObject(item), _gui._redraw);
return () => return () =>
@ -178,7 +186,7 @@ namespace nadena.dev.modular_avatar.core.editor
} }
else else
{ {
using (new Header(_gui, source as UnityEngine.Object).Scope()) using (new MenuObjectHeader(source as UnityEngine.Object).Scope())
{ {
if (_visited.Contains(source)) return; if (_visited.Contains(source)) return;
_visited.Add(source); _visited.Add(source);
@ -190,7 +198,7 @@ namespace nadena.dev.modular_avatar.core.editor
public void PushNode(ModularAvatarMenuInstaller installer) public void PushNode(ModularAvatarMenuInstaller installer)
{ {
using (new Header(_gui, installer).Scope()) using (new MenuObjectHeader(installer).Scope())
{ {
PushMenuInstaller(installer); PushMenuInstaller(installer);
} }

View File

@ -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<Action> _menuItemActions = null;
private void Invalidate()
{
var target = (ToggleGroup) this.target;
var avatar = RuntimeUtil.FindAvatarInParents(target.transform);
var menuItems = avatar.GetComponentsInChildren<ModularAvatarMenuItem>(true);
_menuItemActions = new List<Action>();
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<Action> foldoutInspectors = null;
return () =>
{
using (new MenuObjectHeader(menuItem, enableMenuItem).Scope())
{
coreUI.DoGUI();
foldout = EditorGUILayout.Foldout(foldout, "Actions");
if (foldout)
{
if (foldoutInspectors == null)
{
foldoutInspectors = menuItem.GetComponents<MenuAction>()
.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);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 357527439f084cd6812d6e5dcd9692f8
timeCreated: 1677595893