94 lines
2.8 KiB
JavaScript
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);
|
|
}
|
|
}
|