//SL-- public void InsetNodeBetweenConnect(FlowCanvas.BinderConnection connect, Vector2 insertPosition) { Port sp = connect.sourcePort; Port tp = connect.targetPort; if (sp.type == typeof(Flow)) { var newNode = (FlowNode)AddNode(typeof(FlowCanvas.Nodes.Dummy), insertPosition); RemoveConnection(connect); FlowCanvas.BinderConnection.Create(sp, newNode.GetInputPort("In")); FlowCanvas.BinderConnection.Create(newNode.GetOutputPort("Out"), tp); newNode.GatherPorts(); } else { var newNode = (FlowNode)AddNode(typeof(FlowCanvas.Nodes.ValuePoint <>), insertPosition); RemoveConnection(connect); var bc = FlowCanvas.BinderConnection.Create(sp, newNode.GetInputPort("In")); //newNode.GatherPorts(); FlowCanvas.BinderConnection.Create(((FlowCanvas.FlowNode)(bc.targetNode)).GetOutputPort("Out"), tp); newNode.GatherPorts(); } }
///---------------------------------------------------------------------------------------------- ///Create a NEW BinderConnection object between two ports public static BinderConnection Create(Port source, Port target) { if (!CanBeBoundVerbosed(source, target, null, out string verbose)) { ParadoxNotion.Services.Logger.LogWarning(verbose, LogTag.EDITOR, source.parent); return(null); } ParadoxNotion.Design.UndoUtility.RecordObject(source.parent.graph, "Connect Ports"); BinderConnection binder = null; if (source is FlowOutput && target is FlowInput) { binder = new BinderConnection(); } if (source is ValueOutput && target is ValueInput) { binder = (BinderConnection)System.Activator.CreateInstance(typeof(BinderConnection <>).RTMakeGenericType(new System.Type[] { target.type })); } if (binder != null) { binder.sourcePortID = source.ID; binder.targetPortID = target.ID; binder.SetSourceNode(source.parent); binder.SetTargetNode(target.parent); binder.sourcePort.connections++; binder.targetPort.connections++; binder.sourcePort.parent.OnPortConnected(binder.sourcePort, binder.targetPort); binder.targetPort.parent.OnPortConnected(binder.targetPort, binder.sourcePort); //for live editing if (Application.isPlaying) { binder.Bind(); } } ParadoxNotion.Design.UndoUtility.SetDirty(source.parent.graph); return(binder); }
///---------------------------------------------------------------------------------------------- ///Create a NEW BinderConnection object between two ports public static BinderConnection Create(Port source, Port target) { var verbose = CanBeBoundVerbosed(source, target); if (verbose != null) { ParadoxNotion.Services.Logger.LogWarning(verbose, "Editor", source.parent); return(null); } source.parent.graph.RecordUndo("Connect Ports"); BinderConnection binder = null; if (source is FlowOutput && target is FlowInput) { binder = new BinderConnection(); } if (source is ValueOutput && target is ValueInput) { binder = (BinderConnection)System.Activator.CreateInstance(typeof(BinderConnection <>).RTMakeGenericType(new System.Type[] { target.type })); } if (binder != null) { binder.SetSourcePort(source, true); binder.SetTargetPort(target, true); //we call SetSource and SetTarget with 'isNew' flag, because we need call 'OnPort...' callbacks after both are set binder.sourcePort.connections++; binder.targetPort.connections++; binder.sourcePort.parent.OnPortConnected(binder.sourcePort, binder.targetPort); binder.targetPort.parent.OnPortConnected(binder.targetPort, binder.sourcePort); //for live editing if (Application.isPlaying) { binder.Bind(); } } return(binder); }
//... static void FinalizeConnection(Port sourcePort, FlowNode targetNode) { if (sourcePort == null || targetNode == null) { return; } Port source = null; Port target = null; if (sourcePort is ValueOutput || sourcePort is FlowOutput) { source = sourcePort; target = targetNode.GetFirstInputOfType(sourcePort.type); } else { source = targetNode.GetFirstOutputOfType(sourcePort.type); target = sourcePort; } BinderConnection.Create(source, target); }
///---------------------------------------------------------------------------------------------- ///---------------------------------------------------------------------------------------------- ///Convert nodes to macro (with a bit of hocus pocus) public static void ConvertNodesToMacro(List <Node> originalNodes) { if (originalNodes == null || originalNodes.Count == 0) { return; } if (!UnityEditor.EditorUtility.DisplayDialog("Convert to Macro", "This will create a new Macro out of the nodes.\nPlease note that since Macros are assets, Scene Object references will not be saved.\nThe Macro can NOT be unpacked later on.\nContinue?", "Yes", "No!")) { return; } //create asset var newMacro = EditorUtils.CreateAsset <Macro>(); if (newMacro == null) { return; } //undo var graph = (FlowScriptBase)originalNodes[0].graph; graph.RecordUndo("Convert To Macro"); //clone nodes var cloned = Graph.CloneNodes(originalNodes, newMacro, -newMacro.translation); //clear initial "example" ports newMacro.inputDefinitions.Clear(); newMacro.outputDefinitions.Clear(); //cache used ports var inputMergeMapSource = new Dictionary <Port, Port>(); var inputMergeMapTarget = new Dictionary <Port, Port>(); var outputMergeMapTarget = new Dictionary <Port, Port>(); var outputMergeMapSource = new Dictionary <Port, Port>(); //relink copied nodes to inside macro entry/exit for (var i = 0; i < originalNodes.Count; i++) { var originalNode = originalNodes[i]; //create macro entry node port definitions and link those to input ports of cloned nodes inside foreach (var originalInputConnection in originalNode.inConnections.OfType <BinderConnection>()) { //only do stuff if link source node is not part of the clones if (originalNodes.Contains(originalInputConnection.sourceNode)) { continue; } Port defSourcePort = null; //merge same input ports and same target ports if (!inputMergeMapSource.TryGetValue(originalInputConnection.sourcePort, out defSourcePort)) { if (!inputMergeMapTarget.TryGetValue(originalInputConnection.targetPort, out defSourcePort)) { //remark: we use sourcePort.type instead of target port type, so that connections remain assignable var def = new DynamicPortDefinition(originalInputConnection.targetPort.name, originalInputConnection.sourcePort.type); defSourcePort = newMacro.AddInputDefinition(def); inputMergeMapTarget[originalInputConnection.targetPort] = defSourcePort; } inputMergeMapSource[originalInputConnection.sourcePort] = defSourcePort; } if (defSourcePort.CanAcceptConnections()) //check this for case of merged FlowPorts { var targetPort = (cloned[i] as FlowNode).GetInputPort(originalInputConnection.targetPortID); BinderConnection.Create(defSourcePort, targetPort); } } //create macro exit node port definitions and link those to output ports of cloned nodes inside foreach (var originalOutputConnection in originalNode.outConnections.OfType <BinderConnection>()) { //only do stuff if link target node is not part of the clones if (originalNodes.Contains(originalOutputConnection.targetNode)) { continue; } Port defTargetPort = null; //merge same input ports and same target ports if (!outputMergeMapTarget.TryGetValue(originalOutputConnection.targetPort, out defTargetPort)) { if (!outputMergeMapSource.TryGetValue(originalOutputConnection.sourcePort, out defTargetPort)) { var def = new DynamicPortDefinition(originalOutputConnection.sourcePort.name, originalOutputConnection.sourcePort.type); defTargetPort = newMacro.AddOutputDefinition(def); outputMergeMapSource[originalOutputConnection.sourcePort] = defTargetPort; } outputMergeMapTarget[originalOutputConnection.targetPort] = defTargetPort; } if (defTargetPort.CanAcceptConnections()) //check this for case of merged ValuePorts { var sourcePort = (cloned[i] as FlowNode).GetOutputPort(originalOutputConnection.sourcePortID); BinderConnection.Create(sourcePort, defTargetPort); } } } //Delete originals var originalBounds = RectUtils.GetBoundRect(originalNodes.Select(n => n.rect).ToArray()); foreach (var node in originalNodes.ToArray()) { graph.RemoveNode(node, false); } //Create MacroWrapper. Relink macro wrapper to outside nodes var wrapperPos = originalBounds.center; wrapperPos.x = (int)wrapperPos.x; wrapperPos.y = (int)wrapperPos.y; var wrapper = graph.AddMacroNode(newMacro, wrapperPos, null, null); wrapper.GatherPorts(); foreach (var pair in inputMergeMapSource) { var source = pair.Key; var target = wrapper.GetInputPort(pair.Value.ID); BinderConnection.Create(source, target); } foreach (var pair in outputMergeMapTarget) { var source = wrapper.GetOutputPort(pair.Value.ID); var target = pair.Key; BinderConnection.Create(source, target); } //organize a bit var clonedBounds = RectUtils.GetBoundRect(cloned.Select(n => n.rect).ToArray()); newMacro.entry.position = new Vector2((int)(clonedBounds.xMin - 300), (int)clonedBounds.yMin); newMacro.exit.position = new Vector2((int)(clonedBounds.xMax + 300), (int)clonedBounds.yMin); newMacro.translation = -newMacro.entry.position + new Vector2(300, 300); // //validate and save newMacro.Validate(); UnityEditor.AssetDatabase.SaveAssets(); }
///Create a NEW binding connection object between two ports public static BinderConnection Create(Port source, Port target) { if (source == null || target == null) { return(null); } if (source == target) { return(null); } if (!source.CanAcceptConnections()) { ParadoxNotion.Services.Logger.LogWarning("Source port can accept no more connections.", "Editor", source.parent); return(null); } if (!target.CanAcceptConnections()) { ParadoxNotion.Services.Logger.LogWarning("Target port can accept no more connections.", "Editor", source.parent); return(null); } if (source.parent == target.parent) { ParadoxNotion.Services.Logger.LogWarning("Can't connect ports on the same parent node.", "Editor", source.parent); return(null); } if (source is FlowOutput && !(target is FlowInput)) { ParadoxNotion.Services.Logger.LogWarning("Flow ports can only be connected to other Flow ports.", "Editor", source.parent); return(null); } if ((source is FlowInput && target is FlowInput) || (source is ValueInput && target is ValueInput)) { ParadoxNotion.Services.Logger.LogWarning("Can't connect input to input.", "Editor", source.parent); return(null); } if ((source is FlowOutput && target is FlowOutput) || (source is ValueOutput && target is ValueOutput)) { ParadoxNotion.Services.Logger.LogWarning("Can't connect output to output.", "Editor", source.parent); return(null); } if (!TypeConverter.HasConvertion(source.type, target.type)) { ParadoxNotion.Services.Logger.LogWarning(string.Format("Can't connect ports. Type '{0}' is not assignable from Type '{1}' and there exists no automatic conversion for those types.", target.type.FriendlyName(), source.type.FriendlyName()), "Editor", source.parent); return(null); } #if UNITY_EDITOR UnityEditor.Undo.RecordObject(source.parent.graph, "Connect Ports"); #endif BinderConnection binder = null; if (source is FlowOutput && target is FlowInput) { binder = new BinderConnection(); } if (source is ValueOutput && target is ValueInput) { binder = (BinderConnection)System.Activator.CreateInstance(typeof(BinderConnection <>).RTMakeGenericType(new System.Type[] { target.type })); } if (binder != null) { binder.OnCreate(source, target); } return(binder); }
///Return whether or not source can connect to target. The return is a string with the reason why NOT, null if possible. ///Providing an existing ref connection, will bypass source/target validation respectively if that connection is already connected to that source/target port. public static string CanBeBoundVerbosed(Port source, Port target, BinderConnection refConnection = null) { if (source == null || target == null) { return("A port is null."); } if (source == target) { // return "Can't connect port to itself."; return(string.Empty); } if (source.parent == target.parent) { return("Can't connect ports on the same node."); } if (source.parent == target.parent) { return("Can't connect ports on the same parent node."); } if (source.IsInputPort() && target.IsInputPort()) { return("Can't connect input to input."); } if (source.IsOutputPort() && target.IsOutputPort()) { return("Can't connect output to output."); } if (source.IsFlowPort() != target.IsFlowPort()) { return("Flow ports can only be connected to other Flow ports."); } if (refConnection == null || refConnection.sourcePort != source) { if (!source.CanAcceptConnections()) { return("Source port can accept no more out connections."); } } if (refConnection == null || refConnection.targetPort != target) { if (!target.CanAcceptConnections()) { return("Target port can accept no more in connections."); } } if (!TypeConverter.HasConvertion(source.type, target.type)) { return(string.Format("Can't connect ports. Type '{0}' is not assignable from Type '{1}' and there exists no automatic conversion for those types.", target.type.FriendlyName(), source.type.FriendlyName())); } return(null); }
///Return whether or not source can connect to target. public static bool CanBeBound(Port source, Port target, BinderConnection refConnection = null) { return(CanBeBoundVerbosed(source, target, refConnection) == null); }
//Seal it... sealed protected override void DrawNodeConnections(Rect drawCanvas, bool fullDrawPass, Vector2 canvasMousePos, float zoomFactor) { var e = Event.current; //Port container graphics if (fullDrawPass || drawCanvas.Overlaps(rect)) { GUI.Box(new Rect(rect.x - 8, rect.y + 2, 10, rect.height), string.Empty, CanvasStyles.nodePortContainer); GUI.Box(new Rect(rect.xMax - 2, rect.y + 2, 10, rect.height), string.Empty, CanvasStyles.nodePortContainer); } /// if (fullDrawPass || drawCanvas.Overlaps(rect)) { var portRect = new Rect(0, 0, 10, 10); //INPUT Ports if (orderedInputs != null) { Port instancePort = null; for (var i = 0; i < orderedInputs.Length; i++) { var port = orderedInputs[i]; var canConnect = true; if (clickedPort != null) { if ((port == clickedPort) || (clickedPort.parent == port.parent) || (clickedPort.IsInputPort()) || (port.isConnected && port is ValueInput) || (!TypeConverter.HasConvertion(clickedPort.type, port.type))) { canConnect = false; } } port.pos = new Vector2(rect.x - 5, rect.y + port.posOffsetY); portRect.width = port.isConnected? 12:10; portRect.height = portRect.width; portRect.center = port.pos; //first gameobject or component port is considered to be the 'instance' port by convention if (port == firstValuePort) { if (port.IsUnitySceneObject()) { instancePort = port; } } //Port graphic if (clickedPort != null && !canConnect && clickedPort != port) { GUI.color = new Color(1, 1, 1, 0.3f); } GUI.Box(portRect, string.Empty, port.isConnected? CanvasStyles.nodePortConnected : CanvasStyles.nodePortEmpty); GUI.color = Color.white; //Tooltip if (portRect.Contains(e.mousePosition)) { var labelString = (canConnect || port.isConnected || port == clickedPort)? port.type.FriendlyName() : "Can't Connect Here"; var size = CanvasStyles.box.CalcSize(new GUIContent(labelString)); var rect = new Rect(0, 0, size.x + 10, size.y + 5); rect.x = portRect.x - size.x - 10; rect.y = portRect.y - size.y / 2; GUI.Box(rect, labelString, CanvasStyles.box); //Or value } else { if (port is ValueInput && !port.isConnected) { //Only these types are shown their value if (port.type.IsValueType || port.type == typeof(Type) || port.type == typeof(string) || port.IsUnityObject()) { var value = (port as ValueInput).serializedValue; string labelString = null; if (!(port as ValueInput).isDefaultValue) { if (value is UnityEngine.Object && value as UnityEngine.Object != null) { labelString = string.Format("<b><color=#66ff33>{0}</color></b>", (value as UnityEngine.Object).name); } else { labelString = value.ToStringAdvanced(); } } else if (port == instancePort) { var exists = true; if (graphAgent != null && typeof(Component).IsAssignableFrom(port.type)) { exists = graphAgent.GetComponent(port.type) != null; } var color = exists? "66ff33" : "ff3300"; labelString = string.Format("<color=#{0}><b>♟ <i>Self</i></b></color>", color); } else { GUI.color = new Color(1, 1, 1, 0.15f); labelString = value.ToStringAdvanced(); } var size = CanvasStyles.label.CalcSize(new GUIContent(labelString)); var rect = new Rect(0, 0, size.x, size.y); rect.x = portRect.x - size.x - 5; rect.y = portRect.y - size.y * 0.3f; //*0.3? something's wrong here. FIX GUI.Label(rect, labelString, CanvasStyles.label); GUI.color = Color.white; } } } if (GraphEditorUtility.allowClick) { //Right click removes connections if (port.isConnected && e.type == EventType.ContextClick && portRect.Contains(e.mousePosition)) { foreach (var c in GetInPortConnections(port)) { graph.RemoveConnection(c); } e.Use(); return; } //Click initialize new drag & drop connection if (e.type == EventType.MouseDown && e.button == 0 && portRect.Contains(e.mousePosition)) { if (port.CanAcceptConnections()) { dragDropMisses = 0; clickedPort = port; e.Use(); } } //Drop on creates connection if (e.type == EventType.MouseUp && e.button == 0 && clickedPort != null) { if (portRect.Contains(e.mousePosition) && port.CanAcceptConnections()) { BinderConnection.Create(clickedPort, port); clickedPort = null; e.Use(); } } } } } //OUTPUT Ports if (orderedOutputs != null) { for (var i = 0; i < orderedOutputs.Length; i++) { var port = orderedOutputs[i]; var canConnect = true; if (clickedPort != null) { if ((port == clickedPort) || (clickedPort.parent == port.parent) || (clickedPort.IsOutputPort()) || (port.isConnected && port is FlowOutput) || (!TypeConverter.HasConvertion(port.type, clickedPort.type))) { canConnect = false; } } port.pos = new Vector2(rect.xMax + 5, rect.y + port.posOffsetY); portRect.width = port.isConnected? 12:10; portRect.height = portRect.width; portRect.center = port.pos; //Port graphic if (clickedPort != null && !canConnect && clickedPort != port) { GUI.color = new Color(1, 1, 1, 0.3f); } GUI.Box(portRect, string.Empty, port.isConnected? CanvasStyles.nodePortConnected : CanvasStyles.nodePortEmpty); GUI.color = Color.white; //Tooltip if (portRect.Contains(e.mousePosition)) { var labelString = (canConnect || port.isConnected || port == clickedPort)? port.type.FriendlyName() : "Can't Connect Here"; var size = CanvasStyles.box.CalcSize(new GUIContent(labelString)); var rect = new Rect(0, 0, size.x + 10, size.y + 5); rect.x = portRect.x + 15; rect.y = portRect.y - portRect.height / 2; GUI.Box(rect, labelString, CanvasStyles.box); } if (GraphEditorUtility.allowClick) { //Right click removes connections if (e.type == EventType.ContextClick && portRect.Contains(e.mousePosition)) { foreach (var c in GetOutPortConnections(port)) { graph.RemoveConnection(c); } e.Use(); return; } //Click initialize new drag & drop connection if (e.type == EventType.MouseDown && e.button == 0 && portRect.Contains(e.mousePosition)) { if (port.CanAcceptConnections()) { dragDropMisses = 0; clickedPort = port; e.Use(); } } //Drop on creates connection if (e.type == EventType.MouseUp && e.button == 0 && clickedPort != null) { if (portRect.Contains(e.mousePosition) && port.CanAcceptConnections()) { BinderConnection.Create(port, clickedPort); clickedPort = null; e.Use(); } } } } } } ///ACCEPT CONNECTION if (clickedPort != null && e.type == EventType.MouseUp) { ///ON NODE if (rect.Contains(e.mousePosition)) { var cachePort = clickedPort; var menu = new GenericMenu(); if (cachePort is ValueOutput || cachePort is FlowOutput) { if (orderedInputs != null) { foreach (var _port in orderedInputs.Where(p => p.CanAcceptConnections() && TypeConverter.HasConvertion(cachePort.type, p.type))) { var port = _port; menu.AddItem(new GUIContent(string.Format("To: '{0}'", port.name)), false, () => { BinderConnection.Create(cachePort, port); }); } } } else { if (orderedOutputs != null) { foreach (var _port in orderedOutputs.Where(p => p.CanAcceptConnections() && TypeConverter.HasConvertion(p.type, cachePort.type))) { var port = _port; menu.AddItem(new GUIContent(string.Format("From: '{0}'", port.name)), false, () => { BinderConnection.Create(port, cachePort); }); } } } //append menu items menu = OnDragAndDropPortContextMenu(menu, cachePort); //if there is only 1 option, just do it if (menu.GetItemCount() == 1) { EditorUtils.GetMenuItems(menu)[0].func(); } else { GraphEditorUtility.PostGUI += () => { menu.ShowAsContext(); }; } clickedPort = null; e.Use(); /// ///ON CANVAS } else { dragDropMisses++; if (dragDropMisses == graph.allNodes.Count && clickedPort != null) { var cachePort = clickedPort; clickedPort = null; DoContextPortConnectionMenu(cachePort, e.mousePosition, zoomFactor); e.Use(); } } } //Temp connection line when linking if (clickedPort != null && clickedPort.parent == this) { var from = clickedPort.pos; var to = e.mousePosition; var xDiff = (from.x - to.x) * 0.8f; xDiff = to.x > from.x? xDiff : -xDiff; var tangA = clickedPort is FlowInput || clickedPort is ValueInput? new Vector2(xDiff, 0) : new Vector2(-xDiff, 0); var tangB = tangA * -1; Handles.DrawBezier(from, to, from + tangA, to + tangB, new Color(0.5f, 0.5f, 0.8f, 0.8f), null, 3); } //Actualy draw the existing connections for (var i = 0; i < outConnections.Count; i++) { var binder = outConnections[i] as BinderConnection; if (binder != null) //for in case it's MissingConnection { var sourcePort = binder.sourcePort; var targetPort = binder.targetPort; if (sourcePort != null && targetPort != null) { if (fullDrawPass || drawCanvas.Overlaps(RectUtils.GetBoundRect(sourcePort.pos, targetPort.pos))) { binder.DrawConnectionGUI(sourcePort.pos, targetPort.pos); } } } } }
///Create a NEW binding connection object between two ports public static BinderConnection Create(Port source, Port target) { if (source == null || target == null) { Debug.LogError("Source Port or Target Port is null when making a new Binder Connection"); return(null); } if (!source.CanAcceptConnections()) { Debug.LogWarning("Source port can accept no more connections"); return(null); } if (!target.CanAcceptConnections()) { Debug.LogWarning("Target port can accept no more connections"); return(null); } if (source.parent == target.parent) { Debug.LogWarning("Can't connect ports on the same parent node"); return(null); } if (source is FlowOutput && !(target is FlowInput)) { Debug.LogWarning("Flow ports can only be connected to other Flow ports"); return(null); } if ((source is FlowInput && target is FlowInput) || (source is ValueInput && target is ValueInput)) { Debug.LogWarning("Can't connect input to input"); return(null); } if ((source is FlowOutput && target is FlowOutput) || (source is ValueOutput && target is ValueOutput)) { Debug.LogWarning("Can't connect output to output"); return(null); } if (!TypeConverter.HasConvertion(source.type, target.type)) { Debug.LogWarning(string.Format("Can't connect ports. Type '{0}' is not assignable from Type '{1}' and there exists no internal convertion for those types.", target.type.FriendlyName(), source.type.FriendlyName())); return(null); } #if UNITY_EDITOR UnityEditor.Undo.RecordObject(source.parent.graph, "Connect Ports"); #endif if (source is FlowOutput && target is FlowInput) { var flowBind = new BinderConnection(); flowBind.OnCreate(source, target); return(flowBind); } if (source is ValueOutput && target is ValueInput) { var valueBind = (BinderConnection)System.Activator.CreateInstance(typeof(BinderConnection <>).RTMakeGenericType(new System.Type[] { target.type })); valueBind.OnCreate(source, target); return(valueBind); } return(null); }
///Return whether or not source can connect to target. The return is a string with the reason why NOT, null if possible. ///Providing an existing ref connection, will bypass source/target validation respectively if that connection is already connected to that source/target port. public static bool CanBeBoundVerbosed(Port source, Port target, BinderConnection refConnection, out string verbose) { verbose = CanBeBoundVerbosed_Internal(source, target, refConnection); return(verbose == null); }
///---------------------------------------------------------------------------------------------- ///Return whether or not source can connect to target. public static bool CanBeBound(Port source, Port target, BinderConnection refConnection) { return(CanBeBoundVerbosed(source, target, refConnection, out string v)); }