mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 18:55:06 +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 VisualTreeAsset uxml;
|
||||
|
||||
private DragAndDropManipulator _dragAndDropManipulator;
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
@ -37,7 +38,44 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
listView.showBoundCollectionSize = false;
|
||||
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
|
||||
|
||||
_dragAndDropManipulator = new DragAndDropManipulator(root.Q("group-box"), target as ModularAvatarMaterialSetter);
|
||||
|
||||
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 {
|
||||
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);
|
||||
|
||||
var listView = root.Q<ListView>("Shapes");
|
||||
_dragAndDropManipulator = new DragAndDropManipulator(listView)
|
||||
{
|
||||
TargetComponent = target as ModularAvatarObjectToggle
|
||||
};
|
||||
|
||||
listView.showBoundCollectionSize = false;
|
||||
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
|
||||
|
||||
_dragAndDropManipulator = new DragAndDropManipulator(root.Q("group-box"), target as ModularAvatarObjectToggle);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@ -52,91 +50,25 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
_dragAndDropManipulator.TargetComponent = target as ModularAvatarObjectToggle;
|
||||
}
|
||||
|
||||
private class DragAndDropManipulator : PointerManipulator
|
||||
private class DragAndDropManipulator : DragAndDropManipulator<ModularAvatarObjectToggle>
|
||||
{
|
||||
public ModularAvatarObjectToggle TargetComponent;
|
||||
private GameObject[] _nowDragging = Array.Empty<GameObject>();
|
||||
private Transform _avatarRoot;
|
||||
public DragAndDropManipulator(VisualElement targetElement, ModularAvatarObjectToggle targetComponent)
|
||||
: base(targetElement, targetComponent) { }
|
||||
|
||||
private readonly VisualElement _parentElem;
|
||||
protected override bool AllowKnownObjects => false;
|
||||
|
||||
public DragAndDropManipulator(VisualElement target)
|
||||
protected override void AddObjectReferences(AvatarObjectReference[] references)
|
||||
{
|
||||
this.target = target;
|
||||
_parentElem = target.parent;
|
||||
}
|
||||
Undo.RecordObject(TargetComponent, "Add Toggled Objects");
|
||||
|
||||
protected override void RegisterCallbacksOnTarget()
|
||||
{
|
||||
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)
|
||||
foreach (var reference in references)
|
||||
{
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
|
||||
|
||||
_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);
|
||||
var toggledObject = new ToggledObject { Object = reference, Active = !reference.Get(TargetComponent).activeSelf };
|
||||
TargetComponent.Objects.Add(toggledObject);
|
||||
}
|
||||
|
||||
_nowDragging = Array.Empty<GameObject>();
|
||||
_parentElem.RemoveFromClassList("drop-area--drag-active");
|
||||
EditorUtility.SetDirty(TargetComponent);
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(TargetComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,12 @@
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.drop-area--drag-active > ListView ScrollView {
|
||||
background-color: rgba(0, 255, 255, 0.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);
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
[SerializeField] private StyleSheet uss;
|
||||
[SerializeField] private VisualTreeAsset uxml;
|
||||
|
||||
private DragAndDropManipulator _dragAndDropManipulator;
|
||||
private BlendshapeSelectWindow _window;
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
@ -41,6 +42,8 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
listView.showBoundCollectionSize = false;
|
||||
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...
|
||||
var field_addButton = typeof(BaseListView).GetField("m_AddButton", NonPublic | Instance);
|
||||
var addButton = (Button)field_addButton.GetValue(listView);
|
||||
@ -50,6 +53,41 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
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()
|
||||
{
|
||||
if (_window != null) DestroyImmediate(_window);
|
||||
|
@ -68,3 +68,13 @@
|
||||
.change-type-delete #f-value-delete {
|
||||
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