ui: improve handling of saved/synced checkboxes on MenuItems with sibling items (#1095)

We will now force the state of all related MenuItems to match when the
synced/saved checkboxes are updated on the Menu Item UI.
This commit is contained in:
bd_ 2024-09-03 16:05:18 -07:00 committed by GitHub
parent e07b18d87e
commit d403f1b178
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -202,17 +202,10 @@ namespace nadena.dev.modular_avatar.core.editor
if (forceMixedValues != null) EditorGUI.showMixedValue = forceMixedValues.Value; if (forceMixedValues != null) EditorGUI.showMixedValue = forceMixedValues.Value;
if (forceValue != null) EditorGUI.BeginChangeCheck();
{ var value = EditorGUI.ToggleLeft(rect, label, forceValue ?? prop.boolValue);
EditorGUI.ToggleLeft(rect, label, forceValue.Value); if (EditorGUI.EndChangeCheck()) prop.boolValue = value;
}
else
{
EditorGUI.BeginChangeCheck();
var value = EditorGUI.ToggleLeft(rect, label, prop.boolValue);
if (EditorGUI.EndChangeCheck()) prop.boolValue = value;
}
EditorGUI.EndProperty(); EditorGUI.EndProperty();
} }
@ -438,26 +431,42 @@ namespace nadena.dev.modular_avatar.core.editor
// For now, don't show the UI in this case. // For now, don't show the UI in this case.
return; return;
var multipleSelections = _obj.targetObjects.Length > 1;
var paramName = _parameterName.stringValue; var paramName = _parameterName.stringValue;
var siblings = FindSiblingMenuItems(_obj);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
bool? forceMixedValues = _parameterName.hasMultipleDifferentValues ? true : null; var forceMixedValues = _parameterName.hasMultipleDifferentValues;
var syncedIsMixed = forceMixedValues || _prop_isSynced.hasMultipleDifferentValues ||
siblings.Any(s => s.isSynced != _prop_isSynced.boolValue);
var savedIsMixed = forceMixedValues || _prop_isSaved.hasMultipleDifferentValues ||
siblings.Any(s => s.isSaved != _prop_isSaved.boolValue);
var knownParameter = _parameterName.hasMultipleDifferentValues var knownParameter = _parameterName.hasMultipleDifferentValues
? null ? null
: _knownParameters.GetValueOrDefault(paramName); : _knownParameters.GetValueOrDefault(paramName);
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;
var knownParamDefault = knownParameter?.DefaultValue; var knownParamDefault = knownParameter?.DefaultValue;
var isDefaultByKnownParam = var isDefaultByKnownParam =
knownParamDefault != null ? _value.floatValue == knownParamDefault : (bool?)null; knownParamDefault != null ? _value.floatValue == knownParamDefault : (bool?)null;
if (knownParameter != null && knownParameter.Source is ModularAvatarMenuItem otherMenuItem) if (knownParameter != null && knownParameter.Source is ModularAvatarMenuItem)
isDefaultByKnownParam = null; isDefaultByKnownParam = null;
Object controller = knownParameter?.Source; Object controller = knownParameter?.Source;
var controllerIsElsewhere = controller != null && !(controller is ModularAvatarMenuItem);
// If we can't figure out what to reference the parameter names to, disable the UI // If we can't figure out what to reference the parameter names to, or if they're controlled by something
controllerIsElsewhere = controllerIsElsewhere || _parameterSourceNotDetermined; // other than the Menu Item component itself, disable the UI
var controllerIsElsewhere = externalSource || _parameterSourceNotDetermined;
using (new EditorGUI.DisabledScope( using (new EditorGUI.DisabledScope(
_parameterName.hasMultipleDifferentValues || controllerIsElsewhere) _parameterName.hasMultipleDifferentValues || controllerIsElsewhere)
@ -466,28 +475,42 @@ namespace nadena.dev.modular_avatar.core.editor
// If we have multiple menu items selected, it probably doesn't make sense to make them all default. // 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. // But, we do want to see if _any_ are default.
var anyIsDefault = _prop_isDefault.hasMultipleDifferentValues || _prop_isDefault.boolValue; var anyIsDefault = _prop_isDefault.hasMultipleDifferentValues || _prop_isDefault.boolValue;
var multipleSelections = _obj.targetObjects.Length > 1;
var mixedIsDefault = multipleSelections && anyIsDefault; var mixedIsDefault = multipleSelections && anyIsDefault;
using (new EditorGUI.DisabledScope(multipleSelections || isDefaultByKnownParam != null)) using (new EditorGUI.DisabledScope(multipleSelections || isDefaultByKnownParam != null))
{ {
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
DrawHorizontalToggleProp(_prop_isDefault, G("menuitem.prop.is_default"), mixedIsDefault, DrawHorizontalToggleProp(_prop_isDefault, G("menuitem.prop.is_default"), mixedIsDefault,
multipleSelections ? false : isDefaultByKnownParam); isDefaultByKnownParam);
if (EditorGUI.EndChangeCheck()) if (EditorGUI.EndChangeCheck())
{ {
_obj.ApplyModifiedProperties(); _obj.ApplyModifiedProperties();
ClearConflictingDefaults(_obj); ClearConflictingDefaults(siblings);
} }
} }
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
var isSavedMixed = forceMixedValues ?? EditorGUI.BeginChangeCheck();
(_parameterName.hasMultipleDifferentValues || controllerIsElsewhere ? true : null); DrawHorizontalToggleProp(_prop_isSaved, G("menuitem.prop.is_saved"), savedIsMixed);
DrawHorizontalToggleProp(_prop_isSaved, G("menuitem.prop.is_saved"), isSavedMixed); if (EditorGUI.EndChangeCheck() && siblings != null)
foreach (var sibling in siblings)
{
sibling.isSaved = _prop_isSaved.boolValue;
EditorUtility.SetDirty(sibling);
PrefabUtility.RecordPrefabInstancePropertyModifications(sibling);
}
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
DrawHorizontalToggleProp(_prop_isSynced, G("menuitem.prop.is_synced"), forceMixedValues, EditorGUI.BeginChangeCheck();
knownParameter?.WantSynced); 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);
}
} }
if (controllerIsElsewhere) if (controllerIsElsewhere)
@ -526,21 +549,22 @@ namespace nadena.dev.modular_avatar.core.editor
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
private void ClearConflictingDefaults(SerializedObject serializedObject) private List<ModularAvatarMenuItem> FindSiblingMenuItems(SerializedObject serializedObject)
{ {
if (serializedObject.isEditingMultipleObjects) return; if (serializedObject == null || serializedObject.isEditingMultipleObjects) return null;
var myMenuItem = serializedObject.targetObject as ModularAvatarMenuItem; var myMenuItem = serializedObject.targetObject as ModularAvatarMenuItem;
if (myMenuItem == null) return; if (myMenuItem == null) return null;
var myParameterName = myMenuItem.Control.parameter.name; var myParameterName = myMenuItem.Control.parameter.name;
if (string.IsNullOrEmpty(myParameterName)) return; if (string.IsNullOrEmpty(myParameterName)) return new List<ModularAvatarMenuItem>();
var myMappings = ParameterInfo.ForUI.GetParameterRemappingsAt(myMenuItem.gameObject); var myMappings = ParameterInfo.ForUI.GetParameterRemappingsAt(myMenuItem.gameObject);
if (myMappings.TryGetValue((ParameterNamespace.Animator, myParameterName), out var myReplacement)) if (myMappings.TryGetValue((ParameterNamespace.Animator, myParameterName), out var myReplacement))
myParameterName = myReplacement.ParameterName; myParameterName = myReplacement.ParameterName;
var avatarRoot = RuntimeUtil.FindAvatarInParents(myMenuItem.gameObject.transform); var avatarRoot = RuntimeUtil.FindAvatarInParents(myMenuItem.gameObject.transform);
var siblings = new List<ModularAvatarMenuItem>();
foreach (var otherMenuItem in avatarRoot.GetComponentsInChildren<ModularAvatarMenuItem>(true)) foreach (var otherMenuItem in avatarRoot.GetComponentsInChildren<ModularAvatarMenuItem>(true))
{ {
@ -550,10 +574,25 @@ namespace nadena.dev.modular_avatar.core.editor
if (string.IsNullOrEmpty(otherParameterName)) continue; if (string.IsNullOrEmpty(otherParameterName)) continue;
var otherMappings = ParameterInfo.ForUI.GetParameterRemappingsAt(otherMenuItem.gameObject); var otherMappings = ParameterInfo.ForUI.GetParameterRemappingsAt(otherMenuItem.gameObject);
if (otherMappings.TryGetValue((ParameterNamespace.Animator, otherParameterName), out var otherReplacement)) if (otherMappings.TryGetValue((ParameterNamespace.Animator, otherParameterName),
out var otherReplacement))
otherParameterName = otherReplacement.ParameterName; otherParameterName = otherReplacement.ParameterName;
if (otherParameterName != myParameterName) continue; if (otherParameterName != myParameterName) continue;
siblings.Add(otherMenuItem);
}
return siblings;
}
private void ClearConflictingDefaults(List<ModularAvatarMenuItem> siblingItems)
{
var siblings = siblingItems;
if (siblings == null) return;
foreach (var otherMenuItem in siblings)
{
if (otherMenuItem.isDefault) if (otherMenuItem.isDefault)
{ {
Undo.RecordObject(otherMenuItem, ""); Undo.RecordObject(otherMenuItem, "");