import { Container, Graphics, Rectangle, FederatedPointerEvent } from 'pixi.js';
import { PixelText } from './PixelText';
/**
* Кнопка UI: пиксельная рамка, состояния normal/hover/pressed/focused.
* Активация — клик мышью или onSelect() из клавиатурной навигации (MenuList).
*/
export interface ButtonOptions {
label: string;
width: number;
height: number;
size?: number;
onSelect?: () => void;
}
type ButtonState = 'normal' | 'hover' | 'pressed' | 'focused';
const STATE_COLORS: Record<ButtonState, { fill: number; border: number; text: number }> = {
normal: { fill: 0x16161f, border: 0x8899aa, text: 0xcccccc },
hover: { fill: 0x1e1e2a, border: 0xf0d878, text: 0xffffff },
pressed: { fill: 0x2a2a38, border: 0xf0d878, text: 0xffffff },
focused: { fill: 0x1e1e2a, border: 0xd99a32, text: 0xffffff }
};
export class Button extends Container {
readonly labelText: PixelText;
onSelect: (() => void) | null;
private bg: Graphics;
private state: ButtonState = 'normal';
private readonly w: number;
private readonly h: number;
private readonly size: number;
constructor(options: ButtonOptions) {
super();
this.w = options.width;
this.h = options.height;
this.size = options.size ?? 10;
this.onSelect = options.onSelect ?? null;
this.bg = new Graphics();
this.labelText = new PixelText({
text: options.label,
size: this.size,
color: STATE_COLORS.normal.text
});
this.labelText.anchor.set(0.5);
this.labelText.position.set(this.w / 2, this.h / 2);
this.addChild(this.bg, this.labelText);
this.eventMode = 'static';
this.cursor = 'pointer';
this.hitArea = new Rectangle(0, 0, this.w, this.h);
this.on('pointerover', () => this.setState(this.state === 'pressed' ? 'pressed' : 'hover'));
this.on('pointerout', () => this.setState(this.focused ? 'focused' : 'normal'));
this.on('pointerdown', (e: FederatedPointerEvent) => {
e.stopPropagation();
this.setState('pressed');
});
this.on('pointerup', () => this.setState('hover'));
this.on('pointerupoutside', () => this.setState('normal'));
this.on('pointertap', () => this.onSelect?.());
this.draw();
}
/** Клавиатурный фокус (визуально — рамка акцентного цвета). */
set focused(value: boolean) {
if (value && this.state === 'normal') this.setState('focused');
else if (!value && this.state === 'focused') this.setState('normal');
}
get focused(): boolean {
return this.state === 'focused';
}
/** Программная активация (Enter из MenuList). */
activate(): void {
this.onSelect?.();
}
private setState(s: ButtonState): void {
this.state = s;
this.labelText.style.fill = STATE_COLORS[s].text;
this.draw();
}
private draw(): void {
const c = STATE_COLORS[this.state];
this.bg.clear();
this.bg.rect(0, 0, this.w, this.h).fill({ color: c.fill, alpha: 0.95 });
this.bg.rect(0, 0, this.w, this.h).stroke({ color: c.border, width: 1 });
}
}