pokerogue/src/ui/handlers/abstract-binding-ui-handler.ts

278 lines
8.6 KiB
TypeScript

import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import { NavigationManager } from "#ui/navigation-menu";
import { addTextObject, getTextColor } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";
import i18next from "i18next";
type CancelFn = (success?: boolean) => boolean;
/**
* Abstract class for handling UI elements related to button bindings.
*/
export abstract class AbstractBindingUiHandler extends UiHandler {
// Containers for different segments of the UI.
protected optionSelectContainer: Phaser.GameObjects.Container;
protected actionsContainer: Phaser.GameObjects.Container;
// Background elements for titles and action areas.
protected titleBg: Phaser.GameObjects.NineSlice;
protected actionBg: Phaser.GameObjects.NineSlice;
protected optionSelectBg: Phaser.GameObjects.NineSlice;
// Text elements for displaying instructions and actions.
protected unlockText: Phaser.GameObjects.Text;
protected timerText: Phaser.GameObjects.Text;
protected swapText: Phaser.GameObjects.Text;
protected actionLabel: Phaser.GameObjects.Text;
protected cancelLabel: Phaser.GameObjects.Text;
protected listening = false;
protected buttonPressed: number | null = null;
// Icons for displaying current and new button assignments.
protected newButtonIcon: Phaser.GameObjects.Sprite;
protected targetButtonIcon: Phaser.GameObjects.Sprite;
// Function to call on cancel or completion of binding.
protected cancelFn: CancelFn | null;
abstract swapAction(): boolean;
protected timeLeftAutoClose = 5;
protected countdownTimer;
// The specific setting being modified.
protected target;
/**
* Constructor for the AbstractBindingUiHandler.
*
* @param mode - The UI mode.
*/
constructor(mode: UiMode | null = null) {
super(mode);
}
/**
* Setup UI elements.
*/
setup() {
const ui = this.getUi();
this.optionSelectContainer = globalScene.add.container(0, 0);
this.actionsContainer = globalScene.add.container(0, 0);
// Initially, containers are not visible.
this.optionSelectContainer.setVisible(false);
this.actionsContainer.setVisible(false);
// Add containers to the UI.
ui.add(this.optionSelectContainer);
ui.add(this.actionsContainer);
// Setup backgrounds and text objects for UI.
this.titleBg = addWindow(
globalScene.scaledCanvas.width - this.getWindowWidth(),
-globalScene.scaledCanvas.height + 28 + 21,
this.getWindowWidth(),
24,
);
this.titleBg.setOrigin(0.5);
this.optionSelectContainer.add(this.titleBg);
this.actionBg = addWindow(
globalScene.scaledCanvas.width - this.getWindowWidth(),
-globalScene.scaledCanvas.height + this.getWindowHeight() + 28 + 21 + 21,
this.getWindowWidth(),
24,
);
this.actionBg.setOrigin(0.5);
this.actionsContainer.add(this.actionBg);
// Text prompts and instructions for the user.
this.unlockText = addTextObject(0, 0, i18next.t("settings:pressButton"), TextStyle.WINDOW);
this.unlockText.setOrigin(0, 0);
this.unlockText.setPositionRelative(this.titleBg, 36, 4);
this.optionSelectContainer.add(this.unlockText);
this.timerText = addTextObject(0, 0, "(5)", TextStyle.WINDOW);
this.timerText.setOrigin(0, 0);
this.timerText.setPositionRelative(this.unlockText, this.unlockText.width / 6 + 5, 0);
this.optionSelectContainer.add(this.timerText);
this.optionSelectBg = addWindow(
globalScene.scaledCanvas.width - this.getWindowWidth(),
-globalScene.scaledCanvas.height + this.getWindowHeight() + 28,
this.getWindowWidth(),
this.getWindowHeight(),
);
this.optionSelectBg.setOrigin(0.5);
this.optionSelectContainer.add(this.optionSelectBg);
this.cancelLabel = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL);
this.cancelLabel.setOrigin(0, 0.5);
this.cancelLabel.setPositionRelative(this.actionBg, 10, this.actionBg.height / 2);
this.actionsContainer.add(this.cancelLabel);
}
manageAutoCloseTimer() {
clearTimeout(this.countdownTimer);
this.countdownTimer = setTimeout(() => {
this.timeLeftAutoClose -= 1;
this.timerText.setText(`(${this.timeLeftAutoClose})`);
if (this.timeLeftAutoClose >= 0) {
this.manageAutoCloseTimer();
} else {
this.cancelFn?.();
}
}, 1000);
}
/**
* Show the UI with the provided arguments.
*
* @param args - Arguments to be passed to the show method.
* @returns `true` if successful.
*/
show(args: any[]): boolean {
super.show(args);
this.buttonPressed = null;
this.timeLeftAutoClose = 5;
this.cancelFn = args[0].cancelHandler;
this.target = args[0].target;
// Bring the option and action containers to the front of the UI.
this.getUi().bringToTop(this.optionSelectContainer);
this.getUi().bringToTop(this.actionsContainer);
this.optionSelectContainer.setVisible(true);
setTimeout(() => {
this.listening = true;
this.manageAutoCloseTimer();
}, 100);
return true;
}
/**
* Get the width of the window.
*
* @returns The window width.
*/
getWindowWidth(): number {
return 160;
}
/**
* Get the height of the window.
*
* @returns The window height.
*/
getWindowHeight(): number {
return 64;
}
/**
* Process the input for the given button.
*
* @param button - The button to process.
* @returns `true` if the input was processed successfully.
*/
processInput(button: Button): boolean {
if (this.buttonPressed === null) {
return false; // TODO: is false correct as default? (previously was `undefined`)
}
const ui = this.getUi();
let success = false;
switch (button) {
case Button.LEFT:
case Button.RIGHT: {
// Toggle between action and cancel options.
success = this.setCursor(this.cursor ? 0 : 1);
break;
}
case Button.ACTION:
// Process actions based on current cursor position.
if (this.cursor === 0) {
this.cancelFn?.();
} else {
success = this.swapAction();
NavigationManager.getInstance().updateIcons();
this.cancelFn?.(success);
}
break;
}
// Plays a select sound effect if an action was successfully processed.
if (success) {
ui.playSelect();
} else {
ui.playError();
}
return success;
}
/**
* Set the cursor to the specified position.
*
* @param cursor - The cursor position to set.
* @returns `true` if the cursor was set successfully.
*/
setCursor(cursor: number): boolean {
this.cursor = cursor;
if (cursor === 1) {
this.actionLabel.setColor(getTextColor(TextStyle.SETTINGS_SELECTED));
this.actionLabel.setShadowColor(getTextColor(TextStyle.SETTINGS_SELECTED, true));
this.cancelLabel.setColor(getTextColor(TextStyle.WINDOW));
this.cancelLabel.setShadowColor(getTextColor(TextStyle.WINDOW, true));
return true;
}
this.actionLabel.setColor(getTextColor(TextStyle.WINDOW));
this.actionLabel.setShadowColor(getTextColor(TextStyle.WINDOW, true));
this.cancelLabel.setColor(getTextColor(TextStyle.SETTINGS_SELECTED));
this.cancelLabel.setShadowColor(getTextColor(TextStyle.SETTINGS_SELECTED, true));
return true;
}
/**
* Clear the UI elements and state.
*/
clear() {
super.clear();
clearTimeout(this.countdownTimer);
this.timerText.setText("(5)");
this.timeLeftAutoClose = 5;
this.listening = false;
this.target = null;
this.cancelFn = null;
this.optionSelectContainer.setVisible(false);
this.actionsContainer.setVisible(false);
this.newButtonIcon.setVisible(false);
this.buttonPressed = null;
}
/**
* Handle input down events.
*
* @param buttonIcon - The icon of the button that was pressed.
* @param assignedButtonIcon - The icon of the button that is assigned.
* @param type - The type of button press.
*/
onInputDown(buttonIcon: string, assignedButtonIcon: string | null, type: string): void {
clearTimeout(this.countdownTimer);
this.timerText.setText("");
this.newButtonIcon.setTexture(type);
this.newButtonIcon.setFrame(buttonIcon);
if (assignedButtonIcon) {
this.targetButtonIcon.setTexture(type);
this.targetButtonIcon.setFrame(assignedButtonIcon);
this.targetButtonIcon.setVisible(true);
this.swapText.setVisible(true);
}
this.newButtonIcon.setVisible(true);
this.setCursor(0);
this.actionsContainer.setVisible(true);
}
}