Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADD: Automatically move the viewport when dragging an item close to t… #517

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
8 changes: 8 additions & 0 deletions src/DragAndScale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export class DragAndScale {
dragging?: boolean
viewport?: Rect

/** Auto canvas movement multiplier */
move_factor: number
/** Threshold to trigger auto canvas movement RANGE[0,1] */
move_threshold: number

onredraw?(das: DragAndScale): void
/** @deprecated */
onmouse?(e: unknown): boolean
Expand Down Expand Up @@ -62,6 +67,9 @@ export class DragAndScale {
this.element = null
this.visible_area = new Float32Array(4)

this.move_factor = 350
this.move_threshold = 0.95

if (element) {
this.element = element
if (!skip_events) {
Expand Down
70 changes: 70 additions & 0 deletions src/LGraphCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
createBounds,
isInRect,
snapPoint,
getVector2Clamped,
} from "./measure"
import { strokeShape } from "./draw"
import { DragAndScale } from "./DragAndScale"
Expand Down Expand Up @@ -519,6 +520,17 @@ export class LGraphCanvas implements ConnectionColorContext {
/** The start position of the drag zoom. */
#dragZoomStart: { pos: Point, scale: number } | null = null

/** If true, enable automatic panning of the viewport when interacting close to canvas edges. */
auto_panning_enabled: boolean = false
/**
* Threshold to trigger automatic panning. Works based of the percent distance between the center of the canvas and its edges
* @remarks A value of 1.0 basically disables automatic panning.
*/
auto_panning_threshold: number = 0.95
/** Defines the speed in which the viewport will pan automatic */
auto_panning_speed: number = 350
auto_panning_event: ReturnType<typeof setInterval> | null = null

getMenuOptions?(): IContextMenuValue[]
getExtraMenuOptions?(
canvas: LGraphCanvas,
Expand Down Expand Up @@ -1930,6 +1942,51 @@ export class LGraphCanvas implements ConnectionColorContext {
this.last_mouseclick = 0
}

/**
* Automatically moves the canvas to follow the mouse.
* @param deltaT Time difference since last frame
* @remarks Does not require items to be selected to work properly. Could add ramp up/down for smoother movement transition.
* */
autoPanCanvas(deltaT: number): void {
const moveDirection = getVector2Clamped(
this.mouse,
[this.canvas.clientWidth / 2, this.canvas.clientHeight / 2],
)
if (Math.abs(moveDirection[0]) > this.auto_panning_threshold || Math.abs(moveDirection[1]) > this.auto_panning_threshold) {
const { scale } = this.ds
const newOffsetX: number = (moveDirection[0] * deltaT * this.auto_panning_speed) / scale
const newOffsetY: number = (moveDirection[1] * deltaT * this.auto_panning_speed) / scale

this.ds.offset[0] -= newOffsetX
this.ds.offset[1] -= newOffsetY

this.#dirty()
if (this.connecting_links) { // Prevent selected Positionables from moving while dragging a link
// Update graph_mouse to prevent link from staying behind while dragging
this.graph_mouse[0] += newOffsetX
this.graph_mouse[1] += newOffsetY
return
}
for (const item of this.selectedItems) {
item.move(newOffsetX, newOffsetY, false)
}
}
}

setAutoPanCanvas(enabled: boolean, interval: number = 30) { // 30 ms == roughly 33 fps
if (!this.auto_panning_enabled) return
if (enabled) {
if (this.auto_panning_event) clearInterval(this.auto_panning_event)
this.auto_panning_event = setInterval(() => {
this.autoPanCanvas(interval * 0.001)
}, interval)
return
} else {
clearInterval(this.auto_panning_event)
this.auto_panning_event = null
}
}

/**
* Gets the widget at the current cursor position
* @param node Optional node to check for widgets under cursor
Expand Down Expand Up @@ -2434,6 +2491,9 @@ export class LGraphCanvas implements ConnectionColorContext {
}
}

// enable canvas auto panning for link output
this.setAutoPanCanvas(true)

// TODO: Move callbacks to the start of this closure (onInputClick is already correct).
pointer.onDoubleClick = () => node.onOutputDblClick?.(i, e)
pointer.onClick = () => node.onOutputClick?.(i, e)
Expand Down Expand Up @@ -2481,6 +2541,9 @@ export class LGraphCanvas implements ConnectionColorContext {
}

this.dirty_bgcanvas = true

// enable canvas auto panning for disconnected link input
this.setAutoPanCanvas(true)
}
}
if (!pointer.onDragStart) {
Expand All @@ -2495,6 +2558,9 @@ export class LGraphCanvas implements ConnectionColorContext {
pointer.onDragStart = () => connecting.input = input

this.dirty_bgcanvas = true

// enable canvas auto panning for link input
this.setAutoPanCanvas(true)
}

// pointer.finally = () => this.connecting_links = null
Expand Down Expand Up @@ -2971,10 +3037,12 @@ export class LGraphCanvas implements ConnectionColorContext {
this.isDragging = false
this.graph.afterChange()
this.emitAfterChange()
this.setAutoPanCanvas(false)
}

this.processSelect(item, pointer.eDown, sticky)
this.isDragging = true
this.setAutoPanCanvas(true)
}

/**
Expand Down Expand Up @@ -3125,6 +3193,8 @@ export class LGraphCanvas implements ConnectionColorContext {
}
}
}
// disable canvas auto panning for links
this.setAutoPanCanvas(false)
} else {
this.dirty_canvas = true

Expand Down
16 changes: 16 additions & 0 deletions src/measure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,19 @@ export function snapPoint(pos: Point | Rect, snapTo: number): boolean {
pos[1] = snapTo * Math.round(pos[1] / snapTo)
return true
}

/**
* Calculates the clamped vector between two points.
* @param pointA The first point
* @param pointB The second point
* @returns Vector2 {@link Point} of pointA to pointB, clamped to the range [-1, 1]
* @remarks Calculates a pseudo "normalized" Vector2 by clamping it in range. Faster than using SQRT for truly normalized vectors. DO NOT use as a true unit vector.
* */
export function getVector2Clamped(pointA: Point, pointB: Point): Point {
const x: number = (pointA[0] - pointB[0]) / pointB[0]
const y: number = (pointA[1] - pointB[1]) / pointB[1]
return [
Math.max(-1, Math.min(1, x)),
Math.max(-1, Math.min(1, y)),
]
}