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.9); this.text.setColor(thc); } 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; } }