@benev/tact 0.1.0-2 → 0.1.0-3
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/README.md +120 -56
- package/package.json +3 -2
- package/s/core/{port → bindings}/action.ts +1 -1
- package/s/core/bindings/parts/defaults.ts +29 -0
- package/s/core/{port/resolution → bindings/parts}/lens-algo.ts +6 -7
- package/s/core/bindings/resolver.ts +96 -0
- package/s/core/bindings/types.ts +43 -42
- package/s/core/controllers/standard/gamepad.ts +1 -5
- package/s/core/controllers/standard/keyboard.ts +0 -3
- package/s/core/controllers/standard/pointer.ts +3 -9
- package/s/core/deck/deck.ts +40 -0
- package/s/core/deck/parts/bindings-depot.ts +24 -0
- package/s/core/deck/parts/local-storage-kv.ts +7 -0
- package/s/core/hub/bindings.ts +16 -19
- package/s/core/hub/hub.ts +2 -2
- package/s/core/hub/types.ts +14 -0
- package/s/core/index.ts +5 -3
- package/s/core/port/port.ts +1 -1
- package/s/core/testing/testing.ts +5 -5
- package/x/core/{port → bindings}/action.js +1 -1
- package/x/core/bindings/action.js.map +1 -0
- package/x/core/bindings/parts/defaults.d.ts +3 -0
- package/x/core/bindings/parts/defaults.js +23 -0
- package/x/core/bindings/parts/defaults.js.map +1 -0
- package/x/core/bindings/parts/is-pressed.js.map +1 -0
- package/x/core/bindings/parts/lens-algo.d.ts +2 -0
- package/x/core/{port/resolution → bindings/parts}/lens-algo.js +4 -4
- package/x/core/bindings/parts/lens-algo.js.map +1 -0
- package/x/core/{port/resolution → bindings}/resolver.d.ts +2 -3
- package/x/core/bindings/resolver.js +87 -0
- package/x/core/bindings/resolver.js.map +1 -0
- package/x/core/bindings/types.d.ts +29 -35
- package/x/core/bindings/types.js +0 -1
- package/x/core/bindings/types.js.map +1 -1
- package/x/core/controllers/standard/gamepad.d.ts +1 -2
- package/x/core/controllers/standard/gamepad.js +0 -4
- package/x/core/controllers/standard/gamepad.js.map +1 -1
- package/x/core/controllers/standard/keyboard.js +0 -3
- package/x/core/controllers/standard/keyboard.js.map +1 -1
- package/x/core/controllers/standard/pointer.js +3 -8
- package/x/core/controllers/standard/pointer.js.map +1 -1
- package/x/core/deck/deck.d.ts +16 -0
- package/x/core/deck/deck.js +31 -0
- package/x/core/deck/deck.js.map +1 -0
- package/x/core/deck/parts/bindings-depot.d.ts +9 -0
- package/x/core/deck/parts/bindings-depot.js +19 -0
- package/x/core/deck/parts/bindings-depot.js.map +1 -0
- package/x/core/deck/parts/local-storage-kv.d.ts +2 -0
- package/x/core/deck/parts/local-storage-kv.js +5 -0
- package/x/core/deck/parts/local-storage-kv.js.map +1 -0
- package/x/core/hub/bindings.d.ts +2 -2
- package/x/core/hub/bindings.js +15 -18
- package/x/core/hub/bindings.js.map +1 -1
- package/x/core/hub/hub.d.ts +3 -4
- package/x/core/hub/hub.js +1 -1
- package/x/core/hub/hub.js.map +1 -1
- package/x/core/hub/types.d.ts +9 -0
- package/x/core/hub/types.js +2 -0
- package/x/core/hub/types.js.map +1 -0
- package/x/core/index.d.ts +4 -3
- package/x/core/index.js +4 -3
- package/x/core/index.js.map +1 -1
- package/x/core/port/port.d.ts +1 -1
- package/x/core/port/port.js +1 -1
- package/x/core/port/port.js.map +1 -1
- package/x/core/testing/testing.d.ts +12 -32
- package/x/core/testing/testing.js +5 -5
- package/x/core/testing/testing.js.map +1 -1
- package/x/index.html +2 -2
- package/s/core/controllers/utils/modprefix.ts +0 -16
- package/s/core/port/resolution/defaults.ts +0 -30
- package/s/core/port/resolution/resolver.ts +0 -77
- package/s/core/port/resolution/types.ts +0 -9
- package/s/core/port/types.ts +0 -10
- package/x/core/controllers/utils/modprefix.d.ts +0 -1
- package/x/core/controllers/utils/modprefix.js +0 -16
- package/x/core/controllers/utils/modprefix.js.map +0 -1
- package/x/core/port/action.js.map +0 -1
- package/x/core/port/resolution/defaults.d.ts +0 -4
- package/x/core/port/resolution/defaults.js +0 -23
- package/x/core/port/resolution/defaults.js.map +0 -1
- package/x/core/port/resolution/lens-algo.d.ts +0 -2
- package/x/core/port/resolution/lens-algo.js.map +0 -1
- package/x/core/port/resolution/resolver.js +0 -64
- package/x/core/port/resolution/resolver.js.map +0 -1
- package/x/core/port/resolution/types.d.ts +0 -6
- package/x/core/port/resolution/types.js +0 -2
- package/x/core/port/resolution/types.js.map +0 -1
- package/x/core/port/types.d.ts +0 -7
- package/x/core/port/types.js +0 -2
- package/x/core/port/types.js.map +0 -1
- package/x/core/port/utils/is-pressed.js.map +0 -1
- /package/s/core/{port/utils → bindings/parts}/is-pressed.ts +0 -0
- /package/x/core/{port → bindings}/action.d.ts +0 -0
- /package/x/core/{port/utils → bindings/parts}/is-pressed.d.ts +0 -0
- /package/x/core/{port/utils → bindings/parts}/is-pressed.js +0 -0
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
|
|
2
|
-
# @benev/tact
|
|
3
|
-
> web game input library, from keypress to couch co-op
|
|
2
|
+
# 🎮 @benev/tact
|
|
3
|
+
> *web game input library, from keypress to couch co-op*
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
npm install @benev/tact
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
- 🛹 **deck**
|
|
9
|
+
- 🛹 **deck** full setup with localstorate persistence
|
|
10
10
|
- 🎮 **controllers** produce user input samples
|
|
11
11
|
- 🧩 **bindings** describe how actions interpret samples
|
|
12
12
|
- 🔌 **port** updates actions by interpreting samples
|
|
@@ -18,38 +18,36 @@ npm install @benev/tact
|
|
|
18
18
|
<br/><br/>
|
|
19
19
|
|
|
20
20
|
## 🍋 tact deck
|
|
21
|
-
> *
|
|
21
|
+
> *the full setup*
|
|
22
22
|
|
|
23
|
-
the deck is the heart of tact
|
|
23
|
+
the deck is the heart of tact, tying all the pieces together and putting a bow in it for you.
|
|
24
24
|
|
|
25
25
|
### 🛹 deck setup
|
|
26
26
|
- **import stuff from tact**
|
|
27
27
|
```ts
|
|
28
28
|
import * as tact from "@benev/tact"
|
|
29
29
|
```
|
|
30
|
-
- **setup your deck**
|
|
30
|
+
- **setup your deck, and your game's bindings**
|
|
31
31
|
```ts
|
|
32
|
-
const deck =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const deck = await tact.Deck.load({
|
|
33
|
+
|
|
34
|
+
// how many players ports are possible? 1 is fine..
|
|
35
|
+
portCount: 4,
|
|
36
|
+
|
|
37
|
+
// where to store the user-customized bindings
|
|
38
|
+
kv: tact.localStorageKv(),
|
|
39
|
+
|
|
40
|
+
// default archetypal bindings for your game
|
|
41
|
+
bindings: {
|
|
42
|
+
...tact.hubBindings(),
|
|
36
43
|
walking: {
|
|
37
|
-
forward:
|
|
38
|
-
|
|
39
|
-
{lenses: [{code: "gamepad.stick.left.up"}]},
|
|
40
|
-
],
|
|
41
|
-
jump: [
|
|
42
|
-
{lenses: [{code: "Space"}]},
|
|
43
|
-
{lenses: [{code: "gamepad.a"}]},
|
|
44
|
-
],
|
|
44
|
+
forward: "KeyW",
|
|
45
|
+
jump: "Space",
|
|
45
46
|
},
|
|
46
47
|
gunning: {
|
|
47
|
-
shoot: [
|
|
48
|
-
{lenses: [{code: "pointer.button.left"}]},
|
|
49
|
-
{lenses: [{code: "gamepad.trigger.right"}]},
|
|
50
|
-
],
|
|
48
|
+
shoot: ["or", "pointer.button.left", "gamepad.trigger.right"],
|
|
51
49
|
},
|
|
52
|
-
}
|
|
50
|
+
},
|
|
53
51
|
})
|
|
54
52
|
```
|
|
55
53
|
|
|
@@ -83,10 +81,10 @@ the deck is the heart of tact.. it ties all the important parts together..
|
|
|
83
81
|
const [p1, p2, p3, p4] = deck.hub.poll()
|
|
84
82
|
|
|
85
83
|
// check if the first player is pressing "forward" action
|
|
86
|
-
p1.
|
|
84
|
+
p1.walking.forward.pressed // true
|
|
87
85
|
|
|
88
86
|
// check how hard the second player is pulling that trigger
|
|
89
|
-
p2.
|
|
87
|
+
p2.gunning.shoot.value // 0.123
|
|
90
88
|
})
|
|
91
89
|
```
|
|
92
90
|
|
|
@@ -95,7 +93,7 @@ the deck is the heart of tact.. it ties all the important parts together..
|
|
|
95
93
|
<br/><br/>
|
|
96
94
|
|
|
97
95
|
## 🍋 tact controllers
|
|
98
|
-
> *
|
|
96
|
+
> *sources of user input "samples"*
|
|
99
97
|
|
|
100
98
|
### 🎮 polling is good, actually
|
|
101
99
|
- tact operates on the basis of *polling*
|
|
@@ -117,7 +115,7 @@ the deck is the heart of tact.. it ties all the important parts together..
|
|
|
117
115
|
// ["Space", 0]
|
|
118
116
|
// ]
|
|
119
117
|
```
|
|
120
|
-
-
|
|
118
|
+
- some controllers have disposers to call when you're done with them
|
|
121
119
|
```ts
|
|
122
120
|
keyboard.dispose()
|
|
123
121
|
```
|
|
@@ -135,17 +133,6 @@ the deck is the heart of tact.. it ties all the important parts together..
|
|
|
135
133
|
- sometimes we use numbers greater then `1`, like for dots of pointer movement like in `pointer.move.up`
|
|
136
134
|
- don't worry about sensitivity, deadzones, values like `0.00001` — actions will account for all that using bindings later on
|
|
137
135
|
|
|
138
|
-
### 🎮 sample code modprefixes
|
|
139
|
-
- here at tact, we have this nifty `modprefix` convention
|
|
140
|
-
- consider a keycode like `KeyA`
|
|
141
|
-
- `ctrl-KeyA` means the "ctrl" modifier was held
|
|
142
|
-
- `alt-KeyA` means the "alt" modifier was held
|
|
143
|
-
- `meta-KeyA` means the "meta" modifier was held (command or windows keys)
|
|
144
|
-
- `shift-KeyA` means the "shift" modifier was held
|
|
145
|
-
- `ctrl-alt-meta-shift-KeyA` they can stack, but always in this order (the word `cams` helps remind me the valid order)
|
|
146
|
-
- `x-KeyA` means *no* modifier was held (exclusive)
|
|
147
|
-
- `KeyA` doesn't care if any modifiers were held or not
|
|
148
|
-
|
|
149
136
|
### 🎮 sample code reference
|
|
150
137
|
- **KeyboardController**
|
|
151
138
|
- any [standard keycode](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values)
|
|
@@ -153,19 +140,14 @@ the deck is the heart of tact.. it ties all the important parts together..
|
|
|
153
140
|
- `Space`
|
|
154
141
|
- `Digit2`
|
|
155
142
|
- etc
|
|
156
|
-
- plus the modprefixes
|
|
157
|
-
- `ctrl-KeyA`
|
|
158
|
-
- `alt-shift-Space`
|
|
159
|
-
- `x-Digit2`
|
|
160
|
-
- etc
|
|
161
143
|
- **PointerController**
|
|
162
|
-
- mouse buttons
|
|
144
|
+
- mouse buttons
|
|
163
145
|
- `pointer.button.left`
|
|
164
146
|
- `pointer.button.right`
|
|
165
147
|
- `pointer.button.middle`
|
|
166
148
|
- `pointer.button.4`
|
|
167
149
|
- `pointer.button.5`
|
|
168
|
-
- mouse wheel
|
|
150
|
+
- mouse wheel
|
|
169
151
|
- `pointer.wheel.up`
|
|
170
152
|
- `pointer.wheel.down`
|
|
171
153
|
- `pointer.wheel.left`
|
|
@@ -212,23 +194,93 @@ the deck is the heart of tact.. it ties all the important parts together..
|
|
|
212
194
|
> *keybindings! they describe how actions interpret samples*
|
|
213
195
|
|
|
214
196
|
### 🧩 bindings example
|
|
215
|
-
- let's start with a small example
|
|
197
|
+
- **let's start with a small example:**
|
|
216
198
|
```ts
|
|
217
199
|
const bindings = tact.asBindings({
|
|
218
200
|
walking: {
|
|
219
|
-
forward:
|
|
220
|
-
jump:
|
|
201
|
+
forward: "KeyW",
|
|
202
|
+
jump: "Space",
|
|
221
203
|
},
|
|
222
204
|
gunning: {
|
|
223
|
-
shoot: [
|
|
205
|
+
shoot: ["or", "pointer.button.left", "gamepad.trigger.right"],
|
|
224
206
|
},
|
|
225
207
|
})
|
|
226
208
|
```
|
|
227
209
|
- `walking` and `gunning` are **modes**
|
|
228
210
|
- `forward`, `jump`, and `shoot` are **actions**
|
|
229
|
-
-
|
|
230
|
-
|
|
231
|
-
|
|
211
|
+
- note that whole modes can be enabled or disabled during gameplay
|
|
212
|
+
|
|
213
|
+
### 🧩 bindings are a lispy domain-specific-language
|
|
214
|
+
- **you can do complex stuff**
|
|
215
|
+
```ts
|
|
216
|
+
["or",
|
|
217
|
+
"KeyQ",
|
|
218
|
+
["and",
|
|
219
|
+
"KeyA",
|
|
220
|
+
"KeyD",
|
|
221
|
+
["not", "KeyS"],
|
|
222
|
+
],
|
|
223
|
+
]
|
|
224
|
+
```
|
|
225
|
+
- press Q, or
|
|
226
|
+
- press A + D, while not pressing S
|
|
227
|
+
- **you can get really weird**
|
|
228
|
+
```ts
|
|
229
|
+
["cond",
|
|
230
|
+
["code", "gamepad.trigger.right", {deadzone: 0.5, timing: ["tap"]}],
|
|
231
|
+
["and", "gamepad.bumper.left", ["not", "gamepad.trigger.left"]],
|
|
232
|
+
]
|
|
233
|
+
```
|
|
234
|
+
- hold LB and tap RT halfway while not holding LT
|
|
235
|
+
|
|
236
|
+
### 🧩 bindings atom reference
|
|
237
|
+
- **string** — strings are interpreted as "code" atoms with default settings
|
|
238
|
+
- **"code"** — allows you to customize the settings
|
|
239
|
+
```ts
|
|
240
|
+
["code", "KeyA", {
|
|
241
|
+
scale: 1,
|
|
242
|
+
invert: false,
|
|
243
|
+
deadzone: 0.2,
|
|
244
|
+
timing: ["direct"],
|
|
245
|
+
}]
|
|
246
|
+
```
|
|
247
|
+
- defaults shown
|
|
248
|
+
- `scale` is sensitivity, the value gets multiplied by this
|
|
249
|
+
- `invert` will invert a value by subtracting it from 1
|
|
250
|
+
- `deadzone` ignores values below the threshold (and remaps to preserve the range)
|
|
251
|
+
- `timing` lets you specify special timing considerations
|
|
252
|
+
- `["direct"]` ignores timing considerations
|
|
253
|
+
- `["tap", 250]` only fires for taps under 250ms
|
|
254
|
+
- `["hold", 250]` only fires for holds over 250ms
|
|
255
|
+
- **"or"** — resolves to the maximum value
|
|
256
|
+
```ts
|
|
257
|
+
["or", "KeyA", "KeyB", "KeyC"]
|
|
258
|
+
```
|
|
259
|
+
- **"and"** — resolves to the minimum value
|
|
260
|
+
```ts
|
|
261
|
+
["and", "KeyA", "KeyB", "KeyC"]
|
|
262
|
+
```
|
|
263
|
+
- **"not"** — resolves to the opposite effect
|
|
264
|
+
```ts
|
|
265
|
+
["not", "KeyA"]
|
|
266
|
+
```
|
|
267
|
+
- **"cond"** — conditional situation (example for modifiers shown)
|
|
268
|
+
```ts
|
|
269
|
+
["cond", "KeyA", ["and",
|
|
270
|
+
["or", "ControlLeft", "ControlRight"],
|
|
271
|
+
["not", ["or", "AltLeft", "AltRight"]],
|
|
272
|
+
["not", ["or", "MetaLeft", "MetaRight"]],
|
|
273
|
+
["not", ["or", "ShiftLeft", "ShiftRight"]],
|
|
274
|
+
]]
|
|
275
|
+
```
|
|
276
|
+
- KeyA is the value that gets used
|
|
277
|
+
- but only if the following condition passes
|
|
278
|
+
- **"mods"** — macro for modifiers
|
|
279
|
+
```ts
|
|
280
|
+
["mods", "KeyA", {ctrl: true}]
|
|
281
|
+
```
|
|
282
|
+
- equivalent to the "cond" example above
|
|
283
|
+
- `ctrl`, `alt`, `meta`, `shift` are available
|
|
232
284
|
|
|
233
285
|
|
|
234
286
|
|
|
@@ -237,6 +289,8 @@ the deck is the heart of tact.. it ties all the important parts together..
|
|
|
237
289
|
## 🍋 tact port
|
|
238
290
|
> *polling gives you "actions"*
|
|
239
291
|
|
|
292
|
+
a port represents a single playable port, and you poll it each frame to resolve actions for you to read.
|
|
293
|
+
|
|
240
294
|
### 🔌 port basics
|
|
241
295
|
- **make a port**
|
|
242
296
|
```ts
|
|
@@ -261,6 +315,13 @@ the deck is the heart of tact.. it ties all the important parts together..
|
|
|
261
315
|
```ts
|
|
262
316
|
port.bindings = freshBindings
|
|
263
317
|
```
|
|
318
|
+
- **wire up gamepad auto connect/disconnect**
|
|
319
|
+
```ts
|
|
320
|
+
tact.autoGamepads(controller => {
|
|
321
|
+
port.controllers.add(controller)
|
|
322
|
+
return () => port.controllers.delete(controller)
|
|
323
|
+
})
|
|
324
|
+
```
|
|
264
325
|
|
|
265
326
|
### 🔌 interrogating actions
|
|
266
327
|
- **poll the port every frame**
|
|
@@ -294,11 +355,14 @@ the hub embraces that analogy, helping you coordinate the plugging and unpluggin
|
|
|
294
355
|
### 🛞 create a hub with ports
|
|
295
356
|
- **adopt standard hub bindings**
|
|
296
357
|
```ts
|
|
297
|
-
|
|
298
|
-
|
|
358
|
+
const hubBindings = {
|
|
359
|
+
...yourBindings,
|
|
360
|
+
|
|
361
|
+
// mixin standard hub bindings
|
|
362
|
+
...tact.hubBindings(),
|
|
363
|
+
}
|
|
299
364
|
```
|
|
300
|
-
-
|
|
301
|
-
- this lets players to shimmy what port their controller is plugged into
|
|
365
|
+
- hub bindings let players shimmy what port their controller is plugged into
|
|
302
366
|
- gamepad: hold gamma (middle button) and press bumpers
|
|
303
367
|
- keyboard: left bracket or right bracket
|
|
304
368
|
- **make hub with multiple ports at the ready**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@benev/tact",
|
|
3
|
-
"version": "0.1.0-
|
|
3
|
+
"version": "0.1.0-3",
|
|
4
4
|
"description": "keybindings and gamepad support for web games",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chase Moskal <chasemoskal@gmail.com>",
|
|
@@ -33,9 +33,10 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@benev/math": "^0.2.0-1",
|
|
36
|
+
"@e280/kv": "^0.1.0",
|
|
36
37
|
"@e280/sly": "^0.1.1",
|
|
37
38
|
"@e280/strata": "^0.1.0",
|
|
38
|
-
"@e280/stz": "^0.2.0-
|
|
39
|
+
"@e280/stz": "^0.2.0-5"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@e280/science": "^0.1.1",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
import {Code, CodeSettings, CodeState} from "../types.js"
|
|
3
|
+
|
|
4
|
+
export const defaultHoldTime = 250
|
|
5
|
+
|
|
6
|
+
export function defaultCodeState([,,settings]: Code): CodeState {
|
|
7
|
+
return {
|
|
8
|
+
lastValue: 0,
|
|
9
|
+
holdStart: 0,
|
|
10
|
+
settings: defaultifyCodeSettings(settings),
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function defaultCodeSettings(): CodeSettings {
|
|
15
|
+
return {
|
|
16
|
+
scale: 1,
|
|
17
|
+
invert: false,
|
|
18
|
+
deadzone: 0.2,
|
|
19
|
+
timing: ["direct"],
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function defaultifyCodeSettings(partial: Partial<CodeSettings> = {}): CodeSettings {
|
|
24
|
+
return {
|
|
25
|
+
...defaultCodeSettings(),
|
|
26
|
+
...partial,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
import {pipe} from "@e280/stz"
|
|
3
3
|
import {Scalar} from "@benev/math"
|
|
4
|
-
|
|
5
|
-
import {
|
|
4
|
+
import {CodeState} from "../types.js"
|
|
5
|
+
import {isPressed} from "./is-pressed.js"
|
|
6
6
|
import {defaultHoldTime} from "./defaults.js"
|
|
7
|
-
import {isPressed} from "../utils/is-pressed.js"
|
|
8
7
|
|
|
9
8
|
export const lensAlgo = (
|
|
10
9
|
now: number,
|
|
11
|
-
state:
|
|
10
|
+
state: CodeState,
|
|
12
11
|
v: number,
|
|
13
12
|
) => pipe(v).line(
|
|
14
13
|
|
|
@@ -40,9 +39,9 @@ export const lensAlgo = (
|
|
|
40
39
|
const {settings} = state
|
|
41
40
|
|
|
42
41
|
const holdTime = (
|
|
43
|
-
settings.timing
|
|
42
|
+
settings.timing[0] === "direct"
|
|
44
43
|
? undefined
|
|
45
|
-
: settings.timing
|
|
44
|
+
: settings.timing[1]
|
|
46
45
|
) ?? defaultHoldTime
|
|
47
46
|
|
|
48
47
|
const isFreshlyPressed = !isPressed(state.lastValue) && isPressed(value)
|
|
@@ -54,7 +53,7 @@ export const lensAlgo = (
|
|
|
54
53
|
|
|
55
54
|
state.lastValue = value
|
|
56
55
|
|
|
57
|
-
switch (settings.timing
|
|
56
|
+
switch (settings.timing[0]) {
|
|
58
57
|
case "direct":
|
|
59
58
|
return value
|
|
60
59
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
|
|
2
|
+
import {MapG, pub, obMap} from "@e280/stz"
|
|
3
|
+
import {Action} from "./action.js"
|
|
4
|
+
import {lensAlgo} from "./parts/lens-algo.js"
|
|
5
|
+
import {SampleMap} from "../controllers/types.js"
|
|
6
|
+
import {tmax, tmin} from "../../utils/quick-math.js"
|
|
7
|
+
import {defaultCodeState} from "./parts/defaults.js"
|
|
8
|
+
import {Actions, Atom, Bindings, CodeSettings, CodeState} from "./types.js"
|
|
9
|
+
|
|
10
|
+
export class Resolver<B extends Bindings> {
|
|
11
|
+
#actions: Actions<B>
|
|
12
|
+
|
|
13
|
+
#now = 0
|
|
14
|
+
#samples: SampleMap = new Map()
|
|
15
|
+
#codeStates = new MapG<number, CodeState>()
|
|
16
|
+
#update = pub()
|
|
17
|
+
|
|
18
|
+
constructor(public readonly bindings: B, modes: Set<keyof B>) {
|
|
19
|
+
this.#actions = obMap(bindings, (bracket, mode) => obMap(bracket, atom => {
|
|
20
|
+
const action = new Action()
|
|
21
|
+
this.#update.subscribe(() => {
|
|
22
|
+
const value = modes.has(mode)
|
|
23
|
+
? this.#resolveAtom()(atom)
|
|
24
|
+
: 0
|
|
25
|
+
Action.update(action, value)
|
|
26
|
+
})
|
|
27
|
+
return action
|
|
28
|
+
})) as Actions<B>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
poll(now: number, samples: SampleMap) {
|
|
32
|
+
this.#now = now
|
|
33
|
+
this.#samples = samples
|
|
34
|
+
this.#update()
|
|
35
|
+
return this.#actions
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#resolveCode(count: number, code: string, settings?: Partial<CodeSettings>) {
|
|
39
|
+
const state = this.#codeStates.guarantee(
|
|
40
|
+
count,
|
|
41
|
+
() => defaultCodeState(["code", code, settings]),
|
|
42
|
+
)
|
|
43
|
+
const value = this.#samples.get(code) ?? 0
|
|
44
|
+
return lensAlgo(this.#now, state, value)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#resolveAtom = (context: {count: number} = {count: 0}) => (atom: Atom): number => {
|
|
48
|
+
const resolveAtom = this.#resolveAtom(context)
|
|
49
|
+
if (typeof atom === "string") {
|
|
50
|
+
return this.#resolveCode(context.count++, atom)
|
|
51
|
+
}
|
|
52
|
+
else switch (atom[0]) {
|
|
53
|
+
case "code": {
|
|
54
|
+
const [, code, settings] = atom
|
|
55
|
+
return this.#resolveCode(context.count++, code, settings)
|
|
56
|
+
}
|
|
57
|
+
case "and": {
|
|
58
|
+
const [,...atoms] = atom
|
|
59
|
+
const values = atoms.map(resolveAtom)
|
|
60
|
+
return tmin(values)
|
|
61
|
+
}
|
|
62
|
+
case "or": {
|
|
63
|
+
const [,...atoms] = atom
|
|
64
|
+
const values = atoms.map(resolveAtom)
|
|
65
|
+
return tmax(values)
|
|
66
|
+
}
|
|
67
|
+
case "not": {
|
|
68
|
+
const [, subject] = atom
|
|
69
|
+
const value = resolveAtom(subject)
|
|
70
|
+
return (value > 0) ? 0 : 1
|
|
71
|
+
}
|
|
72
|
+
case "cond": {
|
|
73
|
+
const [, subject, guard] = atom
|
|
74
|
+
return (resolveAtom(guard) > 0)
|
|
75
|
+
? resolveAtom(subject)
|
|
76
|
+
: 0
|
|
77
|
+
}
|
|
78
|
+
case "mods": {
|
|
79
|
+
const [, subject, modifiers] = atom
|
|
80
|
+
const maybe = (x: boolean, ...codes: Atom[]): Atom => x
|
|
81
|
+
? ["or", ...codes]
|
|
82
|
+
: ["not", ["or", ...codes]]
|
|
83
|
+
const guard = resolveAtom(["cond", subject, ["and",
|
|
84
|
+
maybe(modifiers.ctrl ?? false, "ControlLeft", "ControlRight"),
|
|
85
|
+
maybe(modifiers.alt ?? false, "AltLeft", "AltRight"),
|
|
86
|
+
maybe(modifiers.meta ?? false, "MetaLeft", "MetaRight"),
|
|
87
|
+
maybe(modifiers.shift ?? false, "ShiftLeft", "ShiftRight"),
|
|
88
|
+
]])
|
|
89
|
+
return (guard > 0)
|
|
90
|
+
? resolveAtom(subject)
|
|
91
|
+
: 0
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
package/s/core/bindings/types.ts
CHANGED
|
@@ -1,56 +1,57 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
|
-
| DirectTiming
|
|
4
|
-
| TapTiming
|
|
5
|
-
| HoldTiming
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
export type DirectTiming = {style: "direct"}
|
|
9
|
-
export type TapTiming = {style: "tap", holdTime?: number}
|
|
10
|
-
export type HoldTiming = {style: "hold", holdTime?: number}
|
|
2
|
+
import {Action} from "./action.js"
|
|
11
3
|
|
|
12
|
-
export type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
timing: Timing
|
|
4
|
+
export type Actions<B extends Bindings> = {
|
|
5
|
+
[Mode in keyof B]: {
|
|
6
|
+
[K in keyof B[Mode]]: Action
|
|
7
|
+
}
|
|
17
8
|
}
|
|
18
9
|
|
|
19
|
-
export type
|
|
20
|
-
|
|
21
|
-
settings?: Partial<LensSettings>
|
|
22
|
-
}
|
|
10
|
+
export type Bindings = {[mode: string]: Bracket}
|
|
11
|
+
export type Bracket = {[action: string]: Atom}
|
|
23
12
|
|
|
24
|
-
export type
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
forbidden?: Lens[]
|
|
13
|
+
export type AsBindings<B extends Bindings> = B
|
|
14
|
+
export function asBindings<B extends Bindings>(bindings: B) {
|
|
15
|
+
return bindings
|
|
28
16
|
}
|
|
29
17
|
|
|
30
|
-
export type
|
|
18
|
+
export type Code = ["code", string, settings?: Partial<CodeSettings>]
|
|
19
|
+
export type And = ["and", ...Atom[]]
|
|
20
|
+
export type Or = ["or", ...Atom[]]
|
|
21
|
+
export type Not = ["not", Atom]
|
|
22
|
+
export type Cond = ["cond", Atom, guard: Atom]
|
|
23
|
+
export type Mods = ["mods", Atom, modifiers: Partial<Modifiers>]
|
|
24
|
+
|
|
25
|
+
export type Atom = string | (
|
|
26
|
+
| Code
|
|
27
|
+
| And
|
|
28
|
+
| Or
|
|
29
|
+
| Not
|
|
30
|
+
| Cond
|
|
31
|
+
| Mods
|
|
32
|
+
)
|
|
31
33
|
|
|
32
|
-
export type
|
|
33
|
-
|
|
34
|
+
export type CodeSettings = {
|
|
35
|
+
scale: number
|
|
36
|
+
invert: boolean
|
|
37
|
+
deadzone: number
|
|
38
|
+
timing: (
|
|
39
|
+
| ["direct"]
|
|
40
|
+
| ["tap", holdTime?: number]
|
|
41
|
+
| ["hold", holdTime?: number]
|
|
42
|
+
)
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
export type
|
|
37
|
-
|
|
45
|
+
export type CodeState = {
|
|
46
|
+
settings: CodeSettings
|
|
47
|
+
lastValue: number
|
|
48
|
+
holdStart: number
|
|
38
49
|
}
|
|
39
50
|
|
|
40
|
-
export type
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
export type Modifiers = {
|
|
52
|
+
ctrl: boolean
|
|
53
|
+
alt: boolean
|
|
54
|
+
shift: boolean
|
|
55
|
+
meta: boolean
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
export const hubMode = "hub" as const
|
|
47
|
-
|
|
48
|
-
export type AsHubBindings<B extends Bindings> = {
|
|
49
|
-
[hubMode]: {
|
|
50
|
-
shimmyNext: Spoon[],
|
|
51
|
-
shimmyPrevious: Spoon[],
|
|
52
|
-
}
|
|
53
|
-
} & B
|
|
54
|
-
|
|
55
|
-
export type HubBindings = AsHubBindings<Bindings>
|
|
56
|
-
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
+
import {Pad} from "../../../utils/gamepads.js"
|
|
2
3
|
import {tmax} from "../../../utils/quick-math.js"
|
|
3
4
|
import {splitAxis} from "../../../utils/split-axis.js"
|
|
4
5
|
import {SamplerController} from "../infra/sampler.js"
|
|
5
|
-
import {gamepads, Pad} from "../../../utils/gamepads.js"
|
|
6
6
|
|
|
7
7
|
const gamepadButtonCodes = [
|
|
8
8
|
"gamepad.a",
|
|
@@ -25,10 +25,6 @@ const gamepadButtonCodes = [
|
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
export class GamepadController extends SamplerController {
|
|
28
|
-
static on(fn: (controller: GamepadController) => () => void) {
|
|
29
|
-
return gamepads(pad => fn(new this(pad)))
|
|
30
|
-
}
|
|
31
|
-
|
|
32
28
|
constructor(public pad: Pad) {
|
|
33
29
|
super()
|
|
34
30
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import {coalesce, ev, sub} from "@e280/stz"
|
|
3
3
|
import {Sample} from "../types.js"
|
|
4
4
|
import {Controller} from "../controller.js"
|
|
5
|
-
import {modprefix} from "../utils/modprefix.js"
|
|
6
5
|
|
|
7
6
|
export class KeyboardController extends Controller {
|
|
8
7
|
on = sub<Sample>()
|
|
@@ -27,11 +26,9 @@ export class KeyboardController extends Controller {
|
|
|
27
26
|
keydown: (event: KeyboardEvent) => {
|
|
28
27
|
if (event.repeat) return
|
|
29
28
|
down(event.code)
|
|
30
|
-
down(modprefix(event, event.code))
|
|
31
29
|
},
|
|
32
30
|
keyup: (event: KeyboardEvent) => {
|
|
33
31
|
up(event.code)
|
|
34
|
-
up(modprefix(event, event.code))
|
|
35
32
|
},
|
|
36
33
|
}),
|
|
37
34
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import {ev} from "@e280/stz"
|
|
3
3
|
import {Vec2} from "@benev/math"
|
|
4
|
-
import {modprefix} from "../utils/modprefix.js"
|
|
5
4
|
import {splitAxis} from "../../../utils/split-axis.js"
|
|
6
5
|
import {SamplerController} from "../infra/sampler.js"
|
|
7
6
|
|
|
@@ -13,20 +12,15 @@ export class PointerController extends SamplerController {
|
|
|
13
12
|
constructor(target: EventTarget = window) {
|
|
14
13
|
super()
|
|
15
14
|
|
|
16
|
-
const dispatch = (event: PointerEvent | WheelEvent, code: string, value: number) => {
|
|
17
|
-
this.setSample(code, value)
|
|
18
|
-
this.setSample(modprefix(event, code), value)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
15
|
this.dispose = ev(target, {
|
|
22
16
|
pointerdown: (event: PointerEvent) => {
|
|
23
17
|
const code = PointerController.buttonCode(event)
|
|
24
|
-
|
|
18
|
+
this.setSample(code, 1)
|
|
25
19
|
},
|
|
26
20
|
|
|
27
21
|
pointerup: (event: PointerEvent) => {
|
|
28
22
|
const code = PointerController.buttonCode(event)
|
|
29
|
-
|
|
23
|
+
this.setSample(code, 0)
|
|
30
24
|
},
|
|
31
25
|
|
|
32
26
|
pointermove: (event: PointerEvent) => {
|
|
@@ -38,7 +32,7 @@ export class PointerController extends SamplerController {
|
|
|
38
32
|
|
|
39
33
|
wheel: (event: WheelEvent) => {
|
|
40
34
|
for (const [code, value] of PointerController.wheelCodes(event))
|
|
41
|
-
|
|
35
|
+
this.setSample(code, value)
|
|
42
36
|
},
|
|
43
37
|
})
|
|
44
38
|
}
|