Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/consume-once.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
**Draining a service is declared, checked at compile time, and observed at run time.**

- **`System.Consumes<"snapshot">`** marks the methods of a service type that drain it. A system that declares the service with `Game.System.service` sees those methods as a compile error naming the fix (`snapshot() drains Game/Controls: declare it with Game.System.consumeService, or read the resource its capture writes`); `Game.System.consumeService` calls them. Its other methods stay available through `service` (a settings screen can still `rebind`).
- Every input source is marked: `Keyboard.Actions`, `Keyboard.Presses`, `Gamepad.Actions`, `Touch.Touch`, `Pointer.Actions`, `Controls.Device`, `Controls.Controls`, and `InputCapture.Source`. **Breaking:** a system that called `snapshot()` through `Game.System.service` no longer compiles; read the resource `InputCapture.system` writes instead.
- Every input source is marked: `Keyboard.Actions`, `Keyboard.Presses`, `Gamepad.Actions`, `Touch.Actions`, `Pointer.Actions`, `Controls.Device`, `Controls.Controls`, and `InputCapture.Source`. **Breaking:** a system that called `snapshot()` through `Game.System.service` no longer compiles; read the resource `InputCapture.system` writes instead.
- `Game.System.service` returns `ServiceRead<D, false>` and `consumeService` returns `ServiceRead<D, true>`.
- The `service-consumed-twice` lint also reports what a debug runtime saw: a consumed service whose consumers ran at more than one placement in the session, in unnamed schedules too (`Game/Drain (schedule#2, step 0, unnamed)`). Named schedules are reported once, by the static lint.
10 changes: 10 additions & 0 deletions .changeset/pointer-wheel-lock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@typeonce/ritmo-browser": minor
---

**`Pointer` reads the wheel and the pointer lock, and `Touch.track` is `Touch.actions`, so every device is built the same way.**

- **The wheel**, read two ways for two jobs. `wheel` is how far it scrolled since the previous snapshot, in CSS pixels (lines and pages are converted), for zoom. The `wheelUp`, `wheelDown`, `wheelLeft`, and `wheelRight` buttons pulse (pressed and released in one window) once per `Pointer.wheelStep` (100 px, one notch), so a stepping action binds them (`nextWeapon: ["wheelDown"]`) and merges with a gamepad bumper in `Controls`. The page does not scroll over the element unless `preventScroll: false`. `PointerTarget` gains the `wheel` listener.
- **Mouse look:** `movement` is the raw motion since the previous snapshot (`movementX`/`movementY`), locked or not. With `lock: document`, a click on the element locks the pointer (the browser grants a lock only inside a click, so the module asks in its own listener); that click is not reported as a press. `locked` turns `false` when the player leaves with Escape, which releases what was held. `used` also counts motion and scrolling.
- Timelines record and replay `wheel`, `movement`, and `locked`. Their `press`/`release` take `Pointer.PhysicalButton`s; wheel pulses come from `wheel`.
- **Breaking: `Touch.track(target, blurHost, layout, options?)` is `Touch.actions(...)`, the `Touch.Touch<B>` type is `Touch.Actions<B>`, and `Touch.TrackOptions` is `Touch.ActionsOptions`,** matching `Keyboard.actions`, `Gamepad.actions`, and `Pointer.actions`. Arguments and behavior are unchanged.
4 changes: 2 additions & 2 deletions packages/browser/dtslint/Controls.tst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Controls, Gamepad, InputCapture, Keyboard, Pointer, Touch } from "@type
import { describe, expect, it } from "tstyche"

declare const host: Keyboard.KeyboardHost & Gamepad.GamepadHost
declare const touch: Touch.Touch<"jump">
declare const touch: Touch.Actions<"jump">
declare const canvas: Pointer.PointerTarget

const keyBindings = { jump: [" "], pause: ["Escape"] } as const
Expand Down Expand Up @@ -58,7 +58,7 @@ describe("Controls", () => {
})

