flatcherlee's picture
Upload 2334 files
3d5837a verified
import { app } from "../../../scripts/app.js";
import { $el } from "../../../scripts/ui.js";
let setting, guide_setting, guide_config;
const id = "pysssss.SnapToGrid";
const guide_id = id + ".Guide";
const guide_config_default = {
lines: {
enabled: false,
fillStyle: "rgba(255, 0, 0, 0.5)",
},
block: {
enabled: false,
fillStyle: "rgba(0, 0, 255, 0.5)",
},
}
/** Wraps the provided function call to set/reset shiftDown when setting is enabled. */
function wrapCallInSettingCheck(fn) {
if (setting?.value) {
const shift = app.shiftDown;
app.shiftDown = true;
const r = fn();
app.shiftDown = shift;
return r;
}
return fn();
}
const ext = {
name: id,
init() {
if (localStorage.getItem(guide_id) === null) {
localStorage.setItem(guide_id, JSON.stringify(guide_config_default));
}
guide_config = JSON.parse(localStorage.getItem(guide_id));
setting = app.ui.settings.addSetting({
id,
name: "🐍 Always snap to grid",
defaultValue: false,
type: "boolean",
onChange(value) {
app.canvas.align_to_grid = value;
},
});
guide_setting = app.ui.settings.addSetting({
id: id + ".Guide",
name: "🐍 Display drag-and-drop guides",
type: (name, setter, value) => {
return $el("tr", [
$el("td", [
$el("label", {
for: id.replaceAll(".", "-"),
textContent: name,
}),
]),
$el("td", [
$el(
"label",
{
textContent: "Lines: ",
style: {
display: "inline-block",
},
},
[
$el("input", {
id: id.replaceAll(".", "-") + "-line-text",
type: "text",
value: guide_config.lines.fillStyle,
onchange: (event) => {
guide_config.lines.fillStyle = event.target.value;
localStorage.setItem(guide_id, JSON.stringify(guide_config));
}
}),
$el("input", {
id: id.replaceAll(".", "-") + "-line-checkbox",
type: "checkbox",
checked: guide_config.lines.enabled,
onchange: (event) => {
guide_config.lines.enabled = !!event.target.checked;
localStorage.setItem(guide_id, JSON.stringify(guide_config));
},
}),
]
),
$el(
"label",
{
textContent: "Block: ",
style: {
display: "inline-block",
},
},
[
$el("input", {
id: id.replaceAll(".", "-") + "-block-text",
type: "text",
value: guide_config.block.fillStyle,
onchange: (event) => {
guide_config.block.fillStyle = event.target.value;
localStorage.setItem(guide_id, JSON.stringify(guide_config));
}
}),
$el("input", {
id: id.replaceAll(".", "-") + '-block-checkbox',
type: "checkbox",
checked: guide_config.block.enabled,
onchange: (event) => {
guide_config.block.enabled = !!event.target.checked;
localStorage.setItem(guide_id, JSON.stringify(guide_config));
},
}),
]
),
]),
]);
}
});
// We need to register our hooks after the core snap to grid extension runs
// Do this from the graph configure function so we still get onNodeAdded calls
const configure = LGraph.prototype.configure;
LGraph.prototype.configure = function () {
// Override drawNode to draw the drop position
const drawNode = LGraphCanvas.prototype.drawNode;
LGraphCanvas.prototype.drawNode = function () {
wrapCallInSettingCheck(() => drawNode.apply(this, arguments));
};
// Override node added to add a resize handler to force grid alignment
const onNodeAdded = app.graph.onNodeAdded;
app.graph.onNodeAdded = function (node) {
const r = onNodeAdded?.apply(this, arguments);
const onResize = node.onResize;
node.onResize = function () {
wrapCallInSettingCheck(() => onResize?.apply(this, arguments));
};
return r;
};
const groupMove = LGraphGroup.prototype.move;
LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) {
wrapCallInSettingCheck(() => groupMove.apply(this, arguments));
}
const canvasDrawGroups = LGraphCanvas.prototype.drawGroups;
LGraphCanvas.prototype.drawGroups = function (canvas, ctx) {
wrapCallInSettingCheck(() => canvasDrawGroups.apply(this, arguments));
}
const canvasOnGroupAdd = LGraphCanvas.onGroupAdd;
LGraphCanvas.onGroupAdd = function() {
wrapCallInSettingCheck(() => canvasOnGroupAdd.apply(this, arguments));
}
return configure.apply(this, arguments);
};
// Override drag-and-drop behavior to show orthogonal guide lines around selected node(s) and preview of where the node(s) will be placed
const origDrawNode = LGraphCanvas.prototype.drawNode
LGraphCanvas.prototype.drawNode = function (node, ctx) {
const enabled = guide_config.lines.enabled || guide_config.block.enabled;
if (enabled && app.shiftDown && this.node_dragged && node.id in this.selected_nodes) {
// discretize the canvas into grid
let x = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.pos[0] / LiteGraph.CANVAS_GRID_SIZE);
let y = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.pos[1] / LiteGraph.CANVAS_GRID_SIZE);
// calculate the width and height of the node
// (also need to shift the y position of the node, depending on whether the title is visible)
x -= node.pos[0];
y -= node.pos[1];
let w, h;
if (node.flags.collapsed) {
w = node._collapsed_width;
h = LiteGraph.NODE_TITLE_HEIGHT;
y -= 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;
y -= LiteGraph.NODE_TITLE_HEIGHT;
}
}
// save the original fill style
const f = ctx.fillStyle;
// draw preview for drag-and-drop (rectangle to show where the node will be placed)
if (guide_config.block.enabled) {
ctx.fillStyle = guide_config.block.fillStyle;
ctx.fillRect(x, y, w, h);
}
// add guide lines around node (arbitrarily long enough to span most workflows)
if (guide_config.lines.enabled) {
const xd = 10000;
const yd = 10000;
const thickness = 3;
ctx.fillStyle = guide_config.lines.fillStyle;
ctx.fillRect(x - xd, y, 2*xd, thickness);
ctx.fillRect(x, y - yd, thickness, 2*yd);
ctx.fillRect(x - xd, y + h, 2*xd, thickness);
ctx.fillRect(x + w, y - yd, thickness, 2*yd);
}
// restore the original fill style
ctx.fillStyle = f;
}
return origDrawNode.apply(this, arguments);
};
},
};
app.registerExtension(ext);