feat: Menu Item automatic values (#1098)

This commit is contained in:
bd_ 2024-09-03 19:07:33 -07:00 committed by GitHub
parent c63128095e
commit 0ee291076f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 255 additions and 49 deletions

View File

@ -54,6 +54,7 @@ namespace nadena.dev.modular_avatar.core.editor
private readonly SerializedProperty _prop_isSynced; private readonly SerializedProperty _prop_isSynced;
private readonly SerializedProperty _prop_isSaved; private readonly SerializedProperty _prop_isSaved;
private readonly SerializedProperty _prop_isDefault; private readonly SerializedProperty _prop_isDefault;
private readonly SerializedProperty _prop_automaticValue;
public bool AlwaysExpandContents = false; public bool AlwaysExpandContents = false;
public bool ExpandContents = false; public bool ExpandContents = false;
@ -105,6 +106,7 @@ namespace nadena.dev.modular_avatar.core.editor
_prop_isSynced = obj.FindProperty(nameof(ModularAvatarMenuItem.isSynced)); _prop_isSynced = obj.FindProperty(nameof(ModularAvatarMenuItem.isSynced));
_prop_isSaved = obj.FindProperty(nameof(ModularAvatarMenuItem.isSaved)); _prop_isSaved = obj.FindProperty(nameof(ModularAvatarMenuItem.isSaved));
_prop_isDefault = obj.FindProperty(nameof(ModularAvatarMenuItem.isDefault)); _prop_isDefault = obj.FindProperty(nameof(ModularAvatarMenuItem.isDefault));
_prop_automaticValue = obj.FindProperty(nameof(ModularAvatarMenuItem.automaticValue));
_previewGUI = new MenuPreviewGUI(redraw); _previewGUI = new MenuPreviewGUI(redraw);
} }
@ -180,6 +182,7 @@ namespace nadena.dev.modular_avatar.core.editor
_prop_isSynced = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isSynced)); _prop_isSynced = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isSynced));
_prop_isSaved = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isSaved)); _prop_isSaved = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isSaved));
_prop_isDefault = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isDefault)); _prop_isDefault = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isDefault));
_prop_automaticValue = null;
_prop_submenuSource = null; _prop_submenuSource = null;
_prop_otherObjSource = null; _prop_otherObjSource = null;
@ -225,7 +228,7 @@ namespace nadena.dev.modular_avatar.core.editor
EditorGUILayout.PropertyField(_texture, G("menuitem.prop.icon")); EditorGUILayout.PropertyField(_texture, G("menuitem.prop.icon"));
EditorGUILayout.PropertyField(_type, G("menuitem.prop.type")); EditorGUILayout.PropertyField(_type, G("menuitem.prop.type"));
EditorGUILayout.PropertyField(_value, G("menuitem.prop.value")); DoValueField();
_parameterGUI.DoGUI(true); _parameterGUI.DoGUI(true);
@ -462,6 +465,8 @@ namespace nadena.dev.modular_avatar.core.editor
if (knownParameter != null && knownParameter.Source is ModularAvatarMenuItem) if (knownParameter != null && knownParameter.Source is ModularAvatarMenuItem)
isDefaultByKnownParam = null; isDefaultByKnownParam = null;
if (_prop_automaticValue?.boolValue == true) isDefaultByKnownParam = null;
Object controller = knownParameter?.Source; Object controller = knownParameter?.Source;
// If we can't figure out what to reference the parameter names to, or if they're controlled by something // If we can't figure out what to reference the parameter names to, or if they're controlled by something
@ -549,6 +554,55 @@ namespace nadena.dev.modular_avatar.core.editor
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
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();
}
private List<ModularAvatarMenuItem> FindSiblingMenuItems(SerializedObject serializedObject) private List<ModularAvatarMenuItem> FindSiblingMenuItems(SerializedObject serializedObject)
{ {
if (serializedObject == null || serializedObject.isEditingMultipleObjects) return null; if (serializedObject == null || serializedObject.isEditingMultipleObjects) return null;

View File

@ -187,6 +187,8 @@
"menuitem.prop.type.tooltip": "The type of this item", "menuitem.prop.type.tooltip": "The type of this item",
"menuitem.prop.value": "Value", "menuitem.prop.value": "Value",
"menuitem.prop.value.tooltip": "The value to set the parameter to when this control is used", "menuitem.prop.value.tooltip": "The value to set the parameter to when this control is used",
"menuitem.prop.automatic_value": "Auto",
"menuitem.prop.automatic_value.tooltip": "Automatically set this control to a unique value",
"menuitem.prop.parameter": "Parameter", "menuitem.prop.parameter": "Parameter",
"menuitem.prop.label": "Label", "menuitem.prop.label": "Label",
"menuitem.prop.submenu_asset": "Submenu Asset", "menuitem.prop.submenu_asset": "Submenu Asset",

View File

@ -197,6 +197,8 @@
"menuitem.prop.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動するときこの設定が保持されます。", "menuitem.prop.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動するときこの設定が保持されます。",
"menuitem.prop.is_synced": "同期する", "menuitem.prop.is_synced": "同期する",
"menuitem.prop.is_synced.tooltip": "有効の場合はほかのプレイヤーに同期されます。", "menuitem.prop.is_synced.tooltip": "有効の場合はほかのプレイヤーに同期されます。",
"menuitem.prop.automatic_value": "自動",
"menuitem.prop.automatic_value.tooltip": "かぶらない値を自動的に割り振る",
"menuitem.param.rotation": "回転パラメーター名", "menuitem.param.rotation": "回転パラメーター名",
"menuitem.param.rotation.tooltip": "このメニューアイテムの回転に連動するべきパラメーター", "menuitem.param.rotation.tooltip": "このメニューアイテムの回転に連動するべきパラメーター",
"menuitem.param.horizontal": "横パラメーター名", "menuitem.param.horizontal": "横パラメーター名",

View File

@ -63,6 +63,17 @@ namespace nadena.dev.modular_avatar.core.editor
_computeContext.Observe(mami, c => (c.Control?.parameter, c.Control?.type, c.Control?.value, c.isDefault)); _computeContext.Observe(mami, c => (c.Control?.parameter, c.Control?.type, c.Control?.value, c.isDefault));
var mami_condition = ParameterAssignerPass.AssignMenuItemParameter(mami, _simulationInitialStates); var mami_condition = ParameterAssignerPass.AssignMenuItemParameter(mami, _simulationInitialStates);
if (mami_condition != null &&
ForceMenuItems.TryGetValue(mami_condition.Parameter, out var forcedMenuItem))
{
var enable = forcedMenuItem == mami;
mami_condition.InitialValue = 0.5f;
mami_condition.ParameterValueLo = enable ? 0 : 999f;
mami_condition.ParameterValueHi = 1000;
mami_condition.IsConstant = true;
}
if (mami_condition != null) conditions.Add(mami_condition); if (mami_condition != null) conditions.Add(mami_condition);
} }

