mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-01-17 11:50:11 +08:00
feat: add support for drag-and-drop on MaterialSetter and ShapeChanger (#1271)
* feat: add support for drag-and-drop on MaterialSetter and ShapeChanger * fix: allow adding known objects to MaterialSetter and ShapeChanger
This commit is contained in:
parent
7f9e65bcbc
commit
0b8cd3b3b6
106
Editor/Inspector/DragAndDropManipulator.cs
Normal file
106
Editor/Inspector/DragAndDropManipulator.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
internal abstract class DragAndDropManipulator<T> : PointerManipulator where T : Component, IHaveObjReferences
|
||||||
|
{
|
||||||
|
private const string DragActiveClassName = "drop-area--drag-active";
|
||||||
|
|
||||||
|
public T TargetComponent { get; set; }
|
||||||
|
|
||||||
|
protected virtual bool AllowKnownObjects => true;
|
||||||
|
|
||||||
|
private Transform _avatarRoot;
|
||||||
|
private GameObject[] _draggingObjects = Array.Empty<GameObject>();
|
||||||
|
|
||||||
|
public DragAndDropManipulator(VisualElement targetElement, T targetComponent)
|
||||||
|
{
|
||||||
|
target = targetElement;
|
||||||
|
TargetComponent = targetComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected sealed override void RegisterCallbacksOnTarget()
|
||||||
|
{
|
||||||
|
target.RegisterCallback<DragEnterEvent>(OnDragEnter);
|
||||||
|
target.RegisterCallback<DragLeaveEvent>(OnDragLeave);
|
||||||
|
target.RegisterCallback<DragExitedEvent>(OnDragExited);
|
||||||
|
target.RegisterCallback<DragUpdatedEvent>(OnDragUpdated);
|
||||||
|
target.RegisterCallback<DragPerformEvent>(OnDragPerform);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected sealed override void UnregisterCallbacksFromTarget()
|
||||||
|
{
|
||||||
|
target.UnregisterCallback<DragEnterEvent>(OnDragEnter);
|
||||||
|
target.UnregisterCallback<DragLeaveEvent>(OnDragLeave);
|
||||||
|
target.UnregisterCallback<DragExitedEvent>(OnDragExited);
|
||||||
|
target.UnregisterCallback<DragUpdatedEvent>(OnDragUpdated);
|
||||||
|
target.UnregisterCallback<DragPerformEvent>(OnDragPerform);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragEnter(DragEnterEvent _)
|
||||||
|
{
|
||||||
|
if (TargetComponent == null) return;
|
||||||
|
|
||||||
|
_avatarRoot = RuntimeUtil.FindAvatarTransformInParents(TargetComponent.transform);
|
||||||
|
if (_avatarRoot == null) return;
|
||||||
|
|
||||||
|
var knownObjects = TargetComponent.GetObjectReferences().Select(x => x.Get(TargetComponent)).ToHashSet();
|
||||||
|
_draggingObjects = DragAndDrop.objectReferences.OfType<GameObject>()
|
||||||
|
.Where(x => AllowKnownObjects || !knownObjects.Contains(x))
|
||||||
|
.Where(x => RuntimeUtil.FindAvatarTransformInParents(x.transform) == _avatarRoot)
|
||||||
|
.Where(FilterGameObject)
|
||||||
|
.ToArray();
|
||||||
|
if (_draggingObjects.Length == 0) return;
|
||||||
|
|
||||||
|
target.AddToClassList(DragActiveClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragLeave(DragLeaveEvent _)
|
||||||
|
{
|
||||||
|
_draggingObjects = Array.Empty<GameObject>();
|
||||||
|
target.RemoveFromClassList(DragActiveClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragExited(DragExitedEvent _)
|
||||||
|
{
|
||||||
|
_draggingObjects = Array.Empty<GameObject>();
|
||||||
|
target.RemoveFromClassList(DragActiveClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragUpdated(DragUpdatedEvent _)
|
||||||
|
{
|
||||||
|
if (TargetComponent == null) return;
|
||||||
|
if (_avatarRoot == null) return;
|
||||||
|
if (_draggingObjects.Length == 0) return;
|
||||||
|
|
||||||
|
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragPerform(DragPerformEvent _)
|
||||||
|
{
|
||||||
|
if (TargetComponent == null) return;
|
||||||
|
if (_avatarRoot == null) return;
|
||||||
|
if (_draggingObjects.Length == 0) return;
|
||||||
|
|
||||||
|
AddObjectReferences(_draggingObjects
|
||||||
|
.Select(x =>
|
||||||
|
{
|
||||||
|
var reference = new AvatarObjectReference();
|
||||||
|
reference.Set(x);
|
||||||
|
return reference;
|
||||||
|
})
|
||||||
|
.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool FilterGameObject(GameObject obj)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void AddObjectReferences(AvatarObjectReference[] references);
|
||||||
|
}
|
||||||
|
}
|
11
Editor/Inspector/DragAndDropManipulator.cs.meta
Normal file
11
Editor/Inspector/DragAndDropManipulator.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 528c660b56905844ea2f88bc73837e9f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -16,6 +16,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
[SerializeField] private StyleSheet uss;
|
[SerializeField] private StyleSheet uss;
|
||||||
[SerializeField] private VisualTreeAsset uxml;
|
[SerializeField] private VisualTreeAsset uxml;
|
||||||
|
|
||||||
|
private DragAndDropManipulator _dragAndDropManipulator;
|
||||||
|
|
||||||
protected override void OnInnerInspectorGUI()
|
protected override void OnInnerInspectorGUI()
|
||||||
{
|
{
|
||||||
@ -37,7 +38,44 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
listView.showBoundCollectionSize = false;
|
listView.showBoundCollectionSize = false;
|
||||||
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
|
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
|
||||||
|
|
||||||
|
_dragAndDropManipulator = new DragAndDropManipulator(root.Q("group-box"), target as ModularAvatarMaterialSetter);
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
if (_dragAndDropManipulator != null)
|
||||||
|
_dragAndDropManipulator.TargetComponent = target as ModularAvatarMaterialSetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DragAndDropManipulator : DragAndDropManipulator<ModularAvatarMaterialSetter>
|
||||||
|
{
|
||||||
|
public DragAndDropManipulator(VisualElement targetElement, ModularAvatarMaterialSetter targetComponent)
|
||||||
|
: base(targetElement, targetComponent) { }
|
||||||
|
|
||||||
|
protected override bool FilterGameObject(GameObject obj)
|
||||||
|
{
|
||||||
|
if (obj.TryGetComponent<Renderer>(out var renderer))
|
||||||
|
{
|
||||||
|
return renderer.sharedMaterials.Length > 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void AddObjectReferences(AvatarObjectReference[] references)
|
||||||
|
{
|
||||||
|
Undo.RecordObject(TargetComponent, "Add Material Switch Objects");
|
||||||
|
|
||||||
|
foreach (var reference in references)
|
||||||
|
{
|
||||||
|
var materialSwitchObject = new MaterialSwitchObject { Object = reference, MaterialIndex = 0 };
|
||||||
|
TargetComponent.Objects.Add(materialSwitchObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorUtility.SetDirty(TargetComponent);
|
||||||
|
PrefabUtility.RecordPrefabInstancePropertyModifications(TargetComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -62,3 +62,13 @@
|
|||||||
#f-material {
|
#f-material {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drop-area--drag-active {
|
||||||
|
background-color: rgba(0, 127, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-area--drag-active .unity-scroll-view,
|
||||||
|
.drop-area--drag-active .unity-list-view__footer,
|
||||||
|
.drop-area--drag-active .unity-list-view__reorderable-item {
|
||||||
|
background-color: rgba(0, 0, 0, 0.0);
|
||||||
|
}
|
||||||
|
@ -35,14 +35,12 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
ROSimulatorButton.BindRefObject(root, target);
|
ROSimulatorButton.BindRefObject(root, target);
|
||||||
|
|
||||||
var listView = root.Q<ListView>("Shapes");
|
var listView = root.Q<ListView>("Shapes");
|
||||||
_dragAndDropManipulator = new DragAndDropManipulator(listView)
|
|
||||||
{
|
|
||||||
TargetComponent = target as ModularAvatarObjectToggle
|
|
||||||
};
|
|
||||||
|
|
||||||
listView.showBoundCollectionSize = false;
|
listView.showBoundCollectionSize = false;
|
||||||
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
|
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
|
||||||
|
|
||||||
|
_dragAndDropManipulator = new DragAndDropManipulator(root.Q("group-box"), target as ModularAvatarObjectToggle);
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,91 +50,25 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
_dragAndDropManipulator.TargetComponent = target as ModularAvatarObjectToggle;
|
_dragAndDropManipulator.TargetComponent = target as ModularAvatarObjectToggle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DragAndDropManipulator : PointerManipulator
|
private class DragAndDropManipulator : DragAndDropManipulator<ModularAvatarObjectToggle>
|
||||||
{
|
{
|
||||||
public ModularAvatarObjectToggle TargetComponent;
|
public DragAndDropManipulator(VisualElement targetElement, ModularAvatarObjectToggle targetComponent)
|
||||||
private GameObject[] _nowDragging = Array.Empty<GameObject>();
|
: base(targetElement, targetComponent) { }
|
||||||
private Transform _avatarRoot;
|
|
||||||
|
|
||||||
private readonly VisualElement _parentElem;
|
protected override bool AllowKnownObjects => false;
|
||||||
|
|
||||||
public DragAndDropManipulator(VisualElement target)
|
protected override void AddObjectReferences(AvatarObjectReference[] references)
|
||||||
{
|
{
|
||||||
this.target = target;
|
Undo.RecordObject(TargetComponent, "Add Toggled Objects");
|
||||||
_parentElem = target.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void RegisterCallbacksOnTarget()
|
foreach (var reference in references)
|
||||||
{
|
|
||||||
target.RegisterCallback<DragEnterEvent>(OnDragEnter);
|
|
||||||
target.RegisterCallback<DragLeaveEvent>(OnDragLeave);
|
|
||||||
target.RegisterCallback<DragPerformEvent>(OnDragPerform);
|
|
||||||
target.RegisterCallback<DragUpdatedEvent>(OnDragUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UnregisterCallbacksFromTarget()
|
|
||||||
{
|
|
||||||
target.UnregisterCallback<DragEnterEvent>(OnDragEnter);
|
|
||||||
target.UnregisterCallback<DragLeaveEvent>(OnDragLeave);
|
|
||||||
target.UnregisterCallback<DragPerformEvent>(OnDragPerform);
|
|
||||||
target.RegisterCallback<DragUpdatedEvent>(OnDragUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void OnDragEnter(DragEnterEvent evt)
|
|
||||||
{
|
|
||||||
if (TargetComponent == null) return;
|
|
||||||
|
|
||||||
_avatarRoot = RuntimeUtil.FindAvatarTransformInParents(TargetComponent.transform);
|
|
||||||
if (_avatarRoot == null) return;
|
|
||||||
|
|
||||||
_nowDragging = DragAndDrop.objectReferences.OfType<GameObject>()
|
|
||||||
.Where(o => RuntimeUtil.FindAvatarTransformInParents(o.transform) == _avatarRoot)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (_nowDragging.Length > 0)
|
|
||||||
{
|
{
|
||||||
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
|
var toggledObject = new ToggledObject { Object = reference, Active = !reference.Get(TargetComponent).activeSelf };
|
||||||
|
TargetComponent.Objects.Add(toggledObject);
|
||||||
_parentElem.AddToClassList("drop-area--drag-active");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDragUpdate(DragUpdatedEvent _)
|
|
||||||
{
|
|
||||||
if (_nowDragging.Length > 0) DragAndDrop.visualMode = DragAndDropVisualMode.Link;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDragLeave(DragLeaveEvent evt)
|
|
||||||
{
|
|
||||||
_nowDragging = Array.Empty<GameObject>();
|
|
||||||
_parentElem.RemoveFromClassList("drop-area--drag-active");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDragPerform(DragPerformEvent evt)
|
|
||||||
{
|
|
||||||
if (_nowDragging.Length > 0 && TargetComponent != null && _avatarRoot != null)
|
|
||||||
{
|
|
||||||
var knownObjs = TargetComponent.Objects.Select(o => o.Object.Get(TargetComponent)).ToHashSet();
|
|
||||||
|
|
||||||
Undo.RecordObject(TargetComponent, "Add Toggled Objects");
|
|
||||||
foreach (var obj in _nowDragging)
|
|
||||||
{
|
|
||||||
if (knownObjs.Contains(obj)) continue;
|
|
||||||
|
|
||||||
var aor = new AvatarObjectReference();
|
|
||||||
aor.Set(obj);
|
|
||||||
|
|
||||||
var toggledObject = new ToggledObject { Object = aor, Active = !obj.activeSelf };
|
|
||||||
TargetComponent.Objects.Add(toggledObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorUtility.SetDirty(TargetComponent);
|
|
||||||
PrefabUtility.RecordPrefabInstancePropertyModifications(TargetComponent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_nowDragging = Array.Empty<GameObject>();
|
EditorUtility.SetDirty(TargetComponent);
|
||||||
_parentElem.RemoveFromClassList("drop-area--drag-active");
|
PrefabUtility.RecordPrefabInstancePropertyModifications(TargetComponent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,12 @@
|
|||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drop-area--drag-active > ListView ScrollView {
|
.drop-area--drag-active {
|
||||||
background-color: rgba(0, 255, 255, 0.1);
|
background-color: rgba(0, 127, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-area--drag-active .unity-scroll-view,
|
||||||
|
.drop-area--drag-active .unity-list-view__footer,
|
||||||
|
.drop-area--drag-active .unity-list-view__reorderable-item {
|
||||||
|
background-color: rgba(0, 0, 0, 0.0);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
[SerializeField] private StyleSheet uss;
|
[SerializeField] private StyleSheet uss;
|
||||||
[SerializeField] private VisualTreeAsset uxml;
|
[SerializeField] private VisualTreeAsset uxml;
|
||||||
|
|
||||||
|
private DragAndDropManipulator _dragAndDropManipulator;
|
||||||
private BlendshapeSelectWindow _window;
|
private BlendshapeSelectWindow _window;
|
||||||
|
|
||||||
protected override void OnInnerInspectorGUI()
|
protected override void OnInnerInspectorGUI()
|
||||||
@ -41,6 +42,8 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
listView.showBoundCollectionSize = false;
|
listView.showBoundCollectionSize = false;
|
||||||
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
|
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
|
||||||
|
|
||||||
|
_dragAndDropManipulator = new DragAndDropManipulator(root.Q("group-box"), target as ModularAvatarShapeChanger);
|
||||||
|
|
||||||
// The Add button callback isn't exposed publicly for some reason...
|
// The Add button callback isn't exposed publicly for some reason...
|
||||||
var field_addButton = typeof(BaseListView).GetField("m_AddButton", NonPublic | Instance);
|
var field_addButton = typeof(BaseListView).GetField("m_AddButton", NonPublic | Instance);
|
||||||
var addButton = (Button)field_addButton.GetValue(listView);
|
var addButton = (Button)field_addButton.GetValue(listView);
|
||||||
@ -50,6 +53,41 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
if (_dragAndDropManipulator != null)
|
||||||
|
_dragAndDropManipulator.TargetComponent = target as ModularAvatarShapeChanger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DragAndDropManipulator : DragAndDropManipulator<ModularAvatarShapeChanger>
|
||||||
|
{
|
||||||
|
public DragAndDropManipulator(VisualElement targetElement, ModularAvatarShapeChanger targetComponent)
|
||||||
|
: base(targetElement, targetComponent) { }
|
||||||
|
|
||||||
|
protected override bool FilterGameObject(GameObject obj)
|
||||||
|
{
|
||||||
|
if (obj.TryGetComponent<SkinnedMeshRenderer>(out var smr))
|
||||||
|
{
|
||||||
|
return smr.sharedMesh != null && smr.sharedMesh.blendShapeCount > 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void AddObjectReferences(AvatarObjectReference[] references)
|
||||||
|
{
|
||||||
|
Undo.RecordObject(TargetComponent, "Add Changed Shapes");
|
||||||
|
|
||||||
|
foreach (var reference in references)
|
||||||
|
{
|
||||||
|
var changedShape = new ChangedShape { Object = reference, ShapeName = string.Empty };
|
||||||
|
TargetComponent.Shapes.Add(changedShape);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorUtility.SetDirty(TargetComponent);
|
||||||
|
PrefabUtility.RecordPrefabInstancePropertyModifications(TargetComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
if (_window != null) DestroyImmediate(_window);
|
if (_window != null) DestroyImmediate(_window);
|
||||||
|
@ -68,3 +68,13 @@
|
|||||||
.change-type-delete #f-value-delete {
|
.change-type-delete #f-value-delete {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drop-area--drag-active {
|
||||||
|
background-color: rgba(0, 127, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-area--drag-active .unity-scroll-view,
|
||||||
|
.drop-area--drag-active .unity-list-view__footer,
|
||||||
|
.drop-area--drag-active .unity-list-view__reorderable-item {
|
||||||
|
background-color: rgba(0, 0, 0, 0.0);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user