mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-05-10 13:19:01 +08:00
install target selection UI
This commit is contained in:
parent
52dd314f8a
commit
64664ac949
@ -40,7 +40,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
private void OnLostFocus()
|
||||
{
|
||||
Close();
|
||||
//Close();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.core.editor.menu;
|
||||
using UnityEditor;
|
||||
@ -8,6 +9,7 @@ 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 Object = UnityEngine.Object;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
@ -32,6 +34,74 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
FindMenus();
|
||||
FindMenuInstallers();
|
||||
|
||||
VRCAvatarDescriptor commonAvatar = FindCommonAvatar();
|
||||
}
|
||||
|
||||
private long _cacheSeq = -1;
|
||||
private ImmutableList<object> _cachedTargets = null;
|
||||
|
||||
// Interpretation:
|
||||
// <empty> : Inconsistent install targets
|
||||
// List of [null]: Install to root
|
||||
// List of [VRCExpMenu]: Install to expressions menu
|
||||
// List of [InstallTarget]: Install to single install target
|
||||
// List of [InstallTarget, InstallTarget ...]: Install to multiple install targets
|
||||
private ImmutableList<object> InstallTargets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (VirtualMenu.CacheSequence == _cacheSeq && _cachedTargets != null) return _cachedTargets;
|
||||
|
||||
List<ImmutableList<object>> perTarget = new List<ImmutableList<object>>();
|
||||
|
||||
var commonAvatar = FindCommonAvatar();
|
||||
if (commonAvatar == null)
|
||||
{
|
||||
_cacheSeq = VirtualMenu.CacheSequence;
|
||||
_cachedTargets = ImmutableList<object>.Empty;
|
||||
return _cachedTargets;
|
||||
}
|
||||
|
||||
var virtualMenu = VirtualMenu.ForAvatar(commonAvatar);
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var installer = (ModularAvatarMenuInstaller) target;
|
||||
|
||||
var installTargets = virtualMenu.GetInstallTargetsForInstaller(installer)
|
||||
.Select(o => (object) o).ToImmutableList();
|
||||
if (installTargets.Any())
|
||||
{
|
||||
perTarget.Add(installTargets);
|
||||
}
|
||||
else
|
||||
{
|
||||
perTarget.Add(ImmutableList<object>.Empty.Add(installer.installTargetMenu));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < perTarget.Count; i++)
|
||||
{
|
||||
if (perTarget[0].Count != perTarget[i].Count ||
|
||||
perTarget[0].Zip(perTarget[i], (a, b) => (Resolve(a) != Resolve(b))).Any(differs => differs))
|
||||
{
|
||||
perTarget.Clear();
|
||||
perTarget.Add(ImmutableList<object>.Empty);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_cacheSeq = VirtualMenu.CacheSequence;
|
||||
_cachedTargets = perTarget[0];
|
||||
return _cachedTargets;
|
||||
|
||||
object Resolve(object p0)
|
||||
{
|
||||
if (p0 is ModularAvatarMenuInstallTarget target && target != null) return target.transform.parent;
|
||||
return p0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupMenuEditor()
|
||||
@ -63,27 +133,29 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
VRCAvatarDescriptor commonAvatar = FindCommonAvatar();
|
||||
|
||||
if (!installTo.hasMultipleDifferentValues)
|
||||
if (InstallTargets.Count == 0)
|
||||
{
|
||||
if (installTo.objectReferenceValue == null)
|
||||
// TODO - show warning for inconsistent targets?
|
||||
}
|
||||
else if (InstallTargets.Count > 0)
|
||||
{
|
||||
if (InstallTargets.Count == 1)
|
||||
{
|
||||
if (InstallTargets[0] == null)
|
||||
{
|
||||
if (isEnabled)
|
||||
{
|
||||
EditorGUILayout.HelpBox(S("menuinstall.help.hint_set_menu"), MessageType.Info);
|
||||
}
|
||||
}
|
||||
else if (!IsMenuReachable(RuntimeUtil.FindAvatarInParents(((Component) target).transform),
|
||||
(VRCExpressionsMenu) installTo.objectReferenceValue))
|
||||
else if (InstallTargets[0] is VRCExpressionsMenu menu
|
||||
&& !IsMenuReachable(RuntimeUtil.FindAvatarInParents(((Component) target).transform), menu))
|
||||
{
|
||||
EditorGUILayout.HelpBox(S("menuinstall.help.hint_bad_menu"), MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
if (installTo.hasMultipleDifferentValues || commonAvatar == null)
|
||||
{
|
||||
EditorGUILayout.PropertyField(installTo, G("menuinstall.installto"));
|
||||
}
|
||||
else
|
||||
if (InstallTargets.Count == 1 && (InstallTargets[0] is VRCExpressionsMenu || InstallTargets[0] == null))
|
||||
{
|
||||
var displayValue = installTo.objectReferenceValue;
|
||||
if (displayValue == null) displayValue = commonAvatar.expressionsMenu;
|
||||
@ -94,14 +166,41 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
installTo.objectReferenceValue = newValue;
|
||||
_cacheSeq = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
foreach (var target in InstallTargets)
|
||||
{
|
||||
if (target is VRCExpressionsMenu menu)
|
||||
{
|
||||
EditorGUILayout.ObjectField(G("menuinstall.installto"), menu,
|
||||
typeof(VRCExpressionsMenu), true);
|
||||
}
|
||||
else if (target is ModularAvatarMenuInstallTarget t)
|
||||
{
|
||||
EditorGUILayout.ObjectField(G("menuinstall.installto"), t.transform.parent.gameObject,
|
||||
typeof(GameObject), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var avatar = RuntimeUtil.FindAvatarInParents(_installer.transform);
|
||||
if (avatar != null && GUILayout.Button(G("menuinstall.selectmenu")))
|
||||
var avatar = commonAvatar;
|
||||
if (avatar != null && InstallTargets.Count == 1 && GUILayout.Button(G("menuinstall.selectmenu")))
|
||||
{
|
||||
AvMenuTreeViewWindow.Show(avatar, _installer, menu =>
|
||||
{
|
||||
if (InstallTargets.Count != 1 || menu == InstallTargets[0]) return;
|
||||
|
||||
if (InstallTargets[0] is ModularAvatarMenuInstallTarget oldTarget && oldTarget != null)
|
||||
{
|
||||
DestroyInstallTargets();
|
||||
}
|
||||
|
||||
if (menu is VRCExpressionsMenu expMenu)
|
||||
{
|
||||
if (expMenu == avatar.expressionsMenu) installTo.objectReferenceValue = null;
|
||||
@ -113,12 +212,27 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
else if (menu is ModularAvatarMenuItem item)
|
||||
{
|
||||
// TODO
|
||||
installTo.objectReferenceValue = null;
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var installer = (ModularAvatarMenuInstaller) target;
|
||||
var child = new GameObject();
|
||||
Undo.RegisterCreatedObjectUndo(child, "Set install target");
|
||||
child.transform.SetParent(item.transform, false);
|
||||
child.name = installer.gameObject.name;
|
||||
|
||||
var targetComponent = child.AddComponent<ModularAvatarMenuInstallTarget>();
|
||||
targetComponent.installer = installer;
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
VirtualMenu.InvalidateCaches();
|
||||
Repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (targets.Length == 1)
|
||||
{
|
||||
@ -202,6 +316,36 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
Localization.ShowLanguageUI();
|
||||
}
|
||||
|
||||
private void DestroyInstallTargets()
|
||||
{
|
||||
VirtualMenu menu = VirtualMenu.ForAvatar(FindCommonAvatar());
|
||||
|
||||
foreach (var t in targets)
|
||||
{
|
||||
foreach (var oldTarget in menu.GetInstallTargetsForInstaller((ModularAvatarMenuInstaller) t))
|
||||
{
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(oldTarget))
|
||||
{
|
||||
Undo.RecordObject(oldTarget, "Change menu install target");
|
||||
oldTarget.installer = null;
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(oldTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (oldTarget.transform.childCount == 0 &&
|
||||
oldTarget.GetComponents(typeof(Component)).Length == 2)
|
||||
{
|
||||
Undo.DestroyObjectImmediate(oldTarget.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Undo.DestroyObjectImmediate(oldTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private VRCAvatarDescriptor FindCommonAvatar()
|
||||
{
|
||||
VRCAvatarDescriptor commonAvatar = null;
|
||||
|
@ -53,16 +53,16 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
{
|
||||
this.name = control.name;
|
||||
this.type = control.type;
|
||||
this.parameter = new Parameter() {name = control.parameter.name};
|
||||
this.parameter = new Parameter() {name = control?.parameter?.name};
|
||||
this.value = control.value;
|
||||
this.icon = control.icon;
|
||||
this.style = control.style;
|
||||
this.subMenu = null;
|
||||
this.subParameters = control.subParameters.Select(p => new VRCExpressionsMenu.Control.Parameter()
|
||||
this.subParameters = control.subParameters?.Select(p => new VRCExpressionsMenu.Control.Parameter()
|
||||
{
|
||||
name = p.name
|
||||
}).ToArray();
|
||||
this.labels = control.labels.ToArray();
|
||||
})?.ToArray();
|
||||
this.labels = control.labels?.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +74,23 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
{
|
||||
internal readonly object RootMenuKey;
|
||||
|
||||
private static long _cacheSeq = 0;
|
||||
|
||||
internal static void InvalidateCaches()
|
||||
{
|
||||
_cacheSeq++;
|
||||
}
|
||||
|
||||
static VirtualMenu()
|
||||
{
|
||||
RuntimeUtil.OnMenuInvalidate += InvalidateCaches;
|
||||
}
|
||||
|
||||
internal static long CacheSequence => _cacheSeq;
|
||||
|
||||
private readonly long _initialCacheSeq = _cacheSeq;
|
||||
internal bool IsOutdated => _initialCacheSeq != _cacheSeq;
|
||||
|
||||
/// <summary>
|
||||
/// Indexes which menu installers are contributing to which VRCExpressionMenu assets.
|
||||
/// </summary>
|
||||
@ -131,6 +148,20 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
return menu;
|
||||
}
|
||||
|
||||
internal IEnumerable<ModularAvatarMenuInstallTarget> GetInstallTargetsForInstaller(
|
||||
ModularAvatarMenuInstaller installer
|
||||
)
|
||||
{
|
||||
if (_installerToTargetComponent.TryGetValue(installer, out var targets))
|
||||
{
|
||||
return targets;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Array.Empty<ModularAvatarMenuInstallTarget>();
|
||||
}
|
||||
}
|
||||
|
||||
private MenuNode ImportMenu(VRCExpressionsMenu menu, object menuKey = null)
|
||||
{
|
||||
if (menuKey == null) menuKey = menu;
|
||||
@ -184,6 +215,8 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
targets = new List<ModularAvatarMenuInstallTarget>();
|
||||
_installerToTargetComponent[target.installer] = targets;
|
||||
}
|
||||
|
||||
targets.Add(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
|
@ -16,5 +16,12 @@ namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
// Ensure that unity generates an enable checkbox
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
|
||||
RuntimeUtil.InvalidateMenu();
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,13 @@ namespace nadena.dev.modular_avatar.core
|
||||
* Note that this method might be called outside of a build context (e.g. from custom inspectors).
|
||||
*/
|
||||
internal abstract VRCExpressionsMenu.Control[] GenerateMenu();
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
|
||||
RuntimeUtil.InvalidateMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -39,6 +39,13 @@ namespace nadena.dev.modular_avatar.core
|
||||
public static Action<Action> delayCall = (_) => { };
|
||||
public static event Action OnHierarchyChanged;
|
||||
|
||||
internal static event Action OnMenuInvalidate;
|
||||
|
||||
internal static void InvalidateMenu()
|
||||
{
|
||||
OnMenuInvalidate?.Invoke();
|
||||
}
|
||||
|
||||
public enum OnDemandSource
|
||||
{
|
||||
Awake,
|
||||
|
Loading…
x
Reference in New Issue
Block a user