Add initial editors for parameters/menu installers

This commit is contained in:
bd_ 2022-10-04 17:34:04 -07:00 committed by bd_
parent c0ea7f7e7e
commit 8ee6771229
10 changed files with 419 additions and 0 deletions

View File

@ -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);
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8d6eb9f3b9084abb9453139d5b48ae18
timeCreated: 1664855743

View File

@ -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();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 21089a5ec87c4e4fa1616376cbb8fbd7
timeCreated: 1664847495

View File

@ -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();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fcf1a7e74b8a4e7e98e7c557ecb8375e
timeCreated: 1664853873

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7ef83cb0c23d4d7c9d41021e544a1978
timeCreated: 1664850837

View File

@ -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>();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 71a96d4ea0c344f39e277d82035bf9bd
timeCreated: 1664847329