| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766 | using System;using System.Collections.Generic;using System.Linq;using UnityEditor;using UnityEngine;using XNode;using XNodeEditor.Internal;namespace XNodeEditor{    public partial class NodeEditorWindow    {        public enum NodeActivity        {            Idle,            HoldNode,            DragNode,            HoldGrid,            DragGrid        }        public static NodeActivity currentActivity = NodeActivity.Idle;        public static bool isPanning { get; private set; }        public static Vector2[] dragOffset;        public static XNode.Node[] copyBuffer = null;        private bool IsDraggingPort        {            get { return draggedOutput != null; }        }        private bool IsHoveringPort        {            get { return hoveredPort != null; }        }        private bool IsHoveringNode        {            get { return hoveredNode != null; }        }        private bool IsHoveringReroute        {            get { return hoveredReroute.port != null; }        }        private XNode.Node hoveredNode = null;        [NonSerialized] public XNode.NodePort hoveredPort = null;        [NonSerialized] private XNode.NodePort draggedOutput = null;        [NonSerialized] private XNode.NodePort draggedOutputTarget = null;        [NonSerialized] private XNode.NodePort autoConnectOutput = null;        [NonSerialized] private List<Vector2> draggedOutputReroutes = new List<Vector2>();        private RerouteReference hoveredReroute = new RerouteReference();        public List<RerouteReference> selectedReroutes = new List<RerouteReference>();        private Vector2 dragBoxStart;        private List<Node> preBoxSelection;        private RerouteReference[] preBoxSelectionReroute;        private Rect selectionBox;        private bool isDoubleClick = false;        private Vector2 lastMousePosition;        private float dragThreshold = 1f;        public void Controls()        {            wantsMouseMove = true;            Event e = Event.current;            switch (e.type)            {                case EventType.DragUpdated:                case EventType.DragPerform:                    DragAndDrop.visualMode = DragAndDropVisualMode.Generic;                    if (e.type == EventType.DragPerform)                    {                        DragAndDrop.AcceptDrag();                        graphEditor.OnDropObjects(DragAndDrop.objectReferences);                    }                    break;                case EventType.MouseMove:                    //Keyboard commands will not get correct mouse position from Event                    lastMousePosition = e.mousePosition;                    break;                case EventType.ScrollWheel:                    float oldZoom = zoom;                    if (e.delta.y > 0) zoom += 0.1f * zoom;                    else zoom -= 0.1f * zoom;                    if (NodeEditorPreferences.GetSettings().zoomToMouse)                        panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset);                    break;                case EventType.MouseDrag:                    if (e.button == 0)                    {                        if (IsDraggingPort)                        {                            // Set target even if we can't connect, so as to prevent auto-conn menu from opening erroneously                            if (IsHoveringPort && hoveredPort.IsInput && !draggedOutput.IsConnectedTo(hoveredPort))                            {                                draggedOutputTarget = hoveredPort;                            }                            else                            {                                draggedOutputTarget = null;                            }                            Repaint();                        }                        else if (currentActivity == NodeActivity.HoldNode)                        {                            RecalculateDragOffsets(e);                            currentActivity = NodeActivity.DragNode;                            Repaint();                        }                        if (currentActivity == NodeActivity.DragNode)                        {                            // Holding ctrl inverts grid snap                            bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap;                            if (e.control) gridSnap = !gridSnap;                            Vector2 mousePos = WindowToGridPosition(e.mousePosition);                            // Move selected nodes with offset                            for (int i = 0; i < _selectedNodes.Count; i++)                            {                                XNode.Node node = _selectedNodes[i];                                if (node == null)                                    continue;                                Undo.RecordObject(node, "Moved Node");                                Vector2 initial = node.position;                                try                                {                                    node.position = mousePos + dragOffset[i];                                }                                catch (Exception exception)                                {                                    Console.WriteLine(exception);                                    throw;                                }                                if (gridSnap)                                {                                    node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8;                                    node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8;                                }                                // Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame.                                Vector2 offset = node.position - initial;                                if (offset.sqrMagnitude > 0)                                {                                    foreach (XNode.NodePort output in node.Outputs)                                    {                                        Rect rect;                                        if (portConnectionPoints.TryGetValue(output, out rect))                                        {                                            rect.position += offset;                                            portConnectionPoints[output] = rect;                                        }                                    }                                    foreach (XNode.NodePort input in node.Inputs)                                    {                                        Rect rect;                                        if (portConnectionPoints.TryGetValue(input, out rect))                                        {                                            rect.position += offset;                                            portConnectionPoints[input] = rect;                                        }                                    }                                }                            }                            // Move selected reroutes with offset                            for (int i = 0; i < selectedReroutes.Count; i++)                            {                                Vector2 pos = mousePos + dragOffset[_selectedNodes.Count + i];                                if (gridSnap)                                {                                    pos.x = (Mathf.Round(pos.x / 16) * 16);                                    pos.y = (Mathf.Round(pos.y / 16) * 16);                                }                                selectedReroutes[i].SetPoint(pos);                            }                            Repaint();                        }                        else if (currentActivity == NodeActivity.HoldGrid)                        {                            currentActivity = NodeActivity.DragGrid;                            preBoxSelection = _selectedNodes;                            preBoxSelectionReroute = selectedReroutes.ToArray();                            dragBoxStart = WindowToGridPosition(e.mousePosition);                            Repaint();                        }                        else if (currentActivity == NodeActivity.DragGrid)                        {                            Vector2 boxStartPos = GridToWindowPosition(dragBoxStart);                            Vector2 boxSize = e.mousePosition - boxStartPos;                            if (boxSize.x < 0)                            {                                boxStartPos.x += boxSize.x;                                boxSize.x = Mathf.Abs(boxSize.x);                            }                            if (boxSize.y < 0)                            {                                boxStartPos.y += boxSize.y;                                boxSize.y = Mathf.Abs(boxSize.y);                            }                            selectionBox = new Rect(boxStartPos, boxSize);                            Repaint();                        }                    }                    else if (e.button == 1 || e.button == 2)                    {                        //check drag threshold for larger screens                        if (e.delta.magnitude > dragThreshold)                        {                            panOffset += e.delta * zoom;                            isPanning = true;                        }                    }                    break;                case EventType.MouseDown:                    Repaint();                    if (e.button == 0)                    {                        draggedOutputReroutes.Clear();                        if (IsHoveringPort)                        {                            if (hoveredPort.IsOutput)                            {                                draggedOutput = hoveredPort;                                autoConnectOutput = hoveredPort;                            }                            else                            {                                hoveredPort.VerifyConnections();                                autoConnectOutput = null;                                if (hoveredPort.IsConnected)                                {                                    XNode.Node node = hoveredPort.node;                                    XNode.NodePort output = hoveredPort.Connection;                                    int outputConnectionIndex = output.GetConnectionIndex(hoveredPort);                                    draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex);                                    hoveredPort.Disconnect(output);                                    draggedOutput = output;                                    draggedOutputTarget = hoveredPort;                                    if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);                                }                            }                        }                        else if (IsHoveringNode && IsHoveringTitle(hoveredNode))                        {                            // If mousedown on node header, select or deselect                            if (!_selectedNodes.Contains(hoveredNode))                            {                                if (e.control || e.shift)                                {                                    SelectNode(hoveredNode, true);                                }                                else                                {                                    SelectNode(hoveredNode, false);                                }                                if (!e.control && !e.shift) selectedReroutes.Clear();                            }                            // Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown.                            isDoubleClick = (e.clickCount == 2);                            e.Use();                            currentActivity = NodeActivity.HoldNode;                        }                        else if (IsHoveringReroute)                        {                            // If reroute isn't selected                            if (!selectedReroutes.Contains(hoveredReroute))                            {                                // Add it                                if (e.control || e.shift) selectedReroutes.Add(hoveredReroute);                                // Select it                                else                                {                                    selectedReroutes = new List<RerouteReference>() { hoveredReroute };                                    Selection.activeObject = null;                                }                            }                            // Deselect                            else if (e.control || e.shift) selectedReroutes.Remove(hoveredReroute);                            e.Use();                            currentActivity = NodeActivity.HoldNode;                        }                        // If mousedown on grid background, deselect all                        else if (!IsHoveringNode)                        {                            // 暂时屏蔽以前的逻辑了                            currentActivity = NodeActivity.HoldGrid;                            if (!e.control && !e.shift)                            {                                selectedReroutes.Clear();                                _selectedNodes.Clear();                            }                        }                    }                    break;                case EventType.MouseUp:                    if (e.button == 0)                    {                        //Port drag release                        if (IsDraggingPort)                        {                            // If connection is valid, save it                            if (draggedOutputTarget != null && draggedOutput.CanConnectTo(draggedOutputTarget))                            {                                XNode.Node node = draggedOutputTarget.node;                                if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget);                                // ConnectionIndex can be -1 if the connection is removed instantly after creation                                int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget);                                if (connectionIndex != -1)                                {                                    draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes);                                    if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);                                    EditorUtility.SetDirty(graph);                                }                            }                            // Open context menu for auto-connection if there is no target node                            else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate &&                                     autoConnectOutput != null)                            {                                GenericMenu menu = new GenericMenu();                                graphEditor.AddContextMenuItems(menu);                                menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));                            }                            //Release dragged connection                            draggedOutput = null;                            draggedOutputTarget = null;                            EditorUtility.SetDirty(graph);                            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();                        }                        else if (currentActivity == NodeActivity.DragNode)                        {                            IEnumerable<XNode.Node> nodes = _selectedNodes;                            foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node);                            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();                        }                        else if (!IsHoveringNode)                        {                            // If click outside node, release field focus                            if (!isPanning)                            {                                EditorGUI.FocusTextInControl(null);                                EditorGUIUtility.editingTextField = false;                            }                            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();                        }                        // If click node header, select it.                        if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift))                        {                            selectedReroutes.Clear();                            SelectNode(hoveredNode, false);                            // Double click to center node                            if (isDoubleClick)                            {                                Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode)                                    ? nodeSizes[hoveredNode] / 2                                    : Vector2.zero;                                panOffset = -hoveredNode.position - nodeDimension;                            }                        }                        // If click reroute, select it.                        if (IsHoveringReroute && !(e.control || e.shift))                        {                            selectedReroutes = new List<RerouteReference>() { hoveredReroute };                            _selectedNodes.Clear();                        }                        Repaint();                        currentActivity = NodeActivity.Idle;                    }                    else if (e.button == 1 || e.button == 2)                    {                        if (!isPanning)                        {                            if (IsDraggingPort)                            {                                draggedOutputReroutes.Add(WindowToGridPosition(e.mousePosition));                            }                            else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null &&                                     selectedReroutes.Count == 1)                            {                                selectedReroutes[0].InsertPoint(selectedReroutes[0].GetPoint());                                selectedReroutes[0] = new RerouteReference(selectedReroutes[0].port,                                    selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1);                            }                            else if (IsHoveringReroute)                            {                                ShowRerouteContextMenu(hoveredReroute);                            }                            else if (IsHoveringPort)                            {                                ShowPortContextMenu(hoveredPort);                            }                            else if (IsHoveringNode && IsHoveringTitle(hoveredNode))                            {                                if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false);                                autoConnectOutput = null;                                GenericMenu menu = new GenericMenu();                                NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu);                                menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));                                e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places.                            }                            else if (!IsHoveringNode)                            {                                autoConnectOutput = null;                                GenericMenu menu = new GenericMenu();                                graphEditor.AddContextMenuItems(menu);                                menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));                            }                        }                        isPanning = false;                    }                    // Reset DoubleClick                    isDoubleClick = false;                    break;                case EventType.KeyDown:                // if (EditorGUIUtility.editingTextField) break;                // else if (e.keyCode == KeyCode.F) Home();                // if (NodeEditorUtilities.IsMac())                // {                //     if (e.keyCode == KeyCode.Return) RenameSelectedNode();                // }                // else                // {                //     if (e.keyCode == KeyCode.F2) RenameSelectedNode();                // }                //                // if (e.keyCode == KeyCode.A)                // {                //     if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node)))                //     {                //         foreach (XNode.Node node in graph.nodes)                //         {                //             DeselectNode(node);                //         }                //     }                //     else                //     {                //         foreach (XNode.Node node in graph.nodes)                //         {                //             SelectNode(node, true);                //         }                //     }                //                //     Repaint();                // }                //                // break;                case EventType.ValidateCommand:                case EventType.ExecuteCommand:                    if (e.commandName == "SoftDelete")                    {                        if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();                        e.Use();                    }                    else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete")                    {                        if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();                        e.Use();                    }                    else if (e.commandName == "Duplicate")                    {                        if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes();                        e.Use();                    }                    else if (e.commandName == "Copy")                    {                        if (e.type == EventType.ExecuteCommand) CopySelectedNodes();                        e.Use();                    }                    else if (e.commandName == "Paste")                    {                        if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition));                        e.Use();                    }                    Repaint();                    break;                case EventType.Ignore:                    // If release mouse outside window                    if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid)                    {                        Repaint();                        currentActivity = NodeActivity.Idle;                    }                    break;            }        }        private void RecalculateDragOffsets(Event current)        {            dragOffset = new Vector2[_selectedNodes.Count + selectedReroutes.Count];            // Selected nodes            for (int i = 0; i < _selectedNodes.Count; i++)            {                Node node = _selectedNodes[i];                dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition);            }            // Selected reroutes            for (int i = 0; i < selectedReroutes.Count; i++)            {                dragOffset[_selectedNodes.Count + i] =                    selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition);            }        }        /// <summary> Puts all selected nodes in focus. If no nodes are present, resets view and zoom to to origin </summary>        public void Home()        {            if (_selectedNodes.Count > 0)            {                Vector2 minPos = _selectedNodes.Select(x => x.position)                    .Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));                Vector2 maxPos = _selectedNodes                    .Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero))                    .Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y)));                panOffset = -(minPos + (maxPos - minPos) / 2f);            }            else            {                zoom = 2;                panOffset = Vector2.zero;            }        }        /// <summary> Remove nodes in the graph in _selectedNodes</summary>        public void RemoveSelectedNodes()        {            // We need to delete reroutes starting at the highest point index to avoid shifting indices            selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList();            for (int i = 0; i < selectedReroutes.Count; i++)            {                selectedReroutes[i].RemovePoint();            }            selectedReroutes.Clear();            foreach (var node in _selectedNodes)            {                if (!node.IsRemove())                {                    continue;                }                graphEditor.RemoveNode(node);            }        }        /// <summary> Initiate a rename on the currently selected node </summary>        public void RenameSelectedNode()        {            if (_selectedNodes.Count == 1 && Selection.activeObject is XNode.Node)            {                XNode.Node node = Selection.activeObject as XNode.Node;                Vector2 size;                if (nodeSizes.TryGetValue(node, out size))                {                    RenamePopup.Show(Selection.activeObject, size.x);                }                else                {                    RenamePopup.Show(Selection.activeObject);                }            }        }        /// <summary> Draw this node on top of other nodes by placing it last in the graph.nodes list </summary>        public void MoveNodeToTop(XNode.Node node)        {            int index;            while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1)            {                graph.nodes[index] = graph.nodes[index + 1];                graph.nodes[index + 1] = node;            }        }        /// <summary> Duplicate selected nodes and select the duplicates </summary>        public void DuplicateSelectedNodes()        {            // Get selected nodes which are part of this graph            XNode.Node[] selectedNodes = _selectedNodes.Select(x => x as XNode.Node)                .Where(x => x != null && x.graph == graph).ToArray();            if (selectedNodes == null || selectedNodes.Length == 0) return;            // Get top left node position            Vector2 topLeftNode = selectedNodes.Select(x => x.position)                .Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));            InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30));        }        public void CopySelectedNodes()        {            copyBuffer = _selectedNodes.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph)                .ToArray();        }        public void PasteNodes(Vector2 pos)        {            InsertDuplicateNodes(copyBuffer, pos);        }        private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft)        {            if (nodes == null || nodes.Length == 0) return;            // Get top-left node            Vector2 topLeftNode = nodes.Select(x => x.position)                .Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));            Vector2 offset = topLeft - topLeftNode;            List<Node> newNodes = new List<Node>();            Dictionary<XNode.Node, XNode.Node> substitutes = new Dictionary<XNode.Node, XNode.Node>();            for (int i = 0; i < nodes.Length; i++)            {                XNode.Node srcNode = nodes[i];                if (srcNode == null) continue;                // Check if user is allowed to add more of given node type                XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;                Type nodeType = srcNode.GetType();                if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib))                {                    int typeCount = graph.nodes.Count(x => x.GetType() == nodeType);                    if (typeCount >= disallowAttrib.max) continue;                }                XNode.Node newNode = graphEditor.CopyNode(srcNode);                substitutes.Add(srcNode, newNode);                newNode.position = srcNode.position + offset;                newNode.ResetUniqueID();                newNodes.Add(newNode);            }            // Walk through the selected nodes again, recreate connections, using the new nodes            for (int i = 0; i < nodes.Length; i++)            {                XNode.Node srcNode = nodes[i];                if (srcNode == null) continue;                foreach (XNode.NodePort port in srcNode.Ports)                {                    for (int c = 0; c < port.ConnectionCount; c++)                    {                        XNode.NodePort inputPort =                            port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c);                        XNode.NodePort outputPort =                            port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c);                        XNode.Node newNodeIn, newNodeOut;                        if (substitutes.TryGetValue(inputPort.node, out newNodeIn) &&                            substitutes.TryGetValue(outputPort.node, out newNodeOut))                        {                            newNodeIn.UpdatePorts();                            newNodeOut.UpdatePorts();                            inputPort = newNodeIn.GetInputPort(inputPort.fieldName);                            outputPort = newNodeOut.GetOutputPort(outputPort.fieldName);                        }                        if (!inputPort.IsConnectedTo(outputPort)) inputPort.Connect(outputPort);                    }                }            }            // Select the new nodes            _selectedNodes = newNodes;        }        /// <summary> Draw a connection as we are dragging it </summary>        public void DrawDraggedConnection()        {            if (IsDraggingPort)            {                Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null);                float thickness = graphEditor.GetNoodleThickness(draggedOutput, null);                NoodlePath path = graphEditor.GetNoodlePath(draggedOutput, null);                NoodleStroke stroke = graphEditor.GetNoodleStroke(draggedOutput, null);                Rect fromRect;                if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return;                List<Vector2> gridPoints = new List<Vector2>();                gridPoints.Add(fromRect.center);                for (int i = 0; i < draggedOutputReroutes.Count; i++)                {                    gridPoints.Add(draggedOutputReroutes[i]);                }                if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center);                else gridPoints.Add(WindowToGridPosition(Event.current.mousePosition));                DrawNoodle(gradient, path, stroke, thickness, gridPoints);                Color bgcol = Color.black;                Color frcol = gradient.colorKeys[0].color;                bgcol.a = 0.6f;                frcol.a = 0.6f;                // Loop through reroute points again and draw the points                for (int i = 0; i < draggedOutputReroutes.Count; i++)                {                    // Draw reroute point at position                    Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16));                    rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8);                    rect = GridToWindowRect(rect);                    NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol);                }            }        }        bool IsHoveringTitle(XNode.Node node)        {            Vector2 mousePos = Event.current.mousePosition;            //Get node position            Vector2 nodePos = GridToWindowPosition(node.position);            float width;            Vector2 size;            if (nodeSizes.TryGetValue(node, out size)) width = size.x;            else width = 200;            Rect windowRect = new Rect(nodePos, new Vector2(width / zoom, 30 / zoom));            return windowRect.Contains(mousePos);        }        /// <summary> Attempt to connect dragged output to target node </summary>        public void AutoConnect(XNode.Node node)        {            if (autoConnectOutput == null) return;            // Find input port of same type            XNode.NodePort inputPort =                node.Ports.FirstOrDefault(x => x.IsInput && x.ValueType == autoConnectOutput.ValueType);            // Fallback to input port            if (inputPort == null) inputPort = node.Ports.FirstOrDefault(x => x.IsInput);            // Autoconnect if connection is compatible            if (inputPort != null && inputPort.CanConnectTo(autoConnectOutput)) autoConnectOutput.Connect(inputPort);            // Save changes            EditorUtility.SetDirty(graph);            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();            autoConnectOutput = null;        }    }}
 |