View File

@ -21,6 +21,9 @@ namespace nadena.dev.modular_avatar.core.editor
public ImmutableDictionary<string, float> ForcePropertyOverrides { get; set; } = ImmutableDictionary<string, float>.Empty; public ImmutableDictionary<string, float> ForcePropertyOverrides { get; set; } = ImmutableDictionary<string, float>.Empty;
public ImmutableDictionary<string, ModularAvatarMenuItem> ForceMenuItems { get; set; } =
ImmutableDictionary<string, ModularAvatarMenuItem>.Empty;
public ReactiveObjectAnalyzer(ndmf.BuildContext context) public ReactiveObjectAnalyzer(ndmf.BuildContext context)
{ {
_computeContext = ComputeContext.NullContext; _computeContext = ComputeContext.NullContext;
@ -47,7 +50,8 @@ namespace nadena.dev.modular_avatar.core.editor
var mami = obj?.GetComponent<ModularAvatarMenuItem>(); var mami = obj?.GetComponent<ModularAvatarMenuItem>();
if (mami == null) return null; if (mami == null) return null;
return ParameterAssignerPass.AssignMenuItemParameter(mami, _simulationInitialStates)?.Parameter; return ParameterAssignerPass.AssignMenuItemParameter(mami, _simulationInitialStates, ForceMenuItems)
?.Parameter;
} }
public struct AnalysisResult public struct AnalysisResult
@ -68,6 +72,8 @@ namespace nadena.dev.modular_avatar.core.editor
var analysis = new ReactiveObjectAnalyzer(ctx); var analysis = new ReactiveObjectAnalyzer(ctx);
analysis.ForcePropertyOverrides = ctx.Observe(ROSimulator.PropertyOverrides, a=>a, (a,b) => false) analysis.ForcePropertyOverrides = ctx.Observe(ROSimulator.PropertyOverrides, a=>a, (a,b) => false)
?? ImmutableDictionary<string, float>.Empty; ?? ImmutableDictionary<string, float>.Empty;
analysis.ForceMenuItems = ctx.Observe(ROSimulator.MenuItemOverrides, a => a, (a, b) => false)
?? ImmutableDictionary<string, ModularAvatarMenuItem>.Empty;
return analysis.Analyze(root); return analysis.Analyze(root);
}); });
} }

