teamsgoal24 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +27 -0
- package/src/GameImg.js +24 -0
- package/src/GameText.js +15 -0
- package/src/InputController.js +376 -0
- package/src/LayoutManger.js +62 -0
- package/src/UIBuilder.js +86 -0
- package/src/index.js +6 -0
- package/src/keyBoardLayout.js +106 -0
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "teamsgoal24",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Reusable Phaser UI components, keyboard input system and layout manager",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"phaser",
|
|
15
|
+
"phaser4",
|
|
16
|
+
"ui",
|
|
17
|
+
"input",
|
|
18
|
+
"keyboard",
|
|
19
|
+
"layout"
|
|
20
|
+
],
|
|
21
|
+
"author": "TECHTEAM",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"phaser": "^4.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
package/src/GameImg.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as Phaser from "phaser";
|
|
2
|
+
export default class GameImg extends Phaser.GameObjects.Image {
|
|
3
|
+
constructor(scene, x, y, textureKey, frameName = null, options = {}) {
|
|
4
|
+
if (frameName) {
|
|
5
|
+
super(scene, x, y, textureKey, frameName);
|
|
6
|
+
} else {
|
|
7
|
+
super(scene, x, y, textureKey);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
this.setOrigin(options.originX ?? 0.5, options.originY ?? 0.5);
|
|
11
|
+
this.setScale(options.scale ?? 1);
|
|
12
|
+
this.setAlpha(options.alpha ?? 1);
|
|
13
|
+
|
|
14
|
+
if (options.clickEffect) {
|
|
15
|
+
this.on("pointerdown", () => this.setScale(this.scale * 0.95));
|
|
16
|
+
this.on("pointerup", () => this.setScale(this.scale / 0.95));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (options.onClick) {
|
|
20
|
+
this.on("pointerup", options.onClick);
|
|
21
|
+
}
|
|
22
|
+
scene.add.existing(this);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/GameText.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as Phaser from "phaser";
|
|
2
|
+
export default class GameText extends Phaser.GameObjects.Text {
|
|
3
|
+
constructor(scene, x, y, text, styleOverrides = {}) {
|
|
4
|
+
const defaultStyle = {
|
|
5
|
+
fontSize: "42px",
|
|
6
|
+
fontFamily: "ROBOTO_MEDIUM",
|
|
7
|
+
color: "#ffffff",
|
|
8
|
+
stroke: "#ffffff",
|
|
9
|
+
align: "center",
|
|
10
|
+
};
|
|
11
|
+
const finalStyle = Object.assign({}, defaultStyle, styleOverrides);
|
|
12
|
+
super(scene, x, y, text, finalStyle);
|
|
13
|
+
scene.add.existing(this);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { KeyBoardLayout } from "./keyBoardLayout";
|
|
2
|
+
import LayoutManager from "./LayoutManger";
|
|
3
|
+
import UIBuilder from "./UIBuilder";
|
|
4
|
+
export default class InputController {
|
|
5
|
+
constructor(scene) {
|
|
6
|
+
this.scene = scene;
|
|
7
|
+
this.container = scene.add.container();
|
|
8
|
+
this.layout = new LayoutManager(scene.game);
|
|
9
|
+
this.builder = new UIBuilder(scene, this.layout);
|
|
10
|
+
this.activeInput = null;
|
|
11
|
+
this.cursor = null;
|
|
12
|
+
this.cursorIndex = 0;
|
|
13
|
+
this.cursorBlink = null;
|
|
14
|
+
this.keyboard = null;
|
|
15
|
+
this.keyboardUI = null;
|
|
16
|
+
this.selectionBg = null;
|
|
17
|
+
this.isTextSelected = false;
|
|
18
|
+
this.createKeyboard();
|
|
19
|
+
this.scene.game.events.on("Resize", this.onGameResize, this);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
createKeyboard() {
|
|
23
|
+
|
|
24
|
+
const isMobile =
|
|
25
|
+
this.scene.sys.game.device.os.android ||
|
|
26
|
+
this.scene.sys.game.device.os.iOS;
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if (!isMobile) return;
|
|
30
|
+
this.keyboard = this.builder.build(
|
|
31
|
+
{ keyBoard: KeyBoardLayout.keyBoard },
|
|
32
|
+
this.container,
|
|
33
|
+
);
|
|
34
|
+
this.keyboardUI = this.keyboard.keyBoard.ui;
|
|
35
|
+
this.keyboardUI.keyBoardBg.setInteractive();
|
|
36
|
+
this.keyboard.keyBoard.setVisible(false);
|
|
37
|
+
this.keyMap = {
|
|
38
|
+
key1: "1",
|
|
39
|
+
key2: "2",
|
|
40
|
+
key3: "3",
|
|
41
|
+
key4: "4",
|
|
42
|
+
key5: "5",
|
|
43
|
+
key6: "6",
|
|
44
|
+
key7: "7",
|
|
45
|
+
key8: "8",
|
|
46
|
+
key9: "9",
|
|
47
|
+
key0: "0",
|
|
48
|
+
point: ".",
|
|
49
|
+
delete: "⌫",
|
|
50
|
+
};
|
|
51
|
+
Object.keys(this.keyMap).forEach((keyName) => {
|
|
52
|
+
const keyObj = this.keyboardUI[keyName];
|
|
53
|
+
keyObj.setInteractive({ useHandCursor: true });
|
|
54
|
+
keyObj.on("pointerdown", () => {
|
|
55
|
+
this.handleKey(this.keyMap[keyName]);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
this.keyboardUI.input?.setOrigin(0.5);
|
|
59
|
+
}
|
|
60
|
+
attach(background, input, options = {}) {
|
|
61
|
+
this.inputOptions = options;
|
|
62
|
+
|
|
63
|
+
const triggers = Array.isArray(background) ? background : [background];
|
|
64
|
+
|
|
65
|
+
triggers.forEach((bg) => {
|
|
66
|
+
bg.setInteractive({ useHandCursor: true });
|
|
67
|
+
|
|
68
|
+
bg.on("pointerup", (pointer) => {
|
|
69
|
+
pointer.event.stopPropagation();
|
|
70
|
+
options.onBeforeFocus?.({
|
|
71
|
+
trigger: bg,
|
|
72
|
+
input,
|
|
73
|
+
});
|
|
74
|
+
this.activeInput = input;
|
|
75
|
+
this.cursorIndex = input.text.length;
|
|
76
|
+
if (this.keyboardUI?.input) {
|
|
77
|
+
this.keyboardUI.input.setText(input.text);
|
|
78
|
+
}
|
|
79
|
+
if (!this.cursor) {
|
|
80
|
+
this.createCursor(input);
|
|
81
|
+
}
|
|
82
|
+
if (this.isTextSelected && this.selectionBg) {
|
|
83
|
+
this.selectionBg.destroy();
|
|
84
|
+
this.selectionBg = null;
|
|
85
|
+
this.isTextSelected = false;
|
|
86
|
+
if (this.cursor) this.cursor.setVisible(true);
|
|
87
|
+
if (this.cursorBlink) this.cursorBlink.paused = false;
|
|
88
|
+
this.updateCursorPosition();
|
|
89
|
+
} else {
|
|
90
|
+
if (this.cursor) this.cursor.setVisible(false);
|
|
91
|
+
if (this.cursorBlink) this.cursorBlink.paused = true;
|
|
92
|
+
this.highlightText(input);
|
|
93
|
+
this.isTextSelected = true;
|
|
94
|
+
this.updateCursorPosition();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (this.keyboard?.keyBoard) {
|
|
98
|
+
const isTouch =
|
|
99
|
+
pointer.pointerType === "touch" ||
|
|
100
|
+
pointer.event?.type === "touchend";
|
|
101
|
+
|
|
102
|
+
if (isTouch) {
|
|
103
|
+
this.container.setDepth(options.depth);
|
|
104
|
+
this.keyboard.keyBoard.setVisible(true);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this.scene.input.keyboard.on("keydown", (event) => {
|
|
111
|
+
if (!this.activeInput) return;
|
|
112
|
+
this.handleKey(event.key);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
this.scene.input.on("pointerdown", (pointer) => {
|
|
116
|
+
const clickedInput = triggers.some((bg) =>
|
|
117
|
+
bg.getBounds().contains(pointer.worldX, pointer.worldY),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const kbBounds = this.keyboard?.keyBoard
|
|
121
|
+
? this.keyboard.keyBoard.getBounds()
|
|
122
|
+
: null;
|
|
123
|
+
|
|
124
|
+
const clickedKeyboard = kbBounds
|
|
125
|
+
? kbBounds.contains(pointer.worldX, pointer.worldY)
|
|
126
|
+
: false;
|
|
127
|
+
|
|
128
|
+
if (!clickedInput && !clickedKeyboard) {
|
|
129
|
+
this.blur();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
handleKey(key) {
|
|
134
|
+
let MAX_DIGITS = 9;
|
|
135
|
+
|
|
136
|
+
if (!this.activeInput) return;
|
|
137
|
+
let text = this.activeInput.text || "";
|
|
138
|
+
if (text.includes(".") && this.inputOptions?.currencyType === "Fiat") {
|
|
139
|
+
MAX_DIGITS = 9;
|
|
140
|
+
}
|
|
141
|
+
else if (this.inputOptions?.currencyType === "Fiat") {
|
|
142
|
+
MAX_DIGITS = 7;
|
|
143
|
+
}
|
|
144
|
+
if (this.isTextSelected) {
|
|
145
|
+
if (key === "Backspace" || key === "⌫") {
|
|
146
|
+
text = "";
|
|
147
|
+
this.cursorIndex = 0;
|
|
148
|
+
} else if (/^[0-9.]$/.test(key)) {
|
|
149
|
+
text = key === "." ? "0." : key;
|
|
150
|
+
this.cursorIndex = text.length;
|
|
151
|
+
}
|
|
152
|
+
this.isTextSelected = false;
|
|
153
|
+
if (this.selectionBg) {
|
|
154
|
+
this.selectionBg.destroy();
|
|
155
|
+
this.selectionBg = null;
|
|
156
|
+
}
|
|
157
|
+
if (this.cursor) this.cursor.setVisible(true);
|
|
158
|
+
if (this.cursorBlink) this.cursorBlink.paused = false;
|
|
159
|
+
this.applyValue(text, this.activeInput, this.inputOptions);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const MAX_DECIMALS = this.inputOptions?.currencyType === "Fiat" ? 2 : 6;
|
|
164
|
+
if (key === "Backspace" || key === "⌫") {
|
|
165
|
+
if (this.cursorIndex > 0) {
|
|
166
|
+
text =
|
|
167
|
+
text.slice(0, this.cursorIndex - 1) + text.slice(this.cursorIndex);
|
|
168
|
+
this.cursorIndex--;
|
|
169
|
+
}
|
|
170
|
+
} else if (key === "Enter") {
|
|
171
|
+
this.blur();
|
|
172
|
+
return;
|
|
173
|
+
} else if (key === "." && !text.includes(".") && text.length < MAX_DIGITS) {
|
|
174
|
+
text =
|
|
175
|
+
text.slice(0, this.cursorIndex) + "." + text.slice(this.cursorIndex);
|
|
176
|
+
this.cursorIndex++;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
else if (/^[0-9]$/.test(key)) {
|
|
180
|
+
|
|
181
|
+
if (text === "0") {
|
|
182
|
+
text = "";
|
|
183
|
+
this.cursorIndex = 0;
|
|
184
|
+
}
|
|
185
|
+
const digitsCount = text.replace(".", "").length;
|
|
186
|
+
if (digitsCount >= MAX_DIGITS) {
|
|
187
|
+
text =
|
|
188
|
+
text.slice(0, this.cursorIndex - 1) +
|
|
189
|
+
key +
|
|
190
|
+
text.slice(this.cursorIndex);
|
|
191
|
+
|
|
192
|
+
return this.applyValue(text, this.activeInput, this.inputOptions);
|
|
193
|
+
}
|
|
194
|
+
if (text.includes(".")) {
|
|
195
|
+
const dotIndex = text.indexOf(".");
|
|
196
|
+
const decPart = text.split(".")[1] || "";
|
|
197
|
+
if (this.cursorIndex > dotIndex) {
|
|
198
|
+
const decimalCursorPos = this.cursorIndex - dotIndex - 1;
|
|
199
|
+
if (decPart.length >= MAX_DECIMALS) {
|
|
200
|
+
if (decimalCursorPos >= MAX_DECIMALS) {
|
|
201
|
+
text =
|
|
202
|
+
text.slice(0, dotIndex + 1 + MAX_DECIMALS - 1) +
|
|
203
|
+
key +
|
|
204
|
+
text.slice(dotIndex + 1 + MAX_DECIMALS);
|
|
205
|
+
this.cursorIndex = dotIndex + 1 + MAX_DECIMALS;
|
|
206
|
+
return this.applyValue(text, this.activeInput, this.inputOptions);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (digitsCount < MAX_DIGITS) {
|
|
212
|
+
text = text.slice(0, this.cursorIndex) + key + text.slice(this.cursorIndex);
|
|
213
|
+
this.cursorIndex++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this.applyValue(text, this.activeInput, this.inputOptions);
|
|
217
|
+
}
|
|
218
|
+
applyValue(text, input) {
|
|
219
|
+
const { min = 0, max = Infinity, onChange } = this.inputOptions;
|
|
220
|
+
let finalText = text;
|
|
221
|
+
finalText = finalText.replace(/^0+(?=\d)/, "");
|
|
222
|
+
if (finalText.startsWith(".")) {
|
|
223
|
+
finalText = "0" + finalText;
|
|
224
|
+
}
|
|
225
|
+
if (finalText === "") {
|
|
226
|
+
finalText = "0";
|
|
227
|
+
this.cursorIndex = 1;
|
|
228
|
+
this.activeInput.setText(finalText);
|
|
229
|
+
if (this.keyboardUI?.input) {
|
|
230
|
+
this.keyboardUI.input.setText(finalText);
|
|
231
|
+
}
|
|
232
|
+
onChange?.({
|
|
233
|
+
value: null,
|
|
234
|
+
status: "empty",
|
|
235
|
+
rawText: "",
|
|
236
|
+
});
|
|
237
|
+
this.updateCursorPosition();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const value = Number(finalText);
|
|
241
|
+
let status = "valid";
|
|
242
|
+
if (value < min) status = "below-min";
|
|
243
|
+
else if (value > max) status = "above-max";
|
|
244
|
+
this.activeInput.setText(finalText);
|
|
245
|
+
if (this.keyboardUI?.input) {
|
|
246
|
+
this.keyboardUI.input.setText(finalText);
|
|
247
|
+
}
|
|
248
|
+
onChange?.({ value, status, rawText: finalText });
|
|
249
|
+
this.updateCursorPosition();
|
|
250
|
+
}
|
|
251
|
+
createCursor(input) {
|
|
252
|
+
if (this.cursor) this.cursor.destroy();
|
|
253
|
+
if (this.cursorBlink) this.cursorBlink.remove();
|
|
254
|
+
const visualScale = this.getVisualScale(input);
|
|
255
|
+
const fontSize = parseInt(input.style.fontSize || 32) * visualScale.y;
|
|
256
|
+
this.cursor = this.scene.add.text(0, 0, "|", {
|
|
257
|
+
fontSize: `${fontSize}px`,
|
|
258
|
+
fontFamily: input.style.fontFamily,
|
|
259
|
+
color: "#fff",
|
|
260
|
+
});
|
|
261
|
+
this.cursor.setOrigin(0, input.originY ?? 0);
|
|
262
|
+
input.parentContainer.add(this.cursor);
|
|
263
|
+
this.cursorBlink = this.scene.time.addEvent({
|
|
264
|
+
delay: 500,
|
|
265
|
+
loop: true,
|
|
266
|
+
callback: () => {
|
|
267
|
+
if (this.cursor) this.cursor.visible = !this.cursor.visible;
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
updateCursorPosition() {
|
|
272
|
+
if (!this.activeInput || !this.cursor) return;
|
|
273
|
+
const input = this.activeInput;
|
|
274
|
+
const textBefore = input.text.slice(0, this.cursorIndex);
|
|
275
|
+
const context = input.context;
|
|
276
|
+
const visualScale = this.getVisualScale(input);
|
|
277
|
+
context.font = `${parseInt(input.style.fontSize || 32)}px ${input.style.fontFamily || "Arial"
|
|
278
|
+
}`;
|
|
279
|
+
const textWidth = context.measureText(textBefore).width * visualScale.x;
|
|
280
|
+
const padding = 6 * visualScale.x;
|
|
281
|
+
const left = input.x - input.width * (input.originX ?? 0);
|
|
282
|
+
this.cursor.setPosition(left + textWidth + padding, input.y - 2);
|
|
283
|
+
this.cursor.setOrigin(0, input.originY ?? 0);
|
|
284
|
+
}
|
|
285
|
+
blur() {
|
|
286
|
+
if (this.activeInput && this.activeInput.text === "") {
|
|
287
|
+
this.activeInput.setText("0");
|
|
288
|
+
}
|
|
289
|
+
if (this.activeInput && this.inputOptions?.onClose) {
|
|
290
|
+
const text = this.activeInput.text || "0";
|
|
291
|
+
const value = Number(text);
|
|
292
|
+
let status = "valid";
|
|
293
|
+
if (value < (this.inputOptions.min ?? 0)) status = "below-min";
|
|
294
|
+
else if (value > (this.inputOptions.max ?? Infinity)) {
|
|
295
|
+
status = "above-max";
|
|
296
|
+
}
|
|
297
|
+
this.inputOptions.onClose({
|
|
298
|
+
value,
|
|
299
|
+
status,
|
|
300
|
+
rawText: text,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
this.activeInput = null;
|
|
304
|
+
this.cursorIndex = 0;
|
|
305
|
+
if (this.cursor) {
|
|
306
|
+
this.cursor.destroy();
|
|
307
|
+
this.cursor = null;
|
|
308
|
+
}
|
|
309
|
+
if (this.keyboard?.keyBoard) {
|
|
310
|
+
this.keyboard.keyBoard.setVisible(false);
|
|
311
|
+
}
|
|
312
|
+
if (this.cursorBlink) {
|
|
313
|
+
this.cursorBlink.remove();
|
|
314
|
+
this.cursorBlink = null;
|
|
315
|
+
}
|
|
316
|
+
if (this.selectionBg) {
|
|
317
|
+
this.selectionBg.destroy();
|
|
318
|
+
this.selectionBg = null;
|
|
319
|
+
}
|
|
320
|
+
this.isTextSelected = false;
|
|
321
|
+
}
|
|
322
|
+
hideKeyboard() {
|
|
323
|
+
this.blur();
|
|
324
|
+
}
|
|
325
|
+
onGameResize() {
|
|
326
|
+
if (!this.cursor) return;
|
|
327
|
+
this.cursor.setVisible(false);
|
|
328
|
+
if (this.cursorBlink) {
|
|
329
|
+
this.cursorBlink.paused = true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
hideAll() {
|
|
333
|
+
Object.values(this.keyboardUI).forEach((obj) => {
|
|
334
|
+
obj.setVisible(false);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
highlightText(input) {
|
|
338
|
+
const text = input.text || "";
|
|
339
|
+
const context = input.context;
|
|
340
|
+
const visualScale = this.getVisualScale(input);
|
|
341
|
+
context.font = `${parseInt(input.style.fontSize || 32)}px ${input.style.fontFamily || "Arial"
|
|
342
|
+
}`;
|
|
343
|
+
const width =
|
|
344
|
+
context.measureText(text).width * visualScale.x + 8 * visualScale.x;
|
|
345
|
+
const height =
|
|
346
|
+
parseInt(input.style.fontSize || 32) * visualScale.y + 10 * visualScale.y;
|
|
347
|
+
this.cursorIndex = text.length;
|
|
348
|
+
if (this.selectionBg) {
|
|
349
|
+
this.selectionBg.destroy();
|
|
350
|
+
}
|
|
351
|
+
const left = input.x - input.width * (input.originX ?? 0);
|
|
352
|
+
const top = input.y - input.height * (input.originY ?? 0);
|
|
353
|
+
this.selectionBg = this.scene.add
|
|
354
|
+
.rectangle(left, top - 2, width, height, 0x3399ff, 0.7)
|
|
355
|
+
.setOrigin(0, 0);
|
|
356
|
+
input.parentContainer.addAt(
|
|
357
|
+
this.selectionBg,
|
|
358
|
+
input.parentContainer.getIndex(input),
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
getVisualScale(obj) {
|
|
363
|
+
let scaleX = 1;
|
|
364
|
+
let scaleY = 1;
|
|
365
|
+
let current = obj;
|
|
366
|
+
while (current) {
|
|
367
|
+
scaleX *= current.scaleX ?? 1;
|
|
368
|
+
scaleY *= current.scaleY ?? 1;
|
|
369
|
+
current = current.parentContainer;
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
x: scaleX,
|
|
373
|
+
y: scaleY,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export default class LayoutManager {
|
|
2
|
+
constructor(game) {
|
|
3
|
+
this.game = game;
|
|
4
|
+
this.items = [];
|
|
5
|
+
this.isPortrait = this.getOrientation();
|
|
6
|
+
this.isInputActive = false;
|
|
7
|
+
this.isMobile = this.game.device.os.android || this.game.device.os.iOS;
|
|
8
|
+
this.baseWidth = window.innerWidth;
|
|
9
|
+
this.baseHeight = window.innerHeight;
|
|
10
|
+
this.keyboardOpen = false;
|
|
11
|
+
window.addEventListener("resize", this.handleResize.bind(this));
|
|
12
|
+
this.game.scale.on("resize", this.resize, this);
|
|
13
|
+
}
|
|
14
|
+
handleResize() {
|
|
15
|
+
const currentHeight = window.innerHeight;
|
|
16
|
+
const diff = this.baseHeight - currentHeight;
|
|
17
|
+
this.keyboardOpen = this.isMobile && diff > window.innerHeight * 0.25;
|
|
18
|
+
if (!this.keyboardOpen) {
|
|
19
|
+
this.baseHeight = currentHeight;
|
|
20
|
+
this.baseWidth = window.innerWidth;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
register(object, config) {
|
|
25
|
+
this.items.push({ object, config });
|
|
26
|
+
this.applyLayout(object, config);
|
|
27
|
+
}
|
|
28
|
+
resize() {
|
|
29
|
+
const width = window.innerWidth;
|
|
30
|
+
const height = window.innerHeight;
|
|
31
|
+
|
|
32
|
+
let isPortrait;
|
|
33
|
+
|
|
34
|
+
if (this.keyboardOpen || this.isInputActive) {
|
|
35
|
+
isPortrait = this.isPortrait ?? this.getOrientation();
|
|
36
|
+
} else {
|
|
37
|
+
isPortrait = this.getOrientation();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.isPortrait = isPortrait;
|
|
41
|
+
|
|
42
|
+
this.items.forEach(({ object, config }) => {
|
|
43
|
+
this.applyLayout(object, config);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
applyLayout(obj, config) {
|
|
47
|
+
const layout =
|
|
48
|
+
(this.isPortrait ? config.portrait : config.landscape) ||
|
|
49
|
+
config.landscape ||
|
|
50
|
+
config.portrait ||
|
|
51
|
+
{};
|
|
52
|
+
obj.setPosition(layout.x ?? 0, layout.y ?? 0);
|
|
53
|
+
obj.setScale(layout.scale ?? 1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getOrientation() {
|
|
57
|
+
if (screen.orientation) {
|
|
58
|
+
return screen.orientation.type.includes("portrait");
|
|
59
|
+
}
|
|
60
|
+
return window.innerHeight > window.innerWidth;
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/UIBuilder.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import GameImg from "./GameImg";
|
|
2
|
+
import GameText from "./GameText";
|
|
3
|
+
|
|
4
|
+
export default class UIBuilder {
|
|
5
|
+
constructor(scene, layoutManager) {
|
|
6
|
+
this.scene = scene;
|
|
7
|
+
this.layout = layoutManager;
|
|
8
|
+
}
|
|
9
|
+
build(config, parent = null) {
|
|
10
|
+
const ui = {};
|
|
11
|
+
Object.entries(config).forEach(([key, item]) => {
|
|
12
|
+
const obj = this.createObject(item);
|
|
13
|
+
if (!obj) return;
|
|
14
|
+
if (parent) parent.add(obj);
|
|
15
|
+
if (item.x !== undefined || item.y !== undefined) {
|
|
16
|
+
obj.setPosition(item.x ?? 0, item.y ?? 0);
|
|
17
|
+
}
|
|
18
|
+
if (item.scale !== undefined) {
|
|
19
|
+
obj.setScale(item.scale);
|
|
20
|
+
}
|
|
21
|
+
if (item.scaleX !== undefined || item.scaleY !== undefined) {
|
|
22
|
+
obj.setScale(item.scaleX ?? 1, item.scaleY ?? 1);
|
|
23
|
+
}
|
|
24
|
+
if (item.portrait || item.landscape) {
|
|
25
|
+
this.layout.register(obj, item);
|
|
26
|
+
}
|
|
27
|
+
if (item.underline && obj.type === "Text") {
|
|
28
|
+
this.addUnderline(obj, item, parent);
|
|
29
|
+
}
|
|
30
|
+
ui[key] = obj;
|
|
31
|
+
if (item.children) {
|
|
32
|
+
obj.ui = this.build(item.children, obj);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return ui;
|
|
36
|
+
}
|
|
37
|
+
addUnderline(obj, config, parent) {
|
|
38
|
+
const width = obj.width;
|
|
39
|
+
const height = obj.height;
|
|
40
|
+
const originX = obj.originX;
|
|
41
|
+
const originY = obj.originY;
|
|
42
|
+
const offsetX = (0.5 - originX) * width;
|
|
43
|
+
const offsetY = (1 - originY) * height;
|
|
44
|
+
const line = this.scene.add.rectangle(
|
|
45
|
+
obj.x + offsetX,
|
|
46
|
+
obj.y + offsetY,
|
|
47
|
+
width,
|
|
48
|
+
config.underline?.thickness ?? 2,
|
|
49
|
+
Phaser.Display.Color.HexStringToColor(
|
|
50
|
+
config.underline?.color ?? obj.style.color,
|
|
51
|
+
).color,
|
|
52
|
+
);
|
|
53
|
+
line.setOrigin(0.5, 0);
|
|
54
|
+
if (parent) parent.add(line);
|
|
55
|
+
}
|
|
56
|
+
createObject(config) {
|
|
57
|
+
const scene = this.scene;
|
|
58
|
+
const type = config.type ?? "image";
|
|
59
|
+
switch (type) {
|
|
60
|
+
case "image":
|
|
61
|
+
if (config.asset?.atlas) {
|
|
62
|
+
return new GameImg(
|
|
63
|
+
scene,
|
|
64
|
+
0,
|
|
65
|
+
0,
|
|
66
|
+
config.asset?.atlas,
|
|
67
|
+
config.asset?.frame,
|
|
68
|
+
);
|
|
69
|
+
} else {
|
|
70
|
+
return new GameImg(scene, 0, 0, config.asset?.frame);
|
|
71
|
+
}
|
|
72
|
+
case "text":
|
|
73
|
+
return new GameText(
|
|
74
|
+
scene,
|
|
75
|
+
0,
|
|
76
|
+
0,
|
|
77
|
+
config.text?.value ?? "",
|
|
78
|
+
config.text?.style ?? {},
|
|
79
|
+
);
|
|
80
|
+
case "container":
|
|
81
|
+
return scene.add.container(0, 0);
|
|
82
|
+
default:
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as GameImg } from "./GameImg.js";
|
|
2
|
+
export { default as GameText } from "./GameText.js";
|
|
3
|
+
export { default as LayoutManager } from "./LayoutManger.js";
|
|
4
|
+
export { default as UIBuilder } from "./UIBuilder.js";
|
|
5
|
+
export { default as InputController } from "./InputController.js";
|
|
6
|
+
export { KeyBoardLayout } from "./keyBoardLayout.js";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2
|
+
const getFrame = (name) => `${isDark ? "D" : "L"}_${name}.png`;
|
|
3
|
+
const getBg = (name) => `${isDark ? "D" : "L"}_${name}`;
|
|
4
|
+
export const KeyBoardLayout = {
|
|
5
|
+
keyBoard: {
|
|
6
|
+
type: "container",
|
|
7
|
+
children: {
|
|
8
|
+
keyBoardBg: {
|
|
9
|
+
type: "image",
|
|
10
|
+
asset: {
|
|
11
|
+
atlas: "",
|
|
12
|
+
frame: isDark ? getBg("background") : getBg("background"),
|
|
13
|
+
},
|
|
14
|
+
portrait: { x: 600, y: 2200, scale: 2 },
|
|
15
|
+
landscape: { x: 960, y: 870, scale: 0.8 },
|
|
16
|
+
},
|
|
17
|
+
input: {
|
|
18
|
+
type: "text",
|
|
19
|
+
text: {
|
|
20
|
+
value: "100",
|
|
21
|
+
style: {
|
|
22
|
+
fontFamily: "ROBOTO_BLACK",
|
|
23
|
+
fontSize: "62px",
|
|
24
|
+
color: "#fff",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
portrait: { x: 610, y: 1800, scale: 1 },
|
|
28
|
+
landscape: { x: 960, y: 730, scale: 1 },
|
|
29
|
+
},
|
|
30
|
+
key1: {
|
|
31
|
+
type: "image",
|
|
32
|
+
asset: { atlas: "keyBoard", frame: getFrame("1") },
|
|
33
|
+
portrait: { x: 210, y: 1970 },
|
|
34
|
+
landscape: { x: 210, y: 850, scale: 0.7 },
|
|
35
|
+
},
|
|
36
|
+
key2: {
|
|
37
|
+
type: "image",
|
|
38
|
+
asset: { atlas: "keyBoard", frame: getFrame("2") },
|
|
39
|
+
portrait: { x: 610, y: 1970 },
|
|
40
|
+
landscape: { x: 510, y: 850, scale: 0.7 },
|
|
41
|
+
},
|
|
42
|
+
key3: {
|
|
43
|
+
type: "image",
|
|
44
|
+
asset: { atlas: "keyBoard", frame: getFrame("3") },
|
|
45
|
+
portrait: { x: 1010, y: 1970 },
|
|
46
|
+
landscape: { x: 810, y: 850, scale: 0.7 },
|
|
47
|
+
},
|
|
48
|
+
key4: {
|
|
49
|
+
type: "image",
|
|
50
|
+
asset: { atlas: "keyBoard", frame: getFrame("4") },
|
|
51
|
+
portrait: { x: 210, y: 2170 },
|
|
52
|
+
landscape: { x: 1110, y: 850, scale: 0.7 },
|
|
53
|
+
},
|
|
54
|
+
key5: {
|
|
55
|
+
type: "image",
|
|
56
|
+
asset: { atlas: "keyBoard", frame: getFrame("5") },
|
|
57
|
+
portrait: { x: 610, y: 2170 },
|
|
58
|
+
landscape: { x: 1410, y: 850, scale: 0.7 },
|
|
59
|
+
},
|
|
60
|
+
key6: {
|
|
61
|
+
type: "image",
|
|
62
|
+
asset: { atlas: "keyBoard", frame: getFrame("6") },
|
|
63
|
+
portrait: { x: 1010, y: 2170 },
|
|
64
|
+
landscape: { x: 210, y: 1000, scale: 0.7 },
|
|
65
|
+
},
|
|
66
|
+
key7: {
|
|
67
|
+
type: "image",
|
|
68
|
+
asset: { atlas: "keyBoard", frame: getFrame("7") },
|
|
69
|
+
portrait: { x: 210, y: 2370 },
|
|
70
|
+
landscape: { x: 510, y: 1000, scale: 0.7 },
|
|
71
|
+
},
|
|
72
|
+
key8: {
|
|
73
|
+
type: "image",
|
|
74
|
+
asset: { atlas: "keyBoard", frame: getFrame("8") },
|
|
75
|
+
portrait: { x: 610, y: 2370 },
|
|
76
|
+
landscape: { x: 810, y: 1000, scale: 0.7 },
|
|
77
|
+
},
|
|
78
|
+
key9: {
|
|
79
|
+
type: "image",
|
|
80
|
+
asset: { atlas: "keyBoard", frame: getFrame("9") },
|
|
81
|
+
portrait: { x: 1010, y: 2370 },
|
|
82
|
+
landscape: { x: 1110, y: 1000, scale: 0.7 },
|
|
83
|
+
},
|
|
84
|
+
point: {
|
|
85
|
+
type: "image",
|
|
86
|
+
asset: { atlas: "keyBoard", frame: getFrame("point") },
|
|
87
|
+
portrait: { x: 210, y: 2570 },
|
|
88
|
+
landscape: { x: 1710, y: 850, scale: 0.7 },
|
|
89
|
+
},
|
|
90
|
+
key0: {
|
|
91
|
+
type: "image",
|
|
92
|
+
asset: { atlas: "keyBoard", frame: getFrame("0") },
|
|
93
|
+
portrait: { x: 610, y: 2570 },
|
|
94
|
+
landscape: { x: 1410, y: 1000, scale: 0.7 },
|
|
95
|
+
},
|
|
96
|
+
delete: {
|
|
97
|
+
type: "image",
|
|
98
|
+
asset: { atlas: "keyBoard", frame: getFrame("delete") },
|
|
99
|
+
portrait: { x: 1010, y: 2570 },
|
|
100
|
+
landscape: { x: 1710, y: 1000, scale: 0.7 },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default KeyBoardLayout;
|