Skip to content

Commit 15b1b91

Browse files
Implement a legacy canvas widget for vue mode (#6011)
![updated-legacy-widget](https://github.com/user-attachments/assets/3f0a1623-9445-4059-acbb-086baec54980) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6011-Implement-a-legacy-canvas-widget-for-vue-mode-2896d73d36508127a5d1debcccb519a0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <[email protected]>
1 parent e48e11e commit 15b1b91

File tree

6 files changed

+125
-21
lines changed

6 files changed

+125
-21
lines changed

src/lib/litegraph/src/LGraphCanvas.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2801,7 +2801,7 @@ export class LGraphCanvas
28012801
// Widget
28022802
const widget = node.getWidgetOnPos(x, y)
28032803
if (widget) {
2804-
this.#processWidgetClick(e, node, widget)
2804+
this.processWidgetClick(e, node, widget)
28052805
this.node_widget = [node, widget]
28062806
} else {
28072807
// Node background
@@ -2981,13 +2981,12 @@ export class LGraphCanvas
29812981
this.dirty_canvas = true
29822982
}
29832983

2984-
#processWidgetClick(
2984+
processWidgetClick(
29852985
e: CanvasPointerEvent,
29862986
node: LGraphNode,
2987-
widget: IBaseWidget
2987+
widget: IBaseWidget,
2988+
pointer = this.pointer
29882989
) {
2989-
const { pointer } = this
2990-
29912990
// Custom widget - CanvasPointer
29922991
if (typeof widget.onPointerDown === 'function') {
29932992
const handled = widget.onPointerDown(pointer, node, this)

src/lib/litegraph/src/types/widgets.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,14 @@ export interface IBaseWidget<
363363
lowQuality?: boolean
364364
): void
365365

366+
/**
367+
* Compatibility method for widgets implementing the draw
368+
* method when displayed in non-canvas renderers.
369+
* Set by the current renderer implementation.
370+
* When called, performs a draw operation.
371+
*/
372+
triggerDraw?: () => void
373+
366374
/**
367375
* Compute the size of the widget. Overrides {@link IBaseWidget.computeSize}.
368376
* @param width The width of the widget.

src/renderer/extensions/vueNodes/components/NodeWidgets.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<!-- Widget Input Slot Dot -->
2525

2626
<div
27-
class="opacity-0 transition-opacity duration-150 group-hover:opacity-100"
27+
class="z-10 opacity-0 transition-opacity duration-150 group-hover:opacity-100"
2828
>
2929
<InputSlot
3030
:slot-data="{
@@ -63,7 +63,8 @@ import { useErrorHandling } from '@/composables/useErrorHandling'
6363
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
6464
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
6565
import WidgetDOM from '@/renderer/extensions/vueNodes/widgets/components/WidgetDOM.vue'
66-
import WidgetInputText from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue'
66+
// Import widget components directly
67+
import WidgetLegacy from '@/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue'
6768
import {
6869
getComponent,
6970
shouldRenderAsVue
@@ -129,7 +130,7 @@ const processedWidgets = computed((): ProcessedWidget[] => {
129130
130131
const vueComponent =
131132
getComponent(widget.type, widget.name) ||
132-
(widget.isDOMWidget ? WidgetDOM : WidgetInputText)
133+
(widget.isDOMWidget ? WidgetDOM : WidgetLegacy)
133134
134135
const slotMetadata = widget.slotMetadata
135136
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<script setup lang="ts">
2+
import { useResizeObserver } from '@vueuse/core'
3+
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
4+
5+
import { useChainCallback } from '@/composables/functional/useChainCallback'
6+
import { CanvasPointer } from '@/lib/litegraph/src/CanvasPointer'
7+
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
8+
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
9+
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
10+
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
11+
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
12+
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
13+
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
14+
15+
const props = defineProps<{
16+
widget: SimplifiedWidget<void>
17+
readonly?: boolean
18+
}>()
19+
20+
const canvasEl = ref()
21+
22+
const canvas: LGraphCanvas = useCanvasStore().canvas as LGraphCanvas
23+
let node: LGraphNode | undefined
24+
let widgetInstance: IBaseWidget | undefined
25+
let pointer: CanvasPointer | undefined
26+
const scaleFactor = 2
27+
28+
onMounted(() => {
29+
node =
30+
canvas?.graph?.getNodeById(
31+
canvasEl.value.parentElement.attributes['node-id'].value
32+
) ?? undefined
33+
if (!node) return
34+
widgetInstance = node.widgets?.find((w) => w.name === props.widget.name)
35+
if (!widgetInstance) return
36+
canvasEl.value.width *= scaleFactor
37+
if (!widgetInstance.triggerDraw)
38+
widgetInstance.callback = useChainCallback(
39+
widgetInstance.callback,
40+
function (this: IBaseWidget) {
41+
this?.triggerDraw?.()
42+
}
43+
)
44+
widgetInstance.triggerDraw = draw
45+
useResizeObserver(canvasEl.value.parentElement, draw)
46+
watch(() => useColorPaletteStore().activePaletteId, draw)
47+
pointer = new CanvasPointer(canvasEl.value)
48+
})
49+
onBeforeUnmount(() => {
50+
if (widgetInstance) widgetInstance.triggerDraw = () => {}
51+
})
52+
53+
function draw() {
54+
if (!widgetInstance || !node) return
55+
const width = canvasEl.value.parentElement.clientWidth
56+
const height = widgetInstance.computeSize
57+
? widgetInstance.computeSize(width)[1]
58+
: 20
59+
widgetInstance.y = 0
60+
canvasEl.value.height = (height + 2) * scaleFactor
61+
canvasEl.value.width = width * scaleFactor
62+
const ctx = canvasEl.value?.getContext('2d')
63+
if (!ctx) return
64+
ctx.scale(scaleFactor, scaleFactor)
65+
widgetInstance.draw?.(ctx, node, width, 1, height)
66+
}
67+
function translateEvent(e: PointerEvent): asserts e is CanvasPointerEvent {
68+
if (!node) return
69+
canvas.adjustMouseEvent(e)
70+
canvas.graph_mouse[0] = e.offsetX + node.pos[0]
71+
canvas.graph_mouse[1] = e.offsetY + node.pos[1]
72+
}
73+
//See LGraphCanvas.processWidgetClick
74+
function handleDown(e: PointerEvent) {
75+
if (!node || !widgetInstance || !pointer) return
76+
translateEvent(e)
77+
pointer.down(e)
78+
if (widgetInstance.mouse)
79+
pointer.onDrag = (e) =>
80+
widgetInstance!.mouse?.(e, [e.offsetX, e.offsetY], node!)
81+
//NOTE: a mouseUp event is already registed under pointer.finally
82+
canvas.processWidgetClick(e, node, widgetInstance, pointer)
83+
}
84+
function handleUp(e: PointerEvent) {
85+
if (!pointer) return
86+
translateEvent(e)
87+
e.click_time = e.timeStamp - (pointer?.eDown?.timeStamp ?? 0)
88+
pointer.up(e)
89+
}
90+
function handleMove(e: PointerEvent) {
91+
if (!pointer) return
92+
translateEvent(e)
93+
pointer.move(e)
94+
}
95+
</script>
96+
<template>
97+
<div class="relative mx-[-12px] min-w-0 basis-0">
98+
<canvas
99+
ref="canvasEl"
100+
class="absolute mt-[-13px] w-full cursor-crosshair"
101+
@pointerdown="handleDown"
102+
@pointerup="handleUp"
103+
@pointermove="handleMove"
104+
/>
105+
</div>
106+
</template>

src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import WidgetGalleria from '../components/WidgetGalleria.vue'
1414
import WidgetImageCompare from '../components/WidgetImageCompare.vue'
1515
import WidgetInputNumber from '../components/WidgetInputNumber.vue'
1616
import WidgetInputText from '../components/WidgetInputText.vue'
17+
import WidgetLegacy from '../components/WidgetLegacy.vue'
1718
import WidgetMarkdown from '../components/WidgetMarkdown.vue'
1819
import WidgetMultiSelect from '../components/WidgetMultiSelect.vue'
1920
import WidgetRecordAudio from '../components/WidgetRecordAudio.vue'
@@ -114,6 +115,7 @@ const coreWidgetDefinitions: Array<[string, WidgetDefinition]> = [
114115
'markdown',
115116
{ component: WidgetMarkdown, aliases: ['MARKDOWN'], essential: false }
116117
],
118+
['legacy', { component: WidgetLegacy, aliases: [], essential: true }],
117119
[
118120
'audiorecord',
119121
{
@@ -161,19 +163,11 @@ export const getComponent = (type: string, name: string): Component | null => {
161163
return widgets.get(canonicalType)?.component || null
162164
}
163165

164-
const isSupported = (type: string): boolean => {
165-
const canonicalType = getCanonicalType(type)
166-
return widgets.has(canonicalType)
167-
}
168-
169166
export const isEssential = (type: string): boolean => {
170167
const canonicalType = getCanonicalType(type)
171168
return widgets.get(canonicalType)?.essential || false
172169
}
173170

174171
export const shouldRenderAsVue = (widget: Partial<SafeWidgetData>): boolean => {
175-
if (widget.options?.canvasOnly) return false
176-
if (widget.isDOMWidget) return true
177-
if (!widget.type) return false
178-
return isSupported(widget.type)
172+
return !widget.options?.canvasOnly && !!widget.type
179173
}

tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,6 @@ describe('widgetRegistry', () => {
123123
expect(shouldRenderAsVue({ type: 'combo' })).toBe(true)
124124
})
125125

126-
it('should return false for unknown types', () => {
127-
expect(shouldRenderAsVue({ type: 'unknown_type' })).toBe(false)
128-
})
129-
130126
it('should respect options while checking type', () => {
131127
const widget = { type: 'text', options: { someOption: 'value' } }
132128
expect(shouldRenderAsVue(widget)).toBe(true)

0 commit comments

Comments
 (0)