pokerogue/src/ui/dropdown.ts
Leo Kim 0ad4986fa5
[Enhancement] improve passive and win filters by applying new Tri-state toggle dropdown in the starter select UI (#3252)
* split unlocks filter into shiny and passive

* update tri state dropdown. apply it to passive and win dropdown
2024-07-30 12:13:17 -04:00

326 lines
11 KiB
TypeScript

import BattleScene from "#app/battle-scene.js";
import { SceneBase } from "#app/scene-base.js";
import { addTextObject, TextStyle } from "./text";
import { addWindow, WindowVariant } from "./ui-theme";
import i18next from "i18next";
export enum DropDownState {
ON = 0,
OFF = 1,
INCLUDE = 2,
EXCLUDE = 3,
}
export enum DropDownType {
MULTI = 0,
SINGLE = 1,
TRI = 2
}
export enum SortDirection {
ASC = -1,
DESC = 1
}
export class DropDownOption extends Phaser.GameObjects.Container {
public state: DropDownState = DropDownState.ON;
public toggle: Phaser.GameObjects.Sprite;
public text: Phaser.GameObjects.Text;
public sprite?: Phaser.GameObjects.Sprite;
public val: any;
public dir: SortDirection = SortDirection.ASC;
public offStateLabel: string; // label for OFF state in TRI dropdown
public includeStateLabel: string; // label for INCLUDE state in TRI dropdown
public excludeStateLabel: string; // label for EXCLUDE state in TRI dropdown
private onColor = 0x55ff55;
private offColor = 0x272727;
private includeColor = 0x55ff55;
private excludeColor = 0xff5555;
constructor(scene: SceneBase, val: any, text: string | string[], sprite?: Phaser.GameObjects.Sprite, state: DropDownState = DropDownState.ON) {
super(scene);
this.val = val;
this.state = state;
if (text) {
if (Array.isArray(text)) {
this.offStateLabel = text[0];
this.includeStateLabel = text[1];
this.excludeStateLabel = text[2];
text = text[0];
} else {
this.offStateLabel = undefined;
this.includeStateLabel = undefined;
this.excludeStateLabel = undefined;
}
this.text = addTextObject(scene, 0, 0, text, TextStyle.TOOLTIP_CONTENT);
this.text.setOrigin(0, 0.5);
this.add(this.text);
}
if (sprite) {
this.sprite = sprite.setOrigin(0, 0.5);
this.add(this.sprite);
}
}
public setupToggle(type: DropDownType): void {
if (type === DropDownType.MULTI || type === DropDownType.TRI) {
this.toggle = this.scene.add.sprite(0, 0, "candy");
this.toggle.setScale(0.3);
this.toggle.setOrigin(0, 0.5);
} else {
this.toggle = this.scene.add.sprite(0, 0, "cursor");
this.toggle.setScale(0.5);
this.toggle.setOrigin(0, 0.5);
this.toggle.setRotation(Math.PI / 180 * -90);
}
this.add(this.toggle);
}
public setOptionState(type: DropDownType, state: DropDownState): DropDownState {
this.state = state;
// if type is MULTI or SINGLE, set the color of the toggle based on the state
if (type === DropDownType.MULTI || type === DropDownType.SINGLE) {
if (this.state === DropDownState.OFF) {
this.toggle.setTint(this.offColor);
} else if (this.state === DropDownState.ON) {
this.toggle.setTint(this.onColor);
}
} else if (type === DropDownType.TRI) {
if (this.state === DropDownState.OFF) {
this.text.setText(this.offStateLabel);
this.toggle.setTint(this.offColor);
} else if (this.state === DropDownState.INCLUDE) {
this.text.setText(this.includeStateLabel);
this.toggle.setTint(this.includeColor);
} else if (this.state === DropDownState.EXCLUDE) {
this.text.setText(this.excludeStateLabel);
this.toggle.setTint(this.excludeColor);
}
}
return this.state;
}
public toggleOptionState(type: DropDownType): DropDownState {
if (type === DropDownType.TRI) {
switch (this.state) {
case DropDownState.OFF:
this.state = DropDownState.INCLUDE;
break;
case DropDownState.INCLUDE:
this.state = DropDownState.EXCLUDE;
break;
case DropDownState.EXCLUDE:
this.state = DropDownState.OFF;
break;
}
} else {
switch (this.state) {
case DropDownState.ON:
this.state = DropDownState.OFF;
break;
case DropDownState.OFF:
this.state = DropDownState.ON;
break;
}
}
return this.setOptionState(type, this.state);
}
public setDirection(dir: SortDirection): void {
this.dir = dir;
this.toggle.flipX = this.dir === SortDirection.DESC;
}
public toggleDirection(): void {
this.setDirection(this.dir * -1);
}
}
export class DropDown extends Phaser.GameObjects.Container {
public options: DropDownOption[];
private window: Phaser.GameObjects.NineSlice;
private cursorObj: Phaser.GameObjects.Image;
private dropDownType: DropDownType = DropDownType.MULTI;
public cursor: integer = 0;
private onChange: () => void;
private lastDir: SortDirection = SortDirection.ASC;
constructor(scene: BattleScene, x: number, y: number, options: DropDownOption[], onChange: () => void, type: DropDownType = DropDownType.MULTI, optionSpacing: number = 2) {
const windowPadding = 5;
const optionHeight = 7;
const optionPaddingX = 4;
const optionPaddingY = 6;
const cursorOffset = 7;
const optionWidth = 100;
super(scene, x - cursorOffset - windowPadding, y);
this.options = options;
this.dropDownType = type;
this.onChange = onChange;
this.cursorObj = scene.add.image(optionPaddingX + 3, 0, "cursor");
this.cursorObj.setScale(0.5);
this.cursorObj.setOrigin(0, 0.5);
this.cursorObj.setVisible(false);
if (this.dropDownType === DropDownType.MULTI) {
this.options.unshift(new DropDownOption(scene, "ALL", i18next.t("filterBar:all"), null, this.checkForAllOn() ? DropDownState.ON : DropDownState.OFF));
}
options.forEach((option, index) => {
option.setupToggle(type);
if (type === DropDownType.SINGLE && option.state === DropDownState.OFF) {
option.toggle.setVisible(false);
}
option.setOptionState(type, option.state);
option.width = optionWidth;
option.y = index * optionHeight + index * optionSpacing + optionPaddingY;
if (option.text) {
option.text.x = cursorOffset + optionPaddingX + 3 + 8;
option.text.y = optionHeight / 2;
}
if (option.sprite) {
option.sprite.x = cursorOffset + optionPaddingX + 3 + 8;
option.sprite.y = optionHeight / 2;
}
if (type === DropDownType.SINGLE) {
option.toggle.x = cursorOffset + optionPaddingX + 3 + 3;
option.toggle.y = optionHeight / 2 + 1;
} else {
option.toggle.x = cursorOffset + optionPaddingX + 3;
option.toggle.y = optionHeight / 2;
}
});
this.window = addWindow(scene, 0, 0, optionWidth, options[options.length - 1].y + optionHeight + optionPaddingY, false, false, null, null, WindowVariant.XTHIN);
this.add(this.window);
this.add(options);
this.add(this.cursorObj);
this.setVisible(false);
}
toggle(): void {
this.setVisible(!this.visible);
}
setCursor(cursor: integer): boolean {
this.cursor = cursor;
if (cursor < 0) {
cursor = 0;
this.cursorObj.setVisible(false);
return false;
} else if (cursor >= this.options.length) {
cursor = this.options.length - 1;
this.cursorObj.y = this.options[cursor].y + 3.5;
this.cursorObj.setVisible(true);
return false;
} else {
this.cursorObj.y = this.options[cursor].y + 3.5;
this.cursorObj.setVisible(true);
}
return true;
}
toggleOptionState(): void {
if (this.dropDownType === DropDownType.MULTI) {
const newState = this.options[this.cursor].toggleOptionState(this.dropDownType);
if (this.cursor === 0) {
this.options.forEach((option, index) => {
if (index !== this.cursor) {
option.setOptionState(this.dropDownType, newState);
}
});
} else {
if (this.checkForAllOff()) {
this.options[0].setOptionState(this.dropDownType, DropDownState.OFF);
} else if (this.checkForAllOn()) {
this.options[0].setOptionState(this.dropDownType, DropDownState.ON);
} else {
this.options[0].setOptionState(this.dropDownType, DropDownState.OFF);
}
}
} else if (this.dropDownType === DropDownType.SINGLE) {
if (this.options[this.cursor].state === DropDownState.OFF) {
this.options.forEach((option) => {
option.setOptionState(this.dropDownType, DropDownState.OFF);
option.setDirection(SortDirection.ASC);
option.toggle.setVisible(false);
});
this.options[this.cursor].setOptionState(this.dropDownType, DropDownState.ON);
this.options[this.cursor].setDirection(this.lastDir);
this.options[this.cursor].toggle.setVisible(true);
} else {
this.options[this.cursor].toggleDirection();
this.lastDir = this.options[this.cursor].dir;
}
} else if (this.dropDownType === DropDownType.TRI) {
this.options[this.cursor].toggleOptionState(this.dropDownType);
this.autoSize();
}
this.onChange();
}
setVisible(value: boolean): this {
super.setVisible(value);
if (value) {
this.autoSize();
}
return this;
}
checkForAllOn(): boolean {
return this.options.every((option, i) => i === 0 || option.state === DropDownState.ON);
}
checkForAllOff(): boolean {
return this.options.every((option, i) => i === 0 || option.state === DropDownState.OFF);
}
getVals(): any[] {
if (this.dropDownType === DropDownType.MULTI) {
return this.options.filter((option, i) => i > 0 && option.state === DropDownState.ON).map((option) => option.val);
// in TRI dropdown, if state is ON, return the "ON" with the value, if state is OFF, return the "OFF" with the value, if state is TRI, return the "TRI" with the value
} else if (this.dropDownType === DropDownType.TRI) {
return this.options.filter((option, i) => option.state === DropDownState.OFF || option.state === DropDownState.INCLUDE || option.state === DropDownState.EXCLUDE).map((option) => {
return {val: option.val, state: option.state};
});
} else {
return this.options.filter((option, i) => option.state === DropDownState.ON).map((option) => {
return {val: option.val, dir: option.dir};
});
}
}
autoSize(): void {
let maxWidth = 0;
let x = 0;
for (let i = 0; i < this.options.length; i++) {
if (this.options[i].sprite) {
if (this.options[i].sprite.displayWidth > maxWidth) {
maxWidth = this.options[i].sprite.displayWidth;
x = this.options[i].sprite.x;
}
} else {
if (this.options[i].text.displayWidth > maxWidth) {
maxWidth = this.options[i].text.displayWidth;
x = this.options[i].text.x;
}
}
}
this.window.width = maxWidth + x - this.window.x + 6;
if (this.x + this.window.width > this.parentContainer.width) {
this.x = this.parentContainer.width - this.window.width;
}
}
isActive(): boolean {
return this.options.some((option) => option.state === DropDownState.ON);
}
}