Add hover highlight and link dragging features (#222)

* Add new hover highlight and link dragging features

- Hovering effects added for node inputs and outputs
- Adds "mouseout" handler to properly clear node state when mousing off a node
- Fixes bug that causes outputs to always be dimmed out when connecting from inputs
- Slight performance improvement

* Add node highlight text colour option

* Add slot highlight colour fallback
This commit is contained in:
filtered
2024-10-28 07:57:41 +11:00
committed by GitHub
parent e27bbb1e71
commit 748d35b610
4 changed files with 92 additions and 43 deletions

View File

@@ -1530,12 +1530,14 @@ export class LGraphCanvas {
// why mousemove and mouseup were not binded here?
this._mousemove_callback = this.processMouseMove.bind(this)
this._mouseup_callback = this.processMouseUp.bind(this)
this._mouseout_callback = this.processMouseOut.bind(this)
LiteGraph.pointerListenerAdd(canvas, "down", this._mousedown_callback, true) //down do not need to store the binded
canvas.addEventListener("mousewheel", this._mousewheel_callback, false)
LiteGraph.pointerListenerAdd(canvas, "up", this._mouseup_callback, true) // CHECK: ??? binded or not
LiteGraph.pointerListenerAdd(canvas, "move", this._mousemove_callback)
canvas.addEventListener("pointerout", this._mouseout_callback)
canvas.addEventListener("contextmenu", this._doNothing)
canvas.addEventListener(
@@ -1573,6 +1575,7 @@ export class LGraphCanvas {
const ref_window = this.getCanvasWindow()
const document = ref_window.document
this.canvas.removeEventListener("pointerout", this._mouseout_callback)
LiteGraph.pointerListenerRemove(this.canvas, "move", this._mousemove_callback)
LiteGraph.pointerListenerRemove(this.canvas, "up", this._mouseup_callback)
LiteGraph.pointerListenerRemove(this.canvas, "down", this._mousedown_callback)
@@ -1730,6 +1733,34 @@ export class LGraphCanvas {
return null
}
/**
* Clears highlight and mouse-over information from nodes that should not have it.
*
* Intended to be called when the pointer moves away from a node.
* @param {LGraphNode} node The node that the mouse is now over
* @param {MouseEvent} e MouseEvent that is triggering this
*/
updateMouseOverNodes(node: LGraphNode, e: CanvasMouseEvent): void {
const nodes = this.graph._nodes
const l = nodes.length
for (let i = 0; i < l; ++i) {
if (nodes[i].mouseOver && node != nodes[i]) {
//mouse leave
nodes[i].mouseOver = null
this._highlight_input = null
this._highlight_pos = null
// Hover transitions
// TODO: Implement single lerp ease factor for current progress on hover in/out. In drawNode, multiply by ease factor and differential value (e.g. bg alpha +0.5).
nodes[i].lostFocusAt = LiteGraph.getTime()
this.node_over?.onMouseLeave?.(e)
this.node_over = null
this.dirty_canvas = true
}
}
}
processMouseDown(e: CanvasPointerEvent): boolean {
if (this.set_canvas_dirty_on_mouse_event)
@@ -2351,17 +2382,7 @@ export class LGraphCanvas {
}
//remove mouseover flag
for (let i = 0, l = this.graph._nodes.length; i < l; ++i) {
if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i]) {
//mouse leave
this.graph._nodes[i].mouseOver = false
if (this.node_over && this.node_over.onMouseLeave) {
this.node_over.onMouseLeave(e)
}
this.node_over = null
this.dirty_canvas = true
}
}
this.updateMouseOverNodes(node, e)
//mouse over a node
if (node) {
@@ -2369,16 +2390,30 @@ export class LGraphCanvas {
if (node.redraw_on_mouse)
this.dirty_canvas = true
//this.canvas.style.cursor = "move";
// For input/output hovering
const pos: Point = [0, 0] //to store the output of isOverNodeInput
const inputId = this.isOverNodeInput(node, e.canvasX, e.canvasY, pos)
const outputId = this.isOverNodeOutput(node, e.canvasX, e.canvasY, pos)
if (!node.mouseOver) {
//mouse enter
node.mouseOver = true
node.mouseOver = {
inputId: inputId,
outputId: outputId
}
this.node_over = node
this.dirty_canvas = true
node.onMouseEnter?.(e)
}
// The input the mouse is over has changed
if (node.mouseOver.inputId !== inputId || node.mouseOver.outputId !== outputId) {
node.mouseOver.inputId = inputId
node.mouseOver.outputId = outputId
this.dirty_canvas = true
}
//in case the node wants to do something
node.onMouseMove?.(e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this)
@@ -2388,17 +2423,14 @@ export class LGraphCanvas {
if (firstLink.output) {
const pos = this._highlight_input || [0, 0] //to store the output of isOverNodeInput
//on top of input
if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {
//mouse on top of the corner box, don't know what to do
} else {
//check if I have a slot below de mouse
const slot = this.isOverNodeInput(node, e.canvasX, e.canvasY, pos)
if (slot != -1 && node.inputs[slot] && LiteGraph.isValidConnection(firstLink.output.type, node.inputs[slot].type)) {
if (inputId != -1 && node.inputs[inputId] && LiteGraph.isValidConnection(firstLink.output.type, node.inputs[inputId].type)) {
this._highlight_input = pos
this._highlight_input_slot = node.inputs[slot] // XXX CHECK THIS
this._highlight_input_slot = node.inputs[inputId] // XXX CHECK THIS
} else {
// Allow support for linking to widgets, handled externally to LiteGraph
if (this.getWidgetLinkType) {
@@ -2421,18 +2453,12 @@ export class LGraphCanvas {
}
} else if (firstLink.input) {
const pos = this._highlight_output || [0, 0] //to store the output of isOverNodeOutput
//on top of output
if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {
//mouse on top of the corner box, don't know what to do
} else {
//check if I have a slot below de mouse
const slot = this.isOverNodeOutput(node, e.canvasX, e.canvasY, pos)
if (slot != -1 && node.outputs[slot] && LiteGraph.isValidConnection(firstLink.input.type, node.outputs[slot].type)) {
if (outputId != -1 && node.outputs[outputId] && LiteGraph.isValidConnection(firstLink.input.type, node.outputs[outputId].type)) {
this._highlight_output = pos
} else {
this._highlight_output = null
@@ -2835,6 +2861,16 @@ export class LGraphCanvas {
e.preventDefault()
return false
}
/**
* Called when the mouse moves off the canvas. Clears all node hover states.
* @param e
*/
processMouseOut(e: CanvasMouseEvent): void {
// TODO: Check if document.contains(e.relatedTarget) - handle mouseover node textarea etc.
this.updateMouseOverNodes(null, e)
}
/**
* Called when a mouse wheel event has to be processed
**/
@@ -4330,8 +4366,7 @@ export class LGraphCanvas {
size,
color,
bgcolor,
node.is_selected,
node.mouseOver
node.is_selected
)
if (!low_quality) {
@@ -4348,6 +4383,7 @@ export class LGraphCanvas {
ctx.font = this.inner_text_font
const render_text = !low_quality
const highlightColour = LiteGraph.NODE_TEXT_HIGHLIGHT_COLOR ?? LiteGraph.NODE_SELECTED_TITLE_COLOR ?? LiteGraph.NODE_TEXT_COLOR
const out_slot = this.connecting_links ? this.connecting_links[0].output : null
const in_slot = this.connecting_links ? this.connecting_links[0].input : null
@@ -4365,11 +4401,11 @@ export class LGraphCanvas {
const slot_type = slot.type
ctx.globalAlpha = editor_alpha
//change opacity of incompatible slots when dragging a connection
if (out_slot && !LiteGraph.isValidConnection(slot.type, out_slot.type)) {
ctx.globalAlpha = 0.4 * editor_alpha
}
const isValid = !this.connecting_links || (out_slot && LiteGraph.isValidConnection(slot.type, out_slot.type))
const highlight = isValid && node.mouseOver?.inputId === i
const label_color = highlight ? highlightColour : LiteGraph.NODE_TEXT_COLOR
ctx.globalAlpha = isValid ? editor_alpha : 0.4 * editor_alpha
ctx.fillStyle =
slot.link != null
@@ -4392,10 +4428,11 @@ export class LGraphCanvas {
horizontal,
low_quality,
render_text,
label_color: LiteGraph.NODE_TEXT_COLOR,
label_color,
label_position: LabelPosition.Right,
// Input slot is not stroked.
do_stroke: false,
highlight,
})
}
}
@@ -4410,9 +4447,10 @@ export class LGraphCanvas {
const slot_type = slot.type
//change opacity of incompatible slots when dragging a connection
if (in_slot && !LiteGraph.isValidConnection(slot_type, in_slot.type)) {
ctx.globalAlpha = 0.4 * editor_alpha
}
const isValid = !this.connecting_links || (in_slot && LiteGraph.isValidConnection(slot_type, in_slot.type))
const highlight = isValid && node.mouseOver?.outputId === i
const label_color = highlight ? highlightColour : LiteGraph.NODE_TEXT_COLOR
ctx.globalAlpha = isValid ? editor_alpha : 0.4 * editor_alpha
const pos = node.getConnectionPos(false, i, slot_pos)
pos[0] -= node.pos[0]
@@ -4435,9 +4473,10 @@ export class LGraphCanvas {
horizontal,
low_quality,
render_text,
label_color: LiteGraph.NODE_TEXT_COLOR,
label_color,
label_position: LabelPosition.Left,
do_stroke: true,
highlight,
})
}
}
@@ -4598,16 +4637,22 @@ export class LGraphCanvas {
ctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3)
}
/**
* draws the shape of the given node in the canvas
**/
* Draws the shape of the given node on the canvas
* @param node The node to draw
* @param ctx 2D canvas rendering context used to draw
* @param size Size of the background to draw, in graph units. Differs from node size if collapsed, etc.
* @param fgcolor Foreground colour - used for text
* @param bgcolor Background colour of the node
* @param selected Whether to render the node as selected. Likely to be removed in future, as current usage is simply the is_selected property of the node.
* @param mouse_over Deprecated
*/
drawNodeShape(
node: LGraphNode,
ctx: CanvasRenderingContext2D,
size: Size,
fgcolor: CanvasColour,
bgcolor: CanvasColour,
selected: boolean,
mouse_over: boolean
selected: boolean
): void {
//bg rect
ctx.strokeStyle = fgcolor

View File

@@ -144,7 +144,7 @@ export class LGraphNode {
_shape?: RenderShape
subgraph?: LGraph
skip_subgraph_button?: boolean
mouseOver?: boolean
mouseOver?: IMouseOverData
is_selected?: boolean
redraw_on_mouse?: boolean
// Appears unused

View File

@@ -38,6 +38,7 @@ export class LiteGraphGlobal {
NODE_SELECTED_TITLE_COLOR = "#FFF"
NODE_TEXT_SIZE = 14
NODE_TEXT_COLOR = "#AAA"
NODE_TEXT_HIGHLIGHT_COLOR = "#EEE"
NODE_SUBTEXT_SIZE = 12
NODE_DEFAULT_COLOR = "#333"
NODE_DEFAULT_BGCOLOR = "#353535"

View File

@@ -40,6 +40,7 @@ export function drawSlot(
low_quality = false,
render_text = true,
do_stroke = false,
highlight = false,
}: {
label_color?: string;
label_position?: LabelPosition;
@@ -47,6 +48,7 @@ export function drawSlot(
low_quality?: boolean;
render_text?: boolean;
do_stroke?: boolean;
highlight?: boolean;
} = {}
) {
// Save the current fillStyle and strokeStyle
@@ -101,10 +103,10 @@ export function drawSlot(
doStroke = true;
ctx.lineWidth = 3;
ctx.strokeStyle = ctx.fillStyle;
radius = 3;
radius = highlight ? 4 : 3;
} else {
// Normal circle
radius = 4;
radius = highlight ? 5 : 4;
}
ctx.arc(pos[0], pos[1], radius, 0, Math.PI * 2);
}
@@ -117,6 +119,7 @@ export function drawSlot(
if (render_text) {
const text = slot.label != null ? slot.label : slot.name;
if (text) {
// TODO: Finish impl. Highlight text on mouseover unless we're connecting links.
ctx.fillStyle = label_color;
if (label_position === LabelPosition.Right) {