From 2f770cf72450aacce81a82daa3aad1a2664cd0fe Mon Sep 17 00:00:00 2001 From: Lillith Rose Date: Sun, 28 Sep 2025 15:20:35 -0400 Subject: [PATCH 1/2] Menu Icon Replacer --- MenuIconReplacer.meta | 8 ++ MenuIconReplacer/Editor.meta | 8 ++ .../Editor/MenuIconReplacerEditor.asmdef | 20 ++++ .../Editor/MenuIconReplacerEditor.asmdef.meta | 7 ++ .../Editor/MenuIconReplacerInspector.cs | 111 ++++++++++++++++++ .../Editor/MenuIconReplacerInspector.cs.meta | 11 ++ .../Editor/MenuIconReplacerPlugin.cs | 66 +++++++++++ .../Editor/MenuIconReplacerPlugin.cs.meta | 11 ++ MenuIconReplacer/Runtime.meta | 8 ++ .../Runtime/MenuIconReplacerConfig.cs | 19 +++ .../Runtime/MenuIconReplacerConfig.cs.meta | 11 ++ .../Runtime/MenuIconReplacerRuntime.asmdef | 14 +++ .../MenuIconReplacerRuntime.asmdef.meta | 7 ++ 13 files changed, 301 insertions(+) create mode 100644 MenuIconReplacer.meta create mode 100644 MenuIconReplacer/Editor.meta create mode 100644 MenuIconReplacer/Editor/MenuIconReplacerEditor.asmdef create mode 100644 MenuIconReplacer/Editor/MenuIconReplacerEditor.asmdef.meta create mode 100644 MenuIconReplacer/Editor/MenuIconReplacerInspector.cs create mode 100644 MenuIconReplacer/Editor/MenuIconReplacerInspector.cs.meta create mode 100644 MenuIconReplacer/Editor/MenuIconReplacerPlugin.cs create mode 100644 MenuIconReplacer/Editor/MenuIconReplacerPlugin.cs.meta create mode 100644 MenuIconReplacer/Runtime.meta create mode 100644 MenuIconReplacer/Runtime/MenuIconReplacerConfig.cs create mode 100644 MenuIconReplacer/Runtime/MenuIconReplacerConfig.cs.meta create mode 100644 MenuIconReplacer/Runtime/MenuIconReplacerRuntime.asmdef create mode 100644 MenuIconReplacer/Runtime/MenuIconReplacerRuntime.asmdef.meta diff --git a/MenuIconReplacer.meta b/MenuIconReplacer.meta new file mode 100644 index 0000000..85c3931 --- /dev/null +++ b/MenuIconReplacer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4a654954dc6cec1489d07ed6e2948696 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MenuIconReplacer/Editor.meta b/MenuIconReplacer/Editor.meta new file mode 100644 index 0000000..5828efb --- /dev/null +++ b/MenuIconReplacer/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 242e6d8788815f146a868fa94fce66ea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MenuIconReplacer/Editor/MenuIconReplacerEditor.asmdef b/MenuIconReplacer/Editor/MenuIconReplacerEditor.asmdef new file mode 100644 index 0000000..d3b8f12 --- /dev/null +++ b/MenuIconReplacer/Editor/MenuIconReplacerEditor.asmdef @@ -0,0 +1,20 @@ +{ + "name": "MenuIconReplacerEditor", + "rootNamespace": "", + "references": [ + "GUID:108c6ed81f83e074fa168f7087c2a246", + "GUID:62ced99b048af7f4d8dfe4bed8373d76", + "GUID:5718fb738711cd34ea54e9553040911d" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/MenuIconReplacer/Editor/MenuIconReplacerEditor.asmdef.meta b/MenuIconReplacer/Editor/MenuIconReplacerEditor.asmdef.meta new file mode 100644 index 0000000..7f4af44 --- /dev/null +++ b/MenuIconReplacer/Editor/MenuIconReplacerEditor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 80b77092c9bbd5b48b726b52e8b9c308 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MenuIconReplacer/Editor/MenuIconReplacerInspector.cs b/MenuIconReplacer/Editor/MenuIconReplacerInspector.cs new file mode 100644 index 0000000..a607548 --- /dev/null +++ b/MenuIconReplacer/Editor/MenuIconReplacerInspector.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using gay.lilyy.MenuIconReplacer; + +namespace gay.lilyy.MenuIconReplacer.Editor +{ + [CustomEditor(typeof(MenuIconReplacerConfig))] + public class MenuIconReplacerInspector : UnityEditor.Editor + { + private SerializedProperty replacementsProperty; + private bool showReplacements = true; + + private void OnEnable() + { + replacementsProperty = serializedObject.FindProperty("replacements"); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Menu Icon Replacer", EditorStyles.boldLabel); + EditorGUILayout.HelpBox("Configure texture replacements for menu icons. The left texture will be replaced with the right texture in the expressions menu.", MessageType.Info); + + EditorGUILayout.Space(); + + showReplacements = EditorGUILayout.Foldout(showReplacements, $"Replacements ({replacementsProperty.arraySize})", true); + + if (showReplacements) + { + EditorGUI.indentLevel++; + + // Add new replacement button + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button("Add Replacement")) + { + replacementsProperty.InsertArrayElementAtIndex(replacementsProperty.arraySize); + } + + if (GUILayout.Button("Clear All") && replacementsProperty.arraySize > 0) + { + if (EditorUtility.DisplayDialog("Clear All Replacements", + "Are you sure you want to clear all replacements?", "Yes", "Cancel")) + { + replacementsProperty.ClearArray(); + } + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + // Display replacements + for (int i = 0; i < replacementsProperty.arraySize; i++) + { + var element = replacementsProperty.GetArrayElementAtIndex(i); + var fromProperty = element.FindPropertyRelative("from"); + var toProperty = element.FindPropertyRelative("to"); + + EditorGUILayout.BeginVertical("box"); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField($"Replacement {i + 1}", EditorStyles.boldLabel, GUILayout.Width(100)); + + if (GUILayout.Button("Remove", GUILayout.Width(60))) + { + replacementsProperty.DeleteArrayElementAtIndex(i); + break; + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("From:", GUILayout.Width(40)); + EditorGUILayout.PropertyField(fromProperty, GUIContent.none); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("To:", GUILayout.Width(40)); + EditorGUILayout.PropertyField(toProperty, GUIContent.none); + EditorGUILayout.EndHorizontal(); + + // Validation + var fromTexture = fromProperty.objectReferenceValue as Texture2D; + var toTexture = toProperty.objectReferenceValue as Texture2D; + + if (fromTexture != null && toTexture != null && fromTexture == toTexture) + { + EditorGUILayout.HelpBox("Warning: Source and target textures are the same.", MessageType.Warning); + } + else if (fromTexture != null && toTexture == null) + { + EditorGUILayout.HelpBox("Target texture is not set.", MessageType.Warning); + } + else if (fromTexture == null && toTexture != null) + { + EditorGUILayout.HelpBox("Source texture is not set.", MessageType.Warning); + } + + EditorGUILayout.EndVertical(); + EditorGUILayout.Space(); + } + + EditorGUI.indentLevel--; + } + + serializedObject.ApplyModifiedProperties(); + } + + } +} diff --git a/MenuIconReplacer/Editor/MenuIconReplacerInspector.cs.meta b/MenuIconReplacer/Editor/MenuIconReplacerInspector.cs.meta new file mode 100644 index 0000000..35a226e --- /dev/null +++ b/MenuIconReplacer/Editor/MenuIconReplacerInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 43cc613e338942941a9a549788e36afc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MenuIconReplacer/Editor/MenuIconReplacerPlugin.cs b/MenuIconReplacer/Editor/MenuIconReplacerPlugin.cs new file mode 100644 index 0000000..c90609b --- /dev/null +++ b/MenuIconReplacer/Editor/MenuIconReplacerPlugin.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using gay.lilyy.MenuIconReplacer; +using nadena.dev.ndmf; +using NUnit.Framework.Constraints; +using UnityEditor; +using UnityEngine; +using VRC.SDK3.Avatars.ScriptableObjects; +using static VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu; + +[assembly: ExportsPlugin(typeof(MenuIconReplacerPlugin))] + +namespace gay.lilyy.MenuIconReplacer +{ + public class MenuIconReplacerPlugin : Plugin + { + public override string DisplayName => "MenuIconReplacer"; + public override string QualifiedName => "gay.lilyy.MenuIconReplacer"; + + protected override void Configure() + { + InPhase(BuildPhase.Optimizing).BeforePlugin("gay.lilyy.MenuIconRemover").Run("ReplaceMenuIcons", ctx => + { + var obj = ctx.AvatarRootObject.GetComponent(); + if (obj != null) + { + ctx.AvatarDescriptor.expressionsMenu = RecurseMenu(obj, ctx.AvatarDescriptor.expressionsMenu); + Object.DestroyImmediate(obj); + } + }); + } + + private VRCExpressionsMenu RecurseMenu(MenuIconReplacerConfig cfg, VRCExpressionsMenu menu) + { + if (menu == null) return null; + + var newMenu = ScriptableObject.CreateInstance(); + newMenu.controls = new List(); + + foreach (var control in menu.controls) + { + var icon = control.icon; + foreach (var entry in cfg.replacements) + { + if (entry.from == icon) { + icon = entry.to; + } + } + var newControl = new Control + { + name = control.name, + type = control.type, + icon = icon, + parameter = control.parameter, + subMenu = RecurseMenu(cfg, control.subMenu), + value = control.value, + style = control.style, + labels = control.labels, + subParameters = control.subParameters + }; + newMenu.controls.Add(newControl); + } + + return newMenu; + } + } +} diff --git a/MenuIconReplacer/Editor/MenuIconReplacerPlugin.cs.meta b/MenuIconReplacer/Editor/MenuIconReplacerPlugin.cs.meta new file mode 100644 index 0000000..66926d7 --- /dev/null +++ b/MenuIconReplacer/Editor/MenuIconReplacerPlugin.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5b9fdfa35ac20a541848fa1c355dda23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MenuIconReplacer/Runtime.meta b/MenuIconReplacer/Runtime.meta new file mode 100644 index 0000000..55dd68b --- /dev/null +++ b/MenuIconReplacer/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dbba2be0d4a22f24abf8cf72fcc0ae76 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MenuIconReplacer/Runtime/MenuIconReplacerConfig.cs b/MenuIconReplacer/Runtime/MenuIconReplacerConfig.cs new file mode 100644 index 0000000..4c4024d --- /dev/null +++ b/MenuIconReplacer/Runtime/MenuIconReplacerConfig.cs @@ -0,0 +1,19 @@ + +using System.Collections.Generic; +using UnityEngine; + +namespace gay.lilyy.MenuIconReplacer +{ + [System.Serializable] + public class TextureReplacement + { + public Texture2D from; + public Texture2D to; + } + + public class MenuIconReplacerConfig : MonoBehaviour, VRC.SDKBase.IEditorOnly + { + [HideInInspector] + public List replacements = new List(); + } +} \ No newline at end of file diff --git a/MenuIconReplacer/Runtime/MenuIconReplacerConfig.cs.meta b/MenuIconReplacer/Runtime/MenuIconReplacerConfig.cs.meta new file mode 100644 index 0000000..9a2af11 --- /dev/null +++ b/MenuIconReplacer/Runtime/MenuIconReplacerConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6d76942481598d4b8d9c823b1f809c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MenuIconReplacer/Runtime/MenuIconReplacerRuntime.asmdef b/MenuIconReplacer/Runtime/MenuIconReplacerRuntime.asmdef new file mode 100644 index 0000000..80b3b3a --- /dev/null +++ b/MenuIconReplacer/Runtime/MenuIconReplacerRuntime.asmdef @@ -0,0 +1,14 @@ +{ + "name": "MenuIconReplacerRuntime", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/MenuIconReplacer/Runtime/MenuIconReplacerRuntime.asmdef.meta b/MenuIconReplacer/Runtime/MenuIconReplacerRuntime.asmdef.meta new file mode 100644 index 0000000..774a310 --- /dev/null +++ b/MenuIconReplacer/Runtime/MenuIconReplacerRuntime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 108c6ed81f83e074fa168f7087c2a246 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 5badf016a5be60838bcb9bb8d634b73d804d9d38 Mon Sep 17 00:00:00 2001 From: Lillith Rose Date: Sun, 28 Sep 2025 15:24:53 -0400 Subject: [PATCH 2/2] feat(menuStyling): ignore empty menu names --- MenuStyling/Editor/MenuStylingPlugin.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MenuStyling/Editor/MenuStylingPlugin.cs b/MenuStyling/Editor/MenuStylingPlugin.cs index 4b39d56..ce462ee 100644 --- a/MenuStyling/Editor/MenuStylingPlugin.cs +++ b/MenuStyling/Editor/MenuStylingPlugin.cs @@ -58,6 +58,7 @@ namespace gay.lilyy.MenuStyling { foreach (var child in menu.controls) { + if (child.name != " " && child.name != "" && child.name != null) { if (!child.name.StartsWith(config.Prefix)) { child.name = config.Prefix + child.name; @@ -66,6 +67,8 @@ namespace gay.lilyy.MenuStyling { child.name += config.Suffix; } + + } if (child.type == Control.ControlType.SubMenu && child.subMenu != null) { WalkMenu(config, child.subMenu);