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

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