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.
 
 
 
 
 

547 lines
21 KiB

using System.Collections.Generic;
using System.Linq;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.VFX;
using UnityEngine.UIElements;
using UnityEngine.Profiling;
using Type = System.Type;
namespace UnityEditor.VFX.UI
{
class VFXDataAnchor : Port, IControlledElement<VFXDataAnchorController>, IEdgeConnectorListener
{
static readonly Vector2 s_DragEdgeTolerance = new Vector2(3, 3);
static readonly Vector2 portPositionOffset = new Vector2(-4, -20);
VFXDataAnchorController m_Controller;
Controller IControlledElement.controller
{
get { return m_Controller; }
}
public VFXDataAnchorController controller
{
get { return m_Controller; }
set
{
if (m_Controller != null)
{
m_Controller.UnregisterHandler(this);
}
m_Controller = value;
if (m_Controller != null)
{
if (m_Controller.model != null)
m_Controller.model.RefreshErrors();
m_Controller.RegisterHandler(this);
}
}
}
VFXNodeUI m_Node;
public new VFXNodeUI node
{
get { return m_Node; }
}
protected VFXDataAnchor(Orientation anchorOrientation, Direction anchorDirection, Type type, VFXNodeUI node) : base(anchorOrientation, anchorDirection, Capacity.Multi, type)
{
Profiler.BeginSample("VFXDataAnchor.VFXDataAnchor");
this.AddStyleSheetPath("VFXDataAnchor");
AddToClassList("VFXDataAnchor");
this.AddStyleSheetPath("VFXTypeColor");
m_Node = node;
this.Q<VisualElement>("cap").RemoveFromHierarchy();
RegisterCallback<PointerEnterEvent>(OnPointerEnter);
RegisterCallback<PointerLeaveEvent>(OnPointerLeave);
this.AddManipulator(new ContextualMenuManipulator(BuildContextualMenu));
RegisterCallback<CustomStyleResolvedEvent>(OneTimeCustomStyleResolved);
Profiler.EndSample();
}
public VisualElement connector
{
get { return m_ConnectorBox; }
}
public virtual void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
var op = controller.sourceNode.model as VFXOperatorNumericCascadedUnified;
if (op != null)
evt.menu.AppendAction("Remove Slot", OnRemove, e => op.operandCount > 2 ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
}
void OnRemove(DropdownMenuAction e)
{
var op = controller.sourceNode as VFXCascadedOperatorController;
op.RemoveOperand(controller);
}
public static VFXDataAnchor Create(VFXDataAnchorController controller, VFXNodeUI node)
{
var anchor = new VFXDataAnchor(controller.orientation, controller.direction, controller.portType, node);
anchor.m_EdgeConnector = new VFXEdgeConnector(anchor);
anchor.controller = controller;
anchor.AddManipulator(anchor.m_EdgeConnector);
return anchor;
}
bool m_EdgeDragging;
public override void OnStartEdgeDragging()
{
m_EdgeDragging = true;
highlight = false;
}
public override void OnStopEdgeDragging()
{
m_EdgeDragging = false;
highlight = true;
}
protected override void HandleEventBubbleUp(EventBase evt)
{
// Inflate the area to slightly increase sensitive area
var rect = this.connector.layout;
rect.min -= s_DragEdgeTolerance;
rect.max += s_DragEdgeTolerance;
// This is to prevent from initiating a link creation even when click outside the port's dot button
if (evt is PointerDownEvent pointerDownEvent && !rect.Contains(pointerDownEvent.localPosition))
{
evt.StopImmediatePropagation();
}
else
{
base.HandleEventBubbleUp(evt);
}
}
void OnPointerEnter(PointerEnterEvent e)
{
// Prevent VisualElement from setting hover pseudo-state
if (m_EdgeDragging && !highlight)
e.StopPropagation();
this.connector.style.backgroundColor = portColor;
}
void OnPointerLeave(PointerLeaveEvent e)
{
if (m_EdgeDragging && !highlight)
e.StopPropagation();
if (!connected)
this.connector.style.backgroundColor = StyleKeyword.Null;
}
public override bool collapsed => !controller.expandedInHierachy;
IEnumerable<VFXDataEdge> GetAllEdges()
{
VFXView view = GetFirstAncestorOfType<VFXView>();
foreach (var edgeController in controller.connections)
{
VFXDataEdge edge = view.GetDataEdgeByController(edgeController as VFXDataEdgeController);
if (edge != null)
yield return edge;
}
}
void IControlledElement.OnControllerChanged(ref ControllerChangedEvent e)
{
if (e.controller == controller)
{
SelfChange(e.change);
}
}
public virtual void SelfChange(int change)
{
if (change != VFXDataAnchorController.Change.hidden)
{
if (controller.connected)
AddToClassList("connected");
else
RemoveFromClassList("connected");
portType = controller.portType;
string className = VFXTypeDefinition.GetTypeCSSClass(controller.portType);
// update the css type of the class
foreach (var cls in VFXTypeDefinition.GetTypeCSSClasses())
{
if (cls != className)
{
m_ConnectorBox.RemoveFromClassList(cls);
RemoveFromClassList(cls);
}
}
AddToClassList(className);
m_ConnectorBox.AddToClassList(className);
AddToClassList("EdgeConnector");
switch (controller.direction)
{
case Direction.Input:
AddToClassList("Input");
break;
case Direction.Output:
AddToClassList("Output");
break;
}
portName = "";
}
if (controller.expandedInHierachy)
{
style.display = DisplayStyle.Flex;
RemoveFromClassList("hidden");
}
else
{
style.display = DisplayStyle.None;
AddToClassList("hidden");
}
UpdateCapColorCustom();
if (controller.direction == Direction.Output)
m_ConnectorText.text = controller.name;
else
m_ConnectorText.text = "";
}
private void UpdateCapColorCustom()
{
if (this.portCapLit || this.connected)
this.connector.style.backgroundColor = this.portColor;
else
this.connector.style.backgroundColor = StyleKeyword.Null;
}
void IEdgeConnectorListener.OnDrop(GraphView graphView, Edge edge)
{
VFXView view = graphView as VFXView;
VFXDataEdge dataEdge = edge as VFXDataEdge;
VFXDataEdgeController edgeController = new VFXDataEdgeController(dataEdge.input.controller, dataEdge.output.controller);
view.controller.AddElement(edgeController);
}
public override void Connect(Edge edge)
{
base.Connect(edge);
UpdateCapColorCustom();
}
public override void Disconnect(Edge edge)
{
base.Disconnect(edge);
UpdateCapColorCustom();
}
void IEdgeConnectorListener.OnDropOutsidePort(Edge edge, Vector2 position)
{
VFXSlot startSlot = controller.model;
VFXView view = this.GetFirstAncestorOfType<VFXView>();
VFXViewController viewController = view.controller;
List<VisualElement> picked = new List<VisualElement>();
panel.PickAll(position, picked);
VFXNodeUI endNode = null;
foreach (var element in picked)
{
if (element is VFXNodeUI node)
{
endNode = node;
break;
}
}
VFXDataEdge dataEdge = edge as VFXDataEdge;
bool exists = false;
if (dataEdge.controller != null)
{
exists = true;
view.controller.RemoveElement(dataEdge.controller);
}
if (endNode != null)
{
VFXNodeController nodeController = endNode.controller;
if (nodeController != null)
{
if (controller.direction == Direction.Input)
{
foreach (var output in nodeController.outputPorts.Where(t => t.model == null || t.model.IsMasterSlot()))
{
if (viewController.CreateLink(controller, output))
break;
}
}
else
{
foreach (var input in nodeController.inputPorts.Where(t => t.model == null || t.model.IsMasterSlot() && !t.model.HasLink(true)))
{
if (viewController.CreateLink(input, controller))
break;
}
}
}
}
else if (controller.direction == Direction.Input && Event.current.modifiers == EventModifiers.Alt)
{
if (startSlot == null)
{
Debug.LogWarning("Creating a node with a shortcut is not supported for unspecified type ports. Instead, create it by dragging an edge and using node search.");
return;
}
var targetType = controller.portType;
var attribute = VFXLibrary.GetAttributeFromSlotType(controller.portType);
VFXModelDescriptorParameters parameterDesc;
if (attribute != null && attribute.usages.HasFlag(VFXTypeAttribute.Usage.ExcludeFromProperty))
{
parameterDesc = VFXLibrary.GetParameters().FirstOrDefault(t =>
{
var model = t.model;
if (!model.outputSlots[0].CanLink(controller.model))
return false;
var attributeCandidate = VFXLibrary.GetAttributeFromSlotType(model.type);
return attributeCandidate == null || !attributeCandidate.usages.HasFlag(VFXTypeAttribute.Usage.ExcludeFromProperty);
});
}
else
{
parameterDesc = VFXLibrary.GetParameters().FirstOrDefault(t =>
{
var model = t.model;
return model.type == targetType;
});
}
if (parameterDesc != null)
{
Vector2 pos = view.contentViewContainer.GlobalToBound(position) - new Vector2(140, 20);
view.UpdateSelectionWithNewNode();
VFXParameter parameter = viewController.AddVFXParameter(pos, parameterDesc.variant, false);
parameter.SetSettingValue("m_Exposed", true);
var window = VFXViewWindow.GetWindow(view);
var category = string.Empty;
if (window.graphView.blackboard.selection != null)
{
category = window.graphView.blackboard.selection.OfType<VFXBlackboardCategory>().FirstOrDefault()?.category.title;
if (string.IsNullOrEmpty(category))
{
category = window.graphView.blackboard.selection.OfType<VFXBlackboardField>().FirstOrDefault()?.controller.model.category;
}
}
if (!string.IsNullOrEmpty(category))
{
parameter.SetSettingValue("m_Category", category);
}
startSlot.Link(parameter.outputSlots[0]);
CopyValueToParameter(parameter);
viewController.AddVFXModel(pos, parameter);
// Update blackboard because the VFXParameterController will be added on next graph update
EditorApplication.delayCall += () => view.blackboard.Update(true);
}
}
else if (!exists)
{
if (direction == Direction.Input || viewController.model.visualEffectObject is VisualEffectSubgraphOperator || viewController.model.visualEffectObject is VisualEffectSubgraphBlock) // no context for subgraph operators.
VFXFilterWindow.Show(view, Event.current.mousePosition, view.ViewToScreenPosition(Event.current.mousePosition), BuildNodeProvider(viewController, new Type[] { typeof(VFXOperator), typeof(VFXParameter) }));
else
VFXFilterWindow.Show(view, Event.current.mousePosition, view.ViewToScreenPosition(Event.current.mousePosition), BuildNodeProvider(viewController, new Type[] { typeof(VFXOperator), typeof(VFXParameter), typeof(VFXContext) }));
}
}
#if VFX_HAS_UNIT_TEST
public VFXNodeProvider BuildNodeProviderForInternalTest(VFXViewController viewController, IEnumerable<Type> acceptedType)
{
return BuildNodeProvider(viewController, acceptedType);
}
#endif
VFXNodeProvider BuildNodeProvider(VFXViewController viewController, IEnumerable<Type> acceptedType)
{
return new VFXNodeProvider(viewController, AddLinkedNode, ProviderFilter, acceptedType);
}
bool ProviderFilter(IVFXModelDescriptor descriptor)
{
var mySlot = controller.model;
if (descriptor.modelType == typeof(VisualEffectSubgraphOperator))
{
var path = (string)descriptor.variant.settings.Single(x => x.Key == "path").Value;
if (!path.StartsWith(VisualEffectAssetEditorUtility.templatePath) && path.EndsWith(VisualEffectSubgraphOperator.Extension))
{
var subGraph = AssetDatabase.LoadAssetAtPath<VisualEffectSubgraphOperator>(path);
if (subGraph != null && (!controller.viewController.model.isSubgraph || !subGraph.GetResource().GetOrCreateGraph().subgraphDependencies.Contains(controller.viewController.model.subgraph) && subGraph.GetResource() != controller.viewController.model))
return true;
}
return false;
}
if (descriptor.unTypedModel is not IVFXSlotContainer container)
return false;
if (direction == Direction.Output
&& mySlot != null
&& container is VFXOperatorDynamicOperand
&& (container as VFXOperatorDynamicOperand).validTypes.Contains(mySlot.property.type))
return true;
IEnumerable<Type> validTypes = null;
if (mySlot == null)
{
var op = controller.sourceNode.model as VFXOperatorDynamicOperand;
if (op != null)
validTypes = op.validTypes;
}
var getSlots = direction == Direction.Input ? container.GetOutputSlot : (System.Func<int, VFXSlot>)container.GetInputSlot;
var count = direction == Direction.Input ? container.GetNbOutputSlots() : container.GetNbInputSlots();
// Template containers are not sync initially to save time during loading
// For container with no input or output this can be called everytime, but should also be very fast
// In VFXLibrary, the initial apply variant is supposed to clear the slot list (see ClearSlots invocation)
if (count == 0)
{
container.ResyncSlots(false);
count = direction == Direction.Input ? container.GetNbOutputSlots() : container.GetNbInputSlots();
}
for (int i = 0; i < count; ++i)
{
var slot = getSlots(i);
if (mySlot != null && slot.CanLink(mySlot))
return true;
else if (validTypes != null && validTypes.Contains(slot.property.type))
return true;
}
return false;
}
void AddLinkedNode(Variant variant, Vector2 mPos)
{
var mySlot = controller.model;
VFXView view = GetFirstAncestorOfType<VFXView>();
VFXViewController viewController = controller.viewController;
if (view == null) return;
var newNodeController = view.AddNode(variant, mPos);
if (newNodeController == null)
return;
IEnumerable<Type> validTypes = null;
var op = controller.sourceNode.model as VFXOperatorNumericCascadedUnified;
if (mySlot == null && op != null)
{
validTypes = op.validTypes;
}
// If linking to a new parameter, copy the slot value and space
if (direction == Direction.Input && controller.model != null) //model will be null for upcomming which won't have a value
{
if (newNodeController is VFXOperatorController)
{
var inlineOperator = (newNodeController as VFXOperatorController).model as VFXInlineOperator;
if (inlineOperator != null)
{
var value = controller.model.value;
object convertedValue = null;
if (VFXConverter.TryConvertTo(value, inlineOperator.type, out convertedValue))
{
inlineOperator.inputSlots[0].value = convertedValue;
}
if (inlineOperator.inputSlots[0].spaceable && controller.model.spaceable)
{
inlineOperator.inputSlots[0].space = controller.model.space;
}
}
}
}
var ports = direction == Direction.Input ? newNodeController.outputPorts : newNodeController.inputPorts;
int count = ports.Count();
for (int i = 0; i < count; ++i)
{
var port = ports[i];
if (mySlot != null)
{
if (viewController.CreateLink(direction == Direction.Input ? controller : port, direction == Direction.Input ? port : controller))
{
AlignNodeToLinkedPort(view, port, newNodeController);
break;
}
}
else if (validTypes != null)
{
if (validTypes.Contains(port.model.property.type))
{
if (viewController.CreateLink(controller, port))
{
break;
}
}
}
}
}
void AlignNodeToLinkedPort(VFXView view, VFXDataAnchorController port, VFXNodeController nodeController)
{
var portNode = view.GetDataAnchorByController(port);
var connectorElement = portNode.Q<VisualElement>("connector");
var newNode = view.GetNodeByController(nodeController);
var offset = newNode.worldBound.position - connectorElement.worldBound.position + portPositionOffset;
nodeController.model.position += offset / view.scale;
}
void CopyValueToParameter(VFXParameter parameter)
{
var value = controller.model.value;
object convertedValue = null;
if (VFXConverter.TryConvertTo(value, parameter.type, out convertedValue))
{
parameter.value = convertedValue;
}
}
private void OneTimeCustomStyleResolved(CustomStyleResolvedEvent evt)
{
UpdateCapColorCustom();
UnregisterCallback<CustomStyleResolvedEvent>(OneTimeCustomStyleResolved);
}
}
}