mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 19:21:54 +00:00
Group update - titlebar, resize, config (#270)
* Backport group header from frontend * nit - TS types & redundant code * Refactor - simplify group resize * Fix group resize can be inverted * Move group resize check to group class * Add config for group padding & default colour * nit - Remove redundant code
This commit is contained in:
@@ -260,6 +260,9 @@ export class LGraphCanvas {
|
|||||||
selected_nodes: Dictionary<LGraphNode> = {}
|
selected_nodes: Dictionary<LGraphNode> = {}
|
||||||
/** All selected nodes, groups, and reroutes */
|
/** All selected nodes, groups, and reroutes */
|
||||||
selectedItems: Set<Positionable> = new Set()
|
selectedItems: Set<Positionable> = new Set()
|
||||||
|
/** The group currently being resized. */
|
||||||
|
resizingGroup: LGraphGroup | null = null
|
||||||
|
/** @deprecated See {@link LGraphCanvas.selectedItems} */
|
||||||
selected_group: LGraphGroup | null = null
|
selected_group: LGraphGroup | null = null
|
||||||
visible_nodes: LGraphNode[] = []
|
visible_nodes: LGraphNode[] = []
|
||||||
node_dragged?: LGraphNode
|
node_dragged?: LGraphNode
|
||||||
@@ -299,6 +302,7 @@ export class LGraphCanvas {
|
|||||||
block_click?: boolean
|
block_click?: boolean
|
||||||
last_click_position?: Point
|
last_click_position?: Point
|
||||||
resizing_node?: LGraphNode
|
resizing_node?: LGraphNode
|
||||||
|
/** @deprecated See {@link LGraphCanvas.resizingGroup} */
|
||||||
selected_group_resizing?: boolean
|
selected_group_resizing?: boolean
|
||||||
last_mouse_dragging: boolean
|
last_mouse_dragging: boolean
|
||||||
onMouseDown: (arg0: CanvasMouseEvent) => void
|
onMouseDown: (arg0: CanvasMouseEvent) => void
|
||||||
@@ -1253,7 +1257,6 @@ export class LGraphCanvas {
|
|||||||
this.dragging_rectangle = null
|
this.dragging_rectangle = null
|
||||||
|
|
||||||
this.selected_nodes = {}
|
this.selected_nodes = {}
|
||||||
/** The group currently being resized */
|
|
||||||
this.selected_group = null
|
this.selected_group = null
|
||||||
|
|
||||||
this.visible_nodes = []
|
this.visible_nodes = []
|
||||||
@@ -1428,13 +1431,14 @@ export class LGraphCanvas {
|
|||||||
|
|
||||||
if (!skip_events) this.bindEvents()
|
if (!skip_events) this.bindEvents()
|
||||||
}
|
}
|
||||||
//used in some events to capture them
|
/** Captures an event and prevents default - returns false. */
|
||||||
_doNothing(e: Event) {
|
_doNothing(e: Event): boolean {
|
||||||
//console.log("pointerevents: _doNothing "+e.type);
|
//console.log("pointerevents: _doNothing "+e.type);
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
_doReturnTrue(e: Event) {
|
/** Captures an event and prevents default - returns true. */
|
||||||
|
_doReturnTrue(e: Event): boolean {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -1722,7 +1726,6 @@ export class LGraphCanvas {
|
|||||||
let node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes)
|
let node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes)
|
||||||
let skip_action = false
|
let skip_action = false
|
||||||
const now = LiteGraph.getTime()
|
const now = LiteGraph.getTime()
|
||||||
const is_primary = (e.isPrimary === undefined || !e.isPrimary)
|
|
||||||
const is_double_click = (now - this.last_mouseclick < 300)
|
const is_double_click = (now - this.last_mouseclick < 300)
|
||||||
this.mouse[0] = e.clientX
|
this.mouse[0] = e.clientX
|
||||||
this.mouse[1] = e.clientY
|
this.mouse[1] = e.clientY
|
||||||
@@ -1730,7 +1733,7 @@ export class LGraphCanvas {
|
|||||||
this.graph_mouse[1] = e.canvasY
|
this.graph_mouse[1] = e.canvasY
|
||||||
this.last_click_position = [this.mouse[0], this.mouse[1]]
|
this.last_click_position = [this.mouse[0], this.mouse[1]]
|
||||||
|
|
||||||
this.pointer_is_double = this.pointer_is_down && is_primary
|
this.pointer_is_double = this.pointer_is_down && e.isPrimary
|
||||||
this.pointer_is_down = true
|
this.pointer_is_down = true
|
||||||
|
|
||||||
this.canvas.focus()
|
this.canvas.focus()
|
||||||
@@ -2057,11 +2060,8 @@ export class LGraphCanvas {
|
|||||||
this.ctx.lineWidth = lineWidth
|
this.ctx.lineWidth = lineWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const group = this.graph.getGroupOnPos(e.canvasX, e.canvasY)
|
||||||
this.selected_group = this.graph.getGroupOnPos(e.canvasX, e.canvasY)
|
this.selected_group = group
|
||||||
this.selected_group_resizing = false
|
|
||||||
|
|
||||||
const group = this.selected_group
|
|
||||||
if (group && !this.read_only) {
|
if (group && !this.read_only) {
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
this.dragging_rectangle = null
|
this.dragging_rectangle = null
|
||||||
@@ -2069,7 +2069,7 @@ export class LGraphCanvas {
|
|||||||
|
|
||||||
const dist = distance([e.canvasX, e.canvasY], [group.pos[0] + group.size[0], group.pos[1] + group.size[1]])
|
const dist = distance([e.canvasX, e.canvasY], [group.pos[0] + group.size[0], group.pos[1] + group.size[1]])
|
||||||
if (dist * this.ds.scale < 10) {
|
if (dist * this.ds.scale < 10) {
|
||||||
this.selected_group_resizing = true
|
this.resizingGroup = group
|
||||||
} else {
|
} else {
|
||||||
const f = group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE
|
const f = group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE
|
||||||
const headerHeight = f * 1.4
|
const headerHeight = f * 1.4
|
||||||
@@ -2086,7 +2086,7 @@ export class LGraphCanvas {
|
|||||||
this.emitEvent({
|
this.emitEvent({
|
||||||
subType: "group-double-click",
|
subType: "group-double-click",
|
||||||
originalEvent: e,
|
originalEvent: e,
|
||||||
group: this.selected_group,
|
group,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (is_double_click && !this.read_only) {
|
} else if (is_double_click && !this.read_only) {
|
||||||
@@ -2259,19 +2259,20 @@ export class LGraphCanvas {
|
|||||||
|
|
||||||
//get node over
|
//get node over
|
||||||
const node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes)
|
const node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes)
|
||||||
|
const { resizingGroup } = this
|
||||||
|
|
||||||
if (this.dragging_rectangle) {
|
if (this.dragging_rectangle) {
|
||||||
this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]
|
this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]
|
||||||
this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]
|
this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]
|
||||||
this.dirty_canvas = true
|
this.dirty_canvas = true
|
||||||
}
|
}
|
||||||
else if (this.selected_group_resizing && !this.read_only) {
|
else if (resizingGroup && !this.read_only) {
|
||||||
//moving/resizing a group
|
// Resizing a group
|
||||||
this.selected_group.resize(
|
const resized = resizingGroup.resize(
|
||||||
e.canvasX - this.selected_group.pos[0],
|
e.canvasX - resizingGroup.pos[0],
|
||||||
e.canvasY - this.selected_group.pos[1]
|
e.canvasY - resizingGroup.pos[1]
|
||||||
)
|
)
|
||||||
this.dirty_bgcanvas = true
|
if (resized) this.dirty_bgcanvas = true
|
||||||
} else if (this.dragging_canvas) {
|
} else if (this.dragging_canvas) {
|
||||||
this.ds.offset[0] += delta[0] / this.ds.scale
|
this.ds.offset[0] += delta[0] / this.ds.scale
|
||||||
this.ds.offset[1] += delta[1] / this.ds.scale
|
this.ds.offset[1] += delta[1] / this.ds.scale
|
||||||
@@ -2494,6 +2495,7 @@ export class LGraphCanvas {
|
|||||||
this.block_click &&= false
|
this.block_click &&= false
|
||||||
|
|
||||||
if (e.which == 1) {
|
if (e.which == 1) {
|
||||||
|
this.resizingGroup = null
|
||||||
|
|
||||||
if (this.node_widget) {
|
if (this.node_widget) {
|
||||||
this.processNodeWidgets(this.node_widget[0], this.graph_mouse, e)
|
this.processNodeWidgets(this.node_widget[0], this.graph_mouse, e)
|
||||||
@@ -2501,12 +2503,7 @@ export class LGraphCanvas {
|
|||||||
|
|
||||||
//left button
|
//left button
|
||||||
this.node_widget = null
|
this.node_widget = null
|
||||||
|
this.selected_group = null
|
||||||
if (this.selected_group) {
|
|
||||||
this.dirty_canvas = true
|
|
||||||
this.selected_group = null
|
|
||||||
}
|
|
||||||
this.selected_group_resizing = false
|
|
||||||
this.isDragging = false
|
this.isDragging = false
|
||||||
|
|
||||||
let node = this.graph.getNodeOnPos(
|
let node = this.graph.getNodeOnPos(
|
||||||
|
|||||||
@@ -12,12 +12,18 @@ export interface IGraphGroupFlags extends Record<string, unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LGraphGroup implements Positionable {
|
export class LGraphGroup implements Positionable {
|
||||||
|
static minWidth = 140
|
||||||
|
static minHeight = 80
|
||||||
|
static resizeLength = 10
|
||||||
|
static padding = 4
|
||||||
|
static defaultColour = '#335'
|
||||||
|
|
||||||
id: number
|
id: number
|
||||||
color: string
|
color: string
|
||||||
title: string
|
title: string
|
||||||
font?: string
|
font?: string
|
||||||
font_size: number = LiteGraph.DEFAULT_GROUP_FONT || 24
|
font_size: number = LiteGraph.DEFAULT_GROUP_FONT || 24
|
||||||
_bounding: Float32Array = new Float32Array([10, 10, 140, 80])
|
_bounding: Float32Array = new Float32Array([10, 10, LGraphGroup.minWidth, LGraphGroup.minHeight])
|
||||||
_pos: Point = this._bounding.subarray(0, 2)
|
_pos: Point = this._bounding.subarray(0, 2)
|
||||||
_size: Size = this._bounding.subarray(2, 4)
|
_size: Size = this._bounding.subarray(2, 4)
|
||||||
/** @deprecated See {@link _children} */
|
/** @deprecated See {@link _children} */
|
||||||
@@ -54,10 +60,11 @@ export class LGraphGroup implements Positionable {
|
|||||||
set size(v) {
|
set size(v) {
|
||||||
if (!v || v.length < 2) return
|
if (!v || v.length < 2) return
|
||||||
|
|
||||||
this._size[0] = Math.max(140, v[0])
|
this._size[0] = Math.max(LGraphGroup.minWidth, v[0])
|
||||||
this._size[1] = Math.max(80, v[1])
|
this._size[1] = Math.max(LGraphGroup.minHeight, v[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
get boundingRect() {
|
get boundingRect() {
|
||||||
return this._bounding
|
return this._bounding
|
||||||
}
|
}
|
||||||
@@ -113,26 +120,37 @@ export class LGraphGroup implements Positionable {
|
|||||||
* @param {CanvasRenderingContext2D} ctx
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
*/
|
*/
|
||||||
draw(graphCanvas: LGraphCanvas, ctx: CanvasRenderingContext2D): void {
|
draw(graphCanvas: LGraphCanvas, ctx: CanvasRenderingContext2D): void {
|
||||||
const padding = 4
|
const { padding, resizeLength, defaultColour } = LGraphGroup
|
||||||
|
const font_size = this.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE
|
||||||
|
|
||||||
ctx.fillStyle = this.color
|
|
||||||
ctx.strokeStyle = this.color
|
|
||||||
const [x, y] = this._pos
|
const [x, y] = this._pos
|
||||||
const [width, height] = this._size
|
const [width, height] = this._size
|
||||||
|
|
||||||
|
// Titlebar
|
||||||
ctx.globalAlpha = 0.25 * graphCanvas.editor_alpha
|
ctx.globalAlpha = 0.25 * graphCanvas.editor_alpha
|
||||||
|
ctx.fillStyle = this.color || defaultColour
|
||||||
|
ctx.strokeStyle = this.color || defaultColour
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.rect(x + 0.5, y + 0.5, width, font_size * 1.4)
|
||||||
|
ctx.fill()
|
||||||
|
|
||||||
|
// Group background, border
|
||||||
|
ctx.fillStyle = this.color
|
||||||
|
ctx.strokeStyle = this.color
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.rect(x + 0.5, y + 0.5, width, height)
|
ctx.rect(x + 0.5, y + 0.5, width, height)
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
ctx.globalAlpha = graphCanvas.editor_alpha
|
ctx.globalAlpha = graphCanvas.editor_alpha
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
|
|
||||||
|
// Resize marker
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(x + width, y + height)
|
ctx.moveTo(x + width, y + height)
|
||||||
ctx.lineTo(x + width - 10, y + height)
|
ctx.lineTo(x + width - resizeLength, y + height)
|
||||||
ctx.lineTo(x + width, y + height - 10)
|
ctx.lineTo(x + width, y + height - resizeLength)
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
|
|
||||||
const font_size = this.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE
|
// Title
|
||||||
ctx.font = font_size + "px Arial"
|
ctx.font = font_size + "px Arial"
|
||||||
ctx.textAlign = "left"
|
ctx.textAlign = "left"
|
||||||
ctx.fillText(this.title + (this.pinned ? "📌" : ""), x + padding, y + font_size)
|
ctx.fillText(this.title + (this.pinned ? "📌" : ""), x + padding, y + font_size)
|
||||||
@@ -148,11 +166,12 @@ export class LGraphGroup implements Positionable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resize(width: number, height: number): void {
|
resize(width: number, height: number): boolean {
|
||||||
if (this.pinned) return
|
if (this.pinned) return false
|
||||||
|
|
||||||
this._size[0] = width
|
this._size[0] = Math.max(LGraphGroup.minWidth, width)
|
||||||
this._size[1] = height
|
this._size[1] = Math.max(LGraphGroup.minHeight, height)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
move(deltaX: number, deltaY: number, skipChildren: boolean = false): void {
|
move(deltaX: number, deltaY: number, skipChildren: boolean = false): void {
|
||||||
@@ -264,6 +283,16 @@ export class LGraphGroup implements Positionable {
|
|||||||
return isInsideRectangle(x, y, b[0], b[1], b[2], this.titleHeight)
|
return isInsideRectangle(x, y, b[0], b[1], b[2], this.titleHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isInResize(x: number, y: number): boolean {
|
||||||
|
const b = this._bounding
|
||||||
|
const right = b[0] + b[2]
|
||||||
|
const bottom = b[1] + b[3]
|
||||||
|
|
||||||
|
return x < right
|
||||||
|
&& y < bottom
|
||||||
|
&& (x - right) + (y - bottom) > -LGraphGroup.resizeLength
|
||||||
|
}
|
||||||
|
|
||||||
isPointInside = LGraphNode.prototype.isPointInside
|
isPointInside = LGraphNode.prototype.isPointInside
|
||||||
setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas
|
setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user