remove subMenuCreatorModule

This commit is contained in:
raiti-chan 2022-12-17 21:17:08 +09:00
parent 6b96546c0e
commit 51d59ecad3
10 changed files with 53 additions and 742 deletions

View File

@ -1,197 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
namespace nadena.dev.modular_avatar.core.editor {
public class AvMenuFolderCreatorTreeViewWindow : EditorWindow {
private AvMenuFolderCreatorTreeView _treeView;
private VRCAvatarDescriptor Avatar {
set => this._treeView.Avatar = value;
}
private ModularAvatarSubMenuCreator Creator {
set => this._treeView.Creator = value;
}
private Action<ModularAvatarSubMenuCreator> OnMenuSelected = (creator) => { };
private void Awake() {
this._treeView = new AvMenuFolderCreatorTreeView(new TreeViewState()) {
OnSelect = (creator) => this.OnMenuSelected.Invoke(creator),
onDoubleClickSelect = this.Close
};
}
private void OnLostFocus() {
this.Close();
}
private void OnDisable() {
this.OnMenuSelected = (creator) => { };
}
private void OnGUI() {
if (this._treeView == null || this._treeView.Avatar == null) {
this.Close();
return;
}
this._treeView.OnGUI(new Rect(0, 0, this.position.width, this.position.height));
}
internal static void Show(VRCAvatarDescriptor avatar, ModularAvatarSubMenuCreator creator, Action<ModularAvatarSubMenuCreator> OnSelect) {
AvMenuFolderCreatorTreeViewWindow window = GetWindow<AvMenuFolderCreatorTreeViewWindow>();
window.titleContent = new GUIContent("Select menu folder creator");
window.Avatar = avatar;
window.Creator = creator;
window.OnMenuSelected = OnSelect;
window.Show();
}
}
public class AvMenuFolderCreatorTreeView : TreeView {
private VRCAvatarDescriptor _avatar;
public VRCAvatarDescriptor Avatar {
get => this._avatar;
set {
this._avatar = value;
this.Reload();
}
}
private ModularAvatarSubMenuCreator _creator;
public ModularAvatarSubMenuCreator Creator {
get => this._creator;
set {
this._creator = value;
this.Reload();
}
}
private int _currentCreatorIndex;
private readonly Texture2D _currentBackgroundTexture;
internal Action<ModularAvatarSubMenuCreator> OnSelect = (creator) => { };
internal Action onDoubleClickSelect = () => { };
private readonly List<ModularAvatarSubMenuCreator> _creatorItems = new List<ModularAvatarSubMenuCreator>();
private readonly HashSet<ModularAvatarSubMenuCreator> _visitedCreators = new HashSet<ModularAvatarSubMenuCreator>();
private Dictionary<ModularAvatarSubMenuCreator, List<ModularAvatarSubMenuCreator>> _childMap;
private List<ModularAvatarSubMenuCreator> _rootCreators;
public AvMenuFolderCreatorTreeView(TreeViewState state) : base(state) {
this._currentBackgroundTexture = new Texture2D(1, 1);
this._currentBackgroundTexture.SetPixel(0, 0, new Color(0.0f, 0.3f, 0.0f));
this._currentBackgroundTexture.Apply();
}
protected override void SelectionChanged(IList<int> selectedIds) {
if (selectedIds[0] == this._currentCreatorIndex) return;
this.OnSelect.Invoke(this._creatorItems[selectedIds[0]]);
this.Reload();
}
protected override void DoubleClickedItem(int id) {
if (id == this._currentCreatorIndex) return;
this.OnSelect.Invoke(this._creatorItems[id]);
this.onDoubleClickSelect.Invoke();
}
protected override TreeViewItem BuildRoot() {
this._creatorItems.Clear();
this._visitedCreators.Clear();
this._currentCreatorIndex = -1;
this.MappingFolderCreator();
TreeViewItem root = new TreeViewItem(-1, -1, "<root>");
List<TreeViewItem> treeItems = new List<TreeViewItem>();
treeItems.Add(new TreeViewItem {
id = treeItems.Count,
depth = 0,
displayName = $"{this._avatar.name} ({(this._avatar.expressionsMenu != null ? this._avatar.expressionsMenu.name : null)})"
});
this._creatorItems.Add(null);
foreach (ModularAvatarSubMenuCreator rootCreator in this._rootCreators) {
bool isCurrent = rootCreator == this.Creator;
if (isCurrent) {
this._currentCreatorIndex = treeItems.Count;
}
treeItems.Add(new TreeViewItem {
id = treeItems.Count,
depth = 1,
displayName = isCurrent ? "This" : $"{rootCreator.name} ({rootCreator.folderName})"
});
this._creatorItems.Add(rootCreator);
this._visitedCreators.Add(rootCreator);
if (isCurrent) continue;
this.TraverseCreator(2, treeItems, rootCreator);
}
SetupParentsAndChildrenFromDepths(root, treeItems);
return root;
}
private void TraverseCreator(int depth, List<TreeViewItem> items, ModularAvatarSubMenuCreator creator) {
if (!this._childMap.TryGetValue(creator, out List<ModularAvatarSubMenuCreator> children)) return;
foreach (ModularAvatarSubMenuCreator child in children.Where(child => !this._visitedCreators.Contains(child))) {
bool isCurrent = child == this.Creator;
if (isCurrent) {
this._currentCreatorIndex = items.Count;
}
items.Add(new TreeViewItem {
id = items.Count,
depth = depth,
displayName = isCurrent ? "This" : $"{child.name} ({child.folderName})"
});
this._creatorItems.Add(child);
this._visitedCreators.Add(child);
if (isCurrent) continue;
this.TraverseCreator(depth + 1, items, child);
}
}
protected override void RowGUI(RowGUIArgs args) {
if (args.item.id == this._currentCreatorIndex) {
Rect backGroundRect = args.rowRect;
GUI.DrawTexture(backGroundRect, this._currentBackgroundTexture, ScaleMode.StretchToFill, false, 0);
}
base.RowGUI(args);
}
private void MappingFolderCreator() {
this._childMap = new Dictionary<ModularAvatarSubMenuCreator, List<ModularAvatarSubMenuCreator>>();
this._rootCreators = new List<ModularAvatarSubMenuCreator>();
foreach (ModularAvatarSubMenuCreator creator in this.Avatar.gameObject.GetComponentsInChildren<ModularAvatarSubMenuCreator>()) {
if (!creator.enabled) continue;
if (creator.installTargetType == ModularAvatarSubMenuCreator.InstallTargetType.VRCExpressionMenu) {
this._rootCreators.Add(creator);
} else {
if (creator.installTargetCreator == null) {
this._rootCreators.Add(creator);
} else {
if (!this._childMap.TryGetValue(creator.installTargetCreator, out List<ModularAvatarSubMenuCreator> children)) {
children = new List<ModularAvatarSubMenuCreator>();
this._childMap[creator.installTargetCreator] = children;
}
children.Add(creator);
}
}
}
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 8947dd0003544292b4fa12250f5771c9
timeCreated: 1669814530

View File

@ -7,8 +7,6 @@ using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using static nadena.dev.modular_avatar.core.editor.Localization;
using static nadena.dev.modular_avatar.core.editor.Util;
using static nadena.dev.modular_avatar.core.ModularAvatarSubMenuCreator;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core.editor
{
@ -24,7 +22,6 @@ namespace nadena.dev.modular_avatar.core.editor
private bool _devFoldout;
private HashSet<VRCExpressionsMenu> _avatarMenus;
private HashSet<ModularAvatarSubMenuCreator> _menuFolderCreators;
private Dictionary<VRCExpressionsMenu, List<ModularAvatarMenuInstaller>> _menuInstallersMap;
@ -34,7 +31,6 @@ namespace nadena.dev.modular_avatar.core.editor
FindMenus();
FindMenuInstallers();
FindMenuFolderCreators();
}
private void SetupMenuEditor()
@ -58,51 +54,55 @@ namespace nadena.dev.modular_avatar.core.editor
protected override void OnInnerInspectorGUI()
{
SetupMenuEditor();
var installTo = serializedObject.FindProperty(nameof(ModularAvatarMenuInstaller.installTargetMenu));
var isEnabled = targets.Length != 1 || ((ModularAvatarMenuInstaller) target).enabled;
VRCAvatarDescriptor commonAvatar = FindCommonAvatar();
SerializedProperty installTargetTypeProperty = serializedObject.FindProperty(nameof(ModularAvatarMenuInstaller.InstallTargetType));
EditorGUILayout.PropertyField(installTargetTypeProperty, new GUIContent("Install Target Type"));
InstallTargetType installTargetType = (InstallTargetType)Enum.ToObject(typeof(InstallTargetType), installTargetTypeProperty.enumValueIndex);
if (!installTargetTypeProperty.hasMultipleDifferentValues)
if (!installTo.hasMultipleDifferentValues)
{
string installTargetMenuPropertyName;
Type installTargetObjectType;
if (installTargetType == InstallTargetType.VRCExpressionMenu)
if (installTo.objectReferenceValue == null)
{
installTargetMenuPropertyName = nameof(ModularAvatarSubMenuCreator.installTargetMenu);
installTargetObjectType = typeof(VRCExpressionsMenu);
} else
{
installTargetMenuPropertyName = nameof(ModularAvatarSubMenuCreator.installTargetCreator);
installTargetObjectType = typeof(ModularAvatarSubMenuCreator);
commonAvatar = null;
}
SerializedProperty installTargetProperty = this.serializedObject.FindProperty(installTargetMenuPropertyName);
this.ShowMenuInstallerHelpBox(installTargetProperty, installTargetType);
this.ShowInstallTargetPropertyField(installTargetProperty, commonAvatar, installTargetObjectType);
var avatar = RuntimeUtil.FindAvatarInParents(_installer.transform);
if (avatar != null && GUILayout.Button(G("menuinstall.selectmenu")))
{
if (installTargetType == InstallTargetType.VRCExpressionMenu)
if (isEnabled)
{
AvMenuTreeViewWindow.Show(avatar, _installer, menu =>
{
installTargetProperty.objectReferenceValue = menu;
serializedObject.ApplyModifiedProperties();
});
} else {
AvMenuFolderCreatorTreeViewWindow.Show(avatar, null, creator =>
{
installTargetProperty.objectReferenceValue = creator;
serializedObject.ApplyModifiedProperties();
});
EditorGUILayout.HelpBox(S("menuinstall.help.hint_set_menu"), MessageType.Info);
}
}
else if (!IsMenuReachable(RuntimeUtil.FindAvatarInParents(((Component) target).transform),
(VRCExpressionsMenu) installTo.objectReferenceValue))
{
EditorGUILayout.HelpBox(S("menuinstall.help.hint_bad_menu"), MessageType.Error);
}
}
if (installTo.hasMultipleDifferentValues || commonAvatar == null)
{
EditorGUILayout.PropertyField(installTo, G("menuinstall.installto"));
}
else
{
var displayValue = installTo.objectReferenceValue;
if (displayValue == null) displayValue = commonAvatar.expressionsMenu;
EditorGUI.BeginChangeCheck();
var newValue = EditorGUILayout.ObjectField(G("menuinstall.installto"), displayValue,
typeof(VRCExpressionsMenu), false);
if (EditorGUI.EndChangeCheck())
{
installTo.objectReferenceValue = newValue;
}
}
var avatar = RuntimeUtil.FindAvatarInParents(_installer.transform);
if (avatar != null && GUILayout.Button(G("menuinstall.selectmenu")))
{
AvMenuTreeViewWindow.Show(avatar, _installer, menu =>
{
installTo.objectReferenceValue = menu;
serializedObject.ApplyModifiedProperties();
});
}
if (targets.Length == 1)
@ -149,56 +149,6 @@ namespace nadena.dev.modular_avatar.core.editor
Localization.ShowLanguageUI();
}
private void ShowMenuInstallerHelpBox(SerializedProperty installTargetProperty, InstallTargetType installTargetType)
{
if (installTargetProperty.hasMultipleDifferentValues) return;
bool isEnabled = targets.Length != 1 || this._installer.enabled;
if (installTargetProperty.objectReferenceValue == null)
{
if (!isEnabled) return;
EditorGUILayout.HelpBox(S("menuinstall.help.hint_set_menu"), MessageType.Info);
}
else
{
VRCAvatarDescriptor avatar = RuntimeUtil.FindAvatarInParents(this._installer.transform);
switch (installTargetType)
{
case InstallTargetType.VRCExpressionMenu:
if (!this.IsMenuReachable(avatar, (VRCExpressionsMenu)installTargetProperty.objectReferenceValue))
{
EditorGUILayout.HelpBox(Localization.S("menuinstall.help.hint_bad_menu"), MessageType.Error);
}
break;
case InstallTargetType.FolderCreator:
if (!this.IsMenuReachable(avatar, (ModularAvatarSubMenuCreator)installTargetProperty.objectReferenceValue, new HashSet<ModularAvatarSubMenuCreator>()))
{
EditorGUILayout.HelpBox("選択されたメニューフォルダからアバターまでのパスが見つかりません。", MessageType.Error);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(installTargetType), installTargetType, null);
}
}
}
private void ShowInstallTargetPropertyField(SerializedProperty installTargetProperty, VRCAvatarDescriptor avatar, Type propertyType)
{
Object displayValue = installTargetProperty.objectReferenceValue;
if (!installTargetProperty.hasMultipleDifferentValues && avatar != null)
{
if (displayValue == null) displayValue = avatar.expressionsMenu;
}
EditorGUI.BeginChangeCheck();
Object newValue = EditorGUILayout.ObjectField(G("menuinstall.installto"), displayValue, propertyType,
propertyType == typeof(ModularAvatarSubMenuCreator));
if (EditorGUI.EndChangeCheck())
{
installTargetProperty.objectReferenceValue = newValue;
}
}
private VRCAvatarDescriptor FindCommonAvatar()
{
@ -295,23 +245,6 @@ namespace nadena.dev.modular_avatar.core.editor
}
}
private void FindMenuFolderCreators()
{
if (this.targets.Length > 1)
{
this._menuFolderCreators = null;
return;
}
this._menuFolderCreators = new HashSet<ModularAvatarSubMenuCreator>();
VRCAvatarDescriptor avatar = RuntimeUtil.FindAvatarInParents(this._installer.transform);
if (avatar == null) return;
foreach (ModularAvatarSubMenuCreator creator in avatar.gameObject.GetComponentsInChildren<ModularAvatarSubMenuCreator>())
{
this._menuFolderCreators.Add(creator);
}
}
private bool IsMenuReachable(VRCAvatarDescriptor avatar, VRCExpressionsMenu menu, HashSet<ModularAvatarMenuInstaller> visitedInstaller = null)
{
if (_avatarMenus == null || _avatarMenus.Contains(menu)) return true;

View File

@ -1,201 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using static nadena.dev.modular_avatar.core.ModularAvatarSubMenuCreator;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core.editor {
[CustomEditor(typeof(ModularAvatarSubMenuCreator))]
[CanEditMultipleObjects]
internal class SubMenuCreatorEditor : MAEditorBase {
private ModularAvatarSubMenuCreator _creator;
private HashSet<VRCExpressionsMenu> _avatarMenus;
private HashSet<ModularAvatarSubMenuCreator> _menuFolderCreators;
private void OnEnable() {
this._creator = (ModularAvatarSubMenuCreator)this.target;
this.FindMenus();
this.FindMenuFolderCreators();
}
protected override void OnInnerInspectorGUI() {
VRCAvatarDescriptor commonAvatar = this.FindCommonAvatar();
SerializedProperty installTargetTypeProperty = this.serializedObject.FindProperty(nameof(ModularAvatarSubMenuCreator.installTargetType));
EditorGUILayout.PropertyField(installTargetTypeProperty, new GUIContent("Install Target Type"));
InstallTargetType installTargetType = (InstallTargetType)Enum.ToObject(typeof(InstallTargetType), installTargetTypeProperty.enumValueIndex);
if (!installTargetTypeProperty.hasMultipleDifferentValues) {
string installTargetMenuPropertyName;
Type installTargetObjectType;
if (installTargetType == InstallTargetType.VRCExpressionMenu) {
installTargetMenuPropertyName = nameof(ModularAvatarSubMenuCreator.installTargetMenu);
installTargetObjectType = typeof(VRCExpressionsMenu);
} else {
installTargetMenuPropertyName = nameof(ModularAvatarSubMenuCreator.installTargetCreator);
installTargetObjectType = typeof(ModularAvatarSubMenuCreator);
commonAvatar = null;
}
SerializedProperty installTargetProperty = this.serializedObject.FindProperty(installTargetMenuPropertyName);
this.ShowMenuFolderCreateHelpBox(installTargetProperty, installTargetType);
this.ShowInstallTargetPropertyField(installTargetProperty, commonAvatar, installTargetObjectType);
VRCAvatarDescriptor avatar = RuntimeUtil.FindAvatarInParents(this._creator.transform);
if (avatar != null && GUILayout.Button(Localization.G("menuinstall.selectmenu"))) {
if (installTargetType == InstallTargetType.VRCExpressionMenu) {
AvMenuTreeViewWindow.Show(avatar, null, menu => {
installTargetProperty.objectReferenceValue = menu;
serializedObject.ApplyModifiedProperties();
});
} else {
AvMenuFolderCreatorTreeViewWindow.Show(avatar, this._creator, creator => {
installTargetProperty.objectReferenceValue = creator;
serializedObject.ApplyModifiedProperties();
});
}
}
}
SerializedProperty folderNameProperty = this.serializedObject.FindProperty(nameof(ModularAvatarSubMenuCreator.folderName));
EditorGUILayout.PropertyField(folderNameProperty, new GUIContent("Folder Name"));
SerializedProperty iconProperty = this.serializedObject.FindProperty(nameof(ModularAvatarSubMenuCreator.icon));
EditorGUILayout.PropertyField(iconProperty, new GUIContent("Folder Icon"));
serializedObject.ApplyModifiedProperties();
Localization.ShowLanguageUI();
}
private void ShowMenuFolderCreateHelpBox(SerializedProperty installTargetProperty, InstallTargetType installTargetType) {
if (installTargetProperty.hasMultipleDifferentValues) return;
bool isEnabled = this.targets.Length != 1 || this._creator.enabled;
if (installTargetProperty.objectReferenceValue == null) {
if (!isEnabled) return;
EditorGUILayout.HelpBox(Localization.S("menuinstall.help.hint_set_menu"), MessageType.Info);
} else {
VRCAvatarDescriptor avatar = RuntimeUtil.FindAvatarInParents(this._creator.transform);
switch (installTargetType) {
case InstallTargetType.VRCExpressionMenu:
if (!this.IsMenuReachable(avatar, (VRCExpressionsMenu)installTargetProperty.objectReferenceValue)) {
EditorGUILayout.HelpBox(Localization.S("menuinstall.help.hint_bad_menu"), MessageType.Error);
}
break;
case InstallTargetType.FolderCreator:
if (!this.IsMenuReachable(avatar, (ModularAvatarSubMenuCreator)installTargetProperty.objectReferenceValue,
new HashSet<ModularAvatarSubMenuCreator>())) {
EditorGUILayout.HelpBox("選択されたメニューフォルダからアバターまでのパスが見つかりません。", MessageType.Error);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(installTargetType), installTargetType, null);
}
}
}
private void ShowInstallTargetPropertyField(SerializedProperty installTargetProperty, VRCAvatarDescriptor avatar, Type propertyType) {
Object displayValue = installTargetProperty.objectReferenceValue;
if (!installTargetProperty.hasMultipleDifferentValues && avatar != null) {
if (displayValue == null) displayValue = avatar.expressionsMenu;
}
EditorGUI.BeginChangeCheck();
Object newValue = EditorGUILayout.ObjectField(Localization.G("menuinstall.installto"), displayValue, propertyType,
propertyType == typeof(ModularAvatarSubMenuCreator));
if (newValue == this._creator) newValue = displayValue;
if (EditorGUI.EndChangeCheck()) {
installTargetProperty.objectReferenceValue = newValue;
}
}
private VRCAvatarDescriptor FindCommonAvatar() {
VRCAvatarDescriptor commonAvatar = null;
foreach (Object targetObject in targets) {
ModularAvatarSubMenuCreator component = (ModularAvatarSubMenuCreator)targetObject;
VRCAvatarDescriptor avatar = RuntimeUtil.FindAvatarInParents(component.transform);
if (avatar == null) return null;
if (commonAvatar == null) {
commonAvatar = avatar;
} else if (commonAvatar != avatar) {
return null;
}
}
return commonAvatar;
}
private void FindMenus() {
if (this.targets.Length > 1) {
this._avatarMenus = null;
return;
}
this._avatarMenus = new HashSet<VRCExpressionsMenu>();
Queue<VRCExpressionsMenu> queue = new Queue<VRCExpressionsMenu>();
VRCAvatarDescriptor avatar = RuntimeUtil.FindAvatarInParents(this._creator.transform);
if (avatar == null || avatar.expressionsMenu == null) return;
queue.Enqueue(avatar.expressionsMenu);
while (queue.Count > 0) {
var menu = queue.Dequeue();
if (this._avatarMenus.Contains(menu)) continue;
this._avatarMenus.Add(menu);
IEnumerable<VRCExpressionsMenu> subMenus = menu.controls
.Where(control => control.type == VRCExpressionsMenu.Control.ControlType.SubMenu)
.Select(control => control.subMenu);
foreach (VRCExpressionsMenu subMenu in subMenus) {
queue.Enqueue(subMenu);
}
}
}
private void FindMenuFolderCreators() {
if (this.targets.Length > 1) {
this._menuFolderCreators = null;
return;
}
this._menuFolderCreators = new HashSet<ModularAvatarSubMenuCreator>();
VRCAvatarDescriptor avatar = RuntimeUtil.FindAvatarInParents(this._creator.transform);
if (avatar == null) return;
foreach (ModularAvatarSubMenuCreator creator in avatar.gameObject
.GetComponentsInChildren<ModularAvatarSubMenuCreator>()
.Where(creator => creator != this._creator)) {
this._menuFolderCreators.Add(creator);
}
}
private bool IsMenuReachable(VRCAvatarDescriptor avatar, VRCExpressionsMenu menu) {
return this._avatarMenus == null || this._avatarMenus.Contains(menu);
}
private bool IsMenuReachable(VRCAvatarDescriptor avatar, ModularAvatarSubMenuCreator creator, HashSet<ModularAvatarSubMenuCreator> session) {
if (avatar == null) return true;
if (this._menuFolderCreators == null) return true;
if (session.Contains(creator)) return false;
if (!this._menuFolderCreators.Contains(creator)) return false;
if (!creator.enabled) return false;
session.Add(creator);
switch (creator.installTargetType) {
case InstallTargetType.VRCExpressionMenu:
return creator.installTargetMenu == null || this.IsMenuReachable(avatar, creator.installTargetMenu);
case InstallTargetType.FolderCreator:
return creator.installTargetCreator == null || this.IsMenuReachable(avatar, creator.installTargetCreator, session);
default:
throw new ArgumentOutOfRangeException();
}
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 87c99ae6e87240aa9b53fdbf8e962107
timeCreated: 1669789624

View File

@ -1,189 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using static nadena.dev.modular_avatar.core.ModularAvatarSubMenuCreator;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core.editor {
internal class SubMenuCreateHook {
private static readonly Texture2D _MORE_ICON = AssetDatabase.LoadAssetAtPath<Texture2D>(
"Packages/nadena.dev.modular-avatar/Runtime/Icons/Icon_More_A.png"
);
private readonly Dictionary<ModularAvatarSubMenuCreator, List<ModularAvatarSubMenuCreator>> _childMap;
private readonly List<ModularAvatarSubMenuCreator> _rootCreators;
private readonly Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> _clonedMenus;
private readonly Dictionary<ModularAvatarSubMenuCreator, VRCExpressionsMenu> _creatFolders;
private Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> _installTargets;
private VRCExpressionsMenu _rootMenu;
public SubMenuCreateHook() {
this._childMap = new Dictionary<ModularAvatarSubMenuCreator, List<ModularAvatarSubMenuCreator>>();
this._rootCreators = new List<ModularAvatarSubMenuCreator>();
this._clonedMenus = new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>();
this._creatFolders = new Dictionary<ModularAvatarSubMenuCreator, VRCExpressionsMenu>();
}
public void OnPreprocessAvatar(GameObject avatarRoot) {
this._childMap.Clear();
this._rootCreators.Clear();
this._clonedMenus.Clear();
this.MappingFolderCreator(avatarRoot);
VRCAvatarDescriptor avatar = avatarRoot.GetComponent<VRCAvatarDescriptor>();
if (avatar.expressionsMenu == null) {
avatar.expressionsMenu = CreateMenuAsset();
}
this._rootMenu = avatar.expressionsMenu;
avatar.expressionsMenu = this.CloneMenu(avatar.expressionsMenu);
this._installTargets = new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>(this._clonedMenus);
foreach (ModularAvatarSubMenuCreator rootCreator in this._rootCreators.Where(rootCreator => rootCreator.enabled)) {
if (rootCreator.installTargetMenu == null) {
rootCreator.installTargetMenu = this._rootMenu;
}
if (rootCreator.installTargetMenu == null) continue;
if (!this._installTargets.TryGetValue(rootCreator.installTargetMenu, out VRCExpressionsMenu targetMenu)) continue;
if (!this._creatFolders.TryGetValue(rootCreator, out VRCExpressionsMenu folderMenu)) {
folderMenu = CreateMenuAsset();
this._creatFolders[rootCreator] = folderMenu;
}
AddSubMenuElement(targetMenu, rootCreator.folderName, folderMenu, rootCreator.icon);
if (!this._childMap.TryGetValue(rootCreator, out List<ModularAvatarSubMenuCreator> children)) continue;
foreach (ModularAvatarSubMenuCreator child in children) {
this.CreateChildFolder(child);
}
this.SplitMenu(rootCreator);
this.SplitParentMenu(targetMenu, rootCreator);
}
ReassignmentMenuInstaller(avatarRoot);
}
private void CreateChildFolder(ModularAvatarSubMenuCreator creator) {
if (!this._creatFolders.TryGetValue(creator.installTargetCreator, out VRCExpressionsMenu targetMenu)) return;
if (!this._creatFolders.TryGetValue(creator, out VRCExpressionsMenu folderMenu)) {
// 子が1つの親を参照する関係なので、同じ要素が複数現れることはありえない。
// 同様に循環参照等にもたどり付けないので考慮に入れなくてよい。
folderMenu = CreateMenuAsset();
this._creatFolders[creator] = folderMenu;
}
AddSubMenuElement(targetMenu, creator.folderName, folderMenu, creator.icon);
if (!this._childMap.TryGetValue(creator, out List<ModularAvatarSubMenuCreator> children)) return;
foreach (ModularAvatarSubMenuCreator child in children) {
this.CreateChildFolder(child);
}
this.SplitMenu(creator);
}
private void SplitMenu(ModularAvatarSubMenuCreator creator) {
VRCExpressionsMenu targetMenu = this._creatFolders[creator];
while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS) {
VRCExpressionsMenu newMenu = CreateMenuAsset();
const int keepCount = VRCExpressionsMenu.MAX_CONTROLS - 1;
newMenu.controls.AddRange(targetMenu.controls.Skip(keepCount));
targetMenu.controls.RemoveRange(keepCount, targetMenu.controls.Count - keepCount);
AddSubMenuElement(targetMenu, "More", newMenu, _MORE_ICON);
this._creatFolders[creator] = newMenu;
targetMenu = newMenu;
}
}
private void SplitParentMenu(VRCExpressionsMenu targetMenu, ModularAvatarSubMenuCreator rootCreator) {
while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS) {
VRCExpressionsMenu newMenu = CreateMenuAsset();
const int keepCount = VRCExpressionsMenu.MAX_CONTROLS - 1;
newMenu.controls.AddRange(targetMenu.controls.Skip(keepCount));
targetMenu.controls.RemoveRange(keepCount, targetMenu.controls.Count - keepCount);
AddSubMenuElement(targetMenu, "More", newMenu, _MORE_ICON);
this._installTargets[rootCreator.installTargetMenu] = newMenu;
targetMenu = newMenu;
}
}
private void ReassignmentMenuInstaller(GameObject avatarRoot) {
ModularAvatarMenuInstaller[] menuInstallers = avatarRoot.GetComponentsInChildren<ModularAvatarMenuInstaller>(true)
.Where(installer => installer.enabled)
.ToArray();
foreach (ModularAvatarMenuInstaller installer in menuInstallers) {
if (installer.installTargetMenu == null) {
installer.installTargetMenu = this._rootMenu;
}
if (installer.InstallTargetType == InstallTargetType.VRCExpressionMenu || installer.installTargetCreator == null) {
installer.installTargetMenu = this._installTargets[installer.installTargetMenu];
} else {
installer.installTargetMenu = this._creatFolders[installer.installTargetCreator];
}
}
}
private VRCExpressionsMenu CloneMenu(VRCExpressionsMenu menu) {
if (menu == null) return null;
if (this._clonedMenus.TryGetValue(menu, out VRCExpressionsMenu newMenu)) return newMenu;
newMenu = Object.Instantiate(menu);
AssetDatabase.CreateAsset(newMenu, Util.GenerateAssetPath());
this._clonedMenus[menu] = newMenu;
foreach (VRCExpressionsMenu.Control control in newMenu.controls.Where(control => control.type == VRCExpressionsMenu.Control.ControlType.SubMenu)) {
control.subMenu = this.CloneMenu(control.subMenu);
}
return newMenu;
}
private void MappingFolderCreator(GameObject avatarRoot) {
foreach (ModularAvatarSubMenuCreator creator in avatarRoot.GetComponentsInChildren<ModularAvatarSubMenuCreator>(true)) {
if (!creator.enabled) continue;
if (creator.installTargetType == InstallTargetType.VRCExpressionMenu) {
this._rootCreators.Add(creator);
} else {
if (creator.installTargetCreator == null) {
this._rootCreators.Add(creator);
} else {
if (!this._childMap.TryGetValue(creator.installTargetCreator, out List<ModularAvatarSubMenuCreator> children)) {
children = new List<ModularAvatarSubMenuCreator>();
this._childMap[creator.installTargetCreator] = children;
}
children.Add(creator);
}
}
}
}
private static void AddSubMenuElement(VRCExpressionsMenu targetMenu, string elementName, VRCExpressionsMenu subMenu, Texture2D icon = null) {
targetMenu.controls.Add(new VRCExpressionsMenu.Control() {
name = elementName,
type = VRCExpressionsMenu.Control.ControlType.SubMenu,
subMenu = subMenu,
parameter = new VRCExpressionsMenu.Control.Parameter {
name = ""
},
subParameters = Array.Empty<VRCExpressionsMenu.Control.Parameter>(),
icon = icon,
labels = Array.Empty<VRCExpressionsMenu.Control.Label>()
});
}
private static VRCExpressionsMenu CreateMenuAsset() {
VRCExpressionsMenu menuFolder = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
AssetDatabase.CreateAsset(menuFolder, Util.GenerateAssetPath());
return menuFolder;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: b732d550fcb1441a91b791c6caecac36
timeCreated: 1669796064

View File

@ -1,23 +1,20 @@
using UnityEngine;
using UnityEngine.Serialization;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using static nadena.dev.modular_avatar.core.ModularAvatarSubMenuCreator;
namespace nadena.dev.modular_avatar.core
{
[AddComponentMenu("Modular Avatar/MA Menu Installer")]
public class ModularAvatarMenuInstaller : AvatarTagComponent {
public InstallTargetType InstallTargetType;
public VRCExpressionsMenu menuToAppend;
public VRCExpressionsMenu installTargetMenu;
[FormerlySerializedAs("installTargetFolderCreator")] public ModularAvatarSubMenuCreator installTargetCreator;
[AddComponentMenu("Modular Avatar/MA Menu Installer")]
public class ModularAvatarMenuInstaller : AvatarTagComponent
{
public VRCExpressionsMenu menuToAppend;
public VRCExpressionsMenu installTargetMenu;
// ReSharper disable once Unity.RedundantEventFunction
void Start()
{
// Ensure that unity generates an enable checkbox
}
}
// ReSharper disable once Unity.RedundantEventFunction
void Start()
{
// Ensure that unity generates an enable checkbox
}
}
}

View File

@ -1,20 +0,0 @@
using UnityEngine;
using VRC.SDK3.Avatars.ScriptableObjects;
namespace nadena.dev.modular_avatar.core {
[AddComponentMenu("Modular Avatar/MA SubMenu Creator")]
public class ModularAvatarSubMenuCreator : AvatarTagComponent {
public InstallTargetType installTargetType;
public VRCExpressionsMenu installTargetMenu;
public ModularAvatarSubMenuCreator installTargetCreator;
public string folderName;
public Texture2D icon;
public enum InstallTargetType {
VRCExpressionMenu,
FolderCreator,
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: af43171cf0e846c19c34c0420dac976a
timeCreated: 1669789344