mafia / frontend /src /ui /ModalOverlay.ts
Alfaxad's picture
Migrate Mafia game to ZeroGPU Gradio Space
c670567 verified
Raw
History Blame Contribute Delete
4.27 kB
/**
* ============================================================================
* MODAL OVERLAY - Base class for modal dialogs
* ============================================================================
*
* Creates a semi-transparent overlay that blocks background interaction,
* providing a content container for modal windows like quiz panels,
* confirmation dialogs, or information popups.
*
* USAGE:
* class MyModal extends ModalOverlay {
* protected createContent(): void {
* // Add your content to this.content container
* const text = this.scene.add.text(0, 0, 'Hello!');
* this.content.add(text);
* }
* }
* const modal = new MyModal(scene, { width: 600, height: 400 });
* modal.show();
* modal.hide();
*/
import Phaser from 'phaser';
export interface ModalConfig {
/** Modal content area width */
width?: number;
/** Modal content area height */
height?: number;
/** Overlay background color (default: 0x000000) */
overlayColor?: number;
/** Overlay opacity (default: 0.6) */
overlayAlpha?: number;
/** Close on clicking overlay (default: false) */
closeOnOverlayClick?: boolean;
/** Panel background color (default: 0x1a1a2e) */
panelColor?: number;
/** Panel background opacity (default: 0.95) */
panelAlpha?: number;
/** Panel border color (default: 0x6666aa) */
panelStrokeColor?: number;
/** Panel border width (default: 2) */
panelStrokeWidth?: number;
}
export class ModalOverlay extends Phaser.GameObjects.Container {
protected modalConfig: ModalConfig;
protected overlay!: Phaser.GameObjects.Rectangle;
protected panel!: Phaser.GameObjects.Rectangle;
protected content!: Phaser.GameObjects.Container;
protected isShowing: boolean = false;
constructor(scene: Phaser.Scene, config?: ModalConfig) {
super(scene, 0, 0);
this.modalConfig = config ?? {};
scene.add.existing(this);
this.setDepth(300);
this.createOverlay();
this.setVisible(false);
}
// -- Public API --
/** Show the modal with fade-in. */
show(): void {
// Cancel any pending hide tween to prevent it from overriding this show
this.scene.tweens.killTweensOf(this);
this.isShowing = true;
this.setVisible(true);
this.setAlpha(0);
this.scene.tweens.add({
targets: this,
alpha: 1,
duration: 200,
});
}
/** Hide the modal with fade-out. */
hide(): void {
this.scene.tweens.killTweensOf(this);
this.scene.tweens.add({
targets: this,
alpha: 0,
duration: 200,
onComplete: () => {
this.isShowing = false;
this.setVisible(false);
this.emit('hidden');
},
});
}
/** Check if modal is currently visible. */
getIsShowing(): boolean {
return this.isShowing;
}
// -- Protected (override in subclasses) --
/** HOOK: Override to populate the content container with custom UI. */
protected createContent(): void {
// Override in subclass
}
// -- Internal --
private createOverlay(): void {
const cam = this.scene.cameras.main;
const w = cam.width;
const h = cam.height;
const cfg = this.modalConfig;
// Dark overlay
this.overlay = this.scene.add.rectangle(
w / 2,
h / 2,
w,
h,
cfg.overlayColor ?? 0x000000,
cfg.overlayAlpha ?? 0.6,
);
this.overlay.setOrigin(0.5);
this.overlay.setInteractive(); // block clicks to background
this.add(this.overlay);
if (cfg.closeOnOverlayClick) {
this.overlay.on('pointerdown', () => this.hide());
}
// Panel background (colors and stroke are configurable)
const panelW = cfg.width ?? 600;
const panelH = cfg.height ?? 400;
this.panel = this.scene.add.rectangle(
w / 2,
h / 2,
panelW,
panelH,
cfg.panelColor ?? 0x1a1a2e,
cfg.panelAlpha ?? 0.95,
);
this.panel.setOrigin(0.5);
this.panel.setStrokeStyle(
cfg.panelStrokeWidth ?? 2,
cfg.panelStrokeColor ?? 0x6666aa,
);
this.add(this.panel);
// Content container (positioned at panel center)
this.content = this.scene.add.container(w / 2, h / 2);
this.add(this.content);
// Let subclasses populate content
this.createContent();
}
}