172 lines
4.2 KiB
JavaScript
172 lines
4.2 KiB
JavaScript
|
|
|
|
|
|
class UIElement {
|
|
constructor(x, y, w, h) {
|
|
this.x = x; this.y = y;
|
|
this.w = w; this.h = h;
|
|
this.hovered = false;
|
|
this.active = false;
|
|
|
|
}
|
|
|
|
contains(px, py) {
|
|
return px >= this.x && px <= this.x + this.w &&
|
|
py >= this.y && py <= this.y + this.h;
|
|
}
|
|
|
|
handlePointerEvent(event, px, py) {
|
|
switch (event.type) {
|
|
case 'pointermove':
|
|
this.onPointerMove(px, py);
|
|
break;
|
|
case 'pointerdown':
|
|
this.onPointerDown(px, py);
|
|
break;
|
|
case 'pointerup':
|
|
this.onPointerUp(px, py);
|
|
break;
|
|
case 'click':
|
|
// optional: treat click as pointerup for convenience
|
|
if (this.contains(px, py)) {
|
|
this.onClick?.(); // if subclass defines onClick
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
onPointerMove(px, py) {
|
|
this.hovered = this.contains(px, py);
|
|
//console.log(this);
|
|
}
|
|
|
|
onPointerDown(px, py) {
|
|
if (this.contains(px, py)) {
|
|
this.active = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
onPointerUp(px, py) {
|
|
if (this.active && this.contains(px, py)) {
|
|
this.active = false;
|
|
return true;
|
|
}
|
|
this.active = false;
|
|
return false;
|
|
}
|
|
|
|
draw(ctx) { /* override in subclasses */ }
|
|
}
|
|
|
|
export class Panel extends UIElement {
|
|
constructor(x, y, w, h, title = '') {
|
|
super(x, y, w, h);
|
|
this.title = title;
|
|
this.elements = [];
|
|
}
|
|
|
|
addElement(el) { this.elements.push(el); }
|
|
|
|
draw(ctx) {
|
|
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
|
ctx.fillRect(this.x, this.y, this.w, this.h);
|
|
ctx.strokeStyle = '#fff';
|
|
ctx.strokeRect(this.x, this.y, this.w, this.h);
|
|
|
|
if (this.title) {
|
|
ctx.fillStyle = '#fff';
|
|
ctx.font = '16px monospace';
|
|
ctx.fillText(this.title, this.x + 10, this.y + 20);
|
|
}
|
|
|
|
this.elements.forEach(el => el.draw(ctx));
|
|
}
|
|
|
|
handlePointerEvent(event, px, py) {
|
|
super.handlePointerEvent(event, px, py); // panel itself
|
|
this.elements.forEach(el => el.handlePointerEvent(event, px, py));
|
|
}
|
|
|
|
|
|
handleKeyEvent(event) {
|
|
this.elements.forEach(el => {
|
|
if (el.onKeyDown) el.onKeyDown(event);
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export class Button extends UIElement {
|
|
constructor(x, y, w, h, label, onClick) {
|
|
super(x, y, w, h);
|
|
this.label = label;
|
|
this.onClick = onClick;
|
|
}
|
|
|
|
draw(ctx) {
|
|
// ctx.fillStyle = this.active ? '#225433' :
|
|
// this.hovered ? '#666' : '#333';
|
|
// ctx.fillRect(this.x, this.y, this.w, this.h);
|
|
|
|
ctx.fillStyle = '#fff';
|
|
if (this.hovered) {
|
|
ctx.fillStyle = '#666';
|
|
|
|
}
|
|
ctx.font = '14px monospace';
|
|
ctx.fillText(this.label, this.x + 10, this.y + this.h - 8);
|
|
}
|
|
|
|
onPointerMove(px, py) {
|
|
this.hovered = this.contains(px, py);
|
|
//console.log(this);
|
|
}
|
|
|
|
onPointerUp(px, py) {
|
|
const clicked = super.onPointerUp(px, py);
|
|
if (clicked && this.onClick) this.onClick();
|
|
return clicked;
|
|
}
|
|
}
|
|
|
|
export class Textbox extends UIElement {
|
|
constructor(x, y, w, h, initialValue = '') {
|
|
super(x, y, w, h);
|
|
this.value = initialValue;
|
|
this.focused = false;
|
|
}
|
|
|
|
draw(ctx) {
|
|
ctx.fillStyle = '#000';
|
|
ctx.fillRect(this.x, this.y, this.w, this.h);
|
|
|
|
ctx.strokeStyle = this.focused ? '#0f0' : '#aaa';
|
|
ctx.strokeRect(this.x, this.y, this.w, this.h);
|
|
|
|
ctx.fillStyle = '#fff';
|
|
ctx.font = '14px monospace';
|
|
ctx.fillText(this.value, this.x + 5, this.y + this.h - 8);
|
|
}
|
|
|
|
onPointerUp(px, py) {
|
|
const clicked = super.onPointerUp(px, py);
|
|
this.focused = clicked;
|
|
return clicked;
|
|
}
|
|
|
|
onKeyDown(event) {
|
|
if (!this.focused) return;
|
|
if (event.key >= '0' && event.key <= '9') {
|
|
this.value += event.key;
|
|
} else if (event.key === 'Backspace') {
|
|
this.value = this.value.slice(0, -1);
|
|
}
|
|
}
|
|
}
|
|
|