Modification of interfaces in some nodes
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -94,6 +94,8 @@ class LoadImageWithText:
|
||||
|
||||
print(f"[WRITE] Saved in: {output_path}")
|
||||
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
if pil_img.mode not in ('RGB', 'RGBA'):
|
||||
pil_img = pil_img.convert('RGB')
|
||||
|
||||
@@ -120,5 +122,5 @@ NODE_CLASS_MAPPINGS = {
|
||||
"LoadImageWithText": LoadImageWithText,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LoadImageWithText": "🦊 RS Image-Text ",
|
||||
"LoadImageWithText": "🦊 RS Image-Text",
|
||||
}
|
||||
+15
-7
@@ -6,6 +6,9 @@ app.registerExtension({
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name !== "RS Image Selector") return;
|
||||
|
||||
const MIN_WIDTH = 300;
|
||||
const MIN_HEIGHT = 400;
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
if (onNodeCreated) onNodeCreated.apply(this, arguments);
|
||||
@@ -16,7 +19,8 @@ app.registerExtension({
|
||||
this.userResized = false;
|
||||
this.heartbeatInterval = null;
|
||||
|
||||
this.setSize([450, 500]);
|
||||
this.setSize([MIN_WIDTH, MIN_HEIGHT]);
|
||||
this.min_size = [MIN_WIDTH, MIN_HEIGHT];
|
||||
|
||||
this.buttons = [
|
||||
{ label: "➕ SELECT ALL", key: "select_all", color: "#2196F3", hover: false, row: 0, col: 0 },
|
||||
@@ -58,6 +62,10 @@ app.registerExtension({
|
||||
const onResize = nodeType.prototype.onResize;
|
||||
nodeType.prototype.onResize = function(size) {
|
||||
if (onResize) onResize.apply(this, arguments);
|
||||
|
||||
if (this.size[0] < MIN_WIDTH) this.size[0] = MIN_WIDTH;
|
||||
if (this.size[1] < MIN_HEIGHT) this.size[1] = MIN_HEIGHT;
|
||||
|
||||
this.userResized = true;
|
||||
};
|
||||
|
||||
@@ -93,9 +101,9 @@ app.registerExtension({
|
||||
margin: 12,
|
||||
topBarH: 10,
|
||||
bottomBarH: 85,
|
||||
minWidth: 400,
|
||||
minWidth: MIN_WIDTH,
|
||||
maxWidth: 1200,
|
||||
minHeight: 300,
|
||||
minHeight: MIN_HEIGHT,
|
||||
maxHeight: 900
|
||||
};
|
||||
|
||||
@@ -222,7 +230,7 @@ app.registerExtension({
|
||||
|
||||
const titleBarHeight = LiteGraph.NODE_TITLE_HEIGHT || 30;
|
||||
const padding = 10;
|
||||
const footerHeight = 70;
|
||||
const footerHeight = 65;
|
||||
let widgetsTotalHeight = 0;
|
||||
|
||||
if (this.widgets) {
|
||||
@@ -242,10 +250,10 @@ app.registerExtension({
|
||||
|
||||
const titleBarHeight = LiteGraph.NODE_TITLE_HEIGHT || 30;
|
||||
const padding = 10;
|
||||
const footerHeight = 70;
|
||||
const footerHeight = 65;
|
||||
const btnH = 28;
|
||||
const btnGap = 5;
|
||||
const btnY = this.size[1] - 65;
|
||||
const btnY = this.size[1] - 70;
|
||||
|
||||
let widgetsTotalHeight = 0;
|
||||
if (this.widgets) {
|
||||
@@ -351,7 +359,7 @@ app.registerExtension({
|
||||
ctx.fillText("Render Error", 10, 10);
|
||||
}
|
||||
|
||||
const totalBtnW = this.size[0] - 50;
|
||||
const totalBtnW = this.size[0] - 30;
|
||||
const btnW = (totalBtnW - btnGap) / 2;
|
||||
|
||||
this.btnRects = [];
|
||||
|
||||
@@ -699,7 +699,7 @@ app.registerExtension({
|
||||
this.clickZones = [];
|
||||
const startY = this.getLoraListStartY();
|
||||
const padding = 10;
|
||||
const rightPanelWidth = 180;
|
||||
const rightPanelWidth = 170;
|
||||
|
||||
const availableHeight = this.size[1] - startY - 10;
|
||||
const maxVisibleStyles = Math.max(1, Math.floor(availableHeight / this.rowHeight));
|
||||
@@ -756,7 +756,7 @@ app.registerExtension({
|
||||
this.clickZones.push({ type: "info", index: dataIdx, x: infoX, y: y, w: infoW, h: h });
|
||||
|
||||
const nameX = infoX + infoW + 5;
|
||||
const nameW = this.size[0] - (padding * 2) - 50 - rightPanelWidth - 20 - infoW - 5;
|
||||
const nameW = this.size[0] - (padding * 2) - 1 - rightPanelWidth - 20 - infoW - 5;
|
||||
ctx.fillStyle = row.enabled ? "#fff" : "#777";
|
||||
ctx.font = "12px sans-serif";
|
||||
let displayName = row.name;
|
||||
|
||||
+20
-8
@@ -65,13 +65,19 @@ app.registerExtension({
|
||||
widget.slotName = conn.name;
|
||||
widget.draw = function(ctx, node, w, y, h) {
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.fillRect(0, y, w, h);
|
||||
ctx.beginPath();
|
||||
if (ctx.roundRect) {
|
||||
ctx.roundRect(10, y, w - 20, h, 6);
|
||||
} else {
|
||||
ctx.rect(10, y, w - 20, h);
|
||||
}
|
||||
ctx.fill();
|
||||
ctx.fillStyle = "#aaa";
|
||||
ctx.font = "13px Arial";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(this.name, 10, y + h / 2 + 1);
|
||||
ctx.fillText(this.name, 20, y + h / 2 + 1);
|
||||
const toggleW = 30, toggleH = 14;
|
||||
const toggleX = w - toggleW - 10;
|
||||
const toggleX = w - toggleW - 20;
|
||||
const toggleY = y + (h - toggleH) / 2;
|
||||
ctx.fillStyle = this.value ? "#4a4" : "#666";
|
||||
ctx.beginPath();
|
||||
@@ -200,8 +206,8 @@ app.registerExtension({
|
||||
ctx.strokeStyle = this.value === "OFF" ? "#f44336" : "#4caf50";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
if (ctx.roundRect) ctx.roundRect(1, y, w - 2, h, 6);
|
||||
else ctx.rect(1, y, w - 2, h);
|
||||
if (ctx.roundRect) ctx.roundRect(10, y, w - 20, h, 6);
|
||||
else ctx.rect(10, y, w - 20, h);
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "bold 13px Arial";
|
||||
@@ -270,13 +276,19 @@ app.registerExtension({
|
||||
widget.slotName = inputName;
|
||||
widget.draw = function(ctx, node, w, y, h) {
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.fillRect(0, y, w, h);
|
||||
ctx.beginPath();
|
||||
if (ctx.roundRect) {
|
||||
ctx.roundRect(10, y, w - 20, h, 6);
|
||||
} else {
|
||||
ctx.rect(10, y, w - 20, h);
|
||||
}
|
||||
ctx.fill();
|
||||
ctx.fillStyle = "#aaa";
|
||||
ctx.font = "14px Arial";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(this.name, 10, y + h / 2 + 1);
|
||||
ctx.fillText(this.name, 20, y + h / 2 + 1);
|
||||
const toggleW = 30, toggleH = 14;
|
||||
const toggleX = w - toggleW - 10;
|
||||
const toggleX = w - toggleW - 20;
|
||||
const toggleY = y + (h - toggleH) / 2;
|
||||
ctx.fillStyle = this.value ? "#4a4" : "#666";
|
||||
ctx.beginPath();
|
||||
|
||||
+98
-78
@@ -22,17 +22,17 @@ function drawImageFit(ctx, img, areaX, areaY, areaW, areaH, zoom, panX, panY) {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawRoundedRect(ctx, x, y, w, h, r) {
|
||||
function drawRoundRect(ctx, x, y, w, h, r) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + r, y);
|
||||
ctx.lineTo(x + w - r, y);
|
||||
ctx.arcTo(x + w, y, x + w, y + r, r);
|
||||
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
||||
ctx.lineTo(x + w, y + h - r);
|
||||
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
||||
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
||||
ctx.lineTo(x + r, y + h);
|
||||
ctx.arcTo(x, y + h, x, y + h - r, r);
|
||||
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
||||
ctx.lineTo(x, y + r);
|
||||
ctx.arcTo(x, y, x + r, y, r);
|
||||
ctx.quadraticCurveTo(x, y, x + r, y);
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ function getImageArea(node) {
|
||||
const headerHeight = 50;
|
||||
const padding = 10;
|
||||
const yStart = headerHeight;
|
||||
const widgetsHeight = 112;
|
||||
const widgetsHeight = 122; // Обновлено под новые отступы (3*24 + 2*6 + 6 + 22 + 10)
|
||||
const widgetsGap = 10;
|
||||
const yEnd = node.size[1] - widgetsHeight - widgetsGap - padding;
|
||||
const w = Math.max(10, node.size[0] - padding * 2);
|
||||
@@ -50,7 +50,7 @@ function getImageArea(node) {
|
||||
|
||||
function getWidgetsArea(node) {
|
||||
const padding = 10;
|
||||
const widgetsHeight = 112;
|
||||
const widgetsHeight = 122; // Обновлено
|
||||
const y = node.size[1] - widgetsHeight - padding;
|
||||
return { y, height: widgetsHeight };
|
||||
}
|
||||
@@ -89,6 +89,7 @@ app.registerExtension({
|
||||
this.rs_hideImg2 = false;
|
||||
this.rs_dragTarget = null;
|
||||
this.rs_widgetRects = [];
|
||||
this.rs_sliderHover = [];
|
||||
this.setSize([450, 450]);
|
||||
this.min_size = [450, 380];
|
||||
|
||||
@@ -202,94 +203,92 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const ROW_HEIGHT = 24;
|
||||
const ROW_GAP = 4;
|
||||
const LABEL_WIDTH = 50;
|
||||
const INFO_WIDTH = 35;
|
||||
const RESET_WIDTH = 15;
|
||||
const CORNER_RADIUS = 6;
|
||||
const INNER_PADDING = 8;
|
||||
const SLIDER_HEIGHT = 4;
|
||||
const HANDLE_SIZE = 6;
|
||||
const ROW_GAP = 6; // Уменьшено до 6 (как отступ до кнопки сброса)
|
||||
|
||||
this.rs_widgetRects = [];
|
||||
|
||||
const widgets = [
|
||||
{ name: 'zoom', icon: '🔎', label: 'Zoom', value: zoom, format: (v) => v.toFixed(1), default: 1.0, min: 1.0, max: 10.0, step: 0.05 },
|
||||
{ name: 'pan_x', icon: '↔️', label: 'Pan H', value: panPercentX, format: (v) => Math.round(v).toString(), default: 0, min: -100, max: 100, step: 0.5 },
|
||||
{ name: 'pan_y', icon: '️↕️', label: 'Pan V', value: panPercentY, format: (v) => Math.round(v).toString(), default: 0, min: -100, max: 100, step: 0.5 }
|
||||
{ name: 'zoom', label: 'ZOOM', value: zoom, format: (v) => v.toFixed(1), default: 1.0, min: 1.0, max: 10.0, step: 0.05 },
|
||||
{ name: 'pan_x', label: 'PAN H', value: panPercentX, format: (v) => Math.round(v).toString(), default: 0, min: -100, max: 100, step: 0.5 },
|
||||
{ name: 'pan_y', label: 'PAN V', value: panPercentY, format: (v) => Math.round(v).toString(), default: 0, min: -100, max: 100, step: 0.5 }
|
||||
];
|
||||
|
||||
const buttonWidth = w;
|
||||
const sliderWidth = buttonWidth - LABEL_WIDTH - INFO_WIDTH - RESET_WIDTH - 6 * INNER_PADDING;
|
||||
|
||||
const widgetX = padding;
|
||||
const widgetW = w;
|
||||
|
||||
widgets.forEach((widget, i) => {
|
||||
const rowY = widgetsY + i * (ROW_HEIGHT + ROW_GAP);
|
||||
|
||||
const labelX = padding + INNER_PADDING;
|
||||
const sliderX = labelX + LABEL_WIDTH + INNER_PADDING * 2;
|
||||
const infoX = sliderX + sliderWidth + INNER_PADDING;
|
||||
const resetX = infoX + INFO_WIDTH + INNER_PADDING;
|
||||
|
||||
drawRoundedRect(ctx, padding, rowY, buttonWidth, ROW_HEIGHT, CORNER_RADIUS);
|
||||
ctx.fillStyle = "#252525";
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = "#444444";
|
||||
const isHover = this.rs_sliderHover?.[i] || false;
|
||||
const isActive = this.rs_dragTarget?.widget === widget.name;
|
||||
|
||||
ctx.fillStyle = isHover ? "#2a2a2a" : "#252525";
|
||||
ctx.strokeStyle = isActive ? "#4CAF50" : (isHover ? "#4CAF50" : "#444");
|
||||
ctx.lineWidth = 1;
|
||||
drawRoundRect(ctx, widgetX, rowY, widgetW, ROW_HEIGHT, 4);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.font = "bold 12px Arial";
|
||||
ctx.fillStyle = "#C0C0C0";
|
||||
|
||||
ctx.fillStyle = "#aaa";
|
||||
ctx.font = "10px sans-serif";
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "middle";
|
||||
const labelText = `${widget.icon} ${widget.label}`;
|
||||
ctx.fillText(labelText, labelX, rowY + ROW_HEIGHT / 2);
|
||||
|
||||
const sliderTrackY = rowY + ROW_HEIGHT / 2;
|
||||
ctx.fillText(widget.label, widgetX + 8, rowY + ROW_HEIGHT / 2);
|
||||
|
||||
// Gap уменьшен на 40: было 90, стало 50
|
||||
const trackX = widgetX + 50;
|
||||
// Ширина трека увеличена на 40: было 190, стало 150
|
||||
const trackW = widgetW - 142;
|
||||
const trackY = rowY + ROW_HEIGHT / 2;
|
||||
const trackH = 3;
|
||||
|
||||
ctx.fillStyle = "#444";
|
||||
ctx.fillRect(trackX, trackY - trackH / 2, trackW, trackH);
|
||||
|
||||
const ratio = Math.max(0, Math.min(1, (widget.value - widget.min) / (widget.max - widget.min)));
|
||||
const fillW = trackW * ratio;
|
||||
ctx.fillStyle = "#4CAF50";
|
||||
ctx.fillRect(trackX, trackY - trackH / 2, fillW, trackH);
|
||||
|
||||
const handleX = trackX + fillW;
|
||||
const handleSize = 12;
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(sliderX, sliderTrackY);
|
||||
ctx.lineTo(sliderX + sliderWidth, sliderTrackY);
|
||||
ctx.strokeStyle = "#444444";
|
||||
ctx.lineWidth = SLIDER_HEIGHT;
|
||||
ctx.stroke();
|
||||
|
||||
const normalizedValue = (widget.value - widget.min) / (widget.max - widget.min);
|
||||
const handleX = sliderX + normalizedValue * sliderWidth;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(handleX, sliderTrackY - HANDLE_SIZE);
|
||||
ctx.lineTo(handleX + HANDLE_SIZE, sliderTrackY);
|
||||
ctx.lineTo(handleX, sliderTrackY + HANDLE_SIZE);
|
||||
ctx.lineTo(handleX - HANDLE_SIZE, sliderTrackY);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.arc(handleX, trackY, handleSize / 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = "#ffffff";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
|
||||
const infoRect = { x: infoX, y: rowY + 2, w: INFO_WIDTH, h: ROW_HEIGHT - 4 };
|
||||
drawRoundedRect(ctx, infoX, rowY + 2, INFO_WIDTH, ROW_HEIGHT - 4, 4);
|
||||
ctx.fillStyle = "#3B3B3B";
|
||||
|
||||
const valueW = 50;
|
||||
const valueX = widgetX + widgetW - valueW - 30;
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.strokeStyle = "#444";
|
||||
drawRoundRect(ctx, valueX, rowY + 3, valueW, ROW_HEIGHT - 6, 3);
|
||||
ctx.fill();
|
||||
|
||||
ctx.font = "12px monospace";
|
||||
ctx.fillStyle = "#e0e0e0";
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = "#4CAF50";
|
||||
ctx.font = "10px monospace";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(widget.format(widget.value), valueX + valueW / 2, rowY + ROW_HEIGHT / 2);
|
||||
|
||||
const resetBtnSize = 24;
|
||||
const resetBtnX = widgetX + widgetW - resetBtnSize;
|
||||
const resetBtnY = rowY + (ROW_HEIGHT - resetBtnSize) / 2;
|
||||
ctx.fillStyle = "#252525";
|
||||
ctx.strokeStyle = "#444";
|
||||
drawRoundRect(ctx, resetBtnX, resetBtnY, resetBtnSize, resetBtnSize, 4);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = "#888";
|
||||
ctx.font = "14px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(widget.format(widget.value), infoX + INFO_WIDTH / 2, rowY + ROW_HEIGHT / 2);
|
||||
|
||||
const resetRect = { x: resetX, y: rowY, w: RESET_WIDTH, h: ROW_HEIGHT };
|
||||
ctx.font = "12px Arial";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("🔃", resetX + RESET_WIDTH / 2, rowY + ROW_HEIGHT / 2);
|
||||
|
||||
ctx.fillText("🔄", resetBtnX + resetBtnSize / 2, resetBtnY + resetBtnSize / 2);
|
||||
|
||||
this.rs_widgetRects.push({
|
||||
widget: widget.name,
|
||||
sliderRect: { x: sliderX - HANDLE_SIZE, y: rowY - HANDLE_SIZE, w: sliderWidth + HANDLE_SIZE * 2, h: ROW_HEIGHT + HANDLE_SIZE * 2 },
|
||||
sliderTrackX: sliderX,
|
||||
sliderTrackWidth: sliderWidth,
|
||||
infoRect,
|
||||
resetRect,
|
||||
sliderRect: { x: trackX - 6, y: rowY, w: trackW + 12, h: ROW_HEIGHT },
|
||||
sliderTrackX: trackX,
|
||||
sliderTrackWidth: trackW,
|
||||
resetRect: { x: resetBtnX, y: resetBtnY, w: resetBtnSize, h: resetBtnSize },
|
||||
default: widget.default,
|
||||
min: widget.min,
|
||||
max: widget.max,
|
||||
@@ -299,11 +298,12 @@ app.registerExtension({
|
||||
|
||||
const resetAllY = widgetsY + 2 * (ROW_HEIGHT + ROW_GAP) + ROW_HEIGHT + 6;
|
||||
const resetAllHeight = 22;
|
||||
const buttonWidth = w;
|
||||
|
||||
drawRoundedRect(ctx, padding, resetAllY, buttonWidth, resetAllHeight, CORNER_RADIUS);
|
||||
drawRoundRect(ctx, padding, resetAllY, buttonWidth, resetAllHeight, 4);
|
||||
ctx.fillStyle = "#252525";
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = "#444444";
|
||||
ctx.strokeStyle = "#444";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
|
||||
@@ -400,6 +400,26 @@ app.registerExtension({
|
||||
|
||||
const onMouseMove = nodeType.prototype.onMouseMove;
|
||||
nodeType.prototype.onMouseMove = function (e, pos, graphcanvas) {
|
||||
if (this.rs_dragTarget && e && e.buttons === 0) {
|
||||
this.rs_dragTarget = null;
|
||||
graphcanvas.setDirty(true, true);
|
||||
}
|
||||
if (this.rs_widgetRects && pos && !this.rs_dragTarget) {
|
||||
let hoverChanged = false;
|
||||
for (let i = 0; i < this.rs_widgetRects.length; i++) {
|
||||
const rect = this.rs_widgetRects[i];
|
||||
const isOver = pos[0] >= rect.sliderRect.x && pos[0] <= rect.sliderRect.x + rect.sliderRect.w &&
|
||||
pos[1] >= rect.sliderRect.y && pos[1] <= rect.sliderRect.y + rect.sliderRect.h;
|
||||
if ((this.rs_sliderHover[i] || false) !== isOver) {
|
||||
this.rs_sliderHover[i] = isOver;
|
||||
hoverChanged = true;
|
||||
}
|
||||
}
|
||||
if (hoverChanged) {
|
||||
graphcanvas.setDirty(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.rs_dragTarget) {
|
||||
const delta = pos[0] - this.rs_dragTarget.startX;
|
||||
const deltaValue = (delta / this.rs_dragTarget.sliderWidth) * (this.rs_dragTarget.max - this.rs_dragTarget.min);
|
||||
|
||||
@@ -0,0 +1,467 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "RaykoStudio.ImageText",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name !== "LoadImageWithText") return;
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
const node = this;
|
||||
|
||||
node.data = {
|
||||
mode: "read",
|
||||
prefix: "Civitai/prompt",
|
||||
text: "",
|
||||
selected_image: ""
|
||||
};
|
||||
|
||||
const MIN_WIDTH = 360;
|
||||
const MIN_HEIGHT = 520;
|
||||
const PREVIEW_FIXED_HEIGHT = 250;
|
||||
|
||||
node.MIN_WIDTH = MIN_WIDTH;
|
||||
node.MIN_HEIGHT = MIN_HEIGHT;
|
||||
node.setSize([MIN_WIDTH, MIN_HEIGHT]);
|
||||
|
||||
const w_mode = node.widgets?.find(w => w.name === "mode");
|
||||
const w_prefix = node.widgets?.find(w => w.name === "filename_prefix");
|
||||
const w_text = node.widgets?.find(w => w.name === "text_input");
|
||||
const w_image = node.widgets?.find(w => w.name === "image");
|
||||
|
||||
const syncWidget = (widget, dataKey) => {
|
||||
if (widget) {
|
||||
widget.value = node.data[dataKey];
|
||||
widget.serializeValue = () => node.data[dataKey];
|
||||
}
|
||||
};
|
||||
|
||||
if (node.widgets) {
|
||||
node.widgets.forEach(w => {
|
||||
if (w.name === "mode" && w.value) node.data.mode = w.value;
|
||||
if (w.name === "filename_prefix" && w.value) node.data.prefix = w.value;
|
||||
if (w.name === "text_input" && w.value) node.data.text = w.value;
|
||||
if (w.name === "image" && w.value) node.data.selected_image = w.value;
|
||||
|
||||
w.hidden = true;
|
||||
w.type = "hidden";
|
||||
w.computeSize = () => [0, 0];
|
||||
w.computedHeight = 0;
|
||||
w.draw = function() {};
|
||||
|
||||
if (w.element && w.element.parentNode) {
|
||||
w.element.parentNode.removeChild(w.element);
|
||||
}
|
||||
|
||||
w.serializeValue = () => {
|
||||
if (w.name === "mode") return node.data.mode;
|
||||
if (w.name === "filename_prefix") return node.data.prefix;
|
||||
if (w.name === "text_input") return node.data.text;
|
||||
if (w.name === "image") return node.data.selected_image;
|
||||
return w.value;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
node.syncData = function() {
|
||||
if (node.graph) {
|
||||
node.graph.setDirtyCanvas(true, true);
|
||||
node.graph.changeTracker?.dispatchEvent(new Event("change"));
|
||||
}
|
||||
};
|
||||
|
||||
node.image = new Image();
|
||||
node.imageLoaded = false;
|
||||
node.imageLoading = false;
|
||||
node.dragOver = false;
|
||||
|
||||
node.loadImage = function(imagePath) {
|
||||
if (!imagePath || node.imageLoading) return;
|
||||
|
||||
node.imageLoading = true;
|
||||
node.data.selected_image = imagePath;
|
||||
syncWidget(w_image, 'selected_image');
|
||||
|
||||
let filename = imagePath;
|
||||
let subfolder = "";
|
||||
if (imagePath.includes("/")) {
|
||||
const parts = imagePath.split("/");
|
||||
subfolder = parts[0];
|
||||
filename = parts.slice(1).join("/");
|
||||
}
|
||||
|
||||
let imgUrl = `/view?filename=${encodeURIComponent(filename)}&type=input`;
|
||||
if (subfolder) imgUrl += `&subfolder=${encodeURIComponent(subfolder)}`;
|
||||
imgUrl += `&t=${Date.now()}`;
|
||||
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
node.image = img;
|
||||
node.imageLoaded = true;
|
||||
node.imageLoading = false;
|
||||
node.syncData();
|
||||
};
|
||||
img.onerror = () => {
|
||||
node.imageLoaded = false;
|
||||
node.imageLoading = false;
|
||||
node.syncData();
|
||||
};
|
||||
img.src = imgUrl;
|
||||
};
|
||||
|
||||
const uploadFileAndLoad = async (file) => {
|
||||
if (!file || !file.type.startsWith('image/')) return false;
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
formData.append('subfolder', '');
|
||||
formData.append('type', 'input');
|
||||
|
||||
try {
|
||||
const response = await fetch('/upload/image', { method: 'POST', body: formData });
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
const imageName = result.name || result.filename;
|
||||
const subfolder = result.subfolder || '';
|
||||
const finalName = subfolder ? `${subfolder}/${imageName}` : imageName;
|
||||
node.loadImage(finalName);
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[RS Image-Text] Upload error:", err);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
node.triggerFileUpload = function() {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = 'image/png, image/jpeg, image/webp, image/bmp';
|
||||
fileInput.onchange = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
await uploadFileAndLoad(file);
|
||||
fileInput.remove();
|
||||
};
|
||||
fileInput.click();
|
||||
};
|
||||
|
||||
const handleCanvasDrop = (e) => {
|
||||
if (!e.dataTransfer || !e.dataTransfer.files.length) return;
|
||||
const rect = app.canvas.canvas.getBoundingClientRect();
|
||||
const ds = app.canvas.ds;
|
||||
const nodeX = rect.left + ((node.pos[0] + ds.offset[0]) * ds.scale);
|
||||
const nodeY = rect.top + ((node.pos[1] + ds.offset[1]) * ds.scale);
|
||||
const nodeW = node.size[0] * ds.scale;
|
||||
const nodeH = node.size[1] * ds.scale;
|
||||
|
||||
if (e.clientX >= nodeX && e.clientX <= nodeX + nodeW &&
|
||||
e.clientY >= nodeY && e.clientY <= nodeY + nodeH) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
uploadFileAndLoad(file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCanvasDragOver = (e) => {
|
||||
if (!e.dataTransfer || !e.dataTransfer.files.length) return;
|
||||
const rect = app.canvas.canvas.getBoundingClientRect();
|
||||
const ds = app.canvas.ds;
|
||||
const nodeX = rect.left + ((node.pos[0] + ds.offset[0]) * ds.scale);
|
||||
const nodeY = rect.top + ((node.pos[1] + ds.offset[1]) * ds.scale);
|
||||
const nodeW = node.size[0] * ds.scale;
|
||||
const nodeH = node.size[1] * ds.scale;
|
||||
|
||||
if (e.clientX >= nodeX && e.clientX <= nodeX + nodeW &&
|
||||
e.clientY >= nodeY && e.clientY <= nodeY + nodeH) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
app.canvas.canvas.addEventListener('dragover', handleCanvasDragOver, { capture: true });
|
||||
app.canvas.canvas.addEventListener('drop', handleCanvasDrop, { capture: true });
|
||||
|
||||
const updateUIFromData = () => {
|
||||
if (prefixInput) prefixInput.value = node.data.prefix || "";
|
||||
if (customTextArea) customTextArea.value = node.data.text || "";
|
||||
updateModeVisuals();
|
||||
};
|
||||
|
||||
node.onSerialize = function(o) {
|
||||
syncWidget(w_mode, 'mode');
|
||||
syncWidget(w_prefix, 'prefix');
|
||||
syncWidget(w_text, 'text');
|
||||
syncWidget(w_image, 'selected_image');
|
||||
o.data = node.data;
|
||||
};
|
||||
|
||||
node.onConfigure = function(o) {
|
||||
if (o && o.data) {
|
||||
node.data = { ...node.data, ...o.data };
|
||||
}
|
||||
|
||||
node.imageLoading = false;
|
||||
node.imageLoaded = false;
|
||||
|
||||
if (node.data.selected_image) {
|
||||
node.loadImage(node.data.selected_image);
|
||||
}
|
||||
|
||||
updateUIFromData();
|
||||
|
||||
syncWidget(w_mode, 'mode');
|
||||
syncWidget(w_prefix, 'prefix');
|
||||
syncWidget(w_text, 'text');
|
||||
syncWidget(w_image, 'selected_image');
|
||||
};
|
||||
|
||||
node.visibilityHandler = function() {
|
||||
if (!document.hidden && node.graph) {
|
||||
node.graph.setDirtyCanvas(true, true);
|
||||
node.syncData();
|
||||
}
|
||||
};
|
||||
document.addEventListener("visibilitychange", node.visibilityHandler);
|
||||
|
||||
const mainContainer = document.createElement("div");
|
||||
mainContainer.style.cssText = `
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
const modeContainer = document.createElement("div");
|
||||
modeContainer.style.cssText = "display:flex; gap:8px; width:100%; pointer-events: auto;";
|
||||
|
||||
const createModeBtn = (label, value) => {
|
||||
const btn = document.createElement("button");
|
||||
btn.textContent = label;
|
||||
btn.style.cssText = `flex:1; height:28px; padding:0 12px; border:2px solid #555; border-radius:4px; font-weight:normal; cursor:pointer; transition:all 0.2s; font-size:12px; pointer-events: auto;`;
|
||||
btn.onclick = () => {
|
||||
node.data.mode = value;
|
||||
syncWidget(w_mode, 'mode');
|
||||
updateModeVisuals();
|
||||
node.syncData();
|
||||
};
|
||||
return btn;
|
||||
};
|
||||
const btnRead = createModeBtn("📄 READ from IMAGE", "read");
|
||||
const btnWrite = createModeBtn("✏️ WRITE to IMAGE", "write");
|
||||
modeContainer.appendChild(btnRead);
|
||||
modeContainer.appendChild(btnWrite);
|
||||
|
||||
const updateModeVisuals = () => {
|
||||
const isRead = node.data.mode === "read";
|
||||
btnRead.style.backgroundColor = isRead ? "#4CAF50" : "#333";
|
||||
btnRead.style.borderColor = isRead ? "#81C784" : "#555";
|
||||
btnRead.style.color = isRead ? "#fff" : "#aaa";
|
||||
btnWrite.style.backgroundColor = !isRead ? "#2196F3" : "#333";
|
||||
btnWrite.style.borderColor = !isRead ? "#64B5F6" : "#555";
|
||||
btnWrite.style.color = !isRead ? "#fff" : "#aaa";
|
||||
};
|
||||
updateModeVisuals();
|
||||
|
||||
const prefixContainer = document.createElement("div");
|
||||
prefixContainer.style.cssText = "display:flex; align-items:center; gap:8px; width:100%; pointer-events: auto;";
|
||||
|
||||
const prefixLabel = document.createElement("label");
|
||||
prefixLabel.textContent = "PREFIX:";
|
||||
prefixLabel.style.cssText = "color:#aaa; font-size:14px; font-weight:normal; white-space:nowrap; pointer-events: auto; cursor:default;";
|
||||
|
||||
const prefixInput = document.createElement("input");
|
||||
prefixInput.type = "text";
|
||||
prefixInput.value = node.data.prefix;
|
||||
prefixInput.style.cssText = "flex:1; height:28px; padding:0 12px; background:#222; color:#eee; border:1px solid #444; border-radius:4px; font-size:14px; box-sizing:border-box; pointer-events: auto;";
|
||||
|
||||
prefixInput.oninput = () => {
|
||||
node.data.prefix = prefixInput.value;
|
||||
syncWidget(w_prefix, 'prefix');
|
||||
node.syncData();
|
||||
};
|
||||
|
||||
prefixContainer.appendChild(prefixLabel);
|
||||
prefixContainer.appendChild(prefixInput);
|
||||
|
||||
const textContainer = document.createElement("div");
|
||||
textContainer.style.cssText = "width:100%; pointer-events: auto;";
|
||||
|
||||
const customTextArea = document.createElement("textarea");
|
||||
customTextArea.value = node.data.text;
|
||||
customTextArea.placeholder = "Enter text here...";
|
||||
customTextArea.style.cssText = "width:100%; min-height:50px; padding:6px; background:#222; color:#eee; border:1px solid #444; border-radius:4px; resize: none; font-family:monospace; font-size:12px; box-sizing:border-box; pointer-events: auto;";
|
||||
|
||||
customTextArea.oninput = () => {
|
||||
node.data.text = customTextArea.value;
|
||||
syncWidget(w_text, 'text');
|
||||
node.syncData();
|
||||
};
|
||||
textContainer.appendChild(customTextArea);
|
||||
|
||||
const buttonsContainer = document.createElement("div");
|
||||
buttonsContainer.style.cssText = "display:flex; gap:8px; width:100%; pointer-events: auto;";
|
||||
|
||||
const btnUpload = document.createElement("button");
|
||||
btnUpload.textContent = "📂 UPLOAD IMAGE";
|
||||
btnUpload.style.cssText = "flex:1; height:28px; background:#2a2a2a; color:#4CAF50; border:2px solid #4CAF50; border-radius:4px; cursor:pointer; font-weight:normal; font-size:12px; transition:all 0.2s; pointer-events: auto;";
|
||||
btnUpload.onmouseover = () => { btnUpload.style.backgroundColor = "#3a3a3a"; };
|
||||
btnUpload.onmouseout = () => { btnUpload.style.backgroundColor = "#2a2a2a"; };
|
||||
btnUpload.onclick = () => node.triggerFileUpload();
|
||||
|
||||
const btnClear = document.createElement("button");
|
||||
btnClear.textContent = "🗑️ CLEAR TEXT";
|
||||
btnClear.style.cssText = "flex:1; height:28px; background:#2a2a2a; color:#dc3545; border:2px solid #dc3545; border-radius:4px; cursor:pointer; font-weight:normal; font-size:12px; transition:all 0.2s; pointer-events: auto;";
|
||||
btnClear.onmouseover = () => { btnClear.style.backgroundColor = "#3a3a3a"; };
|
||||
btnClear.onmouseout = () => { btnClear.style.backgroundColor = "#2a2a2a"; };
|
||||
btnClear.onclick = () => {
|
||||
node.data.text = "";
|
||||
customTextArea.value = "";
|
||||
syncWidget(w_text, 'text');
|
||||
node.syncData();
|
||||
};
|
||||
|
||||
buttonsContainer.appendChild(btnUpload);
|
||||
buttonsContainer.appendChild(btnClear);
|
||||
|
||||
mainContainer.appendChild(modeContainer);
|
||||
mainContainer.appendChild(prefixContainer);
|
||||
mainContainer.appendChild(textContainer);
|
||||
mainContainer.appendChild(buttonsContainer);
|
||||
|
||||
node.addDOMWidget("custom_widgets", "customtext", mainContainer);
|
||||
|
||||
node.customTextArea = customTextArea;
|
||||
|
||||
const originalOnResize = node.onResize;
|
||||
node.onResize = function(size) {
|
||||
if (size[0] < MIN_WIDTH) size[0] = MIN_WIDTH;
|
||||
if (size[1] < MIN_HEIGHT) size[1] = MIN_HEIGHT;
|
||||
|
||||
const titleBarHeight = 30;
|
||||
const topPadding = 18;
|
||||
const gap = 8;
|
||||
const bottomPadding = 20;
|
||||
const previewSpacing = 35;
|
||||
|
||||
const fixedElements = titleBarHeight + topPadding +
|
||||
28 + gap + 28 + gap +
|
||||
gap + 28 +
|
||||
previewSpacing +
|
||||
PREVIEW_FIXED_HEIGHT +
|
||||
bottomPadding;
|
||||
|
||||
const textareaHeight = Math.max(50, size[1] - fixedElements);
|
||||
|
||||
if (customTextArea) {
|
||||
customTextArea.style.height = textareaHeight + 'px';
|
||||
}
|
||||
|
||||
if (originalOnResize) originalOnResize.apply(this, arguments);
|
||||
node.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
node.onDrawBackground = function(ctx) {
|
||||
const w = this.size[0];
|
||||
const h = this.size[1];
|
||||
const radius = 8;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(radius, 0);
|
||||
ctx.lineTo(w - radius, 0);
|
||||
ctx.quadraticCurveTo(w, 0, w, radius);
|
||||
ctx.lineTo(w, h - radius);
|
||||
ctx.quadraticCurveTo(w, h, w - radius, h);
|
||||
ctx.lineTo(radius, h);
|
||||
ctx.quadraticCurveTo(0, h, 0, h - radius);
|
||||
ctx.lineTo(0, radius);
|
||||
ctx.quadraticCurveTo(0, 0, radius, 0);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.fillStyle = "#353535";
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
node.onDrawForeground = function(ctx) {
|
||||
if (this.flags.collapsed) return;
|
||||
const [w, h] = this.size;
|
||||
|
||||
const leftRightPadding = 10;
|
||||
const bottomPadding = 20;
|
||||
const previewSpacing = 35;
|
||||
|
||||
const previewY = h - bottomPadding - PREVIEW_FIXED_HEIGHT;
|
||||
const previewWidth = w - (leftRightPadding * 2);
|
||||
|
||||
if (this.imageLoaded && this.image) {
|
||||
const imgRatio = this.image.width / this.image.height;
|
||||
let drawW, drawH;
|
||||
|
||||
if (previewWidth / PREVIEW_FIXED_HEIGHT > imgRatio) {
|
||||
drawH = PREVIEW_FIXED_HEIGHT;
|
||||
drawW = drawH * imgRatio;
|
||||
} else {
|
||||
drawW = previewWidth;
|
||||
drawH = drawW / imgRatio;
|
||||
}
|
||||
|
||||
const drawX = leftRightPadding + (previewWidth - drawW) / 2;
|
||||
const drawY = previewY + (PREVIEW_FIXED_HEIGHT - drawH) / 2;
|
||||
|
||||
ctx.drawImage(this.image, drawX, drawY, drawW, drawH);
|
||||
ctx.strokeStyle = this.dragOver ? "#4CAF50" : "#444";
|
||||
ctx.lineWidth = this.dragOver ? 2 : 1;
|
||||
ctx.strokeRect(drawX, drawY, drawW, drawH);
|
||||
|
||||
ctx.fillStyle = "#888";
|
||||
ctx.font = "15px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "bottom";
|
||||
ctx.fillText(`${this.image.width} × ${this.image.height}`, w / 2, previewY - 5);
|
||||
} else {
|
||||
ctx.fillStyle = "#1a1a1a";
|
||||
ctx.fillRect(leftRightPadding, previewY, previewWidth, PREVIEW_FIXED_HEIGHT);
|
||||
ctx.strokeStyle = this.dragOver ? "#4CAF50" : "#333";
|
||||
ctx.lineWidth = this.dragOver ? 2 : 1;
|
||||
ctx.strokeRect(leftRightPadding, previewY, previewWidth, PREVIEW_FIXED_HEIGHT);
|
||||
|
||||
ctx.fillStyle = "#555";
|
||||
ctx.font = "14px Arial";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText("Drop Image Here", w / 2, previewY + PREVIEW_FIXED_HEIGHT / 2);
|
||||
}
|
||||
};
|
||||
|
||||
node.onMouseMove = function() {};
|
||||
node.onMouseDown = function() { return false; };
|
||||
|
||||
const originalOnRemoved = node.onRemoved;
|
||||
node.onRemoved = function() {
|
||||
document.removeEventListener("visibilitychange", node.visibilityHandler);
|
||||
app.canvas.canvas.removeEventListener('dragover', handleCanvasDragOver, { capture: true });
|
||||
app.canvas.canvas.removeEventListener('drop', handleCanvasDrop, { capture: true });
|
||||
if (originalOnRemoved) originalOnRemoved.apply(this, arguments);
|
||||
};
|
||||
|
||||
if (node.data.selected_image) {
|
||||
node.loadImage(node.data.selected_image);
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
node.onResize([MIN_WIDTH, MIN_HEIGHT]);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
setup() {}
|
||||
});
|
||||
+7
-2
@@ -368,11 +368,15 @@ app.registerExtension({
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
// --- ИЗМЕНЕННАЯ ФУНКЦИЯ drawButton с закруглением ---
|
||||
this.drawButton = function(ctx, text, x, y, w, h, iconColor = "#fff") {
|
||||
const radius = 5;
|
||||
ctx.fillStyle = "#242427";
|
||||
ctx.fillRect(x, y, w, h);
|
||||
this.drawRoundedRect(ctx, x, y, w, h, radius);
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = "#3a3a3c";
|
||||
ctx.strokeRect(x, y, w, h);
|
||||
this.drawRoundedRect(ctx, x, y, w, h, radius);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.font = "bold 11px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
@@ -388,6 +392,7 @@ app.registerExtension({
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.fillText(label, x + w/2 + 8, y + h/2);
|
||||
};
|
||||
// ----------------------------------------------
|
||||
|
||||
this.drawRoundedRect = function(ctx, x, y, w, h, r) {
|
||||
ctx.beginPath();
|
||||
|
||||
+52
-18
@@ -253,8 +253,10 @@ app.registerExtension({
|
||||
const canvasRect = canvasEl.getBoundingClientRect();
|
||||
const ds = app.canvas.ds;
|
||||
const btnY = this.size[1] - 45;
|
||||
const btnW = (this.size[0] - 50) / 3;
|
||||
const btnX = 15 + (buttonIndex * (btnW + 5));
|
||||
const padding = 15;
|
||||
const gap = 10;
|
||||
const btnW = (this.size[0] - 2 * padding - 2 * gap) / 3;
|
||||
const btnX = padding + (buttonIndex * (btnW + gap));
|
||||
|
||||
const nodeScreenX = canvasRect.left + ((this.pos[0] + ds.offset[0]) * ds.scale);
|
||||
const nodeScreenY = canvasRect.top + ((this.pos[1] + ds.offset[1]) * ds.scale);
|
||||
@@ -282,6 +284,7 @@ app.registerExtension({
|
||||
syncData();
|
||||
};
|
||||
|
||||
// ---- ФУНКЦИЯ РАСЧЁТА ПОЛОЖЕНИЯ ИЗОБРАЖЕНИЯ (с учётом смещения вниз) ----
|
||||
const calculateImageRect = () => {
|
||||
if (!app.canvas) return null;
|
||||
|
||||
@@ -302,11 +305,13 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const titleBarHeight = LiteGraph.NODE_TITLE_HEIGHT || 30;
|
||||
const padding = 10;
|
||||
const leftRightPadding = 10;
|
||||
const topPadding = 18; // 10 (базовый) + 8 (дополнительный сдвиг вниз)
|
||||
const bottomPadding = 10;
|
||||
const footerHeight = 50;
|
||||
|
||||
const availableHeight = (node.size[1] * scale) - (titleBarHeight * scale) - (widgetsHeight * scale) - (footerHeight * scale) - (padding * 2 * scale);
|
||||
const availableWidth = (node.size[0] * scale) - (padding * 2 * scale);
|
||||
const availableHeight = (node.size[1] * scale) - (titleBarHeight * scale) - (widgetsHeight * scale) - (footerHeight * scale) - (topPadding * scale) - (bottomPadding * scale);
|
||||
const availableWidth = (node.size[0] * scale) - (leftRightPadding * 2 * scale);
|
||||
|
||||
if (availableWidth <= 0 || availableHeight <= 0) return null;
|
||||
|
||||
@@ -326,11 +331,12 @@ app.registerExtension({
|
||||
contentScale = drawW / node.image.width;
|
||||
}
|
||||
|
||||
const drawX = nodeScreenX + (padding * scale) + ((availableWidth - drawW) / 2);
|
||||
const drawY = nodeScreenY + (titleBarHeight * scale) + (widgetsHeight * scale) + (padding * scale) + ((availableHeight - drawH) / 2);
|
||||
const drawX = nodeScreenX + (leftRightPadding * scale) + ((availableWidth - drawW) / 2);
|
||||
const drawY = nodeScreenY + (titleBarHeight * scale) + (widgetsHeight * scale) + (topPadding * scale) + ((availableHeight - drawH) / 2);
|
||||
|
||||
return { left: drawX, top: drawY, width: drawW, height: drawH, scale: contentScale };
|
||||
};
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
const syncPosition = () => {
|
||||
if (!_overlayCanvas) return;
|
||||
@@ -478,6 +484,7 @@ app.registerExtension({
|
||||
updateLoop();
|
||||
};
|
||||
|
||||
// ---- ОТРИСОВКА КНОПОК И ИЗОБРАЖЕНИЯ (с учётом смещения) ----
|
||||
node.onDrawForeground = function(ctx) {
|
||||
if (this.flags.collapsed) return;
|
||||
|
||||
@@ -490,16 +497,22 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const titleBarHeight = LiteGraph.NODE_TITLE_HEIGHT || 30;
|
||||
const padding = 10;
|
||||
const leftRightPadding = 10;
|
||||
const topPadding = 18; // 10 + 8
|
||||
const bottomPadding = 10;
|
||||
const footerHeight = 50;
|
||||
const btnH = 28;
|
||||
const btnY = h - 45;
|
||||
const btnW = (w - 50) / 3;
|
||||
|
||||
// Параметры кнопок (расстояние +10, закругление)
|
||||
const btnPadding = 15;
|
||||
const btnGap = 10;
|
||||
const btnW = (w - 2 * btnPadding - 2 * btnGap) / 3;
|
||||
|
||||
// === РАЗМЕРЫ ИЗОБРАЖЕНИЯ НАД ПРЕВЬЮ ===
|
||||
if (this.imageLoaded && this.image) {
|
||||
ctx.fillStyle = "#888";
|
||||
ctx.font = "11px sans-serif";
|
||||
ctx.font = "15px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillText(
|
||||
@@ -510,9 +523,9 @@ app.registerExtension({
|
||||
}
|
||||
// ====================================
|
||||
|
||||
const startY = titleBarHeight + widgetsHeight + padding;
|
||||
const availableHeight = h - startY - footerHeight - padding;
|
||||
const availableWidth = w - (padding * 2);
|
||||
const startY = titleBarHeight + widgetsHeight + topPadding;
|
||||
const availableHeight = h - startY - footerHeight - bottomPadding;
|
||||
const availableWidth = w - (leftRightPadding * 2);
|
||||
|
||||
if (this.imageLoaded && this.image) {
|
||||
const imgRatio = this.image.width / this.image.height;
|
||||
@@ -526,7 +539,7 @@ app.registerExtension({
|
||||
drawH = drawW / imgRatio;
|
||||
}
|
||||
|
||||
const drawX = padding + (availableWidth - drawW) / 2;
|
||||
const drawX = leftRightPadding + (availableWidth - drawW) / 2;
|
||||
const drawY = startY + (availableHeight - drawH) / 2;
|
||||
|
||||
ctx.drawImage(this.image, drawX, drawY, drawW, drawH);
|
||||
@@ -534,7 +547,7 @@ app.registerExtension({
|
||||
ctx.strokeRect(drawX, drawY, drawW, drawH);
|
||||
} else {
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.fillRect(padding, startY, availableWidth, availableHeight);
|
||||
ctx.fillRect(leftRightPadding, startY, availableWidth, availableHeight);
|
||||
ctx.fillStyle = "#555";
|
||||
ctx.font = "14px Arial";
|
||||
ctx.textAlign = "center";
|
||||
@@ -542,17 +555,37 @@ app.registerExtension({
|
||||
ctx.fillText("Select Image...", w / 2, startY + availableHeight / 2);
|
||||
}
|
||||
|
||||
// ---- РИСУЕМ КНОПКИ (скруглённые, с увеличенным расстоянием) ----
|
||||
for (let i = 0; i < this.buttons.length; i++) {
|
||||
const btn = this.buttons[i];
|
||||
btn.x = 15 + (i * (btnW + 5));
|
||||
btn.x = btnPadding + (i * (btnW + btnGap));
|
||||
btn.y = btnY;
|
||||
btn.w = btnW;
|
||||
btn.h = btnH;
|
||||
|
||||
// Фон со скруглением
|
||||
const radius = 4;
|
||||
const x = btn.x, y = btn.y, w = btn.w, h = btn.h;
|
||||
ctx.fillStyle = btn.hover ? "#444" : "#2a2a2a";
|
||||
ctx.fillRect(btn.x, btn.y, btn.w, btn.h);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.lineTo(x + w - radius, y);
|
||||
ctx.quadraticCurveTo(x + w, y, x + w, y + radius);
|
||||
ctx.lineTo(x + w, y + h - radius);
|
||||
ctx.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
|
||||
ctx.lineTo(x + radius, y + h);
|
||||
ctx.quadraticCurveTo(x, y + h, x, y + h - radius);
|
||||
ctx.lineTo(x, y + radius);
|
||||
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// Обводка
|
||||
ctx.strokeStyle = btn.color;
|
||||
ctx.strokeRect(btn.x, btn.y, btn.w, btn.h);
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
|
||||
// Текст
|
||||
ctx.fillStyle = btn.color;
|
||||
ctx.font = "bold 11px Arial";
|
||||
ctx.textAlign = "center";
|
||||
@@ -560,6 +593,7 @@ app.registerExtension({
|
||||
ctx.fillText(btn.label, btn.x + btn.w / 2, btn.y + btn.h / 2);
|
||||
}
|
||||
};
|
||||
// ------------------------------------------------------------
|
||||
|
||||
node.onMouseMove = function(event, pos) {
|
||||
const [x, y] = pos;
|
||||
|
||||
Reference in New Issue
Block a user