mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 19:21:54 +00:00
Add / fix canvas TS strict types, doc (#237)
* Fix circular depdency in global * Add TS type guard private function * Add TS type * Add TS types & doc * Add TS type initialisers * Add NullableProperties type * Add TS types * Split node arrange code out to separate file
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import type { CanvasColour, Dictionary, Direction, IBoundaryNodes, IContextMenuOptions, INodeSlot, INodeInputSlot, INodeOutputSlot, IOptionalSlotData, Point, Rect, Rect32, Size, IContextMenuValue, ISlotType, ConnectingLink } from "./interfaces"
|
import type { CanvasColour, Dictionary, Direction, IBoundaryNodes, IContextMenuOptions, INodeSlot, INodeInputSlot, INodeOutputSlot, IOptionalSlotData, Point, Rect, Rect32, Size, IContextMenuValue, ISlotType, ConnectingLink, NullableProperties } from "./interfaces"
|
||||||
import type { IWidget, TWidgetValue } from "./types/widgets"
|
import type { IWidget, TWidgetValue } from "./types/widgets"
|
||||||
import type { LGraphNode, NodeId } from "./LGraphNode"
|
import type { LGraphNode, NodeId } from "./LGraphNode"
|
||||||
import type { CanvasDragEvent, CanvasMouseEvent, CanvasWheelEvent, CanvasEventDetail, CanvasPointerEvent } from "./types/events"
|
import type { CanvasDragEvent, CanvasMouseEvent, CanvasWheelEvent, CanvasEventDetail, CanvasPointerEvent } from "./types/events"
|
||||||
@@ -13,7 +13,7 @@ import { drawSlot, LabelPosition } from "./draw"
|
|||||||
import { DragAndScale } from "./DragAndScale"
|
import { DragAndScale } from "./DragAndScale"
|
||||||
import { LinkReleaseContextExtended, LiteGraph, clamp } from "./litegraph"
|
import { LinkReleaseContextExtended, LiteGraph, clamp } from "./litegraph"
|
||||||
import { stringOrEmpty, stringOrNull } from "./strings"
|
import { stringOrEmpty, stringOrNull } from "./strings"
|
||||||
import { distributeNodes } from "./utils/arrange"
|
import { alignNodes, distributeNodes, getBoundaryNodes } from "./utils/arrange"
|
||||||
|
|
||||||
interface IShowSearchOptions {
|
interface IShowSearchOptions {
|
||||||
node_to?: LGraphNode
|
node_to?: LGraphNode
|
||||||
@@ -208,8 +208,8 @@ export class LGraphCanvas {
|
|||||||
allow_reconnect_links: boolean
|
allow_reconnect_links: boolean
|
||||||
align_to_grid: boolean
|
align_to_grid: boolean
|
||||||
drag_mode: boolean
|
drag_mode: boolean
|
||||||
dragging_rectangle?: Rect
|
dragging_rectangle: Rect | null
|
||||||
filter?: string
|
filter?: string | null
|
||||||
set_canvas_dirty_on_mouse_event: boolean
|
set_canvas_dirty_on_mouse_event: boolean
|
||||||
always_render_background: boolean
|
always_render_background: boolean
|
||||||
render_shadows: boolean
|
render_shadows: boolean
|
||||||
@@ -223,56 +223,63 @@ export class LGraphCanvas {
|
|||||||
render_title_colored: boolean
|
render_title_colored: boolean
|
||||||
render_link_tooltip: boolean
|
render_link_tooltip: boolean
|
||||||
links_render_mode: number
|
links_render_mode: number
|
||||||
|
/** mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle */
|
||||||
mouse: Point
|
mouse: Point
|
||||||
|
/** mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle */
|
||||||
graph_mouse: Point
|
graph_mouse: Point
|
||||||
|
/** @deprecated LEGACY: REMOVE THIS, USE {@link graph_mouse} INSTEAD */
|
||||||
canvas_mouse: Point
|
canvas_mouse: Point
|
||||||
|
/** to personalize the search box */
|
||||||
onSearchBox?: (helper: Element, str: string, canvas: LGraphCanvas) => any
|
onSearchBox?: (helper: Element, str: string, canvas: LGraphCanvas) => any
|
||||||
onSearchBoxSelection?: (name: any, event: any, canvas: LGraphCanvas) => void
|
onSearchBoxSelection?: (name: any, event: any, canvas: LGraphCanvas) => void
|
||||||
onMouse?: (e: CanvasMouseEvent) => boolean
|
onMouse?: (e: CanvasMouseEvent) => boolean
|
||||||
|
/** to render background objects (behind nodes and connections) in the canvas affected by transform */
|
||||||
onDrawBackground?: (ctx: CanvasRenderingContext2D, visible_area: any) => void
|
onDrawBackground?: (ctx: CanvasRenderingContext2D, visible_area: any) => void
|
||||||
|
/** to render foreground objects (above nodes and connections) in the canvas affected by transform */
|
||||||
onDrawForeground?: (arg0: CanvasRenderingContext2D, arg1: any) => void
|
onDrawForeground?: (arg0: CanvasRenderingContext2D, arg1: any) => void
|
||||||
connections_width: number
|
connections_width: number
|
||||||
round_radius: number
|
round_radius: number
|
||||||
current_node: LGraphNode
|
current_node: LGraphNode | null
|
||||||
node_widget?: [LGraphNode, IWidget]
|
/** used for widgets */
|
||||||
over_link_center: LLink
|
node_widget?: [LGraphNode, IWidget] | null
|
||||||
|
over_link_center: LLink | null
|
||||||
last_mouse_position: Point
|
last_mouse_position: Point
|
||||||
visible_area?: Rect32
|
visible_area?: Rect32
|
||||||
visible_links?: LLink[]
|
visible_links?: LLink[]
|
||||||
connecting_links: ConnectingLink[]
|
connecting_links: ConnectingLink[] | null
|
||||||
viewport?: Rect
|
viewport?: Rect
|
||||||
autoresize: boolean
|
autoresize: boolean
|
||||||
static active_canvas: LGraphCanvas
|
static active_canvas: LGraphCanvas
|
||||||
static onMenuNodeOutputs?(entries: IOptionalSlotData<INodeOutputSlot>[]): IOptionalSlotData<INodeOutputSlot>[]
|
static onMenuNodeOutputs?(entries: IOptionalSlotData<INodeOutputSlot>[]): IOptionalSlotData<INodeOutputSlot>[]
|
||||||
frame: number
|
frame = 0
|
||||||
last_draw_time: number
|
last_draw_time = 0
|
||||||
render_time: number
|
render_time = 0
|
||||||
fps: number
|
fps = 0
|
||||||
selected_nodes: Dictionary<LGraphNode>
|
selected_nodes: Dictionary<LGraphNode> = {}
|
||||||
/** @deprecated Temporary implementation only - will be replaced with `selectedItems: Set<Positionable>`. */
|
/** @deprecated Temporary implementation only - will be replaced with `selectedItems: Set<Positionable>`. */
|
||||||
selectedGroups: Set<LGraphGroup>
|
selectedGroups: Set<LGraphGroup> = new Set()
|
||||||
selected_group: LGraphGroup
|
selected_group: LGraphGroup | null = null
|
||||||
visible_nodes: LGraphNode[]
|
visible_nodes: LGraphNode[] = []
|
||||||
node_dragged?: LGraphNode
|
node_dragged?: LGraphNode
|
||||||
node_over?: LGraphNode
|
node_over?: LGraphNode
|
||||||
node_capturing_input?: LGraphNode
|
node_capturing_input?: LGraphNode
|
||||||
highlighted_links: Dictionary<boolean>
|
highlighted_links: Dictionary<boolean> = {}
|
||||||
link_over_widget?: IWidget
|
link_over_widget?: IWidget
|
||||||
link_over_widget_type?: string
|
link_over_widget_type?: string
|
||||||
|
|
||||||
dirty_canvas: boolean
|
dirty_canvas: boolean = true
|
||||||
dirty_bgcanvas: boolean
|
dirty_bgcanvas: boolean = true
|
||||||
/** A map of nodes that require selective-redraw */
|
/** A map of nodes that require selective-redraw */
|
||||||
dirty_nodes = new Map<NodeId, LGraphNode>()
|
dirty_nodes = new Map<NodeId, LGraphNode>()
|
||||||
dirty_area?: Rect
|
dirty_area?: Rect
|
||||||
// Unused
|
// Unused
|
||||||
node_in_panel?: LGraphNode
|
node_in_panel?: LGraphNode
|
||||||
last_mouse: Point
|
last_mouse: Point = [0, 0]
|
||||||
last_mouseclick: number
|
last_mouseclick: number = 0
|
||||||
pointer_is_down: boolean
|
pointer_is_down: boolean = false
|
||||||
pointer_is_double: boolean
|
pointer_is_double: boolean = false
|
||||||
graph: LGraph
|
graph!: LGraph
|
||||||
_graph_stack: LGraph[]
|
_graph_stack: LGraph[] | null = null
|
||||||
canvas: HTMLCanvasElement
|
canvas: HTMLCanvasElement
|
||||||
bgcanvas: HTMLCanvasElement
|
bgcanvas: HTMLCanvasElement
|
||||||
ctx?: CanvasRenderingContext2D
|
ctx?: CanvasRenderingContext2D
|
||||||
@@ -310,12 +317,18 @@ export class LGraphCanvas {
|
|||||||
getMenuOptions?(): IContextMenuValue[]
|
getMenuOptions?(): IContextMenuValue[]
|
||||||
getExtraMenuOptions?(canvas: LGraphCanvas, options: IContextMenuValue[]): IContextMenuValue[]
|
getExtraMenuOptions?(canvas: LGraphCanvas, options: IContextMenuValue[]): IContextMenuValue[]
|
||||||
static active_node: LGraphNode
|
static active_node: LGraphNode
|
||||||
|
/** called before modifying the graph */
|
||||||
onBeforeChange?(graph: LGraph): void
|
onBeforeChange?(graph: LGraph): void
|
||||||
|
/** called after modifying the graph */
|
||||||
onAfterChange?(graph: LGraph): void
|
onAfterChange?(graph: LGraph): void
|
||||||
onClear?: () => void
|
onClear?: () => void
|
||||||
|
/** called after moving a node */
|
||||||
onNodeMoved?: (node_dragged: LGraphNode) => void
|
onNodeMoved?: (node_dragged: LGraphNode) => void
|
||||||
|
/** called if the selection changes */
|
||||||
onSelectionChange?: (selected_nodes: Dictionary<LGraphNode>) => void
|
onSelectionChange?: (selected_nodes: Dictionary<LGraphNode>) => void
|
||||||
|
/** called when rendering a tooltip */
|
||||||
onDrawLinkTooltip?: (ctx: CanvasRenderingContext2D, link: LLink, canvas?: LGraphCanvas) => boolean
|
onDrawLinkTooltip?: (ctx: CanvasRenderingContext2D, link: LLink, canvas?: LGraphCanvas) => boolean
|
||||||
|
/** to render foreground objects not affected by transform (for GUIs) */
|
||||||
onDrawOverlay?: (ctx: CanvasRenderingContext2D) => void
|
onDrawOverlay?: (ctx: CanvasRenderingContext2D) => void
|
||||||
onRenderBackground?: (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => boolean
|
onRenderBackground?: (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => boolean
|
||||||
onNodeDblClicked?: (n: LGraphNode) => void
|
onNodeDblClicked?: (n: LGraphNode) => void
|
||||||
@@ -329,17 +342,20 @@ export class LGraphCanvas {
|
|||||||
// FIXME: Has never worked - undefined
|
// FIXME: Has never worked - undefined
|
||||||
visible_rect?: Rect
|
visible_rect?: Rect
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, graph: LGraph, options?: { viewport?: any; skip_events?: any; skip_render?: any; autoresize?: any }) {
|
/**
|
||||||
this.options = options = options || {}
|
* Creates a new instance of LGraphCanvas.
|
||||||
|
* @param canvas The canvas HTML element (or its id) to use, or null / undefined to leave blank.
|
||||||
|
* @param graph The graph that owns this canvas.
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
constructor(canvas: HTMLCanvasElement, graph: LGraph, options?: LGraphCanvas["options"]) {
|
||||||
|
options ||= {}
|
||||||
|
this.options = options
|
||||||
|
|
||||||
//if(graph === undefined)
|
//if(graph === undefined)
|
||||||
// throw ("No graph assigned");
|
// throw ("No graph assigned");
|
||||||
this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE
|
this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE
|
||||||
|
|
||||||
if (canvas && typeof canvas === "string") {
|
|
||||||
canvas = document.querySelector(canvas)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ds = new DragAndScale()
|
this.ds = new DragAndScale()
|
||||||
this.zoom_modify_alpha = true //otherwise it generates ugly patterns when scaling down too much
|
this.zoom_modify_alpha = true //otherwise it generates ugly patterns when scaling down too much
|
||||||
this.zoom_speed = 1.1 // in range (1.01, 2.5). Less than 1 will invert the zoom direction
|
this.zoom_speed = 1.1 // in range (1.01, 2.5). Less than 1 will invert the zoom direction
|
||||||
@@ -404,9 +420,9 @@ export class LGraphCanvas {
|
|||||||
|
|
||||||
this.links_render_mode = LiteGraph.SPLINE_LINK
|
this.links_render_mode = LiteGraph.SPLINE_LINK
|
||||||
|
|
||||||
this.mouse = [0, 0] //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle
|
this.mouse = [0, 0]
|
||||||
this.graph_mouse = [0, 0] //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle
|
this.graph_mouse = [0, 0]
|
||||||
this.canvas_mouse = this.graph_mouse //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD
|
this.canvas_mouse = this.graph_mouse
|
||||||
|
|
||||||
//to personalize the search box
|
//to personalize the search box
|
||||||
this.onSearchBox = null
|
this.onSearchBox = null
|
||||||
@@ -414,23 +430,24 @@ export class LGraphCanvas {
|
|||||||
|
|
||||||
//callbacks
|
//callbacks
|
||||||
this.onMouse = null
|
this.onMouse = null
|
||||||
this.onDrawBackground = null //to render background objects (behind nodes and connections) in the canvas affected by transform
|
this.onDrawBackground = null
|
||||||
this.onDrawForeground = null //to render foreground objects (above nodes and connections) in the canvas affected by transform
|
this.onDrawForeground = null
|
||||||
this.onDrawOverlay = null //to render foreground objects not affected by transform (for GUIs)
|
this.onDrawOverlay = null
|
||||||
this.onDrawLinkTooltip = null //called when rendering a tooltip
|
this.onDrawLinkTooltip = null
|
||||||
this.onNodeMoved = null //called after moving a node
|
this.onNodeMoved = null
|
||||||
this.onSelectionChange = null //called if the selection changes
|
this.onSelectionChange = null
|
||||||
// FIXME: Typo, does nothing
|
// FIXME: Typo, does nothing
|
||||||
|
//called before any link changes
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
this.onConnectingChange = null //called before any link changes
|
this.onConnectingChange = null
|
||||||
this.onBeforeChange = null //called before modifying the graph
|
this.onBeforeChange = null
|
||||||
this.onAfterChange = null //called after modifying the graph
|
this.onAfterChange = null
|
||||||
|
|
||||||
this.connections_width = 3
|
this.connections_width = 3
|
||||||
this.round_radius = 8
|
this.round_radius = 8
|
||||||
|
|
||||||
this.current_node = null
|
this.current_node = null
|
||||||
this.node_widget = null //used for widgets
|
this.node_widget = null
|
||||||
this.over_link_center = null
|
this.over_link_center = null
|
||||||
this.last_mouse_position = [0, 0]
|
this.last_mouse_position = [0, 0]
|
||||||
this.visible_area = this.ds.visible_area
|
this.visible_area = this.ds.visible_area
|
||||||
@@ -527,84 +544,29 @@ export class LGraphCanvas {
|
|||||||
canvas.graph.add(group)
|
canvas.graph.add(group)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
* @deprecated Functionality moved to {@link getBoundaryNodes}. The new function returns null on failure, instead of an object with all null properties.
|
||||||
* Determines the furthest nodes in each direction
|
* Determines the furthest nodes in each direction
|
||||||
* @param {Dictionary<LGraphNode>} nodes the nodes to from which boundary nodes will be extracted
|
* @param {Dictionary<LGraphNode>} nodes the nodes to from which boundary nodes will be extracted
|
||||||
* @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}
|
* @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}
|
||||||
*/
|
*/
|
||||||
static getBoundaryNodes(nodes: LGraphNode[] | Dictionary<LGraphNode>): IBoundaryNodes {
|
static getBoundaryNodes(nodes: LGraphNode[] | Dictionary<LGraphNode>): NullableProperties<IBoundaryNodes> {
|
||||||
let top = null
|
const _nodes = Array.isArray(nodes) ? nodes : Object.values(nodes)
|
||||||
let right = null
|
return getBoundaryNodes(_nodes) ?? {
|
||||||
let bottom = null
|
top: null,
|
||||||
let left = null
|
right: null,
|
||||||
for (const nID in nodes) {
|
bottom: null,
|
||||||
const node = nodes[nID]
|
left: null
|
||||||
const [x, y] = node.pos
|
|
||||||
const [width, height] = node.size
|
|
||||||
|
|
||||||
if (top === null || y < top.pos[1]) {
|
|
||||||
top = node
|
|
||||||
}
|
|
||||||
if (right === null || x + width > right.pos[0] + right.size[0]) {
|
|
||||||
right = node
|
|
||||||
}
|
|
||||||
if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) {
|
|
||||||
bottom = node
|
|
||||||
}
|
|
||||||
if (left === null || x < left.pos[0]) {
|
|
||||||
left = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"top": top,
|
|
||||||
"right": right,
|
|
||||||
"bottom": bottom,
|
|
||||||
"left": left
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
* @deprecated Functionality moved to {@link alignNodes}. The new function does not set dirty canvas.
|
||||||
* @param {Dictionary<LGraphNode>} nodes a list of nodes
|
* @param {Dictionary<LGraphNode>} nodes a list of nodes
|
||||||
* @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes
|
* @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes
|
||||||
* @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction)
|
* @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction)
|
||||||
*/
|
*/
|
||||||
static alignNodes(nodes: Dictionary<LGraphNode>, direction: Direction, align_to?: LGraphNode): void {
|
static alignNodes(nodes: Dictionary<LGraphNode>, direction: Direction, align_to?: LGraphNode): void {
|
||||||
if (!nodes) {
|
alignNodes(Object.values(nodes), direction, align_to)
|
||||||
return
|
LGraphCanvas.active_canvas.setDirty(true, true)
|
||||||
}
|
|
||||||
|
|
||||||
const canvas = LGraphCanvas.active_canvas
|
|
||||||
let boundaryNodes: IBoundaryNodes
|
|
||||||
if (align_to === undefined) {
|
|
||||||
boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes)
|
|
||||||
} else {
|
|
||||||
boundaryNodes = {
|
|
||||||
"top": align_to,
|
|
||||||
"right": align_to,
|
|
||||||
"bottom": align_to,
|
|
||||||
"left": align_to
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const node of Object.values(canvas.selected_nodes)) {
|
|
||||||
switch (direction) {
|
|
||||||
case "right":
|
|
||||||
node.pos[0] = boundaryNodes["right"].pos[0] + boundaryNodes["right"].size[0] - node.size[0]
|
|
||||||
break
|
|
||||||
case "left":
|
|
||||||
node.pos[0] = boundaryNodes["left"].pos[0]
|
|
||||||
break
|
|
||||||
case "top":
|
|
||||||
node.pos[1] = boundaryNodes["top"].pos[1]
|
|
||||||
break
|
|
||||||
case "bottom":
|
|
||||||
node.pos[1] = boundaryNodes["bottom"].pos[1] + boundaryNodes["bottom"].size[1] - node.size[1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.dirty_canvas = true
|
|
||||||
canvas.dirty_bgcanvas = true
|
|
||||||
}
|
}
|
||||||
static onNodeAlign(value: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): void {
|
static onNodeAlign(value: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): void {
|
||||||
new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], {
|
new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], {
|
||||||
@@ -614,7 +576,8 @@ export class LGraphCanvas {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function inner_clicked(value: string) {
|
function inner_clicked(value: string) {
|
||||||
LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, (value.toLowerCase() as Direction), node)
|
alignNodes(Object.values(LGraphCanvas.active_canvas.selected_nodes), (value.toLowerCase() as Direction), node)
|
||||||
|
LGraphCanvas.active_canvas.setDirty(true, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static onGroupAlign(value: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu): void {
|
static onGroupAlign(value: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu): void {
|
||||||
@@ -624,8 +587,9 @@ export class LGraphCanvas {
|
|||||||
parentMenu: prev_menu,
|
parentMenu: prev_menu,
|
||||||
})
|
})
|
||||||
|
|
||||||
function inner_clicked(value) {
|
function inner_clicked(value: string) {
|
||||||
LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase())
|
alignNodes(Object.values(LGraphCanvas.active_canvas.selected_nodes), (value.toLowerCase() as Direction))
|
||||||
|
LGraphCanvas.active_canvas.setDirty(true, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static createDistributeMenu(value: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): void {
|
static createDistributeMenu(value: IContextMenuValue, options: IContextMenuOptions, event: MouseEvent, prev_menu: ContextMenu, node: LGraphNode): void {
|
||||||
@@ -1451,6 +1415,20 @@ export class LGraphCanvas {
|
|||||||
getCurrentGraph(): LGraph {
|
getCurrentGraph(): LGraph {
|
||||||
return this.graph
|
return this.graph
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Finds the canvas if required, throwing on failure.
|
||||||
|
* @param canvas Canvas element, or its element ID
|
||||||
|
* @returns The canvas element
|
||||||
|
* @throws If {@link canvas} is an element ID that does not belong to a valid HTML canvas element
|
||||||
|
*/
|
||||||
|
#validateCanvas(canvas: string | HTMLCanvasElement): HTMLCanvasElement & { data?: LGraphCanvas } {
|
||||||
|
if (typeof canvas === "string") {
|
||||||
|
const el = document.getElementById(canvas)
|
||||||
|
if (!(el instanceof HTMLCanvasElement)) throw "Error validating LiteGraph canvas: Canvas element not found"
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
return canvas
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Sets the current HTML canvas element.
|
* Sets the current HTML canvas element.
|
||||||
* Calls bindEvents to add input event listeners, and (re)creates the background canvas.
|
* Calls bindEvents to add input event listeners, and (re)creates the background canvas.
|
||||||
@@ -1458,16 +1436,8 @@ export class LGraphCanvas {
|
|||||||
* @param canvas The canvas element to assign, or its HTML element ID. If null or undefined, the current reference is cleared.
|
* @param canvas The canvas element to assign, or its HTML element ID. If null or undefined, the current reference is cleared.
|
||||||
* @param skip_events If true, events on the previous canvas will not be removed. Has no effect on the first invocation.
|
* @param skip_events If true, events on the previous canvas will not be removed. Has no effect on the first invocation.
|
||||||
*/
|
*/
|
||||||
setCanvas(canvas: string | HTMLCanvasElement & { data?: LGraphCanvas }, skip_events?: boolean) {
|
setCanvas(canvas: string | HTMLCanvasElement, skip_events?: boolean) {
|
||||||
let element: HTMLCanvasElement & { data?: LGraphCanvas }
|
const element = this.#validateCanvas(canvas)
|
||||||
if (typeof canvas === "string") {
|
|
||||||
const el = document.getElementById(canvas)
|
|
||||||
if (!(el instanceof HTMLCanvasElement)) throw "Error creating LiteGraph canvas: Canvas not found"
|
|
||||||
element = el
|
|
||||||
} else {
|
|
||||||
element = canvas
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element === this.canvas) return
|
if (element === this.canvas) return
|
||||||
//maybe detach events from old_canvas
|
//maybe detach events from old_canvas
|
||||||
if (!element && this.canvas && !skip_events) this.unbindEvents()
|
if (!element && this.canvas && !skip_events) this.unbindEvents()
|
||||||
@@ -6111,8 +6081,8 @@ export class LGraphCanvas {
|
|||||||
* Determines the furthest nodes in each direction for the currently selected nodes
|
* Determines the furthest nodes in each direction for the currently selected nodes
|
||||||
* @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}
|
* @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}
|
||||||
*/
|
*/
|
||||||
boundaryNodesForSelection(): IBoundaryNodes {
|
boundaryNodesForSelection(): NullableProperties<IBoundaryNodes> {
|
||||||
return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes))
|
return LGraphCanvas.getBoundaryNodes(this.selected_nodes)
|
||||||
}
|
}
|
||||||
showLinkMenu(link: LLink, e: CanvasMouseEvent): boolean {
|
showLinkMenu(link: LLink, e: CanvasMouseEvent): boolean {
|
||||||
const graph = this.graph
|
const graph = this.graph
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { LGraphCanvas } from "./LGraphCanvas"
|
|||||||
import { ContextMenu } from "./ContextMenu"
|
import { ContextMenu } from "./ContextMenu"
|
||||||
import { CurveEditor } from "./CurveEditor"
|
import { CurveEditor } from "./CurveEditor"
|
||||||
import { LGraphEventMode, LinkDirection, LinkRenderType, NodeSlotType, RenderShape, TitleMode } from "./types/globalEnums"
|
import { LGraphEventMode, LinkDirection, LinkRenderType, NodeSlotType, RenderShape, TitleMode } from "./types/globalEnums"
|
||||||
import { LiteGraph } from "./litegraph"
|
|
||||||
import { LGraphNode } from "./LGraphNode"
|
import { LGraphNode } from "./LGraphNode"
|
||||||
import { SlotShape, SlotDirection, SlotType, LabelPosition } from "./draw"
|
import { SlotShape, SlotDirection, SlotType, LabelPosition } from "./draw"
|
||||||
import type { Dictionary, ISlotType, Rect } from "./interfaces"
|
import type { Dictionary, ISlotType, Rect } from "./interfaces"
|
||||||
@@ -709,7 +708,7 @@ export class LiteGraphGlobal {
|
|||||||
pointerListenerAdd(oDOM: Node, sEvIn: string, fCall: (e: Event) => boolean | void, capture = false): void {
|
pointerListenerAdd(oDOM: Node, sEvIn: string, fCall: (e: Event) => boolean | void, capture = false): void {
|
||||||
if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall !== "function") return
|
if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall !== "function") return
|
||||||
|
|
||||||
let sMethod = LiteGraph.pointerevents_method
|
let sMethod = this.pointerevents_method
|
||||||
let sEvent = sEvIn
|
let sEvent = sEvIn
|
||||||
|
|
||||||
// UNDER CONSTRUCTION
|
// UNDER CONSTRUCTION
|
||||||
@@ -777,16 +776,16 @@ export class LiteGraphGlobal {
|
|||||||
//both pointer and move events
|
//both pointer and move events
|
||||||
case "down": case "up": case "move": case "over": case "out": case "enter":
|
case "down": case "up": case "move": case "over": case "out": case "enter":
|
||||||
{
|
{
|
||||||
if (LiteGraph.pointerevents_method == "pointer" || LiteGraph.pointerevents_method == "mouse") {
|
if (this.pointerevents_method == "pointer" || this.pointerevents_method == "mouse") {
|
||||||
oDOM.removeEventListener(LiteGraph.pointerevents_method + sEvent, fCall, capture)
|
oDOM.removeEventListener(this.pointerevents_method + sEvent, fCall, capture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
// only pointerevents
|
// only pointerevents
|
||||||
case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture":
|
case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture":
|
||||||
{
|
{
|
||||||
if (LiteGraph.pointerevents_method == "pointer") {
|
if (this.pointerevents_method == "pointer") {
|
||||||
return oDOM.removeEventListener(LiteGraph.pointerevents_method + sEvent, fCall, capture)
|
return oDOM.removeEventListener(this.pointerevents_method + sEvent, fCall, capture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// not "pointer" || "mouse"
|
// not "pointer" || "mouse"
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ import type { LinkId } from "./LLink"
|
|||||||
|
|
||||||
export type Dictionary<T> = { [key: string]: T }
|
export type Dictionary<T> = { [key: string]: T }
|
||||||
|
|
||||||
|
/** Allows all properties to be null. The same as `Partial<T>`, but adds null instead of undefined. */
|
||||||
|
export type NullableProperties<T> = {
|
||||||
|
[P in keyof T]: T[P] | null
|
||||||
|
}
|
||||||
|
|
||||||
export type CanvasColour = string | CanvasGradient | CanvasPattern
|
export type CanvasColour = string | CanvasGradient | CanvasPattern
|
||||||
|
|
||||||
export interface IInputOrOutput {
|
export interface IInputOrOutput {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export interface GenericEventDetail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface OriginalEvent {
|
export interface OriginalEvent {
|
||||||
originalEvent: CanvasMouseEvent,
|
originalEvent: CanvasPointerEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmptyReleaseEventDetail extends OriginalEvent {
|
export interface EmptyReleaseEventDetail extends OriginalEvent {
|
||||||
|
|||||||
@@ -1,5 +1,44 @@
|
|||||||
|
import type { Dictionary, Direction, IBoundaryNodes } from "@/interfaces"
|
||||||
import type { LGraphNode } from "@/LGraphNode"
|
import type { LGraphNode } from "@/LGraphNode"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the nodes that are farthest in all four directions, representing the boundary of the nodes.
|
||||||
|
* @param nodes The nodes to check the edges of
|
||||||
|
* @returns An object listing the furthest node (edge) in all four directions. `null` if no nodes were supplied or the first node was falsy.
|
||||||
|
*/
|
||||||
|
export function getBoundaryNodes(nodes: LGraphNode[]): IBoundaryNodes | null {
|
||||||
|
const valid = nodes?.find(x => x)
|
||||||
|
if (!valid) return null
|
||||||
|
|
||||||
|
let top = valid
|
||||||
|
let right = valid
|
||||||
|
let bottom = valid
|
||||||
|
let left = valid
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (!node) continue
|
||||||
|
const [x, y] = node.pos
|
||||||
|
const [width, height] = node.size
|
||||||
|
|
||||||
|
if (y < top.pos[1]) top = node
|
||||||
|
if (x + width > right.pos[0] + right.size[0]) right = node
|
||||||
|
if (y + height > bottom.pos[1] + bottom.size[1]) bottom = node
|
||||||
|
if (x < left.pos[0]) left = node
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
top,
|
||||||
|
right,
|
||||||
|
bottom,
|
||||||
|
left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distributes nodes evenly along a horizontal or vertical plane.
|
||||||
|
* @param nodes The nodes to distribute
|
||||||
|
* @param horizontal If true, distributes along the horizontal plane. Otherwise, the vertical plane.
|
||||||
|
*/
|
||||||
export function distributeNodes(nodes: LGraphNode[], horizontal?: boolean): void {
|
export function distributeNodes(nodes: LGraphNode[], horizontal?: boolean): void {
|
||||||
const nodeCount = nodes?.length
|
const nodeCount = nodes?.length
|
||||||
if (!(nodeCount > 1)) return
|
if (!(nodeCount > 1)) return
|
||||||
@@ -26,3 +65,41 @@ export function distributeNodes(nodes: LGraphNode[], horizontal?: boolean): void
|
|||||||
startAt += node.size[index]
|
startAt += node.size[index]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aligns all nodes along the edge of a node.
|
||||||
|
* @param nodes The nodes to align
|
||||||
|
* @param direction The edge to align nodes on
|
||||||
|
* @param align_to The node to align all other nodes to. If undefined, the farthest node will be used.
|
||||||
|
*/
|
||||||
|
export function alignNodes(nodes: LGraphNode[], direction: Direction, align_to?: LGraphNode): void {
|
||||||
|
if (!nodes) return
|
||||||
|
|
||||||
|
const boundary = align_to === undefined
|
||||||
|
? getBoundaryNodes(nodes)
|
||||||
|
: {
|
||||||
|
top: align_to,
|
||||||
|
right: align_to,
|
||||||
|
bottom: align_to,
|
||||||
|
left: align_to
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boundary === null) return
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
switch (direction) {
|
||||||
|
case "right":
|
||||||
|
node.pos[0] = boundary.right.pos[0] + boundary.right.size[0] - node.size[0]
|
||||||
|
break
|
||||||
|
case "left":
|
||||||
|
node.pos[0] = boundary.left.pos[0]
|
||||||
|
break
|
||||||
|
case "top":
|
||||||
|
node.pos[1] = boundary.top.pos[1]
|
||||||
|
break
|
||||||
|
case "bottom":
|
||||||
|
node.pos[1] = boundary.bottom.pos[1] + boundary.bottom.size[1] - node.size[1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user