@ksteinstudio/game-controller 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/LICENSE +21 -0
- package/README.md +348 -0
- package/dist/index.d.mts +272 -0
- package/dist/index.d.ts +272 -0
- package/dist/index.js +1116 -0
- package/dist/index.mjs +1060 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ksteinstudio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Universal Game Controller Engine
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@ksteinstudio/game-controller)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
A lightweight, zero-dependency engine for rendering interactive virtual game controllers via JSON configuration. Renders inside an `<iframe>` and communicates with the parent window through `postMessage`. Perfect for mobile web games, browser-based emulators, and remote control interfaces.
|
|
7
|
+
|
|
8
|
+
**Key features:**
|
|
9
|
+
- Percentage-based positioning (0–100) for resolution independence
|
|
10
|
+
- Multi-touch support via PointerEvents API
|
|
11
|
+
- JSON-driven layout — define controllers as data
|
|
12
|
+
- Iframe-sandboxed renderer — secure and isolated
|
|
13
|
+
- GPU-accelerated CSS transforms for low-latency input
|
|
14
|
+
- Zero runtime dependencies
|
|
15
|
+
- TypeScript-first with full type exports
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# npm
|
|
23
|
+
npm install @ksteinstudio/game-controller
|
|
24
|
+
|
|
25
|
+
# yarn
|
|
26
|
+
yarn add @ksteinstudio/game-controller
|
|
27
|
+
|
|
28
|
+
# pnpm
|
|
29
|
+
pnpm add @ksteinstudio/game-controller
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- Works with any framework: React, Vue, Svelte, Angular, or vanilla JS/TS
|
|
33
|
+
- Supports ESM and CommonJS
|
|
34
|
+
- TypeScript types included out of the box
|
|
35
|
+
- Browser-only (requires DOM APIs)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### 1. Define a Controller Layout (JSON)
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"version": "1.0.0",
|
|
46
|
+
"name": "My-Layout",
|
|
47
|
+
"canvas": {
|
|
48
|
+
"aspectRatio": "16:9",
|
|
49
|
+
"backgroundColor": "rgba(0,0,0,0.5)"
|
|
50
|
+
},
|
|
51
|
+
"elements": [
|
|
52
|
+
{
|
|
53
|
+
"id": "left-stick",
|
|
54
|
+
"type": "joystick",
|
|
55
|
+
"position": { "x": 15, "y": 60 },
|
|
56
|
+
"zIndex": 1,
|
|
57
|
+
"radius": 12,
|
|
58
|
+
"innerStickSize": 5,
|
|
59
|
+
"deadzone": 0.1,
|
|
60
|
+
"mode": "static",
|
|
61
|
+
"style": { "color": "rgba(85, 85, 85, 0.6)" }
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "btn-a",
|
|
65
|
+
"type": "button",
|
|
66
|
+
"position": { "x": 85, "y": 65 },
|
|
67
|
+
"zIndex": 1,
|
|
68
|
+
"shape": "circle",
|
|
69
|
+
"size": 8,
|
|
70
|
+
"label": "A",
|
|
71
|
+
"actionKey": "JOY_A",
|
|
72
|
+
"style": { "color": "#4CAF50" }
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. Embed with the SDK
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { createControllerSDK } from '@ksteinstudio/game-controller';
|
|
82
|
+
import layout from './controller.json';
|
|
83
|
+
|
|
84
|
+
const controller = createControllerSDK({
|
|
85
|
+
config: layout,
|
|
86
|
+
container: document.getElementById('controller-container'),
|
|
87
|
+
onInput: (event) => {
|
|
88
|
+
console.log('Input:', event.type, event.payload);
|
|
89
|
+
},
|
|
90
|
+
onReady: () => {
|
|
91
|
+
console.log('Controller is ready');
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Update the layout dynamically
|
|
96
|
+
controller.updateConfig(newLayout);
|
|
97
|
+
|
|
98
|
+
// Clean up
|
|
99
|
+
controller.destroy();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## API Reference
|
|
105
|
+
|
|
106
|
+
### `createControllerSDK(options)`
|
|
107
|
+
|
|
108
|
+
Creates and embeds a controller inside a container element.
|
|
109
|
+
|
|
110
|
+
| Option | Type | Required | Description |
|
|
111
|
+
|--------|------|----------|-------------|
|
|
112
|
+
| `config` | `ControllerConfig` | ✅ | The JSON layout configuration |
|
|
113
|
+
| `container` | `HTMLElement` | ✅ | DOM element to mount the iframe into |
|
|
114
|
+
| `iframeSrc` | `string` | ❌ | Custom URL for the renderer (uses embedded renderer by default) |
|
|
115
|
+
| `onInput` | `(event: InputEvent) => void` | ❌ | Callback for all input events |
|
|
116
|
+
| `onReady` | `() => void` | ❌ | Called when the renderer is initialized |
|
|
117
|
+
| `width` | `string` | ❌ | CSS width for the iframe (default: `100%`) |
|
|
118
|
+
| `height` | `string` | ❌ | CSS height for the iframe (default: `100%`) |
|
|
119
|
+
|
|
120
|
+
**Returns** `ControllerSDKInstance`:
|
|
121
|
+
|
|
122
|
+
| Method | Description |
|
|
123
|
+
|--------|-------------|
|
|
124
|
+
| `updateConfig(config)` | Send a new layout to the renderer |
|
|
125
|
+
| `destroy()` | Remove the iframe and clean up listeners |
|
|
126
|
+
| `getIframe()` | Access the underlying HTMLIFrameElement |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Type Definitions
|
|
131
|
+
|
|
132
|
+
### Position
|
|
133
|
+
|
|
134
|
+
All positions use a **percentage-based coordinate system** (0–100) for resolution independence.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
interface Position {
|
|
138
|
+
x: number; // 0 to 100 (% of canvas width)
|
|
139
|
+
y: number; // 0 to 100 (% of canvas height)
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Element Types
|
|
144
|
+
|
|
145
|
+
| Type | Description | Key Properties |
|
|
146
|
+
|------|-------------|----------------|
|
|
147
|
+
| `button` | Pressable button (circle or square) | `shape`, `size`, `label`, `actionKey` |
|
|
148
|
+
| `joystick` | Analog stick with normalized output | `radius`, `innerStickSize`, `deadzone`, `mode` |
|
|
149
|
+
| `dpad` | 4-directional pad | `size`, `actionKeys` |
|
|
150
|
+
| `slider` | Linear input control | `length`, `orientation`, `actionKey` |
|
|
151
|
+
|
|
152
|
+
### Events Emitted
|
|
153
|
+
|
|
154
|
+
| Event | Payload | When |
|
|
155
|
+
|-------|---------|------|
|
|
156
|
+
| `INPUT_START` | `{ elementId, actionKey, timestamp }` | Button pressed |
|
|
157
|
+
| `INPUT_END` | `{ elementId, actionKey, timestamp }` | Button released |
|
|
158
|
+
| `JOYSTICK_MOVE` | `{ elementId, vector: {x,y}, angle, magnitude, timestamp }` | Joystick moves (vector normalized -1 to 1) |
|
|
159
|
+
| `DPAD_PRESS` | `{ elementId, direction, actionKey, timestamp }` | DPad direction pressed |
|
|
160
|
+
| `DPAD_RELEASE` | `{ elementId, direction, actionKey, timestamp }` | DPad direction released |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Math Utilities
|
|
165
|
+
|
|
166
|
+
Exported utilities for building custom layout editors or tools.
|
|
167
|
+
|
|
168
|
+
### Snap-to-Grid
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { snapToGrid, snapPositionToGrid } from '@ksteinstudio/game-controller';
|
|
172
|
+
|
|
173
|
+
const snapped = snapToGrid(37, 20); // → 35
|
|
174
|
+
const pos = snapPositionToGrid({ x: 37.2, y: 62.8 }, 20); // → { x: 35, y: 65 }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Coordinate Conversion
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { pixelToPercentage, percentageToPixel } from '@ksteinstudio/game-controller';
|
|
181
|
+
|
|
182
|
+
const pct = pixelToPercentage(320, 1920); // → 16.67
|
|
183
|
+
const px = percentageToPixel(50, 1080); // → 540
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Alignment Guides
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { findAlignmentGuides } from '@ksteinstudio/game-controller';
|
|
190
|
+
|
|
191
|
+
const result = findAlignmentGuides(draggedElement, cursorPosition, otherElements);
|
|
192
|
+
// result.position → snapped position
|
|
193
|
+
// result.guides → array of active alignment lines
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Framework Examples
|
|
199
|
+
|
|
200
|
+
### React
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
import { useEffect, useRef } from 'react';
|
|
204
|
+
import { createControllerSDK, ControllerConfig, InputEvent } from '@ksteinstudio/game-controller';
|
|
205
|
+
|
|
206
|
+
const layout: ControllerConfig = {
|
|
207
|
+
version: '1.0.0',
|
|
208
|
+
name: 'react-controller',
|
|
209
|
+
canvas: { aspectRatio: '16:9', backgroundColor: 'rgba(0,0,0,0.4)' },
|
|
210
|
+
elements: [
|
|
211
|
+
{
|
|
212
|
+
id: 'stick', type: 'joystick', position: { x: 20, y: 65 },
|
|
213
|
+
zIndex: 1, radius: 12, innerStickSize: 5, deadzone: 0.1, mode: 'static',
|
|
214
|
+
style: { color: 'rgba(80,80,80,0.6)' },
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: 'btn-a', type: 'button', position: { x: 85, y: 60 },
|
|
218
|
+
zIndex: 1, shape: 'circle', size: 10, label: 'A', actionKey: 'JUMP',
|
|
219
|
+
style: { color: '#4CAF50' },
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
function GameController() {
|
|
225
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
226
|
+
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (!containerRef.current) return;
|
|
229
|
+
|
|
230
|
+
const sdk = createControllerSDK({
|
|
231
|
+
config: layout,
|
|
232
|
+
container: containerRef.current,
|
|
233
|
+
onInput: (event: InputEvent) => {
|
|
234
|
+
console.log(event.type, event.payload);
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return () => sdk.destroy();
|
|
239
|
+
}, []);
|
|
240
|
+
|
|
241
|
+
return <div ref={containerRef} style={{ width: '100%', height: '300px' }} />;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Vue 3
|
|
246
|
+
|
|
247
|
+
```vue
|
|
248
|
+
<template>
|
|
249
|
+
<div ref="controllerRef" style="width: 100%; height: 300px" />
|
|
250
|
+
</template>
|
|
251
|
+
|
|
252
|
+
<script setup lang="ts">
|
|
253
|
+
import { ref, onMounted, onUnmounted } from 'vue';
|
|
254
|
+
import { createControllerSDK, type ControllerSDKInstance } from '@ksteinstudio/game-controller';
|
|
255
|
+
|
|
256
|
+
const controllerRef = ref<HTMLDivElement>();
|
|
257
|
+
let sdk: ControllerSDKInstance;
|
|
258
|
+
|
|
259
|
+
onMounted(() => {
|
|
260
|
+
if (!controllerRef.value) return;
|
|
261
|
+
|
|
262
|
+
sdk = createControllerSDK({
|
|
263
|
+
config: {
|
|
264
|
+
version: '1.0.0',
|
|
265
|
+
name: 'vue-controller',
|
|
266
|
+
canvas: { aspectRatio: '16:9' },
|
|
267
|
+
elements: [
|
|
268
|
+
{
|
|
269
|
+
id: 'joy', type: 'joystick', position: { x: 20, y: 65 },
|
|
270
|
+
zIndex: 1, radius: 12, innerStickSize: 5, deadzone: 0.1, mode: 'static',
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
},
|
|
274
|
+
container: controllerRef.value,
|
|
275
|
+
onInput: (event) => console.log(event),
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
onUnmounted(() => sdk?.destroy());
|
|
280
|
+
</script>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Vanilla JavaScript (No Bundler)
|
|
284
|
+
|
|
285
|
+
```html
|
|
286
|
+
<div id="controller" style="width: 100%; height: 400px;"></div>
|
|
287
|
+
|
|
288
|
+
<script type="module">
|
|
289
|
+
import { createControllerSDK } from 'https://esm.sh/@ksteinstudio/game-controller';
|
|
290
|
+
|
|
291
|
+
createControllerSDK({
|
|
292
|
+
config: {
|
|
293
|
+
version: '1.0.0',
|
|
294
|
+
name: 'vanilla-layout',
|
|
295
|
+
canvas: { aspectRatio: '16:9', backgroundColor: 'rgba(0,0,0,0.3)' },
|
|
296
|
+
elements: [
|
|
297
|
+
{
|
|
298
|
+
id: 'dpad', type: 'dpad', position: { x: 15, y: 50 },
|
|
299
|
+
zIndex: 1, size: 15, style: { color: '#333' },
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: 'btn-a', type: 'button', position: { x: 85, y: 55 },
|
|
303
|
+
zIndex: 1, shape: 'circle', size: 10, label: 'A', actionKey: 'A',
|
|
304
|
+
style: { color: '#4CAF50' },
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
container: document.getElementById('controller'),
|
|
309
|
+
onInput: (e) => console.log(e.type, e.payload),
|
|
310
|
+
});
|
|
311
|
+
</script>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## All Exports
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { createControllerSDK } from '@ksteinstudio/game-controller';
|
|
320
|
+
|
|
321
|
+
import type {
|
|
322
|
+
ControllerConfig,
|
|
323
|
+
ButtonElement,
|
|
324
|
+
JoystickElement,
|
|
325
|
+
DpadElement,
|
|
326
|
+
InputEvent,
|
|
327
|
+
Position,
|
|
328
|
+
} from '@ksteinstudio/game-controller';
|
|
329
|
+
|
|
330
|
+
import {
|
|
331
|
+
snapToGrid,
|
|
332
|
+
snapPositionToGrid,
|
|
333
|
+
pixelToPercentage,
|
|
334
|
+
percentageToPixel,
|
|
335
|
+
findAlignmentGuides,
|
|
336
|
+
calculateCanvasDimensions,
|
|
337
|
+
} from '@ksteinstudio/game-controller';
|
|
338
|
+
|
|
339
|
+
import { createParentBridge, createIframeBridge } from '@ksteinstudio/game-controller';
|
|
340
|
+
|
|
341
|
+
import { renderControllerFromConfig, destroyRenderer } from '@ksteinstudio/game-controller';
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## License
|
|
347
|
+
|
|
348
|
+
[MIT](LICENSE) © Ksteinstudio
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
interface Position {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
}
|
|
5
|
+
interface ElementStyle {
|
|
6
|
+
color?: string;
|
|
7
|
+
opacity?: number;
|
|
8
|
+
scale?: number;
|
|
9
|
+
borderColor?: string;
|
|
10
|
+
borderWidth?: number;
|
|
11
|
+
fontSize?: number;
|
|
12
|
+
fontFamily?: string;
|
|
13
|
+
}
|
|
14
|
+
interface BaseElement {
|
|
15
|
+
id: string;
|
|
16
|
+
type: 'button' | 'joystick' | 'dpad' | 'slider';
|
|
17
|
+
position: Position;
|
|
18
|
+
zIndex: number;
|
|
19
|
+
style?: ElementStyle;
|
|
20
|
+
}
|
|
21
|
+
interface ButtonElement extends BaseElement {
|
|
22
|
+
type: 'button';
|
|
23
|
+
label?: string;
|
|
24
|
+
shape: 'circle' | 'square';
|
|
25
|
+
size: number;
|
|
26
|
+
actionKey: string;
|
|
27
|
+
}
|
|
28
|
+
interface JoystickElement extends BaseElement {
|
|
29
|
+
type: 'joystick';
|
|
30
|
+
radius: number;
|
|
31
|
+
innerStickSize: number;
|
|
32
|
+
deadzone: number;
|
|
33
|
+
mode: 'static' | 'floating';
|
|
34
|
+
}
|
|
35
|
+
interface DpadElement extends BaseElement {
|
|
36
|
+
type: 'dpad';
|
|
37
|
+
size: number;
|
|
38
|
+
actionKeys?: {
|
|
39
|
+
up: string;
|
|
40
|
+
down: string;
|
|
41
|
+
left: string;
|
|
42
|
+
right: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
interface SliderElement extends BaseElement {
|
|
46
|
+
type: 'slider';
|
|
47
|
+
length: number;
|
|
48
|
+
orientation: 'horizontal' | 'vertical';
|
|
49
|
+
actionKey: string;
|
|
50
|
+
}
|
|
51
|
+
type ControllerElement = ButtonElement | JoystickElement | DpadElement | SliderElement;
|
|
52
|
+
interface CanvasSettings {
|
|
53
|
+
aspectRatio: string;
|
|
54
|
+
backgroundColor?: string;
|
|
55
|
+
gridDensity?: number;
|
|
56
|
+
}
|
|
57
|
+
interface ControllerConfig {
|
|
58
|
+
version: string;
|
|
59
|
+
name: string;
|
|
60
|
+
canvas: CanvasSettings;
|
|
61
|
+
elements: ControllerElement[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
declare const enum MessageType {
|
|
65
|
+
CONFIG_LOAD = "CONFIG_LOAD",
|
|
66
|
+
INPUT_START = "INPUT_START",
|
|
67
|
+
INPUT_END = "INPUT_END",
|
|
68
|
+
JOYSTICK_MOVE = "JOYSTICK_MOVE",
|
|
69
|
+
DPAD_PRESS = "DPAD_PRESS",
|
|
70
|
+
DPAD_RELEASE = "DPAD_RELEASE",
|
|
71
|
+
SLIDER_CHANGE = "SLIDER_CHANGE",
|
|
72
|
+
RENDERER_READY = "RENDERER_READY",
|
|
73
|
+
CONFIG_UPDATE = "CONFIG_UPDATE"
|
|
74
|
+
}
|
|
75
|
+
interface Vector2D {
|
|
76
|
+
x: number;
|
|
77
|
+
y: number;
|
|
78
|
+
}
|
|
79
|
+
interface ConfigLoadMessage {
|
|
80
|
+
type: MessageType.CONFIG_LOAD;
|
|
81
|
+
payload: ControllerConfig;
|
|
82
|
+
}
|
|
83
|
+
interface InputStartMessage {
|
|
84
|
+
type: MessageType.INPUT_START;
|
|
85
|
+
payload: {
|
|
86
|
+
elementId: string;
|
|
87
|
+
actionKey: string;
|
|
88
|
+
timestamp: number;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
interface InputEndMessage {
|
|
92
|
+
type: MessageType.INPUT_END;
|
|
93
|
+
payload: {
|
|
94
|
+
elementId: string;
|
|
95
|
+
actionKey: string;
|
|
96
|
+
timestamp: number;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
interface JoystickMoveMessage {
|
|
100
|
+
type: MessageType.JOYSTICK_MOVE;
|
|
101
|
+
payload: {
|
|
102
|
+
elementId: string;
|
|
103
|
+
vector: Vector2D;
|
|
104
|
+
angle: number;
|
|
105
|
+
magnitude: number;
|
|
106
|
+
timestamp: number;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
interface DpadPressMessage {
|
|
110
|
+
type: MessageType.DPAD_PRESS;
|
|
111
|
+
payload: {
|
|
112
|
+
elementId: string;
|
|
113
|
+
direction: 'up' | 'down' | 'left' | 'right';
|
|
114
|
+
actionKey: string;
|
|
115
|
+
timestamp: number;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
interface DpadReleaseMessage {
|
|
119
|
+
type: MessageType.DPAD_RELEASE;
|
|
120
|
+
payload: {
|
|
121
|
+
elementId: string;
|
|
122
|
+
direction: 'up' | 'down' | 'left' | 'right';
|
|
123
|
+
actionKey: string;
|
|
124
|
+
timestamp: number;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
interface SliderChangeMessage {
|
|
128
|
+
type: MessageType.SLIDER_CHANGE;
|
|
129
|
+
payload: {
|
|
130
|
+
elementId: string;
|
|
131
|
+
actionKey: string;
|
|
132
|
+
value: number;
|
|
133
|
+
timestamp: number;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
interface RendererReadyMessage {
|
|
137
|
+
type: MessageType.RENDERER_READY;
|
|
138
|
+
}
|
|
139
|
+
interface ConfigUpdateMessage {
|
|
140
|
+
type: MessageType.CONFIG_UPDATE;
|
|
141
|
+
payload: ControllerConfig;
|
|
142
|
+
}
|
|
143
|
+
type ControllerMessage = ConfigLoadMessage | InputStartMessage | InputEndMessage | JoystickMoveMessage | DpadPressMessage | DpadReleaseMessage | SliderChangeMessage | RendererReadyMessage | ConfigUpdateMessage;
|
|
144
|
+
type InputEvent = InputStartMessage | InputEndMessage | JoystickMoveMessage | DpadPressMessage | DpadReleaseMessage | SliderChangeMessage;
|
|
145
|
+
|
|
146
|
+
type MessageHandler = (message: ControllerMessage) => void;
|
|
147
|
+
declare function createParentBridge(iframe: HTMLIFrameElement): {
|
|
148
|
+
sendConfig(config: ControllerConfig): void;
|
|
149
|
+
updateConfig(config: ControllerConfig): void;
|
|
150
|
+
onMessage(handler: MessageHandler): () => boolean;
|
|
151
|
+
onInput(handler: (event: InputEvent) => void): () => boolean;
|
|
152
|
+
destroy(): void;
|
|
153
|
+
};
|
|
154
|
+
declare function createIframeBridge(): {
|
|
155
|
+
emitControllerEvent(message: ControllerMessage): void;
|
|
156
|
+
emitInputStart(elementId: string, actionKey: string): void;
|
|
157
|
+
emitInputEnd(elementId: string, actionKey: string): void;
|
|
158
|
+
emitJoystickMove(elementId: string, x: number, y: number, angle: number, magnitude: number): void;
|
|
159
|
+
emitDpadPress(elementId: string, direction: "up" | "down" | "left" | "right", actionKey: string): void;
|
|
160
|
+
emitDpadRelease(elementId: string, direction: "up" | "down" | "left" | "right", actionKey: string): void;
|
|
161
|
+
emitSliderChange(elementId: string, actionKey: string, value: number): void;
|
|
162
|
+
signalReady(): void;
|
|
163
|
+
onMessage(handler: MessageHandler): () => boolean;
|
|
164
|
+
destroy(): void;
|
|
165
|
+
};
|
|
166
|
+
type ParentBridge = ReturnType<typeof createParentBridge>;
|
|
167
|
+
type IframeBridge = ReturnType<typeof createIframeBridge>;
|
|
168
|
+
|
|
169
|
+
declare function snapToGrid(value: number, gridDensity: number): number;
|
|
170
|
+
declare function snapPositionToGrid(position: Position, gridDensity: number): Position;
|
|
171
|
+
declare function pixelToPercentage(pixelValue: number, containerSize: number): number;
|
|
172
|
+
declare function percentageToPixel(percentageValue: number, containerSize: number): number;
|
|
173
|
+
declare function convertPixelPositionToPercentage(pixelX: number, pixelY: number, containerWidth: number, containerHeight: number): Position;
|
|
174
|
+
declare function convertPercentagePositionToPixel(position: Position, containerWidth: number, containerHeight: number): {
|
|
175
|
+
x: number;
|
|
176
|
+
y: number;
|
|
177
|
+
};
|
|
178
|
+
declare function clampPercentage(value: number): number;
|
|
179
|
+
declare function clamp(value: number, min: number, max: number): number;
|
|
180
|
+
declare function distance(a: Position, b: Position): number;
|
|
181
|
+
declare function normalizeVector(x: number, y: number): {
|
|
182
|
+
x: number;
|
|
183
|
+
y: number;
|
|
184
|
+
magnitude: number;
|
|
185
|
+
};
|
|
186
|
+
declare function angleFromVector(x: number, y: number): number;
|
|
187
|
+
declare function applyDeadzone(value: number, deadzone: number): number;
|
|
188
|
+
declare function applyDeadzoneToVector(x: number, y: number, deadzone: number): {
|
|
189
|
+
x: number;
|
|
190
|
+
y: number;
|
|
191
|
+
};
|
|
192
|
+
declare function parseAspectRatio(aspectRatio: string): {
|
|
193
|
+
width: number;
|
|
194
|
+
height: number;
|
|
195
|
+
};
|
|
196
|
+
declare function calculateCanvasDimensions(containerWidth: number, containerHeight: number, aspectRatio: string): {
|
|
197
|
+
width: number;
|
|
198
|
+
height: number;
|
|
199
|
+
offsetX: number;
|
|
200
|
+
offsetY: number;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
interface AlignmentGuide {
|
|
204
|
+
axis: 'horizontal' | 'vertical';
|
|
205
|
+
position: number;
|
|
206
|
+
sourceElementId: string;
|
|
207
|
+
targetElementId: string;
|
|
208
|
+
}
|
|
209
|
+
interface SnapResult {
|
|
210
|
+
position: Position;
|
|
211
|
+
guides: AlignmentGuide[];
|
|
212
|
+
}
|
|
213
|
+
declare function findAlignmentGuides(draggedElement: ControllerElement, dragPosition: Position, otherElements: ControllerElement[], threshold?: number): SnapResult;
|
|
214
|
+
declare function generateGridLines(gridDensity: number): number[];
|
|
215
|
+
|
|
216
|
+
interface ButtonRenderContext {
|
|
217
|
+
element: ButtonElement;
|
|
218
|
+
canvasWidth: number;
|
|
219
|
+
canvasHeight: number;
|
|
220
|
+
onInputStart: (elementId: string, actionKey: string) => void;
|
|
221
|
+
onInputEnd: (elementId: string, actionKey: string) => void;
|
|
222
|
+
}
|
|
223
|
+
declare function createButtonElement(context: ButtonRenderContext): HTMLElement;
|
|
224
|
+
declare function updateButtonElement(container: HTMLElement, element: ButtonElement, canvasWidth: number, canvasHeight: number): void;
|
|
225
|
+
|
|
226
|
+
interface JoystickRenderContext {
|
|
227
|
+
element: JoystickElement;
|
|
228
|
+
canvasWidth: number;
|
|
229
|
+
canvasHeight: number;
|
|
230
|
+
onJoystickMove: (elementId: string, vector: Vector2D, angle: number, magnitude: number) => void;
|
|
231
|
+
}
|
|
232
|
+
declare function createJoystickElement(context: JoystickRenderContext): HTMLElement;
|
|
233
|
+
declare function updateJoystickElement(container: HTMLElement, element: JoystickElement, canvasWidth: number, canvasHeight: number): void;
|
|
234
|
+
|
|
235
|
+
interface DpadRenderContext {
|
|
236
|
+
element: DpadElement;
|
|
237
|
+
canvasWidth: number;
|
|
238
|
+
canvasHeight: number;
|
|
239
|
+
onDpadPress: (elementId: string, direction: 'up' | 'down' | 'left' | 'right', actionKey: string) => void;
|
|
240
|
+
onDpadRelease: (elementId: string, direction: 'up' | 'down' | 'left' | 'right', actionKey: string) => void;
|
|
241
|
+
}
|
|
242
|
+
declare function createDpadElement(context: DpadRenderContext): HTMLElement;
|
|
243
|
+
declare function updateDpadElement(container: HTMLElement, element: DpadElement, canvasWidth: number, canvasHeight: number): void;
|
|
244
|
+
|
|
245
|
+
interface RendererState {
|
|
246
|
+
config: ControllerConfig | null;
|
|
247
|
+
canvas: HTMLElement | null;
|
|
248
|
+
elementMap: Map<string, HTMLElement>;
|
|
249
|
+
bridge: IframeBridge;
|
|
250
|
+
resizeObserver: ResizeObserver | null;
|
|
251
|
+
}
|
|
252
|
+
declare function initializeRenderer(rootElement: HTMLElement): RendererState;
|
|
253
|
+
declare function renderControllerFromConfig(rootElement: HTMLElement, config: ControllerConfig): RendererState;
|
|
254
|
+
declare function destroyRenderer(state: RendererState): void;
|
|
255
|
+
|
|
256
|
+
interface ControllerSDKOptions {
|
|
257
|
+
config: ControllerConfig;
|
|
258
|
+
container: HTMLElement;
|
|
259
|
+
iframeSrc?: string;
|
|
260
|
+
onInput?: (event: InputEvent) => void;
|
|
261
|
+
onReady?: () => void;
|
|
262
|
+
width?: string;
|
|
263
|
+
height?: string;
|
|
264
|
+
}
|
|
265
|
+
interface ControllerSDKInstance {
|
|
266
|
+
updateConfig: (config: ControllerConfig) => void;
|
|
267
|
+
destroy: () => void;
|
|
268
|
+
getIframe: () => HTMLIFrameElement;
|
|
269
|
+
}
|
|
270
|
+
declare function createControllerSDK(options: ControllerSDKOptions): ControllerSDKInstance;
|
|
271
|
+
|
|
272
|
+
export { type AlignmentGuide, type BaseElement, type ButtonElement, type ButtonRenderContext, type CanvasSettings, type ConfigLoadMessage, type ConfigUpdateMessage, type ControllerConfig, type ControllerElement, type ControllerMessage, type ControllerSDKInstance, type ControllerSDKOptions, type DpadElement, type DpadPressMessage, type DpadReleaseMessage, type DpadRenderContext, type ElementStyle, type IframeBridge, type InputEndMessage, type InputEvent, type InputStartMessage, type JoystickElement, type JoystickMoveMessage, type JoystickRenderContext, MessageType, type ParentBridge, type Position, type RendererReadyMessage, type SliderChangeMessage, type SliderElement, type SnapResult, type Vector2D, angleFromVector, applyDeadzone, applyDeadzoneToVector, calculateCanvasDimensions, clamp, clampPercentage, convertPercentagePositionToPixel, convertPixelPositionToPercentage, createButtonElement, createControllerSDK, createDpadElement, createIframeBridge, createJoystickElement, createParentBridge, destroyRenderer, distance, findAlignmentGuides, generateGridLines, initializeRenderer, normalizeVector, parseAspectRatio, percentageToPixel, pixelToPercentage, renderControllerFromConfig, snapPositionToGrid, snapToGrid, updateButtonElement, updateDpadElement, updateJoystickElement };
|