diff --git a/public/litegraph.d.ts b/public/litegraph.d.ts index 2595c8957..8adcd23a5 100644 --- a/public/litegraph.d.ts +++ b/public/litegraph.d.ts @@ -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; 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 { diff --git a/src/litegraph.js b/src/litegraph.js index 29f000070..24923d59c 100755 --- a/src/litegraph.js +++ b/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() } }); }