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:
filtered
2024-11-17 09:35:30 +11:00
committed by GitHub
parent f26f7dbe2c
commit b29a32c1ae
14 changed files with 1322 additions and 1419 deletions

View File

@@ -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
View 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)
}
}

View File

@@ -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) {

View File

@@ -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)
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 {

View File

@@ -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

View File

@@ -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]

View File

@@ -219,7 +219,7 @@ export interface IContextMenuOptions extends IContextMenuBase {
scroll_speed?: number
left?: number
top?: number
scale?: string
scale?: number
node?: LGraphNode
autoopen?: boolean
}

View File

@@ -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 {

View File

@@ -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)
}
/**

View File

@@ -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

View File

@@ -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,

View File

@@ -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,