mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
Use Litegraph snap to grid (#1572)
Leaves only settings code in the extension
This commit is contained in:
@@ -1,23 +1,5 @@
|
|||||||
// @ts-strict-ignore
|
|
||||||
import type { SettingParams } from '@/types/settingTypes'
|
|
||||||
import { app } from '../../scripts/app'
|
import { app } from '../../scripts/app'
|
||||||
import {
|
import { LiteGraph } from '@comfyorg/litegraph'
|
||||||
LGraphCanvas,
|
|
||||||
LGraphNode,
|
|
||||||
LGraphGroup,
|
|
||||||
LiteGraph
|
|
||||||
} from '@comfyorg/litegraph'
|
|
||||||
|
|
||||||
// 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({
|
app.registerExtension({
|
||||||
name: 'Comfy.SnapToGrid',
|
name: 'Comfy.SnapToGrid',
|
||||||
@@ -39,164 +21,20 @@ app.registerExtension({
|
|||||||
LiteGraph.CANVAS_GRID_SIZE = +value || 10
|
LiteGraph.CANVAS_GRID_SIZE = +value || 10
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Keep the 'pysssss.SnapToGrid' setting id so we don't need to migrate setting values.
|
// Keep the 'pysssss.SnapToGrid' setting id so we don't need to migrate setting values.
|
||||||
// Using a new setting id can cause existing users to lose their existing settings.
|
// Using a new setting id can cause existing users to lose their existing settings.
|
||||||
const alwaysSnapToGrid = app.ui.settings.addSetting({
|
app.ui.settings.addSetting({
|
||||||
id: 'pysssss.SnapToGrid',
|
id: 'pysssss.SnapToGrid',
|
||||||
category: ['LiteGraph', 'Canvas', 'AlwaysSnapToGrid'],
|
category: ['LiteGraph', 'Canvas', 'AlwaysSnapToGrid'],
|
||||||
name: 'Always snap to grid',
|
name: 'Always snap to grid',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
versionAdded: '1.3.13'
|
versionAdded: '1.3.13',
|
||||||
} as SettingParams)
|
versionModified: '1.3.42',
|
||||||
|
onChange(value) {
|
||||||
const shouldSnapToGrid = () => app.shiftDown || alwaysSnapToGrid.value
|
app.graph.config.alwaysSnapToGrid = !!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 (shouldSnapToGrid()) {
|
|
||||||
// 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 (shouldSnapToGrid()) {
|
|
||||||
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 (
|
|
||||||
shouldSnapToGrid() &&
|
|
||||||
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]
|
|
||||||
const 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: LGraphGroup | null = 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 equivalent to calling an
|
|
||||||
// `LGraphGroup.prototype.onNodeMoved` if it had existed.
|
|
||||||
if (app.canvas.last_mouse_dragging === false && shouldSnapToGrid()) {
|
|
||||||
// After moving a group (while shouldSnapToGrid()), 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 && shouldSnapToGrid()) {
|
|
||||||
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 as [number, number]))
|
|
||||||
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 (shouldSnapToGrid()) {
|
|
||||||
const lastGroup = app.graph.groups[app.graph.groups.length - 1]
|
|
||||||
if (lastGroup) {
|
|
||||||
roundVectorToGrid(lastGroup.pos)
|
|
||||||
roundVectorToGrid(lastGroup.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user