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

View File

@@ -1,4 +1,5 @@
import { toString } from 'es-toolkit/compat' import { toString } from 'es-toolkit/compat'
import { toValue } from 'vue'
import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants' import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants'
import { LitegraphLinkAdapter } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter' 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 // Mousedown callback - can block drag
if (node.onMouseDown?.(e, pos, this)) { if (node.onMouseDown?.(e, pos, this)) {

View File

@@ -18,6 +18,7 @@ import {
containsCentre, containsCentre,
containsRect, containsRect,
createBounds, createBounds,
isInRect,
isInRectangle, isInRectangle,
isPointInRect, isPointInRect,
snapPoint 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 setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas
} }

View File

@@ -1,3 +1,5 @@
import { toValue } from 'vue'
import { LGraphNodeProperties } from '@/lib/litegraph/src/LGraphNodeProperties' import { LGraphNodeProperties } from '@/lib/litegraph/src/LGraphNodeProperties'
import { import {
calculateInputSlotPosFromSlot, calculateInputSlotPosFromSlot,
@@ -2084,7 +2086,13 @@ export class LGraphNode
* checks if a point is inside the shape of a node * checks if a point is inside the shape of a node
*/ */
isPointInside(x: number, y: number): boolean { 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, "font_size": 14,
"graph": [Circular], "graph": [Circular],
"id": 123, "id": 123,
"isPointInside": [Function],
"selected": undefined, "selected": undefined,
"setDirtyCanvas": [Function], "setDirtyCanvas": [Function],
"title": "A group to test with", "title": "A group to test with",

View File

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