mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 06:47:33 +00:00
Support group pinning and show pin icon on pinned node & group (#117)
* Add locking mechanism * Add group locking * Lock move/resize * Move group menu options * Rename locked to pinned * Disable resize on pinned nodes * nit
This commit is contained in:
10
public/litegraph.d.ts
vendored
10
public/litegraph.d.ts
vendored
@@ -966,6 +966,8 @@ export declare class LGraphNode {
|
||||
captureInput(v: any): void;
|
||||
/** Collapse the node to make it smaller on the canvas */
|
||||
collapse(force: boolean): void;
|
||||
|
||||
get pinned(): boolean;
|
||||
/** Forces the node to do not move or realign on Z */
|
||||
pin(v?: boolean): void;
|
||||
localToScreen(x: number, y: number, graphCanvas: LGraphCanvas): Vector2;
|
||||
@@ -1138,17 +1140,25 @@ export declare class LGraphGroup {
|
||||
size: Vector2;
|
||||
pos: Vector2;
|
||||
font_size: number;
|
||||
flags: Record<string, boolean>;
|
||||
|
||||
get titleHeight(): number;
|
||||
get selected(): boolean;
|
||||
|
||||
// Pinned group cannot be selected.
|
||||
get pinned(): boolean;
|
||||
pin(): void;
|
||||
unpin(): void;
|
||||
|
||||
configure(o: SerializedLGraphGroup): void;
|
||||
serialize(): SerializedLGraphGroup;
|
||||
resize(width: number, height: number): void;
|
||||
move(deltaX: number, deltaY: number, ignoreNodes?: boolean): void;
|
||||
recomputeInsideNodes(): void;
|
||||
isPointInside: LGraphNode["isPointInside"];
|
||||
setDirtyCanvas: LGraphNode["setDirtyCanvas"];
|
||||
addNodes(nodes: LGraphNode[], padding?: number): void;
|
||||
getMenuOptions(): ContextMenuItem[];
|
||||
}
|
||||
|
||||
export declare class DragAndScale {
|
||||
|
||||
124
src/litegraph.js
124
src/litegraph.js
@@ -1636,17 +1636,12 @@ const globalExport = {};
|
||||
* @method getGroupOnPos
|
||||
* @param {number} x the x coordinate in canvas space
|
||||
* @param {number} y the y coordinate in canvas space
|
||||
* @return {LGraphGroup} the group or null
|
||||
* @return {LGraphGroup | null} the group or null
|
||||
*/
|
||||
getGroupOnPos(x, y) {
|
||||
for (var i = this._groups.length - 1; i >= 0; i--) {
|
||||
var g = this._groups[i];
|
||||
if (g.isPointInside(x, y, 2, true)) {
|
||||
return g;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
getGroupOnPos(x, y, {margin = 2} = {}) {
|
||||
return this._groups.reverse().find(g => g.isPointInside(x, y, margin));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution
|
||||
* this replaces the ones using the old version with the new version
|
||||
@@ -4777,6 +4772,10 @@ const globalExport = {};
|
||||
this.setDirtyCanvas(true, true);
|
||||
}
|
||||
|
||||
get pinned() {
|
||||
return !!this.flags.pinned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the node to do not move or realign on Z
|
||||
* @method pin
|
||||
@@ -4788,6 +4787,12 @@ const globalExport = {};
|
||||
} else {
|
||||
this.flags.pinned = v;
|
||||
}
|
||||
this.resizable = !this.pinned;
|
||||
// Delete the flag if unpinned, so that we don't get unnecessary
|
||||
// flags.pinned = false in serialized object.
|
||||
if (!this.pinned) {
|
||||
delete this.flags.pinned;
|
||||
}
|
||||
}
|
||||
|
||||
localToScreen(x, y, graphcanvas) {
|
||||
@@ -4817,6 +4822,7 @@ const globalExport = {};
|
||||
this._size = this._bounding.subarray(2, 4);
|
||||
this._nodes = [];
|
||||
this.graph = null;
|
||||
this.flags = {};
|
||||
|
||||
Object.defineProperty(this, "pos", {
|
||||
set: function (v) {
|
||||
@@ -4855,10 +4861,23 @@ const globalExport = {};
|
||||
return !!this.graph?.list_of_graphcanvas?.some(c => c.selected_group === this);
|
||||
}
|
||||
|
||||
get pinned() {
|
||||
return !!this.flags.pinned;
|
||||
}
|
||||
|
||||
pin() {
|
||||
this.flags.pinned = true;
|
||||
}
|
||||
|
||||
unpin() {
|
||||
delete this.flags.pinned;
|
||||
}
|
||||
|
||||
configure(o) {
|
||||
this.title = o.title;
|
||||
this._bounding.set(o.bounding);
|
||||
this.color = o.color;
|
||||
this.flags = o.flags || this.flags;
|
||||
if (o.font_size) {
|
||||
this.font_size = o.font_size;
|
||||
}
|
||||
@@ -4875,7 +4894,8 @@ const globalExport = {};
|
||||
Math.round(b[3])
|
||||
],
|
||||
color: this.color,
|
||||
font_size: this.font_size
|
||||
font_size: this.font_size,
|
||||
flags: this.flags,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4885,6 +4905,8 @@ const globalExport = {};
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
*/
|
||||
draw(graphCanvas, ctx) {
|
||||
const padding = 4;
|
||||
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.strokeStyle = this.color;
|
||||
const [x, y] = this._pos;
|
||||
@@ -4905,7 +4927,7 @@ const globalExport = {};
|
||||
const font_size = this.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;
|
||||
ctx.font = font_size + "px Arial";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(this.title, x + 4, y + font_size);
|
||||
ctx.fillText(this.title, x + padding, y + font_size);
|
||||
|
||||
if (LiteGraph.highlight_selected_group && this.selected) {
|
||||
graphCanvas.drawSelectionBounding(ctx, this._bounding, {
|
||||
@@ -4913,12 +4935,29 @@ const globalExport = {};
|
||||
title_height: this.titleHeight,
|
||||
title_mode: LiteGraph.NORMAL_TITLE,
|
||||
fgcolor: this.color,
|
||||
padding: 4
|
||||
padding,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.pinned) {
|
||||
const iconFontSize = font_size * 0.5;
|
||||
ctx.font = iconFontSize + "px Arial";
|
||||
ctx.fillText("📌", x + width - iconFontSize - padding, y + iconFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
resize(width, height) {
|
||||
if (this.pinned) {
|
||||
return;
|
||||
}
|
||||
this._size[0] = width;
|
||||
this._size[1] = height;
|
||||
}
|
||||
|
||||
move(deltax, deltay, ignore_nodes) {
|
||||
if (this.pinned) {
|
||||
return;
|
||||
}
|
||||
this._pos[0] += deltax;
|
||||
this._pos[1] += deltay;
|
||||
if (ignore_nodes) {
|
||||
@@ -4985,6 +5024,33 @@ const globalExport = {};
|
||||
bounds.bottom - bounds.top + padding * 2 + this.titleHeight
|
||||
];
|
||||
}
|
||||
|
||||
getMenuOptions() {
|
||||
return [
|
||||
{
|
||||
content: this.pinned ? "Unpin" : "Pin",
|
||||
callback: () => {
|
||||
this.pinned ? this.unpin() : this.pin();
|
||||
this.setDirtyCanvas(false, true);
|
||||
},
|
||||
},
|
||||
null,
|
||||
{ content: "Title", callback: LGraphCanvas.onShowPropertyEditor },
|
||||
{
|
||||
content: "Color",
|
||||
has_submenu: true,
|
||||
callback: LGraphCanvas.onMenuNodeColors
|
||||
},
|
||||
{
|
||||
content: "Font size",
|
||||
property: "font_size",
|
||||
type: "Number",
|
||||
callback: LGraphCanvas.onShowPropertyEditor
|
||||
},
|
||||
null,
|
||||
{ content: "Remove", callback: LGraphCanvas.onMenuNodeRemove }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside;
|
||||
@@ -6992,7 +7058,7 @@ const globalExport = {};
|
||||
//it wasn't clicked on the links boxes
|
||||
if (!skip_action) {
|
||||
var block_drag_node = false;
|
||||
if (node && node.flags && node.flags.pinned) {
|
||||
if (node?.pinned) {
|
||||
block_drag_node = true;
|
||||
}
|
||||
var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]];
|
||||
@@ -7323,10 +7389,10 @@ const globalExport = {};
|
||||
else if (this.selected_group && !this.read_only) {
|
||||
//moving/resizing a group
|
||||
if (this.selected_group_resizing) {
|
||||
this.selected_group.size = [
|
||||
this.selected_group.resize(
|
||||
e.canvasX - this.selected_group.pos[0],
|
||||
e.canvasY - this.selected_group.pos[1]
|
||||
];
|
||||
);
|
||||
} else {
|
||||
var deltax = delta[0] / this.ds.scale;
|
||||
var deltay = delta[1] / this.ds.scale;
|
||||
@@ -10040,6 +10106,10 @@ const globalExport = {};
|
||||
);
|
||||
}
|
||||
|
||||
if (node.pinned) {
|
||||
ctx.fillText("📌", node.getBounding()[2] - 20, -10);
|
||||
}
|
||||
|
||||
// these counter helps in conditioning drawing based on if the node has been executed or an action occurred
|
||||
if (node.execute_triggered > 0) node.execute_triggered--;
|
||||
if (node.action_triggered > 0) node.action_triggered--;
|
||||
@@ -13064,7 +13134,7 @@ const globalExport = {};
|
||||
callback: LGraphCanvas.onMenuNodeCollapse
|
||||
},
|
||||
{
|
||||
content: node.flags?.pinned ? "Unpin" : "Pin",
|
||||
content: node.pinned ? "Unpin" : "Pin",
|
||||
callback: LGraphCanvas.onMenuNodePin
|
||||
},
|
||||
{
|
||||
@@ -13137,24 +13207,8 @@ const globalExport = {};
|
||||
return options;
|
||||
}
|
||||
getGroupMenuOptions(node) {
|
||||
var o = [
|
||||
{ content: "Title", callback: LGraphCanvas.onShowPropertyEditor },
|
||||
{
|
||||
content: "Color",
|
||||
has_submenu: true,
|
||||
callback: LGraphCanvas.onMenuNodeColors
|
||||
},
|
||||
{
|
||||
content: "Font size",
|
||||
property: "font_size",
|
||||
type: "Number",
|
||||
callback: LGraphCanvas.onShowPropertyEditor
|
||||
},
|
||||
null,
|
||||
{ content: "Remove", callback: LGraphCanvas.onMenuNodeRemove }
|
||||
];
|
||||
|
||||
return o;
|
||||
console.warn("LGraphCanvas.getGroupMenuOptions is deprecated, use LGraphGroup.getMenuOptions instead");
|
||||
return node.getMenuOptions();
|
||||
}
|
||||
processContextMenu(node, event) {
|
||||
var that = this;
|
||||
@@ -13229,7 +13283,7 @@ const globalExport = {};
|
||||
submenu: {
|
||||
title: "Group",
|
||||
extra: group,
|
||||
options: this.getGroupMenuOptions(group)
|
||||
options: group.getMenuOptions()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user