mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-25 16:26:25 +00:00
[UI] Make Egg List and Egg Summary scrollable (#4391)
This commit is contained in:
parent
06331ccdf6
commit
a25ccbcde6
@ -1,16 +1,21 @@
|
|||||||
import BattleScene from "../battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { Mode } from "./ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler";
|
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler";
|
||||||
import { TextStyle, addTextObject } from "./text";
|
import { TextStyle, addTextObject } from "#app/ui/text";
|
||||||
import MessageUiHandler from "./message-ui-handler";
|
import MessageUiHandler from "#app/ui/message-ui-handler";
|
||||||
import { Egg } from "../data/egg";
|
import { addWindow } from "#app/ui/ui-theme";
|
||||||
import { addWindow } from "./ui-theme";
|
|
||||||
import {Button} from "#enums/buttons";
|
import {Button} from "#enums/buttons";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import ScrollableGridUiHandler from "#app/ui/scrollable-grid-handler";
|
||||||
|
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||||
|
|
||||||
export default class EggListUiHandler extends MessageUiHandler {
|
export default class EggListUiHandler extends MessageUiHandler {
|
||||||
|
private readonly ROWS = 9;
|
||||||
|
private readonly COLUMNS = 11;
|
||||||
|
|
||||||
private eggListContainer: Phaser.GameObjects.Container;
|
private eggListContainer: Phaser.GameObjects.Container;
|
||||||
private eggListIconContainer: Phaser.GameObjects.Container;
|
private eggListIconContainer: Phaser.GameObjects.Container;
|
||||||
|
private eggIcons: Phaser.GameObjects.Sprite[];
|
||||||
private eggSprite: Phaser.GameObjects.Sprite;
|
private eggSprite: Phaser.GameObjects.Sprite;
|
||||||
private eggNameText: Phaser.GameObjects.Text;
|
private eggNameText: Phaser.GameObjects.Text;
|
||||||
private eggDateText: Phaser.GameObjects.Text;
|
private eggDateText: Phaser.GameObjects.Text;
|
||||||
@ -19,6 +24,7 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||||||
private eggListMessageBoxContainer: Phaser.GameObjects.Container;
|
private eggListMessageBoxContainer: Phaser.GameObjects.Container;
|
||||||
|
|
||||||
private cursorObj: Phaser.GameObjects.Image;
|
private cursorObj: Phaser.GameObjects.Image;
|
||||||
|
private scrollGridHandler : ScrollableGridUiHandler;
|
||||||
|
|
||||||
private iconAnimHandler: PokemonIconAnimHandler;
|
private iconAnimHandler: PokemonIconAnimHandler;
|
||||||
|
|
||||||
@ -64,7 +70,7 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||||||
this.eggGachaInfoText.setWordWrapWidth(540);
|
this.eggGachaInfoText.setWordWrapWidth(540);
|
||||||
this.eggListContainer.add(this.eggGachaInfoText);
|
this.eggListContainer.add(this.eggGachaInfoText);
|
||||||
|
|
||||||
this.eggListIconContainer = this.scene.add.container(115, 9);
|
this.eggListIconContainer = this.scene.add.container(113, 5);
|
||||||
this.eggListContainer.add(this.eggListIconContainer);
|
this.eggListContainer.add(this.eggListIconContainer);
|
||||||
|
|
||||||
this.cursorObj = this.scene.add.image(0, 0, "select_cursor");
|
this.cursorObj = this.scene.add.image(0, 0, "select_cursor");
|
||||||
@ -74,6 +80,14 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||||||
this.eggSprite = this.scene.add.sprite(54, 37, "egg");
|
this.eggSprite = this.scene.add.sprite(54, 37, "egg");
|
||||||
this.eggListContainer.add(this.eggSprite);
|
this.eggListContainer.add(this.eggSprite);
|
||||||
|
|
||||||
|
const scrollBar = new ScrollBar(this.scene, 310, 5, 4, 170, this.ROWS);
|
||||||
|
this.eggListContainer.add(scrollBar);
|
||||||
|
|
||||||
|
this.scrollGridHandler = new ScrollableGridUiHandler(this, this.ROWS, this.COLUMNS)
|
||||||
|
.withScrollBar(scrollBar)
|
||||||
|
.withUpdateGridCallBack(() => this.updateEggIcons())
|
||||||
|
.withUpdateSingleElementCallback((i:number) => this.setEggDetails(i));
|
||||||
|
|
||||||
this.eggListMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6);
|
this.eggListMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6);
|
||||||
this.eggListMessageBoxContainer.setVisible(false);
|
this.eggListMessageBoxContainer.setVisible(false);
|
||||||
this.eggListContainer.add(this.eggListMessageBoxContainer);
|
this.eggListContainer.add(this.eggListMessageBoxContainer);
|
||||||
@ -92,76 +106,63 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||||||
show(args: any[]): boolean {
|
show(args: any[]): boolean {
|
||||||
super.show(args);
|
super.show(args);
|
||||||
|
|
||||||
|
this.initEggIcons();
|
||||||
|
|
||||||
this.getUi().bringToTop(this.eggListContainer);
|
this.getUi().bringToTop(this.eggListContainer);
|
||||||
|
|
||||||
this.eggListContainer.setVisible(true);
|
this.eggListContainer.setVisible(true);
|
||||||
|
|
||||||
let e = 0;
|
this.scrollGridHandler.setTotalElements(this.scene.gameData.eggs.length);
|
||||||
|
|
||||||
for (const egg of this.scene.gameData.eggs) {
|
|
||||||
const x = (e % 11) * 18;
|
|
||||||
const y = Math.floor(e / 11) * 18;
|
|
||||||
const icon = this.scene.add.sprite(x - 2, y + 2, "egg_icons");
|
|
||||||
icon.setScale(0.5);
|
|
||||||
icon.setOrigin(0, 0);
|
|
||||||
icon.setFrame(egg.getKey());
|
|
||||||
this.eggListIconContainer.add(icon);
|
|
||||||
this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.NONE);
|
|
||||||
e++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.updateEggIcons();
|
||||||
this.setCursor(0);
|
this.setCursor(0);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
processInput(button: Button): boolean {
|
/**
|
||||||
const ui = this.getUi();
|
* Create the grid of egg icons to display
|
||||||
|
*/
|
||||||
|
private initEggIcons() {
|
||||||
|
this.eggIcons = [];
|
||||||
|
for (let i = 0; i < Math.min(this.ROWS * this.COLUMNS, this.scene.gameData.eggs.length); i++) {
|
||||||
|
const x = (i % this.COLUMNS) * 18;
|
||||||
|
const y = Math.floor(i / this.COLUMNS) * 18;
|
||||||
|
const icon = this.scene.add.sprite(x - 2, y + 2, "egg_icons");
|
||||||
|
icon.setScale(0.5);
|
||||||
|
icon.setOrigin(0, 0);
|
||||||
|
this.eggListIconContainer.add(icon);
|
||||||
|
this.eggIcons.push(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let success = false;
|
/**
|
||||||
const error = false;
|
* Show the grid of egg icons
|
||||||
|
*/
|
||||||
|
private updateEggIcons() {
|
||||||
|
const indexOffset = this.scrollGridHandler.getItemOffset();
|
||||||
|
const eggsToShow = Math.min(this.eggIcons.length, this.scene.gameData.eggs.length - indexOffset);
|
||||||
|
|
||||||
if (button === Button.CANCEL) {
|
this.eggIcons.forEach((icon, i) => {
|
||||||
ui.revertMode();
|
if (i !== this.cursor) {
|
||||||
success = true;
|
this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.NONE);
|
||||||
|
}
|
||||||
|
if (i < eggsToShow) {
|
||||||
|
const egg = this.scene.gameData.eggs[i + indexOffset];
|
||||||
|
icon.setFrame(egg.getKey());
|
||||||
|
icon.setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
const eggCount = this.eggListIconContainer.getAll().length;
|
icon.setVisible(false);
|
||||||
const rows = Math.ceil(eggCount / 11);
|
|
||||||
const row = Math.floor(this.cursor / 11);
|
|
||||||
switch (button) {
|
|
||||||
case Button.UP:
|
|
||||||
if (row) {
|
|
||||||
success = this.setCursor(this.cursor - 11);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Button.DOWN:
|
|
||||||
if (row < rows - 2 || (row < rows - 1 && this.cursor % 11 <= (eggCount - 1) % 11)) {
|
|
||||||
success = this.setCursor(this.cursor + 11);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Button.LEFT:
|
|
||||||
if (this.cursor % 11) {
|
|
||||||
success = this.setCursor(this.cursor - 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Button.RIGHT:
|
|
||||||
if (this.cursor % 11 < (row < rows - 1 ? 10 : (eggCount - 1) % 11)) {
|
|
||||||
success = this.setCursor(this.cursor + 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
/**
|
||||||
ui.playSelect();
|
* Update the information panel with the information of the given egg
|
||||||
} else if (error) {
|
* @param index which egg in the list to display the info for
|
||||||
ui.playError();
|
*/
|
||||||
}
|
private setEggDetails(index: number): void {
|
||||||
|
const egg = this.scene.gameData.eggs[index];
|
||||||
return success || error;
|
|
||||||
}
|
|
||||||
|
|
||||||
setEggDetails(egg: Egg): void {
|
|
||||||
this.eggSprite.setFrame(`egg_${egg.getKey()}`);
|
this.eggSprite.setFrame(`egg_${egg.getKey()}`);
|
||||||
this.eggNameText.setText(`${i18next.t("egg:egg")} (${egg.getEggDescriptor()})`);
|
this.eggNameText.setText(`${i18next.t("egg:egg")} (${egg.getEggDescriptor()})`);
|
||||||
this.eggDateText.setText(
|
this.eggDateText.setText(
|
||||||
@ -176,7 +177,29 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||||||
this.eggGachaInfoText.setText(egg.getEggTypeDescriptor(this.scene));
|
this.eggGachaInfoText.setText(egg.getEggTypeDescriptor(this.scene));
|
||||||
}
|
}
|
||||||
|
|
||||||
setCursor(cursor: integer): boolean {
|
processInput(button: Button): boolean {
|
||||||
|
const ui = this.getUi();
|
||||||
|
|
||||||
|
let success = false;
|
||||||
|
const error = false;
|
||||||
|
|
||||||
|
if (button === Button.CANCEL) {
|
||||||
|
ui.revertMode();
|
||||||
|
success = true;
|
||||||
|
} else {
|
||||||
|
success = this.scrollGridHandler.processInput(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
ui.playSelect();
|
||||||
|
} else if (error) {
|
||||||
|
ui.playError();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success || error;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCursor(cursor: number): boolean {
|
||||||
let changed = false;
|
let changed = false;
|
||||||
|
|
||||||
const lastCursor = this.cursor;
|
const lastCursor = this.cursor;
|
||||||
@ -184,14 +207,15 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||||||
changed = super.setCursor(cursor);
|
changed = super.setCursor(cursor);
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this.cursorObj.setPosition(114 + 18 * (cursor % 11), 10 + 18 * Math.floor(cursor / 11));
|
const icon = this.eggIcons[cursor];
|
||||||
|
this.cursorObj.setPositionRelative(icon, 114, 5);
|
||||||
|
|
||||||
if (lastCursor > -1) {
|
if (lastCursor > -1) {
|
||||||
this.iconAnimHandler.addOrUpdate(this.eggListIconContainer.getAt(lastCursor) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.NONE);
|
this.iconAnimHandler.addOrUpdate(this.eggIcons[lastCursor], PokemonIconAnimMode.NONE);
|
||||||
}
|
}
|
||||||
this.iconAnimHandler.addOrUpdate(this.eggListIconContainer.getAt(cursor) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.ACTIVE);
|
this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.ACTIVE);
|
||||||
|
|
||||||
this.setEggDetails(this.scene.gameData.eggs[cursor]);
|
this.setEggDetails(cursor + this.scrollGridHandler.getItemOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
return changed;
|
return changed;
|
||||||
@ -199,9 +223,11 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
super.clear();
|
super.clear();
|
||||||
|
this.scrollGridHandler.reset();
|
||||||
this.cursor = -1;
|
this.cursor = -1;
|
||||||
this.eggListContainer.setVisible(false);
|
this.eggListContainer.setVisible(false);
|
||||||
this.iconAnimHandler.removeAll();
|
this.iconAnimHandler.removeAll();
|
||||||
this.eggListIconContainer.removeAll(true);
|
this.eggListIconContainer.removeAll(true);
|
||||||
|
this.eggIcons = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,17 @@ import { Mode } from "./ui";
|
|||||||
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler";
|
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler";
|
||||||
import MessageUiHandler from "./message-ui-handler";
|
import MessageUiHandler from "./message-ui-handler";
|
||||||
import { getEggTierForSpecies } from "../data/egg";
|
import { getEggTierForSpecies } from "../data/egg";
|
||||||
import {Button} from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import { Gender } from "#app/data/gender";
|
|
||||||
import { getVariantTint } from "#app/data/variant";
|
|
||||||
import { EggTier } from "#app/enums/egg-type";
|
|
||||||
import PokemonHatchInfoContainer from "./pokemon-hatch-info-container";
|
import PokemonHatchInfoContainer from "./pokemon-hatch-info-container";
|
||||||
import { EggSummaryPhase } from "#app/phases/egg-summary-phase";
|
import { EggSummaryPhase } from "#app/phases/egg-summary-phase";
|
||||||
import { DexAttr } from "#app/system/game-data";
|
|
||||||
import { EggHatchData } from "#app/data/egg-hatch-data";
|
import { EggHatchData } from "#app/data/egg-hatch-data";
|
||||||
|
import ScrollableGridUiHandler from "./scrollable-grid-handler";
|
||||||
|
import { HatchedPokemonContainer } from "./hatched-pokemon-container";
|
||||||
|
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||||
|
|
||||||
const iconContainerX = 115;
|
const iconContainerX = 112;
|
||||||
const iconContainerY = 9;
|
const iconContainerY = 9;
|
||||||
|
const numRows = 9;
|
||||||
const numCols = 11;
|
const numCols = 11;
|
||||||
const iconSize = 18;
|
const iconSize = 18;
|
||||||
|
|
||||||
@ -27,20 +27,20 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||||||
private eggHatchContainer: Phaser.GameObjects.Container;
|
private eggHatchContainer: Phaser.GameObjects.Container;
|
||||||
/** holds the icon containers and info container */
|
/** holds the icon containers and info container */
|
||||||
private summaryContainer: Phaser.GameObjects.Container;
|
private summaryContainer: Phaser.GameObjects.Container;
|
||||||
/** container for the mini pokemon sprites */
|
/** container for the each pokemon sprites and icons */
|
||||||
private pokemonIconSpritesContainer: Phaser.GameObjects.Container;
|
|
||||||
/** container for the icons displayed on top of the mini pokemon sprites (e.g. shiny, HA capsule) */
|
|
||||||
private pokemonIconsContainer: Phaser.GameObjects.Container;
|
private pokemonIconsContainer: Phaser.GameObjects.Container;
|
||||||
/** container for the elements displayed behind the mini pokemon sprites (e.g. egg rarity bg) */
|
/** list of the containers added to pokemonIconsContainer for easier access */
|
||||||
private pokemonBackgroundContainer: Phaser.GameObjects.Container;
|
private pokemonContainers: HatchedPokemonContainer[];
|
||||||
|
|
||||||
/** hatch info container that displays the current pokemon / hatch (main element on left hand side) */
|
/** hatch info container that displays the current pokemon / hatch (main element on left hand side) */
|
||||||
private infoContainer: PokemonHatchInfoContainer;
|
private infoContainer: PokemonHatchInfoContainer;
|
||||||
/** handles jumping animations for the pokemon sprite icons */
|
/** handles jumping animations for the pokemon sprite icons */
|
||||||
private iconAnimHandler: PokemonIconAnimHandler;
|
private iconAnimHandler: PokemonIconAnimHandler;
|
||||||
private eggHatchBg: Phaser.GameObjects.Image;
|
private eggHatchBg: Phaser.GameObjects.Image;
|
||||||
private cursorObj: Phaser.GameObjects.Image;
|
|
||||||
private eggHatchData: EggHatchData[];
|
private eggHatchData: EggHatchData[];
|
||||||
|
|
||||||
|
private scrollGridHandler : ScrollableGridUiHandler;
|
||||||
|
private cursorObj: Phaser.GameObjects.Image;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows subscribers to listen for events
|
* Allows subscribers to listen for events
|
||||||
@ -54,7 +54,6 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||||||
super(scene, Mode.EGG_HATCH_SUMMARY);
|
super(scene, Mode.EGG_HATCH_SUMMARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const ui = this.getUi();
|
const ui = this.getUi();
|
||||||
|
|
||||||
@ -77,11 +76,8 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||||||
this.cursorObj.setOrigin(0, 0);
|
this.cursorObj.setOrigin(0, 0);
|
||||||
this.summaryContainer.add(this.cursorObj);
|
this.summaryContainer.add(this.cursorObj);
|
||||||
|
|
||||||
this.pokemonIconSpritesContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
this.pokemonContainers = [];
|
||||||
this.pokemonIconsContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
this.pokemonIconsContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
||||||
this.pokemonBackgroundContainer = this.scene.add.container(iconContainerX, iconContainerY);
|
|
||||||
this.summaryContainer.add(this.pokemonBackgroundContainer);
|
|
||||||
this.summaryContainer.add(this.pokemonIconSpritesContainer);
|
|
||||||
this.summaryContainer.add(this.pokemonIconsContainer);
|
this.summaryContainer.add(this.pokemonIconsContainer);
|
||||||
|
|
||||||
this.infoContainer = new PokemonHatchInfoContainer(this.scene, this.summaryContainer);
|
this.infoContainer = new PokemonHatchInfoContainer(this.scene, this.summaryContainer);
|
||||||
@ -90,16 +86,24 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||||||
this.infoContainer.setVisible(true);
|
this.infoContainer.setVisible(true);
|
||||||
this.summaryContainer.add(this.infoContainer);
|
this.summaryContainer.add(this.infoContainer);
|
||||||
|
|
||||||
|
const scrollBar = new ScrollBar(this.scene, iconContainerX + numCols * iconSize, iconContainerY + 3, 4, this.scene.game.canvas.height / 6 - 20, numRows);
|
||||||
|
this.summaryContainer.add(scrollBar);
|
||||||
|
|
||||||
|
this.scrollGridHandler = new ScrollableGridUiHandler(this, numRows, numCols)
|
||||||
|
.withScrollBar(scrollBar)
|
||||||
|
.withUpdateGridCallBack(() => this.updatePokemonIcons())
|
||||||
|
.withUpdateSingleElementCallback((i: number) => this.infoContainer.showHatchInfo(this.eggHatchData[i]));
|
||||||
|
|
||||||
this.cursor = -1;
|
this.cursor = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
super.clear();
|
super.clear();
|
||||||
this.cursor = -1;
|
this.cursor = -1;
|
||||||
|
this.scrollGridHandler.reset();
|
||||||
this.summaryContainer.setVisible(false);
|
this.summaryContainer.setVisible(false);
|
||||||
this.pokemonIconSpritesContainer.removeAll(true);
|
|
||||||
this.pokemonIconsContainer.removeAll(true);
|
this.pokemonIconsContainer.removeAll(true);
|
||||||
this.pokemonBackgroundContainer.removeAll(true);
|
this.pokemonContainers = [];
|
||||||
this.eggHatchBg.setVisible(false);
|
this.eggHatchBg.setVisible(false);
|
||||||
this.getUi().hideTooltip();
|
this.getUi().hideTooltip();
|
||||||
|
|
||||||
@ -149,111 +153,51 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getUi().bringToTop(this.summaryContainer);
|
this.getUi().bringToTop(this.summaryContainer);
|
||||||
this.summaryContainer.setVisible(true);
|
this.summaryContainer.setVisible(true);
|
||||||
this.eggHatchContainer.setVisible(true);
|
this.eggHatchContainer.setVisible(true);
|
||||||
this.pokemonIconsContainer.setVisible(true);
|
|
||||||
this.eggHatchBg.setVisible(true);
|
this.eggHatchBg.setVisible(true);
|
||||||
this.infoContainer.hideDisplayPokemon();
|
this.infoContainer.hideDisplayPokemon();
|
||||||
|
|
||||||
this.eggHatchData.forEach( (value: EggHatchData, i: number) => {
|
this.scrollGridHandler.setTotalElements(this.eggHatchData.length);
|
||||||
const x = (i % numCols) * iconSize;
|
this.updatePokemonIcons();
|
||||||
const y = Math.floor(i / numCols) * iconSize;
|
|
||||||
|
|
||||||
const displayPokemon = value.pokemon;
|
|
||||||
const offset = 2;
|
|
||||||
const rightSideX = 12;
|
|
||||||
|
|
||||||
const rarityBg = this.scene.add.image(x + 2, y + 5, "passive_bg");
|
|
||||||
rarityBg.setOrigin(0, 0);
|
|
||||||
rarityBg.setScale(0.75);
|
|
||||||
rarityBg.setVisible(true);
|
|
||||||
this.pokemonBackgroundContainer.add(rarityBg);
|
|
||||||
|
|
||||||
// set tint for passive bg
|
|
||||||
switch (getEggTierForSpecies(displayPokemon.species)) {
|
|
||||||
case EggTier.COMMON:
|
|
||||||
rarityBg.setVisible(false);
|
|
||||||
break;
|
|
||||||
case EggTier.GREAT:
|
|
||||||
rarityBg.setTint(0xabafff);
|
|
||||||
break;
|
|
||||||
case EggTier.ULTRA:
|
|
||||||
rarityBg.setTint(0xffffaa);
|
|
||||||
break;
|
|
||||||
case EggTier.MASTER:
|
|
||||||
rarityBg.setTint(0xdfffaf);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const species = displayPokemon.species;
|
|
||||||
const female = displayPokemon.gender === Gender.FEMALE;
|
|
||||||
const formIndex = displayPokemon.formIndex;
|
|
||||||
const variant = displayPokemon.variant;
|
|
||||||
const isShiny = displayPokemon.shiny;
|
|
||||||
|
|
||||||
// set pokemon icon (and replace with base sprite if there is a mismatch)
|
|
||||||
const pokemonIcon = this.scene.add.sprite(x - offset, y + offset, species.getIconAtlasKey(formIndex, isShiny, variant));
|
|
||||||
pokemonIcon.setScale(0.5);
|
|
||||||
pokemonIcon.setOrigin(0, 0);
|
|
||||||
pokemonIcon.setFrame(species.getIconId(female, formIndex, isShiny, variant));
|
|
||||||
|
|
||||||
if (pokemonIcon.frame.name !== species.getIconId(female, formIndex, isShiny, variant)) {
|
|
||||||
console.log(`${species.name}'s variant icon does not exist. Replacing with default.`);
|
|
||||||
pokemonIcon.setTexture(species.getIconAtlasKey(formIndex, false, variant));
|
|
||||||
pokemonIcon.setFrame(species.getIconId(female, formIndex, false, variant));
|
|
||||||
}
|
|
||||||
this.pokemonIconSpritesContainer.add(pokemonIcon);
|
|
||||||
|
|
||||||
const shinyIcon = this.scene.add.image(x + rightSideX, y + offset, "shiny_star_small");
|
|
||||||
shinyIcon.setOrigin(0, 0);
|
|
||||||
shinyIcon.setScale(0.5);
|
|
||||||
shinyIcon.setVisible(displayPokemon.shiny);
|
|
||||||
shinyIcon.setTint(getVariantTint(displayPokemon.variant));
|
|
||||||
this.pokemonIconsContainer.add(shinyIcon);
|
|
||||||
|
|
||||||
const haIcon = this.scene.add.image(x + rightSideX, y + offset * 4, "ha_capsule");
|
|
||||||
haIcon.setOrigin(0, 0);
|
|
||||||
haIcon.setScale(0.5);
|
|
||||||
haIcon.setVisible(displayPokemon.abilityIndex === 2);
|
|
||||||
this.pokemonIconsContainer.add(haIcon);
|
|
||||||
|
|
||||||
const dexEntry = value.dexEntryBeforeUpdate;
|
|
||||||
const caughtAttr = dexEntry.caughtAttr;
|
|
||||||
const newShiny = BigInt(1 << (displayPokemon.shiny ? 1 : 0));
|
|
||||||
const newVariant = BigInt(1 << (displayPokemon.variant + 4));
|
|
||||||
const newShinyOrVariant = ((newShiny & caughtAttr) === BigInt(0)) || ((newVariant & caughtAttr) === BigInt(0));
|
|
||||||
const newForm = (BigInt(1 << displayPokemon.formIndex) * DexAttr.DEFAULT_FORM & caughtAttr) === BigInt(0);
|
|
||||||
|
|
||||||
const pokeballIcon = this.scene.add.image(x + rightSideX, y + offset * 7, "icon_owned");
|
|
||||||
pokeballIcon.setOrigin(0, 0);
|
|
||||||
pokeballIcon.setScale(0.5);
|
|
||||||
pokeballIcon.setVisible(!caughtAttr || newForm);
|
|
||||||
this.pokemonIconsContainer.add(pokeballIcon);
|
|
||||||
|
|
||||||
const eggMoveIcon = this.scene.add.image(x, y + offset, "icon_egg_move");
|
|
||||||
eggMoveIcon.setOrigin(0, 0);
|
|
||||||
eggMoveIcon.setScale(0.5);
|
|
||||||
eggMoveIcon.setVisible(value.eggMoveUnlocked);
|
|
||||||
this.pokemonIconsContainer.add(eggMoveIcon);
|
|
||||||
|
|
||||||
// add animation to the Pokemon sprite for new unlocks (new catch, new shiny or new form)
|
|
||||||
if (!caughtAttr || newShinyOrVariant || newForm) {
|
|
||||||
this.iconAnimHandler.addOrUpdate(pokemonIcon, PokemonIconAnimMode.PASSIVE);
|
|
||||||
} else {
|
|
||||||
this.iconAnimHandler.addOrUpdate(pokemonIcon, PokemonIconAnimMode.NONE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setCursor(0);
|
this.setCursor(0);
|
||||||
this.scene.playSoundWithoutBgm("evolution_fanfare");
|
this.scene.playSoundWithoutBgm("evolution_fanfare");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the grid of Pokemon icons
|
||||||
|
*/
|
||||||
|
private updatePokemonIcons(): void {
|
||||||
|
const itemOffset = this.scrollGridHandler.getItemOffset();
|
||||||
|
const eggsToShow = Math.min(numRows * numCols, this.eggHatchData.length - itemOffset);
|
||||||
|
|
||||||
|
for (let i = 0; i < numRows * numCols; i++) {
|
||||||
|
const hatchData = this.eggHatchData[i + itemOffset];
|
||||||
|
let hatchContainer = this.pokemonContainers[i];
|
||||||
|
|
||||||
|
if (i < eggsToShow) {
|
||||||
|
if (!hatchContainer) {
|
||||||
|
const x = (i % numCols) * iconSize;
|
||||||
|
const y = Math.floor(i / numCols) * iconSize;
|
||||||
|
hatchContainer = new HatchedPokemonContainer(this.scene, x, y, hatchData).setVisible(false);
|
||||||
|
this.pokemonContainers.push(hatchContainer);
|
||||||
|
this.pokemonIconsContainer.add(hatchContainer);
|
||||||
|
}
|
||||||
|
hatchContainer.setVisible(true);
|
||||||
|
hatchContainer.updateAndAnimate(hatchData, this.iconAnimHandler);
|
||||||
|
} else if (hatchContainer) {
|
||||||
|
hatchContainer.setVisible(false);
|
||||||
|
this.iconAnimHandler.addOrUpdate(hatchContainer.icon, PokemonIconAnimMode.NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
processInput(button: Button): boolean {
|
processInput(button: Button): boolean {
|
||||||
const ui = this.getUi();
|
const ui = this.getUi();
|
||||||
|
|
||||||
@ -266,31 +210,7 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
success = true;
|
success = true;
|
||||||
} else {
|
} else {
|
||||||
const count = this.eggHatchData.length;
|
this.scrollGridHandler.processInput(button);
|
||||||
const rows = Math.ceil(count / numCols);
|
|
||||||
const row = Math.floor(this.cursor / numCols);
|
|
||||||
switch (button) {
|
|
||||||
case Button.UP:
|
|
||||||
if (row) {
|
|
||||||
success = this.setCursor(this.cursor - numCols);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Button.DOWN:
|
|
||||||
if (row < rows - 2 || (row < rows - 1 && this.cursor % numCols <= (count - 1) % numCols)) {
|
|
||||||
success = this.setCursor(this.cursor + numCols);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Button.LEFT:
|
|
||||||
if (this.cursor % numCols) {
|
|
||||||
success = this.setCursor(this.cursor - 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Button.RIGHT:
|
|
||||||
if (this.cursor % numCols < (row < rows - 1 ? 10 : (count - 1) % numCols)) {
|
|
||||||
success = this.setCursor(this.cursor + 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -313,12 +233,11 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
|
|||||||
this.cursorObj.setPosition(iconContainerX - 1 + iconSize * (cursor % numCols), iconContainerY + 1 + iconSize * Math.floor(cursor / numCols));
|
this.cursorObj.setPosition(iconContainerX - 1 + iconSize * (cursor % numCols), iconContainerY + 1 + iconSize * Math.floor(cursor / numCols));
|
||||||
|
|
||||||
if (lastCursor > -1) {
|
if (lastCursor > -1) {
|
||||||
this.iconAnimHandler.addOrUpdate(this.pokemonIconSpritesContainer.getAt(lastCursor) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.NONE);
|
this.iconAnimHandler.addOrUpdate(this.pokemonContainers[lastCursor].icon, PokemonIconAnimMode.NONE);
|
||||||
}
|
}
|
||||||
this.iconAnimHandler.addOrUpdate(this.pokemonIconSpritesContainer.getAt(cursor) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.ACTIVE);
|
this.iconAnimHandler.addOrUpdate(this.pokemonContainers[cursor].icon, PokemonIconAnimMode.ACTIVE);
|
||||||
|
|
||||||
this.infoContainer.showHatchInfo(this.eggHatchData[cursor]);
|
|
||||||
|
|
||||||
|
this.infoContainer.showHatchInfo(this.eggHatchData[cursor + this.scrollGridHandler.getItemOffset()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return changed;
|
return changed;
|
||||||
|
136
src/ui/hatched-pokemon-container.ts
Normal file
136
src/ui/hatched-pokemon-container.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { EggHatchData } from "#app/data/egg-hatch-data";
|
||||||
|
import { Gender } from "#app/data/gender";
|
||||||
|
import { getVariantTint } from "#app/data/variant";
|
||||||
|
import { DexAttr } from "#app/system/game-data";
|
||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import PokemonSpecies from "#app/data/pokemon-species";
|
||||||
|
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container for a Pokemon's sprite and icons to get displayed in the egg summary screen
|
||||||
|
* Shows the Pokemon's sprite, surrounded by icons for:
|
||||||
|
* shiny variant, hidden ability, new egg move, new catch
|
||||||
|
*/
|
||||||
|
export class HatchedPokemonContainer extends Phaser.GameObjects.Container {
|
||||||
|
public scene: BattleScene;
|
||||||
|
public species: PokemonSpecies;
|
||||||
|
public icon: Phaser.GameObjects.Sprite;
|
||||||
|
public shinyIcon: Phaser.GameObjects.Image;
|
||||||
|
public hiddenAbilityIcon: Phaser.GameObjects.Image;
|
||||||
|
public pokeballIcon: Phaser.GameObjects.Image;
|
||||||
|
public eggMoveIcon: Phaser.GameObjects.Image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param scene the current {@linkcode BattleScene}
|
||||||
|
* @param x x position
|
||||||
|
* @param y y position
|
||||||
|
* @param hatchData the {@linkcode EggHatchData} to load the icons and sprites for
|
||||||
|
*/
|
||||||
|
constructor(scene: BattleScene, x: number, y: number, hatchData: EggHatchData) {
|
||||||
|
super(scene, x, y);
|
||||||
|
|
||||||
|
const displayPokemon = hatchData.pokemon;
|
||||||
|
this.species = displayPokemon.species;
|
||||||
|
|
||||||
|
const offset = 2;
|
||||||
|
const rightSideX = 12;
|
||||||
|
const species = displayPokemon.species;
|
||||||
|
const female = displayPokemon.gender === Gender.FEMALE;
|
||||||
|
const formIndex = displayPokemon.formIndex;
|
||||||
|
const variant = displayPokemon.variant;
|
||||||
|
const isShiny = displayPokemon.shiny;
|
||||||
|
|
||||||
|
// Pokemon sprite
|
||||||
|
const pokemonIcon = this.scene.add.sprite(-offset, offset, species.getIconAtlasKey(formIndex, isShiny, variant));
|
||||||
|
pokemonIcon.setScale(0.5);
|
||||||
|
pokemonIcon.setOrigin(0, 0);
|
||||||
|
pokemonIcon.setFrame(species.getIconId(female, formIndex, isShiny, variant));
|
||||||
|
this.icon = pokemonIcon;
|
||||||
|
this.checkIconId(female, formIndex, isShiny, variant);
|
||||||
|
this.add(this.icon);
|
||||||
|
|
||||||
|
// Shiny icon
|
||||||
|
this.shinyIcon = this.scene.add.image(rightSideX, offset, "shiny_star_small");
|
||||||
|
this.shinyIcon.setOrigin(0, 0);
|
||||||
|
this.shinyIcon.setScale(0.5);
|
||||||
|
this.add(this.shinyIcon);
|
||||||
|
|
||||||
|
// Hidden ability icon
|
||||||
|
const haIcon = this.scene.add.image(rightSideX, offset * 4, "ha_capsule");
|
||||||
|
haIcon.setOrigin(0, 0);
|
||||||
|
haIcon.setScale(0.5);
|
||||||
|
this.hiddenAbilityIcon = haIcon;
|
||||||
|
this.add(this.hiddenAbilityIcon);
|
||||||
|
|
||||||
|
// Pokeball icon
|
||||||
|
const pokeballIcon = this.scene.add.image(rightSideX, offset * 7, "icon_owned");
|
||||||
|
pokeballIcon.setOrigin(0, 0);
|
||||||
|
pokeballIcon.setScale(0.5);
|
||||||
|
this.pokeballIcon = pokeballIcon;
|
||||||
|
this.add(this.pokeballIcon);
|
||||||
|
|
||||||
|
// Egg move icon
|
||||||
|
const eggMoveIcon = this.scene.add.image(0, offset, "icon_egg_move");
|
||||||
|
eggMoveIcon.setOrigin(0, 0);
|
||||||
|
eggMoveIcon.setScale(0.5);
|
||||||
|
this.eggMoveIcon = eggMoveIcon;
|
||||||
|
this.add(this.eggMoveIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the Pokemon's sprite and icons based on new hatch data
|
||||||
|
* Animates the pokemon icon if it has a new form or shiny variant
|
||||||
|
*
|
||||||
|
* @param hatchData the {@linkcode EggHatchData} to base the icons on
|
||||||
|
* @param iconAnimHandler the {@linkcode PokemonIconAnimHandler} to use to animate the sprites
|
||||||
|
*/
|
||||||
|
updateAndAnimate(hatchData: EggHatchData, iconAnimHandler: PokemonIconAnimHandler) {
|
||||||
|
const displayPokemon = hatchData.pokemon;
|
||||||
|
this.species = displayPokemon.species;
|
||||||
|
|
||||||
|
const dexEntry = hatchData.dexEntryBeforeUpdate;
|
||||||
|
const caughtAttr = dexEntry.caughtAttr;
|
||||||
|
const newShiny = BigInt(1 << (displayPokemon.shiny ? 1 : 0));
|
||||||
|
const newVariant = BigInt(1 << (displayPokemon.variant + 4));
|
||||||
|
const newShinyOrVariant = ((newShiny & caughtAttr) === BigInt(0)) || ((newVariant & caughtAttr) === BigInt(0));
|
||||||
|
const newForm = (BigInt(1 << displayPokemon.formIndex) * DexAttr.DEFAULT_FORM & caughtAttr) === BigInt(0);
|
||||||
|
|
||||||
|
const female = displayPokemon.gender === Gender.FEMALE;
|
||||||
|
const formIndex = displayPokemon.formIndex;
|
||||||
|
const variant = displayPokemon.variant;
|
||||||
|
const isShiny = displayPokemon.shiny;
|
||||||
|
|
||||||
|
this.icon.setTexture(this.species.getIconAtlasKey(formIndex, isShiny, variant));
|
||||||
|
this.icon.setFrame(this.species.getIconId(female, formIndex, isShiny, variant));
|
||||||
|
this.checkIconId(female, formIndex, isShiny, variant);
|
||||||
|
|
||||||
|
this.shinyIcon.setVisible(displayPokemon.shiny);
|
||||||
|
this.shinyIcon.setTint(getVariantTint(displayPokemon.variant));
|
||||||
|
|
||||||
|
this.eggMoveIcon.setVisible(hatchData.eggMoveUnlocked);
|
||||||
|
this.hiddenAbilityIcon.setVisible(displayPokemon.abilityIndex === 2);
|
||||||
|
this.pokeballIcon.setVisible(!caughtAttr || newForm);
|
||||||
|
|
||||||
|
// add animation to the Pokemon sprite for new unlocks (new catch, new shiny or new form)
|
||||||
|
if (!caughtAttr || newShinyOrVariant || newForm) {
|
||||||
|
iconAnimHandler.addOrUpdate(this.icon, PokemonIconAnimMode.PASSIVE);
|
||||||
|
} else {
|
||||||
|
iconAnimHandler.addOrUpdate(this.icon, PokemonIconAnimMode.NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given Pokemon icon exists, otherwise replace it with a default one
|
||||||
|
* @param female `true` to get the female icon
|
||||||
|
* @param formIndex the form index
|
||||||
|
* @param shiny whether the Pokemon is shiny
|
||||||
|
* @param variant the shiny variant
|
||||||
|
*/
|
||||||
|
private checkIconId(female: boolean, formIndex: number, shiny: boolean, variant: number) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,8 @@ export class ScrollBar extends Phaser.GameObjects.Container {
|
|||||||
super(scene, x, y);
|
super(scene, x, y);
|
||||||
|
|
||||||
this.maxRows = maxRows;
|
this.maxRows = maxRows;
|
||||||
|
this.totalRows = maxRows;
|
||||||
|
this.currentRow = 0;
|
||||||
|
|
||||||
const borderSize = 2;
|
const borderSize = 2;
|
||||||
width = Math.max(width, 4);
|
width = Math.max(width, 4);
|
||||||
@ -46,8 +48,7 @@ export class ScrollBar extends Phaser.GameObjects.Container {
|
|||||||
*/
|
*/
|
||||||
setScrollCursor(scrollCursor: number): void {
|
setScrollCursor(scrollCursor: number): void {
|
||||||
this.currentRow = scrollCursor;
|
this.currentRow = scrollCursor;
|
||||||
this.handleBody.y = 1 + (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.totalRows * this.currentRow;
|
this.updateHandlePosition();
|
||||||
this.handleBottom.y = this.handleBody.y + this.handleBody.displayHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,7 +60,13 @@ export class ScrollBar extends Phaser.GameObjects.Container {
|
|||||||
setTotalRows(rows: number): void {
|
setTotalRows(rows: number): void {
|
||||||
this.totalRows = rows;
|
this.totalRows = rows;
|
||||||
this.handleBody.height = (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) * this.maxRows / this.totalRows;
|
this.handleBody.height = (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) * this.maxRows / this.totalRows;
|
||||||
|
this.updateHandlePosition();
|
||||||
|
|
||||||
this.setVisible(this.totalRows > this.maxRows);
|
this.setVisible(this.totalRows > this.maxRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateHandlePosition(): void {
|
||||||
|
this.handleBody.y = 1 + (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.totalRows * this.currentRow;
|
||||||
|
this.handleBottom.y = this.handleBody.y + this.handleBody.displayHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
197
src/ui/scrollable-grid-handler.ts
Normal file
197
src/ui/scrollable-grid-handler.ts
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import { Button } from "#enums/buttons";
|
||||||
|
import UiHandler from "#app/ui/ui-handler";
|
||||||
|
import { ScrollBar } from "#app/ui/scroll-bar";
|
||||||
|
|
||||||
|
type UpdateGridCallbackFunction = () => void;
|
||||||
|
type UpdateDetailsCallbackFunction = (index: number) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class to handle navigation through a grid of elements that can scroll vertically
|
||||||
|
* Needs to be used by a {@linkcode UiHandler}
|
||||||
|
* How to use:
|
||||||
|
* - in `UiHandler.setup`: Initialize with the {@linkcode UiHandler} that handles the grid,
|
||||||
|
* the number of rows and columns that can be shown at once,
|
||||||
|
* an optional {@linkcode ScrollBar}, and optional callbacks that will get called after scrolling
|
||||||
|
* - in `UiHandler.show`: Set `setTotalElements` to the total number of elements in the list to display
|
||||||
|
* - in `UiHandler.processInput`: call `processNavigationInput` to have it handle the cursor updates while calling the defined callbacks
|
||||||
|
* - in `UiHandler.clear`: call `reset`
|
||||||
|
*/
|
||||||
|
export default class ScrollableGridUiHandler {
|
||||||
|
private readonly ROWS: number;
|
||||||
|
private readonly COLUMNS: number;
|
||||||
|
private handler: UiHandler;
|
||||||
|
private totalElements: number;
|
||||||
|
private cursor: number;
|
||||||
|
private scrollCursor: number;
|
||||||
|
private scrollBar?: ScrollBar;
|
||||||
|
private updateGridCallback?: UpdateGridCallbackFunction;
|
||||||
|
private updateDetailsCallback?: UpdateDetailsCallbackFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param scene the {@linkcode UiHandler} that needs its cursor updated based on the scrolling
|
||||||
|
* @param rows the maximum number of rows shown at once
|
||||||
|
* @param columns the maximum number of columns shown at once
|
||||||
|
* @param updateGridCallback optional function that will get called if the whole grid needs to get updated
|
||||||
|
* @param updateDetailsCallback optional function that will get called if a single element's information needs to get updated
|
||||||
|
*/
|
||||||
|
constructor(handler: UiHandler, rows: number, columns: number) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.ROWS = rows;
|
||||||
|
this.COLUMNS = columns;
|
||||||
|
this.scrollCursor = 0;
|
||||||
|
this.cursor = 0;
|
||||||
|
this.totalElements = rows * columns; // default value for the number of elements
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a scrollBar to get updated with the scrolling
|
||||||
|
* @param scrollBar {@linkcode ScrollBar}
|
||||||
|
* @returns this
|
||||||
|
*/
|
||||||
|
withScrollBar(scrollBar: ScrollBar): ScrollableGridUiHandler {
|
||||||
|
this.scrollBar = scrollBar;
|
||||||
|
this.scrollBar.setTotalRows(Math.ceil(this.totalElements / this.COLUMNS));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set function that will get called if the whole grid needs to get updated
|
||||||
|
* @param callback {@linkcode UpdateGridCallbackFunction}
|
||||||
|
* @returns this
|
||||||
|
*/
|
||||||
|
withUpdateGridCallBack(callback: UpdateGridCallbackFunction): ScrollableGridUiHandler {
|
||||||
|
this.updateGridCallback = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set function that will get called if a single element in the grid needs to get updated
|
||||||
|
* @param callback {@linkcode UpdateDetailsCallbackFunction}
|
||||||
|
* @returns this
|
||||||
|
*/
|
||||||
|
withUpdateSingleElementCallback(callback: UpdateDetailsCallbackFunction): ScrollableGridUiHandler {
|
||||||
|
this.updateDetailsCallback = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param totalElements the total number of elements that the grid needs to display
|
||||||
|
*/
|
||||||
|
setTotalElements(totalElements: number) {
|
||||||
|
this.totalElements = totalElements;
|
||||||
|
if (this.scrollBar) {
|
||||||
|
this.scrollBar.setTotalRows(Math.ceil(this.totalElements / this.COLUMNS));
|
||||||
|
}
|
||||||
|
this.setScrollCursor(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns how many elements are hidden due to scrolling
|
||||||
|
*/
|
||||||
|
getItemOffset(): number {
|
||||||
|
return this.scrollCursor * this.COLUMNS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the cursor and scrollCursor based on user input
|
||||||
|
* @param button the button that was pressed
|
||||||
|
* @returns `true` if either the cursor or scrollCursor was updated
|
||||||
|
*/
|
||||||
|
processInput(button: Button): boolean {
|
||||||
|
let success = false;
|
||||||
|
const onScreenRows = Math.min(this.ROWS, Math.ceil(this.totalElements / this.COLUMNS));
|
||||||
|
const maxScrollCursor = Math.max(0, Math.ceil(this.totalElements / this.COLUMNS) - onScreenRows);
|
||||||
|
const currentRowIndex = Math.floor(this.cursor / this.COLUMNS);
|
||||||
|
const currentColumnIndex = this.cursor % this.COLUMNS;
|
||||||
|
const itemOffset = this.scrollCursor * this.COLUMNS;
|
||||||
|
const lastVisibleIndex = Math.min(this.totalElements - 1, this.totalElements - maxScrollCursor * this.COLUMNS - 1);
|
||||||
|
switch (button) {
|
||||||
|
case Button.UP:
|
||||||
|
if (currentRowIndex > 0) {
|
||||||
|
success = this.setCursor(this.cursor - this.COLUMNS);
|
||||||
|
} else if (this.scrollCursor > 0) {
|
||||||
|
success = this.setScrollCursor(this.scrollCursor - 1);
|
||||||
|
} else {
|
||||||
|
// wrap around to the last row
|
||||||
|
let newCursor = this.cursor + (onScreenRows - 1) * this.COLUMNS;
|
||||||
|
if (newCursor > lastVisibleIndex) {
|
||||||
|
newCursor -= this.COLUMNS;
|
||||||
|
}
|
||||||
|
success = this.setScrollCursor(maxScrollCursor, newCursor);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Button.DOWN:
|
||||||
|
if (currentRowIndex < onScreenRows - 1) {
|
||||||
|
// Go down one row
|
||||||
|
success = this.setCursor(Math.min(this.cursor + this.COLUMNS, this.totalElements - itemOffset - 1));
|
||||||
|
} else if (this.scrollCursor < maxScrollCursor) {
|
||||||
|
// Scroll down one row
|
||||||
|
success = this.setScrollCursor(this.scrollCursor + 1);
|
||||||
|
} else {
|
||||||
|
// Wrap around to the top row
|
||||||
|
success = this.setScrollCursor(0, this.cursor % this.COLUMNS);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Button.LEFT:
|
||||||
|
if (currentColumnIndex > 0) {
|
||||||
|
success = this.setCursor(this.cursor - 1);
|
||||||
|
} else if (this.scrollCursor === maxScrollCursor && currentRowIndex === onScreenRows - 1) {
|
||||||
|
success = this.setCursor(lastVisibleIndex);
|
||||||
|
} else {
|
||||||
|
success = this.setCursor(this.cursor + this.COLUMNS - 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Button.RIGHT:
|
||||||
|
if (currentColumnIndex < this.COLUMNS - 1 && this.cursor + itemOffset < this.totalElements - 1) {
|
||||||
|
success = this.setCursor(this.cursor + 1);
|
||||||
|
} else {
|
||||||
|
success = this.setCursor(this.cursor - currentColumnIndex);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the scrolling
|
||||||
|
*/
|
||||||
|
reset(): void {
|
||||||
|
this.setScrollCursor(0);
|
||||||
|
this.setCursor(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setCursor(cursor: number): boolean {
|
||||||
|
this.cursor = cursor;
|
||||||
|
return this.handler.setCursor(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setScrollCursor(scrollCursor: number, cursor?: number): boolean {
|
||||||
|
const scrollChanged = scrollCursor !== this.scrollCursor;
|
||||||
|
|
||||||
|
// update the scrolling cursor
|
||||||
|
if (scrollChanged) {
|
||||||
|
this.scrollCursor = scrollCursor;
|
||||||
|
if (this.scrollBar) {
|
||||||
|
this.scrollBar.setScrollCursor(scrollCursor);
|
||||||
|
}
|
||||||
|
if (this.updateGridCallback) {
|
||||||
|
this.updateGridCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cursorChanged = false;
|
||||||
|
const newElementIndex = this.cursor + this.scrollCursor * this.COLUMNS;
|
||||||
|
if (cursor !== undefined) {
|
||||||
|
cursorChanged = this.setCursor(cursor);
|
||||||
|
} else if (newElementIndex >= this.totalElements) {
|
||||||
|
// make sure the cursor does not go past the end of the list
|
||||||
|
cursorChanged = this.setCursor(this.totalElements - this.scrollCursor * this.COLUMNS - 1);
|
||||||
|
} else if (scrollChanged && this.updateDetailsCallback) {
|
||||||
|
// scroll was changed but not the normal cursor, update the selected element
|
||||||
|
this.updateDetailsCallback(newElementIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scrollChanged || cursorChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user