mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-01-04 13:45:04 +08:00
Add initial editors for parameters/menu installers
This commit is contained in:
parent
c0ea7f7e7e
commit
8ee6771229
@ -0,0 +1,142 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
using UnityEngine;
|
||||||
|
using VRC.SDK3.Avatars.Components;
|
||||||
|
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||||
|
using VRC.SDKBase;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
class AvMenuTreeViewWindow : EditorWindow
|
||||||
|
{
|
||||||
|
private VRCAvatarDescriptor _avatarDescriptor;
|
||||||
|
private AvMenuTreeView _treeView;
|
||||||
|
|
||||||
|
public VRCAvatarDescriptor Avatar
|
||||||
|
{
|
||||||
|
get => _treeView.Avatar;
|
||||||
|
set => _treeView.Avatar = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action<VRCExpressionsMenu> OnMenuSelected = (menu) => { };
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_treeView = new AvMenuTreeView(new TreeViewState());
|
||||||
|
_treeView.OnSelect = (menu) => OnMenuSelected.Invoke(menu);
|
||||||
|
_treeView.OnDoubleclickSelect = Close;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
OnMenuSelected = (menu) => { };
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI()
|
||||||
|
{
|
||||||
|
if (_treeView == null || _treeView.Avatar == null)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_treeView.OnGUI(new Rect(0, 0, position.width, position.height));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Show(VRCAvatarDescriptor Avatar, Action<VRCExpressionsMenu> OnSelect)
|
||||||
|
{
|
||||||
|
var window = GetWindow<AvMenuTreeViewWindow>();
|
||||||
|
window.titleContent = new GUIContent("Select menu");
|
||||||
|
|
||||||
|
window.Avatar = Avatar;
|
||||||
|
window.OnMenuSelected = OnSelect;
|
||||||
|
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AvMenuTreeView : TreeView
|
||||||
|
{
|
||||||
|
private VRCAvatarDescriptor _avatar;
|
||||||
|
|
||||||
|
public VRCAvatarDescriptor Avatar
|
||||||
|
{
|
||||||
|
get => _avatar;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_avatar = value;
|
||||||
|
Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Action<VRCExpressionsMenu> OnSelect = (menu) => { };
|
||||||
|
internal Action OnDoubleclickSelect = () => { };
|
||||||
|
|
||||||
|
private List<VRCExpressionsMenu> _menuItems = new List<VRCExpressionsMenu>();
|
||||||
|
private HashSet<VRCExpressionsMenu> _visitedMenus = new HashSet<VRCExpressionsMenu>();
|
||||||
|
|
||||||
|
public AvMenuTreeView(TreeViewState state) : base(state)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SelectionChanged(IList<int> selectedIds)
|
||||||
|
{
|
||||||
|
OnSelect.Invoke(_menuItems[selectedIds[0]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DoubleClickedItem(int id)
|
||||||
|
{
|
||||||
|
OnSelect.Invoke(_menuItems[id]);
|
||||||
|
OnDoubleclickSelect.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TreeViewItem BuildRoot()
|
||||||
|
{
|
||||||
|
_menuItems.Clear();
|
||||||
|
_visitedMenus.Clear();
|
||||||
|
|
||||||
|
if (Avatar.expressionsMenu == null)
|
||||||
|
{
|
||||||
|
return new TreeViewItem(0, -1, "No menu");
|
||||||
|
}
|
||||||
|
|
||||||
|
_visitedMenus.Add(Avatar.expressionsMenu);
|
||||||
|
_menuItems.Add(Avatar.expressionsMenu);
|
||||||
|
var root = new TreeViewItem {id = -1, depth = -1, displayName = "<root>"};
|
||||||
|
|
||||||
|
var treeItems = new List<TreeViewItem>();
|
||||||
|
treeItems.Add(new TreeViewItem
|
||||||
|
{id = 0, depth = 0, displayName = $"{Avatar.gameObject.name} ({Avatar.expressionsMenu.name})"});
|
||||||
|
|
||||||
|
TraverseMenu(1, treeItems, Avatar.expressionsMenu);
|
||||||
|
|
||||||
|
SetupParentsAndChildrenFromDepths(root, treeItems);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TraverseMenu(int depth, List<TreeViewItem> items, VRCExpressionsMenu menu)
|
||||||
|
{
|
||||||
|
foreach (var control in menu.controls)
|
||||||
|
{
|
||||||
|
if (control.type == VRCExpressionsMenu.Control.ControlType.SubMenu
|
||||||
|
&& control.subMenu != null && !_visitedMenus.Contains(control.subMenu))
|
||||||
|
{
|
||||||
|
items.Add(new TreeViewItem
|
||||||
|
{
|
||||||
|
id = _menuItems.Count,
|
||||||
|
depth = depth,
|
||||||
|
displayName = $"{control.name} ({control.subMenu.name})"
|
||||||
|
});
|
||||||
|
_menuItems.Add(control.subMenu);
|
||||||
|
_visitedMenus.Add(control.subMenu);
|
||||||
|
|
||||||
|
TraverseMenu(depth + 1, items, control.subMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d6eb9f3b9084abb9453139d5b48ae18
|
||||||
|
timeCreated: 1664855743
|
@ -0,0 +1,143 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditorInternal;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(ModularAvatarParameters))]
|
||||||
|
public class AvatarParametersEditor : Editor
|
||||||
|
{
|
||||||
|
/**********************************************************************
|
||||||
|
* | Field name | Remap to / config |
|
||||||
|
* |------------|--------------------|
|
||||||
|
*/
|
||||||
|
|
||||||
|
private bool _devMode;
|
||||||
|
private ReorderableList _reorderableList;
|
||||||
|
private SerializedProperty _parameters;
|
||||||
|
|
||||||
|
private readonly List<int> _selectedIndices = new List<int>();
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
SetupList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupList()
|
||||||
|
{
|
||||||
|
_parameters = serializedObject.FindProperty(nameof(ModularAvatarParameters.parameters));
|
||||||
|
if (_devMode)
|
||||||
|
{
|
||||||
|
_reorderableList = new ReorderableList(
|
||||||
|
serializedObject,
|
||||||
|
_parameters,
|
||||||
|
true, true, true, true
|
||||||
|
);
|
||||||
|
_reorderableList.drawHeaderCallback = DrawHeader;
|
||||||
|
_reorderableList.drawElementCallback = DrawElement;
|
||||||
|
_reorderableList.onAddCallback = AddElement;
|
||||||
|
_reorderableList.onRemoveCallback = RemoveElement;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_selectedIndices.Clear();
|
||||||
|
for (int i = 0; i < _parameters.arraySize; i++)
|
||||||
|
{
|
||||||
|
var isInternal = _parameters.GetArrayElementAtIndex(i)
|
||||||
|
.FindPropertyRelative(nameof(ParameterConfig.internalParameter));
|
||||||
|
if (isInternal.boolValue) continue;
|
||||||
|
|
||||||
|
_selectedIndices.Add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
_reorderableList = new ReorderableList(
|
||||||
|
_selectedIndices,
|
||||||
|
typeof(int),
|
||||||
|
false, true, false, false
|
||||||
|
);
|
||||||
|
_reorderableList.drawHeaderCallback = DrawHeader;
|
||||||
|
_reorderableList.drawElementCallback = DrawElement;
|
||||||
|
_reorderableList.onAddCallback = AddElement;
|
||||||
|
_reorderableList.onRemoveCallback = RemoveElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddElement(ReorderableList list)
|
||||||
|
{
|
||||||
|
_parameters.arraySize += 1;
|
||||||
|
list.index = _parameters.arraySize - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveElement(ReorderableList list)
|
||||||
|
{
|
||||||
|
if (list.index < 0) return;
|
||||||
|
_parameters.DeleteArrayElementAtIndex(list.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawElement(Rect rect, int index, bool isactive, bool isfocused)
|
||||||
|
{
|
||||||
|
var margin = 20;
|
||||||
|
var halfMargin = margin / 2;
|
||||||
|
var leftHalf = new Rect(rect.x, rect.y, rect.width / 2 - halfMargin, rect.height);
|
||||||
|
var rightHalf = new Rect(rect.x + leftHalf.width + halfMargin, rect.y, leftHalf.width, rect.height);
|
||||||
|
|
||||||
|
if (!_devMode) index = _selectedIndices[index];
|
||||||
|
|
||||||
|
var elem = _parameters.GetArrayElementAtIndex(index);
|
||||||
|
|
||||||
|
var nameOrPrefix = elem.FindPropertyRelative(nameof(ParameterConfig.nameOrPrefix));
|
||||||
|
var remapTo = elem.FindPropertyRelative(nameof(ParameterConfig.remapTo));
|
||||||
|
var internalParameter = elem.FindPropertyRelative(nameof(ParameterConfig.internalParameter));
|
||||||
|
var isPrefix = elem.FindPropertyRelative(nameof(ParameterConfig.isPrefix));
|
||||||
|
|
||||||
|
var indentLevel = EditorGUI.indentLevel;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
indentLevel = 0;
|
||||||
|
if (_devMode)
|
||||||
|
{
|
||||||
|
EditorGUI.PropertyField(leftHalf, nameOrPrefix, GUIContent.none);
|
||||||
|
|
||||||
|
var toggleInternalWidth = EditorStyles.toggle.CalcSize(new GUIContent("Internal")).x;
|
||||||
|
var toggleInternalRect = new Rect(rightHalf.x, rightHalf.y, toggleInternalWidth, rightHalf.height);
|
||||||
|
|
||||||
|
internalParameter.boolValue =
|
||||||
|
EditorGUI.ToggleLeft(toggleInternalRect, "Internal", internalParameter.boolValue);
|
||||||
|
|
||||||
|
var isPrefixRect = new Rect(rightHalf.x + toggleInternalWidth + halfMargin, rightHalf.y,
|
||||||
|
rightHalf.width - toggleInternalWidth - halfMargin, rightHalf.height);
|
||||||
|
isPrefix.boolValue = EditorGUI.ToggleLeft(isPrefixRect, "PhysBones Prefix", isPrefix.boolValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUI.LabelField(leftHalf,
|
||||||
|
isPrefix.boolValue ? nameOrPrefix.stringValue + "*" : nameOrPrefix.stringValue);
|
||||||
|
EditorGUI.PropertyField(rightHalf, remapTo, GUIContent.none);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EditorGUI.indentLevel = indentLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHeader(Rect rect)
|
||||||
|
{
|
||||||
|
var leftHalf = new Rect(rect.x, rect.y, rect.width / 2, rect.height);
|
||||||
|
var rightHalf = new Rect(rect.x + rect.width / 2, rect.y, rect.width / 2, rect.height);
|
||||||
|
|
||||||
|
EditorGUI.LabelField(leftHalf, "Field name");
|
||||||
|
if (!_devMode) EditorGUI.LabelField(rightHalf, "Remap to");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
_devMode = EditorGUILayout.Toggle("Developer mode", _devMode);
|
||||||
|
if (EditorGUI.EndChangeCheck()) SetupList();
|
||||||
|
_reorderableList.DoLayoutList();
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 21089a5ec87c4e4fa1616376cbb8fbd7
|
||||||
|
timeCreated: 1664847495
|
@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(ModularAvatarMenuInstaller))]
|
||||||
|
[CanEditMultipleObjects]
|
||||||
|
public class MenuInstallerEditor : Editor
|
||||||
|
{
|
||||||
|
private ModularAvatarMenuInstaller _installer;
|
||||||
|
private Editor _innerMenuEditor;
|
||||||
|
private VRCExpressionsMenu _menuToAppend;
|
||||||
|
|
||||||
|
private bool _menuFoldout;
|
||||||
|
private bool _devFoldout;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
_installer = (ModularAvatarMenuInstaller) target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupMenuEditor()
|
||||||
|
{
|
||||||
|
if (targets.Length != 1)
|
||||||
|
{
|
||||||
|
_innerMenuEditor = null;
|
||||||
|
_menuToAppend = null;
|
||||||
|
}
|
||||||
|
else if (_installer.menuToAppend != _menuToAppend)
|
||||||
|
{
|
||||||
|
if (_installer.menuToAppend == null) _innerMenuEditor = null;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_innerMenuEditor = CreateEditor(_installer.menuToAppend);
|
||||||
|
}
|
||||||
|
|
||||||
|
_menuToAppend = _installer.menuToAppend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
SetupMenuEditor();
|
||||||
|
|
||||||
|
var installTo = serializedObject.FindProperty(nameof(ModularAvatarMenuInstaller.installTargetMenu));
|
||||||
|
EditorGUILayout.PropertyField(installTo, new GUIContent("Install To"));
|
||||||
|
|
||||||
|
var avatar = RuntimeUtil.FindAvatarInParents(_installer.transform);
|
||||||
|
if (avatar != null && GUILayout.Button("Select menu"))
|
||||||
|
{
|
||||||
|
AvMenuTreeViewWindow.Show(avatar, menu =>
|
||||||
|
{
|
||||||
|
installTo.objectReferenceValue = menu;
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targets.Length == 1)
|
||||||
|
{
|
||||||
|
_menuFoldout = EditorGUILayout.Foldout(_menuFoldout, "Show menu contents");
|
||||||
|
if (_menuFoldout)
|
||||||
|
{
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
using (var disabled = new EditorGUI.DisabledScope(true))
|
||||||
|
{
|
||||||
|
if (_innerMenuEditor != null) _innerMenuEditor.OnInspectorGUI();
|
||||||
|
else EditorGUILayout.HelpBox("No menu selected", MessageType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_devFoldout = EditorGUILayout.Foldout(_devFoldout, "Developer Options");
|
||||||
|
if (_devFoldout)
|
||||||
|
{
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
EditorGUILayout.PropertyField(
|
||||||
|
serializedObject.FindProperty(nameof(ModularAvatarMenuInstaller.menuToAppend)),
|
||||||
|
new GUIContent("Menu to install")
|
||||||
|
);
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fcf1a7e74b8a4e7e98e7c557ecb8375e
|
||||||
|
timeCreated: 1664853873
|
@ -0,0 +1,10 @@
|
|||||||
|
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core
|
||||||
|
{
|
||||||
|
public class ModularAvatarMenuInstaller : AvatarTagComponent
|
||||||
|
{
|
||||||
|
public VRCExpressionsMenu menuToAppend;
|
||||||
|
public VRCExpressionsMenu installTargetMenu;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7ef83cb0c23d4d7c9d41021e544a1978
|
||||||
|
timeCreated: 1664850837
|
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace net.fushizen.modular_avatar.core
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public struct ParameterConfig
|
||||||
|
{
|
||||||
|
public string nameOrPrefix;
|
||||||
|
public string remapTo;
|
||||||
|
public bool internalParameter, isPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModularAvatarParameters : AvatarTagComponent
|
||||||
|
{
|
||||||
|
public List<ParameterConfig> parameters = new List<ParameterConfig>();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 71a96d4ea0c344f39e277d82035bf9bd
|
||||||
|
timeCreated: 1664847329
|
Loading…
Reference in New Issue
Block a user