@benev/tact 0.1.0-4 → 0.1.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.
Files changed (120) hide show
  1. package/README.md +4 -15
  2. package/package.json +9 -9
  3. package/s/core/bindings/parts/defaults.ts +3 -2
  4. package/s/core/bindings/parts/lens-algo.ts +5 -1
  5. package/s/core/bindings/parts/lookup-proxies.ts +17 -0
  6. package/s/core/bindings/resolver.ts +24 -17
  7. package/s/core/bindings/types.ts +1 -0
  8. package/s/core/core.test.ts +22 -0
  9. package/s/core/devices/standard/gamepad.ts +1 -1
  10. package/s/core/devices/standard/pointer.ts +4 -0
  11. package/s/core/testing/testing.ts +2 -1
  12. package/s/deck/components/components.ts +22 -0
  13. package/s/deck/components/deck-bindings/component.ts +99 -0
  14. package/s/{demo/ui/tact-demo → deck/components/deck-bindings}/style.css.ts +3 -1
  15. package/s/deck/components/deck-overlay/component.ts +51 -0
  16. package/s/deck/{views → components}/deck-overlay/style.css.ts +0 -2
  17. package/s/deck/components/framework.ts +17 -0
  18. package/s/deck/deck.ts +12 -7
  19. package/s/deck/index.ts +2 -2
  20. package/s/deck/parts/db.ts +1 -1
  21. package/s/demo/main.bundle.ts +7 -2
  22. package/s/demo/ui/theater/styles.css.ts +1 -0
  23. package/s/demo/ui/theater/view.ts +51 -44
  24. package/s/demo/ui/theater/virtual/view.ts +1 -1
  25. package/s/demo/ui/utils/loader.ts +2 -2
  26. package/s/index.html.ts +1 -1
  27. package/s/nubs/vpad/styles.css.ts +4 -4
  28. package/s/utils/types.ts +19 -0
  29. package/x/core/bindings/parts/defaults.d.ts +1 -1
  30. package/x/core/bindings/parts/defaults.js +3 -2
  31. package/x/core/bindings/parts/defaults.js.map +1 -1
  32. package/x/core/bindings/parts/lens-algo.js +5 -1
  33. package/x/core/bindings/parts/lens-algo.js.map +1 -1
  34. package/x/core/bindings/parts/lookup-proxies.d.ts +1 -0
  35. package/x/core/bindings/parts/lookup-proxies.js +19 -0
  36. package/x/core/bindings/parts/lookup-proxies.js.map +1 -0
  37. package/x/core/bindings/resolver.js +20 -16
  38. package/x/core/bindings/resolver.js.map +1 -1
  39. package/x/core/bindings/types.d.ts +1 -0
  40. package/x/core/core.test.d.ts +1 -0
  41. package/x/core/core.test.js +21 -0
  42. package/x/core/core.test.js.map +1 -1
  43. package/x/core/devices/infra/group.d.ts +1 -2
  44. package/x/core/devices/standard/gamepad.js +1 -1
  45. package/x/core/devices/standard/gamepad.js.map +1 -1
  46. package/x/core/devices/standard/keyboard.d.ts +1 -4
  47. package/x/core/devices/standard/pointer.d.ts +1 -4
  48. package/x/core/devices/standard/pointer.js +4 -0
  49. package/x/core/devices/standard/pointer.js.map +1 -1
  50. package/x/core/hub/port.d.ts +2 -3
  51. package/x/core/testing/testing.d.ts +5 -0
  52. package/x/core/testing/testing.js +1 -0
  53. package/x/core/testing/testing.js.map +1 -1
  54. package/x/deck/components/components.d.ts +14 -0
  55. package/x/deck/components/components.js +9 -0
  56. package/x/deck/components/components.js.map +1 -0
  57. package/x/deck/components/deck-bindings/component.d.ts +6 -0
  58. package/x/deck/components/deck-bindings/component.js +83 -0
  59. package/x/deck/components/deck-bindings/component.js.map +1 -0
  60. package/x/deck/components/deck-bindings/style.css.js +5 -0
  61. package/x/deck/components/deck-bindings/style.css.js.map +1 -0
  62. package/x/deck/components/deck-overlay/component.d.ts +6 -0
  63. package/x/deck/components/deck-overlay/component.js +44 -0
  64. package/x/deck/components/deck-overlay/component.js.map +1 -0
  65. package/x/deck/components/deck-overlay/style.css.js.map +1 -0
  66. package/x/deck/components/framework.d.ts +7 -0
  67. package/x/deck/components/framework.js +13 -0
  68. package/x/deck/components/framework.js.map +1 -0
  69. package/x/deck/deck.d.ts +9 -10
  70. package/x/deck/deck.js +12 -7
  71. package/x/deck/deck.js.map +1 -1
  72. package/x/deck/index.d.ts +2 -2
  73. package/x/deck/index.js +2 -2
  74. package/x/deck/index.js.map +1 -1
  75. package/x/deck/parts/catalog.d.ts +1 -2
  76. package/x/deck/parts/db.d.ts +2 -5
  77. package/x/deck/parts/db.js +1 -1
  78. package/x/deck/parts/db.js.map +1 -1
  79. package/x/deck/parts/overlay-visibility.d.ts +1 -4
  80. package/x/demo/game/game.d.ts +1 -4
  81. package/x/demo/main.bundle.js +8 -2
  82. package/x/demo/main.bundle.js.map +1 -1
  83. package/x/demo/main.bundle.min.js +91 -46
  84. package/x/demo/main.bundle.min.js.map +4 -4
  85. package/x/demo/ui/theater/styles.css.js +1 -0
  86. package/x/demo/ui/theater/styles.css.js.map +1 -1
  87. package/x/demo/ui/theater/view.d.ts +367 -1
  88. package/x/demo/ui/theater/view.js +25 -17
  89. package/x/demo/ui/theater/view.js.map +1 -1
  90. package/x/demo/ui/theater/virtual/view.js +1 -1
  91. package/x/demo/ui/theater/virtual/view.js.map +1 -1
  92. package/x/demo/ui/utils/loader.d.ts +2 -1
  93. package/x/demo/ui/utils/loader.js +2 -2
  94. package/x/demo/ui/utils/loader.js.map +1 -1
  95. package/x/index.html +21 -21
  96. package/x/index.html.js +1 -1
  97. package/x/nubs/stick/component.d.ts +1 -1
  98. package/x/nubs/vpad/component.d.ts +1 -1
  99. package/x/nubs/vpad/styles.css.js +4 -4
  100. package/x/utils/types.d.ts +3 -0
  101. package/x/utils/types.js +3 -0
  102. package/x/utils/types.js.map +1 -0
  103. package/s/deck/views/deck-overlay/component.ts +0 -48
  104. package/s/deck/views/framework.ts +0 -14
  105. package/s/demo/ui/tact-demo/component.ts +0 -13
  106. package/x/deck/views/deck-overlay/component.d.ts +0 -2
  107. package/x/deck/views/deck-overlay/component.js +0 -40
  108. package/x/deck/views/deck-overlay/component.js.map +0 -1
  109. package/x/deck/views/deck-overlay/style.css.js.map +0 -1
  110. package/x/deck/views/framework.d.ts +0 -3
  111. package/x/deck/views/framework.js +0 -8
  112. package/x/deck/views/framework.js.map +0 -1
  113. package/x/demo/ui/tact-demo/component.d.ts +0 -4
  114. package/x/demo/ui/tact-demo/component.js +0 -12
  115. package/x/demo/ui/tact-demo/component.js.map +0 -1
  116. package/x/demo/ui/tact-demo/style.css.js +0 -3
  117. package/x/demo/ui/tact-demo/style.css.js.map +0 -1
  118. /package/x/deck/{views/deck-overlay → components/deck-bindings}/style.css.d.ts +0 -0
  119. /package/x/{demo/ui/tact-demo → deck/components/deck-overlay}/style.css.d.ts +0 -0
  120. /package/x/deck/{views → components}/deck-overlay/style.css.js +0 -0
