import { app } from '../../../scripts/app.js'; // === CANVAS UTILITY FUNCTIONS (from power_lora_loader.js) === export function binarySearch(max, getValue, match) { let min = 0; while (min <= max) { let guess = Math.floor((min + max) / 2); const compareVal = getValue(guess); if (compareVal === match) return guess; if (compareVal < match) min = guess + 1; else max = guess - 1; } return max; } export function fitString(ctx, str, maxWidth) { let width = ctx.measureText(str).width; const ellipsis = "…"; const ellipsisWidth = measureText(ctx, ellipsis); if (width <= maxWidth || width <= ellipsisWidth) { return str; } const index = binarySearch(str.length, (guess) => measureText(ctx, str.substring(0, guess)), maxWidth - ellipsisWidth); return str.substring(0, index) + ellipsis; } export function measureText(ctx, str) { return ctx.measureText(str).width; } export function isLowQuality() { var _a; const canvas = app.canvas; return (((_a = canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) || 1) <= 0.5; } export function drawRoundedRectangle(ctx, options) { const lowQuality = isLowQuality(); options = { ...options }; ctx.save(); ctx.strokeStyle = options.colorStroke || LiteGraph.WIDGET_OUTLINE_COLOR; ctx.fillStyle = options.colorBackground || LiteGraph.WIDGET_BGCOLOR; ctx.beginPath(); ctx.roundRect(...options.pos, ...options.size, lowQuality ? [0] : options.borderRadius ? [options.borderRadius] : [options.size[1] * 0.5]); ctx.fill(); !lowQuality && ctx.stroke(); ctx.restore(); } export function drawWidgetButton(ctx, options, text = null, isMouseDownedAndOver = false) { var _a; const borderRadius = isLowQuality() ? 0 : ((_a = options.borderRadius) !== null && _a !== void 0 ? _a : 4); ctx.save(); if (!isLowQuality() && !isMouseDownedAndOver) { drawRoundedRectangle(ctx, { size: [options.size[0] - 2, options.size[1]], pos: [options.pos[0] + 1, options.pos[1] + 1], borderRadius, colorBackground: "#000000aa", colorStroke: "#000000aa", }); } drawRoundedRectangle(ctx, { size: options.size, pos: [options.pos[0], options.pos[1] + (isMouseDownedAndOver ? 1 : 0)], borderRadius, colorBackground: isMouseDownedAndOver ? "#444" : LiteGraph.WIDGET_BGCOLOR, colorStroke: "transparent", }); if (isLowQuality()) { ctx.restore(); return; } if (!isMouseDownedAndOver) { drawRoundedRectangle(ctx, { size: [options.size[0] - 0.75, options.size[1] - 0.75], pos: options.pos, borderRadius: borderRadius - 0.5, colorBackground: "transparent", colorStroke: "#00000044", }); drawRoundedRectangle(ctx, { size: [options.size[0] - 0.75, options.size[1] - 0.75], pos: [options.pos[0] + 0.75, options.pos[1] + 0.75], borderRadius: borderRadius - 0.5, colorBackground: "transparent", colorStroke: "#ffffff11", }); } drawRoundedRectangle(ctx, { size: [options.size[0] - 1.5, options.size[1] - 1.5], pos: [options.pos[0] + 0.75, options.pos[1] + (isMouseDownedAndOver ? 1.75 : 0.75)], borderRadius: borderRadius - 1, colorBackground: "transparent", colorStroke: isMouseDownedAndOver ? "#00000088" : "#ffffff22", }); if (text) { ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR; ctx.fillText(text, options.pos[0] + options.size[0] / 2, options.pos[1] + options.size[1] / 2 + (isMouseDownedAndOver ? 1 : 0)); } ctx.restore(); } export function drawTogglePart(ctx, options) { const lowQuality = isLowQuality(); ctx.save(); const { posX, posY, height, value } = options; const toggleRadius = height * 0.36; const toggleBgWidth = height * 1.5; if (!lowQuality) { ctx.beginPath(); ctx.roundRect(posX + 4, posY + 4, toggleBgWidth - 8, height - 8, [height * 0.5]); ctx.globalAlpha = app.canvas.editor_alpha * 0.25; ctx.fillStyle = "rgba(255,255,255,0.45)"; ctx.fill(); ctx.globalAlpha = app.canvas.editor_alpha; } // Check for truthiness - objects (driven config) should render as "on" const isOn = !!value; ctx.fillStyle = isOn ? "#89B" : "#888"; const toggleX = lowQuality || !value ? posX + height * 0.5 : posX + height; ctx.beginPath(); ctx.arc(toggleX, posY + height * 0.5, toggleRadius, 0, Math.PI * 2); ctx.fill(); ctx.restore(); return [posX, toggleBgWidth]; } export function drawNumberWidgetPart(ctx, options) { const arrowWidth = 9; const arrowHeight = 10; const innerMargin = 3; const numberWidth = 32; const xBoundsArrowLess = [0, 0]; const xBoundsNumber = [0, 0]; const xBoundsArrowMore = [0, 0]; ctx.save(); let posX = options.posX; const { posY, height, value, textColor } = options; const midY = posY + height / 2; if (options.direction === -1) { posX = posX - arrowWidth - innerMargin - numberWidth - innerMargin - arrowWidth; } ctx.textAlign = "center"; ctx.textBaseline = "middle"; const oldTextcolor = ctx.fillStyle; if (textColor) { ctx.fillStyle = textColor; } // Draw left arrow ctx.fillText("◀", posX + arrowWidth / 2, midY); xBoundsArrowLess[0] = posX; xBoundsArrowLess[1] = arrowWidth; posX += arrowWidth + innerMargin; // Draw number with precision and suffix support const precision = options.precision !== undefined ? options.precision : 0; const suffix = options.suffix || ""; const valueText = value.toFixed(precision) + suffix; ctx.fillText(fitString(ctx, valueText, numberWidth), posX + numberWidth / 2, midY); xBoundsNumber[0] = posX; xBoundsNumber[1] = numberWidth; posX += numberWidth + innerMargin; // Draw right arrow ctx.fillText("▶", posX + arrowWidth / 2, midY); xBoundsArrowMore[0] = posX; xBoundsArrowMore[1] = arrowWidth; ctx.fillStyle = oldTextcolor; ctx.restore(); return [xBoundsArrowLess, xBoundsNumber, xBoundsArrowMore]; } drawNumberWidgetPart.WIDTH_TOTAL = 9 + 3 + 32 + 3 + 9; // === BASE WIDGET CLASS === export class RgthreeBaseWidget { constructor(name) { this.type = "custom"; this.options = {}; this.y = 0; this.last_y = 0; this.mouseDowned = null; this.isMouseDownedAndOver = false; this.hitAreas = {}; this.downedHitAreasForMove = []; this.downedHitAreasForClick = []; this.name = name; } serializeValue(node, index) { return this.value; } clickWasWithinBounds(pos, bounds) { let xStart = bounds[0]; let xEnd = xStart + (bounds.length > 2 ? bounds[2] : bounds[1]); const clickedX = pos[0] >= xStart && pos[0] <= xEnd; if (bounds.length === 2) { return clickedX; } return clickedX && pos[1] >= bounds[1] && pos[1] <= bounds[1] + bounds[3]; } mouse(event, pos, node) { var _a, _b, _c; const canvas = app.canvas; if (event.type == "pointerdown") { this.mouseDowned = [...pos]; this.isMouseDownedAndOver = true; this.downedHitAreasForMove.length = 0; this.downedHitAreasForClick.length = 0; let anyHandled = false; // Check if it's a right click (button 2) const isRightClick = event.button === 2; for (const part of Object.values(this.hitAreas)) { if (this.clickWasWithinBounds(pos, part.bounds)) { if (part.onMove) { this.downedHitAreasForMove.push(part); } if (part.onClick) { this.downedHitAreasForClick.push(part); } // Check for right-click BEFORE calling onDown to prevent toggle from firing on right-click if (isRightClick && part.onRightDown) { const thisHandled = part.onRightDown.apply(this, [event, pos, node, part]); anyHandled = anyHandled || thisHandled == true; } else if (part.onDown) { // Only call onDown if it's NOT a right-click with onRightDown const thisHandled = part.onDown.apply(this, [event, pos, node, part]); anyHandled = anyHandled || thisHandled == true; } part.wasMouseClickedAndIsOver = true; } } return (_a = this.onMouseDown(event, pos, node)) !== null && _a !== void 0 ? _a : anyHandled; } if (event.type == "pointerup") { if (!this.mouseDowned) return true; this.downedHitAreasForMove.length = 0; const wasMouseDownedAndOver = this.isMouseDownedAndOver; this.cancelMouseDown(); let anyHandled = false; for (const part of Object.values(this.hitAreas)) { if (part.onUp && this.clickWasWithinBounds(pos, part.bounds)) { const thisHandled = part.onUp.apply(this, [event, pos, node, part]); anyHandled = anyHandled || thisHandled == true; } part.wasMouseClickedAndIsOver = false; } for (const part of this.downedHitAreasForClick) { if (this.clickWasWithinBounds(pos, part.bounds)) { const thisHandled = part.onClick.apply(this, [event, pos, node, part]); anyHandled = anyHandled || thisHandled == true; } } this.downedHitAreasForClick.length = 0; if (wasMouseDownedAndOver) { const thisHandled = this.onMouseClick(event, pos, node); anyHandled = anyHandled || thisHandled == true; } return (_b = this.onMouseUp(event, pos, node)) !== null && _b !== void 0 ? _b : anyHandled; } if (event.type == "pointermove") { this.isMouseDownedAndOver = !!this.mouseDowned; if (this.mouseDowned && (pos[0] < 15 || pos[0] > node.size[0] - 15 || pos[1] < this.last_y || pos[1] > this.last_y + LiteGraph.NODE_WIDGET_HEIGHT)) { this.isMouseDownedAndOver = false; } for (const part of Object.values(this.hitAreas)) { if (this.downedHitAreasForMove.includes(part)) { part.onMove.apply(this, [event, pos, node, part]); } if (this.downedHitAreasForClick.includes(part)) { part.wasMouseClickedAndIsOver = this.clickWasWithinBounds(pos, part.bounds); } } return (_c = this.onMouseMove(event, pos, node)) !== null && _c !== void 0 ? _c : true; } return false; } cancelMouseDown() { this.mouseDowned = null; this.isMouseDownedAndOver = false; this.downedHitAreasForMove.length = 0; } onMouseDown(event, pos, node) { return; } onMouseUp(event, pos, node) { return; } onMouseClick(event, pos, node) { return; } onMouseMove(event, pos, node) { return; } }