Implement clickable badges (#8401)

Adds an `onClick` handler to LGraphBadge

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8401-Implement-clickable-badges-2f76d73d365081b3b23fc1eaa3bc65b8)
by [Unito](https://www.unito.io)
This commit is contained in:
AustinMroz
2026-01-29 00:04:28 -08:00
committed by GitHub
parent 3866fe7eaa
commit 44baadd7ca
6 changed files with 34 additions and 3 deletions

View File

@@ -1,3 +1,4 @@
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces'
import { LGraphIcon } from './LGraphIcon'
import type { LGraphIconOptions } from './LGraphIcon'
@@ -15,6 +16,7 @@ export interface LGraphBadgeOptions {
height?: number
cornerRadius?: number
iconOptions?: LGraphIconOptions
onClick?: (e: MouseEvent) => void
xOffset?: number
yOffset?: number
}
@@ -28,9 +30,15 @@ export class LGraphBadge {
height: number
cornerRadius: number
icon?: LGraphIcon
onClick?: (e: MouseEvent) => void
xOffset: number
yOffset: number
readonly _boundingRect: [number, number, number, number] = [0, 0, 0, 0]
get boundingRect(): ReadOnlyRect {
return this._boundingRect
}
constructor({
text,
fgColor = 'white',
@@ -40,6 +48,7 @@ export class LGraphBadge {
height = 20,
cornerRadius = 5,
iconOptions,
onClick,
xOffset = 0,
yOffset = 0
}: LGraphBadgeOptions) {
@@ -53,6 +62,7 @@ export class LGraphBadge {
if (iconOptions) {
this.icon = new LGraphIcon(iconOptions)
}
this.onClick = onClick
this.xOffset = xOffset
this.yOffset = yOffset
}
@@ -91,6 +101,8 @@ export class LGraphBadge {
const badgeWidth = this.getWidth(ctx)
const badgeX = 0
this._boundingRect.splice(0, 4, x, y, badgeWidth, this.height)
// Draw badge background
ctx.fillStyle = this.bgColor
ctx.beginPath()

View File

@@ -1,4 +1,5 @@
import { toString } from 'es-toolkit/compat'
import { toValue } from 'vue'
import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants'
import { LitegraphLinkAdapter } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
@@ -2801,6 +2802,12 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
}
}
}
for (const badge of node.badges.map(toValue).filter((b) => b.onClick)) {
if (isInRect(pos[0], pos[1], badge.boundingRect)) {
pointer.onClick = badge.onClick
return
}
}
// Mousedown callback - can block drag
if (node.onMouseDown?.(e, pos, this)) {

View File

@@ -18,6 +18,7 @@ import {
containsCentre,
containsRect,
createBounds,
isInRect,
isInRectangle,
isPointInRect,
snapPoint
@@ -370,6 +371,8 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
)
}
isPointInside = LGraphNode.prototype.isPointInside
isPointInside(x: number, y: number): boolean {
return isInRect(x, y, this.boundingRect)
}
setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas
}

View File

@@ -1,3 +1,5 @@
import { toValue } from 'vue'
import { LGraphNodeProperties } from '@/lib/litegraph/src/LGraphNodeProperties'
import {
calculateInputSlotPosFromSlot,
@@ -2084,7 +2086,13 @@ export class LGraphNode
* checks if a point is inside the shape of a node
*/
isPointInside(x: number, y: number): boolean {
return isInRect(x, y, this.boundingRect)
if (isInRect(x, y, this.boundingRect)) return true
for (const badge of this.badges.map(toValue).filter((b) => b.onClick)) {
if (isInRect(x - this.pos[0], y - this.pos[1], badge.boundingRect))
return true
}
return false
}
/**

View File

@@ -26,7 +26,6 @@ LGraph {
"font_size": 14,
"graph": [Circular],
"id": 123,
"isPointInside": [Function],
"selected": undefined,
"setDirtyCanvas": [Function],
"title": "A group to test with",

View File

@@ -6,6 +6,7 @@
color: fgColor,
backgroundColor: bgColor
}"
@click="(e) => onClick?.(e)"
>
{{ text }}
<i v-if="cssIcon" :class="cn(cssIcon)" />
@@ -20,6 +21,7 @@ export interface NodeBadgeProps {
fgColor?: LGraphBadge['fgColor']
bgColor?: LGraphBadge['bgColor']
cssIcon?: string
onClick?: (e: MouseEvent) => void
}
const {