Spaces:
Runtime error
Runtime error
| import { app } from "../../scripts/app.js"; | |
| // Shift + drag/resize to snap to grid | |
| /** Rounds a Vector2 in-place to the current CANVAS_GRID_SIZE. */ | |
| function roundVectorToGrid(vec) { | |
| vec[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(vec[0] / LiteGraph.CANVAS_GRID_SIZE); | |
| vec[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(vec[1] / LiteGraph.CANVAS_GRID_SIZE); | |
| return vec; | |
| } | |
| app.registerExtension({ | |
| name: "Comfy.SnapToGrid", | |
| init() { | |
| // Add setting to control grid size | |
| app.ui.settings.addSetting({ | |
| id: "Comfy.SnapToGrid.GridSize", | |
| name: "Grid Size", | |
| type: "slider", | |
| attrs: { | |
| min: 1, | |
| max: 500, | |
| }, | |
| tooltip: | |
| "When dragging and resizing nodes while holding shift they will be aligned to the grid, this controls the size of that grid.", | |
| defaultValue: LiteGraph.CANVAS_GRID_SIZE, | |
| onChange(value) { | |
| LiteGraph.CANVAS_GRID_SIZE = +value; | |
| }, | |
| }); | |
| // After moving a node, if the shift key is down align it to grid | |
| const onNodeMoved = app.canvas.onNodeMoved; | |
| app.canvas.onNodeMoved = function (node) { | |
| const r = onNodeMoved?.apply(this, arguments); | |
| if (app.shiftDown) { | |
| // Ensure all selected nodes are realigned | |
| for (const id in this.selected_nodes) { | |
| this.selected_nodes[id].alignToGrid(); | |
| } | |
| } | |
| return r; | |
| }; | |
| // When a node is added, add a resize handler to it so we can fix align the size with the grid | |
| const onNodeAdded = app.graph.onNodeAdded; | |
| app.graph.onNodeAdded = function (node) { | |
| const onResize = node.onResize; | |
| node.onResize = function () { | |
| if (app.shiftDown) { | |
| roundVectorToGrid(node.size); | |
| } | |
| return onResize?.apply(this, arguments); | |
| }; | |
| return onNodeAdded?.apply(this, arguments); | |
| }; | |
| // Draw a preview of where the node will go if holding shift and the node is selected | |
| const origDrawNode = LGraphCanvas.prototype.drawNode; | |
| LGraphCanvas.prototype.drawNode = function (node, ctx) { | |
| if (app.shiftDown && this.node_dragged && node.id in this.selected_nodes) { | |
| const [x, y] = roundVectorToGrid([...node.pos]); | |
| const shiftX = x - node.pos[0]; | |
| let shiftY = y - node.pos[1]; | |
| let w, h; | |
| if (node.flags.collapsed) { | |
| w = node._collapsed_width; | |
| h = LiteGraph.NODE_TITLE_HEIGHT; | |
| shiftY -= LiteGraph.NODE_TITLE_HEIGHT; | |
| } else { | |
| w = node.size[0]; | |
| h = node.size[1]; | |
| let titleMode = node.constructor.title_mode; | |
| if (titleMode !== LiteGraph.TRANSPARENT_TITLE && titleMode !== LiteGraph.NO_TITLE) { | |
| h += LiteGraph.NODE_TITLE_HEIGHT; | |
| shiftY -= LiteGraph.NODE_TITLE_HEIGHT; | |
| } | |
| } | |
| const f = ctx.fillStyle; | |
| ctx.fillStyle = "rgba(100, 100, 100, 0.5)"; | |
| ctx.fillRect(shiftX, shiftY, w, h); | |
| ctx.fillStyle = f; | |
| } | |
| return origDrawNode.apply(this, arguments); | |
| }; | |
| /** | |
| * The currently moving, selected group only. Set after the `selected_group` has actually started | |
| * moving. | |
| */ | |
| let selectedAndMovingGroup = null; | |
| /** | |
| * Handles moving a group; tracking when a group has been moved (to show the ghost in `drawGroups` | |
| * below) as well as handle the last move call from LiteGraph's `processMouseUp`. | |
| */ | |
| const groupMove = LGraphGroup.prototype.move; | |
| LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) { | |
| const v = groupMove.apply(this, arguments); | |
| // When we've started moving, set `selectedAndMovingGroup` as LiteGraph sets `selected_group` | |
| // too eagerly and we don't want to behave like we're moving until we get a delta. | |
| if (!selectedAndMovingGroup && app.canvas.selected_group === this && (deltax || deltay)) { | |
| selectedAndMovingGroup = this; | |
| } | |
| // LiteGraph will call group.move both on mouse-move as well as mouse-up though we only want | |
| // to snap on a mouse-up which we can determine by checking if `app.canvas.last_mouse_dragging` | |
| // has been set to `false`. Essentially, this check here is the equivilant to calling an | |
| // `LGraphGroup.prototype.onNodeMoved` if it had existed. | |
| if (app.canvas.last_mouse_dragging === false && app.shiftDown) { | |
| // After moving a group (while app.shiftDown), snap all the child nodes and, finally, | |
| // align the group itself. | |
| this.recomputeInsideNodes(); | |
| for (const node of this._nodes) { | |
| node.alignToGrid(); | |
| } | |
| LGraphNode.prototype.alignToGrid.apply(this); | |
| } | |
| return v; | |
| }; | |
| /** | |
| * Handles drawing a group when, snapping the size when one is actively being resized tracking and/or | |
| * drawing a ghost box when one is actively being moved. This mimics the node snapping behavior for | |
| * both. | |
| */ | |
| const drawGroups = LGraphCanvas.prototype.drawGroups; | |
| LGraphCanvas.prototype.drawGroups = function (canvas, ctx) { | |
| if (this.selected_group && app.shiftDown) { | |
| if (this.selected_group_resizing) { | |
| roundVectorToGrid(this.selected_group.size); | |
| } else if (selectedAndMovingGroup) { | |
| const [x, y] = roundVectorToGrid([...selectedAndMovingGroup.pos]); | |
| const f = ctx.fillStyle; | |
| const s = ctx.strokeStyle; | |
| ctx.fillStyle = "rgba(100, 100, 100, 0.33)"; | |
| ctx.strokeStyle = "rgba(100, 100, 100, 0.66)"; | |
| ctx.rect(x, y, ...selectedAndMovingGroup.size); | |
| ctx.fill(); | |
| ctx.stroke(); | |
| ctx.fillStyle = f; | |
| ctx.strokeStyle = s; | |
| } | |
| } else if (!this.selected_group) { | |
| selectedAndMovingGroup = null; | |
| } | |
| return drawGroups.apply(this, arguments); | |
| }; | |
| /** Handles adding a group in a snapping-enabled state. */ | |
| const onGroupAdd = LGraphCanvas.onGroupAdd; | |
| LGraphCanvas.onGroupAdd = function() { | |
| const v = onGroupAdd.apply(app.canvas, arguments); | |
| if (app.shiftDown) { | |
| const lastGroup = app.graph._groups[app.graph._groups.length - 1]; | |
| if (lastGroup) { | |
| roundVectorToGrid(lastGroup.pos); | |
| roundVectorToGrid(lastGroup.size); | |
| } | |
| } | |
| return v; | |
| }; | |
| }, | |
| }); | |