sophia_controller/nodeeditor/canvastools.js

211 lines
6.0 KiB
JavaScript

export class CanvasTextInput {
constructor(offsetX, offsetY, width = 120, value = "", label = "", options = {}) {
this.x = 0;
this.y = 0;
this.offsetX = offsetX;
this.offsetY = offsetY;
this.width = width;
this.height = 24;
this.labelOffset = 16; // space above input for label
this.value = String(value);
//this.placeholder = placeholder;
this.focused = false;
this.cursorVisible = false;
this.cursorTimer = 0;
this.numericOnly = options.numericOnly || false;
this.min = options.min ?? -Infinity;
this.max = options.max ?? Infinity;
this.label = label || "";
}
draw(ctx) {
if (this.label) {
ctx.fillStyle = "#000";
ctx.font = "13px sans-serif";
ctx.fillText(this.label, this.x, this.y - this.labelOffset + 15);
}
ctx.fillStyle = "#fff";
ctx.strokeStyle = "#666";
ctx.fillRect(this.x, this.y, this.width, this.height);
ctx.strokeRect(this.x, this.y, this.width, this.height);
ctx.fillStyle = "#000";
ctx.font = "14px sans-serif";
const text = this.value || this.placeholder;
ctx.fillText(text, this.x + 5, this.y + 15);
if (this.focused && this.cursorVisible) {
const textWidth = ctx.measureText(this.value).width;
ctx.beginPath();
ctx.moveTo(this.x + 5 + textWidth, this.y + 4);
ctx.lineTo(this.x + 5 + textWidth, this.y + this.height - 4);
ctx.strokeStyle = "#000";
ctx.stroke();
}
}
contains(mx, my) {
return mx >= this.x && mx <= this.x + this.width &&
my >= this.y && my <= this.y + this.height;
}
handleClick(mx, my) {
console.log("HERE", mx, my);
this.focused = this.contains(mx, my);
return this.focused;
}
handleKey(e) {
if (!this.focused) return false;
if (e.key === "Backspace") {
this.value = this.value.slice(0, -1);
} else if (e.key === "Enter") {
this.clampToRange(); // 👈 clamp on Enter
this.focused = false;
} else if (e.key.length === 1) {
if (this.numericOnly) {
if (/[\d.\-]/.test(e.key)) {
this.value += e.key;
this.clampToRange(); // 👈 clamp on Enter
}
} else {
this.value += e.key;
this.clampToRange(); // 👈 clamp on Enter
}
}
return true;
}
update(dt) {
this.cursorTimer += dt;
if (this.cursorTimer > 500) {
this.cursorVisible = !this.cursorVisible;
this.cursorTimer = 0;
}
}
clampToRange() {
let val = parseFloat(this.value);
if (isNaN(val)) val = 0;
val = Math.min(this.max, Math.max(this.min, val));
this.value = String(val);
}
get numericValue() {
let val = parseFloat(this.value);
if (isNaN(val)) return 0;
return Math.min(this.max, Math.max(this.min, val));
}
}
export class CanvasDropdown {
constructor(offsetX, offsetY, width, items = [], selected = 0) {
this.x = 0;
this.y = 0
this.offsetX = offsetX;
this.offsetY = offsetY;
this.width = width;
this.height = 20;
this.items = items;
this.selectedIndex = 0;
this.open = false;
this.hoverIndex = -1;
}
draw(ctx) {
// Collapsed box
ctx.fillStyle = "#eee";
ctx.strokeStyle = "#666";
ctx.fillRect(this.x, this.y, this.width, this.height);
ctx.strokeRect(this.x, this.y, this.width, this.height);
ctx.fillStyle = "#000";
ctx.font = "14px sans-serif";
ctx.fillText(this.items[this.selectedIndex], this.x + 5, this.y + 15);
// Expanded list
if (this.open) {
for (let i = 0; i < this.items.length; i++) {
const itemY = this.y + this.height + i * this.height;
ctx.fillStyle = i === this.hoverIndex ? "#cce" : "#fff";
ctx.fillRect(this.x, itemY, this.width, this.height);
ctx.strokeRect(this.x, itemY, this.width, this.height);
ctx.fillStyle = "#000";
ctx.fillText(this.items[i], this.x + 5, itemY + 15);
}
}
}
contains(mx, my) {
const totalHeight = this.height + (this.open ? this.items.length * this.height : 0);
return mx >= this.x && mx <= this.x + this.width &&
my >= this.y && my <= this.y + totalHeight;
}
handleClick(mx, my) {
if (this.open) {
for (let i = 0; i < this.items.length; i++) {
const itemY = this.y + this.height + i * this.height;
if (mx >= this.x && mx <= this.x + this.width &&
my >= itemY && my <= itemY + this.height) {
this.selectedIndex = i;
this.open = false;
this.hoverIndex = -1;
return true;
}
}
this.open = false;
this.hoverIndex = -1;
return true;
}
if (mx >= this.x && mx <= this.x + this.width &&
my >= this.y && my <= this.y + this.height) {
this.open = true;
return true;
}
return false;
}
handleMouseMove(mx, my) {
if (!this.open) return false;
let hovered = -1;
for (let i = 0; i < this.items.length; i++) {
const itemY = this.y + this.height + i * this.height;
if (mx >= this.x && mx <= this.x + this.width &&
my >= itemY && my <= itemY + this.height) {
hovered = i;
break;
}
}
if (hovered !== this.hoverIndex) {
this.hoverIndex = hovered;
return true;
}
return false;
}
get selectedValue() {
return this.items[this.selectedIndex];
}
}