mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-04-11 23:19:00 +08:00
Add MenuFolderCreatorModule
This commit is contained in:
parent
19a6923051
commit
7bf3136e00
@ -138,6 +138,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
|
||||
new RenameParametersHook().OnPreprocessAvatar(avatarGameObject);
|
||||
new MenuFolderCreateHook().OnPreprocessAvatar(avatarGameObject);
|
||||
new MenuInstallHook().OnPreprocessAvatar(avatarGameObject);
|
||||
new MergeArmatureHook().OnPreprocessAvatar(avatarGameObject);
|
||||
new RetargetMeshes().OnPreprocessAvatar(avatarGameObject);
|
||||
|
@ -0,0 +1,197 @@
|
||||
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 ModularAvatarMenuFolderCreator Creator {
|
||||
set => this._treeView.Creator = value;
|
||||
}
|
||||
|
||||
private Action<ModularAvatarMenuFolderCreator> 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, ModularAvatarMenuFolderCreator creator, Action<ModularAvatarMenuFolderCreator> 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 ModularAvatarMenuFolderCreator _creator;
|
||||
public ModularAvatarMenuFolderCreator Creator {
|
||||
get => this._creator;
|
||||
set {
|
||||
this._creator = value;
|
||||
this.Reload();
|
||||
}
|
||||
}
|
||||
|
||||
private int _currentCreatorIndex;
|
||||
private readonly Texture2D _currentBackgroundTexture;
|
||||
|
||||
internal Action<ModularAvatarMenuFolderCreator> OnSelect = (creator) => { };
|
||||
internal Action onDoubleClickSelect = () => { };
|
||||
|
||||
private readonly List<ModularAvatarMenuFolderCreator> _creatorItems = new List<ModularAvatarMenuFolderCreator>();
|
||||
private readonly HashSet<ModularAvatarMenuFolderCreator> _visitedCreators = new HashSet<ModularAvatarMenuFolderCreator>();
|
||||
|
||||
private Dictionary<ModularAvatarMenuFolderCreator, List<ModularAvatarMenuFolderCreator>> _childMap;
|
||||
private List<ModularAvatarMenuFolderCreator> _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 (ModularAvatarMenuFolderCreator 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, ModularAvatarMenuFolderCreator creator) {
|
||||
if (!this._childMap.TryGetValue(creator, out List<ModularAvatarMenuFolderCreator> children)) return;
|
||||
foreach (ModularAvatarMenuFolderCreator 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<ModularAvatarMenuFolderCreator, List<ModularAvatarMenuFolderCreator>>();
|
||||
this._rootCreators = new List<ModularAvatarMenuFolderCreator>();
|
||||
|
||||
foreach (ModularAvatarMenuFolderCreator creator in this.Avatar.gameObject.GetComponentsInChildren<ModularAvatarMenuFolderCreator>()) {
|
||||
if (!creator.enabled) continue;
|
||||
if (creator.installTargetType == ModularAvatarMenuFolderCreator.InstallTargetType.VRCExpressionMenu) {
|
||||
this._rootCreators.Add(creator);
|
||||
} else {
|
||||
if (creator.installTargetFolderCreator == null) {
|
||||
this._rootCreators.Add(creator);
|
||||
} else {
|
||||
if (!this._childMap.TryGetValue(creator.installTargetFolderCreator, out List<ModularAvatarMenuFolderCreator> children)) {
|
||||
children = new List<ModularAvatarMenuFolderCreator>();
|
||||
this._childMap[creator.installTargetFolderCreator] = children;
|
||||
}
|
||||
|
||||
children.Add(creator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8947dd0003544292b4fa12250f5771c9
|
||||
timeCreated: 1669814530
|
@ -0,0 +1,198 @@
|
||||
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.ModularAvatarMenuFolderCreator;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor {
|
||||
[CustomEditor(typeof(ModularAvatarMenuFolderCreator))]
|
||||
[CanEditMultipleObjects]
|
||||
internal class MenuFolderCreatorEditor : MAEditorBase {
|
||||
private ModularAvatarMenuFolderCreator _creator;
|
||||
private HashSet<VRCExpressionsMenu> _avatarMenus;
|
||||
private HashSet<ModularAvatarMenuFolderCreator> _menuFolderCreators;
|
||||
|
||||
private void OnEnable() {
|
||||
this._creator = (ModularAvatarMenuFolderCreator)this.target;
|
||||
this.FindMenus();
|
||||
this.FindMenuFolderCreators();
|
||||
}
|
||||
|
||||
protected override void OnInnerInspectorGUI() {
|
||||
VRCAvatarDescriptor commonAvatar = this.FindCommonAvatar();
|
||||
|
||||
SerializedProperty installTargetTypeProperty = this.serializedObject.FindProperty(nameof(ModularAvatarMenuFolderCreator.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(ModularAvatarMenuFolderCreator.installTargetMenu);
|
||||
installTargetObjectType = typeof(VRCExpressionsMenu);
|
||||
} else {
|
||||
installTargetMenuPropertyName = nameof(ModularAvatarMenuFolderCreator.installTargetFolderCreator);
|
||||
installTargetObjectType = typeof(ModularAvatarMenuFolderCreator);
|
||||
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, menu => {
|
||||
installTargetProperty.objectReferenceValue = menu;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
} else {
|
||||
AvMenuFolderCreatorTreeViewWindow.Show(avatar, this._creator, creator => {
|
||||
installTargetProperty.objectReferenceValue = creator;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SerializedProperty folderNameProperty = this.serializedObject.FindProperty(nameof(ModularAvatarMenuFolderCreator.folderName));
|
||||
EditorGUILayout.PropertyField(folderNameProperty, new GUIContent("Folder Name"));
|
||||
|
||||
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, (ModularAvatarMenuFolderCreator)installTargetProperty.objectReferenceValue,
|
||||
new HashSet<ModularAvatarMenuFolderCreator>())) {
|
||||
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(ModularAvatarMenuFolderCreator));
|
||||
if (newValue == this._creator) newValue = displayValue;
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
installTargetProperty.objectReferenceValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
private VRCAvatarDescriptor FindCommonAvatar() {
|
||||
VRCAvatarDescriptor commonAvatar = null;
|
||||
foreach (Object targetObject in targets) {
|
||||
ModularAvatarMenuFolderCreator component = (ModularAvatarMenuFolderCreator)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<ModularAvatarMenuFolderCreator>();
|
||||
VRCAvatarDescriptor avatar = RuntimeUtil.FindAvatarInParents(this._creator.transform);
|
||||
if (avatar == null) return;
|
||||
foreach (ModularAvatarMenuFolderCreator creator in avatar.gameObject
|
||||
.GetComponentsInChildren<ModularAvatarMenuFolderCreator>()
|
||||
.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, ModularAvatarMenuFolderCreator creator, HashSet<ModularAvatarMenuFolderCreator> 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.installTargetFolderCreator == null || this.IsMenuReachable(avatar, creator.installTargetFolderCreator, session);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87c99ae6e87240aa9b53fdbf8e962107
|
||||
timeCreated: 1669789624
|
@ -1,11 +1,14 @@
|
||||
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.editor.Localization;
|
||||
using static nadena.dev.modular_avatar.core.editor.Util;
|
||||
using static nadena.dev.modular_avatar.core.ModularAvatarMenuFolderCreator;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
@ -21,12 +24,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
private bool _devFoldout;
|
||||
|
||||
private HashSet<VRCExpressionsMenu> _avatarMenus;
|
||||
private HashSet<ModularAvatarMenuFolderCreator> _menuFolderCreators;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_installer = (ModularAvatarMenuInstaller) target;
|
||||
|
||||
FindMenus();
|
||||
FindMenuFolderCreators();
|
||||
}
|
||||
|
||||
private void SetupMenuEditor()
|
||||
@ -47,59 +52,54 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
_menuToAppend = _installer.menuToAppend;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
SetupMenuEditor();
|
||||
|
||||
var installTo = serializedObject.FindProperty(nameof(ModularAvatarMenuInstaller.installTargetMenu));
|
||||
|
||||
var isEnabled = targets.Length != 1 || ((ModularAvatarMenuInstaller) target).enabled;
|
||||
|
||||
|
||||
VRCAvatarDescriptor commonAvatar = FindCommonAvatar();
|
||||
|
||||
if (!installTo.hasMultipleDifferentValues)
|
||||
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.objectReferenceValue == null)
|
||||
string installTargetMenuPropertyName;
|
||||
Type installTargetObjectType;
|
||||
if (installTargetType == InstallTargetType.VRCExpressionMenu)
|
||||
{
|
||||
if (isEnabled)
|
||||
installTargetMenuPropertyName = nameof(ModularAvatarMenuFolderCreator.installTargetMenu);
|
||||
installTargetObjectType = typeof(VRCExpressionsMenu);
|
||||
} else
|
||||
{
|
||||
installTargetMenuPropertyName = nameof(ModularAvatarMenuFolderCreator.installTargetFolderCreator);
|
||||
installTargetObjectType = typeof(ModularAvatarMenuFolderCreator);
|
||||
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)
|
||||
{
|
||||
EditorGUILayout.HelpBox(S("menuinstall.help.hint_set_menu"), MessageType.Info);
|
||||
AvMenuTreeViewWindow.Show(avatar, menu =>
|
||||
{
|
||||
installTargetProperty.objectReferenceValue = menu;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
} else {
|
||||
AvMenuFolderCreatorTreeViewWindow.Show(avatar, null, creator =>
|
||||
{
|
||||
installTargetProperty.objectReferenceValue = creator;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
}
|
||||
}
|
||||
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, menu =>
|
||||
{
|
||||
installTo.objectReferenceValue = menu;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (targets.Length == 1)
|
||||
@ -146,6 +146,56 @@ 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, (ModularAvatarMenuFolderCreator)installTargetProperty.objectReferenceValue, new HashSet<ModularAvatarMenuFolderCreator>()))
|
||||
{
|
||||
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(ModularAvatarMenuFolderCreator));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
installTargetProperty.objectReferenceValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
private VRCAvatarDescriptor FindCommonAvatar()
|
||||
{
|
||||
@ -200,11 +250,49 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
|
||||
private void FindMenuFolderCreators()
|
||||
{
|
||||
if (this.targets.Length > 1)
|
||||
{
|
||||
this._menuFolderCreators = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this._menuFolderCreators = new HashSet<ModularAvatarMenuFolderCreator>();
|
||||
VRCAvatarDescriptor avatar = RuntimeUtil.FindAvatarInParents(this._installer.transform);
|
||||
if (avatar == null) return;
|
||||
foreach (ModularAvatarMenuFolderCreator creator in avatar.gameObject.GetComponentsInChildren<ModularAvatarMenuFolderCreator>())
|
||||
{
|
||||
this._menuFolderCreators.Add(creator);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMenuReachable(VRCAvatarDescriptor avatar, VRCExpressionsMenu menu)
|
||||
{
|
||||
return _avatarMenus == null || _avatarMenus.Contains(menu);
|
||||
}
|
||||
|
||||
private bool IsMenuReachable(VRCAvatarDescriptor avatar, ModularAvatarMenuFolderCreator creator, HashSet<ModularAvatarMenuFolderCreator> 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.installTargetFolderCreator == null || this.IsMenuReachable(avatar, creator.installTargetFolderCreator, session);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private static ValidateExpressionMenuIconResult ValidateExpressionMenuIcon(VRCExpressionsMenu menu)
|
||||
{
|
||||
if (menu == null) return ValidateExpressionMenuIconResult.Success;
|
||||
@ -229,6 +317,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
|
||||
return ValidateExpressionMenuIconResult.Success;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
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.ModularAvatarMenuFolderCreator;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor {
|
||||
internal class MenuFolderCreateHook {
|
||||
private static Texture2D _MORE_ICON = AssetDatabase.LoadAssetAtPath<Texture2D>(
|
||||
"Packages/nadena.dev.modular-avatar/Runtime/Icons/Icon_More_A.png"
|
||||
);
|
||||
|
||||
private readonly Dictionary<ModularAvatarMenuFolderCreator, List<ModularAvatarMenuFolderCreator>> _childMap;
|
||||
private readonly List<ModularAvatarMenuFolderCreator> _rootCreators;
|
||||
|
||||
private readonly Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> _clonedMenus;
|
||||
private readonly Dictionary<ModularAvatarMenuFolderCreator, VRCExpressionsMenu> _creatFolders;
|
||||
private Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> _installTargets;
|
||||
|
||||
private VRCExpressionsMenu _rootMenu;
|
||||
|
||||
public MenuFolderCreateHook() {
|
||||
this._childMap = new Dictionary<ModularAvatarMenuFolderCreator, List<ModularAvatarMenuFolderCreator>>();
|
||||
this._rootCreators = new List<ModularAvatarMenuFolderCreator>();
|
||||
this._clonedMenus = new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>();
|
||||
this._creatFolders = new Dictionary<ModularAvatarMenuFolderCreator, 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 (ModularAvatarMenuFolderCreator 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); // TODO: Support Custom Icon
|
||||
if (!this._childMap.TryGetValue(rootCreator, out List<ModularAvatarMenuFolderCreator> children)) continue;
|
||||
foreach (ModularAvatarMenuFolderCreator child in children) {
|
||||
this.CreateChildFolder(child);
|
||||
}
|
||||
this.SplitMenu(rootCreator);
|
||||
|
||||
this.SplitParentMenu(targetMenu, rootCreator);
|
||||
}
|
||||
|
||||
ReassignmentMenuInstaller(avatarRoot);
|
||||
}
|
||||
|
||||
|
||||
private void CreateChildFolder(ModularAvatarMenuFolderCreator creator) {
|
||||
if (!this._creatFolders.TryGetValue(creator.installTargetFolderCreator, out VRCExpressionsMenu targetMenu)) return;
|
||||
if (!this._creatFolders.TryGetValue(creator, out VRCExpressionsMenu folderMenu)) {
|
||||
// 子が1つの親を参照する関係なので、同じ要素が複数現れることはありえない。
|
||||
// 同様に循環参照等にもたどり付けないので考慮に入れなくてよい。
|
||||
folderMenu = CreateMenuAsset();
|
||||
this._creatFolders[creator] = folderMenu;
|
||||
}
|
||||
|
||||
AddSubMenuElement(targetMenu, creator.folderName, folderMenu); // TODO: Support Custom Icon
|
||||
if (!this._childMap.TryGetValue(creator, out List<ModularAvatarMenuFolderCreator> children)) return;
|
||||
foreach (ModularAvatarMenuFolderCreator child in children) {
|
||||
this.CreateChildFolder(child);
|
||||
}
|
||||
|
||||
this.SplitMenu(creator);
|
||||
}
|
||||
|
||||
private void SplitMenu(ModularAvatarMenuFolderCreator 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, ModularAvatarMenuFolderCreator 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.installTargetFolderCreator == null) {
|
||||
installer.installTargetMenu = this._installTargets[installer.installTargetMenu];
|
||||
} else {
|
||||
installer.installTargetMenu = this._creatFolders[installer.installTargetFolderCreator];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (ModularAvatarMenuFolderCreator creator in avatarRoot.GetComponentsInChildren<ModularAvatarMenuFolderCreator>(true)) {
|
||||
if (!creator.enabled) continue;
|
||||
if (creator.installTargetType == InstallTargetType.VRCExpressionMenu) {
|
||||
this._rootCreators.Add(creator);
|
||||
} else {
|
||||
if (creator.installTargetFolderCreator == null) {
|
||||
this._rootCreators.Add(creator);
|
||||
} else {
|
||||
if (!this._childMap.TryGetValue(creator.installTargetFolderCreator, out List<ModularAvatarMenuFolderCreator> children)) {
|
||||
children = new List<ModularAvatarMenuFolderCreator>();
|
||||
this._childMap[creator.installTargetFolderCreator] = children;
|
||||
}
|
||||
|
||||
children.Add(creator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b732d550fcb1441a91b791c6caecac36
|
||||
timeCreated: 1669796064
|
@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core {
|
||||
[AddComponentMenu("Modular Avatar/MA Menu Folder Creator")]
|
||||
public class ModularAvatarMenuFolderCreator : AvatarTagComponent {
|
||||
public InstallTargetType installTargetType;
|
||||
public VRCExpressionsMenu installTargetMenu;
|
||||
public ModularAvatarMenuFolderCreator installTargetFolderCreator;
|
||||
public string folderName;
|
||||
|
||||
|
||||
public enum InstallTargetType {
|
||||
VRCExpressionMenu,
|
||||
FolderCreator,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af43171cf0e846c19c34c0420dac976a
|
||||
timeCreated: 1669789344
|
@ -1,14 +1,16 @@
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
using static nadena.dev.modular_avatar.core.ModularAvatarMenuFolderCreator;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
[AddComponentMenu("Modular Avatar/MA Menu Installer")]
|
||||
public class ModularAvatarMenuInstaller : AvatarTagComponent
|
||||
{
|
||||
public class ModularAvatarMenuInstaller : AvatarTagComponent {
|
||||
public InstallTargetType InstallTargetType;
|
||||
public VRCExpressionsMenu menuToAppend;
|
||||
public VRCExpressionsMenu installTargetMenu;
|
||||
public ModularAvatarMenuFolderCreator installTargetFolderCreator;
|
||||
|
||||
|
||||
// ReSharper disable once Unity.RedundantEventFunction
|
||||
|
Loading…
x
Reference in New Issue
Block a user