diff --git a/src/LGraph.ts b/src/LGraph.ts index 179f7bc673..3a7d8f6c2b 100644 --- a/src/LGraph.ts +++ b/src/LGraph.ts @@ -49,7 +49,7 @@ export class LGraph { * ``` */ links: Map & Record - list_of_graphcanvas?: LGraphCanvas[] + list_of_graphcanvas: LGraphCanvas[] | null status: number last_node_id: number last_link_id: number @@ -58,7 +58,7 @@ export class LGraph { _nodes: LGraphNode[] _nodes_by_id: Record _nodes_in_order: LGraphNode[] - _nodes_executable: LGraphNode[] + _nodes_executable: LGraphNode[] | null _groups: LGraphGroup[] iteration: number globaltime: number @@ -69,7 +69,7 @@ export class LGraph { last_update_time: number starttime: number catch_errors: boolean - execution_timer_id: number + execution_timer_id: number | null errors_in_execution: boolean execution_time: number _last_trigger_time?: number @@ -101,8 +101,8 @@ export class LGraph { onOutputRenamed?(old_name: string, name: string): void onOutputTypeChanged?(name: string, type: string): void onOutputRemoved?(name: string): void - onBeforeChange?(graph: LGraph, info: LGraphNode): void - onAfterChange?(graph: LGraph, info: LGraphNode): void + onBeforeChange?(graph: LGraph, info?: LGraphNode): void + onAfterChange?(graph: LGraph, info?: LGraphNode): void onConnectionChange?(node: LGraphNode): void on_change?(graph: LGraph): void onSerialize?(data: ISerialisedGraph): void @@ -378,11 +378,11 @@ export class LGraph { } //This is more internal, it computes the executable nodes in order and returns it computeExecutionOrder(only_onExecute: boolean, set_level?: boolean): LGraphNode[] { - let L: LGraphNode[] = [] + const L: LGraphNode[] = [] const S: LGraphNode[] = [] const M: Dictionary = {} - const visited_links: Record = {} //to avoid repeating links - const remaining_links: Record = {} //to a + const visited_links: Record = {} //to avoid repeating links + const remaining_links: Record = {} //to a //search for the nodes without inputs (starting nodes) for (let i = 0, l = this._nodes.length; i < l; ++i) { @@ -414,10 +414,10 @@ export class LGraph { } while (true) { - if (S.length == 0) break - //get an starting node const node = S.shift() + if (node === undefined) break + L.push(node) //add to ordered list delete M[node.id] //remove from the pending nodes @@ -471,13 +471,22 @@ export class LGraph { const l = L.length - //save order number in the node - for (let i = 0; i < l; ++i) { - L[i].order = i + /** Ensure type is set */ + type OrderedLGraphNode = LGraphNode & { order: number } + + /** Sets the order property of each provided node to its index in {@link nodes}. */ + function setOrder(nodes: LGraphNode[]): asserts nodes is OrderedLGraphNode[] { + const l = nodes.length + for (let i = 0; i < l; ++i) { + nodes[i].order = i + } } + //save order number in the node + setOrder(L) + //sort now by priority - L = L.sort(function (A, B) { + L.sort(function (A, B) { // @ts-expect-error ctor props const Ap = A.constructor.priority || A.priority || 0 // @ts-expect-error ctor props @@ -490,9 +499,7 @@ export class LGraph { }) //save order number in the node, again... - for (let i = 0; i < l; ++i) { - L[i].order = i - } + setOrder(L) return L } @@ -508,7 +515,7 @@ export class LGraph { while (pending.length) { const current = pending.shift() - if (!current.inputs) continue + if (!current?.inputs) continue if (!visited[current.id] && current != node) { visited[current.id] = true @@ -535,7 +542,7 @@ export class LGraph { margin = margin || 100 const nodes = this.computeExecutionOrder(false, true) - const columns = [] + const columns: LGraphNode[][] = [] for (let i = 0; i < nodes.length; ++i) { const node = nodes[i] const col = node._level || 1 @@ -634,7 +641,7 @@ export class LGraph { * Adds a new node instance to this graph * @param {LGraphNode} node the instance of the node */ - add(node: LGraphNode | LGraphGroup, skip_compute_order?: boolean): LGraphNode | null { + add(node: LGraphNode | LGraphGroup, skip_compute_order?: boolean): LGraphNode | null | undefined { if (!node) return // LEGACY: This was changed from constructor === LGraphGroup @@ -787,7 +794,7 @@ export class LGraph { * @return {Array} a list with all the nodes of this type */ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - findNodesByClass(classObject: Function, result: LGraphNode[]): LGraphNode[] { + findNodesByClass(classObject: Function, result?: LGraphNode[]): LGraphNode[] { result = result || [] result.length = 0 for (let i = 0, l = this._nodes.length; i < l; ++i) { @@ -806,7 +813,7 @@ export class LGraph { result = result || [] result.length = 0 for (let i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].type.toLowerCase() == matchType) + if (this._nodes[i].type?.toLowerCase() == matchType) result.push(this._nodes[i]) } return result @@ -816,7 +823,7 @@ export class LGraph { * @param {String} name the name of the node to search * @return {Node} the node or null */ - findNodeByTitle(title: string): LGraphNode { + findNodeByTitle(title: string): LGraphNode | null { for (let i = 0, l = this._nodes.length; i < l; ++i) { if (this._nodes[i].title == title) return this._nodes[i] @@ -843,20 +850,14 @@ export class LGraph { * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph * @return {LGraphNode} the node at this position or null */ - getNodeOnPos(x: number, y: number, nodes_list?: LGraphNode[], margin?: number): LGraphNode { + getNodeOnPos(x: number, y: number, nodes_list?: LGraphNode[], margin?: number): LGraphNode | null { nodes_list = nodes_list || this._nodes const nRet = null for (let i = nodes_list.length - 1; i >= 0; i--) { const n = nodes_list[i] const skip_title = n.constructor.title_mode == TitleMode.NO_TITLE - if (n.isPointInside(x, y, margin, skip_title)) { - // check for lesser interest nodes (TODO check for overlapping, use the top) - /*if (typeof n == "LGraphGroup"){ - nRet = n; - }else{*/ + if (n.isPointInside(x, y, margin, skip_title)) return n - /*}*/ - } } return nRet } @@ -956,7 +957,7 @@ export class LGraph { * @param {String} old_name * @param {String} new_name */ - renameInput(old_name: string, name: string): boolean { + renameInput(old_name: string, name: string): boolean | undefined { if (name == old_name) return if (!this.inputs[old_name]) return false @@ -978,7 +979,7 @@ export class LGraph { * @param {String} name * @param {String} type */ - changeInputType(name: string, type: string): boolean { + changeInputType(name: string, type: string): boolean | undefined { if (!this.inputs[name]) return false if (this.inputs[name].type && @@ -1045,7 +1046,7 @@ export class LGraph { * @param {String} old_name * @param {String} new_name */ - renameOutput(old_name: string, name: string): boolean { + renameOutput(old_name: string, name: string): boolean | undefined { if (!this.outputs[old_name]) return false if (this.outputs[name]) { @@ -1066,7 +1067,7 @@ export class LGraph { * @param {String} name * @param {String} type */ - changeOutputType(name: string, type: string): boolean { + changeOutputType(name: string, type: string): boolean | undefined { if (!this.outputs[name]) return false if (this.outputs[name].type && @@ -1215,7 +1216,7 @@ export class LGraph { * @param {String} str configure a graph from a JSON string * @param {Boolean} returns if there was any error parsing */ - configure(data: ISerialisedGraph, keep_old?: boolean): boolean { + configure(data: ISerialisedGraph, keep_old?: boolean): boolean | undefined { // TODO: Finish typing configure() if (!data) return diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index 2151ad040a..afccfb962a 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -123,7 +123,7 @@ export class LGraphNode { title: string graph: LGraph | null = null - id?: NodeId + id: NodeId type: string | null = null inputs: INodeInputSlot[] = [] outputs: INodeOutputSlot[] = [] diff --git a/src/interfaces.ts b/src/interfaces.ts index 0e2f779cb1..68036b5004 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -26,29 +26,38 @@ export interface IFoundSlot extends IInputOrOutput { link_pos: Point } -/** A point on the canvas: x, y */ +/** A point represented as `[x, y]` co-ordinates */ export type Point = [x: number, y: number] | Float32Array | Float64Array -/** A size: width, height */ +/** A size represented as `[width, height]` */ export type Size = [width: number, height: number] | Float32Array | Float64Array /** A very firm array */ type ArRect = [x: number, y: number, width: number, height: number] -/** A rectangle starting at top-left coordinates: x, y, width, height */ +/** A rectangle starting at top-left coordinates `[x, y, width, height]` */ export type Rect = ArRect | Float32Array | Float64Array -// TODO: Need regular arrays also? +/** A rectangle starting at top-left coordinates `[x, y, width, height]`. Requires functions exclusive to `TypedArray`. */ export type Rect32 = Float32Array +/** A point represented as `[x, y]` co-ordinates that will not be modified */ +export type ReadOnlyPoint = readonly [x: number, y: number] | ReadOnlyTypedArray | ReadOnlyTypedArray +/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */ +export type ReadOnlyRect = readonly [x: number, y: number, width: number, height: number] | ReadOnlyTypedArray | ReadOnlyTypedArray + +type TypedArrays = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array +type TypedBigIntArrays = BigInt64Array | BigUint64Array +type ReadOnlyTypedArray = Omit + /** Union of property names that are of type Match */ export type KeysOfType = { [P in keyof T]: T[P] extends Match ? P : never }[keyof T] /** A new type that contains only the properties of T that are of type Match */ export type PickByType = { [P in keyof T]: Extract } -/** The names of all methods and functions in T */ -export type MethodNames = KeysOfType any> +/** The names of all (optional) methods and functions in T */ +export type MethodNames = KeysOfType any) | undefined> export interface IBoundaryNodes { top: LGraphNode @@ -113,7 +122,7 @@ export interface ConnectingLink extends IInputOrOutput { interface IContextMenuBase { title?: string className?: string - callback?(value?: unknown, options?: unknown, event?: MouseEvent, previous_menu?: ContextMenu, node?: LGraphNode): void + callback?(value?: unknown, options?: unknown, event?: MouseEvent, previous_menu?: ContextMenu, node?: LGraphNode): void | boolean } /** ContextMenu */ diff --git a/src/measure.ts b/src/measure.ts index 2c1afe8bf7..d02fd69649 100644 --- a/src/measure.ts +++ b/src/measure.ts @@ -1,15 +1,13 @@ -import type { Point, Rect } from "./interfaces" +import type { Point, ReadOnlyPoint, ReadOnlyRect } 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 + * @param a Point a as `x, y` + * @param b Point b as `x, y` + * @returns Distance between point {@link a} & {@link b} */ -export function distance(a: PointReadOnly, b: PointReadOnly): number { +export function distance(a: ReadOnlyPoint, b: ReadOnlyPoint): number { return Math.sqrt( (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) ) @@ -18,21 +16,21 @@ export function distance(a: PointReadOnly, b: PointReadOnly): number { /** * 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 + * @param a Point a as `x, y` + * @param b Point b as `x, y` + * @returns Distance2 (squared) between point {@link a} & {@link b} */ -export function dist2(a: PointReadOnly, b: PointReadOnly): number { +export function dist2(a: ReadOnlyPoint, b: ReadOnlyPoint): 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 + * @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 { +export function isPointInRectangle(point: ReadOnlyPoint, rect: ReadOnlyRect): boolean { return rect[0] < point[0] && rect[0] + rect[2] > point[0] && rect[1] < point[1] @@ -47,7 +45,7 @@ export function isPointInRectangle(point: PointReadOnly, rect: Rect): boolean { * @param top Rect y * @param width Rect width * @param height Rect height - * @returns true if the point is inside the rect, otherwise false + * @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 @@ -61,7 +59,7 @@ export function isInsideRectangle(x: number, y: number, left: number, top: numbe * @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 + * @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): boolean { const sum = Math.min(radius, Math.abs(x)) + Math.min(radius, Math.abs(y)) @@ -70,11 +68,11 @@ export function isSortaInsideOctagon(x: number, y: number, radius: number): bool /** * 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 + * @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 { +export function overlapBounding(a: ReadOnlyRect, b: ReadOnlyRect): boolean { const aRight = a[0] + a[2] const aBottom = a[1] + a[3] const bRight = b[0] + b[2] @@ -88,6 +86,36 @@ export function overlapBounding(a: Rect, b: Rect): boolean { : true } +/** + * Determines if rectangle {@link a} contains the centre point of rectangle {@link b}. + * @param a Container rectangle A as `x, y, width, height` + * @param b Sub-rectangle B as `x, y, width, height` + * @returns `true` if {@link a} contains most of {@link b}, otherwise `false` + */ +export function containsCentre(a: ReadOnlyRect, b: ReadOnlyRect): boolean { + const centreX = b[0] + (b[2] * 0.5) + const centreY = b[1] + (b[3] * 0.5) + return isInsideRectangle(centreX, centreY, a[0], a[1], a[2], a[3]) +} + +/** + * Determines if rectangle {@link a} wholly contains rectangle {@link b}. + * @param a Container rectangle A as `x, y, width, height` + * @param b Sub-rectangle B as `x, y, width, height` + * @returns `true` if {@link a} wholly contains {@link b}, otherwise `false` + */ +export function containsRect(a: ReadOnlyRect, b: ReadOnlyRect): 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] < b[0] + && a[1] < b[1] + && aRight > bRight + && aBottom > bBottom +} + /** * Adds an offset in the specified direction to {@link out} * @param amount Amount of offset to add @@ -185,6 +213,34 @@ export function rotateLink(offset: Point, from: LinkDirection, to: LinkDirection * @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 { +export function getOrientation(lineStart: ReadOnlyPoint, lineEnd: ReadOnlyPoint, x: number, y: number): number { return ((lineEnd[1] - lineStart[1]) * (x - lineEnd[0])) - ((lineEnd[0] - lineStart[0]) * (y - lineEnd[1])) } + +/** + * + * @param out The array to store the point in + * @param a Start point + * @param b End point + * @param controlA Start curve control point + * @param controlB End curve control point + * @param t Time: factor of distance to travel along the curve (e.g 0.25 is 25% along the curve) + */ +export function findPointOnCurve( + out: Point, + a: ReadOnlyPoint, + b: ReadOnlyPoint, + controlA: ReadOnlyPoint, + controlB: ReadOnlyPoint, + t: number = 0.5, +): void { + const iT = 1 - t + + const c1 = iT * iT * iT + const c2 = 3 * (iT * iT) * t + const c3 = 3 * iT * (t * t) + const c4 = t * t * t + + out[0] = (c1 * a[0]) + (c2 * controlA[0]) + (c3 * controlB[0]) + (c4 * b[0]) + out[1] = (c1 * a[1]) + (c2 * controlA[1]) + (c3 * controlB[1]) + (c4 * b[1]) +} diff --git a/src/types/serialisation.ts b/src/types/serialisation.ts index 20c4a2aac6..24ae6b8854 100644 --- a/src/types/serialisation.ts +++ b/src/types/serialisation.ts @@ -22,7 +22,7 @@ export interface Serialisable { /** Serialised LGraphNode */ export interface ISerialisedNode { title?: string - id?: NodeId + id: NodeId type?: string pos?: Point size?: Size