fertig-classic-games/src/ui/Button.js

122 lines
3.6 KiB
JavaScript

import * as Phaser from 'phaser';
import { COLORS } from '../config.js';
const RADIUS = 8;
export class Button extends Phaser.GameObjects.Container {
constructor(scene, x, y, label, onClick, options = {}) {
super(scene, x, y);
const {
width = 280,
height = 64,
bg = COLORS.panel,
bgHover = COLORS.gold,
textColor = COLORS.textHex,
textHoverColor = COLORS.textDarkHex,
fontSize = 28,
variant = 'solid',
} = options;
this.options = { width, height, bg, bgHover, textColor, textHoverColor, fontSize, variant };
const isGhost = variant === 'ghost';
this.bgRect = scene.add.graphics();
this.bgRect.postFX.addShadow(0, 3, 0.004, 1.5, 0x000000, 8, 0.65);
this._drawBg(bg, isGhost ? 0.35 : 1);
this.text = scene.add.text(0, 0, label, {
fontFamily: '"Julius Sans One"',
fontSize: `${fontSize}px`,
color: textColor,
}).setOrigin(0.5);
// Long labels on a fixed-width button otherwise render past the edge of
// the rounded rect. Purely corrective: only ever shrinks text that
// wouldn't have fit at the requested size, so every button whose label
// already fits renders exactly as before.
const maxTextWidth = width - 16;
if (this.text.width > maxTextWidth) {
const fitSize = Math.max(11, Math.floor(fontSize * (maxTextWidth / this.text.width)));
this.text.setFontSize(fitSize);
}
this.add([this.bgRect, this.text]);
this.setSize(width, height);
this.setInteractive({
useHandCursor: true,
hitArea: new Phaser.Geom.Rectangle(0, 0, width, height),
hitAreaCallback: Phaser.Geom.Rectangle.Contains,
});
const onOver = () => {
if (this._active) return;
const { bgHover: bgh, textHoverColor: thc, variant: v } = this.options;
if (v === 'ghost') {
this._drawBg(bgh, 0.18);
this.text.setColor(COLORS.goldHex);
} else {
this._drawBg(bgh, 1);
this.text.setColor(thc);
}
};
const onOut = () => {
if (this._active) return;
const { bg: b, textColor: tc, variant: v } = this.options;
this._drawBg(b, v === 'ghost' ? 0.35 : 1);
this.text.setColor(tc);
};
const onDown = () => this.bgRect.setScale(0.97);
const onUp = () => this.bgRect.setScale(1);
this.on('pointerover', onOver);
this.on('pointerout', onOut);
this.on('pointerdown', onDown);
this.on('pointerup', onUp);
this.on('pointerupoutside', onUp);
if (onClick) this.on('pointerup', onClick);
scene.add.existing(this);
}
_drawBg(fillColor, fillAlpha) {
const { width, height } = this.options;
const hw = width / 2;
const hh = height / 2;
this.bgRect.clear();
if (fillAlpha > 0) {
this.bgRect.fillStyle(fillColor, fillAlpha);
this.bgRect.fillRoundedRect(-hw, -hh, width, height, RADIUS);
}
this.bgRect.lineStyle(2, COLORS.accent, 1);
this.bgRect.strokeRoundedRect(-hw, -hh, width, height, RADIUS);
}
setActive(active) {
this._active = active;
const { bg, bgHover, textColor, textHoverColor, variant } = this.options;
if (active) {
this._drawBg(bgHover, 1);
this.text.setColor(textHoverColor);
} else {
this._drawBg(bg, variant === 'ghost' ? 0.35 : 1);
this.text.setColor(textColor);
}
return this;
}
setLabel(label) {
this.text.setText(label);
return this;
}
setEnabled(enabled) {
this.setAlpha(enabled ? 1 : 0.5);
if (enabled) this.setInteractive({ useHandCursor: true });
else this.disableInteractive();
return this;
}
}