change: MA Info is now a normal editor window (#850)

This commit is contained in:
bd_ 2024-05-13 05:19:55 +09:00 committed by GitHub
parent 61a70feed7
commit bc12e3a985
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 239 additions and 472 deletions

View File

@ -22,25 +22,29 @@
* SOFTWARE. * SOFTWARE.
*/ */
#region
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using nadena.dev.modular_avatar.ui; using nadena.dev.modular_avatar.ui;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
#endregion
[assembly: InternalsVisibleTo("Tests")] [assembly: InternalsVisibleTo("Tests")]
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
public class AvatarProcessor public class AvatarProcessor
{ {
[MenuItem("GameObject/ModularAvatar/Manual bake avatar", true, 100)] [MenuItem(UnityMenuItems.GameObject_ManualBake, true, UnityMenuItems.GameObject_ManualBakeOrder)]
static bool ValidateApplyToCurrentAvatarGameobject() static bool ValidateApplyToCurrentAvatarGameobject()
{ {
return ValidateApplyToCurrentAvatar(); return ValidateApplyToCurrentAvatar();
} }
[MenuItem("GameObject/ModularAvatar/Manual bake avatar", false, 100)] [MenuItem(UnityMenuItems.GameObject_ManualBake, false, UnityMenuItems.GameObject_ManualBakeOrder)]
static void ApplyToCurrentAvatarGameobject() static void ApplyToCurrentAvatarGameobject()
{ {
ApplyToCurrentAvatar(); ApplyToCurrentAvatar();

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using nadena.dev.modular_avatar.ui;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
@ -114,11 +115,10 @@ namespace nadena.dev.modular_avatar.core.editor
internal static class EasySetupOutfit internal static class EasySetupOutfit
{ {
private const int PRIORITY = 49;
private static string[] errorMessageGroups; private static string[] errorMessageGroups;
private static string errorHeader; private static string errorHeader;
[MenuItem("GameObject/ModularAvatar/Setup Outfit", false, PRIORITY)] [MenuItem(UnityMenuItems.GameObject_SetupOutfit, false, UnityMenuItems.GameObject_SetupOutfitOrder)]
internal static void SetupOutfit(MenuCommand cmd) internal static void SetupOutfit(MenuCommand cmd)
{ {
if (!ValidateSetupOutfit()) if (!ValidateSetupOutfit())

View File

@ -1,102 +0,0 @@
#region
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
#endregion
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
{
internal static class InjectParamsUsageUI
{
private static readonly Type type = AccessTools.TypeByName("UnityEditor.PropertyEditor");
private static readonly PropertyInfo _editorsElement = AccessTools.Property(type, "editorsElement");
private static readonly Type editorElem = AccessTools.TypeByName("UnityEditor.UIElements.EditorElement");
private static readonly PropertyInfo editorElem_editor = AccessTools.Property(editorElem, "editor");
public static void Patch(Harmony h)
{
var type = AccessTools.TypeByName("UnityEditor.PropertyEditor");
var drawEditors = AccessTools.Method(type, "DrawEditors");
h.Patch(drawEditors, transpiler: new HarmonyMethod(typeof(InjectParamsUsageUI), nameof(Transpile)));
var objNames = AccessTools.TypeByName("UnityEditor.ObjectNames");
var m_GetObjectTypeName = AccessTools.Method(objNames, "GetObjectTypeName");
var postfix_GetObjectTypeName =
AccessTools.Method(typeof(InjectParamsUsageUI), nameof(Postfix_GetObjectTypeName));
h.Patch(m_GetObjectTypeName, postfix: new HarmonyMethod(postfix_GetObjectTypeName));
}
private static void Postfix_GetObjectTypeName(ref string __result, Object o)
{
if (o is ModularAvatarInformation)
{
__result = "Modular Avatar Information";
}
}
private static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> ci)
{
var target = AccessTools.Method(typeof(VisualElement), "Add");
foreach (var i in ci)
{
if (i.opcode != OpCodes.Callvirt)
{
yield return i;
continue;
}
if (i.opcode == OpCodes.Callvirt
&& i.operand is MethodInfo method
&& method == target
)
{
yield return new CodeInstruction(OpCodes.Ldarg_0);
yield return new CodeInstruction(OpCodes.Call,
AccessTools.Method(typeof(InjectParamsUsageUI), nameof(EditorAdd)));
continue;
}
yield return i;
}
}
private static void EditorAdd(VisualElement container, VisualElement child, object caller)
{
container.Add(child);
var editorsElement = _editorsElement.GetValue(caller) as VisualElement;
if (editorsElement != container)
{
return;
}
if (!child.ClassListContains("game-object-inspector"))
{
return;
}
var editor = editorElem_editor.GetValue(child) as Editor;
if (editor == null) return;
if (editor.targets.Length != 1) return;
if (editor.target is GameObject obj)
{
var elem = new ParamsUsageUI();
container.Add(elem);
}
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5d62a8f41641443ea8bffdc0429e0ad1
timeCreated: 1710223876

View File

@ -17,7 +17,6 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
#if UNITY_2022_3_OR_NEWER #if UNITY_2022_3_OR_NEWER
HandleUtilityPatches.Patch_FilterInstanceIDs, HandleUtilityPatches.Patch_FilterInstanceIDs,
PickingObjectPatch.Patch, PickingObjectPatch.Patch,
InjectParamsUsageUI.Patch,
#endif #endif
}; };

View File

@ -1,11 +1,13 @@
using System.Collections.Generic; #region
using System.Collections.Generic;
using System.Linq; using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEditor.UIElements; using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.UIElements; using UnityEngine.UIElements;
#endregion
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
public class LogoElement : VisualElement public class LogoElement : VisualElement
@ -85,19 +87,8 @@ namespace nadena.dev.modular_avatar.core.editor
public LogoElement() public LogoElement()
{ {
_inner = new VisualElement(); _inner = new LogoImage();
_inner.style.display = DisplayStyle.None; _inner.style.display = DisplayStyle.None;
_inner.style.flexDirection = FlexDirection.Row;
_inner.style.alignItems = Align.Center;
_inner.style.justifyContent = Justify.Center;
var image = new Image();
image.image = LogoDisplay.LOGO_ASSET;
image.style.width = new Length(LogoDisplay.ImageWidth(LogoDisplay.TARGET_HEIGHT), LengthUnit.Pixel);
image.style.height = new Length(LogoDisplay.TARGET_HEIGHT, LengthUnit.Pixel);
_inner.Add(image);
this.Add(_inner); this.Add(_inner);
RegisterCallback<GeometryChangedEvent>(OnGeomChanged); RegisterCallback<GeometryChangedEvent>(OnGeomChanged);

View File

@ -0,0 +1,38 @@
#region
using UnityEngine.UIElements;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
public class LogoImage : VisualElement
{
VisualElement _inner;
public new class UxmlFactory : UxmlFactory<LogoImage, UxmlTraits>
{
}
public new class UxmlTraits : VisualElement.UxmlTraits
{
}
public LogoImage()
{
_inner = new VisualElement();
_inner.style.flexDirection = FlexDirection.Row;
_inner.style.alignItems = Align.Center;
_inner.style.justifyContent = Justify.Center;
var image = new Image();
image.image = LogoDisplay.LOGO_ASSET;
image.style.width = new Length(LogoDisplay.ImageWidth(LogoDisplay.TARGET_HEIGHT), LengthUnit.Pixel);
image.style.height = new Length(LogoDisplay.TARGET_HEIGHT, LengthUnit.Pixel);
_inner.Add(image);
Add(_inner);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d12c8166c3f14b78a76dbef22b07fad1
timeCreated: 1715480586

View File

@ -1,7 +1,11 @@
using System; #region
using System;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
#endregion
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
internal static class LogoDisplay internal static class LogoDisplay
@ -37,7 +41,7 @@ namespace nadena.dev.modular_avatar.core.editor
internal static void DisplayLogo() internal static void DisplayLogo()
{ {
if (LOGO_ASSET == null) return; if (LOGO_ASSET == null || EditorStyles.label == null) return;
var height = TARGET_HEIGHT; var height = TARGET_HEIGHT;
var width = ImageWidth(height); var width = ImageWidth(height);

View File

@ -246,5 +246,6 @@
"ma_info.param_usage_ui.header": "Expressions Parameter Usage", "ma_info.param_usage_ui.header": "Expressions Parameter Usage",
"ma_info.param_usage_ui.other_objects": "Other objects on this avatar", "ma_info.param_usage_ui.other_objects": "Other objects on this avatar",
"ma_info.param_usage_ui.free_space": "Unused parameter space ({0} bits)", "ma_info.param_usage_ui.free_space": "Unused parameter space ({0} bits)",
"ma_info.param_usage_ui.bits_template": "{0} ({1} bits)" "ma_info.param_usage_ui.bits_template": "{0} ({1} bits)",
"ma_info.param_usage_ui.no_data": "[ NO DATA ]"
} }

View File

@ -242,5 +242,6 @@
"ma_info.param_usage_ui.header": "Expressions Parameter 使用状況", "ma_info.param_usage_ui.header": "Expressions Parameter 使用状況",
"ma_info.param_usage_ui.other_objects": "このアバター内の他のオブジェクト", "ma_info.param_usage_ui.other_objects": "このアバター内の他のオブジェクト",
"ma_info.param_usage_ui.free_space": "未使用領域 ({0} 個のビット)", "ma_info.param_usage_ui.free_space": "未使用領域 ({0} 個のビット)",
"ma_info.param_usage_ui.bits_template": "{0} ({1} 個のビットを使用中)" "ma_info.param_usage_ui.bits_template": "{0} ({1} 個のビットを使用中)",
"ma_info.param_usage_ui.no_data": "[ NO DATA ]"
} }

View File

@ -1,19 +1,23 @@
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
#region
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using nadena.dev.modular_avatar.ui;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects; using VRC.SDK3.Avatars.ScriptableObjects;
#endregion
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
internal class MenuExtractor internal class MenuExtractor
{ {
private const int PRIORITY = 49; [MenuItem(UnityMenuItems.GameObject_ExtractMenu, false, UnityMenuItems.GameObject_ExtractMenuOrder)]
[MenuItem("GameObject/ModularAvatar/Extract menu", false, PRIORITY)]
static void ExtractMenu(MenuCommand menuCommand) static void ExtractMenu(MenuCommand menuCommand)
{ {
if (!(menuCommand.context is GameObject gameObj)) return; if (!(menuCommand.context is GameObject gameObj)) return;
@ -32,10 +36,10 @@ namespace nadena.dev.modular_avatar.core.editor
var assetPath = AssetDatabase.GetAssetPath(avatar.expressionsMenu); var assetPath = AssetDatabase.GetAssetPath(avatar.expressionsMenu);
var dummyAssetPathBase = assetPath.Replace(".asset", " placeholder"); var dummyAssetPathBase = assetPath.Replace(".asset", " placeholder");
if (dummyAssetPathBase.StartsWith("Packages" + System.IO.Path.DirectorySeparatorChar)) if (dummyAssetPathBase.StartsWith("Packages" + Path.DirectorySeparatorChar))
{ {
var filename = System.IO.Path.GetFileName(dummyAssetPathBase); var filename = Path.GetFileName(dummyAssetPathBase);
dummyAssetPathBase = System.IO.Path.Combine("Assets", filename); dummyAssetPathBase = Path.Combine("Assets", filename);
} }
// Check that a similarly-named file doesn't already exist // Check that a similarly-named file doesn't already exist
@ -43,7 +47,7 @@ namespace nadena.dev.modular_avatar.core.editor
do do
{ {
var fullPath = dummyAssetPathBase + (i > 0 ? " " + i : "") + ".asset"; var fullPath = dummyAssetPathBase + (i > 0 ? " " + i : "") + ".asset";
if (System.IO.File.Exists(fullPath)) if (File.Exists(fullPath))
{ {
var asset = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>(fullPath); var asset = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>(fullPath);
if (asset != null && asset.controls.Count == 0) if (asset != null && asset.controls.Count == 0)
@ -52,7 +56,7 @@ namespace nadena.dev.modular_avatar.core.editor
break; break;
} }
} }
else if (!System.IO.File.Exists(fullPath)) else if (!File.Exists(fullPath))
{ {
var dummyAsset = ScriptableObject.CreateInstance<VRCExpressionsMenu>(); var dummyAsset = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
AssetDatabase.CreateAsset(dummyAsset, fullPath); AssetDatabase.CreateAsset(dummyAsset, fullPath);

View File

@ -1,23 +0,0 @@
#region
using UnityEngine;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
[HelpURL("https://m-a.nadena.dev/docs/intro?lang=auto")]
internal class ModularAvatarInformation : ScriptableObject
{
internal static ModularAvatarInformation _instance;
internal static ModularAvatarInformation instance
{
get
{
if (_instance == null) _instance = CreateInstance<ModularAvatarInformation>();
return _instance;
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: f902feee12ad4fcbb8a975bbea565ab1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: a8edd5bd1a0a64a40aa99cc09fb5f198, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -5,10 +5,12 @@
#Outerbox { #Outerbox {
margin-top: 4px; margin-top: 4px;
margin-bottom: 8px;
border-top-width: 1px; border-top-width: 1px;
border-bottom-width: 1px; border-bottom-width: 1px;
border-left-width: 1px; /*border-left-width: 1px;
border-right-width: 1px; border-right-width: 1px;*/
border-color: black; border-color: black;
padding: 4px; padding: 4px;
@ -18,6 +20,11 @@
margin-bottom: 12px; margin-bottom: 12px;
} }
#Footer {
margin-left: 15px;
margin-right: 15px;
}
#UsageBox { #UsageBox {
height: 16px; height: 16px;
flex-direction: row; flex-direction: row;
@ -73,3 +80,32 @@
border-left-width: 4px; border-left-width: 4px;
border-right-width: 4px; border-right-width: 4px;
} }
/* NO DATA display */
#Outerbox Toggle {
display: flex;
}
/* Why doesn't #Outerbox.no-data > .unity-foldout__content > * work here? */
#Outerbox.no-data #UsageBox {
display: none;
}
#Outerbox.no-data #Legend {
display: none;
}
#NoData {
display: none;
}
#Outerbox.no-data #NoData {
font-size: 150%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 100%;
margin-top: 20px;
margin-bottom: 20px;
}

View File

@ -1,8 +1,12 @@
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ma="nadena.dev.modular_avatar.core.editor"> <UXML xmlns:ui="UnityEngine.UIElements" xmlns:ma="nadena.dev.modular_avatar.core.editor">
<ui:VisualElement name="root-box"> <ui:VisualElement name="root-box">
<ui:Label text="ma_info.param_usage_ui.header" class="header ndmf-tr"/> <ma:LogoImage/>
<ui:Foldout text="ma_info.param_usage_ui.header" class="header ndmf-tr" name="Outerbox">
<ui:VisualElement name="NoData">
<ui:Label text="ma_info.param_usage_ui.no_data" class="ndmf-tr"/>
</ui:VisualElement>
<ui:VisualElement name="Outerbox">
<ui:VisualElement name="UsageBox"> <ui:VisualElement name="UsageBox">
<ui:VisualElement name="OtherObjects" style="background-color: #888888; flex-grow: 1;"/> <ui:VisualElement name="OtherObjects" style="background-color: #888888; flex-grow: 1;"/>
<ui:VisualElement name="UnusedSpace" style="width: auto; background-color: #eeeeee; flex-grow: 1;"/> <ui:VisualElement name="UnusedSpace" style="width: auto; background-color: #eeeeee; flex-grow: 1;"/>
@ -28,8 +32,10 @@
</ui:VisualElement> </ui:VisualElement>
</ui:VisualElement> </ui:VisualElement>
</ui:VisualElement> </ui:Foldout>
<ui:VisualElement name="Footer">
<ma:LanguageSwitcherElement/> <ma:LanguageSwitcherElement/>
</ui:VisualElement> </ui:VisualElement>
</ui:VisualElement>
</UXML> </UXML>

View File

@ -1,40 +0,0 @@
using System;
using nadena.dev.modular_avatar.ui;
using UnityEditor;
using UnityEngine.Serialization;
namespace nadena.dev.modular_avatar.core.editor
{
#if UNITY_2022_3_OR_NEWER
[FilePath("modular-avatar/ParamsUsagePrefs.asset", FilePathAttribute.Location.PreferencesFolder)]
internal sealed class ParamsUsagePrefs : ScriptableSingleton<ParamsUsagePrefs>
{
public static event Action<bool> OnChange_EnableInfoMenu;
[FormerlySerializedAs("EnableInfoMenu")] public bool enableInfoMenu = true;
[MenuItem(UnityMenuItems.TopMenu_EnableInfo, false, UnityMenuItems.TopMenu_EnableInfoOrder)]
private static void Menu_EnableInfo()
{
ParamsUsagePrefs.instance.enableInfoMenu = !ParamsUsagePrefs.instance.enableInfoMenu;
Menu.SetChecked(UnityMenuItems.TopMenu_EnableInfo, ParamsUsagePrefs.instance.enableInfoMenu);
ParamsUsagePrefs.instance.Save(true);
OnChange_EnableInfoMenu?.Invoke(ParamsUsagePrefs.instance.enableInfoMenu);
}
[InitializeOnLoadMethod]
private static void Initialize()
{
Menu.SetChecked(UnityMenuItems.TopMenu_EnableInfo, ParamsUsagePrefs.instance.enableInfoMenu);
}
}
#else
internal sealed class ParamsUsagePrefs
{
public static ParamsUsagePrefs instance => new ParamsUsagePrefs();
public static event Action<bool> OnChange_EnableInfoMenu;
public bool enableInfoMenu => false;
}
#endif
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 3ebf965fd4064a52896def62c36c6a90
timeCreated: 1713742583

View File

@ -1,220 +0,0 @@
#region
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using HarmonyLib;
using nadena.dev.ndmf.localization;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
internal class ParamsUsageUI : VisualElement
{
private static readonly Type editorElem = AccessTools.TypeByName("UnityEditor.UIElements.EditorElement");
private static readonly PropertyInfo editorElem_editor = AccessTools.Property(editorElem, "editor");
private class FoldoutState
{
public bool Visible;
}
private static ConditionalWeakTable<VisualElement, FoldoutState> FoldoutStateHolder =
new ConditionalWeakTable<VisualElement, FoldoutState>();
private VisualElement _gameObjectEditorElement;
private Editor _parentEditor;
private Object _rawTarget;
private GameObject _target;
private ParamsUsageEditor _editor;
private FoldoutState _foldoutState;
private bool _recursing = false;
public ParamsUsageUI()
{
RegisterCallback<AttachToPanelEvent>(OnAttach);
RegisterCallback<DetachFromPanelEvent>(OnDetach);
LanguagePrefs.RegisterLanguageChangeCallback(this,
(self) => self.OnLanguageChangedCallback());
}
private void OnChange_EnableState(bool enableState)
{
if (_editor != null)
{
Rebuild();
}
}
private void OnLanguageChangedCallback()
{
if (_editor != null)
{
Rebuild();
}
}
private void OnDetach(DetachFromPanelEvent evt)
{
if (_recursing) return;
Clear();
if (_editor != null)
{
Object.DestroyImmediate(_editor);
_editor = null;
}
ParamsUsagePrefs.OnChange_EnableInfoMenu -= OnChange_EnableState;
}
private void OnAttach(AttachToPanelEvent evt)
{
if (_recursing) return;
Rebuild();
ParamsUsagePrefs.OnChange_EnableInfoMenu += OnChange_EnableState;
}
private void Rebuild()
{
if (parent == null) return;
if (!ParamsUsagePrefs.instance.enableInfoMenu)
{
Clear();
return;
}
SetRedrawSensor();
if (_gameObjectEditorElement?.parent != parent)
{
_gameObjectEditorElement = null;
var kv = FindEditorElement();
if (kv != null)
{
var elem = kv.Value.Item1;
var index = kv.Value.Item2;
if (index != parent.Children().ToList().IndexOf(this))
{
_recursing = true;
var p = parent;
RemoveFromHierarchy();
p.Insert(index + 1, this);
_recursing = false;
}
_gameObjectEditorElement = elem;
}
}
if (_gameObjectEditorElement == null) return;
_parentEditor = editorElem_editor.GetValue(_gameObjectEditorElement) as Editor;
if (_parentEditor == null) return;
_rawTarget = _parentEditor.target;
_target = _rawTarget as GameObject;
if (_target == null) return;
Clear();
_redrawSensorActive = false;
BuildContent();
}
private (VisualElement, int)? FindEditorElement()
{
foreach (var (elem, index) in parent.Children().Select((e, i) => (e, i)))
{
if (elem.ClassListContains("game-object-inspector"))
{
return (elem, index);
}
}
return null;
}
private bool _redrawSensorActive = false;
private void SetRedrawSensor()
{
if (_redrawSensorActive) return;
Clear();
_redrawSensorActive = true;
Add(new IMGUIContainer(() => EditorApplication.delayCall += Rebuild));
}
private void BuildContent()
{
Clear();
if (!FoldoutStateHolder.TryGetValue(parent, out _foldoutState))
{
_foldoutState = new FoldoutState();
FoldoutStateHolder.Add(parent, _foldoutState);
}
if (RuntimeUtil.FindAvatarTransformInParents(_target.transform) == null)
{
return;
}
_editor = Editor.CreateEditorWithContext(new Object[] { ModularAvatarInformation.instance }, _target,
typeof(ParamsUsageEditor))
as ParamsUsageEditor;
if (_editor == null) return;
var inspectorElement = new InspectorElement(_editor);
Add(new IMGUIContainer(() =>
{
if (_gameObjectEditorElement?.parent != parent || _parentEditor == null ||
_parentEditor.target != _rawTarget)
{
EditorApplication.delayCall += Rebuild;
return;
}
switch (Event.current.rawType)
{
case EventType.Repaint:
case EventType.MouseMove:
case EventType.Layout:
break;
case EventType.MouseDrag:
case EventType.DragUpdated:
case EventType.DragPerform:
case EventType.DragExited:
return;
default:
break;
}
_foldoutState.Visible = EditorGUILayout.InspectorTitlebar(_foldoutState.Visible, _editor);
inspectorElement.style.display = _foldoutState.Visible ? DisplayStyle.Flex : DisplayStyle.None;
_editor.Visible = _foldoutState.Visible;
}));
_editor.Visible = _foldoutState.Visible;
Add(inspectorElement);
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 18364f2754ed43c3baba0f1e18ac03cd
timeCreated: 1710226452

View File

@ -1,7 +1,9 @@
#region #region
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using nadena.dev.modular_avatar.ui;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
@ -12,7 +14,7 @@ using VRC.SDK3.Avatars.ScriptableObjects;
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
internal class ParamsUsageEditor : MAEditorBase internal class ParamsUsageWindow : EditorWindow
{ {
[SerializeField] private StyleSheet uss; [SerializeField] private StyleSheet uss;
[SerializeField] private VisualTreeAsset uxml; [SerializeField] private VisualTreeAsset uxml;
@ -32,11 +34,59 @@ namespace nadena.dev.modular_avatar.core.editor
if (_visible == value) return; if (_visible == value) return;
_visible = value; _visible = value;
if (_visible) Recalculate(); #if UNITY_2022_1_OR_NEWER
if (_visible)
{
Recalculate();
ObjectChangeEvents.changesPublished += OnChangesPublished;
}
else
{
ObjectChangeEvents.changesPublished -= OnChangesPublished;
}
#endif
}
}
private void OnBecameVisible()
{
Visible = true;
}
private void OnBecameInvisible()
{
Visible = false;
}
private void OnSelectionChange()
{
if (Visible)
{
Recalculate();
} }
} }
#if UNITY_2022_1_OR_NEWER #if UNITY_2022_1_OR_NEWER
[MenuItem(UnityMenuItems.TopMenu_EnableInfo, false, UnityMenuItems.TopMenu_EnableInfoOrder)]
public static void ShowWindow()
{
var window = GetWindow<ParamsUsageWindow>();
window.titleContent = new GUIContent("MA Information");
window.Show();
}
[MenuItem(UnityMenuItems.GameObject_EnableInfo, false, UnityMenuItems.GameObject_EnableInfoOrder)]
public static void ShowWindowFromGameObject()
{
ShowWindow();
}
[MenuItem(UnityMenuItems.GameObject_EnableInfo, true, UnityMenuItems.GameObject_EnableInfoOrder)]
public static bool ValidateShowWindowFromGameObject()
{
return Selection.gameObjects.Length == 1;
}
private bool _delayPending = false; private bool _delayPending = false;
private void DelayRecalculate() private void DelayRecalculate()
@ -47,40 +97,46 @@ namespace nadena.dev.modular_avatar.core.editor
private void OnChangesPublished(ref ObjectChangeEventStream stream) private void OnChangesPublished(ref ObjectChangeEventStream stream)
{ {
if (_root == null || !_visible) return;
if (!_delayPending) EditorApplication.delayCall += DelayRecalculate; if (!_delayPending) EditorApplication.delayCall += DelayRecalculate;
_delayPending = true; _delayPending = true;
} }
#endif #endif
protected override VisualElement CreateInnerInspectorGUI() bool GUIIsReady()
{ {
try
{
return uxml != null && EditorStyles.label != null;
}
catch (NullReferenceException _)
{
return false;
}
}
protected void CreateGUI()
{
if (!GUIIsReady())
{
// After domain reload, the uxml field (and EditorStyles, etc) isn't initialized immediately.
// Try again in the next frame.
EditorApplication.delayCall += CreateGUI;
return;
}
_root = uxml.CloneTree(); _root = uxml.CloneTree();
rootVisualElement.Add(_root);
_root.styleSheets.Add(uss); _root.styleSheets.Add(uss);
Localization.L.LocalizeUIElements(_root); Localization.L.LocalizeUIElements(_root);
_legendContainer = _root.Q<VisualElement>("Legend"); _legendContainer = _root.Q<VisualElement>("Legend");
_usageBoxContainer = _root.Q<VisualElement>("UsageBox"); _usageBoxContainer = _root.Q<VisualElement>("UsageBox");
#if UNITY_2022_1_OR_NEWER
_root.RegisterCallback<AttachToPanelEvent>(_evt =>
{
ObjectChangeEvents.changesPublished += OnChangesPublished;
Recalculate(); Recalculate();
});
_root.RegisterCallback<DetachFromPanelEvent>(_evt =>
{
ObjectChangeEvents.changesPublished -= OnChangesPublished;
});
#endif
return _root;
} }
protected override void OnInnerInspectorGUI()
{
// no-op
}
private static IEnumerable<Color> Colors() private static IEnumerable<Color> Colors()
{ {
@ -109,14 +165,24 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
if (_root == null || !_visible) return; if (_root == null || !_visible) return;
var ctx = serializedObject.context as GameObject; var objects = Selection.gameObjects;
var target = objects.Length == 1 ? objects[0] : null;
var avatarRoot = target != null
? RuntimeUtil.FindAvatarTransformInParents(target.transform)?.gameObject
: null;
if (ctx == null) return; var outerbox = _root.Q<VisualElement>("Outerbox");
if (avatarRoot == null)
{
outerbox.AddToClassList("no-data");
return;
}
else
{
outerbox.RemoveFromClassList("no-data");
}
var avatarRoot = RuntimeUtil.FindAvatarTransformInParents(ctx.transform)?.gameObject; var orderedPlugins = ParameterInfo.ForUI.GetParametersForObject(target)
if (avatarRoot == null) return;
var orderedPlugins = ParameterInfo.ForUI.GetParametersForObject(ctx)
.GroupBy(p => p.Plugin) .GroupBy(p => p.Plugin)
.Select(group => (group.Key, group.Sum(p => p.BitUsage))) .Select(group => (group.Key, group.Sum(p => p.BitUsage)))
.Where((kv, index) => kv.Item2 > 0) .Where((kv, index) => kv.Item2 > 0)

View File

@ -1,13 +1,32 @@
namespace nadena.dev.modular_avatar.ui namespace nadena.dev.modular_avatar.ui
{ {
internal class UnityMenuItems internal static class UnityMenuItems
{ {
internal const string GameObject_SetupOutfit = "GameObject/Modular Avatar/Setup Outfit";
internal const int GameObject_SetupOutfitOrder = -1000;
internal const string GameObject_ManualBake = "GameObject/Modular Avatar/Manual Bake Avatar";
internal const int GameObject_ManualBakeOrder = GameObject_SetupOutfitOrder + 1;
// <separator>
internal const string GameObject_EnableInfo = "GameObject/Modular Avatar/Show Modular Avatar Information";
internal const int GameObject_EnableInfoOrder = -799;
internal const string GameObject_ExtractMenu = "GameObject/Modular Avatar/Extract Menu";
internal const int GameObject_ExtractMenuOrder = GameObject_EnableInfoOrder + 1;
internal const string TopMenu_EditModeBoneSync = "Tools/Modular Avatar/Sync Bones in Edit Mode"; internal const string TopMenu_EditModeBoneSync = "Tools/Modular Avatar/Sync Bones in Edit Mode";
internal const int TopMenu_EditModeBoneSyncOrder = 100; internal const int TopMenu_EditModeBoneSyncOrder = 100;
internal const string TopMenu_EnableInfo = "Tools/Modular Avatar/Show Modular Avatar Information"; internal const string TopMenu_EnableInfo = "Tools/Modular Avatar/Show Modular Avatar Information";
internal const int TopMenu_EnableInfoOrder = 101; internal const int TopMenu_EnableInfoOrder = 101;
// <separator>
internal const string TopMenu_ManualBakeAvatar = "Tools/Modular Avatar/Manual Bake Avatar"; internal const string TopMenu_ManualBakeAvatar = "Tools/Modular Avatar/Manual Bake Avatar";
internal const int TopMenu_ManualBakeAvatarOrder = 1000; internal const int TopMenu_ManualBakeAvatarOrder = 1000;