mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 23:50:08 +00:00
Add CanvasPointer API (#308)
* Add position rounding feature Replaces previous impls. which only worked on some items, and were triggered when unexpected e.g. clicking a node that hadn't been moved. Update test expectations * Narrow TS types - readonly * nit - Clean up, Doc * nit - Clean up legacy accessors Marks as deprecated * Fix TS type - IContextMenuOptions.scale * [Refactor] dist2 for use in pointer API * Add CanvasPointer - API for pointer events Add TS strict types Add final click drag distance math Add option to retain events * nit - Rename * nit * Remove Subgraph - unused & not maintained * Remove live_mode Unused, not maintained. * Update README Remove live_mode reference * Update delete selected - include reroutes & groups * Bypass link menu if alt/shift pressed * Remove old dragged_node interface Incomplete impl. - unused. Superceded by selectedItems * Fix top/left edge of rectangles not in hitbox * [Refactor] Match function names to interface names * Add interface to find widgets by Point LGraphNode.getWidgetOnPos * Add widget search param - includeDisabled * nit - Doc * Rewrite canvas mouse handling - Rewrites most pointer handling to use CanvasPointer callbacks - All callbacks are declared ahead of time during the initial pointerdown event, logically grouped together - Drastically simplifies the alteration or creation of new click / drag interactions - Click events are all clicks, rather than some processed on mouse down, others on mouse up - Functions return instead of setting and repeatedly checking multiple state vars - Removes all lines that needed THIRTEEN tab indents * Split middle click out from processMouseDown * Use pointer API for link menus * Narrow canvas event interfaces * Fix canvas event types Replaces original workarounds with final types * Refactor - deprecated isInsideRectangle * Add canvas hovering over state - Centralises cursor set behaviour - Provides simple downstream override * nit * [Refactor] Use measure functions * Add double-click API to CanvasPointer a * nit - Doc * Allow larger gap between double click events * Rewrite double-click into CanvasPointer actions * Improve double-click UX Prefer down events over up events * Add production defaults * Add middle-click handling * Remove debug code * Remove redundant code * Fix add reroute alt-click adds two undo steps * Fix click on connected input disconnects Old behaviour was to disconnect, then recreate a new link on drop. * Add module export: CanvasPointer
This commit is contained in:
@@ -32,7 +32,6 @@ Try it in the [demo site](https://tamats.com/projects/litegraph/editor).
|
||||
- Customizable theme (colors, shapes, background)
|
||||
- Callbacks to personalize every action/drawing/event of nodes
|
||||
- Subgraphs (nodes that contain graphs themselves)
|
||||
- Live mode system (hides the graph but calls nodes to render whatever they want, useful to create UIs)
|
||||
- Graphs can be executed in NodeJS
|
||||
- Highly customizable nodes (color, shape, slots vertical or horizontal, widgets, custom rendering)
|
||||
- Easy to integrate in any JS application (one single file, no dependencies)
|
||||
|
||||
268
src/CanvasPointer.ts
Normal file
268
src/CanvasPointer.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
import type { CanvasPointerEvent } from "./types/events"
|
||||
import type { LGraphCanvas } from "./LGraphCanvas"
|
||||
import { dist2 } from "./measure"
|
||||
|
||||
/**
|
||||
* Allows click and drag actions to be declared ahead of time during a pointerdown event.
|
||||
*
|
||||
* Depending on whether the user clicks or drags the pointer, only the appropriate callbacks are called:
|
||||
* - {@link onClick}
|
||||
* - {@link onDoubleClick}
|
||||
* - {@link onDragStart}
|
||||
* - {@link onDrag}
|
||||
* - {@link onDragEnd}
|
||||
* - {@link finally}
|
||||
*
|
||||
* @seealso
|
||||
* - {@link LGraphCanvas.processMouseDown}
|
||||
* - {@link LGraphCanvas.processMouseMove}
|
||||
* - {@link LGraphCanvas.processMouseUp}
|
||||
*/
|
||||
export class CanvasPointer {
|
||||
/** Maximum time in milliseconds to ignore click drift */
|
||||
static bufferTime = 150
|
||||
|
||||
/** Maximum gap between pointerup and pointerdown events to be considered as a double click */
|
||||
static doubleClickTime = 300
|
||||
|
||||
/** Maximum offset from click location */
|
||||
static get maxClickDrift() {
|
||||
return this.#maxClickDrift
|
||||
}
|
||||
static set maxClickDrift(value) {
|
||||
this.#maxClickDrift = value
|
||||
this.#maxClickDrift2 = value * value
|
||||
}
|
||||
static #maxClickDrift = 6
|
||||
/** {@link maxClickDrift} squared. Used to calculate click drift without `sqrt`. */
|
||||
static #maxClickDrift2 = this.#maxClickDrift ** 2
|
||||
|
||||
/** The element this PointerState should capture input against when dragging. */
|
||||
element: Element
|
||||
/** Pointer ID used by drag capture. */
|
||||
pointerId: number
|
||||
|
||||
/** Set to true when if the pointer moves far enough after a down event, before the corresponding up event is fired. */
|
||||
dragStarted: boolean = false
|
||||
|
||||
/** The {@link eUp} from the last successful click */
|
||||
eLastDown?: CanvasPointerEvent
|
||||
|
||||
/** Used downstream for touch event support. */
|
||||
isDouble: boolean = false
|
||||
/** Used downstream for touch event support. */
|
||||
isDown: boolean = false
|
||||
|
||||
/**
|
||||
* If `true`, {@link eDown}, {@link eMove}, and {@link eUp} will be set to
|
||||
* `null` when {@link reset} is called.
|
||||
*
|
||||
* Default: `true`
|
||||
*/
|
||||
clearEventsOnReset: boolean = true
|
||||
|
||||
/** The last pointerdown event for the primary button */
|
||||
eDown: CanvasPointerEvent | null = null
|
||||
/** The last pointermove event for the primary button */
|
||||
eMove: CanvasPointerEvent | null = null
|
||||
/** The last pointerup event for the primary button */
|
||||
eUp: CanvasPointerEvent | null = null
|
||||
|
||||
/** If set, as soon as the mouse moves outside the click drift threshold, this action is run once. */
|
||||
onDragStart?(): unknown
|
||||
|
||||
/**
|
||||
* Called on pointermove whilst dragging.
|
||||
* @param eMove The pointermove event of this ongoing drag action
|
||||
*/
|
||||
onDrag?(eMove: CanvasPointerEvent): unknown
|
||||
|
||||
/**
|
||||
* Called on pointerup after dragging (i.e. not called if clicked).
|
||||
* @param upEvent The pointerup or pointermove event that triggered this callback
|
||||
*/
|
||||
onDragEnd?(upEvent: CanvasPointerEvent): unknown
|
||||
|
||||
/**
|
||||
* Callback that will be run once, the next time a pointerup event appears to be a normal click.
|
||||
* @param upEvent The pointerup or pointermove event that triggered this callback
|
||||
*/
|
||||
onClick?(upEvent: CanvasPointerEvent): unknown
|
||||
|
||||
/**
|
||||
* Callback that will be run once, the next time a pointerup event appears to be a normal click.
|
||||
* @param upEvent The pointerup or pointermove event that triggered this callback
|
||||
*/
|
||||
onDoubleClick?(upEvent: CanvasPointerEvent): unknown
|
||||
|
||||
/**
|
||||
* Run-once callback, called at the end of any click or drag, whether or not it was successful in any way.
|
||||
*
|
||||
* The setter of this callback will call the existing value before replacing it.
|
||||
* Therefore, simply setting this value twice will execute the first callback.
|
||||
*/
|
||||
get finally() {
|
||||
return this.#finally
|
||||
}
|
||||
set finally(value) {
|
||||
try {
|
||||
this.#finally?.()
|
||||
} finally {
|
||||
this.#finally = value
|
||||
}
|
||||
}
|
||||
#finally?: () => unknown
|
||||
|
||||
constructor(element: Element) {
|
||||
this.element = element
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for `pointerdown` events. To be used as the event handler (or called by it).
|
||||
* @param e The `pointerdown` event
|
||||
*/
|
||||
down(e: CanvasPointerEvent): void {
|
||||
this.reset()
|
||||
this.eDown = e
|
||||
this.pointerId = e.pointerId
|
||||
this.element.setPointerCapture(e.pointerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for `pointermove` events. To be used as the event handler (or called by it).
|
||||
* @param e The `pointermove` event
|
||||
*/
|
||||
move(e: CanvasPointerEvent): void {
|
||||
const { eDown } = this
|
||||
if (!eDown) return
|
||||
|
||||
// No buttons down, but eDown exists - clean up & leave
|
||||
if (!e.buttons) {
|
||||
this.reset()
|
||||
return
|
||||
}
|
||||
|
||||
// Primary button released - treat as pointerup.
|
||||
if (!(e.buttons & eDown.buttons)) {
|
||||
this.#completeClick(e)
|
||||
this.reset()
|
||||
return
|
||||
}
|
||||
this.eMove = e
|
||||
this.onDrag?.(e)
|
||||
|
||||
// Dragging, but no callback to run
|
||||
if (this.dragStarted) return
|
||||
|
||||
const longerThanBufferTime = e.timeStamp - eDown.timeStamp > CanvasPointer.bufferTime
|
||||
if (longerThanBufferTime || !this.#hasSamePosition(e, eDown)) {
|
||||
this.#setDragStarted()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for `pointerup` events. To be used as the event handler (or called by it).
|
||||
* @param e The `pointerup` event
|
||||
*/
|
||||
up(e: CanvasPointerEvent): boolean {
|
||||
if (e.button !== this.eDown?.button) return false
|
||||
|
||||
this.#completeClick(e)
|
||||
const { dragStarted } = this
|
||||
this.reset()
|
||||
return !dragStarted
|
||||
}
|
||||
|
||||
#completeClick(e: CanvasPointerEvent): void {
|
||||
const { eDown } = this
|
||||
if (!eDown) return
|
||||
|
||||
this.eUp = e
|
||||
|
||||
if (this.dragStarted) {
|
||||
// A move event already started drag
|
||||
this.onDragEnd?.(e)
|
||||
} else if (!this.#hasSamePosition(e, eDown)) {
|
||||
// Teleport without a move event (e.g. tab out, move, tab back)
|
||||
this.#setDragStarted()
|
||||
this.onDragEnd?.(e)
|
||||
} else if (this.onDoubleClick && this.#isDoubleClick()) {
|
||||
// Double-click event
|
||||
this.onDoubleClick(e)
|
||||
this.eLastDown = undefined
|
||||
} else {
|
||||
// Normal click event
|
||||
this.onClick?.(e)
|
||||
this.eLastDown = eDown
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if two events occurred near each other - not further apart than the maximum click drift.
|
||||
* @param a The first event to compare
|
||||
* @param b The second event to compare
|
||||
* @param tolerance2 The maximum distance (squared) before the positions are considered different
|
||||
* @returns `true` if the two events were no more than {@link maxClickDrift} apart, otherwise `false`
|
||||
*/
|
||||
#hasSamePosition(
|
||||
a: PointerEvent,
|
||||
b: PointerEvent,
|
||||
tolerance2 = CanvasPointer.#maxClickDrift2,
|
||||
): boolean {
|
||||
const drift = dist2(a.clientX, a.clientY, b.clientX, b.clientY)
|
||||
return drift <= tolerance2
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the pointer is currently past the max click drift threshold.
|
||||
* @param e The latest pointer event
|
||||
* @returns `true` if the latest pointer event is past the the click drift threshold
|
||||
*/
|
||||
#isDoubleClick(): boolean {
|
||||
const { eDown, eLastDown } = this
|
||||
if (!eDown || !eLastDown) return false
|
||||
|
||||
// Use thrice the drift distance for double-click gap
|
||||
const tolerance2 = (3 * CanvasPointer.#maxClickDrift) ** 2
|
||||
const diff = eDown.timeStamp - eLastDown.timeStamp
|
||||
return diff > 0 &&
|
||||
diff < CanvasPointer.doubleClickTime &&
|
||||
this.#hasSamePosition(eDown, eLastDown, tolerance2)
|
||||
}
|
||||
|
||||
#setDragStarted(): void {
|
||||
this.dragStarted = true
|
||||
this.onDragStart?.()
|
||||
delete this.onDragStart
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the state of this {@link CanvasPointer} instance.
|
||||
*
|
||||
* The {@link finally} callback is first executed, then all callbacks and intra-click
|
||||
* state is cleared.
|
||||
*/
|
||||
reset(): void {
|
||||
// The setter executes the callback before clearing it
|
||||
this.finally = undefined
|
||||
delete this.onClick
|
||||
delete this.onDoubleClick
|
||||
delete this.onDragStart
|
||||
delete this.onDrag
|
||||
delete this.onDragEnd
|
||||
|
||||
this.isDown = false
|
||||
this.isDouble = false
|
||||
this.dragStarted = false
|
||||
|
||||
if (this.clearEventsOnReset) {
|
||||
this.eDown = null
|
||||
this.eMove = null
|
||||
this.eUp = null
|
||||
}
|
||||
|
||||
const { element, pointerId } = this
|
||||
if (element.hasPointerCapture(pointerId))
|
||||
element.releasePointerCapture(pointerId)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Point, Rect, Rect32 } from "./interfaces"
|
||||
import type { CanvasMouseEvent } from "./types/events"
|
||||
import { LiteGraph } from "./litegraph"
|
||||
import { isInRect } from "./measure"
|
||||
|
||||
export class DragAndScale {
|
||||
/** Maximum scale (zoom in) */
|
||||
@@ -98,7 +99,7 @@ export class DragAndScale {
|
||||
e.canvasy = y
|
||||
e.dragging = this.dragging
|
||||
|
||||
const is_inside = !this.viewport || (this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]))
|
||||
const is_inside = !this.viewport || isInRect(x, y, this.viewport)
|
||||
|
||||
let ignore = false
|
||||
if (this.onmouse) {
|
||||
|
||||
@@ -826,9 +826,6 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
|
||||
const canvas = this.list_of_graphcanvas[i]
|
||||
if (canvas.selected_nodes[node.id])
|
||||
delete canvas.selected_nodes[node.id]
|
||||
|
||||
if (canvas.node_dragged == node)
|
||||
canvas.node_dragged = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1222,18 +1219,6 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
|
||||
// @ts-expect-error
|
||||
this.canvasAction(c => c.onConnectionChange?.())
|
||||
}
|
||||
/**
|
||||
* returns if the graph is in live mode
|
||||
*/
|
||||
isLive(): boolean {
|
||||
if (!this.list_of_graphcanvas) return false
|
||||
|
||||
for (let i = 0; i < this.list_of_graphcanvas.length; ++i) {
|
||||
const c = this.list_of_graphcanvas[i]
|
||||
if (c.live_mode) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
/**
|
||||
* clears the triggered slot animation in all links (stop visual animation)
|
||||
*/
|
||||
@@ -1334,7 +1319,7 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
|
||||
|
||||
const node = this.getNodeById(link.target_id)
|
||||
node?.disconnectInput(link.target_slot)
|
||||
|
||||
|
||||
link.disconnect(this)
|
||||
}
|
||||
|
||||
|
||||
2278
src/LGraphCanvas.ts
2278
src/LGraphCanvas.ts
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
import type { IContextMenuValue, IPinnable, Point, Positionable, ReadOnlyRect, Size } from "./interfaces"
|
||||
import type { IContextMenuValue, IPinnable, Point, Positionable, Size } from "./interfaces"
|
||||
import type { LGraph } from "./LGraph"
|
||||
import type { ISerialisedGroup } from "./types/serialisation"
|
||||
import { LiteGraph } from "./litegraph"
|
||||
import { LGraphCanvas } from "./LGraphCanvas"
|
||||
import { containsCentre, containsRect, isInsideRectangle, isPointInRectangle, createBounds } from "./measure"
|
||||
import { containsCentre, containsRect, isInRectangle, isPointInRect, createBounds } from "./measure"
|
||||
import { LGraphNode } from "./LGraphNode"
|
||||
import { RenderShape, TitleMode } from "./types/globalEnums"
|
||||
|
||||
@@ -64,7 +64,6 @@ export class LGraphGroup implements Positionable, IPinnable {
|
||||
this._size[1] = Math.max(LGraphGroup.minHeight, v[1])
|
||||
}
|
||||
|
||||
|
||||
get boundingRect() {
|
||||
return this._bounding
|
||||
}
|
||||
@@ -209,7 +208,7 @@ export class LGraphGroup implements Positionable, IPinnable {
|
||||
|
||||
// Move reroutes we overlap the centre point of
|
||||
for (const reroute of reroutes.values()) {
|
||||
if (isPointInRectangle(reroute.pos, this._bounding))
|
||||
if (isPointInRect(reroute.pos, this._bounding))
|
||||
children.add(reroute)
|
||||
}
|
||||
|
||||
@@ -283,8 +282,8 @@ export class LGraphGroup implements Positionable, IPinnable {
|
||||
}
|
||||
|
||||
isPointInTitlebar(x: number, y: number): boolean {
|
||||
const b = this._bounding
|
||||
return isInsideRectangle(x, y, b[0], b[1], b[2], this.titleHeight)
|
||||
const b = this.boundingRect
|
||||
return isInRectangle(x, y, b[0], b[1], b[2], this.titleHeight)
|
||||
}
|
||||
|
||||
isInResize(x: number, y: number): boolean {
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { Reroute, RerouteId } from "./Reroute"
|
||||
import { LGraphEventMode, NodeSlotType, TitleMode, RenderShape } from "./types/globalEnums"
|
||||
import { BadgePosition, LGraphBadge } from "./LGraphBadge"
|
||||
import { type LGraphNodeConstructor, LiteGraph } from "./litegraph"
|
||||
import { isInsideRectangle, isXyInRectangle } from "./measure"
|
||||
import { isInRectangle, isInRect } from "./measure"
|
||||
import { LLink } from "./LLink"
|
||||
|
||||
export type NodeId = number | string
|
||||
@@ -322,8 +322,11 @@ export class LGraphNode implements Positionable, IPinnable {
|
||||
onGetOutputs?(this: LGraphNode): INodeOutputSlot[]
|
||||
onMouseUp?(this: LGraphNode, e: CanvasMouseEvent, pos: Point): void
|
||||
onMouseEnter?(this: LGraphNode, e: CanvasMouseEvent): void
|
||||
/** Blocks drag if return value is truthy. @param pos Offset from {@link LGraphNode.pos}. */
|
||||
onMouseDown?(this: LGraphNode, e: CanvasMouseEvent, pos: Point, canvas: LGraphCanvas): boolean
|
||||
/** @param pos Offset from {@link LGraphNode.pos}. */
|
||||
onDblClick?(this: LGraphNode, e: CanvasMouseEvent, pos: Point, canvas: LGraphCanvas): void
|
||||
/** @param pos Offset from {@link LGraphNode.pos}. */
|
||||
onNodeTitleDblClick?(this: LGraphNode, e: CanvasMouseEvent, pos: Point, canvas: LGraphCanvas): void
|
||||
onDrawTitle?(this: LGraphNode, ctx: CanvasRenderingContext2D): void
|
||||
onDrawTitleText?(this: LGraphNode, ctx: CanvasRenderingContext2D, title_height: number, size: Size, scale: number, title_text_font: string, selected: boolean): void
|
||||
@@ -1330,7 +1333,7 @@ export class LGraphNode implements Positionable, IPinnable {
|
||||
inResizeCorner(canvasX: number, canvasY: number): boolean {
|
||||
const rows = this.outputs ? this.outputs.length : 1
|
||||
const outputs_offset = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT
|
||||
return isInsideRectangle(canvasX,
|
||||
return isInRectangle(canvasX,
|
||||
canvasY,
|
||||
this.pos[0] + this.size[0] - 15,
|
||||
this.pos[1] + Math.max(this.size[1] - 15, outputs_offset),
|
||||
@@ -1518,7 +1521,7 @@ export class LGraphNode implements Positionable, IPinnable {
|
||||
* @return {boolean}
|
||||
*/
|
||||
isPointInside(x: number, y: number): boolean {
|
||||
return isXyInRectangle(x, y, this.boundingRect)
|
||||
return isInRect(x, y, this.boundingRect)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1529,7 +1532,7 @@ export class LGraphNode implements Positionable, IPinnable {
|
||||
*/
|
||||
isPointInCollapse(x: number, y: number): boolean {
|
||||
const squareLength = LiteGraph.NODE_TITLE_HEIGHT
|
||||
return isInsideRectangle(x, y, this.pos[0], this.pos[1] - squareLength, squareLength, squareLength)
|
||||
return isInRectangle(x, y, this.pos[0], this.pos[1] - squareLength, squareLength, squareLength)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1545,7 +1548,7 @@ export class LGraphNode implements Positionable, IPinnable {
|
||||
for (let i = 0, l = this.inputs.length; i < l; ++i) {
|
||||
const input = this.inputs[i]
|
||||
this.getConnectionPos(true, i, link_pos)
|
||||
if (isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20, 10)) {
|
||||
if (isInRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20, 10)) {
|
||||
return { input, slot: i, link_pos }
|
||||
}
|
||||
}
|
||||
@@ -1555,7 +1558,7 @@ export class LGraphNode implements Positionable, IPinnable {
|
||||
for (let i = 0, l = this.outputs.length; i < l; ++i) {
|
||||
const output = this.outputs[i]
|
||||
this.getConnectionPos(false, i, link_pos)
|
||||
if (isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20, 10)) {
|
||||
if (isInRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20, 10)) {
|
||||
return { output, slot: i, link_pos }
|
||||
}
|
||||
}
|
||||
@@ -1564,6 +1567,36 @@ export class LGraphNode implements Positionable, IPinnable {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the widget on this node at the given co-ordinates.
|
||||
* @param canvasX X co-ordinate in graph space
|
||||
* @param canvasY Y co-ordinate in graph space
|
||||
* @returns The widget found, otherwise `null`
|
||||
*/
|
||||
getWidgetOnPos(canvasX: number, canvasY: number, includeDisabled = false): IWidget | null {
|
||||
const { widgets, pos, size } = this
|
||||
if (!widgets?.length) return null
|
||||
|
||||
const x = canvasX - pos[0]
|
||||
const y = canvasY - pos[1]
|
||||
const nodeWidth = size[0]
|
||||
|
||||
for (const widget of widgets) {
|
||||
if (!widget || (widget.disabled && !includeDisabled) || widget.hidden || (widget.advanced && !this.showAdvanced)) continue
|
||||
|
||||
const h = widget.computeSize
|
||||
? widget.computeSize(nodeWidth)[1]
|
||||
: LiteGraph.NODE_WIDGET_HEIGHT
|
||||
const w = widget.width || nodeWidth
|
||||
if (
|
||||
widget.last_y !== undefined &&
|
||||
isInRectangle(x, y, 6, widget.last_y, w - 12, h)
|
||||
)
|
||||
return widget
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input slot with a given name (used for dynamic slots), -1 if not found
|
||||
* @param name the name of the slot
|
||||
|
||||
@@ -132,6 +132,8 @@ export class LiteGraphGlobal {
|
||||
ctrl_alt_click_do_break_link = true // [true!] who accidentally ctrl-alt-clicks on an in/output? nobody! that's who!
|
||||
snaps_for_comfy = true // [true!] snaps links when dragging connections over valid targets
|
||||
snap_highlights_node = true // [true!] renders a partial border to highlight when a dragged link is snapped to a node
|
||||
/** After moving items on the canvas, their positions will be rounded. Effectively "snap to grid" with a grid size of 1. */
|
||||
always_round_positions = false
|
||||
|
||||
search_hide_on_mouse_leave = true // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false
|
||||
search_filter_enabled = false // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out]
|
||||
|
||||
@@ -219,7 +219,7 @@ export interface IContextMenuOptions extends IContextMenuBase {
|
||||
scroll_speed?: number
|
||||
left?: number
|
||||
top?: number
|
||||
scale?: string
|
||||
scale?: number
|
||||
node?: LGraphNode
|
||||
autoopen?: boolean
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export { LGraphBadge, BadgePosition }
|
||||
export { SlotShape, LabelPosition, SlotDirection, SlotType }
|
||||
export { EaseFunction, LinkMarkerShape } from "./types/globalEnums"
|
||||
export type { SerialisableGraph, SerialisableLLink } from "./types/serialisation"
|
||||
export { CanvasPointer } from "./CanvasPointer"
|
||||
export { createBounds } from "./measure"
|
||||
|
||||
export function clamp(v: number, a: number, b: number): number {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Point, Positionable, ReadOnlyPoint, ReadOnlyRect } from "./interfaces"
|
||||
import type { Point, Positionable, ReadOnlyPoint, ReadOnlyRect, Rect } from "./interfaces"
|
||||
import { LinkDirection } from "./types/globalEnums"
|
||||
|
||||
/**
|
||||
@@ -16,43 +16,70 @@ export function distance(a: ReadOnlyPoint, b: ReadOnlyPoint): 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 {@link a} & {@link b}
|
||||
* @param x1 Origin point X
|
||||
* @param y1 Origin point Y
|
||||
* @param x2 Destination point X
|
||||
* @param y2 Destination point Y
|
||||
* @returns Distance2 (squared) between point [{@link x1}, {@link y1}] & [{@link x2}, {@link y2}]
|
||||
*/
|
||||
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]))
|
||||
export function dist2(x1: number, y1: number, x2: number, y2: number): number {
|
||||
return ((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a point is inside a rectangle.
|
||||
*
|
||||
* Otherwise identical to {@link isInsideRectangle}, it also returns `true` if `x` equals `left` or `y` equals `top`.
|
||||
* @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 isInRectangle(x: number, y: number, left: number, top: number, width: number, height: number): boolean {
|
||||
return x >= left
|
||||
&& x < left + width
|
||||
&& y >= top
|
||||
&& y < top + height
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a {@link Point} is inside a {@link Rect}.
|
||||
* @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: ReadOnlyPoint, rect: ReadOnlyRect): boolean {
|
||||
return rect[0] <= point[0]
|
||||
&& rect[0] + rect[2] > point[0]
|
||||
&& rect[1] <= point[1]
|
||||
&& rect[1] + rect[3] > point[1]
|
||||
export function isPointInRect(point: ReadOnlyPoint, rect: ReadOnlyRect): boolean {
|
||||
return point[0] >= rect[0]
|
||||
&& point[0] < rect[0] + rect[2]
|
||||
&& point[1] >= rect[1]
|
||||
&& point[1] < rect[1] + rect[3]
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a point is inside a rectangle.
|
||||
* Determines whether the point represented by {@link x}, {@link y} is inside a {@link Rect}.
|
||||
* @param x X co-ordinate of the point to check
|
||||
* @param y Y co-ordinate of the point to check
|
||||
* @param rect The rectangle, as `x, y, width, height`
|
||||
* @returns `true` if the point is inside the rect, otherwise `false`
|
||||
*/
|
||||
export function isXyInRectangle(x: number, y: number, rect: ReadOnlyRect): boolean {
|
||||
return rect[0] <= x
|
||||
&& rect[0] + rect[2] > x
|
||||
&& rect[1] <= y
|
||||
&& rect[1] + rect[3] > y
|
||||
export function isInRect(x: number, y: number, rect: ReadOnlyRect): boolean {
|
||||
return x >= rect[0]
|
||||
&& x < rect[0] + rect[2]
|
||||
&& y >= rect[1]
|
||||
&& y < rect[1] + rect[3]
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a point is inside a rectangle.
|
||||
* Determines whether a point (`x, y`) is inside a rectangle.
|
||||
*
|
||||
* This is the original litegraph implementation. It returns `false` if `x` is equal to `left`, or `y` is equal to `top`.
|
||||
* @deprecated
|
||||
* Use {@link isInRectangle} to match inclusive of top left.
|
||||
* This function returns a false negative when an integer point (e.g. pixel) is on the leftmost or uppermost edge of a rectangle.
|
||||
*
|
||||
* @param x Point x
|
||||
* @param y Point y
|
||||
* @param left Rect x
|
||||
@@ -109,7 +136,7 @@ export function overlapBounding(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
||||
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 isXyInRectangle(centreX, centreY, a)
|
||||
return isInRect(centreX, centreY, a)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,41 +10,32 @@ import type { LGraphGroup } from "../LGraphGroup"
|
||||
/** For Canvas*Event - adds graph space co-ordinates (property names are shipped) */
|
||||
export interface ICanvasPosition {
|
||||
/** X co-ordinate of the event, in graph space (NOT canvas space) */
|
||||
canvasX?: number
|
||||
canvasX: number
|
||||
/** Y co-ordinate of the event, in graph space (NOT canvas space) */
|
||||
canvasY?: number
|
||||
canvasY: number
|
||||
}
|
||||
|
||||
/** For Canvas*Event */
|
||||
export interface IDeltaPosition {
|
||||
deltaX?: number
|
||||
deltaY?: number
|
||||
deltaX: number
|
||||
deltaY: number
|
||||
}
|
||||
|
||||
interface LegacyMouseEvent {
|
||||
/** @deprecated Part of DragAndScale mouse API - incomplete / not maintained */
|
||||
dragging?: boolean
|
||||
click_time?: number
|
||||
}
|
||||
|
||||
/** PointerEvent with canvasX/Y and deltaX/Y properties */
|
||||
export interface CanvasPointerEvent extends PointerEvent, CanvasMouseEvent { }
|
||||
|
||||
/** MouseEvent with canvasX/Y and deltaX/Y properties */
|
||||
export interface CanvasMouseEvent extends MouseEvent, ICanvasPosition, IDeltaPosition {
|
||||
/** @deprecated Part of DragAndScale mouse API - incomplete / not maintained */
|
||||
dragging?: boolean
|
||||
click_time?: number
|
||||
dataTransfer?: unknown
|
||||
}
|
||||
|
||||
/** WheelEvent with canvasX/Y properties */
|
||||
export interface CanvasWheelEvent extends WheelEvent, ICanvasPosition {
|
||||
dragging?: boolean
|
||||
click_time?: number
|
||||
dataTransfer?: unknown
|
||||
}
|
||||
export interface CanvasMouseEvent extends MouseEvent, Readonly<ICanvasPosition>, Readonly<IDeltaPosition>, LegacyMouseEvent { }
|
||||
|
||||
/** DragEvent with canvasX/Y and deltaX/Y properties */
|
||||
export interface CanvasDragEvent extends DragEvent, ICanvasPosition, IDeltaPosition { }
|
||||
|
||||
/** TouchEvent with canvasX/Y and deltaX/Y properties */
|
||||
export interface CanvasTouchEvent extends TouchEvent, ICanvasPosition, IDeltaPosition { }
|
||||
|
||||
export type CanvasEventDetail =
|
||||
GenericEventDetail
|
||||
| DragggingCanvasEventDetail
|
||||
|
||||
@@ -16,6 +16,22 @@ export enum RenderShape {
|
||||
HollowCircle = 7,
|
||||
}
|
||||
|
||||
/** Bit flags used to indicate what the pointer is currently hovering over. */
|
||||
export enum CanvasItem {
|
||||
/** No items / none */
|
||||
Nothing = 0,
|
||||
/** At least one node */
|
||||
Node = 1 << 0,
|
||||
/** At least one group */
|
||||
Group = 1 << 1,
|
||||
/** A reroute (not its path) */
|
||||
Reroute = 1 << 2,
|
||||
/** The path of a link */
|
||||
Link = 1 << 3,
|
||||
/** A resize in the bottom-right corner */
|
||||
ResizeSe = 1 << 4,
|
||||
}
|
||||
|
||||
/** The direction that a link point will flow towards - e.g. horizontal outputs are right by default */
|
||||
export enum LinkDirection {
|
||||
NONE = 0,
|
||||
|
||||
@@ -134,6 +134,7 @@ LiteGraphGlobal {
|
||||
"allow_multi_output_for_events": true,
|
||||
"allow_scripts": false,
|
||||
"alt_drag_do_clone_nodes": false,
|
||||
"always_round_positions": false,
|
||||
"auto_load_slot_types": false,
|
||||
"auto_sort_node_types": false,
|
||||
"catch_exceptions": true,
|
||||
|
||||
Reference in New Issue
Block a user