mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 06:19:58 +00:00
Typescript LGraphCanvas (#202)
* Format only * Revert accidental change * Fix redundant falsy check - uninit. var * nit - Refactor const/let * nit - Refactor const/let (manual) * nit - Redeclared params * Add TS types & minor refactor only * Refactor - Clean up / reformat - Add strings.ts helper functions - Remove unused vars & local function params - Simplifies code - Rename vars for clarity - Add TODOs and other comments - Add ts-expect-error * Redirect draw.ts enums to global file (temp.) Should be revisited after TS merge complete Corrects import of types from draw.ts into interfaces * Add measure.ts - move util funcs from Global * Add all imports required for TS conversion * Refactor - TS narrowing * nit - TS types & minor refactor * Add missing types from recent PRs Removes duplicate declarations Fixes some type mismatches * nit - Refactor recent PRs * Revert incorrect decls backported * Remove unused params * Add TS types only * Fix minor TS type coercion issues Also removes redundant code * nit - Refactor * Remove @ts-nocheck * Fix refactor regression - drag link to output Issue was the result of fixing var declared outside of closure * Restore original logic --------- Co-authored-by: huchenlei <huchenlei@proton.me>
This commit is contained in:
7873
src/LGraphCanvas.ts
7873
src/LGraphCanvas.ts
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ import type { Point, Size } from "./interfaces"
|
||||
import type { LGraph } from "./LGraph"
|
||||
import { LiteGraph } from "./litegraph";
|
||||
import { LGraphCanvas } from "./LGraphCanvas";
|
||||
import { overlapBounding } from "./LiteGraphGlobal";
|
||||
import { overlapBounding } from "./measure";
|
||||
import { LGraphNode } from "./LGraphNode";
|
||||
|
||||
export interface IGraphGroup {
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { LGraphCanvas } from "./LGraphCanvas"
|
||||
import type { CanvasMouseEvent } from "./types/events"
|
||||
import { BadgePosition, LGraphBadge } from "./LGraphBadge";
|
||||
import { LiteGraph } from "./litegraph";
|
||||
import { isInsideRectangle } from "./LiteGraphGlobal";
|
||||
import { isInsideRectangle } from "./measure";
|
||||
import { LLink } from "./LLink";
|
||||
|
||||
export type NodeId = number | string
|
||||
@@ -145,7 +145,7 @@ export class LGraphNode {
|
||||
_shape?: RenderShape
|
||||
subgraph?: LGraph
|
||||
skip_subgraph_button?: boolean
|
||||
mouseOver?: IMouseOverData
|
||||
mouseOver?: boolean
|
||||
is_selected?: boolean
|
||||
redraw_on_mouse?: boolean
|
||||
// Appears unused
|
||||
@@ -228,6 +228,7 @@ export class LGraphNode {
|
||||
onMouseMove?(this: LGraphNode, e: MouseEvent, pos: Point, arg2: LGraphCanvas): void
|
||||
onPropertyChange?(this: LGraphNode): void
|
||||
updateOutputData?(this: LGraphNode, origin_slot: number): void
|
||||
isValidWidgetLink?(slot_index: number, node: LGraphNode, overWidget: IWidget): boolean | undefined
|
||||
|
||||
constructor(title: string) {
|
||||
this._ctor(title);
|
||||
@@ -2705,7 +2706,7 @@ export class LGraphNode {
|
||||
* Forces the node to do not move or realign on Z or resize
|
||||
* @method pin
|
||||
**/
|
||||
pin(v) {
|
||||
pin(v?) {
|
||||
this.graph._version++;
|
||||
if (v === undefined) {
|
||||
this.flags.pinned = !this.flags.pinned;
|
||||
|
||||
@@ -22,6 +22,7 @@ export class LLink {
|
||||
_data?: unknown
|
||||
_pos: Float32Array
|
||||
_last_time?: number
|
||||
path?: Path2D
|
||||
|
||||
#color?: CanvasColour
|
||||
/** Custom colour for this link only */
|
||||
|
||||
@@ -9,6 +9,7 @@ import { LiteGraph } from "./litegraph"
|
||||
import { LGraphNode } from "./LGraphNode"
|
||||
import { drawSlot, SlotShape, SlotDirection, SlotType, LabelPosition } from "./draw"
|
||||
import type { Dictionary, ISlotType, Rect } from "./interfaces"
|
||||
import { distance, isInsideRectangle, overlapBounding } from "./measure"
|
||||
|
||||
/**
|
||||
* The Global Scope. It contains all the registered node classes.
|
||||
@@ -1060,34 +1061,3 @@ export class LiteGraphGlobal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function distance(a, b) {
|
||||
return Math.sqrt(
|
||||
(b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])
|
||||
)
|
||||
}
|
||||
|
||||
export function isInsideRectangle(x, y, left, top, width, height) {
|
||||
if (left < x && left + width > x && top < y && top + height > y) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//bounding overlap, format: [ startx, starty, width, height ]
|
||||
export function overlapBounding(a, b) {
|
||||
var A_end_x = a[0] + a[2]
|
||||
var A_end_y = a[1] + a[3]
|
||||
var B_end_x = b[0] + b[2]
|
||||
var B_end_y = b[1] + b[3]
|
||||
|
||||
if (
|
||||
a[0] > B_end_x ||
|
||||
a[1] > B_end_y ||
|
||||
A_end_x < b[0] ||
|
||||
A_end_y < b[1]
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
27
src/draw.ts
27
src/draw.ts
@@ -1,24 +1,27 @@
|
||||
import type { Vector2 } from "./litegraph";
|
||||
import type { INodeSlot } from "./interfaces"
|
||||
import { LinkDirection, RenderShape } from "./types/globalEnums"
|
||||
|
||||
export enum SlotType {
|
||||
Array = "array",
|
||||
Event = -1,
|
||||
}
|
||||
|
||||
/** @see RenderShape */
|
||||
export enum SlotShape {
|
||||
Box = 1,
|
||||
Arrow = 5,
|
||||
Grid = 6,
|
||||
Circle = 3,
|
||||
HollowCircle = 7,
|
||||
Box = RenderShape.BOX,
|
||||
Arrow = RenderShape.ARROW,
|
||||
Grid = RenderShape.GRID,
|
||||
Circle = RenderShape.CIRCLE,
|
||||
HollowCircle = RenderShape.HollowCircle,
|
||||
}
|
||||
|
||||
/** @see LinkDirection */
|
||||
export enum SlotDirection {
|
||||
Up = 1,
|
||||
Right = 2,
|
||||
Down = 3,
|
||||
Left = 4,
|
||||
Up = LinkDirection.UP,
|
||||
Right = LinkDirection.RIGHT,
|
||||
Down = LinkDirection.DOWN,
|
||||
Left = LinkDirection.LEFT,
|
||||
}
|
||||
|
||||
export enum LabelPosition {
|
||||
@@ -28,7 +31,7 @@ export enum LabelPosition {
|
||||
|
||||
export function drawSlot(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
slot: INodeSlot,
|
||||
slot: Partial<INodeSlot>,
|
||||
pos: Vector2,
|
||||
{
|
||||
label_color = "#AAA",
|
||||
@@ -117,13 +120,13 @@ export function drawSlot(
|
||||
ctx.fillStyle = label_color;
|
||||
|
||||
if (label_position === LabelPosition.Right) {
|
||||
if (horizontal || slot.dir == SlotDirection.Up) {
|
||||
if (horizontal || slot.dir == LinkDirection.UP) {
|
||||
ctx.fillText(text, pos[0], pos[1] - 10);
|
||||
} else {
|
||||
ctx.fillText(text, pos[0] + 10, pos[1] + 5);
|
||||
}
|
||||
} else {
|
||||
if (horizontal || slot.dir == SlotDirection.Down) {
|
||||
if (horizontal || slot.dir == LinkDirection.DOWN) {
|
||||
ctx.fillText(text, pos[0], pos[1] - 8);
|
||||
} else {
|
||||
ctx.fillText(text, pos[0] - 10, pos[1] + 5);
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { ContextMenu } from "./ContextMenu"
|
||||
import type { LGraphNode } from "./LGraphNode"
|
||||
import type { LinkDirection, RenderShape } from "./types/globalEnums"
|
||||
import type { LinkId } from "./LLink"
|
||||
import type { SlotDirection } from "./draw"
|
||||
|
||||
export type Dictionary<T> = { [key: string]: T }
|
||||
|
||||
@@ -67,7 +66,7 @@ export type ISlotType = number | string
|
||||
export interface INodeSlot {
|
||||
name: string
|
||||
type: ISlotType
|
||||
dir?: LinkDirection & SlotDirection
|
||||
dir?: LinkDirection
|
||||
removable?: boolean
|
||||
shape?: RenderShape
|
||||
not_subgraph_input?: boolean
|
||||
@@ -126,6 +125,7 @@ export interface IContextMenuOptions {
|
||||
|
||||
export interface IContextMenuValue {
|
||||
title?: string
|
||||
value?: string
|
||||
content: string
|
||||
has_submenu?: boolean
|
||||
disabled?: boolean
|
||||
|
||||
188
src/measure.ts
Normal file
188
src/measure.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import type { Point, Rect } from "./interfaces"
|
||||
import { LinkDirection } from "./types/globalEnums"
|
||||
|
||||
type PointReadOnly = readonly [x: number, y: number] | Float32Array | Float64Array
|
||||
|
||||
/**
|
||||
* Calculates the distance between two points (2D vector)
|
||||
* @param a Point a as x, y
|
||||
* @param b Point b as x, y
|
||||
* @returns Distance between point a & b
|
||||
*/
|
||||
export function distance(a: PointReadOnly, b: PointReadOnly): number {
|
||||
return Math.sqrt(
|
||||
(b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the distance2 (squared) between two points (2D vector).
|
||||
* Much faster when only comparing distances (closest/furthest point).
|
||||
* @param a Point a as x, y
|
||||
* @param b Point b as x, y
|
||||
* @returns Distance2 (squared) between point a & b
|
||||
*/
|
||||
export function dist2(a: PointReadOnly, b: PointReadOnly): number {
|
||||
return ((b[0] - a[0]) * (b[0] - a[0])) + ((b[1] - a[1]) * (b[1] - a[1]))
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a point is inside a rectangle.
|
||||
* @param point The point to check, as x, y
|
||||
* @param rect The rectangle, as x, y, width, height
|
||||
* @returns true if the point is inside the rect, otherwise false
|
||||
*/
|
||||
export function isPointInRectangle(point: PointReadOnly, rect: Rect): boolean {
|
||||
return rect[0] < point[0]
|
||||
&& rect[0] + rect[2] > point[0]
|
||||
&& rect[1] < point[1]
|
||||
&& rect[1] + rect[3] > point[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a point is inside a rectangle.
|
||||
* @param x Point x
|
||||
* @param y Point y
|
||||
* @param left Rect x
|
||||
* @param top Rect y
|
||||
* @param width Rect width
|
||||
* @param height Rect height
|
||||
* @returns true if the point is inside the rect, otherwise false
|
||||
*/
|
||||
export function isInsideRectangle(x: number, y: number, left: number, top: number, width: number, height: number): boolean {
|
||||
return left < x
|
||||
&& left + width > x
|
||||
&& top < y
|
||||
&& top + height > y
|
||||
}
|
||||
|
||||
/**
|
||||
* Cheap, low accuracy check to determine if a point is roughly inside a sort-of octagon
|
||||
* @param x Point x
|
||||
* @param y Point y
|
||||
* @param radius Radius to use as rough guide for octagon
|
||||
* @returns true if the point is roughly inside the octagon centred on 0,0 with specified radius
|
||||
*/
|
||||
export function isSortaInsideOctagon(x: number, y: number, radius: number) {
|
||||
const sum = Math.min(radius, Math.abs(x)) + Math.min(radius, Math.abs(y))
|
||||
return sum < radius * 0.75
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if two rectangles have any overlap
|
||||
* @param a Rectangle A as x, y, width, height
|
||||
* @param b Rectangle B as x, y, width, height
|
||||
* @returns true if rectangles overlap, otherwise false
|
||||
*/
|
||||
export function overlapBounding(a: Rect, b: Rect): boolean {
|
||||
const aRight = a[0] + a[2]
|
||||
const aBottom = a[1] + a[3]
|
||||
const bRight = b[0] + b[2]
|
||||
const bBottom = b[1] + b[3]
|
||||
|
||||
return a[0] > bRight
|
||||
|| a[1] > bBottom
|
||||
|| aRight < b[0]
|
||||
|| aBottom < b[1]
|
||||
? false
|
||||
: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an offset in the specified direction to {@link out}
|
||||
* @param amount Amount of offset to add
|
||||
* @param direction Direction to add the offset to
|
||||
* @param out The {@link Point} to add the offset to
|
||||
*/
|
||||
export function addDirectionalOffset(amount: number, direction: LinkDirection, out: Point): void {
|
||||
switch (direction) {
|
||||
case LinkDirection.LEFT:
|
||||
out[0] -= amount
|
||||
return
|
||||
case LinkDirection.RIGHT:
|
||||
out[0] += amount
|
||||
return
|
||||
case LinkDirection.UP:
|
||||
out[1] -= amount
|
||||
return
|
||||
case LinkDirection.DOWN:
|
||||
out[1] += amount
|
||||
return
|
||||
// LinkDirection.CENTER: Nothing to do.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips axes of a 2D vector - "rotating" them by 90°
|
||||
* @param offset The offset to rotate
|
||||
* @param from Direction to rotate from
|
||||
* @param to Direction to rotate to
|
||||
*/
|
||||
export function rotateLinkDirection(offset: Point, from: LinkDirection, to: LinkDirection): void {
|
||||
let x: number
|
||||
let y: number
|
||||
|
||||
// Normalise to left
|
||||
switch (from) {
|
||||
case to:
|
||||
case LinkDirection.CENTER:
|
||||
case LinkDirection.NONE:
|
||||
// Nothing to do
|
||||
return
|
||||
|
||||
case LinkDirection.LEFT:
|
||||
x = offset[0]
|
||||
y = offset[1]
|
||||
break
|
||||
case LinkDirection.RIGHT:
|
||||
x = -offset[0]
|
||||
y = -offset[1]
|
||||
break
|
||||
case LinkDirection.UP:
|
||||
x = -offset[1]
|
||||
y = offset[0]
|
||||
break
|
||||
case LinkDirection.DOWN:
|
||||
x = offset[1]
|
||||
y = -offset[0]
|
||||
break
|
||||
}
|
||||
|
||||
// Apply new direction
|
||||
switch (to) {
|
||||
case LinkDirection.CENTER:
|
||||
case LinkDirection.NONE:
|
||||
// Nothing to do
|
||||
return
|
||||
|
||||
case LinkDirection.LEFT:
|
||||
offset[0] = x
|
||||
offset[1] = y
|
||||
break
|
||||
case LinkDirection.RIGHT:
|
||||
offset[0] = -x
|
||||
offset[1] = -y
|
||||
break
|
||||
case LinkDirection.UP:
|
||||
offset[0] = y
|
||||
offset[1] = -x
|
||||
break
|
||||
case LinkDirection.DOWN:
|
||||
offset[0] = -y
|
||||
offset[1] = x
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a point is to to the left or right of a line.
|
||||
* Project a line from lineStart -> lineEnd. Determine if point is to the left or right of that projection.
|
||||
* {@link https://www.geeksforgeeks.org/orientation-3-ordered-points/}
|
||||
* @param lineStart The start point of the line
|
||||
* @param lineEnd The end point of the line
|
||||
* @param point The point to check
|
||||
* @returns 0 if all three points are in a straight line, a negative value if point is to the left of the projected line, or positive if the point is to the right
|
||||
*/
|
||||
export function getOrientation(lineStart: PointReadOnly, lineEnd: PointReadOnly, x: number, y: number): number {
|
||||
return ((lineEnd[1] - lineStart[1]) * (x - lineEnd[0])) - ((lineEnd[0] - lineStart[0]) * (y - lineEnd[1]))
|
||||
}
|
||||
17
src/strings.ts
Normal file
17
src/strings.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Uses the standard String() function to coerce to string, unless the value is null or undefined - then null.
|
||||
* @param value The value to convert
|
||||
* @returns String(value) or null
|
||||
*/
|
||||
export function stringOrNull(value: unknown): string | null {
|
||||
return value == null ? null : String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the standard String() function to coerce to string, unless the value is null or undefined - then an empty string
|
||||
* @param value The value to convert
|
||||
* @returns String(value) or ""
|
||||
*/
|
||||
export function stringOrEmpty(value: unknown): string {
|
||||
return value == null ? "" : String(value)
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export enum RenderShape {
|
||||
ARROW = 5,
|
||||
/** intended for slot arrays */
|
||||
GRID = 6,
|
||||
HollowCircle = 7,
|
||||
}
|
||||
|
||||
/** The direction that a link point will flow towards - e.g. horizontal outputs are right by default */
|
||||
|
||||
Reference in New Issue
Block a user