mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-03-10 07:44:57 +08:00
feat: Menu Item automatic values (#1098)
This commit is contained in:
parent
c63128095e
commit
0ee291076f
@ -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;
|
||||||
|
@ -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",
|
||||||
|
@ -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": "横パラメーター名",
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user