Skip to content

Commit

Permalink
Typescript LGraphCanvas (#202)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
webfiltered and huchenlei authored Oct 11, 2024
1 parent edfa5e7 commit d69a2ae
Show file tree
Hide file tree
Showing 10 changed files with 4,005 additions and 4,193 deletions.
7,919 changes: 3,775 additions & 4,144 deletions src/LGraphCanvas.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/LGraphGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 4 additions & 3 deletions src/LGraphNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/LLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class LLink {
_data?: unknown
_pos: Float32Array
_last_time?: number
path?: Path2D

#color?: CanvasColour
/** Custom colour for this link only */
Expand Down
32 changes: 1 addition & 31 deletions src/LiteGraphGlobal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 changes: 15 additions & 12 deletions src/draw.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -28,7 +31,7 @@ export enum LabelPosition {

export function drawSlot(
ctx: CanvasRenderingContext2D,
slot: INodeSlot,
slot: Partial<INodeSlot>,
pos: Vector2,
{
label_color = "#AAA",
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -126,6 +125,7 @@ export interface IContextMenuOptions {

export interface IContextMenuValue {
title?: string
value?: string
content: string
has_submenu?: boolean
disabled?: boolean
Expand Down
188 changes: 188 additions & 0 deletions src/measure.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 17 additions & 0 deletions src/strings.ts
Original file line number Diff line number Diff line change
@@ -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)
}
1 change: 1 addition & 0 deletions src/types/globalEnums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down

0 comments on commit d69a2ae

Please sign in to comment.