package/README.md CHANGED
@@ -1,17 +1,4 @@
1
1
 
2
-
3
- > [!CAUTION]
4
- > ### 🚨🚨 TACT IS UNDER DEVELOPMENT!! 🚨🚨
5
- > *everything is half-broken right now.. just gimmie a minute to finish coding this, will ya?*
6
-
7
- <br></br>
8
-
9
- ---
10
-
11
- ---
12
-
13
- <br></br>
14
-
15
2
  # 🎮 @benev/tact
16
3
  > *web game input library, from keypress to couch co-op*
17
4
 
@@ -246,14 +233,16 @@ the deck ties together all the important pieces of tact into a single user exper
246
233
  ["code", "KeyA", {
247
234
  scale: 1,
248
235
  invert: false,
249
- deadzone: 0,
250
236
  timing: ["direct"],
251
237
  }]
252
238
  ```
253
239
  - defaults shown
254
240
  - `scale` is sensitivity, the value gets multiplied by this
255
241
  - `invert` will invert a value by subtracting it from 1
256
- - `deadzone` ignores values below the threshold (and remaps to preserve the range)
242
+ - `clamp` clamps the value with a lower and upper bound
243
+ - `range` restricts value to the given range, and remaps that range 0 to 1
244
+ - `bottom` zeroes the value if it's less than the given bottom value
245
+ - `top` clamps the value to an upper bound
257
246
  - `timing` lets you specify special timing considerations
258
247
  - `["direct"]` ignores timing considerations
259
248
  - `["tap", 250]` only fires for taps under 250ms
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benev/tact",
3
- "version": "0.1.0-4",
3
+ "version": "0.1.0",
4
4
  "description": "keybindings and gamepad support for web games",
5
5
  "license": "MIT",
6
6
  "author": "Chase Moskal <chasemoskal@gmail.com>",
@@ -29,21 +29,21 @@
29
29
  "count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +"
30
30
  },
31
31
  "peerDependencies": {
32
- "lit": "^3.3.1"
32
+ "lit": "^3.3.2"
33
33
  },
34
34
  "dependencies": {
35
- "@benev/math": "^0.2.0-4",
35
+ "@benev/math": "^0.2.0",
36
36
  "@e280/kv": "^0.1.0",
37
- "@e280/sly": "^0.2.0-16",
38
- "@e280/strata": "^0.2.0-13",
39
- "@e280/stz": "^0.2.5"
37
+ "@e280/sly": "^0.2.5",
38
+ "@e280/strata": "^0.2.8",
39
+ "@e280/stz": "^0.2.24"
40
40
  },
41
41
  "devDependencies": {
42
- "@e280/science": "^0.1.2",
43
- "@e280/scute": "^0.1.0",
42
+ "@e280/science": "^0.1.8",
43
+ "@e280/scute": "^0.1.5",
44
44
  "http-server": "^14.1.1",
45
45
  "npm-run-all": "^4.1.5",
46
- "typescript": "^5.9.2"
46
+ "typescript": "^5.9.3"
47
47
  },
48
48
  "repository": {
49
49
  "type": "git",
@@ -3,10 +3,10 @@ import {Code, CodeSettings, CodeState} from "../types.js"
3
3
 
4
4
  export const defaultHoldTime = 250
5
5
 
6
- export function defaultCodeState([,,settings]: Code): CodeState {
6
+ export function defaultCodeState([,,settings]: Code, now: number): CodeState {
7
7
  return {
8
8
  lastValue: 0,
9
- holdStart: 0,
9
+ holdStart: now,
10
10
  settings: defaultifyCodeSettings(settings),
11
11
  }
12
12
  }
@@ -15,6 +15,7 @@ function defaultCodeSettings(): CodeSettings {
15
15
  return {
16
16
  scale: 1,
17
17
  invert: false,
18
+ clamp: null,
18
19
  range: null,
19
20
  bottom: null,
20
21
  top: null,
@@ -13,6 +13,10 @@ export const lensAlgo = (
13
13
 
14
14
  function clippings(value) {
15
15
  const {settings} = state
16
+ if (settings.clamp) {
17
+ const [bottom, top] = settings.clamp
18
+ value = Scalar.clamp(value, bottom, top)
19
+ }
16
20
  if (settings.range) {
17
21
  const [bottom, top] = settings.range
18
22
  value = Scalar.isBetween(value, bottom, top)
@@ -24,7 +28,7 @@ export const lensAlgo = (
24
28
  )
25
29
  : 0
26
30
  }
27
- if (settings.bottom) value = Math.max(settings.bottom, value)
31
+ if (settings.bottom) value = (value < settings.bottom) ? 0 : value
28
32
  if (settings.top) value = Math.min(settings.top, value)
29
33
  return value
30
34
  },
@@ -0,0 +1,17 @@
1
+ // // TODO
2
+ //
3
+ // import {Bindings} from "../types.js"
4
+ //
5
+ // export function makeLookupProxies<B extends Bindings>(
6
+ // bindings: B,
7
+ // ) {
8
+ //
9
+ // const getModeProxy = (mode: string) => new Proxy(bindings[mode], {
10
+ // get: (_, action: string) => {},
11
+ // })
12
+ //
13
+ // return new Proxy(bindings, {
14
+ // get: (_, mode: string) => getModeProxy(mode),
15
+ // })
16
+ // }
17
+ //
@@ -1,5 +1,5 @@
1
1
 
2
- import {MapG, pub, obMap} from "@e280/stz"
2
+ import {pub, obMap, GMap} from "@e280/stz"
3
3
  import {Action} from "./action.js"
4
4
  import {SampleMap} from "./sample-map.js"
5
5
  import {lensAlgo} from "./parts/lens-algo.js"
@@ -12,7 +12,7 @@ export class Resolver<B extends Bindings> {
12
12
  #modes = new Set<keyof B>()
13
13
  #sampleMap = new SampleMap()
14
14
  #now = 0
15
- #codeStates = new MapG<number, CodeState>()
15
+ #codeStates = new GMap<string, CodeState>()
16
16
  #update = pub()
17
17
 
18
18
  constructor(public bindings: B) {
@@ -20,7 +20,7 @@ export class Resolver<B extends Bindings> {
20
20
  const action = new Action()
21
21
  this.#update.subscribe(() => {
22
22
  action.value = this.#modes.has(mode)
23
- ? this.#resolveAtom()(atom)
23
+ ? this.#resolveAtom([])(atom, mode as string)
24
24
  : 0
25
25
  })
26
26
  return action
@@ -35,24 +35,31 @@ export class Resolver<B extends Bindings> {
35
35
  return this.actions
36
36
  }
37
37
 
38
- #resolveCode(count: number, code: string, settings?: Partial<CodeSettings>) {
38
+ #resolveCode(path: string[], code: string, settings?: Partial<CodeSettings>) {
39
39
  const state = this.#codeStates.guarantee(
40
- count,
41
- () => defaultCodeState(["code", code, settings]),
40
+ path.join("/"),
41
+ () => defaultCodeState(["code", code, settings], this.#now),
42
42
  )
43
43
  const value = this.#sampleMap.get(code) ?? 0
44
44
  return lensAlgo(this.#now, state, value)
45
45
  }
46
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
- }
47
+ #resolveAtom = (path: string[]) => (atom: Atom, zone: string | number): number => {
48
+ const nextPath = [
49
+ ...path,
50
+ String(zone),
51
+ typeof atom === "string" ? atom : atom[0],
52
+ ]
53
+
54
+ const resolveAtom = this.#resolveAtom(nextPath)
55
+
56
+ if (typeof atom === "string")
57
+ return this.#resolveCode(nextPath, atom)
58
+
52
59
  else switch (atom[0]) {
53
60
  case "code": {
54
61
  const [, code, settings] = atom
55
- return this.#resolveCode(context.count++, code, settings)
62
+ return this.#resolveCode(nextPath, code, settings)
56
63
  }
57
64
  case "and": {
58
65
  const [,...atoms] = atom
@@ -66,13 +73,13 @@ export class Resolver<B extends Bindings> {
66
73
  }
67
74
  case "not": {
68
75
  const [, subject] = atom
69
- const value = resolveAtom(subject)
76
+ const value = resolveAtom(subject, "subject")
70
77
  return (value > 0) ? 0 : 1
71
78
  }
72
79
  case "cond": {
73
80
  const [, subject, guard] = atom
74
- return (resolveAtom(guard) > 0)
75
- ? resolveAtom(subject)
81
+ return (resolveAtom(guard, "guard") > 0)
82
+ ? resolveAtom(subject, "subject")
76
83
  : 0
77
84
  }
78
85
  case "mods": {
@@ -85,9 +92,9 @@ export class Resolver<B extends Bindings> {
85
92
  maybe(modifiers.alt ?? false, "AltLeft", "AltRight"),
86
93
  maybe(modifiers.meta ?? false, "MetaLeft", "MetaRight"),
87
94
  maybe(modifiers.shift ?? false, "ShiftLeft", "ShiftRight"),
88
- ]])
95
+ ]], "guard")
89
96
  return (guard > 0)
90
- ? resolveAtom(subject)
97
+ ? resolveAtom(subject, "subject")
91
98
  : 0
92
99
  }
93
100
  }
@@ -34,6 +34,7 @@ export type Atom = string | (
34
34
  export type CodeSettings = {
35
35
  scale: number
36
36
  invert: boolean
37
+ clamp: null | [number, number]
37
38
  range: null | [number, number]
38
39
  bottom: null | number
39
40
  top: null | number
@@ -24,6 +24,28 @@ export default Science.suite({
24
24
  }
25
25
  }),
26
26
 
27
+ "hold timing": test(async() => {
28
+ const {time, device, resolve} = testSetupAlpha()
29
+ {
30
+ time.frame = 1
31
+ device.setSample("KeyG", 0)
32
+ const actions = resolve()
33
+ expect(actions.basic.grenade.value).is(0)
34
+ }
35
+ {
36
+ time.frame = 2
37
+ device.setSample("KeyG", 1)
38
+ const actions = resolve()
39
+ expect(actions.basic.grenade.value).is(0)
40
+ }
41
+ {
42
+ time.frame = 15
43
+ device.setSample("KeyG", 1)
44
+ const actions = resolve()
45
+ expect(actions.basic.grenade.value).is(1)
46
+ }
47
+ }),
48
+
27
49
  "hub": Science.suite({
28
50
  "device inputs work": test(async() => {
29
51
  const {hub, time} = testSetupBravo()
@@ -59,7 +59,7 @@ export class GamepadDevice extends SamplerDevice {
59
59
  }
60
60
 
61
61
  #pollSticks(gamepad: Gamepad) {
62
- const [leftX, leftY, rightX, rightY] = gamepad.axes
62
+ const [leftX = 0, leftY = 0, rightX = 0, rightY = 0] = gamepad.axes
63
63
 
64
64
  const stickLeft = splitVector(
65
65
  circularClamp(new Vec2(leftX, leftY), this.range)
@@ -31,6 +31,10 @@ export class PointerDevice extends SamplerDevice {
31
31
  },
32
32
 
33
33
  wheel: (event: WheelEvent) => {
34
+ this.sampleMap.set("pointer.wheel.right", 0)
35
+ this.sampleMap.set("pointer.wheel.left", 0)
36
+ this.sampleMap.set("pointer.wheel.up", 0)
37
+ this.sampleMap.set("pointer.wheel.down", 0)
34
38
  for (const [code, value] of PointerDevice.wheelCodes(event))
35
39
  this.setSample(code, value)
36
40
  },
@@ -2,7 +2,7 @@
2
2
  import {Hub} from "../hub/hub.js"
3
3
  import {Port} from "../hub/port.js"
4
4
  import {Device} from "../devices/device.js"
5
- import {asBindings} from "../bindings/types.js"
5
+ import {asBindings, Code} from "../bindings/types.js"
6
6
  import {Resolver} from "../bindings/resolver.js"
7
7
  import {SampleMap} from "../bindings/sample-map.js"
8
8
  import {SamplerDevice} from "../devices/infra/sampler.js"
@@ -20,6 +20,7 @@ export function testBindings() {
20
20
  basic: {
21
21
  jump: "Space",
22
22
  shoot: "pointer.button.left",
23
+ grenade: <Code>["code", "KeyG", {timing: ["hold", 200]}]
23
24
  },
24
25
  })
25
26
  }
@@ -0,0 +1,22 @@
1
+
2
+ import {ob} from "@e280/stz"
3
+ import {Deck} from "../deck.js"
4
+ import {DropFirstParam} from "../../utils/types.js"
5
+ import {DeckOverlay} from "./deck-overlay/component.js"
6
+ import {DeckBindings} from "./deck-bindings/component.js"
7
+
8
+ const components = {DeckOverlay, DeckBindings}
9
+
10
+ export const deckComponents = (deck: Deck<any>) => (
11
+ ob(components)
12
+ .map(C => class extends C { deck = deck })
13
+ ) as DeckComponents
14
+
15
+ export type DeckComponents = typeof components
16
+
17
+ export type DeckViews = {
18
+ [P in keyof DeckComponents]: (
19
+ DropFirstParam<DeckComponents[P]["view"]>
20
+ )
21
+ }
22
+
@@ -0,0 +1,99 @@
1
+
2
+ import {html} from "lit"
3
+ import {Bytename} from "@e280/stz"
4
+ import {cssReset, view} from "@e280/sly"
5
+
6
+ import {Deck} from "../../deck.js"
7
+ import styleCss from "./style.css.js"
8
+ import {DeckComponent} from "../framework.js"
9
+ import {Port} from "../../../core/hub/port.js"
10
+ import {Profile} from "../../parts/catalog.js"
11
+ import {Atom, Bindings, Bracket} from "../../../core/bindings/types.js"
12
+
13
+ export class DeckBindings extends (
14
+ view(use => (deck: Deck<any>) => {
15
+ use.css(cssReset, styleCss)
16
+ use.attrs.strings.deck = "bindings"
17
+ const {db, hub} = deck
18
+
19
+ const catalog = db.$catalog()
20
+ const defaultProfile: Profile = {id: "default", label: "default", bindings: deck.baseBindings}
21
+ const allProfiles = [defaultProfile, ...catalog.profiles.values()]
22
+ const $selectedProfileId = use.signal<string>("default")
23
+ const profile = db.$catalog().getProfile($selectedProfileId()) ?? defaultProfile
24
+
25
+ function renderPort(port: Port<Bindings>, index: number) {
26
+ const portProfile = catalog.getProfileForPort(index) ?? defaultProfile
27
+ return html`
28
+ <div class=port>
29
+ <span>port ${index + 1}</span>
30
+ <select>
31
+ ${allProfiles.map(profile => html`
32
+ <option
33
+ data-id="${profile.id}"
34
+ ?selected="${profile.id === portProfile.id}">
35
+ ${profile.label}
36
+ </option>
37
+ `)}
38
+ </select>
39
+ </div>
40
+ `
41
+ }
42
+
43
+ function renderBindingBracket([mode, bracket]: [mode: string, bracket: Bracket]) {
44
+ return html`
45
+ <div class=bracket>
46
+ <strong class=mode>${mode}</strong>
47
+ <div>${Object.entries(bracket).map(renderBinds)}</div>
48
+ </div>
49
+ `
50
+ }
51
+
52
+ function renderBinds([action, atom]: [action: string, atom: Atom]) {
53
+ return html`
54
+ <div class=bind>
55
+ <span class=action>${action}</span>
56
+ </div>
57
+ `
58
+ }
59
+
60
+ const clickClone = async() => {
61
+ const {bindings} = profile
62
+ const label = Bytename.random(4)
63
+ const p = await db.createProfile(label, bindings)
64
+ $selectedProfileId(p.id)
65
+ }
66
+
67
+ const deleteProfile = (id: string) => async() => {
68
+ await db.deleteProfile(id)
69
+ }
70
+
71
+ const onSelected = (event: InputEvent) => {
72
+ const select = event.target as HTMLSelectElement
73
+ const id = select.value
74
+ $selectedProfileId(id)
75
+ }
76
+
77
+ return html`
78
+ <div class=portlist>
79
+ ${hub.ports.map(renderPort)}
80
+ </div>
81
+
82
+ <div class=bindable>
83
+ <select @input="${onSelected}">
84
+ ${allProfiles.map(p => html`
85
+ <option value="${p.id}" ?selected="${p.id === profile.id}">${p.label}</option>
86
+ `)}
87
+ </select>
88
+ <button @click="${clickClone}">clone</button>
89
+ <button ?disabled="${profile.id === defaultProfile.id}" @click="${deleteProfile(profile.id)}">delete</button>
90
+ <div class=bindings>
91
+ ${Object.entries(profile.bindings).map(renderBindingBracket)}
92
+ </div>
93
+ </div>
94
+ `
95
+ })
96
+ .component(DeckComponent)
97
+ .props(el => [el.deck])
98
+ ) {}
99
+
@@ -1,4 +1,6 @@
1
1
 
2
2
  import {css} from "lit"
3
- export default css``
3
+ export default css`
4
+
5
+ `
4
6
 
@@ -0,0 +1,51 @@
1
+
2
+ import {html} from "lit"
3
+ import {cssReset, view} from "@e280/sly"
4
+ import {Deck} from "../../deck.js"
5
+ import styleCss from "./style.css.js"
6
+ import {DeckComponent} from "../framework.js"
7
+ import {Device} from "../../../core/devices/device.js"
8
+
9
+ export class DeckOverlay extends (
10
+ view(use => (deck: Deck<any>) => {
11
+ use.css(cssReset, styleCss)
12
+ use.attrs.strings.deck = "overlay"
13
+ const {hub, deviceSkins, overlayVisibility: {$visible, $showLabels}} = deck
14
+
15
+ function renderDevice(device: Device) {
16
+ const skin = deviceSkins.get(device)
17
+ const style = `--color: ${skin.color};`
18
+ const next = () => hub.shimmy(device, 1)
19
+ const previous = () => hub.shimmy(device, -1)
20
+ return html`
21
+ <div class=device style="${style}">
22
+ <div class="primary row">
23
+ <button @click="${previous}">&lt;</button>
24
+ <div class=icon>${skin.icon}</div>
25
+ <button @click="${next}">&gt;</button>
26
+ </div>
27
+
28
+ ${$showLabels() ? html`
29
+ <div class="secondary row">
30
+ <div class=label>${skin.label}</div>
31
+ </div>
32
+ ` : null}
33
+ </div>
34
+ `
35
+ }
36
+
37
+ return html`
38
+ <div class=portlist ?data-active="${$visible()}">
39
+ ${hub.ports.map((port, index) => html`
40
+ <div class=port>
41
+ <header>P${index + 1}</header>
42
+ ${port.devices.array().map(renderDevice)}
43
+ </div>
44
+ `)}
45
+ </div>
46
+ `
47
+ })
48
+ .component(DeckComponent)
49
+ .props(el => [el.deck])
50
+ ) {}
51
+
@@ -108,5 +108,3 @@ export default css`
108
108
 
