From b01b280f79b4600e489da8c341ca21de12132f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C3=AF=7E?= Date: Thu, 5 Sep 2024 11:35:51 +0200 Subject: [PATCH 1/4] feat: menu name may use string field --- Editor/Inspector/Menu/MenuItemGUI.cs | 46 +++++++++++++++++++++++++--- Editor/Localization/en-US.json | 5 ++- Runtime/ModularAvatarMenuItem.cs | 10 +++++- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/Editor/Inspector/Menu/MenuItemGUI.cs b/Editor/Inspector/Menu/MenuItemGUI.cs index c89c3817..cf1ada0b 100644 --- a/Editor/Inspector/Menu/MenuItemGUI.cs +++ b/Editor/Inspector/Menu/MenuItemGUI.cs @@ -92,11 +92,14 @@ internal class MenuItemCoreGUI private readonly SerializedProperty _prop_isDefault; private readonly SerializedProperty _prop_automaticValue; + private readonly SerializedProperty _prop_label; + public bool AlwaysExpandContents = false; public bool ExpandContents = false; private readonly Dictionary _knownParameters = new(); private bool _parameterSourceNotDetermined; + private bool _isTryingRichLabel; public MenuItemCoreGUI(SerializedObject obj, Action redraw) { @@ -144,6 +147,8 @@ public MenuItemCoreGUI(SerializedObject obj, Action redraw) _prop_isDefault = obj.FindProperty(nameof(ModularAvatarMenuItem.isDefault)); _prop_automaticValue = obj.FindProperty(nameof(ModularAvatarMenuItem.automaticValue)); + _prop_label = obj.FindProperty(nameof(ModularAvatarMenuItem.label)); + _previewGUI = new MenuPreviewGUI(redraw); } @@ -255,11 +260,28 @@ public void DoGUI() EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginVertical(); - EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(_name, G("menuitem.prop.name")); - if (EditorGUI.EndChangeCheck()) + var needsRichLabel = (!string.IsNullOrEmpty(_prop_label.stringValue) || _isTryingRichLabel); + if (!needsRichLabel) + { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(_name, G("menuitem.prop.name")); + if (EditorGUI.EndChangeCheck()) + { + _name.serializedObject.ApplyModifiedProperties(); + } + } + else + { + EditorGUILayout.PropertyField(_prop_label, G("menuitem.prop.name")); + } + + if (needsRichLabel) { - _name.serializedObject.ApplyModifiedProperties(); + var style = new GUIStyle(EditorStyles.textField); + style.richText = true; + style.alignment = TextAnchor.MiddleCenter; + + EditorGUILayout.LabelField(" ", _prop_label.stringValue, style, GUILayout.Height(EditorGUIUtility.singleLineHeight * 3)); } EditorGUILayout.PropertyField(_texture, G("menuitem.prop.icon")); @@ -269,7 +291,21 @@ public void DoGUI() _parameterGUI.DoGUI(true); ShowInnateParameterGUI(); - + + var newRichValue = EditorGUILayout.Toggle(G("menuitem.prop.rich_text"), needsRichLabel); + if (newRichValue != needsRichLabel) + { + if (newRichValue) + { + _isTryingRichLabel = true; + } + else + { + _isTryingRichLabel = false; + _prop_label.stringValue = ""; + } + } + EditorGUILayout.EndVertical(); if (_texture != null) diff --git a/Editor/Localization/en-US.json b/Editor/Localization/en-US.json index bc7e8ec8..de041189 100644 --- a/Editor/Localization/en-US.json +++ b/Editor/Localization/en-US.json @@ -280,5 +280,8 @@ "ro_sim.effect_group.rule_inverted": "This rule is inverted", "ro_sim.effect_group.rule_inverted.tooltip": "This rule will be applied when one of its conditions is NOT met", - "ro_sim.effect_group.conditions": "Conditions" + "ro_sim.effect_group.conditions": "Conditions", + + "menuitem.prop.rich_text": "Rich text", + "menuitem.prop.rich_text.tooltip": "Use a long name which may contain rich text and line breaks." } diff --git a/Runtime/ModularAvatarMenuItem.cs b/Runtime/ModularAvatarMenuItem.cs index b5fd5d31..d8cf2d36 100644 --- a/Runtime/ModularAvatarMenuItem.cs +++ b/Runtime/ModularAvatarMenuItem.cs @@ -49,6 +49,14 @@ public class ModularAvatarMenuItem : AvatarTagComponent, MenuSource /// public bool automaticValue; + /// + /// Although unspecified, the label of a menu may contain rich text and line breaks. + /// If label is not an empty string, this MenuItem will use that as its name. + /// Otherwise, it will use the name of the containing game object as the label. + /// + [Multiline] + public string label = ""; + private void Reset() { // Init settings only when added or reset manually from the Inspector. @@ -98,7 +106,7 @@ public void Visit(NodeContext context) var cloned = new VirtualControl(Control); cloned.subMenu = null; - cloned.name = gameObject.name; + cloned.name = string.IsNullOrEmpty(label) ? gameObject.name : label; FilterSubParameters(cloned); From 776f08be3f1c6e8de8772e425a7931cfc05e484a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C3=AF=7E?= Date: Tue, 3 Sep 2024 22:25:20 +0200 Subject: [PATCH 2/4] review: - Add a link/unlink icon to the right of the name field. - Only show the rich text preview when there is the character '<' in the label field. - Toggling link from ON to OFF will set the label to the current GameObject name. --- Editor/Inspector/Menu/MenuItemGUI.cs | 34 ++++++++++++++++------------ Editor/Localization/en-US.json | 4 ++-- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Editor/Inspector/Menu/MenuItemGUI.cs b/Editor/Inspector/Menu/MenuItemGUI.cs index cf1ada0b..a48a35de 100644 --- a/Editor/Inspector/Menu/MenuItemGUI.cs +++ b/Editor/Inspector/Menu/MenuItemGUI.cs @@ -260,6 +260,8 @@ public void DoGUI() EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginVertical(); + + EditorGUILayout.BeginHorizontal(); var needsRichLabel = (!string.IsNullOrEmpty(_prop_label.stringValue) || _isTryingRichLabel); if (!needsRichLabel) { @@ -274,8 +276,24 @@ public void DoGUI() { EditorGUILayout.PropertyField(_prop_label, G("menuitem.prop.name")); } + var linkIcon = EditorGUIUtility.IconContent(needsRichLabel ? "UnLinked" : "Linked").image; + var guiIcon = new GUIContent(linkIcon, S(needsRichLabel ? "menuitem.rich_text.toggle_off.tooltip" : "menuitem.rich_text.toggle_on.tooltip")); + if (GUILayout.Button(guiIcon, GUILayout.Height(EditorGUIUtility.singleLineHeight), GUILayout.Width(25))) + { + if (!needsRichLabel) + { + _isTryingRichLabel = true; + _prop_label.stringValue = _name.stringValue; + } + else + { + _isTryingRichLabel = false; + _prop_label.stringValue = ""; + } + } + EditorGUILayout.EndHorizontal(); - if (needsRichLabel) + if (needsRichLabel && _prop_label.stringValue.Contains("<")) { var style = new GUIStyle(EditorStyles.textField); style.richText = true; @@ -292,20 +310,6 @@ public void DoGUI() ShowInnateParameterGUI(); - var newRichValue = EditorGUILayout.Toggle(G("menuitem.prop.rich_text"), needsRichLabel); - if (newRichValue != needsRichLabel) - { - if (newRichValue) - { - _isTryingRichLabel = true; - } - else - { - _isTryingRichLabel = false; - _prop_label.stringValue = ""; - } - } - EditorGUILayout.EndVertical(); if (_texture != null) diff --git a/Editor/Localization/en-US.json b/Editor/Localization/en-US.json index de041189..8bc6fc02 100644 --- a/Editor/Localization/en-US.json +++ b/Editor/Localization/en-US.json @@ -282,6 +282,6 @@ "ro_sim.effect_group.rule_inverted.tooltip": "This rule will be applied when one of its conditions is NOT met", "ro_sim.effect_group.conditions": "Conditions", - "menuitem.prop.rich_text": "Rich text", - "menuitem.prop.rich_text.tooltip": "Use a long name which may contain rich text and line breaks." + "menuitem.rich_text.toggle_on.tooltip": "Use a long name which may contain rich text and line breaks.", + "menuitem.rich_text.toggle_off.tooltip": "Use the GameObject name." } From c5e787045a979363a3de8e3aa4a7a18ccd56a4a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C3=AF=7E?= Date: Fri, 6 Sep 2024 15:01:09 +0200 Subject: [PATCH 3/4] review: - Rebased to 1.10.0-rc.4 because the inspector of SubMenu of source Expressions Menu were broken in the base commit this branch initially started with, which was preventing testing some aspects raised during review. - When this is being rendered as part of an SubMenu of source Expressions Menu, don't use any of the label logic, as menu items within such an Expressions Menu are not backed by any GameObject. - Rename _isTryingRichLabel to _useLabel. - Since switching to unlinked always overwrites the label field with the current ObjectName, and switching to linked always empties the label field, the state of _useLabel while the Inspector is open is implied by the value of the label field, or the previous state of the _useLabel field itself when the label field is being emptied out. - In addition, use the |= operator. - When the name is linked, and the user begins typing the "<" character, set the label field, and do not apply the name. This will automatically switch to linked mode as the inspector will be reevaluated a second time. - If the original object name already contains a "<" character (i.e. it comes from a previous version of Modular Avatar), there will be no automatic conversion happening as long as the object name still contains the "<" character. - Changed the localization keys to discard the rich text toggle aspect. - Not addressed: When multiple Menu Item components are selected, the behaviour of the inspector currently edits the GameObject name, with no link button, and no automatic conversion when typing "<", regardless of the contents of the label field. --- Editor/Inspector/Menu/MenuItemGUI.cs | 48 +++++++++++++++++++--------- Editor/Localization/en-US.json | 4 +-- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/Editor/Inspector/Menu/MenuItemGUI.cs b/Editor/Inspector/Menu/MenuItemGUI.cs index a48a35de..8a9c427c 100644 --- a/Editor/Inspector/Menu/MenuItemGUI.cs +++ b/Editor/Inspector/Menu/MenuItemGUI.cs @@ -59,6 +59,8 @@ internal static ImmutableList GetParametersForObject(GameObje internal class MenuItemCoreGUI { + private const string ImpliesRichText = "<"; + private static readonly ObjectIDGenerator IdGenerator = new ObjectIDGenerator(); private readonly GameObject _parameterReference; private readonly Action _redraw; @@ -99,7 +101,7 @@ internal class MenuItemCoreGUI private readonly Dictionary _knownParameters = new(); private bool _parameterSourceNotDetermined; - private bool _isTryingRichLabel; + private bool _useLabel; public MenuItemCoreGUI(SerializedObject obj, Action redraw) { @@ -262,8 +264,8 @@ public void DoGUI() EditorGUILayout.BeginVertical(); EditorGUILayout.BeginHorizontal(); - var needsRichLabel = (!string.IsNullOrEmpty(_prop_label.stringValue) || _isTryingRichLabel); - if (!needsRichLabel) + + if (_parameterReference == null) { EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_name, G("menuitem.prop.name")); @@ -274,26 +276,42 @@ public void DoGUI() } else { - EditorGUILayout.PropertyField(_prop_label, G("menuitem.prop.name")); - } - var linkIcon = EditorGUIUtility.IconContent(needsRichLabel ? "UnLinked" : "Linked").image; - var guiIcon = new GUIContent(linkIcon, S(needsRichLabel ? "menuitem.rich_text.toggle_off.tooltip" : "menuitem.rich_text.toggle_on.tooltip")); - if (GUILayout.Button(guiIcon, GUILayout.Height(EditorGUIUtility.singleLineHeight), GUILayout.Width(25))) - { - if (!needsRichLabel) + _useLabel |= !string.IsNullOrEmpty(_prop_label.stringValue); + + if (!_useLabel) { - _isTryingRichLabel = true; - _prop_label.stringValue = _name.stringValue; + EditorGUI.BeginChangeCheck(); + var previousName = _name.stringValue; + EditorGUILayout.PropertyField(_name, G("menuitem.prop.name")); + if (EditorGUI.EndChangeCheck()) + { + if (!previousName.Contains(ImpliesRichText) && _name.stringValue.Contains(ImpliesRichText)) + { + _prop_label.stringValue = _name.stringValue; + } + else + { + _name.serializedObject.ApplyModifiedProperties(); + } + } } else { - _isTryingRichLabel = false; - _prop_label.stringValue = ""; + EditorGUILayout.PropertyField(_prop_label, G("menuitem.prop.name")); + } + + var linkIcon = EditorGUIUtility.IconContent(_useLabel ? "UnLinked" : "Linked").image; + var guiIcon = new GUIContent(linkIcon, S(_useLabel ? "menuitem.label.gameobject_name.tooltip" : "menuitem.label.long_name.tooltip")); + if (GUILayout.Button(guiIcon, GUILayout.Height(EditorGUIUtility.singleLineHeight), GUILayout.Width(25))) + { + _prop_label.stringValue = !_useLabel ? _name.stringValue : ""; + _useLabel = !_useLabel; } } + EditorGUILayout.EndHorizontal(); - if (needsRichLabel && _prop_label.stringValue.Contains("<")) + if (_useLabel && _prop_label.stringValue.Contains(ImpliesRichText)) { var style = new GUIStyle(EditorStyles.textField); style.richText = true; diff --git a/Editor/Localization/en-US.json b/Editor/Localization/en-US.json index 8bc6fc02..08c1dcf3 100644 --- a/Editor/Localization/en-US.json +++ b/Editor/Localization/en-US.json @@ -282,6 +282,6 @@ "ro_sim.effect_group.rule_inverted.tooltip": "This rule will be applied when one of its conditions is NOT met", "ro_sim.effect_group.conditions": "Conditions", - "menuitem.rich_text.toggle_on.tooltip": "Use a long name which may contain rich text and line breaks.", - "menuitem.rich_text.toggle_off.tooltip": "Use the GameObject name." + "menuitem.label.long_name.tooltip": "Use a long name which may contain rich text and line breaks.", + "menuitem.label.gameobject_name.tooltip": "Use the GameObject name." } From 94002e4594291ca094e2214353ad1acd6eff3de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C3=AF=7E?= Date: Tue, 1 Oct 2024 21:28:54 +0200 Subject: [PATCH 4/4] review: - Rebased to 1.10.0 - When editing multiple objects, always edit the label. --- Editor/Inspector/Menu/MenuItemGUI.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Editor/Inspector/Menu/MenuItemGUI.cs b/Editor/Inspector/Menu/MenuItemGUI.cs index 8a9c427c..fca5fcd9 100644 --- a/Editor/Inspector/Menu/MenuItemGUI.cs +++ b/Editor/Inspector/Menu/MenuItemGUI.cs @@ -268,7 +268,14 @@ public void DoGUI() if (_parameterReference == null) { EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(_name, G("menuitem.prop.name")); + if (_obj != null && _obj.isEditingMultipleObjects) + { + EditorGUILayout.PropertyField(_prop_label, G("menuitem.prop.name")); + } + else + { + EditorGUILayout.PropertyField(_name, G("menuitem.prop.name")); + } if (EditorGUI.EndChangeCheck()) { _name.serializedObject.ApplyModifiedProperties();