[QoL] New Starter Select UI with Filter (#2916)
* update images for new UI * add updated starter UI with filter code * update starter-select test code * update win filter condition to pass test * remove unnecessary console log * update test code to match current filter UI * merge update * apply bugfix & chrry-pick small issues fix which are handled beta branch * resolve conflicts * fix lint errors * Fixed a bug where the target location for escaping using the left and right buttons on the starter button did not account for scrolling * update filter bar label color change when activated * fix lint error * fix lint * fix octolock.text.ts. it looks override import error. idk why it is happend in this PR. but it looks ok now * add passive dropdown in unlocks filter * fix lint * fix double button sound bug. refactoring genSpecies -> allSpecies, starterContainers -> starterContainer which are remove unnecessary generation axis * optimize updateStarterValueLabel function which is bottleneck of UI update latency * apply translation of gen filter label. fix lint * add # candies sort option * merge beta * resolve confilcts * fix offset of starter and start cursor * make compatible with starter UI * add missing feature * add images for legacy UI. adjust the position and size of the starterContainerWindow
BIN
public/images/ui/legacy/scroll_bar.png
Normal file
After Width: | Height: | Size: 143 B |
BIN
public/images/ui/legacy/scroll_bar_handle.png
Normal file
After Width: | Height: | Size: 108 B |
BIN
public/images/ui/legacy/starter_container_bg.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
public/images/ui/scroll_bar.png
Normal file
After Width: | Height: | Size: 143 B |
BIN
public/images/ui/scroll_bar_handle.png
Normal file
After Width: | Height: | Size: 108 B |
BIN
public/images/ui/starter_container_bg.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.2 KiB |
@ -146,6 +146,9 @@ export class LoadingScene extends SceneBase {
|
||||
this.loadImage(`summary_tabs_${t}`, "ui");
|
||||
}
|
||||
|
||||
this.loadImage("scroll_bar", "ui");
|
||||
this.loadImage("scroll_bar_handle", "ui");
|
||||
this.loadImage("starter_container_bg", "ui");
|
||||
this.loadImage("starter_select_bg", "ui");
|
||||
this.loadImage("select_cursor", "ui");
|
||||
this.loadImage("select_cursor_highlight", "ui");
|
||||
|
@ -54,6 +54,7 @@ describe("UI - Starter select", () => {
|
||||
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
|
||||
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.LEFT);
|
||||
handler.processInput(Button.ACTION);
|
||||
game.phaseInterceptor.unlock();
|
||||
});
|
||||
@ -113,6 +114,7 @@ describe("UI - Starter select", () => {
|
||||
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
|
||||
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.LEFT);
|
||||
handler.processInput(Button.CYCLE_GENDER);
|
||||
handler.processInput(Button.ACTION);
|
||||
game.phaseInterceptor.unlock();
|
||||
@ -174,6 +176,7 @@ describe("UI - Starter select", () => {
|
||||
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
|
||||
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.LEFT);
|
||||
handler.processInput(Button.CYCLE_GENDER);
|
||||
handler.processInput(Button.CYCLE_NATURE);
|
||||
handler.processInput(Button.CYCLE_ABILITY);
|
||||
@ -237,6 +240,7 @@ describe("UI - Starter select", () => {
|
||||
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
|
||||
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.LEFT);
|
||||
handler.processInput(Button.CYCLE_GENDER);
|
||||
handler.processInput(Button.ACTION);
|
||||
game.phaseInterceptor.unlock();
|
||||
@ -297,6 +301,7 @@ describe("UI - Starter select", () => {
|
||||
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
|
||||
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.LEFT);
|
||||
handler.processInput(Button.CYCLE_SHINY);
|
||||
handler.processInput(Button.ACTION);
|
||||
game.phaseInterceptor.unlock();
|
||||
@ -356,6 +361,7 @@ describe("UI - Starter select", () => {
|
||||
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
|
||||
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.LEFT);
|
||||
handler.processInput(Button.V);
|
||||
handler.processInput(Button.V);
|
||||
handler.processInput(Button.ACTION);
|
||||
@ -416,6 +422,7 @@ describe("UI - Starter select", () => {
|
||||
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
|
||||
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.LEFT);
|
||||
handler.processInput(Button.V);
|
||||
handler.processInput(Button.V);
|
||||
handler.processInput(Button.V);
|
||||
@ -479,7 +486,6 @@ describe("UI - Starter select", () => {
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.ACTION);
|
||||
game.phaseInterceptor.unlock();
|
||||
});
|
||||
@ -509,10 +515,10 @@ describe("UI - Starter select", () => {
|
||||
});
|
||||
});
|
||||
|
||||
expect(starterSelectUiHandler.starterGens[0]).toBe(0);
|
||||
expect(starterSelectUiHandler.starterCursors[0]).toBe(3);
|
||||
expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
|
||||
expect(starterSelectUiHandler.cursorObj.y).toBe(10);
|
||||
// expect(starterSelectUiHandler.starterGens[0]).toBe(0);
|
||||
// expect(starterSelectUiHandler.starterCursors[0]).toBe(3);
|
||||
// expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
|
||||
// expect(starterSelectUiHandler.cursorObj.y).toBe(10);
|
||||
|
||||
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
|
||||
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
|
||||
@ -544,7 +550,6 @@ describe("UI - Starter select", () => {
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.RIGHT);
|
||||
handler.processInput(Button.DOWN);
|
||||
handler.processInput(Button.ACTION);
|
||||
game.phaseInterceptor.unlock();
|
||||
@ -575,10 +580,11 @@ describe("UI - Starter select", () => {
|
||||
});
|
||||
});
|
||||
|
||||
expect(starterSelectUiHandler.starterGens[0]).toBe(0);
|
||||
expect(starterSelectUiHandler.starterCursors[0]).toBe(12);
|
||||
expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
|
||||
expect(starterSelectUiHandler.cursorObj.y).toBe(28);
|
||||
expect(starterSelectUiHandler.starterSpecies.length).toBe(1);
|
||||
expect(starterSelectUiHandler.starterSpecies[0].generation).toBe(1);
|
||||
expect(starterSelectUiHandler.starterSpecies[0].speciesId).toBe(32);
|
||||
expect(starterSelectUiHandler.cursorObj.x).toBe(53);
|
||||
expect(starterSelectUiHandler.cursorObj.y).toBe(31);
|
||||
|
||||
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
|
||||
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
|
||||
|
@ -90,6 +90,10 @@ export default class MockSprite {
|
||||
return this.phaserSprite.setPosition(x, y);
|
||||
}
|
||||
|
||||
setRotation(radians) {
|
||||
return this.phaserSprite.setRotation(radians);
|
||||
}
|
||||
|
||||
stop() {
|
||||
return this.phaserSprite.stop();
|
||||
}
|
||||
|
253
src/ui/dropdown.ts
Normal file
@ -0,0 +1,253 @@
|
||||
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";
|
||||
|
||||
export enum DropDownState {
|
||||
ON = 0,
|
||||
OFF
|
||||
}
|
||||
|
||||
export enum DropDownType {
|
||||
MULTI = 0,
|
||||
SINGLE
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
constructor(scene: SceneBase, val: any, text: string, sprite?: Phaser.GameObjects.Sprite, state: DropDownState = DropDownState.ON) {
|
||||
super(scene);
|
||||
this.val = val;
|
||||
if (text) {
|
||||
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);
|
||||
}
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public setupToggle(type: DropDownType): void {
|
||||
if (type === DropDownType.MULTI) {
|
||||
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(state: DropDownState): DropDownState {
|
||||
this.state = state % 2;
|
||||
if (this.state === DropDownState.OFF) {
|
||||
this.toggle.setTint(0x272727);
|
||||
} else {
|
||||
this.toggle.setTint(0x55ff55);
|
||||
}
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public toggleOptionState(): DropDownState {
|
||||
return this.setOptionState(this.state + 1);
|
||||
}
|
||||
|
||||
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", "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(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;
|
||||
}
|
||||
option.toggle.x = cursorOffset + optionPaddingX + 3 + (type === DropDownType.MULTI ? 0 : 3);
|
||||
option.toggle.y = optionHeight / 2 + (type === DropDownType.MULTI ? 0 : 1);
|
||||
});
|
||||
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();
|
||||
|
||||
if (this.cursor === 0) {
|
||||
this.options.forEach((option, index) => {
|
||||
if (index !== this.cursor) {
|
||||
option.setOptionState(newState);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (this.checkForAllOff()) {
|
||||
this.options[0].setOptionState(DropDownState.OFF);
|
||||
} else if (this.checkForAllOn()) {
|
||||
this.options[0].setOptionState(DropDownState.ON);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.options[this.cursor].state === DropDownState.OFF) {
|
||||
this.options.forEach((option) => {
|
||||
option.setOptionState(DropDownState.OFF);
|
||||
option.setDirection(SortDirection.ASC);
|
||||
option.toggle.setVisible(false);
|
||||
});
|
||||
this.options[this.cursor].setOptionState(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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
} 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);
|
||||
}
|
||||
}
|
201
src/ui/filter-bar.ts
Normal file
@ -0,0 +1,201 @@
|
||||
import BattleScene from "#app/battle-scene.js";
|
||||
import { DropDown } from "./dropdown";
|
||||
import { StarterContainer } from "./starter-container";
|
||||
import { addTextObject, TextStyle } from "./text";
|
||||
import { addWindow, WindowVariant } from "./ui-theme";
|
||||
|
||||
export enum DropDownColumn {
|
||||
GEN,
|
||||
TYPES,
|
||||
UNLOCKS,
|
||||
WIN,
|
||||
SORT
|
||||
}
|
||||
|
||||
export class FilterBar extends Phaser.GameObjects.Container {
|
||||
private window: Phaser.GameObjects.NineSlice;
|
||||
public labels: Phaser.GameObjects.Text[] = [];
|
||||
public dropDowns: DropDown[] = [];
|
||||
public cursorObj: Phaser.GameObjects.Image;
|
||||
public numFilters: number = 0;
|
||||
public openDropDown: boolean = false;
|
||||
private lastCursor: number = -1;
|
||||
public defaultGenVals: any[] = [];
|
||||
public defaultTypeVals: any[] = [];
|
||||
public defaultUnlockVals: any[] = [];
|
||||
public defaultWinVals: any[] = [];
|
||||
public defaultSortVals: any[] = [];
|
||||
|
||||
constructor(scene: BattleScene, x: number, y: number, width: number, height: number) {
|
||||
super(scene, x, y);
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
this.window = addWindow(scene, 0, 0, width, height, false, false, null, null, WindowVariant.THIN);
|
||||
this.add(this.window);
|
||||
|
||||
this.cursorObj = this.scene.add.image(1, 1, "cursor");
|
||||
this.cursorObj.setScale(0.5);
|
||||
this.cursorObj.setVisible(false);
|
||||
this.cursorObj.setOrigin(0, 0);
|
||||
this.add(this.cursorObj);
|
||||
}
|
||||
|
||||
addFilter(text: string, dropDown: DropDown): void {
|
||||
const filterTypesLabel = addTextObject(this.scene, 0, 3, text, TextStyle.TOOLTIP_CONTENT);
|
||||
this.labels.push(filterTypesLabel);
|
||||
this.add(filterTypesLabel);
|
||||
this.dropDowns.push(dropDown);
|
||||
this.add(dropDown);
|
||||
|
||||
this.calcFilterPositions();
|
||||
this.numFilters++;
|
||||
}
|
||||
|
||||
|
||||
updateFilterLabels(): void {
|
||||
const genVals = this.getVals(DropDownColumn.GEN);
|
||||
const typeVals = this.getVals(DropDownColumn.TYPES);
|
||||
const unlockVals = this.getVals(DropDownColumn.UNLOCKS);
|
||||
const winVals = this.getVals(DropDownColumn.WIN);
|
||||
const sortVals = this.getVals(DropDownColumn.SORT);
|
||||
|
||||
// onColor is Yellow, offColor is White
|
||||
const onColor = 0xffef5c;
|
||||
const offColor = 0xffffff;
|
||||
|
||||
// if genVals and defaultGenVals has same elements, set the label to White else set it to Green
|
||||
if (genVals.length === this.defaultGenVals.length && genVals.every((value, index) => value === this.defaultGenVals[index])) {
|
||||
this.labels[DropDownColumn.GEN].setTint(offColor);
|
||||
} else {
|
||||
this.labels[DropDownColumn.GEN].setTint(onColor);
|
||||
}
|
||||
|
||||
// if typeVals and defaultTypeVals has same elements, set the label to White else set it to Green
|
||||
if (typeVals.length === this.defaultTypeVals.length && typeVals.every((value, index) => value === this.defaultTypeVals[index])) {
|
||||
this.labels[DropDownColumn.TYPES].setTint(offColor);
|
||||
} else {
|
||||
this.labels[DropDownColumn.TYPES].setTint(onColor);
|
||||
}
|
||||
|
||||
// if unlockVals and defaultUnlockVals has same elements, set the label to White else set it to Green
|
||||
if (unlockVals.length === this.defaultUnlockVals.length && unlockVals.every((value, index) => value === this.defaultUnlockVals[index])) {
|
||||
this.labels[DropDownColumn.UNLOCKS].setTint(offColor);
|
||||
} else {
|
||||
this.labels[DropDownColumn.UNLOCKS].setTint(onColor);
|
||||
}
|
||||
|
||||
// if winVals and defaultWinVals has same elements, set the label to White else set it to Green
|
||||
if (winVals.length === this.defaultWinVals.length && winVals.every((value, index) => value === this.defaultWinVals[index])) {
|
||||
this.labels[DropDownColumn.WIN].setTint(offColor);
|
||||
} else {
|
||||
this.labels[DropDownColumn.WIN].setTint(onColor);
|
||||
}
|
||||
|
||||
// if sortVals and defaultSortVals has same value and dir, set the label to White else set it to Green
|
||||
if (sortVals[0]["dir"] === this.defaultSortVals[0]["dir"] && sortVals[0]["val"] === this.defaultSortVals[0]["val"]) {
|
||||
this.labels[DropDownColumn.SORT].setTint(offColor);
|
||||
} else {
|
||||
this.labels[DropDownColumn.SORT].setTint(onColor);
|
||||
}
|
||||
}
|
||||
|
||||
calcFilterPositions(): void {
|
||||
const paddingX = 6;
|
||||
const cursorOffset = 8;
|
||||
|
||||
// position labels with even space across the width of the container
|
||||
let totalWidth = paddingX * 2 + cursorOffset;
|
||||
this.labels.forEach(label => {
|
||||
totalWidth += label.displayWidth + cursorOffset;
|
||||
});
|
||||
const spacing = (this.width - totalWidth) / (this.labels.length - 1);
|
||||
for (let i=0; i<this.labels.length; i++) {
|
||||
if (i === 0) {
|
||||
this.labels[i].x = paddingX + cursorOffset;
|
||||
} else {
|
||||
const lastRight = this.labels[i-1].x + this.labels[i-1].displayWidth;
|
||||
this.labels[i].x = lastRight + spacing + cursorOffset;
|
||||
}
|
||||
|
||||
this.dropDowns[i].x = this.labels[i].x - cursorOffset - paddingX;
|
||||
this.dropDowns[i].y = this.height;
|
||||
}
|
||||
}
|
||||
|
||||
setCursor(cursor: number): void {
|
||||
if (this.lastCursor > -1) {
|
||||
if (this.dropDowns[this.lastCursor].visible) {
|
||||
this.dropDowns[this.lastCursor].setVisible(false);
|
||||
this.dropDowns[cursor].setVisible(true);
|
||||
this.dropDowns[cursor].setCursor(0);
|
||||
}
|
||||
}
|
||||
|
||||
const cursorOffset = 8;
|
||||
this.cursorObj.setPosition(this.labels[cursor].x - cursorOffset + 2, 6);
|
||||
this.lastCursor = cursor;
|
||||
}
|
||||
|
||||
toggleDropDown(index: number): void {
|
||||
this.dropDowns[index].toggle();
|
||||
this.openDropDown = this.dropDowns[index].visible;
|
||||
this.dropDowns[index].setCursor(0);
|
||||
}
|
||||
|
||||
hideDropDowns(): void {
|
||||
this.dropDowns.forEach(dropDown => {
|
||||
dropDown.setVisible(false);
|
||||
});
|
||||
this.openDropDown = false;
|
||||
}
|
||||
|
||||
incDropDownCursor(): boolean {
|
||||
if (this.dropDowns[this.lastCursor].cursor === this.dropDowns[this.lastCursor].options.length - 1) {// if at the bottom of the list, wrap around
|
||||
return this.dropDowns[this.lastCursor].setCursor(0);
|
||||
} else {
|
||||
return this.dropDowns[this.lastCursor].setCursor(this.dropDowns[this.lastCursor].cursor + 1);
|
||||
}
|
||||
}
|
||||
|
||||
decDropDownCursor(): boolean {
|
||||
if (this.dropDowns[this.lastCursor].cursor === 0) {// if at the top of the list, wrap around
|
||||
return this.dropDowns[this.lastCursor].setCursor(this.dropDowns[this.lastCursor].options.length - 1);
|
||||
} else {
|
||||
return this.dropDowns[this.lastCursor].setCursor(this.dropDowns[this.lastCursor].cursor - 1);
|
||||
}
|
||||
}
|
||||
|
||||
toggleOptionState(): void {
|
||||
this.dropDowns[this.lastCursor].toggleOptionState();
|
||||
}
|
||||
|
||||
getVals(col: DropDownColumn): any[] {
|
||||
return this.dropDowns[col].getVals();
|
||||
}
|
||||
|
||||
getNearestFilter(container: StarterContainer): number {
|
||||
// find the nearest filter to the x position
|
||||
const midx = container.x + container.icon.displayWidth / 2;
|
||||
let nearest = 0;
|
||||
let nearestDist = 1000;
|
||||
for (let i=0; i < this.labels.length; i++) {
|
||||
const dist = Math.abs(midx - (this.labels[i].x + this.labels[i].displayWidth / 3));
|
||||
if (dist < nearestDist) {
|
||||
nearest = i;
|
||||
nearestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
|
||||
getLastFilterX(): number {
|
||||
return this.labels[this.lastCursor].x + this.labels[this.lastCursor].displayWidth / 2;
|
||||
}
|
||||
|
||||
isFilterActive(index: number) {
|
||||
return this.dropDowns[index].isActive();
|
||||
}
|
||||
}
|
36
src/ui/scroll-bar.ts
Normal file
@ -0,0 +1,36 @@
|
||||
export class ScrollBar extends Phaser.GameObjects.Container {
|
||||
private bg: Phaser.GameObjects.Image;
|
||||
private handleBody: Phaser.GameObjects.Rectangle;
|
||||
private handleBottom: Phaser.GameObjects.Image;
|
||||
private pages: number;
|
||||
private page: number;
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, pages: number) {
|
||||
super(scene, x, y);
|
||||
|
||||
this.bg = scene.add.image(0, 0, "scroll_bar");
|
||||
this.bg.setOrigin(0, 0);
|
||||
this.add(this.bg);
|
||||
|
||||
this.handleBody = scene.add.rectangle(1, 1, 3, 4, 0xaaaaaa);
|
||||
this.handleBody.setOrigin(0, 0);
|
||||
this.add(this.handleBody);
|
||||
|
||||
this.handleBottom = scene.add.image(1, 1, "scroll_bar_handle");
|
||||
this.handleBottom.setOrigin(0, 0);
|
||||
this.add(this.handleBottom);
|
||||
}
|
||||
|
||||
setPage(page: number): void {
|
||||
this.page = page;
|
||||
this.handleBody.y = 1 + (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.pages * page;
|
||||
this.handleBottom.y = this.handleBody.y + this.handleBody.displayHeight;
|
||||
}
|
||||
|
||||
setPages(pages: number): void {
|
||||
this.pages = pages;
|
||||
this.handleBody.height = (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.pages;
|
||||
|
||||
this.setVisible(this.pages > 1);
|
||||
}
|
||||
}
|
101
src/ui/starter-container.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import BattleScene from "../battle-scene";
|
||||
import PokemonSpecies from "../data/pokemon-species";
|
||||
import { addTextObject, TextStyle } from "./text";
|
||||
|
||||
export class StarterContainer extends Phaser.GameObjects.Container {
|
||||
public scene: BattleScene;
|
||||
public species: PokemonSpecies;
|
||||
public icon: Phaser.GameObjects.Sprite;
|
||||
public shinyIcons: Phaser.GameObjects.Image[] = [];
|
||||
public label: Phaser.GameObjects.Text;
|
||||
public starterPassiveBgs: Phaser.GameObjects.Image;
|
||||
public hiddenAbilityIcon: Phaser.GameObjects.Image;
|
||||
public classicWinIcon: Phaser.GameObjects.Image;
|
||||
public candyUpgradeIcon: Phaser.GameObjects.Image;
|
||||
public candyUpgradeOverlayIcon: Phaser.GameObjects.Image;
|
||||
public cost: number = 0;
|
||||
|
||||
constructor(scene: BattleScene, species: PokemonSpecies) {
|
||||
super(scene, 0, 0);
|
||||
|
||||
this.species = species;
|
||||
|
||||
const defaultDexAttr = scene.gameData.getSpeciesDefaultDexAttr(species, false, true);
|
||||
const defaultProps = scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
||||
|
||||
// starter passive bg
|
||||
const starterPassiveBg = this.scene.add.image(2, 5, "passive_bg");
|
||||
starterPassiveBg.setOrigin(0, 0);
|
||||
starterPassiveBg.setScale(0.75);
|
||||
starterPassiveBg.setVisible(false);
|
||||
this.add(starterPassiveBg);
|
||||
this.starterPassiveBgs = starterPassiveBg;
|
||||
|
||||
// icon
|
||||
this.icon = this.scene.add.sprite(-2, 2, species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant));
|
||||
this.icon.setScale(0.5);
|
||||
this.icon.setOrigin(0, 0);
|
||||
this.icon.setFrame(species.getIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant));
|
||||
this.checkIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant);
|
||||
this.icon.setTint(0);
|
||||
this.add(this.icon);
|
||||
|
||||
// shiny icons
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const shinyIcon = this.scene.add.image(i * -3 + 12, 2, "shiny_star_small");
|
||||
shinyIcon.setScale(0.5);
|
||||
shinyIcon.setOrigin(0, 0);
|
||||
shinyIcon.setVisible(false);
|
||||
this.shinyIcons.push(shinyIcon);
|
||||
}
|
||||
this.add(this.shinyIcons);
|
||||
|
||||
// value label
|
||||
const label = addTextObject(this.scene, 1, 2, "0", TextStyle.WINDOW, { fontSize: "32px" });
|
||||
label.setShadowOffset(2, 2);
|
||||
label.setOrigin(0, 0);
|
||||
label.setVisible(false);
|
||||
this.add(label);
|
||||
this.label = label;
|
||||
|
||||
// hidden ability icon
|
||||
const abilityIcon = this.scene.add.image(12, 7, "ha_capsule");
|
||||
abilityIcon.setOrigin(0, 0);
|
||||
abilityIcon.setScale(0.5);
|
||||
abilityIcon.setVisible(false);
|
||||
this.add(abilityIcon);
|
||||
this.hiddenAbilityIcon = abilityIcon;
|
||||
|
||||
// classic win icon
|
||||
const classicWinIcon = this.scene.add.image(2, 12, "champion_ribbon");
|
||||
classicWinIcon.setOrigin(0, 0);
|
||||
classicWinIcon.setScale(0.5);
|
||||
classicWinIcon.setVisible(false);
|
||||
this.add(classicWinIcon);
|
||||
this.classicWinIcon = classicWinIcon;
|
||||
|
||||
// candy upgrade icon
|
||||
const candyUpgradeIcon = this.scene.add.image(12, 12, "candy");
|
||||
candyUpgradeIcon.setOrigin(0, 0);
|
||||
candyUpgradeIcon.setScale(0.25);
|
||||
candyUpgradeIcon.setVisible(false);
|
||||
this.add(candyUpgradeIcon);
|
||||
this.candyUpgradeIcon = candyUpgradeIcon;
|
||||
|
||||
// candy upgrade overlay icon
|
||||
const candyUpgradeOverlayIcon = this.scene.add.image(12, 12, "candy_overlay");
|
||||
candyUpgradeOverlayIcon.setOrigin(0, 0);
|
||||
candyUpgradeOverlayIcon.setScale(0.25);
|
||||
candyUpgradeOverlayIcon.setVisible(false);
|
||||
this.add(candyUpgradeOverlayIcon);
|
||||
this.candyUpgradeOverlayIcon = candyUpgradeOverlayIcon;
|
||||
}
|
||||
|
||||
checkIconId(female, formIndex, shiny, variant) {
|
||||
if (this.icon.frame.name !== this.species.getIconId(female, formIndex, shiny, variant)) {
|
||||
console.log(`${this.species.name}'s variant icon does not exist. Replacing with default.`);
|
||||
this.icon.setTexture(this.species.getIconAtlasKey(formIndex, false, variant));
|
||||
this.icon.setFrame(this.species.getIconId(female, formIndex, false, variant));
|
||||
}
|
||||
}
|
||||
}
|