private static void HandleGroupDragging(NodeEditorInputInfo inputInfo) { NodeEditorState state = inputInfo.editorState; NodeGroup active = state.activeGroup; if (active != null) { // Handle dragging and resizing of active group if (state.dragUserID != "group") { state.activeGroup = null; state.resizeGroup = false; return; } // Update drag operation Vector2 dragChange = state.UpdateDrag("group", inputInfo.inputPos); Vector2 newSizePos = state.dragObjectPos; if (state.resizeGroup) { // Resizing -> Apply drag to selected borders while keeping a minimum size // Note: Binary operator and !=0 checks of the flag is enabled, but not necessarily the only flag (in which case you would use ==) Rect r = active.rect; if ((NodeGroup.resizeDir & BorderSelection.Left) != 0) { active.rect.xMin = r.xMax - Math.Max(minGroupSize, r.xMax - newSizePos.x); } else if ((NodeGroup.resizeDir & BorderSelection.Right) != 0) { active.rect.xMax = r.xMin + Math.Max(minGroupSize, newSizePos.x - r.xMin); } if ((NodeGroup.resizeDir & BorderSelection.Top) != 0) { active.rect.yMin = r.yMax - Math.Max(minGroupSize, r.yMax - newSizePos.y); } else if ((NodeGroup.resizeDir & BorderSelection.Bottom) != 0) { active.rect.yMax = r.yMin + Math.Max(minGroupSize, newSizePos.y - r.yMin); } } else { // Dragging -> Apply drag to body and pinned nodes active.rect.position = newSizePos; foreach (Node pinnedNode in active.pinnedNodes) { pinnedNode.position += dragChange; } foreach (NodeGroup pinnedGroup in active.pinnedGroups) { pinnedGroup.rect.position += dragChange; } } inputInfo.inputEvent.Use(); NodeEditor.RepaintClients(); } }
104)] // Priority over hundred to make it call after the GUI, and before Node dragging (110) and window panning (105) private static void HandleGroupDraggingStart(NodeEditorInputInfo inputInfo) { if (GUIUtility.hotControl > 0) { return; // GUI has control } NodeEditorState state = inputInfo.editorState; if (inputInfo.inputEvent.button == 0 && state.focusedNode == null && state.dragNode == false) { // Do not interfere with other dragging stuff NodeGroup focusedGroup = GroupAtPositionInput(state, NodeEditor.ScreenToCanvasSpace(inputInfo.inputPos)); if (focusedGroup != null) { // Start dragging the focused group UpdateGroupOrder(); Vector2 canvasInputPos = NodeEditor.ScreenToCanvasSpace(inputInfo.inputPos); if (CheckBorderSelection(state, focusedGroup.bodyRect, canvasInputPos, out NodeGroup.resizeDir)) { // Resizing state.activeGroup = focusedGroup; // Get start drag position Vector2 startSizePos = Vector2.zero; if ((NodeGroup.resizeDir & BorderSelection.Left) != 0) { startSizePos.x = focusedGroup.rect.xMin; } else if ((NodeGroup.resizeDir & BorderSelection.Right) != 0) { startSizePos.x = focusedGroup.rect.xMax; } if ((NodeGroup.resizeDir & BorderSelection.Top) != 0) { startSizePos.y = focusedGroup.rect.yMin; } else if ((NodeGroup.resizeDir & BorderSelection.Bottom) != 0) { startSizePos.y = focusedGroup.rect.yMax; } // Start the resize drag state.StartDrag("group", inputInfo.inputPos, startSizePos); state.resizeGroup = true; inputInfo.inputEvent.Use(); } else if (focusedGroup.headerRect.Contains(canvasInputPos)) { // Dragging state.activeGroup = focusedGroup; state.StartDrag("group", inputInfo.inputPos, state.activeGroup.rect.position); state.activeGroup.UpdatePins(); inputInfo.inputEvent.Use(); } } } }
[EventHandlerAttribute(EventType.MouseDown, -1)] // Before the other context clicks because they won't account for groups private static void HandleGroupContextClick(NodeEditorInputInfo inputInfo) { NodeEditorState state = inputInfo.editorState; if (inputInfo.inputEvent.button == 1 && state.focusedNode == null) { // Right-click NOT on a node NodeGroup focusedGroup = HeaderAtPosition(state, NodeEditor.ScreenToCanvasSpace(inputInfo.inputPos)); if (focusedGroup != null) { // Context click for the group. This is static, not dynamic, because it would be useless GenericMenu context = new GenericMenu(); context.AddItem(new GUIContent("Delete"), false, () => { NodeEditor.curNodeCanvas = state.canvas; focusedGroup.Delete(); }); context.ShowAsContext(); inputInfo.inputEvent.Use(); } } }
/// <summary> /// Gets a NodeGroup under the mouse. If multiple groups are adressed, the group lowest in the pin hierarchy is returned. /// </summary> private static NodeGroup GroupAtPosition(NodeEditorState state, Vector2 canvasPos) { if (NodeEditorInputSystem.shouldIgnoreInput(state)) { return(null); } for (int i = state.canvas.groups.Count - 1; i >= 0; i--) { NodeGroup group = state.canvas.groups[i]; if (group.headerRect.Contains(canvasPos) || group.rect.Contains(canvasPos)) { return(group); } } return(null); }
[EventHandlerAttribute(EventType.MouseDown, 20)] // Priority over hundred to make it call after the GUI private static void HandleWindowSelectionStart(NodeEditorInputInfo inputInfo) { if (GUIUtility.hotControl > 0) { return; // GUI has control } NodeEditorState state = inputInfo.editorState; if (inputInfo.inputEvent.button == 0 && state.focusedNode == null && NodeGroup.HeaderAtPosition(state, NodeEditor.ScreenToCanvasSpace(inputInfo.inputPos)) == null) { // Left clicked on the empty canvas -> Start the selection process state.boxSelecting = true; startSelectionPos = inputInfo.inputPos; } }
/// <summary> /// Gets a NodeGroup which has it's header under the mouse. If multiple groups are adressed, the smallest is returned. /// </summary> private static NodeGroup HeaderAtPosition(NodeEditorState state, Vector2 canvasPos) { if (NodeEditorInputSystem.shouldIgnoreInput(state)) { return(null); } NodeCanvas canvas = state.canvas; for (int groupCnt = canvas.groups.Count - 1; groupCnt >= 0; groupCnt--) { NodeGroup group = canvas.groups[groupCnt]; if (group.headerRect.Contains(canvasPos)) { return(group); } } return(null); }
/// <summary> /// Gets a NodeGroup under the mouse that requires input (header or border). If multiple groups are adressed, the group lowest in the pin hierarchy is returned. /// 获取鼠标点击的需要执行操作的Group(Header或Border),如果找到了多个group,则只处理第一个group /// </summary> private static NodeGroup GroupAtPositionInput(NodeEditorState state, Vector2 canvasPos) { if (NodeEditorInputSystem.shouldIgnoreInput(state)) { return(null); } for (int i = state.canvas.groups.Count - 1; i >= 0; i--) { NodeGroup group = state.canvas.groups[i]; BorderSelection border; if (group.headerRect.Contains(canvasPos) || CheckBorderSelection(state, group.bodyRect, canvasPos, out border)) { return(group); } } return(null); }
/// <summary> /// Recursively adds the pinned nodes and subGroups to the active group's pinned nodes and groups /// </summary> private void AddPins() { for (int groupCnt = NodeEditor.curNodeCanvas.groups.IndexOf(this); groupCnt < NodeEditor.curNodeCanvas.groups.Count; groupCnt++) { // Get all pinned groups -> all groups atleast half in the group NodeGroup group = NodeEditor.curNodeCanvas.groups[groupCnt]; if (rect.Contains(group.rect.center) && group != this && !pinnedGroups.Contains(group)) { pinnedGroups.Add(group); group.AddPins(); } } foreach (Node node in NodeEditor.curNodeCanvas.nodes) { // Get all pinned nodes -> all nodes atleast half in the group if (rect.Contains(node.rect.center) && !pinnedNodes.Contains(node)) { pinnedNodes.Add(node); } } }
/// <summary> /// Draws the Node Canvas on the screen in the rect specified by editorState without one-time wrappers like GUISkin and OverlayGUI. Made for nested Canvases (WIP) /// </summary> private static void DrawSubCanvas(NodeCanvas nodeCanvas, NodeEditorState editorState) { if (!editorState.drawing) { return; } BeginEditingCanvas(nodeCanvas, editorState); if (curNodeCanvas == null || curEditorState == null || !curEditorState.drawing) { return; } if (Event.current.type == EventType.Repaint) { // Draw Background when Repainting // Offset from origin in tile units Vector2 tileOffset = new Vector2(-(curEditorState.zoomPos.x * curEditorState.zoom + curEditorState.panOffset.x) / NodeEditorGUI.Background.width, ((curEditorState.zoomPos.y - curEditorState.canvasRect.height) * curEditorState.zoom + curEditorState.panOffset.y) / NodeEditorGUI.Background.height); // Amount of tiles Vector2 tileAmount = new Vector2(Mathf.Round(curEditorState.canvasRect.width * curEditorState.zoom) / NodeEditorGUI.Background.width, Mathf.Round(curEditorState.canvasRect.height * curEditorState.zoom) / NodeEditorGUI.Background.height); // Draw tiled background GUI.DrawTextureWithTexCoords(curEditorState.canvasRect, NodeEditorGUI.Background, new Rect(tileOffset, tileAmount)); } // Handle input events NodeEditorInputSystem.HandleInputEvents(curEditorState); if (Event.current.type != EventType.Layout) { curEditorState.ignoreInput = new List <Rect> (); } // We're using a custom scale method, as default one is messing up clipping rect Rect canvasRect = curEditorState.canvasRect; curEditorState.zoomPanAdjust = GUIScaleUtility.BeginScale(ref canvasRect, curEditorState.zoomPos, curEditorState.zoom, NodeEditorGUI.isEditorWindow, false); // ---- BEGIN SCALE ---- // Some features which require zoomed drawing: if (curEditorState.navigate) { // Draw a curve to the origin/active node for orientation purposes Vector2 startPos = (curEditorState.selectedNode != null? curEditorState.selectedNode.rect.center : curEditorState.panOffset) + curEditorState.zoomPanAdjust; Vector2 endPos = Event.current.mousePosition; RTEditorGUI.DrawLine(startPos, endPos, Color.green, null, 3); RepaintClients(); } if (curEditorState.connectKnob != null) { // Draw the currently drawn connection curEditorState.connectKnob.DrawConnection(Event.current.mousePosition); RepaintClients(); } // Draw the groups below everything else for (int groupCnt = 0; groupCnt < curNodeCanvas.groups.Count; groupCnt++) { NodeGroup group = curNodeCanvas.groups[groupCnt]; if (Event.current.type == EventType.Layout) { group.isClipped = !curEditorState.canvasViewport.Overlaps(group.fullAABBRect); } if (!group.isClipped) { group.DrawGroup(); } } // Push the active node to the top of the draw order. if (Event.current.type == EventType.Layout && curEditorState.selectedNode != null) { curNodeCanvas.nodes.Remove(curEditorState.selectedNode); curNodeCanvas.nodes.Add(curEditorState.selectedNode); } // Draw the transitions and connections. Has to be drawn before nodes as transitions originate from node centers for (int nodeCnt = 0; nodeCnt < curNodeCanvas.nodes.Count; nodeCnt++) { curNodeCanvas.nodes [nodeCnt].DrawConnections(); } // Draw the nodes for (int nodeCnt = 0; nodeCnt < curNodeCanvas.nodes.Count; nodeCnt++) { Node node = curNodeCanvas.nodes [nodeCnt]; if (Event.current.type == EventType.Layout) { node.isClipped = !curEditorState.canvasViewport.Overlaps(curNodeCanvas.nodes[nodeCnt].fullAABBRect); } if (!node.isClipped) { node.DrawNode(); if (Event.current.type == EventType.Repaint) { node.DrawKnobs(); } } } // ---- END SCALE ---- // End scaling group GUIScaleUtility.EndScale(); // Handle input events with less priority than node GUI controls NodeEditorInputSystem.HandleLateInputEvents(curEditorState); EndEditingCanvas(); }
/// <summary> /// Draws the Node Canvas on the screen in the rect specified by editorState without one-time wrappers like GUISkin and OverlayGUI. Made for nested Canvases (WIP) /// </summary> private static void DrawSubCanvas(NodeCanvas nodeCanvas, NodeEditorState editorState) { if (!editorState.drawing) { return; } BeginEditingCanvas(nodeCanvas, editorState); if (curNodeCanvas == null || curEditorState == null || !curEditorState.drawing) { return; } if (Event.current.type == EventType.Repaint) { //绘制NodeEditor背景 EditorGUI.DrawRect(curEditorState.canvasRect, new Color(28 / 255f, 28 / 255f, 28 / 255f)); } // Handle input events NodeEditorInputSystem.HandleInputEvents(curEditorState); HandleMultipleSelect(); if (Event.current.type != EventType.Layout) { curEditorState.ignoreInput = new List <Rect>(); } // We're using a custom scale method, as default one is messing up clipping rect Rect canvasRect = curEditorState.canvasRect; curEditorState.zoomPanAdjust = GUIScaleUtility.BeginScale(ref canvasRect, curEditorState.zoomPos, curEditorState.zoom, NodeEditorGUI.isEditorWindow, false); // 开始绘制缩放区域 if (curEditorState.connectKnob != null) { // Draw the currently drawn connection curEditorState.connectKnob.DrawConnection(Event.current.mousePosition); RepaintClients(); } // Draw the groups below everything else for (int groupCnt = 0; groupCnt < curNodeCanvas.groups.Count; groupCnt++) { NodeGroup group = curNodeCanvas.groups[groupCnt]; if (Event.current.type == EventType.Layout) { group.isClipped = !curEditorState.canvasViewport.Overlaps(group.fullAABBRect); } if (!group.isClipped) { group.DrawGroup(); } } // Draw the transitions and connections. Has to be drawn before nodes as transitions originate from node centers for (int nodeCnt = 0; nodeCnt < curNodeCanvas.nodes.Count; nodeCnt++) { curNodeCanvas.nodes[nodeCnt].DrawConnections(); } // Draw the nodes for (int nodeCnt = 0; nodeCnt < curNodeCanvas.nodes.Count; nodeCnt++) { Node node = curNodeCanvas.nodes[nodeCnt]; if (Event.current.type == EventType.Layout) { node.isClipped = !curEditorState.canvasViewport.Overlaps(curNodeCanvas.nodes[nodeCnt].fullAABBRect); } if (node.isClipped) { continue; } node.DrawNode(); if (Event.current.type == EventType.Repaint) { node.DrawKnobs(); } } // ---- END SCALE ---- // End scaling group GUIScaleUtility.EndScale(); // Handle input events with less priority than node GUI controls NodeEditorInputSystem.HandleLateInputEvents(curEditorState); EndEditingCanvas(); }
/// <summary> /// Validates this canvas, checking for any broken nodes or references and cleans them. /// </summary> public void Validate(bool deepValidate = false) { if (nodes == null) { Debug.LogWarning("NodeCanvas '" + name + "' nodes were erased and set to null! Automatically fixed!"); nodes = new List <Node> (); } if (deepValidate) { for (int groupCnt = 0; groupCnt < groups.Count; groupCnt++) { NodeGroup group = groups[groupCnt]; if (group == null) { Debug.LogWarning("NodeCanvas '" + name + "' contained broken (null) group! Automatically fixed!"); groups.RemoveAt(groupCnt); groupCnt--; continue; } } for (int nodeCnt = 0; nodeCnt < nodes.Count; nodeCnt++) { Node node = nodes[nodeCnt]; if (node == null) { Debug.LogWarning("NodeCanvas '" + saveName + "' (" + name + ") contained broken (null) node! Automatically fixed!"); nodes.RemoveAt(nodeCnt); nodeCnt--; continue; } for (int knobCnt = 0; knobCnt < node.nodeKnobs.Count; knobCnt++) { NodeKnob nodeKnob = node.nodeKnobs[knobCnt]; if (nodeKnob == null) { Debug.LogWarning("NodeCanvas '" + name + "' Node '" + node.name + "' contained broken (null) NodeKnobs! Automatically fixed!"); nodes.RemoveAt(nodeCnt); nodeCnt--; break; // node.nodeKnobs.RemoveAt (knobCnt); // knobCnt--; // continue; } if (nodeKnob is NodeInput) { NodeInput input = nodeKnob as NodeInput; if (input.connection != null && input.connection.body == null) { // References broken node; Clear connection input.connection = null; } // for (int conCnt = 0; conCnt < (nodeKnob as NodeInput).connection.Count; conCnt++) } else if (nodeKnob is NodeOutput) { NodeOutput output = nodeKnob as NodeOutput; for (int conCnt = 0; conCnt < output.connections.Count; conCnt++) { NodeInput con = output.connections[conCnt]; if (con == null || con.body == null) { // Broken connection; Clear connection output.connections.RemoveAt(conCnt); conCnt--; } } } } } if (editorStates == null) { Debug.LogWarning("NodeCanvas '" + name + "' editorStates were erased! Automatically fixed!"); editorStates = new NodeEditorState[0]; } editorStates = editorStates.Where((NodeEditorState state) => state != null).ToArray(); foreach (NodeEditorState state in editorStates) { if (!nodes.Contains(state.selectedNode)) { state.selectedNode = null; } } } OnValidate(); }