109
109
  `
110
110
 
111
-
112
-
@@ -0,0 +1,17 @@
1
+
2
+ import {BaseElement} from "@e280/sly"
3
+ import {Deck} from "../deck.js"
4
+
5
+ export class DeckComponent extends BaseElement {
6
+ #deck: Deck<any> | undefined
7
+
8
+ get deck() {
9
+ if (!this.#deck) throw new Error(".deck was not set on component, but is required")
10
+ return this.#deck
11
+ }
12
+
13
+ set deck(d: Deck<any>) {
14
+ this.#deck = d
15
+ }
16
+ }
17
+
package/s/deck/deck.ts CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  import {Kv} from "@e280/kv"
3
+ import {dom} from "@e280/sly"
3
4
  import {disposer, ob, range} from "@e280/stz"
4
5
 
5
6
  import {Db} from "./parts/db.js"
@@ -8,13 +9,11 @@ import {Port} from "../core/hub/port.js"
8
9
  import {Bindings} from "../core/bindings/types.js"
9
10
  import {mergeBindings} from "./parts/merge-bindings.js"
10
11
  import {MetaBindings, metaMode} from "../core/hub/types.js"
11
- import {autoGamepads} from "../core/devices/auto-gamepads.js"
12
- import {DeckOverlay} from "./views/deck-overlay/component.js"
13
12
  import {makeMetaBindings} from "../core/hub/meta-bindings.js"
14
13
  import {DeviceSkins} from "./parts/device-skins/device-skin.js"
15
14
  import {OverlayVisibility} from "./parts/overlay-visibility.js"
16
15
  import {PrimaryDevice} from "../core/devices/standard/primary.js"
17
- import { dom } from "@e280/sly"
16
+ import {deckComponents, DeckViews} from "./components/components.js"
18
17
 
19
18
  export type DeckOptions<B extends Bindings, MB extends MetaBindings = any> = {
20
19
  kv: Kv
@@ -39,22 +38,28 @@ export class Deck<B extends Bindings, MB extends MetaBindings = any> {
39
38
  ?? makeMetaBindings(),
40
39
  )
41
40
 
42
- return new this(hub, db)
41
+ return new this(options.bindings, options.metaBindings, hub, db)
43
42
  }
44
43
 
45
44
  dispose = disposer()
46
45
  deviceSkins = new DeviceSkins()
47
- overlayVisibility: OverlayVisibility
48
46
  primaryDevice = new PrimaryDevice()
47
+ overlayVisibility: OverlayVisibility
48
+
49
+ components = deckComponents(this)
49
50
 
50
- views = ob({DeckOverlay}).map(fn => fn(this))
51
- components = ob(this.views).map(v => v.component().props(_c => []))
51
+ views = (
52
+ ob(this.components as any)
53
+ .map(c => (...a: any[]) => c.view(this, ...a))
54
+ ) as DeckViews
52
55
 
53
56
  registerComponents() {
54
57
  dom.register(this.components)
55
58
  }
56
59
 
57
60
  constructor(
61
+ public baseBindings: B,
62
+ public baseMetaBindings: MB,
58
63
  public hub: Hub<B, MB>,
59
64
  public db: Db,
60
65
  ) {
package/s/deck/index.ts CHANGED
@@ -7,8 +7,8 @@ export * from "./parts/local-storage-kv.js"
7
7
  export * from "./parts/merge-bindings.js"
8
8
  export * from "./parts/overlay-visibility.js"
9
9
 
10
- export * from "./views/deck-overlay/component.js"
11
- export * from "./views/deck-overlay/style.css.js"
10
+ export * from "./components/deck-overlay/component.js"
11
+ export * from "./components/deck-overlay/style.css.js"
12
12
 
13
13
  export * from "./deck.js"
14
14
 
@@ -57,7 +57,7 @@ export class Db {
57
57
  })
58
58
  }
59
59
 
60
- async assignPortToProfile(index: number, profileId: string | null) {
60
+ async assignPortProfile(index: number, profileId: string | null) {
61
61
  return this.#mutateAndSave(catalog => {
62
62
  catalog.portProfiles[index] = profileId
63
63
  })
@@ -1,8 +1,13 @@
1
1
 
2
2
  import {dom} from "@e280/sly"
3
- import {TactDemo} from "./ui/tact-demo/component.js"
3
+ import {Game} from "./game/game.js"
4
+ import {DemoTheater} from "./ui/theater/view.js"
4
5
 
5
- dom.register({TactDemo})
6
+ const game = await Game.load()
7
+
8
+ dom.register({
9
+ DemoTheater: class extends DemoTheater { game = game },
10
+ })
6
11
 
7
12
  console.log("🎮 tact")
8
13
 
@@ -40,6 +40,7 @@ export const styles = css`
40
40
  right: 0;
41
41
 
42
42
  display: flex;
43
+ flex-direction: column;
43
44
  justify-content: center;
44
45
  align-items: center;
45
46
  }