Files
2026-06-20 12:36:18 +02:00

193 lines
5.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { COLORS, EMPTY_VALUE } from "../constants.js";
import { fitString } from "../utils.js";
// --- Constants ---
const ROW_HEIGHT = 17;
const ROW_MARGIN = 10;
const INNER_MARGIN = 4;
const BTN_WIDTH = 36;
const EMPTY_PRESET = "";
// --- PresetRowWidget ---
export class PresetRowWidget {
constructor(name, onNew, onSave, onDelete, onSelect) {
this.name = name;
this.type = "custom";
this.value = EMPTY_PRESET;
this.presets = [];
this.last_y = 0;
this.onNew = onNew;
this.onSave = onSave;
this.onDelete = onDelete;
this.onSelect = onSelect;
// Hit areas [x, width]
this._hitDropdown = [0, 0];
this._hitNew = [0, 0];
this._hitSave = [0, 0];
this._hitDelete = [0, 0];
}
computeSize() {
return [0, ROW_HEIGHT];
}
// --- Public API ---
setPresets(presets) {
this.presets = presets;
if (!presets.length) {
this.value = EMPTY_PRESET;
} else if (!presets.includes(this.value)) {
this.value = presets[0];
}
}
isEmpty() {
return !this.presets.length;
}
flashSave(node, label = "✅", duration = 1500) {
if (this._flashTimer) clearTimeout(this._flashTimer);
this._saveFlash = label;
if (node.graph) node.graph.setDirtyCanvas(true, true);
this._flashTimer = setTimeout(() => {
this._saveFlash = null;
this._flashTimer = null;
if (node.graph) node.graph.setDirtyCanvas(true, true);
}, duration);
}
// --- Draw ---
draw(ctx, node, width, posY, height) {
this.last_y = posY;
const h = height ?? ROW_HEIGHT;
const midY = posY + h * 0.5;
const margin = ROW_MARGIN;
ctx.save();
ctx.font = "11px monospace";
ctx.textBaseline = "middle";
const totalWidth = width - margin * 2;
const btnsWidth = BTN_WIDTH * 3 + INNER_MARGIN * 2;
const dropdownWidth = totalWidth - btnsWidth - INNER_MARGIN;
const dropdownX = margin;
const newX = margin + dropdownWidth + INNER_MARGIN;
const saveX = newX + BTN_WIDTH + INNER_MARGIN;
const deleteX = saveX + BTN_WIDTH + INNER_MARGIN;
// Dropdown
ctx.fillStyle = "#222233";
ctx.beginPath();
ctx.roundRect(dropdownX, posY + 4, dropdownWidth, h - 8, 4);
ctx.fill();
ctx.fillStyle = this.isEmpty() ? COLORS.inactive : COLORS.text;
ctx.textAlign = "left";
ctx.fillText(
this.isEmpty()
? EMPTY_VALUE
: fitString(ctx, this.value, dropdownWidth - 16),
dropdownX + 8,
midY,
);
// Dropdown arrow
ctx.fillStyle = COLORS.inactive;
ctx.textAlign = "center";
ctx.fillText("▾", dropdownX + dropdownWidth - 10, midY);
this._hitDropdown = [dropdownX, dropdownWidth];
// New button
ctx.fillStyle = "#223322";
ctx.beginPath();
ctx.roundRect(newX, posY + 4, BTN_WIDTH, h - 8, 4);
ctx.fill();
ctx.fillStyle = COLORS.text;
ctx.textAlign = "center";
ctx.fillText("➕", newX + BTN_WIDTH / 2, midY);
this._hitNew = [newX, BTN_WIDTH];
// Save button (disabled if empty)
ctx.fillStyle = this.isEmpty() ? "#1a1a1a" : "#222233";
ctx.beginPath();
ctx.roundRect(saveX, posY + 4, BTN_WIDTH, h - 8, 4);
ctx.fill();
ctx.fillStyle = this.isEmpty() ? COLORS.inactive : COLORS.text;
ctx.fillText(this._saveFlash ?? "💾", saveX + BTN_WIDTH / 2, midY);
this._hitSave = [saveX, BTN_WIDTH];
// Delete button (disabled if empty)
ctx.fillStyle = this.isEmpty() ? "#1a1a1a" : "#4a1a1a";
ctx.beginPath();
ctx.roundRect(deleteX, posY + 4, BTN_WIDTH, h - 8, 4);
ctx.fill();
ctx.fillStyle = this.isEmpty() ? COLORS.inactive : "#ff6666";
ctx.fillText("🗑️", deleteX + BTN_WIDTH / 2, midY);
this._hitDelete = [deleteX, BTN_WIDTH];
ctx.restore();
}
// --- Mouse ---
mouse(event, pos, node) {
const x = pos[0];
const type = event.type;
if (type !== "pointerdown" && type !== "mousedown") return false;
if (this._inHit(x, this._hitNew)) {
this.onNew?.();
return true;
}
if (this._inHit(x, this._hitSave)) {
if (!this.isEmpty()) this.onSave?.();
return true;
}
if (this._inHit(x, this._hitDelete)) {
if (!this.isEmpty()) this.onDelete?.();
return true;
}
if (this._inHit(x, this._hitDropdown)) {
this._showDropdown(event, node);
return true;
}
return false;
}
// --- Utils ---
_inHit(x, hit) {
return x >= hit[0] && x <= hit[0] + hit[1];
}
// --- Dropdown ---
_showDropdown(event, node) {
if (!this.presets.length) return;
const items = this.presets.map((name) => ({
content: name,
callback: () => {
this.value = name;
this.onSelect?.(name);
node.setDirtyCanvas(true, true);
},
}));
new LiteGraph.ContextMenu(items, {
event,
title: "Select Preset",
});
}
}