View File

@ -75,7 +75,7 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
if (condition.IsConstant) continue; if (condition.IsConstant) continue;
if (!initialValues.ContainsKey(condition.Parameter)) if (!initialValues.ContainsKey(condition.Parameter) && condition.InitialValue > -999f)
initialValues[condition.Parameter] = condition.InitialValue; initialValues[condition.Parameter] = condition.InitialValue;
} }
} }

View File

@ -47,11 +47,14 @@ namespace nadena.dev.modular_avatar.core.editor
var paramIndex = 0; var paramIndex = 0;
var declaredParams = context.AvatarDescriptor.expressionParameters.parameters.Select(p => p.name) var declaredParams = context.AvatarDescriptor.expressionParameters.parameters
.ToHashSet(); .GroupBy(p => p.name).Select(l => l.First())
.ToDictionary(p => p.name);
Dictionary<string, VRCExpressionParameters.Parameter> newParameters = new(); Dictionary<string, VRCExpressionParameters.Parameter> newParameters = new();
Dictionary<string, int> nextParamValue = new();
Dictionary<string, List<ModularAvatarMenuItem>> _mamiByParam = new();
foreach (var mami in context.AvatarRootTransform.GetComponentsInChildren<ModularAvatarMenuItem>(true)) foreach (var mami in context.AvatarRootTransform.GetComponentsInChildren<ModularAvatarMenuItem>(true))
{ {
if (string.IsNullOrWhiteSpace(mami.Control?.parameter?.name)) if (string.IsNullOrWhiteSpace(mami.Control?.parameter?.name))
@ -67,48 +70,94 @@ namespace nadena.dev.modular_avatar.core.editor
var paramName = mami.Control.parameter.name; var paramName = mami.Control.parameter.name;
if (!declaredParams.Contains(paramName)) if (!_mamiByParam.TryGetValue(paramName, out var mamiList))
{ {
newParameters.TryGetValue(paramName, out var existingNewParam); mamiList = new List<ModularAvatarMenuItem>();
var wantedType = existingNewParam?.valueType ?? VRCExpressionParameters.ValueType.Bool; _mamiByParam[paramName] = mamiList;
}
if (wantedType != VRCExpressionParameters.ValueType.Float && mamiList.Add(mami);
(mami.Control.value > 1.01 || mami.Control.value < -0.01)) }
wantedType = VRCExpressionParameters.ValueType.Int;
if (Mathf.Abs(Mathf.Round(mami.Control.value) - mami.Control.value) > 0.01f) foreach (var (paramName, list) in _mamiByParam)
wantedType = VRCExpressionParameters.ValueType.Float;
if (existingNewParam == null)
{ {
existingNewParam = new VRCExpressionParameters.Parameter // Assign automatic values first
float defaultValue;
if (declaredParams.TryGetValue(paramName, out var p))
{ {
name = paramName, defaultValue = p.defaultValue;
valueType = wantedType,
saved = mami.isSaved,
defaultValue = -1,
networkSynced = mami.isSynced
};
newParameters[paramName] = existingNewParam;
} }
else else
{ {
existingNewParam.valueType = wantedType; defaultValue = list.FirstOrDefault(m => m.isDefault && !m.automaticValue)?.Control?.value ?? 0;
if (list.Count == 1)
// If we have only a single entry, it's probably an on-off toggle, so we'll implicitly let 0
// be the 'unselected' default value
defaultValue = 1;
} }
// TODO: warn on inconsistent configuration HashSet<int> usedValues = new();
existingNewParam.saved = existingNewParam.saved || mami.isSaved; usedValues.Add((int)defaultValue);
existingNewParam.networkSynced = existingNewParam.networkSynced || mami.isSynced;
existingNewParam.defaultValue = mami.isDefault ? mami.Control.value : existingNewParam.defaultValue; foreach (var item in list)
if (!item.automaticValue && Mathf.Abs(item.Control.value - Mathf.Round(item.Control.value)) < 0.01f)
usedValues.Add(Mathf.RoundToInt(item.Control.value));
var nextValue = 1;
var canBeBool = true;
var canBeInt = true;
var isSaved = true;
var isSynced = true;
foreach (var mami in list)
{
if (mami.automaticValue)
{
if (mami.isDefault)
{
mami.Control.value = defaultValue;
}
else
{
while (usedValues.Contains(nextValue)) nextValue++;
mami.Control.value = nextValue;
usedValues.Add(nextValue);
}
}
if (Mathf.Abs(mami.Control.value - Mathf.Round(mami.Control.value)) > 0.01f)
canBeInt = false;
else
canBeBool &= mami.Control.value is >= 0 and <= 1;
isSaved &= mami.isSaved;
isSynced &= mami.isSynced;
}
if (!declaredParams.ContainsKey(paramName))
{
VRCExpressionParameters.ValueType newType;
if (canBeBool) newType = VRCExpressionParameters.ValueType.Bool;
else if (canBeInt) newType = VRCExpressionParameters.ValueType.Int;
else newType = VRCExpressionParameters.ValueType.Float;
var newParam = new VRCExpressionParameters.Parameter
{
name = paramName,
valueType = newType,
saved = isSaved,
defaultValue = defaultValue,
networkSynced = isSynced
};
newParameters[paramName] = newParam;
} }
} }
if (newParameters.Count > 0) if (newParameters.Count > 0)
{ {
foreach (var p in newParameters)
if (p.Value.defaultValue < 0)
p.Value.defaultValue = 0;
var expParams = context.AvatarDescriptor.expressionParameters; var expParams = context.AvatarDescriptor.expressionParameters;
if (!context.IsTemporaryAsset(expParams)) if (!context.IsTemporaryAsset(expParams))
{ {
@ -120,15 +169,22 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
internal static ControlCondition AssignMenuItemParameter(ModularAvatarMenuItem mami, Dictionary<string, float> simulationInitialStates = null) internal static ControlCondition AssignMenuItemParameter(
ModularAvatarMenuItem mami,
Dictionary<string, float> simulationInitialStates = null,
IDictionary<string, ModularAvatarMenuItem> isDefaultOverrides = null)
{ {
var paramName = mami?.Control?.parameter?.name; var paramName = mami?.Control?.parameter?.name;
if (mami?.Control != null && simulationInitialStates != null && ShouldAssignParametersToMami(mami)) if (mami?.Control != null && simulationInitialStates != null && ShouldAssignParametersToMami(mami))
{ {
paramName = "___AutoProp/" + mami.Control?.parameter?.name; paramName = mami.Control?.parameter?.name;
if (paramName == "___AutoProp/") paramName += mami.GetInstanceID(); if (string.IsNullOrEmpty(paramName)) paramName = "___AutoProp/" + mami.GetInstanceID();
if (mami.isDefault) var isDefault = mami.isDefault;
if (isDefaultOverrides?.TryGetValue(paramName, out var target) == true)
isDefault = ReferenceEquals(mami, target);
if (isDefault)
{ {
simulationInitialStates[paramName] = mami.Control.value; simulationInitialStates[paramName] = mami.Control.value;
} else if (!simulationInitialStates.ContainsKey(paramName)) } else if (!simulationInitialStates.ContainsKey(paramName))
@ -144,7 +200,10 @@ namespace nadena.dev.modular_avatar.core.editor
Parameter = paramName, Parameter = paramName,
DebugName = mami.gameObject.name, DebugName = mami.gameObject.name,
IsConstant = false, IsConstant = false,
InitialValue = mami.isDefault ? mami.Control.value : -999, // TODO // Note: This slightly odd-looking value is key to making the Auto checkbox work for editor previews;
// we basically force-disable any conditions for nonselected menu items and force-enable any for default
// menu items.
InitialValue = mami.isDefault ? mami.Control.value : -999,
ParameterValueLo = mami.Control.value - 0.5f, ParameterValueLo = mami.Control.value - 0.5f,
ParameterValueHi = mami.Control.value + 0.5f, ParameterValueHi = mami.Control.value + 0.5f,
DebugReference = mami, DebugReference = mami,

View File

@ -15,6 +15,7 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
internal class ROSimulator : EditorWindow, IHasCustomMenu internal class ROSimulator : EditorWindow, IHasCustomMenu
{ {
public static PublishedValue<ImmutableDictionary<string, float>> PropertyOverrides = new(null); public static PublishedValue<ImmutableDictionary<string, float>> PropertyOverrides = new(null);
public static PublishedValue<ImmutableDictionary<string, ModularAvatarMenuItem>> MenuItemOverrides = new(null);
internal static string ROOT_PATH = "Packages/nadena.dev.modular-avatar/Editor/ReactiveObjects/Simulator/"; internal static string ROOT_PATH = "Packages/nadena.dev.modular-avatar/Editor/ReactiveObjects/Simulator/";
private static string USS = ROOT_PATH + "ROSimulator.uss"; private static string USS = ROOT_PATH + "ROSimulator.uss";
@ -64,6 +65,7 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
private void OnEnable() private void OnEnable()
{ {
PropertyOverrides.Value = ImmutableDictionary<string, float>.Empty; PropertyOverrides.Value = ImmutableDictionary<string, float>.Empty;
MenuItemOverrides.Value = ImmutableDictionary<string, ModularAvatarMenuItem>.Empty;
EditorApplication.delayCall += LoadUI; EditorApplication.delayCall += LoadUI;
Selection.selectionChanged += SelectionChanged; Selection.selectionChanged += SelectionChanged;
is_enabled = true; is_enabled = true;
@ -79,6 +81,7 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
EditorApplication.delayCall += () => EditorApplication.delayCall += () =>
{ {
PropertyOverrides.Value = null; PropertyOverrides.Value = null;
MenuItemOverrides.Value = null;
}; };
} }
@ -107,6 +110,25 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
EditorApplication.delayCall += RefreshUI; EditorApplication.delayCall += RefreshUI;
} }
private void UpdateMenuItemOverride(string prop, ModularAvatarMenuItem item, bool? value)
{
if (value == null)
{
MenuItemOverrides.Value = MenuItemOverrides.Value.Remove(prop);
}
else if (value.Value)
{
MenuItemOverrides.Value = MenuItemOverrides.Value.SetItem(prop, item);
}
else
{
if (MenuItemOverrides.Value.TryGetValue(prop, out var existing) && ReferenceEquals(existing, item))
MenuItemOverrides.Value = MenuItemOverrides.Value.SetItem(prop, null);
}
EditorApplication.delayCall += RefreshUI;
}
private void UpdatePropertyOverride(string prop, bool? value) private void UpdatePropertyOverride(string prop, bool? value)
{ {
if (value == null) if (value == null)
@ -209,6 +231,7 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
var analysis = new ReactiveObjectAnalyzer(_lastComputeContext); var analysis = new ReactiveObjectAnalyzer(_lastComputeContext);
analysis.ForcePropertyOverrides = PropertyOverrides.Value; analysis.ForcePropertyOverrides = PropertyOverrides.Value;
analysis.ForceMenuItems = MenuItemOverrides.Value;
var result = analysis.Analyze(avatar.gameObject); var result = analysis.Analyze(avatar.gameObject);
SetThisObjectOverrides(analysis); SetThisObjectOverrides(analysis);
@ -227,9 +250,36 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
private void SetThisObjectOverrides(ReactiveObjectAnalyzer analysis) private void SetThisObjectOverrides(ReactiveObjectAnalyzer analysis)
{ {
BindOverrideToParameter("this-obj-override", analysis.GetGameObjectStateProperty(currentSelection), 1); BindOverrideToParameter("this-obj-override", analysis.GetGameObjectStateProperty(currentSelection), 1);
BindOverrideToParameter("this-menu-override", analysis.GetMenuItemProperty(currentSelection), currentSelection.TryGetComponent<ModularAvatarMenuItem>(out var mami);
currentSelection.GetComponent<ModularAvatarMenuItem>()?.Control?.value ?? 1 BindOverrideToMenuItem("this-menu-override", mami);
); }
private void BindOverrideToMenuItem(string overrideElemName, ModularAvatarMenuItem mami)
{
var elem = e_debugInfo.Q<VisualElement>(overrideElemName);
var soc = elem.Q<StateOverrideController>();
if (mami == null)
{
elem.style.display = DisplayStyle.None;
return;
}
var prop = ParameterAssignerPass.AssignMenuItemParameter(mami)?.Parameter;
if (prop == null)
{
elem.style.display = DisplayStyle.None;
return;
}
elem.style.display = DisplayStyle.Flex;
if (MenuItemOverrides.Value.TryGetValue(prop, out var overrideValue))
soc.SetWithoutNotify(ReferenceEquals(mami, overrideValue));
else
soc.SetWithoutNotify(null);
soc.OnStateOverrideChanged += value => { UpdateMenuItemOverride(prop, mami, value); };
} }
private void BindOverrideToParameter(string overrideElemName, string property, float targetValue) private void BindOverrideToParameter(string overrideElemName, string property, float targetValue)
@ -461,7 +511,22 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
targetValue = Mathf.Round((condition.ParameterValueLo + condition.ParameterValueHi) / 2); targetValue = Mathf.Round((condition.ParameterValueLo + condition.ParameterValueHi) / 2);
} }
if (condition.DebugReference is ModularAvatarMenuItem mami)
{
bool? menuOverride = null;
if (MenuItemOverrides.Value.TryGetValue(prop, out var target))
{
menuOverride = ReferenceEquals(mami, target);
soc.SetWithoutNotify(menuOverride);
}
soc.OnStateOverrideChanged += value => { UpdateMenuItemOverride(prop, mami, value); };
}
else
{
soc.OnStateOverrideChanged += value => UpdatePropertyOverride(prop, value, targetValue); soc.OnStateOverrideChanged += value => UpdatePropertyOverride(prop, value, targetValue);
}
var active = condition.InitiallyActive; var active = condition.InitiallyActive;
var active_label = active ? "active" : "inactive"; var active_label = active ? "active" : "inactive";

View File

@ -1,6 +1,5 @@
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
using System;
using System.Linq; using System.Linq;
using nadena.dev.modular_avatar.core.menu; using nadena.dev.modular_avatar.core.menu;
using UnityEngine; using UnityEngine;
@ -43,6 +42,13 @@ namespace nadena.dev.modular_avatar.core
/// </summary> /// </summary>
public bool isDefault; public bool isDefault;
/// <summary>
/// If true, the value for this toggle or button menu item will be automatically selected.
/// Typically, this will be zero for the default menu item, then subsequent menu items will be allocated
/// sequentially in hierarchy order.
/// </summary>
public bool automaticValue;
private void Reset() private void Reset()
{ {
Control = new VRCExpressionsMenu.Control(); Control = new VRCExpressionsMenu.Control();
@ -51,6 +57,7 @@ namespace nadena.dev.modular_avatar.core
isSaved = true; isSaved = true;
isSynced = true; isSynced = true;
isDefault = false; isDefault = false;
automaticValue = true;
MenuSource = SubmenuSource.Children; MenuSource = SubmenuSource.Children;
} }