You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1019 lines
39 KiB
1019 lines
39 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
using UnityEditor.Experimental.GraphView;
|
|
using UnityEditor.VFX.Block;
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.VFX;
|
|
using UnityEngine.UIElements;
|
|
using UnityEngine.Profiling;
|
|
|
|
using PositionType = UnityEngine.UIElements.Position;
|
|
|
|
namespace UnityEditor.VFX.UI
|
|
{
|
|
class VFXContextUI : VFXNodeUI
|
|
{
|
|
// TODO: Unused except for debugging
|
|
readonly CustomStyleProperty<Color> RectColorProperty = new CustomStyleProperty<Color>("--rect-color");
|
|
|
|
Image m_HeaderIcon;
|
|
Label m_HeaderSpace;
|
|
Label m_Subtitle;
|
|
Label m_Footer;
|
|
|
|
VisualElement m_FlowInputConnectorContainer;
|
|
VisualElement m_FlowOutputConnectorContainer;
|
|
VisualElement m_BlockContainer;
|
|
VisualElement m_NoBlock;
|
|
|
|
VisualElement m_DragDisplay;
|
|
|
|
Label m_Label;
|
|
TextField m_TextField;
|
|
|
|
public new VFXContextController controller
|
|
{
|
|
get { return base.controller as VFXContextController; }
|
|
}
|
|
protected override void OnNewController()
|
|
{
|
|
m_CanHaveBlocks = controller.model.CanHaveBlocks();
|
|
}
|
|
|
|
public bool canHaveBlocks { get => m_CanHaveBlocks; }
|
|
|
|
public static string ContextEnumToClassName(string name)
|
|
{
|
|
if (name[0] == 'k')
|
|
{
|
|
Debug.LogError("Fix this since k should have been removed from enums");
|
|
}
|
|
|
|
return name.ToLower();
|
|
}
|
|
|
|
public void UpdateLabel()
|
|
{
|
|
var graph = controller.model.GetGraph();
|
|
if (graph != null && controller.model.contextType == VFXContextType.Spawner)
|
|
m_Label.text = graph.systemNames.GetUniqueSystemName(controller.model.GetData());
|
|
else
|
|
m_Label.text = controller.model.label;
|
|
}
|
|
|
|
protected override void SelfChange()
|
|
{
|
|
base.SelfChange();
|
|
|
|
Profiler.BeginSample("VFXContextUI.CreateBlockProvider");
|
|
if (m_BlockProvider == null)
|
|
{
|
|
m_BlockProvider = new VFXBlockProvider(controller, (variant, mPos) =>
|
|
{
|
|
if (variant.modelType != typeof(VisualEffectSubgraphBlock))
|
|
{
|
|
UpdateSelectionWithNewBlocks();
|
|
AddBlock(mPos, variant);
|
|
}
|
|
else
|
|
{
|
|
var path = variant.settings.Single(x => x.Key == "path").Value as string;
|
|
var subgraphBlock = AssetDatabase.LoadAssetAtPath<VisualEffectSubgraphBlock>(path);
|
|
var view = GetFirstAncestorOfType<VFXView>();
|
|
var graph = subgraphBlock.GetResource().GetOrCreateGraph();
|
|
if (view.HasCustomAttributeConflicts(graph.attributesManager.GetCustomAttributes()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Prevent cyclic recursion
|
|
if (controller.model.GetGraph() == graph)
|
|
{
|
|
Debug.LogWarning("Cannot add this subgraph because it would create a cyclic recursion");
|
|
return;
|
|
}
|
|
|
|
int blockIndex = GetDragBlockIndex(mPos);
|
|
VFXBlock newModel = ScriptableObject.CreateInstance<VFXSubgraphBlock>();
|
|
|
|
newModel.SetSettingValue("m_Subgraph", subgraphBlock);
|
|
UpdateSelectionWithNewBlocks();
|
|
using var growContext = new GrowContext(this);
|
|
controller.AddBlock(blockIndex, newModel, true);
|
|
}
|
|
});
|
|
}
|
|
Profiler.EndSample();
|
|
|
|
if (inputContainer.childCount == 0 && !hasSettings)
|
|
{
|
|
mainContainer.AddToClassList("empty");
|
|
}
|
|
else
|
|
{
|
|
mainContainer.RemoveFromClassList("empty");
|
|
}
|
|
|
|
m_HeaderIcon.image = GetIconForVFXType(controller.model.inputType);
|
|
m_HeaderIcon.SendToBack(); // Actually move it as first child so it's before the title label
|
|
if (m_HeaderIcon.image == null)
|
|
m_HeaderIcon.AddToClassList("Empty");
|
|
|
|
var subTitle = controller.subtitle;
|
|
m_Subtitle.text = controller.subtitle;
|
|
if (string.IsNullOrEmpty(subTitle))
|
|
{
|
|
m_Subtitle.AddToClassList("empty");
|
|
}
|
|
else
|
|
{
|
|
m_Subtitle.RemoveFromClassList("empty");
|
|
}
|
|
|
|
Profiler.BeginSample("VFXContextUI.SetAllStyleClasses");
|
|
|
|
VFXContextType contextType = controller.model.contextType;
|
|
foreach (VFXContextType value in System.Enum.GetValues(typeof(VFXContextType)))
|
|
{
|
|
if (value != contextType)
|
|
RemoveFromClassList(ContextEnumToClassName(value.ToString()));
|
|
}
|
|
AddToClassList(ContextEnumToClassName(contextType.ToString()));
|
|
|
|
var inputType = controller.model.inputType;
|
|
if (inputType == VFXDataType.None)
|
|
{
|
|
inputType = controller.model.ownedType;
|
|
}
|
|
foreach (VFXDataType value in System.Enum.GetValues(typeof(VFXDataType)))
|
|
{
|
|
if (inputType != value)
|
|
RemoveFromClassList("inputType" + ContextEnumToClassName(value.ToString()));
|
|
}
|
|
AddToClassList("inputType" + ContextEnumToClassName(inputType.ToString()));
|
|
|
|
var outputType = controller.model.outputType;
|
|
foreach (VFXDataType value in System.Enum.GetValues(typeof(VFXDataType)))
|
|
{
|
|
if (value != outputType)
|
|
RemoveFromClassList("outputType" + ContextEnumToClassName(value.ToString()));
|
|
}
|
|
AddToClassList("outputType" + ContextEnumToClassName(outputType.ToString()));
|
|
|
|
var type = controller.model.ownedType;
|
|
foreach (VFXDataType value in System.Enum.GetValues(typeof(VFXDataType)))
|
|
{
|
|
if (value != type)
|
|
RemoveFromClassList("type" + ContextEnumToClassName(value.ToString()));
|
|
}
|
|
AddToClassList("type" + ContextEnumToClassName(type.ToString()));
|
|
|
|
var space = controller.model.space;
|
|
m_HeaderSpace.text = space.ToString();
|
|
m_HeaderSpace.tooltip = $"{space.ToString()} Space";
|
|
m_HeaderSpace.RemoveFromClassList(VFXSpace.World.ToString());
|
|
m_HeaderSpace.RemoveFromClassList(VFXSpace.Local.ToString());
|
|
m_HeaderSpace.AddToClassList(space.ToString());
|
|
|
|
Profiler.EndSample();
|
|
if (controller.model.outputType == VFXDataType.None)
|
|
{
|
|
if (m_Footer.parent != null)
|
|
m_Footer.RemoveFromHierarchy();
|
|
}
|
|
else
|
|
{
|
|
if (m_Footer.parent == null)
|
|
mainContainer.Add(m_Footer);
|
|
|
|
if (controller.model.outputFlowSlot.Any())
|
|
{
|
|
m_Footer.text = controller.model.outputType.ToString();
|
|
m_Footer.style.backgroundImage = GetIconForVFXType(controller.model.outputType);
|
|
}
|
|
else
|
|
{
|
|
m_Footer.text = string.Empty;
|
|
m_Footer.style.backgroundImage = null;
|
|
}
|
|
}
|
|
|
|
Profiler.BeginSample("VFXContextUI.CreateInputFlow");
|
|
HashSet<VisualElement> newInAnchors = new HashSet<VisualElement>();
|
|
foreach (var inanchorcontroller in controller.flowInputAnchors.Take(VFXContext.kMaxFlowCount))
|
|
{
|
|
var existing = m_FlowInputConnectorContainer.Children().Select(t => t as VFXFlowAnchor).FirstOrDefault(t => t.controller == inanchorcontroller);
|
|
if (existing == null)
|
|
{
|
|
var anchor = VFXFlowAnchor.Create(inanchorcontroller);
|
|
m_FlowInputConnectorContainer.Add(anchor);
|
|
newInAnchors.Add(anchor);
|
|
}
|
|
else
|
|
{
|
|
newInAnchors.Add(existing);
|
|
}
|
|
}
|
|
|
|
foreach (var nonLongerExistingAnchor in m_FlowInputConnectorContainer.Children().Where(t => !newInAnchors.Contains(t)).ToList()) // ToList to make a copy because the enumerable will change when we delete
|
|
{
|
|
m_FlowInputConnectorContainer.Remove(nonLongerExistingAnchor);
|
|
}
|
|
Profiler.EndSample();
|
|
|
|
Profiler.BeginSample("VFXContextUI.CreateInputFlow");
|
|
HashSet<VisualElement> newOutAnchors = new HashSet<VisualElement>();
|
|
|
|
foreach (var outanchorcontroller in controller.flowOutputAnchors.Take(VFXContext.kMaxFlowCount))
|
|
{
|
|
var existing = m_FlowOutputConnectorContainer.Children().Select(t => t as VFXFlowAnchor).FirstOrDefault(t => t.controller == outanchorcontroller);
|
|
if (existing == null)
|
|
{
|
|
var anchor = VFXFlowAnchor.Create(outanchorcontroller);
|
|
m_FlowOutputConnectorContainer.Add(anchor);
|
|
newOutAnchors.Add(anchor);
|
|
}
|
|
else
|
|
{
|
|
newOutAnchors.Add(existing);
|
|
}
|
|
}
|
|
|
|
foreach (var nonLongerExistingAnchor in m_FlowOutputConnectorContainer.Children().Where(t => !newOutAnchors.Contains(t)).ToList()) // ToList to make a copy because the enumerable will change when we delete
|
|
{
|
|
m_FlowOutputConnectorContainer.Remove(nonLongerExistingAnchor);
|
|
}
|
|
Profiler.EndSample();
|
|
|
|
UpdateLabel();
|
|
|
|
if (string.IsNullOrEmpty(m_Label.text))
|
|
{
|
|
m_Label.AddToClassList("empty");
|
|
}
|
|
else
|
|
{
|
|
m_Label.RemoveFromClassList("empty");
|
|
}
|
|
|
|
foreach (var inEdge in m_FlowInputConnectorContainer.Children().OfType<VFXFlowAnchor>().SelectMany(t => t.connections))
|
|
inEdge.UpdateEdgeControl();
|
|
foreach (var outEdge in m_FlowOutputConnectorContainer.Children().OfType<VFXFlowAnchor>().SelectMany(t => t.connections))
|
|
outEdge.UpdateEdgeControl();
|
|
|
|
RefreshContext();
|
|
}
|
|
|
|
public VFXContextUI() : base("uxml/VFXContext")
|
|
{
|
|
capabilities |= Capabilities.Selectable | Capabilities.Movable | Capabilities.Deletable | Capabilities.Ascendable;
|
|
|
|
styleSheets.Add(VFXView.LoadStyleSheet("VFXContext"));
|
|
styleSheets.Add(VFXView.LoadStyleSheet("Selectable"));
|
|
|
|
AddToClassList("VFXContext");
|
|
AddToClassList("selectable");
|
|
|
|
this.mainContainer.style.overflow = Overflow.Visible;
|
|
|
|
m_FlowInputConnectorContainer = this.Q("flow-inputs");
|
|
|
|
m_FlowOutputConnectorContainer = this.Q("flow-outputs");
|
|
|
|
m_HeaderIcon = titleContainer.Q<Image>("icon");
|
|
m_HeaderSpace = titleContainer.Q<Label>("header-space");
|
|
m_HeaderSpace.AddManipulator(new Clickable(OnSpace));
|
|
m_Subtitle = this.Q<Label>("subtitle");
|
|
|
|
m_BlockContainer = this.Q("block-container");
|
|
m_NoBlock = m_BlockContainer.Q("no-blocks");
|
|
|
|
m_Footer = this.Q<Label>("footer");
|
|
|
|
m_DragDisplay = new VisualElement();
|
|
m_DragDisplay.AddToClassList("dragdisplay");
|
|
|
|
m_Label = this.Q<Label>("user-label");
|
|
m_TextField = this.Q<TextField>("user-title-textfield");
|
|
m_TextField.maxLength = 175;
|
|
m_TextField.style.display = DisplayStyle.None;
|
|
|
|
m_Label.RegisterCallback<MouseDownEvent>(OnTitleMouseDown);
|
|
m_TextField.RegisterCallback<ChangeEvent<string>>(OnTitleChange);
|
|
m_TextField.Q(TextField.textInputUssName).RegisterCallback<FocusOutEvent>(OnTitleBlur, TrickleDown.TrickleDown);
|
|
|
|
this.Q("selection-border").SendToBack();
|
|
RegisterCallback<DragUpdatedEvent>(OnDragUpdated);
|
|
RegisterCallback<DragPerformEvent>(OnDragPerform);
|
|
RegisterCallback<DragExitedEvent>(OnDragExited);
|
|
RegisterCallback<DragLeaveEvent>(OnDragExited);
|
|
}
|
|
|
|
bool m_CanHaveBlocks = false;
|
|
void OnSpace()
|
|
{
|
|
if (controller.model.space == VFXSpace.World)
|
|
controller.model.space = VFXSpace.Local;
|
|
else
|
|
controller.model.space = VFXSpace.World;
|
|
}
|
|
|
|
private bool HasDroppableAttributeItems(IEnumerable<AttributeItem> attributeItems)
|
|
{
|
|
foreach (var attributeItem in GetDroppableAttributeItems(attributeItems))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private IEnumerable<AttributeItem> GetDroppableAttributeItems(IEnumerable<AttributeItem> attributeItems)
|
|
{
|
|
if (controller.model.contextType == VFXContextType.Spawner)
|
|
{
|
|
foreach (var attributeItem in attributeItems)
|
|
{
|
|
if (AttributeProviderSpawner.kSupportedAttributesFromSpawnContext.Contains(attributeItem.title))
|
|
yield return attributeItem;
|
|
}
|
|
yield break;
|
|
}
|
|
|
|
foreach (var attributeItem in attributeItems)
|
|
{
|
|
if (!attributeItem.isReadOnly)
|
|
yield return attributeItem;
|
|
}
|
|
}
|
|
|
|
private bool CanDrop(IEnumerable<VFXBlockUI> blocks)
|
|
{
|
|
bool accept = true;
|
|
if (blocks.Count() == 0) return false;
|
|
foreach (var block in blocks)
|
|
{
|
|
if (!controller.model.AcceptChild(block.controller.model))
|
|
{
|
|
accept = false;
|
|
break;
|
|
}
|
|
}
|
|
return accept;
|
|
}
|
|
|
|
private void PlaceDragIndicator(int index)
|
|
{
|
|
var y = GetBlockIndexY(index, false);
|
|
m_DragDisplay.RemoveFromHierarchy();
|
|
m_DragDisplay.style.top = y;
|
|
m_BlockContainer.Add(m_DragDisplay);
|
|
}
|
|
|
|
private void RemoveDragIndicator()
|
|
{
|
|
if (m_DragDisplay.parent != null)
|
|
m_BlockContainer.Remove(m_DragDisplay);
|
|
}
|
|
|
|
bool m_DragStarted;
|
|
|
|
private float GetBlockIndexY(int index, bool middle)
|
|
{
|
|
float y = 0;
|
|
if (controller.blockControllers.Count == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
if (index >= controller.blockControllers.Count)
|
|
{
|
|
return blocks[controller.blockControllers.Last()].layout.yMax;
|
|
}
|
|
else if (middle)
|
|
{
|
|
return blocks[controller.blockControllers[index]].layout.center.y;
|
|
}
|
|
else
|
|
{
|
|
y = blocks[controller.blockControllers[index]].layout.yMin;
|
|
|
|
if (index > 0)
|
|
{
|
|
y = (y + blocks[controller.blockControllers[index - 1]].layout.yMax) * 0.5f;
|
|
}
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
private int GetDragBlockIndex(Vector2 mousePosition)
|
|
{
|
|
for (int i = 0; i < controller.blockControllers.Count; ++i)
|
|
{
|
|
float y = GetBlockIndexY(i, true);
|
|
|
|
if (mousePosition.y < y)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return controller.blockControllers.Count;
|
|
}
|
|
|
|
private void OnDragUpdated(DragUpdatedEvent evt)
|
|
{
|
|
Vector2 mousePosition = m_BlockContainer.WorldToLocal(evt.mousePosition);
|
|
int blockIndex = GetDragBlockIndex(mousePosition);
|
|
|
|
if (DragAndDrop.GetGenericData("DragSelection") is List<ISelectable> dragSelection)
|
|
{
|
|
var blocksUI = dragSelection.OfType<VFXBlockUI>().ToArray();
|
|
var dragBlocks = CanDrop(blocksUI);
|
|
if (dragBlocks)
|
|
{
|
|
DragAndDrop.visualMode = evt.ctrlKey ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Move;
|
|
|
|
if (!m_DragStarted)
|
|
{
|
|
// TODO: Do something on first DragUpdated event (initiate drag)
|
|
m_DragStarted = true;
|
|
AddToClassList("dropping");
|
|
}
|
|
|
|
PlaceDragIndicator(blockIndex);
|
|
}
|
|
else
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
|
|
evt.StopPropagation();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var references = DragAndDrop.objectReferences.OfType<VisualEffectSubgraphBlock>();
|
|
|
|
if (references.Any() && (!controller.viewController.model.isSubgraph || !references.Any(t => t.GetResource().GetOrCreateGraph().subgraphDependencies.Contains(controller.viewController.model.subgraph) || t.GetResource() == controller.viewController.model)))
|
|
{
|
|
var compatibleReferences = references.Where(x =>
|
|
{
|
|
var subgraphBlock = x?.GetResource()?.GetOrCreateGraph()?.children.OfType<VFXBlockSubgraphContext>().First();
|
|
return subgraphBlock != null ? controller.model.Accept(subgraphBlock.compatibleContextType, subgraphBlock.ownedType) : false;
|
|
});
|
|
|
|
if (compatibleReferences.Any())
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Move;
|
|
evt.StopPropagation();
|
|
PlaceDragIndicator(blockIndex);
|
|
if (!m_DragStarted)
|
|
{
|
|
// TODO: Do something on first DragUpdated event (initiate drag)
|
|
m_DragStarted = true;
|
|
AddToClassList("dropping");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
|
|
evt.StopPropagation();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var attributeItems = GetFirstAncestorOfType<VFXView>().selection.OfType<VFXBlackboardAttributeField>().Select(x => x.attribute).ToArray();
|
|
if (HasDroppableAttributeItems(attributeItems))
|
|
{
|
|
if (!m_DragStarted)
|
|
{
|
|
// TODO: Do something on first DragUpdated event (initiate drag)
|
|
m_DragStarted = true;
|
|
AddToClassList("dropping");
|
|
}
|
|
|
|
PlaceDragIndicator(blockIndex);
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Move;
|
|
}
|
|
else
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
|
|
evt.StopPropagation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnDragPerform(DragPerformEvent evt)
|
|
{
|
|
RemoveDragIndicator();
|
|
if (DragAndDrop.GetGenericData("DragSelection") is List<ISelectable> dragSelection)
|
|
{
|
|
Vector2 mousePosition = m_BlockContainer.WorldToLocal(evt.mousePosition);
|
|
int blockIndex = GetDragBlockIndex(mousePosition);
|
|
|
|
var blocksUI = dragSelection.OfType<VFXBlockUI>().ToArray();
|
|
var dropBlocks = CanDrop(blocksUI);
|
|
if (dropBlocks)
|
|
{
|
|
BlocksDropped(blockIndex, blocksUI, evt.ctrlKey);
|
|
DragAndDrop.AcceptDrag();
|
|
evt.StopPropagation();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var references = DragAndDrop.objectReferences.OfType<VisualEffectSubgraphBlock>().ToArray();
|
|
|
|
if (references.Any() && (!controller.viewController.model.isSubgraph || !references.Any(t => t.GetResource().GetOrCreateGraph().subgraphDependencies.Contains(controller.viewController.model.subgraph) || t.GetResource() == controller.viewController.model)))
|
|
{
|
|
VFXView view = GetFirstAncestorOfType<VFXView>();
|
|
foreach (var reference in references)
|
|
{
|
|
var graph = reference != null ? reference.GetResource().GetOrCreateGraph() : null;
|
|
if (graph != null)
|
|
{
|
|
var subgraphContext = graph.children.OfType<VFXBlockSubgraphContext>().First();
|
|
if (!controller.model.Accept(subgraphContext.compatibleContextType, subgraphContext.ownedType))
|
|
continue;
|
|
|
|
DragAndDrop.AcceptDrag();
|
|
if (view.HasCustomAttributeConflicts(graph.attributesManager.GetCustomAttributes()))
|
|
{
|
|
break;
|
|
}
|
|
|
|
Vector2 mousePosition = m_BlockContainer.WorldToLocal(evt.mousePosition);
|
|
|
|
int blockIndex = GetDragBlockIndex(mousePosition);
|
|
VFXBlock newModel = ScriptableObject.CreateInstance<VFXSubgraphBlock>();
|
|
|
|
newModel.SetSettingValue("m_Subgraph", reference);
|
|
|
|
UpdateSelectionWithNewBlocks();
|
|
controller.AddBlock(blockIndex, newModel);
|
|
}
|
|
else if (reference != null)
|
|
{
|
|
Debug.LogWarning($"Could not drag & drop asset '{reference.name}' because it's not supported in a context of type '{controller.model.contextType}'");
|
|
}
|
|
}
|
|
|
|
evt.StopPropagation();
|
|
}
|
|
else
|
|
{
|
|
var data = DragAndDrop.GetGenericData("DragSelection");
|
|
if (data is List<IParameterItem> items)
|
|
{
|
|
var attributeItems = GetDroppableAttributeItems(items.OfType<AttributeItem>()).ToArray();
|
|
if (attributeItems.Length > 0)
|
|
{
|
|
var mousePosition = m_BlockContainer.WorldToLocal(evt.mousePosition);
|
|
var blockIndex = GetDragBlockIndex(mousePosition);
|
|
foreach (var attributeItem in attributeItems)
|
|
{
|
|
var setAttribute = controller.model.contextType != VFXContextType.Spawner
|
|
? (VFXBlock)ScriptableObject.CreateInstance<SetAttribute>()
|
|
: ScriptableObject.CreateInstance<VFXSpawnerSetAttribute>();
|
|
setAttribute.SetSettingValue("attribute", attributeItem.title);
|
|
controller.model.AddChild(setAttribute, blockIndex);
|
|
}
|
|
|
|
DragAndDrop.AcceptDrag();
|
|
evt.StopPropagation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_DragStarted = false;
|
|
RemoveFromClassList("dropping");
|
|
}
|
|
|
|
private void BlocksDropped(int blockIndex, IEnumerable<VFXBlockUI> draggedBlocks, bool copy)
|
|
{
|
|
HashSet<VFXContextController> contexts = new HashSet<VFXContextController>();
|
|
foreach (var draggedBlock in draggedBlocks)
|
|
{
|
|
contexts.Add(draggedBlock.context.controller);
|
|
}
|
|
|
|
using (var growContext = new GrowContext(this))
|
|
{
|
|
controller.BlocksDropped(blockIndex, draggedBlocks.Select(t => t.controller), copy);
|
|
|
|
foreach (var context in contexts)
|
|
{
|
|
context.ApplyChanges();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnDragExited(EventBase e)
|
|
{
|
|
// TODO: Do something when current drag is canceled
|
|
RemoveDragIndicator();
|
|
m_DragStarted = false;
|
|
}
|
|
|
|
private VFXBlockUI InstantiateBlock(VFXBlockController blockController)
|
|
{
|
|
Profiler.BeginSample("VFXContextUI.InstantiateBlock");
|
|
Profiler.BeginSample("VFXContextUI.new VFXBlockUI");
|
|
var blockUI = new VFXBlockUI();
|
|
Profiler.EndSample();
|
|
blockUI.controller = blockController;
|
|
blocks[blockController] = blockUI;
|
|
Profiler.EndSample();
|
|
|
|
return blockUI;
|
|
}
|
|
|
|
Dictionary<VFXBlockController, VFXBlockUI> blocks = new Dictionary<VFXBlockController, VFXBlockUI>();
|
|
|
|
|
|
private void RefreshContext()
|
|
{
|
|
Profiler.BeginSample("VFXContextUI.RefreshContext");
|
|
var blockControllers = controller.blockControllers;
|
|
int blockControllerCount = blockControllers.Count();
|
|
|
|
bool somethingChanged = m_BlockContainer.childCount < blockControllerCount || (!m_CanHaveBlocks && m_NoBlock.parent != null);
|
|
|
|
int cptBlock = 0;
|
|
foreach (var child in m_BlockContainer.Children().OfType<VFXBlockUI>())
|
|
{
|
|
if (!somethingChanged && blockControllerCount > cptBlock && child.controller != blockControllers[cptBlock])
|
|
{
|
|
somethingChanged = true;
|
|
}
|
|
cptBlock++;
|
|
}
|
|
if (somethingChanged || cptBlock != blockControllerCount)
|
|
{
|
|
VFXView view = GetFirstAncestorOfType<VFXView>();
|
|
|
|
foreach (var controllerToRemove in blocks.Keys.Except(blockControllers).ToArray())
|
|
{
|
|
view.RemoveNodeEdges(blocks[controllerToRemove]);
|
|
m_BlockContainer.Remove(blocks[controllerToRemove]);
|
|
blocks.Remove(controllerToRemove);
|
|
}
|
|
if (blockControllers.Any() || !m_CanHaveBlocks)
|
|
{
|
|
m_NoBlock.RemoveFromHierarchy();
|
|
}
|
|
else if (m_NoBlock.parent == null)
|
|
{
|
|
m_BlockContainer.Add(m_NoBlock);
|
|
}
|
|
if (blockControllers.Any())
|
|
{
|
|
VFXBlockUI prevBlock = null;
|
|
|
|
var addedBlocks = new List<ISelectable>();
|
|
foreach (var blockController in blockControllers)
|
|
{
|
|
if (!blocks.TryGetValue(blockController, out var blockUI))
|
|
{
|
|
blockUI = InstantiateBlock(blockController);
|
|
m_BlockContainer.Insert(prevBlock == null ? 0 : m_BlockContainer.IndexOf(prevBlock) + 1, blockUI);
|
|
|
|
if (m_UpdateSelectionWithNewBlocks)
|
|
{
|
|
addedBlocks.Add(blockUI);
|
|
}
|
|
//Refresh error can only be called after the block has been instantiated
|
|
blockController.model.RefreshErrors();
|
|
}
|
|
|
|
if (prevBlock != null)
|
|
blockUI.PlaceInFront(prevBlock);
|
|
else
|
|
{
|
|
blockUI.SendToBack();
|
|
blockUI.AddToClassList("first");
|
|
}
|
|
|
|
prevBlock = blockUI;
|
|
}
|
|
|
|
if (addedBlocks.Any())
|
|
{
|
|
view.ClearSelection();
|
|
view.AddRangeToSelection(addedBlocks);
|
|
}
|
|
|
|
m_UpdateSelectionWithNewBlocks = false;
|
|
}
|
|
}
|
|
Profiler.EndSample();
|
|
}
|
|
|
|
Texture2D GetIconForVFXType(VFXDataType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case VFXDataType.SpawnEvent:
|
|
return VFXView.LoadImage("Execution");
|
|
case VFXDataType.Particle:
|
|
return VFXView.LoadImage("Particles");
|
|
case VFXDataType.ParticleStrip:
|
|
return VFXView.LoadImage("ParticleStrips");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
internal class GrowContext : IDisposable
|
|
{
|
|
VFXContextUI m_Context;
|
|
float m_PrevSize;
|
|
public GrowContext(VFXContextUI context)
|
|
{
|
|
m_Context = context;
|
|
m_PrevSize = context.layout.size.y;
|
|
}
|
|
|
|
void IDisposable.Dispose()
|
|
{
|
|
VFXView view = m_Context.GetFirstAncestorOfType<VFXView>();
|
|
m_Context.controller.ApplyChanges();
|
|
m_Context.panel.InternalValidateLayout();
|
|
|
|
view.PushUnderContext(m_Context, m_Context.layout.size.y - m_PrevSize);
|
|
}
|
|
}
|
|
|
|
void AddBlock(Vector2 position, Variant variant)
|
|
{
|
|
int blockIndex = -1;
|
|
|
|
var blocks = m_BlockContainer.Query().OfType<VFXBlockUI>().ToList();
|
|
for (int i = 0; i < blocks.Count; ++i)
|
|
{
|
|
Rect worldBounds = blocks[i].worldBound;
|
|
if (worldBounds.Contains(position))
|
|
{
|
|
if (position.y > worldBounds.center.y)
|
|
{
|
|
blockIndex = i + 1;
|
|
}
|
|
else
|
|
{
|
|
blockIndex = i;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
using (new GrowContext(this))
|
|
{
|
|
controller.AddBlock(blockIndex, (VFXBlock)variant.CreateInstance(), true /* freshly created block, should init space */);
|
|
}
|
|
}
|
|
|
|
private void OnCreateBlock(DropdownMenuAction evt)
|
|
{
|
|
Vector2 referencePosition = evt.eventInfo.mousePosition;
|
|
|
|
OnCreateBlock(referencePosition);
|
|
}
|
|
|
|
public void OnCreateBlock(Vector2 referencePosition)
|
|
{
|
|
VFXView view = GetFirstAncestorOfType<VFXView>();
|
|
|
|
Vector2 screenPosition = view.ViewToScreenPosition(referencePosition);
|
|
|
|
VFXFilterWindow.Show(referencePosition, screenPosition, m_BlockProvider);
|
|
}
|
|
|
|
VFXBlockProvider m_BlockProvider = null;
|
|
|
|
// TODO: Remove, unused except for debugging
|
|
// Declare new USS rect-color and use it
|
|
protected override void OnCustomStyleResolved(ICustomStyle styles)
|
|
{
|
|
base.OnCustomStyleResolved(styles);
|
|
styles.TryGetValue(RectColorProperty, out m_RectColor);
|
|
}
|
|
|
|
// TODO: Remove, unused except for debugging
|
|
Color m_RectColor = Color.magenta;
|
|
Color rectColor { get { return m_RectColor; } }
|
|
|
|
public IEnumerable<VFXBlockUI> GetAllBlocks()
|
|
{
|
|
foreach (VFXBlockUI block in m_BlockContainer.Children().OfType<VFXBlockUI>())
|
|
{
|
|
yield return block;
|
|
}
|
|
}
|
|
|
|
public IEnumerable<VFXFlowAnchor> GetFlowAnchors(bool input, bool output)
|
|
{
|
|
if (input)
|
|
foreach (VFXFlowAnchor anchor in m_FlowInputConnectorContainer.Children())
|
|
{
|
|
yield return anchor;
|
|
}
|
|
if (output)
|
|
foreach (VFXFlowAnchor anchor in m_FlowOutputConnectorContainer.Children())
|
|
{
|
|
yield return anchor;
|
|
}
|
|
}
|
|
|
|
private class VFXContextOnlyVFXNodeProvider : VFXNodeProvider
|
|
{
|
|
public VFXContextOnlyVFXNodeProvider(VFXViewController controller, Action<Variant, Vector2> onAddBlock, Func<IVFXModelDescriptor, bool> filter) :
|
|
base(controller, onAddBlock, filter, new Type[] { typeof(VFXContext) })
|
|
{
|
|
}
|
|
}
|
|
|
|
bool ProviderFilter(IVFXModelDescriptor descriptor)
|
|
{
|
|
if (!descriptor.modelType.IsSubclassOf(typeof(VFXAbstractParticleOutput)))
|
|
return false;
|
|
var toContext = (VFXContext)descriptor.unTypedModel;
|
|
foreach (var links in controller.model.inputFlowSlot.Select((t, i) => new { index = i, links = t.link }))
|
|
{
|
|
foreach (var link in links.links)
|
|
{
|
|
if (!VFXContext.CanLink(link.context, toContext, links.index, link.slotIndex))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return toContext.contextType == VFXContextType.Output;
|
|
}
|
|
|
|
void OnConvertContext(DropdownMenuAction action)
|
|
{
|
|
VFXView view = this.GetFirstAncestorOfType<VFXView>();
|
|
VFXFilterWindow.Show(action.eventInfo.mousePosition, view.ViewToScreenPosition(action.eventInfo.mousePosition), new VFXContextOnlyVFXNodeProvider(view.controller, ConvertContext, ProviderFilter));
|
|
}
|
|
|
|
void ConvertContext(Variant variant, Vector2 mPos)
|
|
{
|
|
VFXView view = GetFirstAncestorOfType<VFXView>();
|
|
VFXViewController viewController = controller.viewController;
|
|
if (view == null) return;
|
|
|
|
mPos = view.contentViewContainer.ChangeCoordinatesTo(view, controller.position);
|
|
var newNodeController = view.AddNode(variant, mPos);
|
|
var newContextController = newNodeController as VFXContextController;
|
|
newContextController.model.label = controller.model.label;
|
|
|
|
//transfer blocks
|
|
foreach (var block in controller.model.children.ToArray()) // To array needed as the IEnumerable content will change
|
|
newContextController.AddBlock(-1, block);
|
|
|
|
|
|
//transfer settings
|
|
List<KeyValuePair<string, object>> settings = new();
|
|
foreach (var setting in newContextController.model.GetSettings(true))
|
|
{
|
|
if (!newContextController.model.CanTransferSetting(setting))
|
|
continue;
|
|
|
|
if (!setting.valid || setting.field.GetCustomAttributes(typeof(VFXSettingAttribute), true).Length == 0)
|
|
continue;
|
|
|
|
var sourceSetting = controller.model.GetSetting(setting.name);
|
|
if (!sourceSetting.valid)
|
|
continue;
|
|
|
|
object value;
|
|
if (VFXConverter.TryConvertTo(sourceSetting.value, setting.field.FieldType, out value))
|
|
settings.Add(new(setting.field.Name, value));
|
|
}
|
|
newContextController.model.SetSettingValues(settings);
|
|
|
|
//transfer flow edges
|
|
if (controller.flowInputAnchors.Count == 1)
|
|
{
|
|
foreach (var output in controller.flowInputAnchors[0].connections.Select(t => t.output).ToArray())
|
|
newContextController.model.LinkFrom(output.context.model, output.slotIndex);
|
|
}
|
|
|
|
// Apply the slot changes that can be the result of settings changes
|
|
newContextController.ApplyChanges();
|
|
|
|
VFXSlot firstTextureSlot = null;
|
|
|
|
//transfer master slot values
|
|
foreach (var slot in newContextController.model.inputSlots)
|
|
{
|
|
VFXSlot mySlot = controller.model.inputSlots.FirstOrDefault(t => t.name == slot.name);
|
|
if (mySlot == null)
|
|
{
|
|
if (slot.valueType == VFXValueType.Texture2D && firstTextureSlot == null)
|
|
firstTextureSlot = slot;
|
|
continue;
|
|
}
|
|
|
|
object value;
|
|
if (VFXConverter.TryConvertTo(mySlot.value, slot.property.type, out value))
|
|
slot.value = value;
|
|
}
|
|
//Hack to copy the first texture in the first texture slot if not found by name
|
|
if (firstTextureSlot != null)
|
|
{
|
|
VFXSlot mySlot = controller.model.inputSlots.FirstOrDefault(t => t.valueType == VFXValueType.Texture2D);
|
|
|
|
if (mySlot != null)
|
|
firstTextureSlot.value = mySlot.value;
|
|
}
|
|
|
|
foreach (var anchor in newContextController.inputPorts)
|
|
{
|
|
string path = anchor.path;
|
|
var myAnchor = controller.inputPorts.FirstOrDefault(t => t.path == path);
|
|
|
|
if (myAnchor == null || !myAnchor.HasLink())
|
|
continue;
|
|
|
|
//There should be only one
|
|
var output = myAnchor.connections.First().output;
|
|
|
|
viewController.CreateLink(anchor, output);
|
|
}
|
|
|
|
// Apply the change so that it won't unlink the blocks links
|
|
controller.ApplyChanges();
|
|
|
|
viewController.RemoveElement(controller);
|
|
}
|
|
|
|
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
|
|
{
|
|
if (evt.target is VFXContextUI || evt.target is VFXBlockUI)
|
|
{
|
|
if (m_CanHaveBlocks)
|
|
{
|
|
evt.menu.InsertAction(0, "Create Block", OnCreateBlock, e => DropdownMenuAction.Status.Normal);
|
|
evt.menu.AppendSeparator();
|
|
}
|
|
}
|
|
|
|
if (evt.target is VFXContextUI && controller.model is VFXAbstractParticleOutput)
|
|
{
|
|
evt.menu.InsertAction(1, "Convert Output", OnConvertContext, e => DropdownMenuAction.Status.Normal);
|
|
}
|
|
}
|
|
|
|
void OnTitleMouseDown(MouseDownEvent e)
|
|
{
|
|
if (e.clickCount == 2)
|
|
{
|
|
OnRename();
|
|
e.StopPropagation();
|
|
focusController.IgnoreEvent(e);
|
|
}
|
|
}
|
|
|
|
public void OnRename()
|
|
{
|
|
m_Label.RemoveFromClassList("empty");
|
|
m_Label.style.display = DisplayStyle.None;
|
|
m_TextField.value = m_Label.text;
|
|
m_TextField.style.display = DisplayStyle.Flex;
|
|
m_TextField.Q(TextField.textInputUssName).Focus();
|
|
m_TextField.SelectAll();
|
|
}
|
|
|
|
void OnTitleBlur(FocusOutEvent e)
|
|
{
|
|
controller.model.label = m_TextField.value
|
|
.Trim()
|
|
.Replace("/", "")
|
|
.Replace("\\", "")
|
|
.Replace(":", "")
|
|
.Replace("<", "")
|
|
.Replace(">", "")
|
|
.Replace("*", "")
|
|
.Replace("?", "")
|
|
.Replace("\"", "")
|
|
.Replace("|", "")
|
|
;
|
|
m_TextField.style.display = DisplayStyle.None;
|
|
m_Label.style.display = DisplayStyle.Flex;
|
|
}
|
|
|
|
void OnTitleChange(ChangeEvent<string> e)
|
|
{
|
|
m_Label.text = m_TextField.value;
|
|
}
|
|
|
|
bool m_UpdateSelectionWithNewBlocks;
|
|
public void UpdateSelectionWithNewBlocks()
|
|
{
|
|
m_UpdateSelectionWithNewBlocks = true;
|
|
}
|
|
}
|
|
}
|