@wwawing/virtual-pad 0.0.0-exe-chousa.based-on.3.12.21
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/LICENSE +22 -0
- package/lib/VirtualPad.d.ts +31 -0
- package/lib/VirtualPad.js +211 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/makeInject.d.ts +1 -0
- package/lib/makeInject.js +16 -0
- package/lib/viewportFit.d.ts +2 -0
- package/lib/viewportFit.js +24 -0
- package/package.json +25 -0
- package/src/VirtualPad.ts +373 -0
- package/src/index.ts +14 -0
- package/src/makeInject.ts +16 -0
- package/src/viewportFit.ts +39 -0
- package/tsconfig.json +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 1996-2025 NAO
|
|
4
|
+
Copyright (c) 2015-2025 WWA Wing Team
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const VirtualPadButtonCodes: readonly ["BUTTON_ENTER", "BUTTON_ESC", "BUTTON_SIDEBAR", "BUTTON_FAST", "BUTTON_SLOW", "BUTTON_LEFT", "BUTTON_UP", "BUTTON_RIGHT", "BUTTON_DOWN"];
|
|
2
|
+
export type VirtualPadButtonCode = typeof VirtualPadButtonCodes[number];
|
|
3
|
+
export type VirtualPadState = "NONE" | "TOUCH" | "TOUCHING" | "LEAVE";
|
|
4
|
+
export type VirtualPadButtons = {
|
|
5
|
+
[key in string]: HTMLButtonElement;
|
|
6
|
+
};
|
|
7
|
+
type VirtualPadEventFunction = (buttonCode: VirtualPadButtonCode) => void;
|
|
8
|
+
export default class VirtualPadStore {
|
|
9
|
+
private _enabled;
|
|
10
|
+
private _visible;
|
|
11
|
+
private _onTouchStart;
|
|
12
|
+
private _onTouchEnd;
|
|
13
|
+
private _availableButtons;
|
|
14
|
+
private _isTouchingButtons;
|
|
15
|
+
private _moveButtonsElement;
|
|
16
|
+
private _buttonWrapperElement;
|
|
17
|
+
constructor(buttons: VirtualPadButtons | null, firstVisible?: boolean, buttonWrapper?: HTMLElement | null, moveButtons?: HTMLElement | null, onTouchStart?: VirtualPadEventFunction | null, onTouchEnd?: VirtualPadEventFunction | null);
|
|
18
|
+
private _detectMovingMoveButton;
|
|
19
|
+
private _setVisible;
|
|
20
|
+
checkTouchButton(buttonType: VirtualPadButtonCode): boolean;
|
|
21
|
+
checkTouchingButton(buttonType: VirtualPadButtonCode): boolean;
|
|
22
|
+
getButtonState(buttonType: VirtualPadButtonCode): VirtualPadState;
|
|
23
|
+
setTouchInfo(buttonType: VirtualPadButtonCode): void;
|
|
24
|
+
clearTouchInfo(buttonType: VirtualPadButtonCode): void;
|
|
25
|
+
allMoveClear(): void;
|
|
26
|
+
allClear(): void;
|
|
27
|
+
static isMoveButton(buttonType: VirtualPadButtonCode): boolean;
|
|
28
|
+
update(): void;
|
|
29
|
+
toggleVisible(): void;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
export const VirtualPadButtonCodes = [
|
|
2
|
+
"BUTTON_ENTER",
|
|
3
|
+
"BUTTON_ESC",
|
|
4
|
+
"BUTTON_SIDEBAR",
|
|
5
|
+
"BUTTON_FAST",
|
|
6
|
+
"BUTTON_SLOW",
|
|
7
|
+
"BUTTON_LEFT",
|
|
8
|
+
"BUTTON_UP",
|
|
9
|
+
"BUTTON_RIGHT",
|
|
10
|
+
"BUTTON_DOWN"
|
|
11
|
+
];
|
|
12
|
+
const VirtualPadMoveButtonCodes = [
|
|
13
|
+
"BUTTON_LEFT",
|
|
14
|
+
"BUTTON_UP",
|
|
15
|
+
"BUTTON_RIGHT",
|
|
16
|
+
"BUTTON_DOWN"
|
|
17
|
+
];
|
|
18
|
+
const VirtualPadMoveButtonsSet = new Set(VirtualPadMoveButtonCodes);
|
|
19
|
+
export default class VirtualPadStore {
|
|
20
|
+
constructor(buttons, firstVisible = false, buttonWrapper = null, moveButtons = null, onTouchStart = null, onTouchEnd = null) {
|
|
21
|
+
this._enabled = buttons !== null;
|
|
22
|
+
this._visible = firstVisible;
|
|
23
|
+
this._onTouchStart = onTouchStart || null;
|
|
24
|
+
this._onTouchEnd = onTouchEnd || null;
|
|
25
|
+
this._availableButtons = [];
|
|
26
|
+
this._isTouchingButtons = {};
|
|
27
|
+
this._moveButtonsElement = moveButtons;
|
|
28
|
+
this._buttonWrapperElement = buttonWrapper;
|
|
29
|
+
this._setVisible(firstVisible);
|
|
30
|
+
if (this._moveButtonsElement) {
|
|
31
|
+
this._moveButtonsElement.style.visibility = "visible";
|
|
32
|
+
}
|
|
33
|
+
if (this._buttonWrapperElement) {
|
|
34
|
+
this._buttonWrapperElement.style.visibility = "visible";
|
|
35
|
+
}
|
|
36
|
+
if (buttons === null) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
for (const buttonTypeString in buttons) {
|
|
40
|
+
const button = buttons[buttonTypeString];
|
|
41
|
+
if (!button) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const buttonCode = buttonTypeString;
|
|
45
|
+
this._availableButtons.push(buttonCode);
|
|
46
|
+
button.setAttribute("type", buttonTypeString);
|
|
47
|
+
this._isTouchingButtons[buttonCode] = {
|
|
48
|
+
prev: false,
|
|
49
|
+
current: false,
|
|
50
|
+
next: false
|
|
51
|
+
};
|
|
52
|
+
const cancelBrowserTouchEvent = (event) => {
|
|
53
|
+
if (event.cancelable) {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const cancelVirtualPad = (event) => {
|
|
58
|
+
cancelBrowserTouchEvent(event);
|
|
59
|
+
this.allClear();
|
|
60
|
+
};
|
|
61
|
+
button.addEventListener("touchstart", (event) => {
|
|
62
|
+
cancelBrowserTouchEvent(event);
|
|
63
|
+
this.setTouchInfo(buttonCode);
|
|
64
|
+
});
|
|
65
|
+
button.addEventListener("touchcancel", cancelVirtualPad);
|
|
66
|
+
if (VirtualPadStore.isMoveButton(buttonCode)) {
|
|
67
|
+
button.addEventListener("touchmove", this._detectMovingMoveButton.bind(this));
|
|
68
|
+
button.addEventListener("touchend", this.allMoveClear.bind(this));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
button.addEventListener("touchend", (event) => {
|
|
72
|
+
cancelBrowserTouchEvent(event);
|
|
73
|
+
this.clearTouchInfo(buttonCode);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (moveButtons !== null) {
|
|
78
|
+
moveButtons.addEventListener("touchmove", this._detectMovingMoveButton.bind(this));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
_detectMovingMoveButton(event) {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
this.allMoveClear();
|
|
84
|
+
const touch = event.targetTouches.item(0);
|
|
85
|
+
if (!this._moveButtonsElement || touch === null) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const moveButtonRect = this._moveButtonsElement.getBoundingClientRect();
|
|
89
|
+
const bodyRect = document.body.getBoundingClientRect();
|
|
90
|
+
const centerPositionX = moveButtonRect.left - bodyRect.left + (moveButtonRect.width / 2);
|
|
91
|
+
const centerPositionY = moveButtonRect.top - bodyRect.top + (moveButtonRect.height / 2);
|
|
92
|
+
const touchX = touch.pageX - centerPositionX;
|
|
93
|
+
const touchY = touch.pageY - centerPositionY;
|
|
94
|
+
if (touchX >= touchY) {
|
|
95
|
+
if (touchX <= -touchY) {
|
|
96
|
+
this.setTouchInfo("BUTTON_UP");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.setTouchInfo("BUTTON_RIGHT");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
if (touchX >= -touchY) {
|
|
104
|
+
this.setTouchInfo("BUTTON_DOWN");
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
this.setTouchInfo("BUTTON_LEFT");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
_setVisible(visible) {
|
|
112
|
+
if (this._moveButtonsElement !== null) {
|
|
113
|
+
this._moveButtonsElement.style.display = visible ? "grid" : "none";
|
|
114
|
+
}
|
|
115
|
+
if (this._buttonWrapperElement !== null) {
|
|
116
|
+
this._buttonWrapperElement.style.display = visible ? "grid" : "none";
|
|
117
|
+
}
|
|
118
|
+
this._visible = visible;
|
|
119
|
+
}
|
|
120
|
+
checkTouchButton(buttonType) {
|
|
121
|
+
if (!this._enabled) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const state = this.getButtonState(buttonType);
|
|
125
|
+
return state === "TOUCH";
|
|
126
|
+
}
|
|
127
|
+
checkTouchingButton(buttonType) {
|
|
128
|
+
if (!this._enabled) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
const state = this.getButtonState(buttonType);
|
|
132
|
+
return state === "TOUCH" || state === "TOUCHING";
|
|
133
|
+
}
|
|
134
|
+
getButtonState(buttonType) {
|
|
135
|
+
const button = this._isTouchingButtons[buttonType];
|
|
136
|
+
if (!this._enabled || button === undefined) {
|
|
137
|
+
return "NONE";
|
|
138
|
+
}
|
|
139
|
+
const touched = button.prev;
|
|
140
|
+
const isTouching = button.current;
|
|
141
|
+
if (touched && isTouching) {
|
|
142
|
+
return "TOUCHING";
|
|
143
|
+
}
|
|
144
|
+
else if (isTouching) {
|
|
145
|
+
return "TOUCH";
|
|
146
|
+
}
|
|
147
|
+
else if (touched) {
|
|
148
|
+
return "LEAVE";
|
|
149
|
+
}
|
|
150
|
+
return "NONE";
|
|
151
|
+
}
|
|
152
|
+
setTouchInfo(buttonType) {
|
|
153
|
+
const button = this._isTouchingButtons[buttonType];
|
|
154
|
+
if (!this._enabled || button === undefined) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
button.next = true;
|
|
158
|
+
}
|
|
159
|
+
clearTouchInfo(buttonType) {
|
|
160
|
+
const button = this._isTouchingButtons[buttonType];
|
|
161
|
+
if (!this._enabled || button === undefined) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
button.next = false;
|
|
165
|
+
}
|
|
166
|
+
allMoveClear() {
|
|
167
|
+
VirtualPadMoveButtonCodes.forEach((buttonCode) => {
|
|
168
|
+
if (this._availableButtons.includes(buttonCode)) {
|
|
169
|
+
this.clearTouchInfo(buttonCode);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
allClear() {
|
|
174
|
+
this._availableButtons.forEach((buttonCode) => {
|
|
175
|
+
if (buttonCode !== undefined) {
|
|
176
|
+
const button = this._isTouchingButtons[buttonCode];
|
|
177
|
+
if (button !== undefined) {
|
|
178
|
+
button.next = false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
static isMoveButton(buttonType) {
|
|
184
|
+
return VirtualPadMoveButtonsSet.has(buttonType);
|
|
185
|
+
}
|
|
186
|
+
update() {
|
|
187
|
+
this._availableButtons.forEach((buttonCode) => {
|
|
188
|
+
var _a, _b;
|
|
189
|
+
if (buttonCode === undefined) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const currentButtonTouching = this._isTouchingButtons[buttonCode];
|
|
193
|
+
if (currentButtonTouching === undefined) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
this._isTouchingButtons[buttonCode].prev = currentButtonTouching.current;
|
|
197
|
+
this._isTouchingButtons[buttonCode].current = currentButtonTouching.next;
|
|
198
|
+
switch (this.getButtonState(buttonCode)) {
|
|
199
|
+
case "TOUCH":
|
|
200
|
+
(_a = this._onTouchStart) === null || _a === void 0 ? void 0 : _a.call(this, buttonCode);
|
|
201
|
+
break;
|
|
202
|
+
case "LEAVE":
|
|
203
|
+
(_b = this._onTouchEnd) === null || _b === void 0 ? void 0 : _b.call(this, buttonCode);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
toggleVisible() {
|
|
209
|
+
this._setVisible(!this._visible);
|
|
210
|
+
}
|
|
211
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import VirtualPadStore, { VirtualPadState, VirtualPadButtonCodes, VirtualPadButtonCode, VirtualPadButtons } from './VirtualPad';
|
|
2
|
+
import viewportFit, { initializeViewport } from './viewportFit';
|
|
3
|
+
import makeInject from './makeInject';
|
|
4
|
+
export { VirtualPadStore, VirtualPadState, VirtualPadButtonCodes, VirtualPadButtonCode, VirtualPadButtons, viewportFit, initializeViewport, makeInject };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import VirtualPadStore, { VirtualPadButtonCodes } from './VirtualPad';
|
|
2
|
+
import viewportFit, { initializeViewport } from './viewportFit';
|
|
3
|
+
import makeInject from './makeInject';
|
|
4
|
+
export { VirtualPadStore, VirtualPadButtonCodes, viewportFit, initializeViewport, makeInject };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function makeInject(): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default function makeInject() {
|
|
2
|
+
return `
|
|
3
|
+
<div id="wwa-virtualpad-left">
|
|
4
|
+
<button class="wwa-virtualpad__button" id="wwa-up-button"></button>
|
|
5
|
+
<button class="wwa-virtualpad__button" id="wwa-left-button"></button>
|
|
6
|
+
<button class="wwa-virtualpad__button" id="wwa-right-button"></button>
|
|
7
|
+
<button class="wwa-virtualpad__button" id="wwa-down-button"></button>
|
|
8
|
+
<div class="wwa-virtualpad__hole"></div>
|
|
9
|
+
</div>
|
|
10
|
+
<div id="wwa-virtualpad-right">
|
|
11
|
+
<button class="wwa-virtualpad__button" id="wwa-slow-button">I</button>
|
|
12
|
+
<button class="wwa-virtualpad__button" id="wwa-fast-button">P</button>
|
|
13
|
+
<button class="wwa-virtualpad__button" id="wwa-enter-button">Y</button>
|
|
14
|
+
<button class="wwa-virtualpad__button" id="wwa-esc-button">N</button>
|
|
15
|
+
</div>`;
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function initializeViewport() {
|
|
2
|
+
const headElement = document.getElementsByTagName('head')[0];
|
|
3
|
+
let viewportElement = document.createElement('meta');
|
|
4
|
+
viewportElement.setAttribute('name', 'viewport');
|
|
5
|
+
headElement.appendChild(viewportElement);
|
|
6
|
+
viewportFit();
|
|
7
|
+
}
|
|
8
|
+
export default function viewportFit() {
|
|
9
|
+
const WWA_WIDTH = 560;
|
|
10
|
+
const WWA_HEIGHT = 440;
|
|
11
|
+
const BROWSER_MARGIN = 8;
|
|
12
|
+
const viewportElement = document.querySelector("meta[name='viewport']");
|
|
13
|
+
const browserWidth = window.innerWidth;
|
|
14
|
+
const browserHeight = window.innerHeight;
|
|
15
|
+
let viewportValue = "user-scalable=no";
|
|
16
|
+
if (browserWidth > browserHeight) {
|
|
17
|
+
const width = Math.floor((browserWidth / browserHeight) * (WWA_HEIGHT + (BROWSER_MARGIN) * 2));
|
|
18
|
+
viewportValue = `width=${width},${viewportValue}`;
|
|
19
|
+
}
|
|
20
|
+
else if (browserWidth <= browserHeight) {
|
|
21
|
+
viewportValue = `width=${WWA_WIDTH + (BROWSER_MARGIN * 2)},${viewportValue}`;
|
|
22
|
+
}
|
|
23
|
+
viewportElement === null || viewportElement === void 0 ? void 0 : viewportElement.setAttribute("content", `${viewportValue}`);
|
|
24
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wwawing/virtual-pad",
|
|
3
|
+
"version": "0.0.0-exe-chousa.based-on.3.12.21",
|
|
4
|
+
"description": "WWA Wing Virtual Pad for mobile phone",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/WWAWing/WWAWing.git",
|
|
9
|
+
"directory": "packages/virtual-pad"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
13
|
+
"build": "tsc -p .",
|
|
14
|
+
"prepare": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"author": "WWA Wing Team",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.8.3"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"gitHead": "b52982378576ed3ef7223fae8457a7923eba7ace"
|
|
25
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
export const VirtualPadButtonCodes = [
|
|
2
|
+
"BUTTON_ENTER",
|
|
3
|
+
"BUTTON_ESC",
|
|
4
|
+
"BUTTON_SIDEBAR",
|
|
5
|
+
"BUTTON_FAST",
|
|
6
|
+
"BUTTON_SLOW",
|
|
7
|
+
"BUTTON_LEFT",
|
|
8
|
+
"BUTTON_UP",
|
|
9
|
+
"BUTTON_RIGHT",
|
|
10
|
+
"BUTTON_DOWN"
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
export type VirtualPadButtonCode = typeof VirtualPadButtonCodes[number];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* NONE: ボタンに一切触れていません
|
|
17
|
+
* TOUCH: ボタンに触れ始めました
|
|
18
|
+
* TOUCHING: ボタンに触れています
|
|
19
|
+
* LEAVE: ボタンから離れました
|
|
20
|
+
*/
|
|
21
|
+
export type VirtualPadState = "NONE" | "TOUCH" | "TOUCHING" | "LEAVE";
|
|
22
|
+
|
|
23
|
+
export type VirtualPadButtons = { [key in string]: HTMLButtonElement };
|
|
24
|
+
|
|
25
|
+
type VirtualPadButtonTouching = {
|
|
26
|
+
prev: boolean,
|
|
27
|
+
current: boolean,
|
|
28
|
+
next: boolean
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type VirtualPadEventFunction = (buttonCode: VirtualPadButtonCode) => void;
|
|
32
|
+
|
|
33
|
+
const VirtualPadMoveButtonCodes = [
|
|
34
|
+
"BUTTON_LEFT",
|
|
35
|
+
"BUTTON_UP",
|
|
36
|
+
"BUTTON_RIGHT",
|
|
37
|
+
"BUTTON_DOWN"
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 移動ボタンかどうかの判定に使用します。
|
|
42
|
+
*/
|
|
43
|
+
const VirtualPadMoveButtonsSet = new Set<VirtualPadButtonCode>(VirtualPadMoveButtonCodes);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 仮想パッドの状態管理を行うクラスです。
|
|
47
|
+
* @todo PCといった、タッチデバイスでないデバイスでは、このクラスのインスタンスを生成しないようにする。
|
|
48
|
+
* (this._enabled で判別しているけど、いちいち判定処理書くのつらい...)
|
|
49
|
+
*/
|
|
50
|
+
export default class VirtualPadStore {
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 仮想パッドが有効か
|
|
54
|
+
* @property
|
|
55
|
+
*/
|
|
56
|
+
private _enabled: boolean;
|
|
57
|
+
|
|
58
|
+
private _visible: boolean;
|
|
59
|
+
|
|
60
|
+
private _onTouchStart: VirtualPadEventFunction | null;
|
|
61
|
+
private _onTouchEnd: VirtualPadEventFunction | null;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 有効なボタンが格納されている配列です。各ボタンを繰り返し処理する場合に使用します。
|
|
65
|
+
* @property
|
|
66
|
+
*/
|
|
67
|
+
private _availableButtons: [VirtualPadButtonCode?];
|
|
68
|
+
private _isTouchingButtons: {
|
|
69
|
+
[key in VirtualPadButtonCode]?: VirtualPadButtonTouching
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 移動ボタンの要素自体です。主に移動ボタンで指を動かす際に使用します。
|
|
74
|
+
* @property
|
|
75
|
+
*/
|
|
76
|
+
private _moveButtonsElement: HTMLElement | null;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 操作ボタン群の親要素です。 {@code _moveButtonsElement} と併せて表示や非表示の切り替えに使用します。
|
|
80
|
+
*/
|
|
81
|
+
private _buttonWrapperElement: HTMLElement | null;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param buttons 仮想パッドの各要素
|
|
85
|
+
* @param firstVisible インスタンス化時点で仮想パッドを表示するか
|
|
86
|
+
* @param buttonWrapper 仮想パッドのボタン (主に右側) を覆う親要素
|
|
87
|
+
* @param moveButtons 仮想パッドの移動ボタンの要素
|
|
88
|
+
* @param onTouchStart 仮想パッドを押下した場合に発生するコールバック関数
|
|
89
|
+
* @param onTouchEnd 仮想パッドを話した場合に発生するコールバック関数
|
|
90
|
+
*/
|
|
91
|
+
constructor(
|
|
92
|
+
buttons: VirtualPadButtons | null,
|
|
93
|
+
firstVisible: boolean = false,
|
|
94
|
+
buttonWrapper: HTMLElement | null = null,
|
|
95
|
+
moveButtons: HTMLElement | null = null,
|
|
96
|
+
onTouchStart: VirtualPadEventFunction | null = null,
|
|
97
|
+
onTouchEnd: VirtualPadEventFunction | null = null,
|
|
98
|
+
) {
|
|
99
|
+
this._enabled = buttons !== null;
|
|
100
|
+
this._visible = firstVisible;
|
|
101
|
+
this._onTouchStart = onTouchStart || null;
|
|
102
|
+
this._onTouchEnd = onTouchEnd || null;
|
|
103
|
+
this._availableButtons = [];
|
|
104
|
+
this._isTouchingButtons = {};
|
|
105
|
+
this._moveButtonsElement = moveButtons;
|
|
106
|
+
this._buttonWrapperElement = buttonWrapper;
|
|
107
|
+
|
|
108
|
+
this._setVisible(firstVisible);
|
|
109
|
+
// PC でアクセスする場合、 VirtualPadStore が生成されるまでの間一瞬表示されてしまう
|
|
110
|
+
// そうならないようにするために、 CSS 側で visibility: hidden を付与し、インラインスタイルシートで解除している
|
|
111
|
+
if (this._moveButtonsElement) {
|
|
112
|
+
this._moveButtonsElement.style.visibility = "visible";
|
|
113
|
+
}
|
|
114
|
+
if (this._buttonWrapperElement) {
|
|
115
|
+
this._buttonWrapperElement.style.visibility = "visible";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (buttons === null) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
for (const buttonTypeString in buttons) {
|
|
122
|
+
const button = buttons[buttonTypeString];
|
|
123
|
+
if (!button) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// ここまでくれば buttonTypeString は VirtualPadButtonCode になる
|
|
127
|
+
const buttonCode = buttonTypeString as VirtualPadButtonCode;
|
|
128
|
+
this._availableButtons.push(buttonCode);
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* type 属性について
|
|
132
|
+
* HTML側の各仮想パッドボタンは、 type 属性を元に各ボタンを特定しています。
|
|
133
|
+
* 各仮想パッドボタンの種類を判別するために、初期化時に type 属性を割り振っています。
|
|
134
|
+
*/
|
|
135
|
+
button.setAttribute("type", buttonTypeString);
|
|
136
|
+
|
|
137
|
+
this._isTouchingButtons[buttonCode] = {
|
|
138
|
+
prev: false,
|
|
139
|
+
current: false,
|
|
140
|
+
next: false
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const cancelBrowserTouchEvent = (event: TouchEvent) => {
|
|
144
|
+
if (event.cancelable) {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 何かしらの割り込み処理が発生した場合に、緊急で仮想パッドを停止する処理です。
|
|
151
|
+
* @param event
|
|
152
|
+
*/
|
|
153
|
+
const cancelVirtualPad = (event: TouchEvent) => {
|
|
154
|
+
cancelBrowserTouchEvent(event);
|
|
155
|
+
this.allClear();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
button.addEventListener("touchstart", (event: TouchEvent) => {
|
|
159
|
+
cancelBrowserTouchEvent(event);
|
|
160
|
+
this.setTouchInfo(buttonCode);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// button.addEventListener("cancel", cancelVirtualPad);
|
|
164
|
+
button.addEventListener("touchcancel", cancelVirtualPad);
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 移動ボタンに関しては、指先を移動しながら操作することがあるため、 touchmove イベントにも対応します。
|
|
168
|
+
*/
|
|
169
|
+
if (VirtualPadStore.isMoveButton(buttonCode)) {
|
|
170
|
+
button.addEventListener("touchmove", this._detectMovingMoveButton.bind(this));
|
|
171
|
+
button.addEventListener("touchend", this.allMoveClear.bind(this));
|
|
172
|
+
} else {
|
|
173
|
+
button.addEventListener("touchend", (event: TouchEvent) => {
|
|
174
|
+
cancelBrowserTouchEvent(event);
|
|
175
|
+
this.clearTouchInfo(buttonCode);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (moveButtons !== null) {
|
|
180
|
+
moveButtons.addEventListener("touchmove", this._detectMovingMoveButton.bind(this));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* TouchMove した要素から触れた先の要素に、触れたことを呼び出します。
|
|
186
|
+
* @param event
|
|
187
|
+
*/
|
|
188
|
+
private _detectMovingMoveButton(event: TouchEvent) {
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
this.allMoveClear();
|
|
191
|
+
|
|
192
|
+
const touch = event.targetTouches.item(0);
|
|
193
|
+
if (!this._moveButtonsElement || touch === null) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 中央のポジションを取得
|
|
198
|
+
const moveButtonRect = this._moveButtonsElement.getBoundingClientRect();
|
|
199
|
+
const bodyRect = document.body.getBoundingClientRect();
|
|
200
|
+
const centerPositionX = moveButtonRect.left - bodyRect.left + (moveButtonRect.width / 2);
|
|
201
|
+
const centerPositionY = moveButtonRect.top - bodyRect.top + (moveButtonRect.height / 2);
|
|
202
|
+
|
|
203
|
+
const touchX = touch.pageX - centerPositionX;
|
|
204
|
+
const touchY = touch.pageY - centerPositionY;
|
|
205
|
+
|
|
206
|
+
if (touchX >= touchY) { // 上と右
|
|
207
|
+
/**
|
|
208
|
+
* touchY をマイナスにして、グラフの空間上で擬似的に表現します。
|
|
209
|
+
*/
|
|
210
|
+
if (touchX <= -touchY) { // 上
|
|
211
|
+
this.setTouchInfo("BUTTON_UP");
|
|
212
|
+
} else { // 右
|
|
213
|
+
this.setTouchInfo("BUTTON_RIGHT");
|
|
214
|
+
}
|
|
215
|
+
} else { // 左と下
|
|
216
|
+
if (touchX >= -touchY) { // 下
|
|
217
|
+
this.setTouchInfo("BUTTON_DOWN");
|
|
218
|
+
} else { // 左
|
|
219
|
+
this.setTouchInfo("BUTTON_LEFT");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private _setVisible(visible: boolean) {
|
|
225
|
+
if (this._moveButtonsElement !== null) {
|
|
226
|
+
this._moveButtonsElement.style.display = visible ? "grid" : "none";
|
|
227
|
+
}
|
|
228
|
+
if (this._buttonWrapperElement !== null) {
|
|
229
|
+
this._buttonWrapperElement.style.display = visible ? "grid" : "none";
|
|
230
|
+
}
|
|
231
|
+
this._visible = visible;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 指定したボタンが押されたかを判別します。
|
|
236
|
+
* 戦闘予測ボタンといった、1回しか押さないボタンの判定に使用します。
|
|
237
|
+
* @param buttonType
|
|
238
|
+
*/
|
|
239
|
+
public checkTouchButton(buttonType: VirtualPadButtonCode): boolean {
|
|
240
|
+
if (!this._enabled) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
const state = this.getButtonState(buttonType);
|
|
244
|
+
return state === "TOUCH";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 指定したボタンが押されているかを判別します。
|
|
249
|
+
* 移動ボタンといった、長時間押すボタンの判定に使用します。
|
|
250
|
+
* @param buttonType
|
|
251
|
+
*/
|
|
252
|
+
public checkTouchingButton(buttonType: VirtualPadButtonCode): boolean {
|
|
253
|
+
if (!this._enabled) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const state = this.getButtonState(buttonType);
|
|
257
|
+
return state === "TOUCH" || state === "TOUCHING";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 今の仮想パッドのボタンの状態を出力します
|
|
262
|
+
* @param buttonType
|
|
263
|
+
*/
|
|
264
|
+
public getButtonState(buttonType: VirtualPadButtonCode): VirtualPadState {
|
|
265
|
+
const button = this._isTouchingButtons[buttonType];
|
|
266
|
+
if (!this._enabled || button === undefined) {
|
|
267
|
+
return "NONE";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const touched = button.prev;
|
|
271
|
+
const isTouching = button.current;
|
|
272
|
+
if (touched && isTouching) {
|
|
273
|
+
return "TOUCHING";
|
|
274
|
+
} else if (isTouching) {
|
|
275
|
+
return "TOUCH";
|
|
276
|
+
} else if (touched) {
|
|
277
|
+
return "LEAVE";
|
|
278
|
+
}
|
|
279
|
+
return "NONE";
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* ボタンにタッチ信号を与えます
|
|
284
|
+
* @param buttonType
|
|
285
|
+
*/
|
|
286
|
+
public setTouchInfo(buttonType: VirtualPadButtonCode) {
|
|
287
|
+
const button = this._isTouchingButtons[buttonType];
|
|
288
|
+
if (!this._enabled || button === undefined) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
button.next = true;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* ボタンのタッチ信号を解除します
|
|
297
|
+
* @param buttonType
|
|
298
|
+
*/
|
|
299
|
+
public clearTouchInfo(buttonType: VirtualPadButtonCode) {
|
|
300
|
+
const button = this._isTouchingButtons[buttonType];
|
|
301
|
+
if (!this._enabled || button === undefined) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
button.next = false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* すべての移動ボタンのタッチ信号をキャンセルします
|
|
310
|
+
*/
|
|
311
|
+
public allMoveClear(): void {
|
|
312
|
+
VirtualPadMoveButtonCodes.forEach((buttonCode) => {
|
|
313
|
+
if (this._availableButtons.includes(buttonCode)) {
|
|
314
|
+
this.clearTouchInfo(buttonCode);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* すべてのボタンのタッチ信号をキャンセルします。
|
|
321
|
+
*/
|
|
322
|
+
public allClear(): void {
|
|
323
|
+
this._availableButtons.forEach((buttonCode) => {
|
|
324
|
+
if (buttonCode !== undefined) {
|
|
325
|
+
const button = this._isTouchingButtons[buttonCode];
|
|
326
|
+
if (button !== undefined) {
|
|
327
|
+
button.next = false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* 押したボタンが移動ボタンか判定します。
|
|
335
|
+
* @param buttonType
|
|
336
|
+
*/
|
|
337
|
+
public static isMoveButton(buttonType: VirtualPadButtonCode): boolean {
|
|
338
|
+
return VirtualPadMoveButtonsSet.has(buttonType);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 今の仮想パッドボタンの状態を次の状態に送ります。
|
|
343
|
+
*/
|
|
344
|
+
public update() {
|
|
345
|
+
this._availableButtons.forEach((buttonCode) => {
|
|
346
|
+
if (buttonCode === undefined) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const currentButtonTouching = this._isTouchingButtons[buttonCode];
|
|
350
|
+
if (currentButtonTouching === undefined) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
(this._isTouchingButtons[buttonCode] as VirtualPadButtonTouching).prev = currentButtonTouching.current;
|
|
354
|
+
(this._isTouchingButtons[buttonCode] as VirtualPadButtonTouching).current = currentButtonTouching.next;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* ボタンの状態に応じて、コールバック関数を実行
|
|
358
|
+
*/
|
|
359
|
+
switch (this.getButtonState(buttonCode)) {
|
|
360
|
+
case "TOUCH":
|
|
361
|
+
this._onTouchStart?.(buttonCode);
|
|
362
|
+
break;
|
|
363
|
+
case "LEAVE":
|
|
364
|
+
this._onTouchEnd?.(buttonCode);
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
public toggleVisible() {
|
|
371
|
+
this._setVisible(!this._visible);
|
|
372
|
+
}
|
|
373
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import VirtualPadStore, { VirtualPadState, VirtualPadButtonCodes, VirtualPadButtonCode, VirtualPadButtons } from './VirtualPad';
|
|
2
|
+
import viewportFit, { initializeViewport } from './viewportFit';
|
|
3
|
+
import makeInject from './makeInject';
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
VirtualPadStore,
|
|
7
|
+
VirtualPadState,
|
|
8
|
+
VirtualPadButtonCodes,
|
|
9
|
+
VirtualPadButtonCode,
|
|
10
|
+
VirtualPadButtons,
|
|
11
|
+
viewportFit,
|
|
12
|
+
initializeViewport,
|
|
13
|
+
makeInject
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default function makeInject() {
|
|
2
|
+
return `
|
|
3
|
+
<div id="wwa-virtualpad-left">
|
|
4
|
+
<button class="wwa-virtualpad__button" id="wwa-up-button"></button>
|
|
5
|
+
<button class="wwa-virtualpad__button" id="wwa-left-button"></button>
|
|
6
|
+
<button class="wwa-virtualpad__button" id="wwa-right-button"></button>
|
|
7
|
+
<button class="wwa-virtualpad__button" id="wwa-down-button"></button>
|
|
8
|
+
<div class="wwa-virtualpad__hole"></div>
|
|
9
|
+
</div>
|
|
10
|
+
<div id="wwa-virtualpad-right">
|
|
11
|
+
<button class="wwa-virtualpad__button" id="wwa-slow-button">I</button>
|
|
12
|
+
<button class="wwa-virtualpad__button" id="wwa-fast-button">P</button>
|
|
13
|
+
<button class="wwa-virtualpad__button" id="wwa-enter-button">Y</button>
|
|
14
|
+
<button class="wwa-virtualpad__button" id="wwa-esc-button">N</button>
|
|
15
|
+
</div>`;
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ビューポート制御の初期化を行うメソッドです。
|
|
3
|
+
*/
|
|
4
|
+
export function initializeViewport() {
|
|
5
|
+
const headElement = document.getElementsByTagName('head')[0];
|
|
6
|
+
let viewportElement = document.createElement('meta');
|
|
7
|
+
|
|
8
|
+
viewportElement.setAttribute('name', 'viewport');
|
|
9
|
+
headElement.appendChild(viewportElement);
|
|
10
|
+
viewportFit();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 画面の向きやサイズに合わせて自動的に画面のビューポートを調整する機能のメソッドです。 "resize" イベントと併せてご利用ください。
|
|
15
|
+
* @example window.addEventListener("resize", viewportFit);
|
|
16
|
+
*/
|
|
17
|
+
export default function viewportFit() {
|
|
18
|
+
const WWA_WIDTH = 560;
|
|
19
|
+
const WWA_HEIGHT = 440;
|
|
20
|
+
// ブラウザーCSSをここから取得することはできないようなのでここで仮の値を設定しておく
|
|
21
|
+
const BROWSER_MARGIN = 8;
|
|
22
|
+
|
|
23
|
+
const viewportElement = document.querySelector("meta[name='viewport']");
|
|
24
|
+
const browserWidth = window.innerWidth;
|
|
25
|
+
const browserHeight = window.innerHeight;
|
|
26
|
+
/**
|
|
27
|
+
* viewportFit を有効化すると、誤動作を防ぐため、拡大操作が無効になります。
|
|
28
|
+
* Webページの中に設置する場合は、特にご注意ください。
|
|
29
|
+
*/
|
|
30
|
+
let viewportValue = "user-scalable=no";
|
|
31
|
+
|
|
32
|
+
if (browserWidth > browserHeight) {
|
|
33
|
+
const width = Math.floor((browserWidth / browserHeight) * (WWA_HEIGHT + (BROWSER_MARGIN) * 2));
|
|
34
|
+
viewportValue = `width=${width},${viewportValue}`;
|
|
35
|
+
} else if (browserWidth <= browserHeight) {
|
|
36
|
+
viewportValue = `width=${WWA_WIDTH + (BROWSER_MARGIN * 2)},${viewportValue}`;
|
|
37
|
+
}
|
|
38
|
+
viewportElement?.setAttribute("content", `${viewportValue}`);
|
|
39
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"outDir": "./lib",
|
|
6
|
+
"module": "esnext",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noImplicitAny": true,
|
|
9
|
+
},
|
|
10
|
+
"include": [
|
|
11
|
+
"src/**/*.ts"
|
|
12
|
+
],
|
|
13
|
+
"exclude": [
|
|
14
|
+
"node_modules"
|
|
15
|
+
]
|
|
16
|
+
}
|