[Feature] Move touch controls configuration (Reopened) (#3256)
* [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>
This commit is contained in:
parent
638a0a66b5
commit
566cd80522
301
index.css
301
index.css
|
@ -1,16 +1,8 @@
|
|||
/* Global */
|
||||
:root {
|
||||
--color-base: hsl(0, 0%, 55%);
|
||||
--color-light: hsl(0, 0%, 90%);
|
||||
--color-dark: hsl(0, 0%, 10%);
|
||||
--controls-size: 10vh;
|
||||
--text-shadow-size: 0.65vh;
|
||||
}
|
||||
|
||||
@media (orientation: landscape) {
|
||||
:root {
|
||||
--controls-size: 20vh;
|
||||
--text-shadow-size: 1.3vh;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
|
@ -43,33 +35,173 @@ body {
|
|||
transform-origin: top !important;
|
||||
}
|
||||
|
||||
#layout:fullscreen #dpad, #layout:fullscreen {
|
||||
bottom: 6rem;
|
||||
}
|
||||
|
||||
input:-internal-autofill-selected {
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Need adjust input font-size */
|
||||
input {
|
||||
font-size: 3.2rem;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
input:-internal-autofill-selected {
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Touch Controls: */
|
||||
|
||||
#touchControls {
|
||||
--text-shadow-size: 0.65vh;
|
||||
--controls-size: 10vh;
|
||||
--touch-control-opacity: 0.6;
|
||||
|
||||
--controls-padding: 1rem;
|
||||
|
||||
--controls-size-with-padding: calc(var(--controls-size) + var(--controls-padding));
|
||||
--control-group-extra-size: calc(var(--controls-size) * 0.8);
|
||||
|
||||
--control-group-extra-2-offset: calc(var(--controls-size-with-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2);
|
||||
--control-group-extra-1-offset: calc(var(--controls-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2);
|
||||
|
||||
--small-control-size: calc(var(--controls-size) / 3);
|
||||
--rect-control-size: calc(var(--controls-size) * 0.74);
|
||||
|
||||
font-family: 'emerald';
|
||||
font-size: var(--controls-size);
|
||||
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size);
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
@media (orientation: landscape) {
|
||||
#touchControls {
|
||||
--controls-size: 20vh;
|
||||
--text-shadow-size: 1.3vh;
|
||||
--small-button-offset: 4vh;
|
||||
}
|
||||
}
|
||||
|
||||
#touchControls:not(.visible) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#dpad, #apad {
|
||||
#touchControls .active {
|
||||
opacity: var(--touch-control-opacity);
|
||||
}
|
||||
|
||||
.control-group {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
width: var(--controls-size);
|
||||
}
|
||||
|
||||
.control-group-dpad {
|
||||
width: calc(2 * var(--controls-size));
|
||||
height: calc(2 * var(--controls-size));
|
||||
}
|
||||
|
||||
.control-group-extra {
|
||||
width: var(--control-group-extra-size);
|
||||
height: var(--control-group-extra-size);
|
||||
}
|
||||
/* Hide buttons on specific UIs */
|
||||
|
||||
/* Show #cycleForm and #cycleShiny only on STARTER_SELECT and SETTINGS */
|
||||
#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode^='SETTINGS']) #apadCycleForm,
|
||||
#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode^='SETTINGS']) #apadCycleShiny {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show #apadInfo only in battle */
|
||||
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']) #apadInfo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show #apadInfo only in battle and target select */
|
||||
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']):not([data-ui-mode='MODIFIER_SELECT']) #apadStats {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show cycle buttons only on STARTER_SELECT and on touch configuration panel */
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleNature,
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleAbility,
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleGender,
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleVariant {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Configuration toolbar */
|
||||
|
||||
#configToolbar {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
left: 0;
|
||||
z-index: 9;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#configToolbar .column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10%;
|
||||
padding: 0 var(--controls-padding);
|
||||
}
|
||||
|
||||
#configToolbar .button-row {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#configToolbar .info-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#configToolbar .button {
|
||||
z-index: 3;
|
||||
background-color: var(--color-base);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
border-radius: 10%;
|
||||
height: var(--small-control-size);
|
||||
font-size: var(--small-control-size);
|
||||
border-radius: 8px;
|
||||
padding: 2px 8px;
|
||||
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
|
||||
}
|
||||
|
||||
@media (orientation: portrait) {
|
||||
#dpad, #apad {
|
||||
bottom: calc(1rem + env(safe-area-inset-bottom));
|
||||
}
|
||||
#configToolbar .button:active {
|
||||
opacity: var(--touch-control-opacity)
|
||||
}
|
||||
|
||||
#configToolbar .orientation-label {
|
||||
font-size: var(--small-control-size);
|
||||
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
|
||||
}
|
||||
|
||||
/* dpad */
|
||||
#dpad {
|
||||
left: 1rem;
|
||||
}
|
||||
|
||||
#apad {
|
||||
right: 1rem;
|
||||
z-index: 3;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#dpad svg {
|
||||
|
@ -78,114 +210,83 @@ input {
|
|||
fill: var(--color-base);
|
||||
}
|
||||
|
||||
#dpad svg rect {
|
||||
opacity: 0.6;
|
||||
}
|
||||
/* apad buttons */
|
||||
|
||||
#apad > * {
|
||||
width: var(--controls-size);
|
||||
height: var(--controls-size);
|
||||
}
|
||||
|
||||
#apad .apadBtn {
|
||||
width: var(--controls-size);
|
||||
height: var(--controls-size);
|
||||
.apad-button {
|
||||
background-color: var(--color-base);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: var(--controls-size);
|
||||
height: var(--controls-size);
|
||||
opacity: 0.8;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#apad .apadLabel {
|
||||
font-family: 'emerald';
|
||||
font-size: var(--controls-size);
|
||||
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size);
|
||||
color: var(--color-light);
|
||||
.apad-small {
|
||||
width: var(--small-control-size);
|
||||
height: var(--small-control-size);
|
||||
}
|
||||
|
||||
.apad-label {
|
||||
user-select: none;
|
||||
height: 100%;
|
||||
margin-right: -2px;
|
||||
}
|
||||
|
||||
#apad .apadLabelSmall {
|
||||
font-size: calc(var(--controls-size) / 3);
|
||||
.apad-small > .apad-label {
|
||||
font-size: var(--small-control-size);
|
||||
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
|
||||
}
|
||||
|
||||
#apad #apadLabelAction, #apad #apadLabelCancel {
|
||||
margin-left: calc(var(--controls-size) / 3);
|
||||
line-height: 0.9;
|
||||
}
|
||||
|
||||
#apad > :nth-child(2) {
|
||||
position: relative;
|
||||
right: var(--controls-size);
|
||||
}
|
||||
|
||||
#apad .apadRectBtn {
|
||||
position: relative;
|
||||
.apad-rectangle {
|
||||
text-align: center;
|
||||
padding-right: 10%;
|
||||
border-radius: 10%;
|
||||
bottom: calc(var(--controls-size) * 0.05);
|
||||
width: calc(var(--controls-size) * 0.6);
|
||||
height: calc(var(--controls-size) * 0.3);
|
||||
width: var(--rect-control-size);
|
||||
height: var(--small-control-size);
|
||||
}
|
||||
|
||||
#apad .apadSqBtn {
|
||||
border-radius: 10%;
|
||||
width: calc(var(--controls-size) * 0.3);
|
||||
height: calc(var(--controls-size) * 0.3);
|
||||
.apad-square {
|
||||
width: var(--small-control-size);
|
||||
height: var(--small-control-size);
|
||||
}
|
||||
|
||||
#apad .apadBtnContainer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
.apad-circle {
|
||||
width: var(--controls-size);
|
||||
height: var(--controls-size);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#apad .apadRectBtnContainer {
|
||||
flex-wrap: wrap;
|
||||
margin-top: calc(var(--controls-size) * -0.8);
|
||||
left: calc(var(--controls-size) * 0.175);
|
||||
height: calc(var(--controls-size) * 0.8);
|
||||
/* Defaults:*/
|
||||
|
||||
#control-group-dpad {
|
||||
left: var(--controls-padding);
|
||||
bottom: var(--controls-padding);
|
||||
}
|
||||
|
||||
#apad .apadSqBtnContainer {
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
margin-bottom: calc(var(--controls-size) * -0.8);
|
||||
top: calc(var(--controls-size) * -0.9);
|
||||
width: calc(var(--controls-size) * 0.8);
|
||||
height: calc(var(--controls-size) * 0.8);
|
||||
#control-group-action {
|
||||
right: var(--controls-padding);
|
||||
bottom: var(--controls-size-with-padding);
|
||||
}
|
||||
|
||||
#apad .apadRectBtnContainer > #apadMenu {
|
||||
align-self: flex-end;
|
||||
#control-group-cancel {
|
||||
right: var(--controls-size-with-padding);
|
||||
bottom: var(--controls-padding);;
|
||||
}
|
||||
|
||||
#apad .apadRectBtnContainer > .apadSqBtn:not(:first-child) {
|
||||
margin-left: 10%;
|
||||
#control-group-extra-1 {
|
||||
right: var(--control-group-extra-1-offset);
|
||||
bottom: var(--control-group-extra-1-offset);
|
||||
}
|
||||
|
||||
#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadRectBtnContainer > .apadSqBtn:not(.apadBattle),
|
||||
#touchControls:not([data-ui-mode='STARTER_SELECT']):not([data-ui-mode='SETTINGS']):not([data-ui-mode='SETTINGS_DISPLAY']):not([data-ui-mode='SETTINGS_AUDIO']):not([data-ui-mode='SETTINGS_GAMEPAD']):not([data-ui-mode='SETTINGS_KEYBOARD']) #apad .apadSqBtnContainer > .apadSqBtn:not(.apadBattle)
|
||||
{
|
||||
display: none;
|
||||
#control-group-extra-2 {
|
||||
right: var(--control-group-extra-2-offset);
|
||||
bottom: var(--control-group-extra-2-offset);
|
||||
}
|
||||
|
||||
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']):not([data-ui-mode='MODIFIER_SELECT']) #apad .apadBattle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#apad .apadRectBtnContainer + .apadSqBtnContainer {
|
||||
top: calc(var(--controls-size) * -1.9);
|
||||
left: calc(var(--controls-size) * -0.9);
|
||||
}
|
||||
|
||||
#apad .apadBtnContainer .apadLabel {
|
||||
margin-left: calc(var(--controls-size) / 12);
|
||||
line-height: 0.8;
|
||||
}
|
||||
|
||||
#dpad path:not(.active), #apad .apadBtn:not(.active) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
/* Layout */
|
||||
|
||||
#layout:fullscreen #dpad, #layout:fullscreen #apad {
|
||||
bottom: 6rem;
|
||||
|
|
94
index.html
94
index.html
|
@ -64,54 +64,70 @@
|
|||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="touchControls">
|
||||
<div id="dpad" class="unselectable">
|
||||
<div class="left">
|
||||
<div id="control-group-dpad" class="control-group control-group-dpad">
|
||||
<div id="dpad" data-control-key="DPAD">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72">
|
||||
<path id="dpadUp" data-key="UP" d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
|
||||
<path id="dpadRight" data-key="RIGHT" d="M66.2,24H48v24h18.2c3.3,0,5.8-2.7,5.8-6V29.9C72,26.5,69.5,24,66.2,24z" />
|
||||
<path id="dpadDown" data-key="DOWN" d="M24,66.3c0,3.3,2.6,5.7,5.9,5.7H42c3.3,0,6-2.4,6-5.7V48H24V66.3z" />
|
||||
<path id="dpadLeft" data-key="LEFT" d="M5.7,24C2.4,24,0,26.5,0,29.9V42c0,3.3,2.3,6,5.7,6H24V24H5.7z" />
|
||||
<path id="dpadUp" data-key="UP"
|
||||
d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
|
||||
<path id="dpadRight" data-key="RIGHT"
|
||||
d="M66.2,24H48v24h18.2c3.3,0,5.8-2.7,5.8-6V29.9C72,26.5,69.5,24,66.2,24z" />
|
||||
<path id="dpadDown" data-key="DOWN"
|
||||
d="M24,66.3c0,3.3,2.6,5.7,5.9,5.7H42c3.3,0,6-2.4,6-5.7V48H24V66.3z" />
|
||||
<path id="dpadLeft" data-key="LEFT"
|
||||
d="M5.7,24C2.4,24,0,26.5,0,29.9V42c0,3.3,2.3,6,5.7,6H24V24H5.7z" />
|
||||
<rect id="dpadCenter" x="24" y="24" width="24" height="24" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="apad" class="unselectable">
|
||||
<div id="apadAction" class="apadCircBtn apadBtn" data-key="ACTION">
|
||||
<text id="apadLabelAction" class="apadLabel">A</text>
|
||||
</div>
|
||||
<div id="apadCancel" class="apadCircBtn apadBtn" data-key="CANCEL">
|
||||
<text id="apadLabelCancel" class="apadLabel">B</text>
|
||||
</div>
|
||||
<div class="apadBtnContainer apadRectBtnContainer">
|
||||
<div id="apadCycleShiny" class="apadSqBtn apadBtn" data-key="CYCLE_SHINY">
|
||||
<text class="apadLabel apadLabelSmall">R</text>
|
||||
</div>
|
||||
<div id="apadCycleVariant" class="apadSqBtn apadBtn" data-key="V">
|
||||
<text class="apadLabel apadLabelSmall">V</text>
|
||||
</div>
|
||||
<div id="apadStats" class="apadRectBtn apadBtn apadBattle" data-key="STATS">
|
||||
<text class="apadLabel apadLabelSmall">C</text>
|
||||
</div>
|
||||
<div id="apadMenu" class="apadRectBtn apadBtn" data-key="MENU">
|
||||
<text class="apadLabel apadLabelSmall">Menu</text>
|
||||
<div class="right">
|
||||
<div id="control-group-action" class="control-group">
|
||||
<div id="apadAction" class="apad-button apad-circle" data-key="ACTION">
|
||||
<span class="apad-label">A</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="apadBtnContainer apadSqBtnContainer">
|
||||
<div id="apadCycleForm" class="apadSqBtn apadBtn" data-key="CYCLE_FORM">
|
||||
<text class="apadLabel apadLabelSmall">F</text>
|
||||
</div>
|
||||
<div id="apadCycleGender" class="apadSqBtn apadBtn" data-key="CYCLE_GENDER">
|
||||
<text class="apadLabel apadLabelSmall">G</text>
|
||||
</div>
|
||||
<div id="apadCycleAbility" class="apadSqBtn apadBtn" data-key="CYCLE_ABILITY">
|
||||
<text class="apadLabel apadLabelSmall">E</text>
|
||||
</div>
|
||||
<div id="apadCycleNature" class="apadSqBtn apadBtn" data-key="CYCLE_NATURE">
|
||||
<text class="apadLabel apadLabelSmall">N</text>
|
||||
</div>
|
||||
<div id="apadInfo" class="apadRectBtn apadBtn apadBattle" data-key="V">
|
||||
<text class="apadLabel apadLabelSmall">V</text>
|
||||
|
||||
<div id="control-group-cancel" class="control-group">
|
||||
<div id="apadCancel" class="apad-button apad-circle" data-key="CANCEL">
|
||||
<span class="apad-label">B</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="control-group-extra-1" class="control-group control-group-extra">
|
||||
<div id="apadCycleShiny" class="apad-button apad-square apad-small" data-key="CYCLE_SHINY">
|
||||
<span class="apad-label">R</span>
|
||||
</div>
|
||||
<div id="apadCycleVariant" class="apad-button apad-square apad-small" data-key="V">
|
||||
<span class="apad-label">V</span>
|
||||
</div>
|
||||
<div id="apadStats" class="apad-button apad-rectangle apad-small" data-key="STATS">
|
||||
<span class="apad-label">C</span>
|
||||
</div>
|
||||
<div id="apadMenu" class="apad-button apad-rectangle apad-small" data-key="MENU">
|
||||
<span class="apad-label">Menu</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="control-group-extra-2" class="control-group control-group-extra">
|
||||
<div id="apadCycleForm" class="apad-button apad-square apad-small" data-key="CYCLE_FORM">
|
||||
<span class="apad-label">F</span>
|
||||
</div>
|
||||
<div id="apadCycleGender" class="apad-button apad-square apad-small" data-key="CYCLE_GENDER">
|
||||
<span class="apad-label">G</span>
|
||||
</div>
|
||||
<div id="apadCycleAbility" class="apad-button apad-square apad-small" data-key="CYCLE_ABILITY">
|
||||
<span class="apad-label">E</span>
|
||||
</div>
|
||||
<div id="apadCycleNature" class="apad-button apad-square apad-small" data-key="CYCLE_NATURE">
|
||||
<span class="apad-label">N</span>
|
||||
</div>
|
||||
<div id="apadInfo" class="apad-button apad-rectangle apad-small" data-key="V">
|
||||
<span class="apad-label">V</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="tnc-links">
|
||||
|
|
|
@ -21,6 +21,7 @@ import {SettingKeyboard} from "#app/system/settings/settings-keyboard";
|
|||
import TouchControl from "#app/touch-controls";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { Device } from "#enums/devices";
|
||||
import MoveTouchControlsHandler from "./ui/settings/move-touch-controls-handler";
|
||||
|
||||
export interface DeviceMapping {
|
||||
[key: string]: number;
|
||||
|
@ -105,6 +106,7 @@ export class InputsController {
|
|||
public lastSource: string = "keyboard";
|
||||
private inputInterval: NodeJS.Timeout[] = new Array();
|
||||
private touchControls: TouchControl;
|
||||
public moveTouchControlsHandler: MoveTouchControlsHandler;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the game control system, setting up initial state and configurations.
|
||||
|
@ -182,6 +184,7 @@ export class InputsController {
|
|||
this.scene.input.keyboard?.on("keyup", this.keyboardKeyUp, this);
|
||||
}
|
||||
this.touchControls = new TouchControl(this.scene);
|
||||
this.moveTouchControlsHandler = new MoveTouchControlsHandler(this.touchControls);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -97,5 +97,6 @@ export const settings: SimpleTranslationEntries = {
|
|||
"controller": "Controller",
|
||||
"gamepadSupport": "Controllerunterstützung",
|
||||
"showBgmBar": "Musiknamen anzeigen",
|
||||
"moveTouchControls": "Bewegung Touch Steuerung",
|
||||
"shopOverlayOpacity": "Shop Overlay Deckkraft",
|
||||
} as const;
|
||||
|
|
|
@ -97,5 +97,6 @@ export const settings: SimpleTranslationEntries = {
|
|||
"controller": "Controller",
|
||||
"gamepadSupport": "Gamepad Support",
|
||||
"showBgmBar": "Show Music Names",
|
||||
"moveTouchControls": "Move Touch Controls",
|
||||
"shopOverlayOpacity": "Shop Overlay Opacity"
|
||||
} as const;
|
||||
|
|
|
@ -97,5 +97,6 @@ export const settings: SimpleTranslationEntries = {
|
|||
"controller": "Controller",
|
||||
"gamepadSupport": "Gamepad Support",
|
||||
"showBgmBar": "Show Music Names",
|
||||
"moveTouchControls": "Move Touch Controls",
|
||||
"shopOverlayOpacity": "Opacidad de la fase de compra"
|
||||
} as const;
|
||||
|
|
|
@ -97,5 +97,6 @@ export const settings: SimpleTranslationEntries = {
|
|||
"controller": "Controller",
|
||||
"gamepadSupport": "Gamepad Support",
|
||||
"showBgmBar": "Titre de la musique",
|
||||
"moveTouchControls": "Déplacer les contrôles tactiles",
|
||||
"shopOverlayOpacity": "Opacité boutique"
|
||||
} as const;
|
||||
|
|
|
@ -97,5 +97,6 @@ export const settings: SimpleTranslationEntries = {
|
|||
"controller": "Controller",
|
||||
"gamepadSupport": "Supporto Gamepad",
|
||||
"showBgmBar": "Mostra Nomi Musica",
|
||||
"moveTouchControls": "Move Touch Controls",
|
||||
"shopOverlayOpacity": "Opacità Finestra Negozio"
|
||||
} as const;
|
||||
|
|
|
@ -97,5 +97,6 @@ export const settings: SimpleTranslationEntries = {
|
|||
"controller": "컨트롤러",
|
||||
"gamepadSupport": "게임패드 지원",
|
||||
"showBgmBar": "BGM 제목 보여주기",
|
||||
"moveTouchControls": "터치 컨트롤 이동",
|
||||
"shopOverlayOpacity": "상점 오버레이 투명도"
|
||||
} as const;
|
||||
|
|
|
@ -97,5 +97,6 @@ export const settings: SimpleTranslationEntries = {
|
|||
"controller": "Controle",
|
||||
"gamepadSupport": "Suporte para Controle",
|
||||
"showBgmBar": "Exibir Nomes das Músicas",
|
||||
"moveTouchControls": "Move Touch Controls",
|
||||
"shopOverlayOpacity": "Opacidade da Loja"
|
||||
} as const;
|
||||
|
|
|
@ -97,5 +97,6 @@ export const settings: SimpleTranslationEntries = {
|
|||
"controller": "控制器",
|
||||
"gamepadSupport": "手柄支持",
|
||||
"showBgmBar": "显示音乐名称",
|
||||
"moveTouchControls": "移动触摸控制",
|
||||
"shopOverlayOpacity": "商店显示不透明度"
|
||||
} as const;
|
||||
|
|
|
@ -97,5 +97,6 @@ export const settings: SimpleTranslationEntries = {
|
|||
"controller": "控制器",
|
||||
"gamepadSupport": "手柄支持",
|
||||
"showBgmBar": "Show Music Names",
|
||||
"moveTouchControls": "移動觸控控制",
|
||||
"shopOverlayOpacity": "Shop Overlay Opacity"
|
||||
} as const;
|
||||
|
|
|
@ -65,6 +65,10 @@ export interface Setting {
|
|||
default: number
|
||||
type: SettingType
|
||||
requireReload?: boolean
|
||||
/** Whether the setting can be activated or not */
|
||||
activatable?: boolean
|
||||
/** Determines whether the setting should be hidden from the UI */
|
||||
isHidden?: () => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,6 +110,7 @@ export const SettingKeys = {
|
|||
SE_Volume: "SE_VOLUME",
|
||||
Music_Preference: "MUSIC_PREFERENCE",
|
||||
Show_BGM_Bar: "SHOW_BGM_BAR",
|
||||
Move_Touch_Controls: "MOVE_TOUCH_CONTROLS",
|
||||
Shop_Overlay_Opacity: "SHOP_OVERLAY_OPACITY"
|
||||
};
|
||||
|
||||
|
@ -550,6 +555,20 @@ export const Setting: Array<Setting> = [
|
|||
type: SettingType.AUDIO,
|
||||
requireReload: true
|
||||
},
|
||||
{
|
||||
key: SettingKeys.Move_Touch_Controls,
|
||||
label: i18next.t("settings:moveTouchControls"),
|
||||
options: [
|
||||
{
|
||||
value: "Configure",
|
||||
label: i18next.t("settings:change")
|
||||
}
|
||||
],
|
||||
default: 0,
|
||||
type: SettingType.GENERAL,
|
||||
activatable: true,
|
||||
isHidden: () => !hasTouchscreen()
|
||||
},
|
||||
{
|
||||
key: SettingKeys.Shop_Overlay_Opacity,
|
||||
label: i18next.t("settings:shopOverlayOpacity"),
|
||||
|
@ -557,7 +576,7 @@ export const Setting: Array<Setting> = [
|
|||
default: 7,
|
||||
type: SettingType.DISPLAY,
|
||||
requireReload: false
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,50 +1,69 @@
|
|||
<!DOCTYPE html><body>
|
||||
<div id="touchControls">
|
||||
<div id="dpad" class="unselectable">
|
||||
<div class="left">
|
||||
<div id="control-group-dpad" class="control-group control-group-dpad">
|
||||
<div id="dpad" data-control-key="DPAD">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72">
|
||||
<path id="dpadUp" data-key="UP" d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
|
||||
<path id="dpadRight" data-key="RIGHT" d="M66.2,24H48v24h18.2c3.3,0,5.8-2.7,5.8-6V29.9C72,26.5,69.5,24,66.2,24z" />
|
||||
<path id="dpadDown" data-key="DOWN" d="M24,66.3c0,3.3,2.6,5.7,5.9,5.7H42c3.3,0,6-2.4,6-5.7V48H24V66.3z" />
|
||||
<path id="dpadLeft" data-key="LEFT" d="M5.7,24C2.4,24,0,26.5,0,29.9V42c0,3.3,2.3,6,5.7,6H24V24H5.7z" />
|
||||
<path id="dpadUp" data-key="UP"
|
||||
d="M48,5.8C48,2.5,45.4,0,42,0H29.9C26.6,0,24,2.4,24,5.8V24h24V5.8z" />
|
||||
<path id="dpadRight" data-key="RIGHT"
|
||||
d="M66.2,24H48v24h18.2c3.3,0,5.8-2.7,5.8-6V29.9C72,26.5,69.5,24,66.2,24z" />
|
||||
<path id="dpadDown" data-key="DOWN"
|
||||
d="M24,66.3c0,3.3,2.6,5.7,5.9,5.7H42c3.3,0,6-2.4,6-5.7V48H24V66.3z" />
|
||||
<path id="dpadLeft" data-key="LEFT"
|
||||
d="M5.7,24C2.4,24,0,26.5,0,29.9V42c0,3.3,2.3,6,5.7,6H24V24H5.7z" />
|
||||
<rect id="dpadCenter" x="24" y="24" width="24" height="24" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="apad" class="unselectable">
|
||||
<div id="apadAction" class="apadCircBtn apadBtn" data-key="ACTION">
|
||||
<text id="apadLabelAction" class="apadLabel">A</text>
|
||||
</div>
|
||||
<div id="apadCancel" class="apadCircBtn apadBtn" data-key="CANCEL">
|
||||
<text id="apadLabelCancel" class="apadLabel">B</text>
|
||||
</div>
|
||||
<div class="apadBtnContainer apadRectBtnContainer">
|
||||
<div id="apadCycleShiny" class="apadSqBtn apadBtn" data-key="CYCLE_SHINY">
|
||||
<text class="apadLabel apadLabelSmall">R</text>
|
||||
</div>
|
||||
<div id="apadCycleVariant" class="apadSqBtn apadBtn" data-key="CYCLE_VARIANT">
|
||||
<text class="apadLabel apadLabelSmall">V</text>
|
||||
</div>
|
||||
<div id="apadStats" class="apadRectBtn apadBtn" data-key="STATS">
|
||||
<text class="apadLabel apadLabelSmall">C</text>
|
||||
</div>
|
||||
<div id="apadMenu" class="apadRectBtn apadBtn" data-key="MENU">
|
||||
<text class="apadLabel apadLabelSmall">Menu</text>
|
||||
<div class="right">
|
||||
<div id="control-group-action" class="control-group">
|
||||
<div id="apadAction" class="apad-button apad-circle" data-key="ACTION">
|
||||
<span class="apad-label">A</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="apadBtnContainer apadSqBtnContainer">
|
||||
<div id="apadCycleForm" class="apadSqBtn apadBtn" data-key="CYCLE_FORM">
|
||||
<text class="apadLabel apadLabelSmall">F</text>
|
||||
</div>
|
||||
<div id="apadCycleGender" class="apadSqBtn apadBtn" data-key="CYCLE_GENDER">
|
||||
<text class="apadLabel apadLabelSmall">G</text>
|
||||
</div>
|
||||
<div id="apadCycleAbility" class="apadSqBtn apadBtn" data-key="CYCLE_ABILITY">
|
||||
<text class="apadLabel apadLabelSmall">E</text>
|
||||
</div>
|
||||
<div id="apadCycleNature" class="apadSqBtn apadBtn" data-key="CYCLE_NATURE">
|
||||
<text class="apadLabel apadLabelSmall">N</text>
|
||||
|
||||
<div id="control-group-cancel" class="control-group">
|
||||
<div id="apadCancel" class="apad-button apad-circle" data-key="CANCEL">
|
||||
<span class="apad-label">B</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="control-group-extra-1" class="control-group control-group-extra">
|
||||
<div id="apadCycleShiny" class="apad-button apad-square apad-small" data-key="CYCLE_SHINY">
|
||||
<span class="apad-label">R</span>
|
||||
</div>
|
||||
<div id="apadCycleVariant" class="apad-button apad-square apad-small" data-key="V">
|
||||
<span class="apad-label">V</span>
|
||||
</div>
|
||||
<div id="apadStats" class="apad-button apad-rectangle apad-small" data-key="STATS">
|
||||
<span class="apad-label">C</span>
|
||||
</div>
|
||||
<div id="apadMenu" class="apad-button apad-rectangle apad-small" data-key="MENU">
|
||||
<span class="apad-label">Menu</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="control-group-extra-2" class="control-group control-group-extra">
|
||||
<div id="apadCycleForm" class="apad-button apad-square apad-small" data-key="CYCLE_FORM">
|
||||
<span class="apad-label">F</span>
|
||||
</div>
|
||||
<div id="apadCycleGender" class="apad-button apad-square apad-small" data-key="CYCLE_GENDER">
|
||||
<span class="apad-label">G</span>
|
||||
</div>
|
||||
<div id="apadCycleAbility" class="apad-button apad-square apad-small" data-key="CYCLE_ABILITY">
|
||||
<span class="apad-label">E</span>
|
||||
</div>
|
||||
<div id="apadCycleNature" class="apad-button apad-square apad-small" data-key="CYCLE_NATURE">
|
||||
<span class="apad-label">N</span>
|
||||
</div>
|
||||
<div id="apadInfo" class="apad-button apad-rectangle apad-small" data-key="V">
|
||||
<span class="apad-label">V</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
|
@ -8,18 +8,38 @@ export default class TouchControl {
|
|||
events: EventEmitter;
|
||||
private buttonLock: string[] = new Array();
|
||||
private inputInterval: NodeJS.Timeout[] = new Array();
|
||||
/** Whether touch controls are disabled */
|
||||
private disabled: boolean = false;
|
||||
/** Whether the last touch event has finished before disabling */
|
||||
private finishedLastTouch: boolean = false;
|
||||
|
||||
constructor(scene: BattleScene) {
|
||||
this.events = scene.game.events;
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable touch controls
|
||||
*/
|
||||
disable() {
|
||||
this.disabled = true;
|
||||
this.finishedLastTouch = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable touch controls
|
||||
*/
|
||||
enable() {
|
||||
this.disabled = false;
|
||||
this.finishedLastTouch = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize touch controls by binding keys to buttons.
|
||||
*/
|
||||
init() {
|
||||
this.preventElementZoom(document.querySelector("#dpad"));
|
||||
this.preventElementZoom(document.querySelector("#apad"));
|
||||
document.querySelectorAll(".apad-button").forEach((element) => this.preventElementZoom(element as HTMLElement));
|
||||
// Select all elements with the 'data-key' attribute and bind keys to them
|
||||
for (const button of document.querySelectorAll("[data-key]")) {
|
||||
// @ts-ignore - Bind the key to the button using the dataset key
|
||||
|
@ -56,10 +76,14 @@ export default class TouchControl {
|
|||
if (this.buttonLock.includes(key)) {
|
||||
return;
|
||||
}
|
||||
this.simulateKeyboardEvent("keydown", key);
|
||||
if (!this.simulateKeyboardEvent("keydown", key)) {
|
||||
return;
|
||||
}
|
||||
clearInterval(this.inputInterval[key]);
|
||||
this.inputInterval[key] = setInterval(() => {
|
||||
this.simulateKeyboardEvent("keydown", key);
|
||||
if (!this.simulateKeyboardEvent("keydown", key)) {
|
||||
clearInterval(this.inputInterval[key]);
|
||||
}
|
||||
}, repeatInputDelayMillis);
|
||||
this.buttonLock.push(key);
|
||||
node.classList.add("active");
|
||||
|
@ -67,11 +91,11 @@ export default class TouchControl {
|
|||
}
|
||||
|
||||
touchButtonUp(node: HTMLElement, key: string, id: string) {
|
||||
if (!this.buttonLock.includes(key)) {
|
||||
if (!this.buttonLock.includes(key) || this.disabled && this.finishedLastTouch) {
|
||||
return;
|
||||
}
|
||||
this.finishedLastTouch = true;
|
||||
this.simulateKeyboardEvent("keyup", key);
|
||||
|
||||
node.classList.remove("active");
|
||||
|
||||
document.getElementById(id)?.classList.remove("active");
|
||||
|
@ -81,18 +105,19 @@ export default class TouchControl {
|
|||
}
|
||||
|
||||
/**
|
||||
* Simulates a keyboard event on the canvas.
|
||||
* Simulates a keyboard event on the canvas if the button is not disabled.
|
||||
*
|
||||
* @param eventType - The type of the keyboard event ('keydown' or 'keyup').
|
||||
* @param key - The key to simulate.
|
||||
*
|
||||
* @returns Whether the simulation was successful.
|
||||
* @remarks
|
||||
* This function checks if the key exists in the Button enum. If it does, it retrieves the corresponding button
|
||||
* and emits the appropriate event ('input_down' or 'input_up') based on the event type.
|
||||
*/
|
||||
simulateKeyboardEvent(eventType: string, key: string) {
|
||||
if (!Button.hasOwnProperty(key)) {
|
||||
return;
|
||||
simulateKeyboardEvent(eventType: string, key: string): boolean {
|
||||
console.log("simulateKeyboardEvent", eventType, key);
|
||||
if (!Button.hasOwnProperty(key) || this.disabled) {
|
||||
return false;
|
||||
}
|
||||
const button = Button[key];
|
||||
|
||||
|
@ -112,6 +137,7 @@ export default class TouchControl {
|
|||
});
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,7 @@ import { addWindow } from "../ui-theme";
|
|||
import {Button} from "#enums/buttons";
|
||||
import {InputsIcons} from "#app/ui/settings/abstract-control-settings-ui-handler.js";
|
||||
import NavigationMenu, {NavigationManager} from "#app/ui/settings/navigationMenu";
|
||||
import { Setting, SettingKeys } from "#app/system/settings/settings";
|
||||
import { Setting, SettingKeys, SettingType } from "#app/system/settings/settings";
|
||||
import i18next from "i18next";
|
||||
|
||||
|
||||
|
@ -40,9 +40,9 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
protected settings: Array<Setting>;
|
||||
protected localStorageKey: string;
|
||||
|
||||
constructor(scene: BattleScene, mode: Mode | null = null) {
|
||||
constructor(scene: BattleScene, type: SettingType, mode: Mode | null = null) {
|
||||
super(scene, mode);
|
||||
|
||||
this.settings = Setting.filter(s => s.type === type && !s?.isHidden?.());
|
||||
this.reloadRequired = false;
|
||||
this.rowsToDisplay = 8;
|
||||
}
|
||||
|
@ -264,6 +264,12 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
case Button.CYCLE_SHINY:
|
||||
success = this.navigationContainer.navigate(button);
|
||||
break;
|
||||
case Button.ACTION:
|
||||
const setting: Setting = this.settings[cursor];
|
||||
if (setting?.activatable) {
|
||||
success = this.activateSetting(setting);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,6 +281,20 @@ export default class AbstractSettingsUiHandler extends UiHandler {
|
|||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the specified setting if it is activatable.
|
||||
* @param setting The setting to activate.
|
||||
* @returns Whether the setting was successfully activated.
|
||||
*/
|
||||
activateSetting(setting: Setting): boolean {
|
||||
switch (setting.key) {
|
||||
case SettingKeys.Move_Touch_Controls:
|
||||
this.scene.inputController.moveTouchControlsHandler.enableConfigurationMode(this.getUi(), this.scene);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cursor to the specified position.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,380 @@
|
|||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,7 @@ import BattleScene from "../../battle-scene";
|
|||
import { Mode } from "../ui";
|
||||
"#app/inputs-controller.js";
|
||||
import AbstractSettingsUiHandler from "./abstract-settings-ui-handler";
|
||||
import { Setting, SettingType } from "#app/system/settings/settings";
|
||||
import { SettingType } from "#app/system/settings/settings";
|
||||
|
||||
export default class SettingsAudioUiHandler extends AbstractSettingsUiHandler {
|
||||
/**
|
||||
|
@ -12,9 +12,8 @@ export default class SettingsAudioUiHandler extends AbstractSettingsUiHandler {
|
|||
* @param mode - The UI mode, optional.
|
||||
*/
|
||||
constructor(scene: BattleScene, mode: Mode | null = null) {
|
||||
super(scene, mode);
|
||||
super(scene, SettingType.AUDIO, mode);
|
||||
this.title = "Audio";
|
||||
this.settings = Setting.filter(s => s.type === SettingType.AUDIO);
|
||||
this.localStorageKey = "settings";
|
||||
this.rowsToDisplay = 4;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import BattleScene from "../../battle-scene";
|
|||
import { Mode } from "../ui";
|
||||
"#app/inputs-controller.js";
|
||||
import AbstractSettingsUiHandler from "./abstract-settings-ui-handler";
|
||||
import { Setting, SettingKeys, SettingType } from "#app/system/settings/settings";
|
||||
import { SettingKeys, SettingType } from "#app/system/settings/settings";
|
||||
|
||||
export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler {
|
||||
/**
|
||||
|
@ -12,9 +12,8 @@ export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler
|
|||
* @param mode - The UI mode, optional.
|
||||
*/
|
||||
constructor(scene: BattleScene, mode: Mode | null = null) {
|
||||
super(scene, mode);
|
||||
super(scene, SettingType.DISPLAY, mode);
|
||||
this.title = "Display";
|
||||
this.settings = Setting.filter(s => s.type === SettingType.DISPLAY);
|
||||
|
||||
/**
|
||||
* Update to current language from default value.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import BattleScene from "../../battle-scene";
|
||||
import {Setting, SettingType} from "../../system/settings/settings";
|
||||
import { SettingType } from "../../system/settings/settings";
|
||||
import { Mode } from "../ui";
|
||||
import AbstractSettingsUiHandler from "./abstract-settings-ui-handler";
|
||||
|
||||
|
@ -11,9 +11,8 @@ export default class SettingsUiHandler extends AbstractSettingsUiHandler {
|
|||
* @param mode - The UI mode, optional.
|
||||
*/
|
||||
constructor(scene: BattleScene, mode: Mode | null = null) {
|
||||
super(scene, mode);
|
||||
super(scene, SettingType.GENERAL, mode);
|
||||
this.title = "General";
|
||||
this.settings = Setting.filter(s => s.type === SettingType.GENERAL);
|
||||
this.localStorageKey = "settings";
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue