Files
2026-09-24 17:57:54 +02:00

94 lines
2.8 KiB
JavaScript

/**
* Senkrechte Menüliste aus Canvas-Texten, bedienbar mit Maus und Tastatur.
* @module ui/MenuList
*/
import { oncePerEvent } from '../systems/keyboard.js';
import { UI } from './theme.js';
/**
* @typedef {Object} MenuItem
* @property {string} label
* @property {() => void} onSelect
*/
export class MenuList {
/**
* @param {Phaser.Scene} scene
* @param {number} x Mittelpunkt
* @param {number} y Oberkante des ersten Eintrags
* @param {MenuItem[]} items
* @param {{spacing?: number, fontSize?: number}} [options]
*/
constructor(scene, x, y, items, options = {}) {
this.scene = scene;
this.items = items;
this.audio = scene.registry.get('services')?.audio ?? null;
this.index = 0;
this.enabled = true;
const spacing = options.spacing ?? 58;
const fontSize = options.fontSize ?? 30;
this.texts = items.map((item, i) => {
const text = scene.add
.text(x, y + i * spacing, item.label.toUpperCase(), {
...UI.text,
fontSize: `${fontSize}px`,
color: UI.colors.cream,
})
.setOrigin(0.5)
.setInteractive({ useHandCursor: true });
text.on('pointerover', () => {
if (this.enabled) this.focus(i);
});
text.on('pointerup', () => {
if (this.enabled) this.select(i);
});
return text;
});
this.keys = scene.input.keyboard.addKeys('UP,DOWN,W,S,ENTER,SPACE');
this.onKeyDown = oncePerEvent((event) => {
if (!this.enabled || event.repeat) return;
if (event.code === 'ArrowUp' || event.code === 'KeyW') this.focus(this.index - 1);
else if (event.code === 'ArrowDown' || event.code === 'KeyS') this.focus(this.index + 1);
else if (event.code === 'Enter' || event.code === 'Space' || event.code === 'NumpadEnter') this.select(this.index);
});
scene.input.keyboard.on('keydown', this.onKeyDown);
scene.events.once('shutdown', () => this.destroy());
this.focus(0);
}
/** @param {number} index */
focus(index) {
const count = this.items.length;
const next = (index + count) % count;
if (next !== this.index) this.audio?.play('ui-click');
this.index = next;
this.texts.forEach((text, i) => {
const active = i === this.index;
text.setColor(active ? UI.colors.gold : UI.colors.cream);
text.setScale(active ? 1.08 : 1);
// Großbuchstaben wie auf einem Plakat; die Texte selbst bleiben normal geschrieben.
const label = this.items[i].label.toUpperCase();
text.setText(active ? `» ${label} «` : label);
});
}
/** @param {number} index */
select(index) {
this.focus(index);
this.audio?.play('ui-confirm');
this.items[index].onSelect();
}
/** @param {boolean} enabled */
setEnabled(enabled) {
this.enabled = enabled;
}
destroy() {
this.scene.input.keyboard?.off('keydown', this.onKeyDown);
}
}