/** * 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); } }