1212 lines
32 KiB
JavaScript
1212 lines
32 KiB
JavaScript
import { app } from "../../../scripts/app.js";
|
|
import { Shapes, lightenColor, hexDataToImage } from "./utils.js";
|
|
import { api } from "../../../scripts/api.js";
|
|
|
|
export class Widget {
|
|
constructor(x, y, width = 50, height = 50, onClick = null) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.color = "crimson";
|
|
this.radius = width / 2;
|
|
this.outline = true;
|
|
this.outlineColor = "#434343";
|
|
this.outlineWidth = 0.5;
|
|
this.allowVisualClick = true;
|
|
this.onClick = onClick;
|
|
this._shape = Shapes.SQUARE;
|
|
this.mousePos = [0, 0];
|
|
}
|
|
|
|
draw(ctx) {
|
|
// Draw rectangle
|
|
if (this.shape === Shapes.SQUARE) {
|
|
ctx.fillStyle = this.color;
|
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
|
|
// Draw outline if enabled
|
|
if (this.outline) {
|
|
ctx.strokeStyle = this.outlineColor;
|
|
ctx.lineWidth = this.outlineWidth;
|
|
ctx.strokeRect(this.x, this.y, this.width, this.height);
|
|
}
|
|
}
|
|
|
|
// Draw circle
|
|
if (this.shape === Shapes.CIRCLE) {
|
|
ctx.beginPath();
|
|
ctx.arc(
|
|
this.x + this.radius,
|
|
this.y + this.radius,
|
|
this.radius,
|
|
0,
|
|
Math.PI * 2
|
|
);
|
|
ctx.fillStyle = this.color;
|
|
ctx.fill();
|
|
|
|
// Draw outline if enabled
|
|
if (this.outline) {
|
|
ctx.strokeStyle = this.outlineColor;
|
|
ctx.lineWidth = this.outlineWidth;
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
// Draw rounded rectangle
|
|
if (this.shape === Shapes.ROUND) {
|
|
const radius = Math.min(this.width, this.height) / 5; // Adjust rounding level
|
|
ctx.beginPath();
|
|
ctx.moveTo(this.x + radius, this.y);
|
|
ctx.lineTo(this.x + this.width - radius, this.y);
|
|
ctx.arcTo(
|
|
this.x + this.width,
|
|
this.y,
|
|
this.x + this.width,
|
|
this.y + radius,
|
|
radius
|
|
);
|
|
ctx.lineTo(this.x + this.width, this.y + this.height - radius);
|
|
ctx.arcTo(
|
|
this.x + this.width,
|
|
this.y + this.height,
|
|
this.x + this.width - radius,
|
|
this.y + this.height,
|
|
radius
|
|
);
|
|
ctx.lineTo(this.x + radius, this.y + this.height);
|
|
ctx.arcTo(
|
|
this.x,
|
|
this.y + this.height,
|
|
this.x,
|
|
this.y + this.height - radius,
|
|
radius
|
|
);
|
|
ctx.lineTo(this.x, this.y + radius);
|
|
ctx.arcTo(this.x, this.y, this.x + radius, this.y, radius);
|
|
ctx.closePath();
|
|
|
|
ctx.fillStyle = this.color;
|
|
ctx.fill();
|
|
|
|
// Draw outline if enabled
|
|
if (this.outline) {
|
|
ctx.strokeStyle = this.outlineColor;
|
|
ctx.lineWidth = this.outlineWidth;
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
// isClicked(x, y) {
|
|
// if (this.shape === Shapes.SQUARE) {
|
|
// return (
|
|
// x >= this.x &&
|
|
// x <= this.x + this.width &&
|
|
// y >= this.y &&
|
|
// y <= this.y + this.height
|
|
// );
|
|
// } else if (this.shape === Shapes.CIRCLE) {
|
|
// const distance = Math.sqrt(
|
|
// (x - (this.x + this.radius)) ** 2 + (y - (this.y + this.radius)) ** 2
|
|
// );
|
|
// return distance <= this.radius;
|
|
// }
|
|
// return false;
|
|
// }
|
|
|
|
isClicked(x, y) {
|
|
if ( this.shape === Shapes.SQUARE || this.shape === Shapes.ROUND ) {
|
|
return (
|
|
x >= this.x &&
|
|
x <= this.x + this.width &&
|
|
y >= this.y &&
|
|
y <= this.y + this.height
|
|
);
|
|
} else if (this.shape === Shapes.CIRCLE) {
|
|
const distance = Math.sqrt(
|
|
(x - (this.x + this.radius)) ** 2 + (y - (this.y + this.radius)) ** 2
|
|
);
|
|
return distance <= this.radius;}
|
|
// } else if (this.shape === Shapes.ROUND) {
|
|
// const radius = Math.min(this.width, this.height) / 5;
|
|
|
|
// // Check if the point is inside the main rectangle (excluding rounded corners)
|
|
// if (
|
|
// x >= this.x + radius &&
|
|
// x <= this.x + this.width - radius &&
|
|
// y >= this.y &&
|
|
// y <= this.y + this.height
|
|
// ) {
|
|
// return true;
|
|
// }
|
|
// if (
|
|
// x >= this.x &&
|
|
// x <= this.x + this.width &&
|
|
// y >= this.y + radius &&
|
|
// y <= this.y + this.height - radius
|
|
// ) {
|
|
// return true;
|
|
// }
|
|
|
|
// // Check if the point is inside the rounded corners
|
|
// const cornerCenters = [
|
|
// { cx: this.x + radius, cy: this.y + radius }, // Top-left
|
|
// { cx: this.x + this.width - radius, cy: this.y + radius }, // Top-right
|
|
// { cx: this.x + radius, cy: this.y + this.height - radius }, // Bottom-left
|
|
// { cx: this.x + this.width - radius, cy: this.y + this.height - radius }, // Bottom-right
|
|
// ];
|
|
|
|
// return cornerCenters.some(({ cx, cy }) => {
|
|
// return (x - cx) ** 2 + (y - cy) ** 2 <= radius ** 2;
|
|
// });
|
|
// }
|
|
|
|
return false;
|
|
}
|
|
|
|
visualClick() {
|
|
const originalColor = this.color;
|
|
const originalPosX = this.x;
|
|
const originalPosY = this.y;
|
|
setTimeout(() => {
|
|
this.color = originalColor;
|
|
this.x = originalPosX;
|
|
this.y = originalPosY;
|
|
}, 100);
|
|
this.color = lightenColor(this.color, 20);
|
|
this.x = originalPosX + 0.5;
|
|
this.y = originalPosY + 0.5;
|
|
}
|
|
|
|
async handleClick(x, y) {
|
|
if (this.isClicked(x, y)) {
|
|
if (this.allowVisualClick) this.visualClick();
|
|
if (this.onClick) this.onClick();
|
|
} else {
|
|
//console.log("clicked"); // Default behavior
|
|
}
|
|
}
|
|
|
|
updateMousePos(pos) {}
|
|
|
|
isMouseDown() {
|
|
return app.canvas.pointer.isDown;
|
|
}
|
|
|
|
set shape(value) {
|
|
this._shape = value;
|
|
if (value === Shapes.CIRCLE) this.height = this.width;
|
|
}
|
|
|
|
get shape() {
|
|
return this._shape;
|
|
}
|
|
}
|
|
|
|
export class Button extends Widget {
|
|
constructor(x, y, text, width = 40, height = 40, onClick = null) {
|
|
super(x, y, width, height, text, onClick);
|
|
this.outlineColor = "#DDD";
|
|
this.outlineWidth = 1;
|
|
this.text = text;
|
|
this.textColor = LiteGraph.NODE_TEXT_COLOR || "white";
|
|
this.font = "16px Arial Bold";
|
|
}
|
|
|
|
draw(ctx) {
|
|
super.draw(ctx); // Call the draw method of the Widget class
|
|
|
|
// Draw text
|
|
if (this.text) {
|
|
ctx.fillStyle = this.textColor;
|
|
ctx.font = this.font;
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "middle"; //"bottom";
|
|
ctx.fillText(
|
|
this.text,
|
|
this.x + this.width / 2,
|
|
this.y + this.height / 2
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class Checkbox extends Widget {
|
|
constructor(x, y, width = 15, height = 15, isChecked = true, onClick = null) {
|
|
super(x, y, width, height, onClick);
|
|
this.color = "#80a1c0";
|
|
this.checkedColor = LiteGraph.WIDGET_BGCOLOR;
|
|
this.isChecked = isChecked;
|
|
this.checkMarkShrink = width / 2.5;
|
|
}
|
|
|
|
draw(ctx) {
|
|
// Draw rectangle
|
|
ctx.fillStyle = this.color;
|
|
ctx.beginPath();
|
|
ctx.roundRect(this.x, this.y, this.width, this.height, 5);
|
|
ctx.fill();
|
|
|
|
// Draw box checkMark if checked
|
|
if (this.isChecked) {
|
|
ctx.fillStyle = this.checkedColor;
|
|
ctx.beginPath();
|
|
ctx.roundRect(
|
|
this.x + this.checkMarkShrink / 2,
|
|
this.y + this.checkMarkShrink / 2,
|
|
this.width - this.checkMarkShrink,
|
|
this.height - this.checkMarkShrink,
|
|
2.5
|
|
);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
}
|
|
|
|
export class Label {
|
|
constructor(x, y, text) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.text = text;
|
|
this.textColor = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
|
|
this.font = "14px Arial";
|
|
this.textAlign = "left";
|
|
this.textBaseline = "middle";
|
|
this.xOffset = 0;
|
|
this.yOffset = 7;
|
|
}
|
|
|
|
draw(ctx) {
|
|
// Draw text
|
|
if (this.text) {
|
|
ctx.fillStyle = this.textColor;
|
|
ctx.font = this.font;
|
|
ctx.textAlign = this.textAlign;
|
|
ctx.textBaseline = this.textBaseline;
|
|
ctx.fillText(this.text, this.x + this.xOffset, this.y + this.yOffset);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export class Slider {
|
|
constructor(
|
|
x,
|
|
y,
|
|
width = 200,
|
|
height = 10,
|
|
min = 5,
|
|
max = 100,
|
|
value = 20,
|
|
trackColor = LiteGraph.WIDGET_BGCOLOR,
|
|
handleColor = "#80a1c0",
|
|
onChange = null
|
|
) {
|
|
Object.assign(this, {
|
|
x, y, width, height, min, max, value, trackColor, handleColor, onChange,
|
|
handleWidth: 20,
|
|
handleHeight: 20,
|
|
});
|
|
}
|
|
|
|
draw(ctx) {
|
|
const trackY = this.y + (this.handleHeight - this.height) / 2;
|
|
|
|
// Draw the track
|
|
ctx.fillStyle = this.trackColor;
|
|
ctx.fillRect(this.x, trackY, this.width, this.height);
|
|
|
|
// Calculate handle position
|
|
const handleX = this.x + ((this.value - this.min) / (this.max - this.min)) * (this.width - this.handleWidth);
|
|
|
|
// Draw the handle
|
|
ctx.fillStyle = this.handleColor;
|
|
ctx.beginPath();
|
|
ctx.roundRect(handleX, this.y, this.handleWidth, this.handleHeight, 5);
|
|
ctx.fill();
|
|
|
|
// Draw value text
|
|
ctx.fillStyle = "white";
|
|
ctx.font = "12px Arial Bold";
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "middle";
|
|
ctx.fillText(`Brush Size: ${this.value.toFixed(2)}`, this.x + this.width / 2, this.y + 30);
|
|
}
|
|
|
|
isHandleClicked([mouseX, mouseY]) {
|
|
return (
|
|
mouseX >= this.x - this.handleWidth &&
|
|
mouseX <= this.x + this.width + this.handleWidth &&
|
|
mouseY >= this.y &&
|
|
mouseY <= this.y + this.handleHeight
|
|
);
|
|
}
|
|
|
|
isMouseDown() {
|
|
return app.canvas.pointer.isDown;
|
|
}
|
|
|
|
handleMouseMove([mouseX]) {
|
|
if (!this.isMouseDown() || !this.isHandleClicked([mouseX, this.y])) return;
|
|
|
|
const scale = (this.max - this.min) / this.width;
|
|
let newValue = this.min + (mouseX - this.x) * scale;
|
|
newValue = Math.max(this.min, Math.min(this.max, newValue));
|
|
|
|
if (newValue !== this.value) {
|
|
this.value = newValue;
|
|
this.onChange?.(this.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
export class DropdownMenu {
|
|
constructor(x, y, width, height, options) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.options = options;
|
|
this.isOpen = false;
|
|
this.selectedOption = null;
|
|
}
|
|
|
|
draw(ctx) {
|
|
// Draw the main dropdown button
|
|
ctx.fillStyle = "#4CAF50";
|
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
ctx.fillStyle = "#000";
|
|
ctx.font = "16px Arial";
|
|
ctx.fillText(
|
|
this.selectedOption || "Select an option",
|
|
this.x + 10,
|
|
this.y + 25
|
|
);
|
|
|
|
// Draw the dropdown arrow
|
|
ctx.beginPath();
|
|
ctx.moveTo(this.x + this.width - 20, this.y + 10);
|
|
ctx.lineTo(this.x + this.width - 10, this.y + 10);
|
|
ctx.lineTo(this.x + this.width - 15, this.y + 20);
|
|
ctx.closePath();
|
|
ctx.fillStyle = "#000";
|
|
ctx.fill();
|
|
|
|
// Draw the dropdown options if open
|
|
if (this.isOpen) {
|
|
for (let i = 0; i < this.options.length; i++) {
|
|
const optionY = this.y + this.height + i * 30;
|
|
ctx.fillStyle = "#4CAF50";
|
|
ctx.fillRect(this.x, optionY, this.width, 30);
|
|
ctx.fillStyle = "#000";
|
|
ctx.fillText(this.options[i], this.x + 10, optionY + 20);
|
|
}
|
|
}
|
|
}
|
|
|
|
handleClick(mouseX, mouseY) {
|
|
// Check if the main button is clicked
|
|
if (
|
|
mouseX >= this.x &&
|
|
mouseX <= this.x + this.width &&
|
|
mouseY >= this.y &&
|
|
mouseY <= this.y + this.height
|
|
) {
|
|
this.isOpen = !this.isOpen;
|
|
} else if (this.isOpen) {
|
|
// Check if an option is clicked
|
|
for (let i = 0; i < this.options.length; i++) {
|
|
const optionY = this.y + this.height + i * 30;
|
|
if (
|
|
mouseX >= this.x &&
|
|
mouseX <= this.x + this.width &&
|
|
mouseY >= optionY &&
|
|
mouseY <= optionY + 30
|
|
) {
|
|
this.selectedOption = this.options[i];
|
|
this.isOpen = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export class ColorPicker {
|
|
constructor(x, y, width, height) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.heightDisplay = 20;
|
|
this.selectedColor = null;
|
|
this.ctx = null;
|
|
this.isVisible = false;
|
|
this.allowDisplayColor = false;
|
|
this.isSelecting = false; }
|
|
|
|
open() {
|
|
this.isVisible = true;
|
|
|
|
setTimeout(() => {
|
|
if (this.isVisible) this.isSelecting = true;
|
|
}, 200);
|
|
}
|
|
|
|
close() {
|
|
if(this.isVisible){
|
|
this.isVisible = false;
|
|
}
|
|
this.isSelecting = false;
|
|
}
|
|
|
|
toggleShow() {
|
|
this.isVisible = !this.isVisible;
|
|
}
|
|
|
|
draw(ctx) {
|
|
if (!this.isVisible) return;
|
|
// Ensure the context is set
|
|
if (this.ctx === null) this.ctx = ctx;
|
|
|
|
// Create a horizontal gradient for hue
|
|
const hueGradient = this.ctx.createLinearGradient(
|
|
this.x,
|
|
this.y,
|
|
this.x + this.width,
|
|
this.y
|
|
);
|
|
hueGradient.addColorStop(0, "red");
|
|
hueGradient.addColorStop(0.17, "orange");
|
|
hueGradient.addColorStop(0.34, "yellow");
|
|
hueGradient.addColorStop(0.51, "green");
|
|
hueGradient.addColorStop(0.68, "blue");
|
|
hueGradient.addColorStop(0.85, "indigo");
|
|
hueGradient.addColorStop(1, "violet");
|
|
|
|
// Fill the canvas with the hue gradient
|
|
this.ctx.fillStyle = hueGradient;
|
|
this.ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
|
|
// Create a vertical gradient for brightness
|
|
const brightnessGradient = this.ctx.createLinearGradient(
|
|
this.x,
|
|
this.y,
|
|
this.x,
|
|
this.y + this.height
|
|
);
|
|
brightnessGradient.addColorStop(0, "rgba(255, 255, 255, 1)"); // White
|
|
brightnessGradient.addColorStop(0.5, "rgba(255, 255, 255, 0)"); // Transparent
|
|
brightnessGradient.addColorStop(0.5, "rgba(0, 0, 0, 0)"); // Transparent
|
|
brightnessGradient.addColorStop(1, "rgba(0, 0, 0, 1)"); // Black
|
|
|
|
// Use global composite operation to blend the gradients
|
|
this.ctx.globalCompositeOperation = "multiply";
|
|
this.ctx.fillStyle = brightnessGradient;
|
|
this.ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
|
|
// Reset the global composite operation to default
|
|
this.ctx.globalCompositeOperation = "source-over";
|
|
|
|
if(this.allowDisplayColor) this.displaySelectedColor();
|
|
}
|
|
|
|
|
|
setColorUnderCurser(event) {
|
|
if (!this.ctx) return;
|
|
const rect = this.ctx.canvas.getBoundingClientRect();
|
|
const scaleX = this.ctx.canvas.width / rect.width;
|
|
const scaleY = this.ctx.canvas.height / rect.height;
|
|
|
|
const canvasX = (event.clientX - rect.left) * scaleX;
|
|
const canvasY = (event.clientY - rect.top) * scaleY;
|
|
|
|
const pixel = this.ctx.getImageData(canvasX, canvasY, 1, 1).data;
|
|
this.selectedColor = `rgb(${pixel[0]}, ${pixel[1]}, ${pixel[2]})`;
|
|
}
|
|
|
|
|
|
displaySelectedColor() {
|
|
// // Clear a small area below the canvas to display the selected color
|
|
// this.ctx.clearRect(0, this.canvas.height - 30, this.canvas.width, 30); // Adjusted coordinates
|
|
this.ctx.fillStyle = this.selectedColor;
|
|
this.ctx.fillRect(
|
|
this.x,
|
|
this.y + this.height,
|
|
this.width,
|
|
this.heightDisplay
|
|
); // Adjusted coordinates
|
|
// // Add text to show the RGB value
|
|
// this.ctx.fillStyle = "#000";
|
|
// this.ctx.font = "16px Arial";
|
|
// this.ctx.fillText(
|
|
// `Selected Color: ${this.selectedColor}`,
|
|
// 10,
|
|
// this.canvas.height - 10
|
|
// ); // Adjusted coordinates
|
|
}
|
|
}
|
|
|
|
export class PaintArea extends Widget {
|
|
constructor(x, y, width, height) {
|
|
super(x, y, width, height);
|
|
this.color = "white";
|
|
this.clearColor = null;
|
|
this.ctx = null;
|
|
this.paintCanvas = null;
|
|
this.paintCtx = null;
|
|
this.lastImage = null;
|
|
this.loadedImage = null;
|
|
this.tempImage = null;
|
|
this.isPainting = false;
|
|
this.blockPainting = false;
|
|
this.brushSize = 10;
|
|
this.brushColor = "#000000";
|
|
|
|
this.yOffset = 30;
|
|
|
|
this.init();
|
|
this.getDrawingFromAPI();
|
|
}
|
|
|
|
init() {
|
|
// init paint canvas
|
|
this.paintCanvas = document.createElement("canvas");
|
|
this.paintCanvas.width = this.width; // Match the dimensions of the original canvas
|
|
this.paintCanvas.height = this.height;
|
|
this.paintCtx = this.paintCanvas.getContext("2d");
|
|
|
|
// Fill the new canvas with a white background
|
|
this.paintCtx.fillStyle = "white";
|
|
this.paintCtx.fillRect(
|
|
this.x,
|
|
this.y,
|
|
this.paintCanvas.width,
|
|
this.paintCanvas.height
|
|
);
|
|
}
|
|
|
|
clearWithColor(color) {
|
|
this.clearColor = color;
|
|
}
|
|
|
|
updateMousePos(pos) {
|
|
const x = pos[0];
|
|
const y = pos[1] - this.yOffset;
|
|
this.mousePos = [x, y];
|
|
this.isMouseDown();
|
|
}
|
|
|
|
togglePainting() {
|
|
this.isPainting = !this.isPainting;
|
|
}
|
|
|
|
isDragging() {
|
|
return app.canvas.pointer.isDown;
|
|
}
|
|
|
|
isMouseDown() {
|
|
this.isPainting = app.canvas.pointer.isDown;
|
|
}
|
|
|
|
saveTempImage() {
|
|
console.log("image saved");
|
|
this.tempImage = this.paintCanvas.toDataURL();
|
|
}
|
|
|
|
loadTempImage() {
|
|
let img = new Image();
|
|
img.src = this.tempImage;
|
|
img.onload = () => {
|
|
this.loadedImage = img;
|
|
};
|
|
}
|
|
|
|
// send image to be saved by python
|
|
async sendDrawingToAPI() {
|
|
const filename = "iToolsPaintedImage";
|
|
if (!this.paintCanvas) {
|
|
console.error("Canvas is not initialized.");
|
|
return;
|
|
}
|
|
|
|
// Convert the canvas content to a data URL
|
|
const dataURL = this.paintCanvas.toDataURL("image/png");
|
|
|
|
// Convert the data URL to a Blob
|
|
const blob = await fetch(dataURL).then((res) => res.blob());
|
|
|
|
// Create a FormData object to send the file
|
|
const formData = new FormData();
|
|
formData.append("file", blob, `${filename}.png`);
|
|
|
|
// Send the file to the API endpoint
|
|
try {
|
|
const response = await api.fetchApi("/itools/request_save_paint", {
|
|
method: "POST",
|
|
body: formData,
|
|
});
|
|
//console.log(response.json());
|
|
console.log("Drawing sent successfully.");
|
|
} catch (error) {
|
|
console.error("Error sending the drawing:", error);
|
|
}
|
|
}
|
|
|
|
// request last image saved by python
|
|
async getDrawingFromAPI() {
|
|
const filename = "iToolsPaintedImage.png";
|
|
|
|
const formData = new FormData();
|
|
formData.append("filename", filename);
|
|
try {
|
|
const response = await api.fetchApi("/itools/request_the_paint_file", {
|
|
method: "POST",
|
|
body: formData,
|
|
});
|
|
|
|
console.log("response", response);
|
|
|
|
// Ensure the response is properly parsed as JSON
|
|
const result = await response.json();
|
|
|
|
// Check if the response is successful
|
|
if (result.status === "success") {
|
|
console.log("Drawing received successfully.", result);
|
|
this.loadedImage = hexDataToImage(result["data"]);
|
|
} else {
|
|
console.error("Error:", result.message);
|
|
}
|
|
} catch (error) {
|
|
console.error("iTools No Temp Drawing Found", error);
|
|
}
|
|
}
|
|
|
|
draw(ctx) {
|
|
if (this.ctx === null) this.ctx = ctx;
|
|
//if (this.blockPainting)
|
|
// if (this.blockPainting) {
|
|
// if (!this.isImageSaved) {
|
|
// this.savedImage = this.paintCtx.getImageData(
|
|
// 0,
|
|
// 0,
|
|
// this.paintCanvas.width,
|
|
// this.paintCanvas.height
|
|
// );
|
|
// this.isImageSaved = true; // Ensure it runs only once
|
|
// this.paintCtx.fillStyle = "rgba(128, 128, 128, 0.5)";
|
|
// this.paintCtx.fillRect(
|
|
// 0,
|
|
// 0,
|
|
// this.paintCanvas.width,
|
|
// this.paintCanvas.height
|
|
// );
|
|
// ctx.drawImage(this.paintCanvas, this.x, this.y);
|
|
// }
|
|
// } else {
|
|
// if (this.isImageSaved) {
|
|
// this.paintCtx.putImageData(this.savedImage, 0, 0);
|
|
// this.isImageSaved = false; // Reset flag to allow saving again
|
|
// }
|
|
// }
|
|
// use loaded image once
|
|
if (this.loadedImage !== null) {
|
|
console.log("loaded image used");
|
|
this.paintCtx.drawImage(this.loadedImage, 0, 0);
|
|
//ctx.drawImage(this.paintCanvas, 0, 0);
|
|
this.loadedImage = null;
|
|
}
|
|
|
|
// use clear color once
|
|
if (this.clearColor !== null) {
|
|
this.paintCtx.fillStyle = this.clearColor;
|
|
this.paintCtx.fillRect(
|
|
0,
|
|
0,
|
|
this.paintCanvas.width,
|
|
this.paintCanvas.height
|
|
);
|
|
this.sendDrawingToAPI(); // save after clear
|
|
this.clearColor = null;
|
|
}
|
|
|
|
// draw phase
|
|
if (this.isPainting && this.isDragging() && !this.blockPainting) {
|
|
// Set the drawing properties
|
|
this.paintCtx.lineWidth = this.brushSize * 2;
|
|
this.paintCtx.lineCap = "round";
|
|
this.paintCtx.strokeStyle = this.brushColor;
|
|
|
|
// Draw on the new canvas
|
|
this.paintCtx.lineTo(this.mousePos[0], this.mousePos[1]);
|
|
this.paintCtx.stroke();
|
|
|
|
this.paintCtx.beginPath();
|
|
this.paintCtx.moveTo(this.mousePos[0], this.mousePos[1]);
|
|
|
|
// Store the last drawn image
|
|
this.lastImage = this.paintCanvas.toDataURL();
|
|
} else {
|
|
// in case mouse up miss
|
|
this.isPainting = false;
|
|
this.paintCtx.beginPath();
|
|
}
|
|
// keep drawing on screen
|
|
ctx.drawImage(this.paintCanvas, this.x, this.y);
|
|
}
|
|
}
|
|
|
|
export class Preview {
|
|
constructor() {
|
|
this.mousePos = [0, 0];
|
|
this.brushSize = 10;
|
|
this.color = "rgba(128, 128, 128, 0.2)"; // 50% transparent gray
|
|
this.dashColor = "black";
|
|
this.widthLimit = 512;
|
|
this.heightLimit = 592;
|
|
this.yOffset = 80;
|
|
this.isMouseIn = true;
|
|
this.allowInnerCircle = false;
|
|
this.init();
|
|
}
|
|
|
|
updateBrushSize(size) {
|
|
this.brushSize = size;
|
|
}
|
|
|
|
updateMousePos(pos) {
|
|
this.mousePos = pos;
|
|
}
|
|
|
|
init() {
|
|
//init preview canvas
|
|
this.previewCanvas = document.createElement("canvas");
|
|
this.previewCanvas.width = this.brushSize; //app.canvasContainer.width;
|
|
this.previewCanvas.height = this.brushSize; //app.canvasContainer.height;
|
|
this.previewCtx = this.previewCanvas.getContext("2d");
|
|
}
|
|
draw(ctx) {
|
|
if (this.mousePos[0] > this.widthLimit) return;
|
|
if (this.mousePos[1] > this.heightLimit) return;
|
|
if (this.mousePos[1] < this.yOffset) return;
|
|
if (!this.isMouseIn) return;
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(this.mousePos[0], this.mousePos[1], this.brushSize, 0, Math.PI * 2);
|
|
ctx.fillStyle = this.allowInnerCircle? this.color : "rgba(255, 255, 255, 0)";
|
|
ctx.fill();
|
|
|
|
// Draw dotted outline
|
|
ctx.setLineDash([3, 3]); // [dash length, gap length]
|
|
ctx.strokeStyle = this.dashColor; // Outline color
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
ctx.setLineDash([]); // Reset line dash to solid
|
|
}
|
|
}
|
|
|
|
// DEPRECATED
|
|
|
|
export class ColorPickerOld {
|
|
constructor(x, y, width, height) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.selectedColor = null;
|
|
this.ctx = null;
|
|
}
|
|
|
|
draw(ctx) {
|
|
if (this.ctx === null) this.ctx = ctx;
|
|
|
|
const gradient = ctx.createLinearGradient(
|
|
this.x,
|
|
this.y,
|
|
this.x + this.width,
|
|
this.y
|
|
);
|
|
|
|
// Create a horizontal gradient for hue
|
|
gradient.addColorStop(0, "red");
|
|
gradient.addColorStop(0.17, "orange");
|
|
gradient.addColorStop(0.34, "yellow");
|
|
gradient.addColorStop(0.51, "green");
|
|
gradient.addColorStop(0.68, "blue");
|
|
gradient.addColorStop(0.85, "indigo");
|
|
gradient.addColorStop(1, "violet");
|
|
|
|
// Fill the canvas with the gradient
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
|
|
// Add a vertical gradient for brightness
|
|
const brightnessGradient = ctx.createLinearGradient(
|
|
this.x,
|
|
this.y,
|
|
this.x,
|
|
this.y + this.height
|
|
);
|
|
brightnessGradient.addColorStop(0, "rgba(255, 255, 255, 1)"); // White
|
|
brightnessGradient.addColorStop(0.5, "rgba(255, 255, 255, 0)"); // Transparent
|
|
brightnessGradient.addColorStop(0.5, "rgba(0, 0, 0, 0)"); // Transparent
|
|
brightnessGradient.addColorStop(1, "rgba(0, 0, 0, 1)"); // Black
|
|
|
|
ctx.fillStyle = brightnessGradient;
|
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
|
|
// Display the selected color
|
|
ctx.fillStyle = this.selectedColor;
|
|
ctx.fillRect(this.x, this.y + this.height, this.width, 30); // Adjusted coordinates
|
|
|
|
// Add text to show the RGB value
|
|
ctx.fillStyle = "#000";
|
|
ctx.font = "16px Arial";
|
|
ctx.fillText(
|
|
`Selected Color: ${this.selectedColor}`,
|
|
this.x + 10,
|
|
this.height - 10
|
|
); // Adjusted coordinates
|
|
}
|
|
|
|
handleOnClick(event, pos, node) {
|
|
const x = pos[0];
|
|
const y = pos[1];
|
|
|
|
// Get the color at the clicked position
|
|
const pixel = this.ctx.getImageData(x, y, 1, 1).data;
|
|
this.selectedColor = `rgb(${pixel[0]}, ${pixel[1]}, ${pixel[2]})`;
|
|
}
|
|
|
|
displaySelectedColor() {
|
|
// Clear a small area below the canvas to display the selected color
|
|
//this.ctx.clearRect(this.x , this.height - 30, this.width, 30); // Adjusted coordinates
|
|
this.ctx.fillStyle = this.selectedColor;
|
|
this.ctx.fillRect(this.x, this.height, this.width, 30); // Adjusted coordinates
|
|
|
|
// Add text to show the RGB value
|
|
this.ctx.fillStyle = "#000";
|
|
this.ctx.font = "16px Arial";
|
|
this.ctx.fillText(
|
|
`Selected Color: ${this.selectedColor}`,
|
|
this.x + 10,
|
|
this.height - 10
|
|
); // Adjusted coordinates
|
|
}
|
|
}
|
|
|
|
export class CanvasObjectManager {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
this.objects = [];
|
|
}
|
|
|
|
addObject(obj) {
|
|
this.objects.push(obj);
|
|
}
|
|
|
|
drawAll() {
|
|
this.objects.forEach((obj) => obj.draw(this.ctx));
|
|
}
|
|
|
|
handleClick(x, y) {
|
|
this.objects.forEach((obj) => {
|
|
if (obj.isClicked && obj.isClicked(x, y)) {
|
|
obj.onClick(); // Call object's click handler
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export class WidgetGlobal {
|
|
constructor(
|
|
x,
|
|
y,
|
|
width = 50,
|
|
height = 50,
|
|
color = "crimson",
|
|
outline = false,
|
|
outlineColor = "dark-grey",
|
|
outlineWidth = 0.5,
|
|
onClick = null,
|
|
onMouseIn = null,
|
|
onMouseOut = null
|
|
) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.color = color;
|
|
this.outline = outline;
|
|
this.outlineColor = outlineColor;
|
|
this.outlineWidth = outlineWidth;
|
|
this.onClick = onClick;
|
|
this.onMouseIn = onMouseIn;
|
|
this.onMouseOut = onMouseOut;
|
|
|
|
this.mousePos = [0, 0];
|
|
}
|
|
|
|
updateNodePos(nodePos) {
|
|
this.nodePos = nodePos;
|
|
}
|
|
|
|
updateMousePos(nodePos) {
|
|
this.nodePos = nodePos;
|
|
const canvasX = app.canvas.canvas_mouse[0] || 0;
|
|
const canvasY = app.canvas.canvas_mouse[1] || 0;
|
|
this.mousePos[0] = nodePos[0] - canvasX;
|
|
this.mousePos[1] = nodePos[1] - canvasY;
|
|
}
|
|
|
|
draw(ctx) {
|
|
this.trackMouse(this.mousePos[0], this.mousePos[1]);
|
|
ctx.fillStyle = this.color;
|
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
ctx.fillText("Test", this.x + 50, this.y + 50);
|
|
}
|
|
|
|
trackMouse(x, y) {
|
|
if (
|
|
x >= this.x &&
|
|
x <= this.x + this.width &&
|
|
y >= this.y &&
|
|
y <= this.y + this.height
|
|
) {
|
|
console.log("mouse in"); //this.onMouseIn()
|
|
}
|
|
}
|
|
}
|
|
|
|
export class ButtonOLd {
|
|
constructor(
|
|
x,
|
|
y,
|
|
width = 20,
|
|
height = 20,
|
|
color = "red",
|
|
text = null,
|
|
outline = false,
|
|
outlineColor = "dark-grey",
|
|
outlineWidth = 0.5,
|
|
onClick = null
|
|
) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.color = color;
|
|
(this._shape = Shapes.SQUARE), (this.text = text);
|
|
this.radius = width / 2; // Radius for circle
|
|
this.outline = outline; // Outline property
|
|
this.outlineColor = outlineColor; // Outline color
|
|
this.outlineWidth = outlineWidth;
|
|
this.onClick = onClick; // Callback function for click event
|
|
|
|
this.textColor = "black";
|
|
this.font = "8px Arial";
|
|
|
|
if (this.shape == Shapes.CIRCLE) {
|
|
console.log("yes it is");
|
|
this.height = this.width;
|
|
}
|
|
}
|
|
|
|
draw(ctx) {
|
|
// Draw rectangle
|
|
if (this.shape === Shapes.SQUARE) {
|
|
ctx.fillStyle = this.color;
|
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
|
|
// Draw outline if enabled
|
|
if (this.outline) {
|
|
ctx.strokeStyle = this.outlineColor;
|
|
ctx.lineWidth = this.outlineWidth;
|
|
ctx.strokeRect(this.x, this.y, this.width, this.height);
|
|
}
|
|
}
|
|
|
|
// Draw circle
|
|
if (this.shape === Shapes.CIRCLE) {
|
|
ctx.beginPath();
|
|
ctx.arc(
|
|
this.x + this.radius,
|
|
this.y + this.radius,
|
|
this.radius,
|
|
0,
|
|
Math.PI * 2
|
|
);
|
|
ctx.fillStyle = this.color;
|
|
ctx.fill();
|
|
|
|
// Draw outline if enabled
|
|
if (this.outline) {
|
|
ctx.strokeStyle = this.outlineColor;
|
|
ctx.lineWidth = this.outlineWidth;
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
// Draw text
|
|
if (this.text) {
|
|
ctx.fillStyle = this.textColor;
|
|
ctx.font = this.font;
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "middle"; //"bottom";
|
|
ctx.fillText(
|
|
this.text,
|
|
this.x + this.width / 2,
|
|
this.y + this.height / 2
|
|
);
|
|
}
|
|
}
|
|
|
|
isClicked(x, y) {
|
|
if (this.shape === Shapes.SQUARE) {
|
|
return (
|
|
x >= this.x &&
|
|
x <= this.x + this.width &&
|
|
y >= this.y &&
|
|
y <= this.y + this.height
|
|
);
|
|
} else if (this.shape === Shapes.CIRCLE) {
|
|
const distance = Math.sqrt(
|
|
(x - (this.x + this.radius)) ** 2 + (y - (this.y + this.radius)) ** 2
|
|
);
|
|
return distance <= this.radius;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
handleClick(x, y) {
|
|
if (this.isClicked(x, y)) {
|
|
if (this.onClick) {
|
|
const originalColor = this.color;
|
|
const originalPosX = this.x;
|
|
const originalPosY = this.y;
|
|
setTimeout(() => {
|
|
this.color = originalColor;
|
|
this.x = originalPosX;
|
|
this.y = originalPosY;
|
|
}, 100);
|
|
this.color = lightenColor(this.color, 20);
|
|
this.x = originalPosX + 1;
|
|
this.y = originalPosY + 1;
|
|
this.onClick(); // Call the provided callback function
|
|
} else {
|
|
console.log(this.text, "clicked"); // Default behavior
|
|
}
|
|
}
|
|
}
|
|
|
|
set shape(value) {
|
|
this._shape = value;
|
|
if (value === Shapes.CIRCLE) this.height = this.width;
|
|
}
|
|
|
|
get shape() {
|
|
return this._shape;
|
|
}
|
|
}
|
|
|
|
// export class Slider {
|
|
// constructor(
|
|
// x,
|
|
// y,
|
|
// width = 200,
|
|
// height = 10,
|
|
// min = 5,
|
|
// max = 100,
|
|
// value = 20,
|
|
// trackColor = LiteGraph.WIDGET_BGCOLOR,
|
|
// handleColor = "#80a1c0",
|
|
// onChange = null
|
|
// ) {
|
|
// this.x = x;
|
|
// this.y = y;
|
|
// this.width = width;
|
|
// this.height = height;
|
|
// this.min = min;
|
|
// this.max = max;
|
|
// this.value = value;
|
|
// this.trackColor = trackColor;
|
|
// this.handleColor = handleColor;
|
|
// this.onChange = onChange;
|
|
|
|
// this.handleWidth = 20;
|
|
// this.handleHeight = 20;
|
|
// }
|
|
|
|
// draw(ctx) {
|
|
// // Draw the track
|
|
// ctx.fillStyle = this.trackColor;
|
|
// ctx.fillRect(
|
|
// this.x,
|
|
// this.y + (this.handleHeight - this.height) / 2,
|
|
// this.width,
|
|
// this.height
|
|
// );
|
|
|
|
// // Calculate the handle position
|
|
// const handleX =
|
|
// this.x +
|
|
// ((this.value - this.min) / (this.max - this.min)) *
|
|
// (this.width - this.handleWidth);
|
|
|
|
// // Draw the handle
|
|
// ctx.fillStyle = this.handleColor;
|
|
// ctx.beginPath();
|
|
// ctx.roundRect(handleX, this.y, this.handleWidth, this.handleHeight, 5);
|
|
// ctx.fill();
|
|
|
|
// //Draw value text
|
|
// if (this.value) {
|
|
// ctx.fillStyle = "white";
|
|
// ctx.font = "12px Arial Bold";
|
|
// ctx.textAlign = "center";
|
|
// ctx.textBaseline = "middle"; //"bottom";
|
|
// ctx.fillText(
|
|
// "Brush Size: " + this.value.toFixed(2),
|
|
// this.x + this.width / 2,
|
|
// this.y + 30
|
|
// );
|
|
// }
|
|
// }
|
|
|
|
// isHandleClicked(mousePos) {
|
|
// const x = mousePos[0];
|
|
// const y = mousePos[1];
|
|
// //console.log(mousePos);
|
|
// return (
|
|
// x >= this.x - 20 &&
|
|
// x <= this.x + this.width + 20 &&
|
|
// y >= this.y &&
|
|
// y <= this.y + this.handleHeight
|
|
// );
|
|
// }
|
|
|
|
// isMouseDown() {
|
|
// return app.canvas.pointer.isDown;
|
|
// }
|
|
|
|
// isDragging(mousePos){
|
|
// return this.isHandleClicked(mousePos)
|
|
// }
|
|
|
|
// handleMouseMove(mousePos) {
|
|
// if (!this.isMouseDown() || !this.isHandleClicked(mousePos)) return;
|
|
|
|
// const { x, width, min, max } = this;
|
|
// let newValue = ((mousePos[0] - x) / width) * (max - min) + min;
|
|
// newValue = Math.max(min, Math.min(max, newValue));
|
|
|
|
// if (newValue !== this.value) {
|
|
// this.value = newValue;
|
|
// this.onChange?.(this.value);
|
|
// }
|
|
// }
|
|
// }
|