it("requires lacking to name exactly the missing actions", () => {
expect(Actions.lacking(touch, ["pause"])).type.toBe<Controls.Lacking<Touch.Touch<"jump">, "pause">>()
expect(Actions.lacking(touch, ["pause"])).type.toBe<Controls.Lacking<Touch.Actions<"jump">, "pause">>()
// @ts-expect-error This device also lacks pause: bind it, or list it here
Actions.lacking(touch, [])
// @ts-expect-error Type '"paus"' is not assignable to type '"pause"'
Expand Down
22 changes: 21 additions & 1 deletion packages/browser/dtslint/Pointer.tst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe("Pointer", () => {
expect(input.used).type.toBe<boolean>()
// @ts-expect-error Property 'fier' does not exist
input.actions.fier
expect(Pointer.buttonNames).type.toBe<readonly ["primary", "middle", "secondary", "back", "forward"]>()
expect(Pointer.buttonNames).type.toBe<readonly ["primary", "middle", "secondary", "back", "forward", "wheelUp", "wheelDown", "wheelLeft", "wheelRight"]>()
})

it("binds only pointer buttons, at least one per action", () => {
Expand Down Expand Up @@ -60,4 +60,24 @@ describe("Pointer", () => {
services.mouse.snapshot()
})
})

it("reports the wheel, raw motion, and the lock, and binds wheel pulses", () => {
const mouse = Pointer.actions(canvas, blurHost, { next: ["wheelDown"], previous: ["wheelUp"] })
const input = mouse.snapshot()
expect(input.wheel).type.toBe<Pointer.Delta>()
expect(input.movement).type.toBe<Pointer.Delta>()
expect(input.locked).type.toBe<boolean>()
expect(input.buttons.wheelLeft).type.toBe<Pointer.ActionState>()
})

it("presses only physical buttons in a timeline", () => {
expect(Pointer.scripted(bindings, [{ frame: 0, wheel: { x: 0, y: 100 }, press: ["primary"] }])).type.toBe<Pointer.Scripted<typeof bindings>>()
// @ts-expect-error Type '"wheelUp"' is not assignable
Pointer.scripted(bindings, [{ frame: 0, press: ["wheelUp"] }])
})

it("takes a DOM element and the document as hosts", () => {
const element = {} as HTMLCanvasElement
expect(Pointer.actions(element, window, bindings, { lock: document })).type.toBe<Pointer.Actions<typeof bindings>>()
})
})
4 changes: 2 additions & 2 deletions packages/browser/dtslint/Touch.tst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { describe, expect, it } from "tstyche"
declare const target: Touch.TouchTarget
declare const blurHost: Touch.BlurHost

const touch = Touch.track(target, blurHost, ({ width, height }) => ({
const touch = Touch.actions(target, blurHost, ({ width, height }) => ({
stick: { region: { left: 0, top: 0, width: width / 2, height }, radius: 60 },
buttons: {
attack: { center: { x: width - 90, y: height - 90 }, radius: 55, aimRadius: 80 },
Expand All @@ -14,7 +14,7 @@ const touch = Touch.track(target, blurHost, ({ width, height }) => ({

describe("Touch", () => {
it("infers the button names from the layout", () => {
expect(touch).type.toBe<Touch.Touch<"attack" | "dash">>()
expect(touch).type.toBe<Touch.Actions<"attack" | "dash">>()
expect(touch.snapshot().actions.attack).type.toBe<Touch.ButtonState>()
expect(touch.snapshot().stick.vector).type.toBe<Touch.Vector>()
// @ts-expect-error Property 'jump' does not exist on type
Expand Down
4 changes: 2 additions & 2 deletions packages/browser/src/Controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* Declare the game's actions once with `Controls.actions([...])`, bind them
* on every device (`Keyboard.actions`, `Gamepad.actions`, `Pointer.actions`,
* `Touch.track`), and merge the devices with the declaration's `merge`.
* `Touch.actions`), and merge the devices with the declaration's `merge`.
* Merged devices
* behave like one device with all their bindings: an action is `held` while
* any device holds it, `pressed` when any device pressed it, and `released`
Expand Down Expand Up @@ -77,7 +77,7 @@ export interface DeviceSnapshot<A extends string> {
readonly used: boolean
}

/** A device `Controls` can merge: `Keyboard.actions`, `Gamepad.actions`, `Pointer.actions`, `Touch.track`, or their scripted forms. */
/** A device `Controls` can merge: `Keyboard.actions`, `Gamepad.actions`, `Pointer.actions`, `Touch.actions`, or their scripted forms. */
export interface Device<A extends string = string> extends System.Consumes<"snapshot"> {
snapshot(): DeviceSnapshot<A>
dispose(): void
Expand Down
Loading
Loading