2023-11-10 14:37:56 +08:00
|
|
|
|
#if MA_VRCSDK3_AVATARS
|
|
|
|
|
|
|
|
|
|
using System;
|
2024-08-05 10:31:43 +08:00
|
|
|
|
using System.Collections.Generic;
|
2024-09-18 11:25:47 +08:00
|
|
|
|
using System.Collections.Immutable;
|
2023-02-25 15:45:24 +08:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Runtime.Serialization;
|
|
|
|
|
using nadena.dev.modular_avatar.core.menu;
|
2024-08-05 10:31:43 +08:00
|
|
|
|
using nadena.dev.ndmf;
|
2024-09-18 11:25:47 +08:00
|
|
|
|
using nadena.dev.ndmf.preview;
|
2023-02-25 15:45:24 +08:00
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEngine;
|
2024-08-14 11:32:42 +08:00
|
|
|
|
using VRC.SDK3.Avatars.Components;
|
2023-02-25 15:45:24 +08:00
|
|
|
|
using VRC.SDK3.Avatars.ScriptableObjects;
|
|
|
|
|
using static nadena.dev.modular_avatar.core.editor.Localization;
|
2024-08-05 10:31:43 +08:00
|
|
|
|
using Object = UnityEngine.Object;
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
namespace nadena.dev.modular_avatar.core.editor
|
|
|
|
|
{
|
|
|
|
|
[CustomPropertyDrawer(typeof(SubmenuSource))]
|
|
|
|
|
class SubmenuSourceDrawer : EnumDrawer<SubmenuSource>
|
|
|
|
|
{
|
|
|
|
|
protected override string localizationPrefix => "submenu_source";
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 11:25:47 +08:00
|
|
|
|
internal static class ParameterIntrospectionCache
|
|
|
|
|
{
|
2024-09-26 11:01:59 +08:00
|
|
|
|
internal static PropCache<GameObject, ImmutableList<ProvidedParameter>> ProvidedParameterCache =
|
|
|
|
|
new("GetParametersForObject", GetParametersForObject_miss);
|
2024-09-18 11:25:47 +08:00
|
|
|
|
|
|
|
|
|
internal static PropCache<GameObject, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping>>
|
2024-09-26 11:01:59 +08:00
|
|
|
|
ParameterRemappingCache = new("GetParameterRemappingsAt", GetParameterRemappingsAt_miss);
|
2024-09-18 11:25:47 +08:00
|
|
|
|
|
|
|
|
|
private static ImmutableList<ProvidedParameter> GetParametersForObject_miss(ComputeContext ctx, GameObject obj)
|
|
|
|
|
{
|
|
|
|
|
if (obj == null) return ImmutableList<ProvidedParameter>.Empty;
|
|
|
|
|
|
|
|
|
|
return ParameterInfo.ForPreview(ctx).GetParametersForObject(obj).ToImmutableList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static ImmutableDictionary<(ParameterNamespace, string), ParameterMapping>
|
|
|
|
|
GetParameterRemappingsAt_miss(ComputeContext ctx, GameObject obj)
|
|
|
|
|
{
|
|
|
|
|
if (obj == null) return ImmutableDictionary<(ParameterNamespace, string), ParameterMapping>.Empty;
|
|
|
|
|
|
|
|
|
|
return ParameterInfo.ForPreview(ctx).GetParameterRemappingsAt(obj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static ImmutableList<ProvidedParameter> GetParametersForObject(GameObject avatar)
|
|
|
|
|
{
|
|
|
|
|
return ProvidedParameterCache.Get(ComputeContext.NullContext, avatar);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> GetParameterRemappingsAt(GameObject avatar)
|
|
|
|
|
{
|
|
|
|
|
return ParameterRemappingCache.Get(ComputeContext.NullContext, avatar);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
internal class MenuItemCoreGUI
|
|
|
|
|
{
|
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.
2024-09-06 21:01:09 +08:00
|
|
|
|
private const string ImpliesRichText = "<";
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
private static readonly ObjectIDGenerator IdGenerator = new ObjectIDGenerator();
|
|
|
|
|
private readonly GameObject _parameterReference;
|
|
|
|
|
private readonly Action _redraw;
|
|
|
|
|
|
|
|
|
|
private readonly SerializedObject _obj;
|
|
|
|
|
|
|
|
|
|
private readonly SerializedProperty _name;
|
|
|
|
|
private readonly SerializedProperty _texture;
|
|
|
|
|
private readonly SerializedProperty _type;
|
|
|
|
|
private readonly SerializedProperty _value;
|
|
|
|
|
private readonly SerializedProperty _submenu;
|
|
|
|
|
|
|
|
|
|
private readonly ParameterGUI _parameterGUI;
|
2024-08-05 10:31:43 +08:00
|
|
|
|
private readonly SerializedProperty _parameterName;
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
private readonly SerializedProperty _subParamsRoot;
|
|
|
|
|
private readonly SerializedProperty _labelsRoot;
|
|
|
|
|
|
|
|
|
|
private readonly MenuPreviewGUI _previewGUI;
|
|
|
|
|
|
|
|
|
|
private ParameterGUI[] _subParams;
|
|
|
|
|
private SerializedProperty[] _labels;
|
|
|
|
|
|
|
|
|
|
private int texPicker = -1;
|
|
|
|
|
|
|
|
|
|
private readonly SerializedProperty _prop_submenuSource;
|
|
|
|
|
private readonly SerializedProperty _prop_otherObjSource;
|
|
|
|
|
|
2024-08-05 10:31:43 +08:00
|
|
|
|
private readonly SerializedProperty _prop_isSynced;
|
|
|
|
|
private readonly SerializedProperty _prop_isSaved;
|
|
|
|
|
private readonly SerializedProperty _prop_isDefault;
|
2024-09-04 10:07:33 +08:00
|
|
|
|
private readonly SerializedProperty _prop_automaticValue;
|
2024-08-05 10:31:43 +08:00
|
|
|
|
|
2024-09-05 17:35:51 +08:00
|
|
|
|
private readonly SerializedProperty _prop_label;
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
public bool AlwaysExpandContents = false;
|
|
|
|
|
public bool ExpandContents = false;
|
|
|
|
|
|
2024-08-14 11:32:42 +08:00
|
|
|
|
private readonly Dictionary<string, ProvidedParameter> _knownParameters = new();
|
|
|
|
|
private bool _parameterSourceNotDetermined;
|
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.
2024-09-06 21:01:09 +08:00
|
|
|
|
private bool _useLabel;
|
2024-08-05 10:31:43 +08:00
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
public MenuItemCoreGUI(SerializedObject obj, Action redraw)
|
|
|
|
|
{
|
|
|
|
|
_obj = obj;
|
|
|
|
|
|
|
|
|
|
GameObject parameterReference = null;
|
|
|
|
|
if (obj.targetObjects.Length == 1)
|
|
|
|
|
{
|
|
|
|
|
parameterReference = (obj.targetObject as Component)?.gameObject;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_parameterReference = parameterReference;
|
|
|
|
|
_redraw = redraw;
|
|
|
|
|
|
2024-08-05 10:31:43 +08:00
|
|
|
|
InitKnownParameters();
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
var gameObjects = new SerializedObject(
|
|
|
|
|
obj.targetObjects.Select(o =>
|
2024-08-05 10:31:43 +08:00
|
|
|
|
(Object) ((ModularAvatarMenuItem) o).gameObject
|
2023-02-25 15:45:24 +08:00
|
|
|
|
).ToArray()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
_name = gameObjects.FindProperty("m_Name");
|
2023-03-04 13:31:23 +08:00
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
var control = obj.FindProperty(nameof(ModularAvatarMenuItem.Control));
|
|
|
|
|
|
|
|
|
|
_texture = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.icon));
|
|
|
|
|
_type = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.type));
|
2024-08-05 10:31:43 +08:00
|
|
|
|
_parameterName = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter))
|
2023-02-25 15:45:24 +08:00
|
|
|
|
.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter.name));
|
2024-08-05 10:31:43 +08:00
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
_value = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.value));
|
|
|
|
|
_submenu = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subMenu));
|
|
|
|
|
|
2024-08-05 10:31:43 +08:00
|
|
|
|
_parameterGUI = new ParameterGUI(parameterReference, _parameterName, redraw);
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
_subParamsRoot = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subParameters));
|
|
|
|
|
_labelsRoot = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.labels));
|
|
|
|
|
|
|
|
|
|
_prop_submenuSource = obj.FindProperty(nameof(ModularAvatarMenuItem.MenuSource));
|
|
|
|
|
_prop_otherObjSource = obj.FindProperty(nameof(ModularAvatarMenuItem.menuSource_otherObjectChildren));
|
2024-08-05 10:31:43 +08:00
|
|
|
|
|
|
|
|
|
_prop_isSynced = obj.FindProperty(nameof(ModularAvatarMenuItem.isSynced));
|
|
|
|
|
_prop_isSaved = obj.FindProperty(nameof(ModularAvatarMenuItem.isSaved));
|
|
|
|
|
_prop_isDefault = obj.FindProperty(nameof(ModularAvatarMenuItem.isDefault));
|
2024-09-04 10:07:33 +08:00
|
|
|
|
_prop_automaticValue = obj.FindProperty(nameof(ModularAvatarMenuItem.automaticValue));
|
2024-08-05 10:31:43 +08:00
|
|
|
|
|
2024-09-05 17:35:51 +08:00
|
|
|
|
_prop_label = obj.FindProperty(nameof(ModularAvatarMenuItem.label));
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
_previewGUI = new MenuPreviewGUI(redraw);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-05 10:31:43 +08:00
|
|
|
|
private void InitKnownParameters()
|
|
|
|
|
{
|
2024-08-14 11:32:42 +08:00
|
|
|
|
var paramRef = _parameterReference;
|
|
|
|
|
if (_parameterReference == null)
|
|
|
|
|
// TODO: This could give incorrect results in some cases when we have multiple objects selected with
|
|
|
|
|
// different rename contexts.
|
|
|
|
|
paramRef = (_obj.targetObjects[0] as Component)?.gameObject;
|
2024-08-05 10:31:43 +08:00
|
|
|
|
|
2024-08-14 11:32:42 +08:00
|
|
|
|
if (paramRef == null)
|
|
|
|
|
{
|
|
|
|
|
_parameterSourceNotDetermined = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-22 11:28:52 +08:00
|
|
|
|
var parentAvatar = RuntimeUtil.FindAvatarInParents(paramRef.transform);
|
|
|
|
|
if (parentAvatar == null)
|
|
|
|
|
{
|
|
|
|
|
_parameterSourceNotDetermined = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-14 11:32:42 +08:00
|
|
|
|
Dictionary<string, ProvidedParameter> rootParameters = new();
|
2024-11-17 11:02:37 +08:00
|
|
|
|
|
2024-09-18 11:25:47 +08:00
|
|
|
|
foreach (var param in ParameterIntrospectionCache.GetParametersForObject(parentAvatar.gameObject)
|
2024-08-22 11:28:52 +08:00
|
|
|
|
.Where(p => p.Namespace == ParameterNamespace.Animator)
|
2024-08-14 11:32:42 +08:00
|
|
|
|
)
|
2024-11-17 11:02:37 +08:00
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(param.EffectiveName))
|
|
|
|
|
{
|
|
|
|
|
rootParameters[param.EffectiveName] = param;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-05 10:31:43 +08:00
|
|
|
|
|
2024-09-18 11:25:47 +08:00
|
|
|
|
var remaps = ParameterIntrospectionCache.GetParameterRemappingsAt(paramRef);
|
2024-08-05 10:31:43 +08:00
|
|
|
|
foreach (var remap in remaps)
|
|
|
|
|
{
|
|
|
|
|
if (remap.Key.Item1 != ParameterNamespace.Animator) continue;
|
2024-08-14 11:32:42 +08:00
|
|
|
|
if (rootParameters.ContainsKey(remap.Value.ParameterName))
|
|
|
|
|
_knownParameters[remap.Key.Item2] = rootParameters[remap.Value.ParameterName];
|
2024-08-05 10:31:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var rootParam in rootParameters)
|
2024-08-14 11:32:42 +08:00
|
|
|
|
if (!remaps.ContainsKey((ParameterNamespace.Animator, rootParam.Key)))
|
|
|
|
|
_knownParameters[rootParam.Key] = rootParam.Value;
|
2024-08-05 10:31:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-03-04 13:31:23 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Builds a menu item GUI for a raw VRCExpressionsMenu.Control reference.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="parameterReference"></param>
|
|
|
|
|
/// <param name="_control"></param>
|
|
|
|
|
/// <param name="redraw"></param>
|
2023-02-25 15:45:24 +08:00
|
|
|
|
public MenuItemCoreGUI(GameObject parameterReference, SerializedProperty _control, Action redraw)
|
|
|
|
|
{
|
|
|
|
|
_obj = _control.serializedObject;
|
|
|
|
|
_parameterReference = parameterReference;
|
2024-08-05 10:31:43 +08:00
|
|
|
|
InitKnownParameters();
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
_redraw = redraw;
|
|
|
|
|
_name = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.name));
|
|
|
|
|
_texture = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.icon));
|
|
|
|
|
_type = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.type));
|
2024-08-05 10:31:43 +08:00
|
|
|
|
_parameterName = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter))
|
2023-02-25 15:45:24 +08:00
|
|
|
|
.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter.name));
|
2024-08-05 10:31:43 +08:00
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
_value = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.value));
|
|
|
|
|
_submenu = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subMenu));
|
|
|
|
|
|
2024-08-05 10:31:43 +08:00
|
|
|
|
_parameterGUI = new ParameterGUI(parameterReference, _parameterName, redraw);
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
_subParamsRoot = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subParameters));
|
|
|
|
|
_labelsRoot = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.labels));
|
|
|
|
|
|
2024-08-05 10:31:43 +08:00
|
|
|
|
_prop_isSynced = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isSynced));
|
|
|
|
|
_prop_isSaved = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isSaved));
|
|
|
|
|
_prop_isDefault = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isDefault));
|
2024-09-04 10:07:33 +08:00
|
|
|
|
_prop_automaticValue = null;
|
2024-08-05 10:31:43 +08:00
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
_prop_submenuSource = null;
|
|
|
|
|
_prop_otherObjSource = null;
|
|
|
|
|
_previewGUI = new MenuPreviewGUI(redraw);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-14 11:32:42 +08:00
|
|
|
|
private void DrawHorizontalToggleProp(
|
|
|
|
|
SerializedProperty prop,
|
|
|
|
|
GUIContent label,
|
|
|
|
|
bool? forceMixedValues = null,
|
|
|
|
|
bool? forceValue = null
|
|
|
|
|
)
|
2024-08-05 10:31:43 +08:00
|
|
|
|
{
|
|
|
|
|
var toggleSize = EditorStyles.toggle.CalcSize(new GUIContent());
|
|
|
|
|
var labelSize = EditorStyles.label.CalcSize(label);
|
|
|
|
|
var width = toggleSize.x + labelSize.x + 4;
|
|
|
|
|
|
|
|
|
|
var rect = EditorGUILayout.GetControlRect(GUILayout.Width(width));
|
|
|
|
|
EditorGUI.BeginProperty(rect, label, prop);
|
|
|
|
|
|
2024-08-14 11:32:42 +08:00
|
|
|
|
if (forceMixedValues != null) EditorGUI.showMixedValue = forceMixedValues.Value;
|
|
|
|
|
|
2024-09-04 07:05:18 +08:00
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
var value = EditorGUI.ToggleLeft(rect, label, forceValue ?? prop.boolValue);
|
|
|
|
|
if (EditorGUI.EndChangeCheck()) prop.boolValue = value;
|
|
|
|
|
|
2024-08-05 10:31:43 +08:00
|
|
|
|
EditorGUI.EndProperty();
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
public void DoGUI()
|
|
|
|
|
{
|
2024-09-04 06:53:44 +08:00
|
|
|
|
if (_obj != null) _obj.UpdateIfRequiredOrScript();
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.BeginVertical();
|
2024-09-04 04:25:20 +08:00
|
|
|
|
|
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
2024-12-03 09:35:43 +08:00
|
|
|
|
|
|
|
|
|
if (_prop_label == null)
|
2023-03-04 13:31:23 +08:00
|
|
|
|
{
|
2024-09-05 17:35:51 +08:00
|
|
|
|
EditorGUI.BeginChangeCheck();
|
2024-10-02 03:28:54 +08:00
|
|
|
|
if (_obj != null && _obj.isEditingMultipleObjects)
|
|
|
|
|
{
|
|
|
|
|
EditorGUILayout.PropertyField(_prop_label, G("menuitem.prop.name"));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
EditorGUILayout.PropertyField(_name, G("menuitem.prop.name"));
|
|
|
|
|
}
|
2024-09-05 17:35:51 +08:00
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
|
|
|
{
|
|
|
|
|
_name.serializedObject.ApplyModifiedProperties();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
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.
2024-09-06 21:01:09 +08:00
|
|
|
|
_useLabel |= !string.IsNullOrEmpty(_prop_label.stringValue);
|
|
|
|
|
|
|
|
|
|
if (!_useLabel)
|
2024-09-04 04:25:20 +08:00
|
|
|
|
{
|
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.
2024-09-06 21:01:09 +08:00
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-04 04:25:20 +08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
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.
2024-09-06 21:01:09 +08:00
|
|
|
|
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;
|
2024-09-04 04:25:20 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
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.
2024-09-06 21:01:09 +08:00
|
|
|
|
|
2024-09-04 04:25:20 +08:00
|
|
|
|
EditorGUILayout.EndHorizontal();
|
2024-09-05 17:35:51 +08:00
|
|
|
|
|
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.
2024-09-06 21:01:09 +08:00
|
|
|
|
if (_useLabel && _prop_label.stringValue.Contains(ImpliesRichText))
|
2024-09-05 17:35:51 +08:00
|
|
|
|
{
|
|
|
|
|
var style = new GUIStyle(EditorStyles.textField);
|
|
|
|
|
style.richText = true;
|
|
|
|
|
style.alignment = TextAnchor.MiddleCenter;
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.LabelField(" ", _prop_label.stringValue, style, GUILayout.Height(EditorGUIUtility.singleLineHeight * 3));
|
2023-03-04 13:31:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
EditorGUILayout.PropertyField(_texture, G("menuitem.prop.icon"));
|
|
|
|
|
EditorGUILayout.PropertyField(_type, G("menuitem.prop.type"));
|
2024-09-04 10:07:33 +08:00
|
|
|
|
DoValueField();
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
2023-05-14 19:24:43 +08:00
|
|
|
|
_parameterGUI.DoGUI(true);
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
2024-08-22 10:23:26 +08:00
|
|
|
|
ShowInnateParameterGUI();
|
2024-09-05 17:35:51 +08:00
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
|
|
|
|
|
|
if (_texture != null)
|
|
|
|
|
{
|
|
|
|
|
var tex = _texture.objectReferenceValue as Texture2D;
|
|
|
|
|
if (tex != null && !_texture.hasMultipleDifferentValues)
|
|
|
|
|
{
|
|
|
|
|
var size = EditorGUIUtility.singleLineHeight * 5;
|
|
|
|
|
var margin = 4;
|
|
|
|
|
var withMargin = new Vector2(margin + size, margin + size);
|
|
|
|
|
|
|
|
|
|
var rect = GUILayoutUtility.GetRect(withMargin.x, withMargin.y, GUILayout.ExpandWidth(false),
|
|
|
|
|
GUILayout.ExpandHeight(true));
|
|
|
|
|
rect.x += margin;
|
|
|
|
|
rect.y = rect.y + rect.height / 2 - size / 2;
|
|
|
|
|
rect.width = size;
|
|
|
|
|
rect.height = size;
|
|
|
|
|
|
|
|
|
|
GUI.Box(rect, new GUIContent(), "flow node 1");
|
|
|
|
|
GUI.DrawTexture(rect, tex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
|
EditorGUILayout.BeginVertical();
|
|
|
|
|
|
|
|
|
|
if (_type.hasMultipleDifferentValues) return;
|
2024-10-20 08:46:31 +08:00
|
|
|
|
var controlTypeArray = Enum.GetValues(typeof(VRCExpressionsMenu.Control.ControlType));
|
|
|
|
|
var index = Math.Clamp(_type.enumValueIndex, 0, controlTypeArray.Length - 1);
|
|
|
|
|
var type = (VRCExpressionsMenu.Control.ControlType)controlTypeArray.GetValue(index);
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case VRCExpressionsMenu.Control.ControlType.Button:
|
|
|
|
|
case VRCExpressionsMenu.Control.ControlType.Toggle:
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
|
|
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case VRCExpressionsMenu.Control.ControlType.SubMenu:
|
|
|
|
|
{
|
|
|
|
|
object menuSource = null;
|
2023-03-04 13:31:23 +08:00
|
|
|
|
bool canExpand = false;
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
if (_prop_submenuSource != null)
|
|
|
|
|
{
|
|
|
|
|
EditorGUILayout.PropertyField(_prop_submenuSource, G("menuitem.prop.submenu_source"));
|
|
|
|
|
if (_prop_submenuSource.hasMultipleDifferentValues) break;
|
|
|
|
|
|
|
|
|
|
var sourceType = (SubmenuSource) Enum.GetValues(typeof(SubmenuSource))
|
|
|
|
|
.GetValue(_prop_submenuSource.enumValueIndex);
|
|
|
|
|
|
|
|
|
|
switch (sourceType)
|
|
|
|
|
{
|
|
|
|
|
case SubmenuSource.Children:
|
|
|
|
|
{
|
|
|
|
|
EditorGUILayout.PropertyField(_prop_otherObjSource,
|
|
|
|
|
G("menuitem.prop.source_override"));
|
|
|
|
|
if (_prop_otherObjSource.hasMultipleDifferentValues) break;
|
|
|
|
|
if (_prop_otherObjSource.objectReferenceValue == null)
|
|
|
|
|
{
|
|
|
|
|
if (_obj.targetObjects.Length != 1) break;
|
|
|
|
|
menuSource = new MenuNodesUnder((_obj.targetObject as Component)?.gameObject);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
menuSource =
|
|
|
|
|
new MenuNodesUnder((GameObject) _prop_otherObjSource.objectReferenceValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case SubmenuSource.MenuAsset:
|
|
|
|
|
{
|
|
|
|
|
EditorGUILayout.PropertyField(_submenu, G("menuitem.prop.submenu_asset"));
|
2023-03-04 13:31:23 +08:00
|
|
|
|
canExpand = true;
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
if (_submenu.hasMultipleDifferentValues) break;
|
|
|
|
|
menuSource = _submenu.objectReferenceValue;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Native VRCSDK control
|
|
|
|
|
EditorGUILayout.PropertyField(_submenu, G("menuitem.prop.submenu_asset"));
|
|
|
|
|
if (_submenu.hasMultipleDifferentValues) break;
|
|
|
|
|
menuSource = _submenu.objectReferenceValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (menuSource != null)
|
|
|
|
|
{
|
|
|
|
|
if (AlwaysExpandContents)
|
|
|
|
|
{
|
|
|
|
|
ExpandContents = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
EditorGUI.indentLevel += 1;
|
|
|
|
|
ExpandContents = EditorGUILayout.Foldout(ExpandContents, G("menuitem.showcontents"));
|
|
|
|
|
EditorGUI.indentLevel -= 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ExpandContents)
|
|
|
|
|
{
|
|
|
|
|
if (menuSource is VRCExpressionsMenu menu) _previewGUI.DoGUI(menu, _parameterReference);
|
|
|
|
|
else if (menuSource is MenuSource nodes) _previewGUI.DoGUI(nodes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-04 13:31:23 +08:00
|
|
|
|
if (canExpand && (_submenu.hasMultipleDifferentValues || _submenu.objectReferenceValue != null))
|
|
|
|
|
{
|
|
|
|
|
if (GUILayout.Button(G("menuitem.misc.extract")))
|
|
|
|
|
{
|
|
|
|
|
_obj.ApplyModifiedProperties();
|
|
|
|
|
|
|
|
|
|
foreach (var targetObj in _obj.targetObjects)
|
|
|
|
|
{
|
|
|
|
|
var menuItem = (ModularAvatarMenuItem) targetObj;
|
|
|
|
|
if (menuItem.Control.type == VRCExpressionsMenu.Control.ControlType.SubMenu
|
|
|
|
|
&& menuItem.Control.subMenu != null
|
|
|
|
|
&& menuItem.MenuSource == SubmenuSource.MenuAsset
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
Undo.RecordObject(menuItem, "Extract menu");
|
|
|
|
|
MenuExtractor.ExtractSingleLayerMenu(menuItem.Control.subMenu,
|
|
|
|
|
menuItem.gameObject);
|
|
|
|
|
menuItem.Control.subMenu = null;
|
|
|
|
|
menuItem.MenuSource = SubmenuSource.Children;
|
|
|
|
|
menuItem.menuSource_otherObjectChildren = null;
|
|
|
|
|
EditorUtility.SetDirty(menuItem);
|
|
|
|
|
PrefabUtility.RecordPrefabInstancePropertyModifications(menuItem);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_obj.Update();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case VRCExpressionsMenu.Control.ControlType.RadialPuppet:
|
|
|
|
|
{
|
|
|
|
|
EnsureParameterCount(1);
|
|
|
|
|
|
2023-05-14 19:24:43 +08:00
|
|
|
|
_subParams[0].DoGUI(true,
|
2023-03-04 13:31:23 +08:00
|
|
|
|
G("menuitem.param.rotation"));
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case VRCExpressionsMenu.Control.ControlType.TwoAxisPuppet:
|
|
|
|
|
{
|
|
|
|
|
EnsureParameterCount(2);
|
|
|
|
|
EnsureLabelCount(4);
|
|
|
|
|
|
2023-03-04 13:31:23 +08:00
|
|
|
|
EditorGUILayout.LabelField(G("menuitem.label.parameters"), EditorStyles.boldLabel);
|
2023-02-25 15:45:24 +08:00
|
|
|
|
EditorGUILayout.Space(2);
|
|
|
|
|
|
2023-05-14 19:24:43 +08:00
|
|
|
|
_subParams[0].DoGUI(true,
|
2023-03-04 13:31:23 +08:00
|
|
|
|
G("menuitem.param.horizontal"));
|
2023-05-14 19:24:43 +08:00
|
|
|
|
_subParams[1].DoGUI(true,
|
2023-03-04 13:31:23 +08:00
|
|
|
|
G("menuitem.param.vertical"));
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
DoFourAxisLabels(false);
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case VRCExpressionsMenu.Control.ControlType.FourAxisPuppet:
|
|
|
|
|
{
|
|
|
|
|
DoFourAxisLabels(true);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
|
|
_obj.ApplyModifiedProperties();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-22 10:23:26 +08:00
|
|
|
|
private void ShowInnateParameterGUI()
|
|
|
|
|
{
|
|
|
|
|
if (_prop_isDefault == null)
|
|
|
|
|
// This is probably coming from a VRC Expressions menu asset.
|
|
|
|
|
// For now, don't show the UI in this case.
|
|
|
|
|
return;
|
|
|
|
|
|
2024-09-04 07:05:18 +08:00
|
|
|
|
var multipleSelections = _obj.targetObjects.Length > 1;
|
|
|
|
|
|
2024-08-22 10:23:26 +08:00
|
|
|
|
var paramName = _parameterName.stringValue;
|
2024-09-04 07:05:18 +08:00
|
|
|
|
var siblings = FindSiblingMenuItems(_obj);
|
2024-08-22 10:23:26 +08:00
|
|
|
|
|
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
|
|
2024-09-04 07:05:18 +08:00
|
|
|
|
var forceMixedValues = _parameterName.hasMultipleDifferentValues;
|
|
|
|
|
|
|
|
|
|
var syncedIsMixed = forceMixedValues || _prop_isSynced.hasMultipleDifferentValues ||
|
2024-09-04 09:51:36 +08:00
|
|
|
|
siblings != null && siblings.Any(s => s.isSynced != _prop_isSynced.boolValue);
|
2024-09-04 07:05:18 +08:00
|
|
|
|
var savedIsMixed = forceMixedValues || _prop_isSaved.hasMultipleDifferentValues ||
|
2024-09-04 09:51:36 +08:00
|
|
|
|
siblings != null && siblings.Any(s => s.isSaved != _prop_isSaved.boolValue);
|
2024-09-04 07:05:18 +08:00
|
|
|
|
|
2024-08-22 10:23:26 +08:00
|
|
|
|
var knownParameter = _parameterName.hasMultipleDifferentValues
|
|
|
|
|
? null
|
|
|
|
|
: _knownParameters.GetValueOrDefault(paramName);
|
|
|
|
|
|
2024-09-04 07:05:18 +08:00
|
|
|
|
var knownSource = knownParameter?.Source;
|
|
|
|
|
var externalSource = knownSource != null && knownSource is not ModularAvatarMenuItem;
|
|
|
|
|
|
|
|
|
|
if (externalSource) savedIsMixed = true; // NDMF doesn't yet support querying for the saved state
|
|
|
|
|
var forceSyncedValue = externalSource ? knownParameter?.WantSynced : null;
|
|
|
|
|
|
2024-08-22 10:23:26 +08:00
|
|
|
|
var knownParamDefault = knownParameter?.DefaultValue;
|
|
|
|
|
var isDefaultByKnownParam =
|
|
|
|
|
knownParamDefault != null ? _value.floatValue == knownParamDefault : (bool?)null;
|
2024-09-03 08:54:36 +08:00
|
|
|
|
|
2024-09-04 07:05:18 +08:00
|
|
|
|
if (knownParameter != null && knownParameter.Source is ModularAvatarMenuItem)
|
2024-09-03 08:54:36 +08:00
|
|
|
|
isDefaultByKnownParam = null;
|
|
|
|
|
|
2024-09-04 10:07:33 +08:00
|
|
|
|
if (_prop_automaticValue?.boolValue == true) isDefaultByKnownParam = null;
|
|
|
|
|
|
2024-08-22 10:23:26 +08:00
|
|
|
|
Object controller = knownParameter?.Source;
|
2024-09-04 07:05:18 +08:00
|
|
|
|
|
|
|
|
|
// If we can't figure out what to reference the parameter names to, or if they're controlled by something
|
|
|
|
|
// other than the Menu Item component itself, disable the UI
|
|
|
|
|
var controllerIsElsewhere = externalSource || _parameterSourceNotDetermined;
|
2024-08-22 10:23:26 +08:00
|
|
|
|
|
|
|
|
|
using (new EditorGUI.DisabledScope(
|
|
|
|
|
_parameterName.hasMultipleDifferentValues || controllerIsElsewhere)
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
// If we have multiple menu items selected, it probably doesn't make sense to make them all default.
|
|
|
|
|
// But, we do want to see if _any_ are default.
|
|
|
|
|
var anyIsDefault = _prop_isDefault.hasMultipleDifferentValues || _prop_isDefault.boolValue;
|
|
|
|
|
var mixedIsDefault = multipleSelections && anyIsDefault;
|
2024-11-17 11:20:36 +08:00
|
|
|
|
|
|
|
|
|
var allAreAutoParams = !_parameterName.hasMultipleDifferentValues &&
|
|
|
|
|
string.IsNullOrWhiteSpace(_parameterName.stringValue);
|
|
|
|
|
|
|
|
|
|
using (new EditorGUI.DisabledScope((!allAreAutoParams && multipleSelections) ||
|
|
|
|
|
isDefaultByKnownParam != null))
|
2024-08-22 10:23:26 +08:00
|
|
|
|
{
|
2024-09-03 08:54:36 +08:00
|
|
|
|
EditorGUI.BeginChangeCheck();
|
2024-08-22 10:23:26 +08:00
|
|
|
|
DrawHorizontalToggleProp(_prop_isDefault, G("menuitem.prop.is_default"), mixedIsDefault,
|
2024-09-04 07:05:18 +08:00
|
|
|
|
isDefaultByKnownParam);
|
2024-09-03 08:54:36 +08:00
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
|
|
|
{
|
|
|
|
|
_obj.ApplyModifiedProperties();
|
2024-09-04 07:05:18 +08:00
|
|
|
|
ClearConflictingDefaults(siblings);
|
2024-09-03 08:54:36 +08:00
|
|
|
|
}
|
2024-08-22 10:23:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GUILayout.FlexibleSpace();
|
|
|
|
|
|
2024-09-04 07:05:18 +08:00
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
DrawHorizontalToggleProp(_prop_isSaved, G("menuitem.prop.is_saved"), savedIsMixed);
|
|
|
|
|
if (EditorGUI.EndChangeCheck() && siblings != null)
|
|
|
|
|
foreach (var sibling in siblings)
|
|
|
|
|
{
|
|
|
|
|
sibling.isSaved = _prop_isSaved.boolValue;
|
|
|
|
|
EditorUtility.SetDirty(sibling);
|
|
|
|
|
PrefabUtility.RecordPrefabInstancePropertyModifications(sibling);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-22 10:23:26 +08:00
|
|
|
|
GUILayout.FlexibleSpace();
|
2024-09-04 07:05:18 +08:00
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
DrawHorizontalToggleProp(_prop_isSynced, G("menuitem.prop.is_synced"), syncedIsMixed,
|
|
|
|
|
forceSyncedValue);
|
|
|
|
|
if (EditorGUI.EndChangeCheck() && siblings != null)
|
|
|
|
|
foreach (var sibling in siblings)
|
|
|
|
|
{
|
|
|
|
|
sibling.isSynced = _prop_isSynced.boolValue;
|
|
|
|
|
EditorUtility.SetDirty(sibling);
|
|
|
|
|
PrefabUtility.RecordPrefabInstancePropertyModifications(sibling);
|
|
|
|
|
}
|
2024-08-22 10:23:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (controllerIsElsewhere)
|
|
|
|
|
{
|
|
|
|
|
var refStyle = EditorStyles.toggle;
|
|
|
|
|
var refContent = new GUIContent("test");
|
|
|
|
|
var refRect = refStyle.CalcSize(refContent);
|
|
|
|
|
var height = refRect.y + EditorStyles.toggle.margin.top + EditorStyles.toggle.margin.bottom;
|
|
|
|
|
|
|
|
|
|
GUILayout.FlexibleSpace();
|
|
|
|
|
var style = new GUIStyle(EditorStyles.miniButton);
|
|
|
|
|
style.fixedWidth = 0;
|
|
|
|
|
style.fixedHeight = 0;
|
|
|
|
|
style.stretchHeight = true;
|
|
|
|
|
style.stretchWidth = true;
|
|
|
|
|
style.imagePosition = ImagePosition.ImageOnly;
|
|
|
|
|
var icon = EditorGUIUtility.FindTexture("d_Search Icon");
|
|
|
|
|
|
|
|
|
|
var rect = GUILayoutUtility.GetRect(new GUIContent(), style, GUILayout.ExpandWidth(false),
|
|
|
|
|
GUILayout.Width(height), GUILayout.Height(height));
|
|
|
|
|
|
|
|
|
|
if (GUI.Button(rect, new GUIContent(), style))
|
|
|
|
|
{
|
|
|
|
|
if (controller is VRCAvatarDescriptor desc) controller = desc.expressionParameters;
|
|
|
|
|
Selection.activeObject = controller;
|
|
|
|
|
EditorGUIUtility.PingObject(controller);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rect.xMin += 2;
|
|
|
|
|
rect.yMin += 2;
|
|
|
|
|
rect.xMax -= 2;
|
|
|
|
|
rect.yMax -= 2;
|
|
|
|
|
GUI.DrawTexture(rect, icon);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-04 10:07:33 +08:00
|
|
|
|
private void DoValueField()
|
|
|
|
|
{
|
|
|
|
|
var value_label = G("menuitem.prop.value");
|
|
|
|
|
var auto_label = G("menuitem.prop.automatic_value");
|
|
|
|
|
|
|
|
|
|
if (_prop_automaticValue == null)
|
|
|
|
|
{
|
|
|
|
|
EditorGUILayout.PropertyField(_value, value_label);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var toggleSize = EditorStyles.toggle.CalcSize(new GUIContent());
|
|
|
|
|
var autoLabelSize = EditorStyles.label.CalcSize(auto_label);
|
|
|
|
|
|
|
|
|
|
var style = EditorStyles.numberField;
|
|
|
|
|
var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight, style);
|
|
|
|
|
|
|
|
|
|
var valueRect = rect;
|
|
|
|
|
valueRect.xMax -= toggleSize.x + autoLabelSize.x + 4;
|
|
|
|
|
|
|
|
|
|
var autoRect = rect;
|
|
|
|
|
autoRect.xMin = valueRect.xMax + 4;
|
|
|
|
|
|
|
|
|
|
var suppressValue = _prop_automaticValue.boolValue || _prop_automaticValue.hasMultipleDifferentValues;
|
|
|
|
|
|
|
|
|
|
using (new EditorGUI.DisabledScope(suppressValue))
|
|
|
|
|
{
|
|
|
|
|
if (suppressValue)
|
|
|
|
|
{
|
|
|
|
|
EditorGUI.TextField(valueRect, value_label, "", style);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
EditorGUI.PropertyField(valueRect, _value, value_label);
|
|
|
|
|
if (EditorGUI.EndChangeCheck()) _prop_automaticValue.boolValue = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorGUI.BeginProperty(autoRect, auto_label, _prop_automaticValue);
|
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
|
|
|
|
|
EditorGUI.showMixedValue = _prop_automaticValue.hasMultipleDifferentValues;
|
|
|
|
|
var autoValue = EditorGUI.ToggleLeft(autoRect, auto_label, _prop_automaticValue.boolValue);
|
|
|
|
|
|
|
|
|
|
if (EditorGUI.EndChangeCheck()) _prop_automaticValue.boolValue = autoValue;
|
|
|
|
|
EditorGUI.EndProperty();
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-04 07:05:18 +08:00
|
|
|
|
private List<ModularAvatarMenuItem> FindSiblingMenuItems(SerializedObject serializedObject)
|
2024-09-03 08:54:36 +08:00
|
|
|
|
{
|
2024-09-04 07:05:18 +08:00
|
|
|
|
if (serializedObject == null || serializedObject.isEditingMultipleObjects) return null;
|
2024-09-03 08:54:36 +08:00
|
|
|
|
|
2024-09-04 06:28:27 +08:00
|
|
|
|
var myMenuItem = serializedObject.targetObject as ModularAvatarMenuItem;
|
2024-09-04 07:05:18 +08:00
|
|
|
|
if (myMenuItem == null) return null;
|
2024-09-03 08:54:36 +08:00
|
|
|
|
|
2024-10-02 11:04:12 +08:00
|
|
|
|
var avatarRoot = RuntimeUtil.FindAvatarInParents(myMenuItem.gameObject.transform);
|
|
|
|
|
if (avatarRoot == null) return null;
|
|
|
|
|
|
2024-09-04 06:28:27 +08:00
|
|
|
|
var myParameterName = myMenuItem.Control.parameter.name;
|
2024-09-04 07:05:18 +08:00
|
|
|
|
if (string.IsNullOrEmpty(myParameterName)) return new List<ModularAvatarMenuItem>();
|
2024-09-03 08:54:36 +08:00
|
|
|
|
|
2024-09-18 11:25:47 +08:00
|
|
|
|
var myMappings = ParameterIntrospectionCache.GetParameterRemappingsAt(myMenuItem.gameObject);
|
2024-09-04 06:28:27 +08:00
|
|
|
|
if (myMappings.TryGetValue((ParameterNamespace.Animator, myParameterName), out var myReplacement))
|
|
|
|
|
myParameterName = myReplacement.ParameterName;
|
2024-09-03 08:54:36 +08:00
|
|
|
|
|
2024-09-04 07:05:18 +08:00
|
|
|
|
var siblings = new List<ModularAvatarMenuItem>();
|
2024-09-03 08:54:36 +08:00
|
|
|
|
|
2024-09-04 06:28:27 +08:00
|
|
|
|
foreach (var otherMenuItem in avatarRoot.GetComponentsInChildren<ModularAvatarMenuItem>(true))
|
2024-09-03 08:54:36 +08:00
|
|
|
|
{
|
2024-09-04 06:28:27 +08:00
|
|
|
|
if (otherMenuItem == myMenuItem) continue;
|
2024-09-03 08:54:36 +08:00
|
|
|
|
|
2024-09-04 06:28:27 +08:00
|
|
|
|
var otherParameterName = otherMenuItem.Control.parameter.name;
|
|
|
|
|
if (string.IsNullOrEmpty(otherParameterName)) continue;
|
2024-09-03 08:54:36 +08:00
|
|
|
|
|
2024-09-18 11:25:47 +08:00
|
|
|
|
var otherMappings = ParameterIntrospectionCache.GetParameterRemappingsAt(otherMenuItem.gameObject);
|
2024-09-04 07:05:18 +08:00
|
|
|
|
if (otherMappings.TryGetValue((ParameterNamespace.Animator, otherParameterName),
|
|
|
|
|
out var otherReplacement))
|
2024-09-04 06:28:27 +08:00
|
|
|
|
otherParameterName = otherReplacement.ParameterName;
|
|
|
|
|
|
|
|
|
|
if (otherParameterName != myParameterName) continue;
|
2024-09-04 07:05:18 +08:00
|
|
|
|
|
|
|
|
|
siblings.Add(otherMenuItem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return siblings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ClearConflictingDefaults(List<ModularAvatarMenuItem> siblingItems)
|
|
|
|
|
{
|
|
|
|
|
var siblings = siblingItems;
|
|
|
|
|
if (siblings == null) return;
|
|
|
|
|
|
|
|
|
|
foreach (var otherMenuItem in siblings)
|
|
|
|
|
{
|
2024-09-04 06:28:27 +08:00
|
|
|
|
if (otherMenuItem.isDefault)
|
2024-09-03 08:54:36 +08:00
|
|
|
|
{
|
2024-09-04 06:28:27 +08:00
|
|
|
|
Undo.RecordObject(otherMenuItem, "");
|
|
|
|
|
otherMenuItem.isDefault = false;
|
|
|
|
|
EditorUtility.SetDirty(otherMenuItem);
|
|
|
|
|
PrefabUtility.RecordPrefabInstancePropertyModifications(otherMenuItem);
|
2024-09-03 08:54:36 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
private void EnsureLabelCount(int i)
|
|
|
|
|
{
|
|
|
|
|
if (_labels == null || _labelsRoot.arraySize < i || _labels.Length < i)
|
|
|
|
|
{
|
|
|
|
|
_labelsRoot.arraySize = i;
|
|
|
|
|
_labels = new SerializedProperty[i];
|
|
|
|
|
for (int j = 0; j < i; j++)
|
|
|
|
|
{
|
|
|
|
|
_labels[j] = _labelsRoot.GetArrayElementAtIndex(j);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CenterLabel(Rect rect, GUIContent content, GUIStyle style)
|
|
|
|
|
{
|
|
|
|
|
var size = style.CalcSize(content);
|
|
|
|
|
var x = rect.x + rect.width / 2 - size.x / 2;
|
|
|
|
|
var y = rect.y + rect.height / 2 - size.y / 2;
|
|
|
|
|
GUI.Label(new Rect(x, y, size.x, size.y), content, style);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DoFourAxisLabels(bool showParams)
|
|
|
|
|
{
|
|
|
|
|
float maxWidth = 128 * 3;
|
|
|
|
|
|
|
|
|
|
EnsureLabelCount(4);
|
|
|
|
|
if (showParams) EnsureParameterCount(4);
|
|
|
|
|
|
|
|
|
|
float extraHeight = EditorGUIUtility.singleLineHeight * 3;
|
|
|
|
|
if (showParams) extraHeight += EditorGUIUtility.singleLineHeight;
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.LabelField(
|
|
|
|
|
G(showParams ? "menuitem.label.control_labels_and_params" : "menuitem.label.control_labels"),
|
|
|
|
|
EditorStyles.boldLabel);
|
|
|
|
|
|
|
|
|
|
var square = GUILayoutUtility.GetAspectRect(1, GUILayout.MaxWidth(maxWidth));
|
|
|
|
|
var extraSpace = GUILayoutUtility.GetRect(0, 0, extraHeight,
|
|
|
|
|
extraHeight, GUILayout.ExpandWidth(true));
|
|
|
|
|
|
|
|
|
|
var rect = square;
|
|
|
|
|
rect.height += extraSpace.height;
|
|
|
|
|
|
|
|
|
|
float extraWidth = Math.Max(0, extraSpace.width - rect.width);
|
|
|
|
|
rect.x += extraWidth / 2;
|
|
|
|
|
|
|
|
|
|
var blockHeight = rect.height / 3;
|
|
|
|
|
var blockWidth = rect.width / 3;
|
|
|
|
|
|
|
|
|
|
var up = rect;
|
|
|
|
|
up.yMax -= blockHeight * 2;
|
|
|
|
|
up.xMin += blockWidth;
|
|
|
|
|
up.xMax -= blockWidth;
|
|
|
|
|
|
|
|
|
|
var down = rect;
|
|
|
|
|
down.yMin += blockHeight * 2;
|
|
|
|
|
down.xMin += blockWidth;
|
|
|
|
|
down.xMax -= blockWidth;
|
|
|
|
|
|
|
|
|
|
var left = rect;
|
|
|
|
|
left.yMin += blockHeight;
|
|
|
|
|
left.yMax -= blockHeight;
|
|
|
|
|
left.xMax -= blockWidth * 2;
|
|
|
|
|
|
|
|
|
|
var right = rect;
|
|
|
|
|
right.yMin += blockHeight;
|
|
|
|
|
right.yMax -= blockHeight;
|
|
|
|
|
right.xMin += blockWidth * 2;
|
|
|
|
|
|
|
|
|
|
var center = rect;
|
|
|
|
|
center.yMin += blockHeight;
|
|
|
|
|
center.yMax -= blockHeight;
|
|
|
|
|
center.xMin += blockWidth;
|
|
|
|
|
center.xMax -= blockWidth;
|
|
|
|
|
|
2023-05-14 19:24:43 +08:00
|
|
|
|
SingleLabel(0, up);
|
|
|
|
|
SingleLabel(1, right);
|
|
|
|
|
SingleLabel(2, down);
|
|
|
|
|
SingleLabel(3, left);
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
var rect_param_l = center;
|
|
|
|
|
rect_param_l.yMin = rect_param_l.yMax - EditorGUIUtility.singleLineHeight;
|
|
|
|
|
var rect_name_l = rect_param_l;
|
|
|
|
|
if (showParams) rect_name_l.y -= rect_param_l.height;
|
|
|
|
|
|
|
|
|
|
if (showParams) CenterLabel(rect_param_l, G("menuitem.prop.parameter"), EditorStyles.label);
|
|
|
|
|
CenterLabel(rect_name_l, G("menuitem.prop.label"), EditorStyles.label);
|
|
|
|
|
|
2023-05-14 19:24:43 +08:00
|
|
|
|
void SingleLabel(int index, Rect block)
|
2023-02-25 15:45:24 +08:00
|
|
|
|
{
|
|
|
|
|
var prop_name = _labels[index].FindPropertyRelative(nameof(VRCExpressionsMenu.Control.Label.name));
|
|
|
|
|
var prop_icon = _labels[index].FindPropertyRelative(nameof(VRCExpressionsMenu.Control.Label.icon));
|
|
|
|
|
|
|
|
|
|
var rect_param = block;
|
|
|
|
|
rect_param.yMin = rect_param.yMax - EditorGUIUtility.singleLineHeight;
|
|
|
|
|
|
|
|
|
|
var rect_name = rect_param;
|
|
|
|
|
if (showParams) rect_name.y -= rect_param.height;
|
|
|
|
|
|
|
|
|
|
var rect_icon = block;
|
|
|
|
|
rect_icon.yMax = rect_name.yMin;
|
|
|
|
|
|
|
|
|
|
EditorGUI.PropertyField(rect_name, prop_name, GUIContent.none);
|
|
|
|
|
if (showParams)
|
|
|
|
|
{
|
2023-05-14 19:24:43 +08:00
|
|
|
|
_subParams[index].DoGUI(rect_param, true, GUIContent.none);
|
2023-02-25 15:45:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tex = prop_icon.objectReferenceValue as Texture;
|
|
|
|
|
GUIContent icon_content;
|
|
|
|
|
|
|
|
|
|
if (prop_icon.hasMultipleDifferentValues)
|
|
|
|
|
{
|
|
|
|
|
icon_content = G("menuitem.misc.multiple");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
icon_content = tex != null ? new GUIContent(tex) : G("menuitem.misc.no_icon");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int objectId = GUIUtility.GetControlID(
|
|
|
|
|
((int) IdGenerator.GetId(this, out bool _) << 2) | index,
|
|
|
|
|
FocusType.Passive,
|
|
|
|
|
block
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (GUI.Button(rect_icon, icon_content))
|
|
|
|
|
{
|
|
|
|
|
texPicker = index;
|
|
|
|
|
|
|
|
|
|
EditorGUIUtility.ShowObjectPicker<Texture2D>(
|
|
|
|
|
prop_icon.hasMultipleDifferentValues ? null : prop_icon.objectReferenceValue, false,
|
|
|
|
|
"t:texture2d", objectId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (texPicker == index)
|
|
|
|
|
{
|
|
|
|
|
if (Event.current.commandName == "ObjectSelectorUpdated" &&
|
|
|
|
|
EditorGUIUtility.GetObjectPickerControlID() == objectId)
|
|
|
|
|
{
|
|
|
|
|
prop_icon.objectReferenceValue = EditorGUIUtility.GetObjectPickerObject() as Texture;
|
|
|
|
|
_redraw();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EnsureParameterCount(int i)
|
|
|
|
|
{
|
|
|
|
|
if (_subParams == null || _subParamsRoot.arraySize < i || _subParams.Length < i)
|
|
|
|
|
{
|
|
|
|
|
_subParamsRoot.arraySize = i;
|
|
|
|
|
_subParams = new ParameterGUI[i];
|
|
|
|
|
for (int j = 0; j < i; j++)
|
|
|
|
|
{
|
|
|
|
|
var prop = _subParamsRoot.GetArrayElementAtIndex(j)
|
|
|
|
|
.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.Parameter.name));
|
|
|
|
|
_subParams[j] = new ParameterGUI(_parameterReference, prop, _redraw);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-10 14:37:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|