Files
siraxe edaa843d7d refactorv1
spline refactor
position change
+ preview
scaling works
2025-10-13 03:45:31 +01:00

317 lines
12 KiB
JavaScript

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;
}
}