ui: Improvements to MA Parameters editor (#1329)

* Add import-from-asset feature (Closes: #880, #668, #410)
* Limit size of scrollable area to avoid double-scrolling
* Add support for pressing the "delete" key to delete parameters.
This commit is contained in:
bd_ 2024-11-02 15:17:58 -07:00 committed by GitHub
parent 497d16f89d
commit a3b9acba39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 108 additions and 1 deletions

View File

@ -5,8 +5,12 @@ using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEditor.UIElements; using UnityEditor.UIElements;
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
using UnityEngine.UIElements; using UnityEngine.UIElements;
using VRC.SDK3.Avatars.ScriptableObjects;
using static nadena.dev.modular_avatar.core.editor.Localization; using static nadena.dev.modular_avatar.core.editor.Localization;
using Button = UnityEngine.UIElements.Button;
using Image = UnityEngine.UIElements.Image;
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
@ -35,6 +39,37 @@ namespace nadena.dev.modular_avatar.core.editor
listView.showBoundCollectionSize = false; listView.showBoundCollectionSize = false;
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight; listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
listView.selectionType = SelectionType.Multiple;
listView.RegisterCallback<KeyDownEvent>(evt =>
{
if (evt.keyCode == KeyCode.Delete)
{
serializedObject.Update();
var prop = serializedObject.FindProperty("parameters");
var indices = listView.selectedIndices.ToList();
foreach (var index in indices.OrderByDescending(i => i))
{
prop.DeleteArrayElementAtIndex(index);
}
serializedObject.ApplyModifiedProperties();
if (indices.Count == 0)
{
EditorApplication.delayCall += () =>
{
// Works around an issue where the inner text boxes are auto-selected, preventing you from
// just hitting delete over and over
listView.SetSelectionWithoutNotify(indices);
};
}
}
evt.StopPropagation();
}, TrickleDown.NoTrickleDown);
unregisteredListView = root.Q<ListView>("UnregisteredParameters"); unregisteredListView = root.Q<ListView>("UnregisteredParameters");
@ -129,9 +164,72 @@ namespace nadena.dev.modular_avatar.core.editor
} }
}; };
var importProp = root.Q<ObjectField>("p_import");
importProp.RegisterValueChangedCallback(evt =>
{
ImportValues(importProp);
importProp.SetValueWithoutNotify(null);
});
importProp.objectType = typeof(VRCExpressionParameters);
importProp.allowSceneObjects = false;
return root; return root;
} }
private void ImportValues(ObjectField importProp)
{
var known = new HashSet<string>();
var target = (ModularAvatarParameters)this.target;
foreach (var parameter in target.parameters)
{
if (!parameter.isPrefix)
{
known.Add(parameter.nameOrPrefix);
}
}
Undo.RecordObject(target, "Import parameters");
var source = (VRCExpressionParameters)importProp.value;
if (source == null)
{
return;
}
foreach (var parameter in source.parameters)
{
if (!known.Contains(parameter.name))
{
ParameterSyncType pst;
switch (parameter.valueType)
{
case VRCExpressionParameters.ValueType.Bool: pst = ParameterSyncType.Bool; break;
case VRCExpressionParameters.ValueType.Float: pst = ParameterSyncType.Float; break;
case VRCExpressionParameters.ValueType.Int: pst = ParameterSyncType.Int; break;
default: pst = ParameterSyncType.Float; break;
}
if (!parameter.networkSynced)
{
pst = ParameterSyncType.NotSynced;
}
target.parameters.Add(new ParameterConfig()
{
internalParameter = false,
nameOrPrefix = parameter.name,
isPrefix = false,
remapTo = "",
syncType = pst,
defaultValue = parameter.defaultValue,
saved = parameter.saved,
});
}
}
}
private void DetectParameters() private void DetectParameters()
{ {
var known = new HashSet<string>(); var known = new HashSet<string>();

View File

@ -81,6 +81,12 @@ namespace nadena.dev.modular_avatar.core.editor.Parameters
updateRemapToPlaceholder(); updateRemapToPlaceholder();
foreach (var elem in root.Query<TextElement>().Build())
{
// Prevent keypresses from bubbling up
elem.RegisterCallback<KeyDownEvent>(evt => evt.StopPropagation(), TrickleDown.NoTrickleDown);
}
return root; return root;
} }

View File

@ -1,5 +1,6 @@
#ListViewContainer { #ListViewContainer {
margin-top: 4px; margin-top: 4px;
max-height: 500px;
} }
.horizontal { .horizontal {

View File

@ -12,7 +12,6 @@
show-border="true" show-border="true"
show-foldout-header="false" show-foldout-header="false"
name="Parameters" name="Parameters"
item-height="100"
binding-path="parameters" binding-path="parameters"
style="flex-grow: 1;" style="flex-grow: 1;"
/> />
@ -33,5 +32,7 @@
/> />
</ui:Foldout> </ui:Foldout>
<editor:ObjectField name="p_import" label="merge_parameter.ui.importFromAsset" class="ndmf-tr"/>
<ma:LanguageSwitcherElement/> <ma:LanguageSwitcherElement/>
</UXML> </UXML>

View File

@ -51,6 +51,7 @@
"merge_parameter.ui.add_button": "Add", "merge_parameter.ui.add_button": "Add",
"merge_parameter.ui.details": "Parameter Configuration", "merge_parameter.ui.details": "Parameter Configuration",
"merge_parameter.ui.overrideAnimatorDefaults": "Override Animator Defaults", "merge_parameter.ui.overrideAnimatorDefaults": "Override Animator Defaults",
"merge_parameter.ui.importFromAsset": "Import from asset",
"merge_armature.merge_target": "Merge Target", "merge_armature.merge_target": "Merge Target",
"merge_armature.merge_target.tooltip": "The armature (or subtree) to merge this object into", "merge_armature.merge_target.tooltip": "The armature (or subtree) to merge this object into",
"merge_armature.prefix": "Prefix", "merge_armature.prefix": "Prefix",