mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 18:55:06 +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