mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-03-10 11:58:30 +00:00
* [Hotfix] Fix interactions of some moves not changing types (#3183) * [Hotfix] Fix wild spawns not having their HA (#3190) * [Hotfix] Allow to hatch pokemon with Hidden Ability again (#3222) * chore: Update TNC links layout and position in index.html * chore: Update TNC links font size in index.css (#3230) * Move Touch Controls * ConfigToolbar alignment * Insert config toolbar on open, camel-case classes, hidden setting * Better toolbar styling, fixed double configToolbar bug * Fixed typedocs --------- Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt>
381 lines
14 KiB
TypeScript
381 lines
14 KiB
TypeScript
import TouchControl from "#app/touch-controls.js";
|
|
import UI from "#app/ui/ui.js";
|
|
import { Scene } from "phaser";
|
|
|
|
export const TOUCH_CONTROL_POSITIONS_LANDSCAPE = "touchControlPositionsLandscape";
|
|
export const TOUCH_CONTROL_POSITIONS_PORTRAIT = "touchControlPositionsPortrait";
|
|
|
|
|
|
|
|
type ControlPosition = { id: string, x: number, y: number };
|
|
|
|
type ConfigurationEventListeners = {
|
|
"touchstart": EventListener[]
|
|
"touchmove": EventListener[]
|
|
"touchend": EventListener[]
|
|
};
|
|
|
|
type ToolbarRefs = {
|
|
toolbar: HTMLDivElement,
|
|
saveButton: HTMLDivElement
|
|
resetButton: HTMLDivElement
|
|
cancelButton: HTMLDivElement
|
|
};
|
|
|
|
/**
|
|
* Handles the dragging of touch controls around the screen.
|
|
*/
|
|
export default class MoveTouchControlsHandler {
|
|
|
|
/** The element that is currently being dragged */
|
|
private draggingElement: HTMLElement | null = null;
|
|
|
|
/**
|
|
* Whether the user is currently configuring the touch controls.
|
|
* When this is true, the touch controls can be dragged around the screen and the controls of the game are disabled.
|
|
*/
|
|
public inConfigurationMode: boolean;
|
|
|
|
/**
|
|
* The event listeners for the configuration mode.
|
|
* These are used to remove the event listeners when the configuration mode is disabled.
|
|
*/
|
|
private configurationEventListeners: ConfigurationEventListeners = {
|
|
"touchstart": [],
|
|
"touchmove": [],
|
|
"touchend": []
|
|
};
|
|
|
|
private overlay: Phaser.GameObjects.Container;
|
|
|
|
private isLandscapeMode: boolean = this.getScreenSize().width > this.getScreenSize().height;
|
|
private touchControls: TouchControl;
|
|
|
|
constructor(touchControls: TouchControl) {
|
|
this.touchControls = touchControls;
|
|
this.inConfigurationMode = false;
|
|
this.setPositions(this.getSavedPositionsOfCurrentOrientation() ?? []);
|
|
window.addEventListener("resize", (event) => {
|
|
const screenSize = this.getScreenSize();
|
|
if (screenSize.width > screenSize.height !== this.isLandscapeMode) {
|
|
this.changeOrientation(screenSize.width > screenSize.height);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Changes the state of the touch controls to the given orientation.
|
|
* @param isLandscapeMode Whether the screen is in landscape mode.
|
|
*/
|
|
private async changeOrientation(isLandscapeMode: boolean) {
|
|
this.isLandscapeMode = isLandscapeMode;
|
|
if (this.inConfigurationMode) {
|
|
const orientation = document.querySelector("#touchControls #orientation");
|
|
if (orientation) {
|
|
orientation.textContent = this.isLandscapeMode? "Landscape" : "Portrait";
|
|
}
|
|
}
|
|
const positions = this.getSavedPositionsOfCurrentOrientation() ?? [];
|
|
this.setPositions(positions);
|
|
}
|
|
|
|
private getScreenSize() {
|
|
return { width: window.screen.width, height: window.screen.height };
|
|
}
|
|
|
|
/**
|
|
* Creates the toolbar element for the configuration mode.
|
|
* @returns A new div element that contains the toolbar for the configuration mode.
|
|
*/
|
|
private createToolbarElement(): HTMLDivElement {
|
|
const toolbar = document.createElement("div");
|
|
toolbar.id = "configToolbar";
|
|
toolbar.innerHTML = `
|
|
<div class="column">
|
|
<div class="button-row">
|
|
<div id="resetButton" class="button">Reset</div>
|
|
<div id="saveButton" class="button">Save & close</div>
|
|
<div id="cancelButton" class="button">Cancel</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="orientation-label">
|
|
Orientation: <span id="orientation">${this.isLandscapeMode ? "Landscape" : "Portrait"}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
return toolbar;
|
|
}
|
|
|
|
/**
|
|
* Initializes the toolbar of the configuration mode.
|
|
* Places its elements at the top of the touch controls and adds event listeners to them.
|
|
*/
|
|
private createToolbar() {
|
|
document.querySelector("#touchControls")?.prepend(this.createToolbarElement());
|
|
const refs = this.getConfigToolbarRefs();
|
|
if (!refs) {
|
|
return;
|
|
}
|
|
const { saveButton, resetButton, cancelButton } = refs;
|
|
|
|
saveButton.addEventListener("click", () => {
|
|
this.saveCurrentPositions();
|
|
this.disableConfigurationMode();
|
|
});
|
|
resetButton.addEventListener("click", () => {
|
|
this.resetPositions();
|
|
});
|
|
cancelButton.addEventListener("click", () => {
|
|
const positions = this.getSavedPositionsOfCurrentOrientation();
|
|
this.setPositions(positions);
|
|
this.disableConfigurationMode();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns the references to the elements of the configuration toolbar.
|
|
* @returns The references to the elements of the configuration toolbar
|
|
* or undefined if the elements can not be found (e.g. during tests)
|
|
*/
|
|
private getConfigToolbarRefs(): ToolbarRefs | undefined {
|
|
const toolbar = document.querySelector("#touchControls #configToolbar") as HTMLDivElement;
|
|
if (!toolbar) {
|
|
return;
|
|
}
|
|
return {
|
|
toolbar,
|
|
saveButton: toolbar.querySelector("#saveButton")!,
|
|
resetButton: toolbar.querySelector("#resetButton")!,
|
|
cancelButton: toolbar.querySelector("#cancelButton")!
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Elements that are inside the left div are anchored to the left boundary of the screen.
|
|
* The x value of the positions are considered offsets to their respective boundaries.
|
|
* @param element Either an element in the left div or the right div.
|
|
* @returns Whether the given element is inside the left div.
|
|
*/
|
|
private isLeft = (element: HTMLElement) => document.querySelector("#touchControls .left")?.contains(element);
|
|
|
|
/**
|
|
* Start dragging the given button.
|
|
* @param controlGroup The button that is being dragged.
|
|
* @param touch The touch event that started the drag.
|
|
*/
|
|
private startDrag = (controlGroup: HTMLElement): void => {
|
|
this.draggingElement = controlGroup;
|
|
};
|
|
|
|
/**
|
|
* Drags the currently dragged element to the given touch position.
|
|
* @param touch The touch event that is currently happening.
|
|
* @param isLeft Whether the dragged element is a left button.
|
|
*/
|
|
private drag = (touch: Touch): void => {
|
|
if (!this.draggingElement) {
|
|
return;
|
|
}
|
|
const rect = this.draggingElement.getBoundingClientRect();
|
|
// Map the touch position to the center of the dragged element.
|
|
const xOffset = this.isLeft(this.draggingElement) ? touch.clientX - rect.width / 2 : window.innerWidth - touch.clientX - rect.width / 2;
|
|
const yOffset = window.innerHeight - touch.clientY - rect.height / 2;
|
|
this.setPosition(this.draggingElement, xOffset, yOffset);
|
|
};
|
|
|
|
/**
|
|
* Stops dragging the currently dragged element.
|
|
*/
|
|
private stopDrag = () => {
|
|
this.draggingElement = null;
|
|
};
|
|
|
|
/**
|
|
* Returns the current positions of all touch controls that have moved from their default positions of this orientation.
|
|
* @returns {ControlPosition[]} The current positions of all touch controls that have moved from their default positions of this orientation
|
|
*/
|
|
private getModifiedCurrentPositions(): ControlPosition[] {
|
|
return this.getControlGroupElements()
|
|
.filter((controlGroup: HTMLElement) => controlGroup.style.right || controlGroup.style.left)
|
|
.map((controlGroup: HTMLElement) => {
|
|
return {
|
|
id: controlGroup.id,
|
|
x: parseFloat(this.isLeft(controlGroup) ? controlGroup.style.left : controlGroup.style.right),
|
|
y: parseFloat(controlGroup.style.bottom),
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns the key of the local storage for the control positions data of this orientation
|
|
*/
|
|
private getLocalStorageKey(): string {
|
|
return this.isLandscapeMode ? TOUCH_CONTROL_POSITIONS_LANDSCAPE : TOUCH_CONTROL_POSITIONS_PORTRAIT;
|
|
}
|
|
|
|
/**
|
|
* Returns the saved positions of the touch controls.
|
|
* Filters result by the given orientation.
|
|
* @returns The saved positions of the touch controls of this orientation
|
|
*/
|
|
private getSavedPositionsOfCurrentOrientation(): ControlPosition[] {
|
|
const positions = localStorage.getItem(this.getLocalStorageKey());
|
|
if (!positions) {
|
|
return [];
|
|
}
|
|
return JSON.parse(positions) as ControlPosition[];
|
|
}
|
|
|
|
/**
|
|
* Saves the current positions of the touch controls to the local storage.
|
|
*/
|
|
private saveCurrentPositions() {
|
|
const pos = this.getModifiedCurrentPositions();
|
|
localStorage.setItem(this.getLocalStorageKey(), JSON.stringify(pos));
|
|
}
|
|
|
|
/**
|
|
* Updates the positions of the touch controls.
|
|
* @param positions The new positions of the touch controls.
|
|
*/
|
|
private setPositions(positions: ControlPosition[]) {
|
|
this.resetPositions();
|
|
return positions.forEach((pos: ControlPosition) => {
|
|
const controlGroup = document.querySelector(`#${pos.id}`) as HTMLElement;
|
|
this.setPosition(controlGroup, pos.x, pos.y);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sets a control element to the given position.
|
|
* The x values are either offsets to the left or right boundary of the screen, depending on the side of the element.
|
|
* E.g. For left elements, (0, 0) is the bottom left corner of the screen and
|
|
* for right elements, (0, 0) is the bottom right corner of the screen.
|
|
* @param controlElement
|
|
* @param x Either an offset to the left or right boundary of the screen.
|
|
* @param y An offset to the bottom boundary of the screen.
|
|
*/
|
|
private setPosition(controlElement: HTMLElement, x: number, y: number) {
|
|
const rect = controlElement.getBoundingClientRect();
|
|
const checkBound = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
|
|
const { height, width } = this.getScreenSize();
|
|
x = checkBound(x, 0, width - rect.width);
|
|
y = checkBound(y, 0, height - rect.height);
|
|
if (this.isLeft(controlElement)) {
|
|
controlElement.style.left = `${x}px`;
|
|
} else {
|
|
controlElement.style.right = `${x}px`;
|
|
}
|
|
controlElement.style.bottom = `${y}px`;
|
|
}
|
|
|
|
/**
|
|
* Resets the positions of the touch controls to their default positions and clears the saved positions.
|
|
* Does not save the changes.
|
|
*/
|
|
private resetPositions() {
|
|
this.getControlGroupElements().forEach((controlGroup: HTMLDivElement) => {
|
|
controlGroup.style.removeProperty("left");
|
|
controlGroup.style.removeProperty("right");
|
|
controlGroup.style.removeProperty("bottom");
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns all control groups of the touch controls.
|
|
* These are groups of buttons that can be dragged around the screen.
|
|
* @returns All control groups of the touch controls.
|
|
*/
|
|
private getControlGroupElements(): HTMLDivElement[] {
|
|
return [...document.querySelectorAll("#touchControls .control-group")] as HTMLDivElement[];
|
|
}
|
|
|
|
/**
|
|
* Creates the event listeners for the configuration mode.
|
|
* @param controlGroups The elements that can be dragged around the screen.
|
|
* @returns The event listeners for the configuration mode.
|
|
*/
|
|
private createConfigurationEventListeners(controlGroups: HTMLDivElement[]): ConfigurationEventListeners {
|
|
return {
|
|
"touchstart": controlGroups.map((element: HTMLDivElement) => {
|
|
const startDrag = () => this.startDrag(element);
|
|
element.addEventListener("touchstart", startDrag, { passive: true });
|
|
return startDrag;
|
|
}),
|
|
"touchmove": controlGroups.map(() => {
|
|
const drag = (event) => this.drag(event.touches[0]);
|
|
window.addEventListener("touchmove", drag, { passive: true });
|
|
return drag;
|
|
}),
|
|
"touchend": controlGroups.map(() => {
|
|
const stopDrag = () => this.stopDrag();
|
|
window.addEventListener("touchend", stopDrag, { passive: true });
|
|
return stopDrag;
|
|
})
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates an overlay that covers the screen and allows the user to drag the touch controls around.
|
|
* Also enables the toolbar for saving, resetting, and canceling the changes.
|
|
* @param ui The UI of the game.
|
|
* @param scene The scene of the game.
|
|
*/
|
|
private createOverlay(ui: UI, scene: Scene) {
|
|
const container = new Phaser.GameObjects.Container(scene, 0, 0);
|
|
const overlay = new Phaser.GameObjects.Rectangle(scene, 0, 0, scene.game.canvas.width, scene.game.canvas.height, 0x000000, 0.5);
|
|
overlay.setInteractive();
|
|
container.add(overlay);
|
|
ui.add(container);
|
|
this.overlay = container;
|
|
|
|
// Display toolbar
|
|
document.querySelector("#touchControls")?.classList.add("config-mode");
|
|
}
|
|
|
|
/**
|
|
* Allows the user to configure the touch controls by dragging buttons around the screen.
|
|
* @param ui The UI of the game.
|
|
* @param scene The scene of the game.
|
|
*/
|
|
public enableConfigurationMode(ui: UI, scene: Scene) {
|
|
if (this.inConfigurationMode) {
|
|
return;
|
|
}
|
|
this.inConfigurationMode = true;
|
|
this.touchControls.disable();
|
|
this.createOverlay(ui, scene);
|
|
this.createToolbar();
|
|
// Create event listeners with a delay to prevent the touchstart event from being triggered immediately.
|
|
setTimeout(() => {
|
|
// Remember the event listeners so they can be removed later.
|
|
this.configurationEventListeners = this.createConfigurationEventListeners(this.getControlGroupElements());
|
|
}, 500);
|
|
}
|
|
|
|
/**
|
|
* Disables the configuration mode.
|
|
*/
|
|
public disableConfigurationMode() {
|
|
this.inConfigurationMode = false;
|
|
this.draggingElement = null;
|
|
|
|
// Remove event listeners
|
|
const { touchstart, touchmove, touchend } = this.configurationEventListeners;
|
|
this.getControlGroupElements().forEach((element, index) => element.removeEventListener("touchstart", touchstart[index]));
|
|
touchmove.forEach((listener) => window.removeEventListener("touchmove", listener));
|
|
touchend.forEach((listener) => window.removeEventListener("touchend", listener));
|
|
|
|
// Remove configuration toolbar
|
|
const toolbar = document.querySelector("#touchControls #configToolbar");
|
|
toolbar?.remove();
|
|
|
|
// Remove overlay
|
|
this.overlay?.destroy();
|
|
document.querySelector("#touchControls")?.classList.remove("config-mode");
|
|
this.touchControls.enable();
|
|
}
|
|
|
|
}
|