From 94acb31ea489f1e4b66c155eb2d7c6f388f69b02 Mon Sep 17 00:00:00 2001 From: Blueprint Coding Date: Mon, 17 Feb 2025 15:58:08 -0700 Subject: [PATCH] Lots of resize improvements and fixed critical bug Lots of resizing improvements in multi-int node and fixed issue where wrong number of outputs would appear if less than 5 and that outputs wouldn't initiate properly to show the correct value. --- js/MultiIntNode.js | 396 +++++++++++++++++++++++++++------------------ 1 file changed, 239 insertions(+), 157 deletions(-) diff --git a/js/MultiIntNode.js b/js/MultiIntNode.js index 6325ef7..d3cf1a3 100644 --- a/js/MultiIntNode.js +++ b/js/MultiIntNode.js @@ -26,13 +26,39 @@ function getTextColorForBackground(bgColor) { return luminance < 0.5 ? '#ffffff' : '#000000'; // White for dark background, black for light } +function truncateText(ctx, text, maxWidth) { + const ellipsis = "..."; + const ellipsisWidth = ctx.measureText(ellipsis).width; + + if (ctx.measureText(text).width <= maxWidth) { + return text; // No need to truncate + } + + // Start removing characters until it fits + let truncated = text; + while (truncated && + ctx.measureText(truncated).width + ellipsisWidth > maxWidth) { + truncated = truncated.slice(0, -1); + } + return truncated + ellipsis; + } + + class MultiIntNode { constructor(node) { this.node = node; // Default properties if (typeof node.properties.activeCount !== "number") { - node.properties.activeCount = 5; + node.properties.activeCount = 20; + } + // Default manual height settings (if not set) + if (typeof node.properties.manualHeight !== "boolean") { + node.properties.manualHeight = false; + } + if (typeof node.properties.customHeight !== "number") { + // If not manually set, leave it undefined (or you can set a default) + node.properties.customHeight = undefined; } // We'll use an array for per–row step values. if (!Array.isArray(node.properties.intSteps)) { @@ -95,70 +121,81 @@ class MultiIntNode { onDrawForeground(ctx) { if (this.node.flags.collapsed) return; + + // Ensure outputs match activeCount + if (this.node.outputs.length !== this.node.properties.activeCount) { + this.node.outputs = []; + this.node.widgets = []; + // Rebuild outputs/widgets + this.setupOutputsAndWidgets(); + if (this.node.onOutputsChange) { + this.node.onOutputsChange(); + } + } + const margin = 8; const rowHeight = 30; const rowSpacing = margin; const count = this.node.properties.activeCount; - // Compute node size - let totalHeight = margin + (count * (rowHeight + rowSpacing)); - const MIN_WIDTH = 320; - if (this.node.size[0] < MIN_WIDTH) { - this.node.size[0] = MIN_WIDTH; + // Enforce minimum height so rows don’t overflow + const minHeight = margin + (count * (rowHeight + rowSpacing)); + if (this.node.size[1] < minHeight) { + this.node.size[1] = minHeight; } - this.node.size[1] = totalHeight; - - // Use the color stored in bgColor, or fallback to the default color - const bgColor = this.node.properties.bgColor || "#181818"; // Default color - ctx.fillStyle = bgColor; - // Draw a background with rounded bottom corners - const radius = 8; // Adjust the radius to control the roundness of the corners + // Enforce minimum width + if (this.node.size[0] < 250) { + this.node.size[0] = 250; + } + + // Enforce max width + if (this.node.size[0] > 1000) { + this.node.size[0] = 1000; + } + + + + // Draw node background + const bgColor = this.node.properties.bgColor || "#181818"; + ctx.fillStyle = bgColor; + const radius = 8; ctx.beginPath(); - // Top-left corner - ctx.moveTo(0, 0); - ctx.lineTo(this.node.size[0], 0); // Top edge - ctx.lineTo(this.node.size[0], this.node.size[1] - radius); // Right edge (top to bottom, leaving space for bottom rounded corner) - ctx.arcTo(this.node.size[0], this.node.size[1], this.node.size[0] - radius, this.node.size[1], radius); // Bottom-right corner - ctx.lineTo(radius, this.node.size[1]); // Bottom edge (right to left) - ctx.arcTo(0, this.node.size[1], 0, this.node.size[1] - radius, radius); // Bottom-left corner - ctx.lineTo(0, radius); // Left edge (bottom to top) + ctx.moveTo(0, 0); + ctx.lineTo(this.node.size[0], 0); + ctx.lineTo(this.node.size[0], this.node.size[1] - radius); + ctx.arcTo(this.node.size[0], this.node.size[1], this.node.size[0] - radius, this.node.size[1], radius); + ctx.lineTo(radius, this.node.size[1]); + ctx.arcTo(0, this.node.size[1], 0, this.node.size[1] - radius, radius); + ctx.lineTo(0, radius); ctx.closePath(); ctx.fill(); - - // Row backgrounds - const rowBgWidth = this.node.size[0] - margin - 38; // Reserve space for outputs + + const rowBgWidth = this.node.size[0] - margin - 38; // space for outputs + const rowBgColor = this.node.properties.rowBgColor || "#121212"; + const rowTextColor = getTextColorForBackground(rowBgColor) || "#fff"; + this.labelRects = []; this.valueRects = []; this.pencilRects = []; this.incrementRects = []; this.decrementRects = []; - - const rowBgColor = this.node.properties.rowBgColor || "#121212"; - const rowTextColor = getTextColorForBackground(rowBgColor) || '#fff'; let y = margin; for (let i = 0; i < count; i++) { - // Row background - // Set the radius for the rounded corners - const radius = 5; // Adjust as needed - // Row background with rounded corners + // Draw row background ctx.fillStyle = rowBgColor; ctx.beginPath(); - // Top-left corner - ctx.moveTo(margin + radius, y); - ctx.arcTo(margin + rowBgWidth, y, margin + rowBgWidth, y + rowHeight, radius); // Top-right corner - // Bottom-right corner - ctx.arcTo(margin + rowBgWidth, y + rowHeight, margin, y + rowHeight, radius); // Bottom-right corner - // Bottom-left corner - ctx.arcTo(margin, y + rowHeight, margin, y, radius); // Bottom-left corner - // Top-left corner (to close the path) - ctx.arcTo(margin, y, margin + rowBgWidth, y, radius); // Top-left corner - + ctx.moveTo(margin + radius, y); + ctx.arcTo(margin + rowBgWidth, y, margin + rowBgWidth, y + rowHeight, radius); + ctx.arcTo(margin + rowBgWidth, y + rowHeight, margin, y + rowHeight, radius); + ctx.arcTo(margin, y + rowHeight, margin, y, radius); + ctx.arcTo(margin, y, margin + rowBgWidth, y, radius); ctx.closePath(); ctx.fill(); - // Static label (left) - e.g., i1, i2, i3, etc. + + // 1) Static label: "·iN" ctx.fillStyle = rowTextColor; ctx.font = "12px Arial"; ctx.textBaseline = "middle"; @@ -168,43 +205,85 @@ class MultiIntNode { const staticLabelX = margin + 2; const staticLabelY = y + rowHeight / 2; ctx.fillText(staticLabel, staticLabelX, staticLabelY); - - // Draw the vertical line - const lineStartX = staticLabelX + staticLabelMetrics.width + 4; // Right of static label - const lineStartY = staticLabelY - rowHeight / 2; // Start from the middle of the static label - const lineEndY = lineStartY + rowHeight; // End after the row height (making the line same length as row) - - // Set the line color - ctx.strokeStyle = rowTextColor; // Same color as label for consistency - ctx.lineWidth = 1; // Line thickness - + + // Vertical line after static label + const lineStartX = staticLabelX + staticLabelMetrics.width + 4; + ctx.strokeStyle = rowTextColor; + ctx.lineWidth = 1; ctx.beginPath(); - ctx.moveTo(lineStartX, lineStartY); // Starting point of the line - ctx.lineTo(lineStartX, lineEndY); // Vertical line to the bottom of the row - ctx.stroke(); // Actually draw the line + ctx.moveTo(lineStartX, staticLabelY - rowHeight / 2); + ctx.lineTo(lineStartX, staticLabelY + rowHeight / 2); + ctx.stroke(); + + // 2) Numeric display position + const valX = margin + rowBgWidth - 90; + const valY = staticLabelY; // same vertical center + + // Calculate available space for the custom label: + const labelAreaStart = lineStartX + 6; // small gap after line + const labelAreaEnd = valX - 80; // leave some padding before the numeric value + let availableWidth = labelAreaEnd - labelAreaStart; + if (availableWidth < 0) { + availableWidth = 0; + } + + // Define breakpoint rules for label width adjustments. + const labelWidthRules = [ + { max: 10, set: 60 }, + { max: 15, set: 65 }, + { max: 30, set: 80 }, + { max: 50, set: 90 }, + { max: 70, set: 100 }, + { max: 90, set: 110 }, + { max: 150, multiplier: 1.2 }, + { max: 200, multiplier: 1.15 }, + { max: 400, multiplier: 0.98 }, + { max: 500, multiplier: 0.95 }, + { max: Infinity, multiplier: 0.93 } + ]; + + function adjustLabelWidth(width) { + for (const rule of labelWidthRules) { + if (width <= rule.max) { + if (rule.set !== undefined) { + return rule.set; + } + if (rule.multiplier !== undefined) { + return width * rule.multiplier; + } + } + } + return width; + } + + // Raw label text from node properties + const customLabelRaw = this.node.properties.labels[i] || `INT ${i + 1}`; + // Adjust the available width using our rules: + const adjustedWidth = adjustLabelWidth(availableWidth); - // Custom label (right of static label) - e.g., INT 1, INT 2, etc. + // Then truncate the text to fit within adjustedWidth: + const displayLabel = truncateText(ctx, customLabelRaw, adjustedWidth); + + // Draw truncated label ctx.fillStyle = rowTextColor; ctx.font = "14px Arial"; - const customLabel = this.node.properties.labels[i] || `INT ${i + 1}`; - const customLabelMetrics = ctx.measureText(customLabel); - const customLabelX = staticLabelX + staticLabelMetrics.width + 10; // space between iN and INT N - ctx.fillText(customLabel, customLabelX, staticLabelY); + ctx.fillText(displayLabel, labelAreaStart, staticLabelY); + + // Save label rect + const displayedLabelWidth = ctx.measureText(displayLabel).width; this.labelRects[i] = { - x: customLabelX, + x: labelAreaStart, y, - w: customLabelMetrics.width + 8, + w: displayedLabelWidth + 8, h: rowHeight }; - - - // Value (middle) + + // 4) Numeric value const val = this.node.properties.values[i] ?? 0; const valStr = val.toString(); ctx.textAlign = "right"; + ctx.font = "14px Arial"; const valMetrics = ctx.measureText(valStr); - const valX = margin + rowBgWidth - 90; // space for increment/decrement buttons and pencil - const valY = y + rowHeight / 2; ctx.fillText(valStr, valX, valY); this.valueRects[i] = { x: valX - valMetrics.width - 4, @@ -213,39 +292,33 @@ class MultiIntNode { h: rowHeight }; - // Pencil icon + // 5) Pencil icon ctx.textAlign = "left"; ctx.fillStyle = rowTextColor; ctx.font = "16px Arial"; const pencilIcon = "✎"; const pencilMetrics = ctx.measureText(pencilIcon); const pencilX = valX + 6; - const pencilY = y + rowHeight / 2; - ctx.fillText(pencilIcon, pencilX, pencilY); + ctx.fillText(pencilIcon, pencilX, valY); this.pencilRects[i] = { x: pencilX, y, w: pencilMetrics.width + 4, h: rowHeight }; - - - const incDecColor = this.node.properties.buttonColor || "#4b3e72"; // Default color - const textColor = getTextColorForBackground(incDecColor); // Get text color based on the background - - - // Create a fixed-size rounded box for the decrement (-) button - const decrementButtonBoxX = valX + 36; // Space for the box (positioned to the right of the value) + + // 6) Increment/decrement buttons + const incDecColor = this.node.properties.buttonColor || "#4b3e72"; + const incDecTextColor = getTextColorForBackground(incDecColor); + const decrementButtonBoxX = valX + 36; const decrementButtonBoxY = y + (rowHeight / 4) - 2; - const buttonBoxWidth = 20; // Fixed width for the button box - const buttonBoxHeight = 20; // Fixed height for the button box - - // Create a fixed-size rounded box for the increment (+) button - const incrementButtonBoxX = decrementButtonBoxX + 24; // Space for the box to the right of the + button - const incrementButtonBoxY = decrementButtonBoxY; // Same Y position as the + button box - - // Set the background color for the decrement button box - ctx.fillStyle = incDecColor; // Purple background + const buttonBoxWidth = 20; + const buttonBoxHeight = 20; + const incrementButtonBoxX = decrementButtonBoxX + 24; + const incrementButtonBoxY = decrementButtonBoxY; + + // Decrement button + ctx.fillStyle = incDecColor; ctx.beginPath(); ctx.moveTo(decrementButtonBoxX + 5, decrementButtonBoxY); // Top-left corner ctx.arcTo(decrementButtonBoxX + buttonBoxWidth, decrementButtonBoxY, decrementButtonBoxX + buttonBoxWidth, decrementButtonBoxY + buttonBoxHeight, 5); // Top-right corner @@ -254,15 +327,18 @@ class MultiIntNode { ctx.arcTo(decrementButtonBoxX, decrementButtonBoxY, decrementButtonBoxX + buttonBoxWidth, decrementButtonBoxY, 5); // Top-left corner ctx.closePath(); ctx.fill(); - - // Draw Decrement Button (-) - const decrementButton = "-"; - ctx.font = "12px Arial"; // Smaller font size for the button text - ctx.fillStyle = textColor; // White color for the text inside the button - ctx.fillText(decrementButton, decrementButtonBoxX + 8, decrementButtonBoxY + buttonBoxHeight / 2); // Draw - button in the middle - - // Set the background color for the increment button box - ctx.fillStyle = incDecColor; // Purple background + ctx.font = "12px Arial"; + ctx.fillStyle = incDecTextColor; + ctx.fillText("-", decrementButtonBoxX + 8, decrementButtonBoxY + buttonBoxHeight / 2); + this.decrementRects[i] = { + x: decrementButtonBoxX, + y: decrementButtonBoxY, + w: buttonBoxWidth, + h: buttonBoxHeight + }; + + // Increment button + ctx.fillStyle = incDecColor; ctx.beginPath(); ctx.moveTo(incrementButtonBoxX + 5, incrementButtonBoxY); // Top-left corner ctx.arcTo(incrementButtonBoxX + buttonBoxWidth, incrementButtonBoxY, incrementButtonBoxX + buttonBoxWidth, incrementButtonBoxY + buttonBoxHeight, 5); // Top-right corner @@ -271,34 +347,20 @@ class MultiIntNode { ctx.arcTo(incrementButtonBoxX, incrementButtonBoxY, incrementButtonBoxX + buttonBoxWidth, incrementButtonBoxY, 5); // Top-left corner ctx.closePath(); ctx.fill(); - - // Draw Increment Button (+) - const incrementButton = "+"; - ctx.font = "12px Arial"; // Smaller font size for the button text - ctx.fillStyle = textColor; // White color for the text inside the button - ctx.fillText(incrementButton, incrementButtonBoxX + 6, incrementButtonBoxY + buttonBoxHeight / 2); // Draw + button in the middle - - - - // Save button areas for interaction detection (with separate button box positions) + ctx.font = "12px Arial"; + ctx.fillStyle = incDecTextColor; + ctx.fillText("+", incrementButtonBoxX + 6, incrementButtonBoxY + buttonBoxHeight / 2); this.incrementRects[i] = { x: incrementButtonBoxX, y: incrementButtonBoxY, w: buttonBoxWidth, h: buttonBoxHeight }; - - this.decrementRects[i] = { - x: decrementButtonBoxX, - y: decrementButtonBoxY, - w: buttonBoxWidth, - h: buttonBoxHeight - }; - - y += rowHeight + rowSpacing; - } - // Gear icon at bottom-right + y += rowHeight + rowSpacing; + } + + // 7) Gear icon at bottom-right const gearIcon = "⚙️"; ctx.font = "18px Arial"; ctx.textAlign = "center"; @@ -309,7 +371,6 @@ class MultiIntNode { const gearX = this.node.size[0] - margin - (gearWidth / 2); const gearY = this.node.size[1] - margin - (gearHeight / 2); ctx.fillText(gearIcon, gearX, gearY); - this.gearRect = { x: gearX - gearWidth / 2, y: gearY - gearHeight / 2, @@ -318,6 +379,7 @@ class MultiIntNode { }; } + startValueDragToggle(index, initialEvent) { // If already dragging, then stop dragging (commit the value) if (this.dragActive) { @@ -532,13 +594,18 @@ class MultiIntNode { }, 10); const commit = () => { - const val = input.value.trim(); + let val = input.value.trim(); + // Enforce a maximum length of 120 characters + if (val.length > 120) { + val = val.substring(0, 120); + } if (val.length > 0) { - this.node.properties.labels[index] = val; - this.node.setDirtyCanvas(true, true); + this.node.properties.labels[index] = val; + this.node.setDirtyCanvas(true, true); } remove(); - }; + }; + const remove = () => { document.removeEventListener("click", outsideClick, true); @@ -863,7 +930,7 @@ class MultiIntNode { const input = document.createElement("input"); input.type = "text"; input.value = this.node.properties.values[index].toString(); - + input.style.position = "fixed"; const offsetX = -(rect.w * 0.5); const offsetY = -(rect.h * 0.5); @@ -873,7 +940,7 @@ class MultiIntNode { input.style.top = (screenY + offsetY + FixedOffsetY) + "px"; input.style.width = Math.max(rect.w, 60) + "px"; input.style.height = Math.max(rect.h, 24) + "px"; - + input.style.fontSize = "14px"; input.style.zIndex = "10000"; input.style.background = "#181818"; @@ -882,53 +949,68 @@ class MultiIntNode { input.style.borderRadius = "6px"; input.style.outline = "none"; input.style.padding = "0 6px"; - + document.body.appendChild(input); - + setTimeout(() => { - input.focus(); - input.select(); + input.focus(); + input.select(); }, 10); - + + // Updated commit: evaluate the input as a math expression if possible. const commit = () => { - const val = parseInt(input.value, 10); - if (!isNaN(val)) { - this.node.properties.values[index] = val; - if (this.node.widgets[index]) { - this.node.widgets[index].value = val; - } - this.node.setDirtyCanvas(true, true); + let inputVal = input.value.trim(); + if (inputVal.length > 0) { + let result; + try { + // Allow only digits, spaces, decimal points, and math operators (+ - * / ( ) ) + if (/^[0-9+\-*/().\s]+$/.test(inputVal)) { + result = eval(inputVal); + } else { + result = parseInt(inputVal, 10); + } + } catch (ex) { + result = parseInt(inputVal, 10); } - remove(); + if (!isNaN(result)) { + this.node.properties.values[index] = result; + if (this.node.widgets[index]) { + this.node.widgets[index].value = result; + } + this.node.setDirtyCanvas(true, true); + } + } + remove(); }; - + const remove = () => { - document.removeEventListener("click", outsideClick, true); - if (input.parentNode) { - input.parentNode.removeChild(input); - } + document.removeEventListener("click", outsideClick, true); + if (input.parentNode) { + input.parentNode.removeChild(input); + } }; - + const outsideClick = (evt) => { - if (evt.target !== input) { - commit(); - } + if (evt.target !== input) { + commit(); + } }; - + setTimeout(() => { - document.addEventListener("click", outsideClick, true); + document.addEventListener("click", outsideClick, true); }, 100); - + input.addEventListener("keydown", (evt) => { - if (evt.key === "Enter") { - evt.preventDefault(); - commit(); - } else if (evt.key === "Escape") { - evt.preventDefault(); - remove(); - } + if (evt.key === "Enter") { + evt.preventDefault(); + commit(); + } else if (evt.key === "Escape") { + evt.preventDefault(); + remove(); + } }); - } + } + onValueChanged(index, val) { this.node.properties.values[index] = parseInt(val, 10) || 0;