@solidrt/core 0.0.63 → 0.0.65
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/AGENTS.md +92 -9
- package/README.md +1 -1
- package/agents/performance.md +7 -2
- package/docs/index.md +9 -0
- package/package.json +7 -5
- package/src/camera-control.ts +9 -2
- package/src/core.ts +6 -1
- package/src/double-tap.ts +2 -2
- package/src/gamepad.ts +32 -1
- package/src/gpu.ts +25 -10
- package/src/image.ts +10 -2
- package/src/index.ts +8 -4
- package/src/input-gamepad-device.ts +38 -14
- package/src/input-gamepad.ts +6 -4
- package/src/input-keyboard.ts +11 -2
- package/src/input-map.ts +12 -1
- package/src/input-pointer.ts +3 -3
- package/src/input-processors.ts +65 -25
- package/src/pan.ts +4 -3
- package/src/renderer.ts +1 -1
- package/src/runtime-modules.d.ts +68 -1
- package/src/swipe.ts +6 -6
- package/src/textures.ts +333 -0
- package/src/transform.ts +16 -7
- package/src/types.d.ts +66 -12
- package/src/velocity.ts +113 -50
- package/src/window.ts +41 -3
package/src/input-keyboard.ts
CHANGED
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
//
|
|
18
18
|
// Held state is a count behind a signal, so a source's rate is reactive
|
|
19
19
|
// (a held key wakes a control's frame loop) and a blur - the up that never
|
|
20
|
-
// arrives once focus has left - clears it.
|
|
20
|
+
// arrives once focus has left - clears it. A source also reports when its
|
|
21
|
+
// keys last changed (the key event's timeStamp; nothing after a blur,
|
|
22
|
+
// which is no release by the player). Composites clamp nothing: the
|
|
21
23
|
// map's combination does (W and D together read [1, -1] before the
|
|
22
24
|
// vec2 clamp to unit length, so diagonals walk at the same speed).
|
|
23
25
|
//
|
|
@@ -60,9 +62,11 @@ let matches = (event: KeyEvent, key: string): boolean => {
|
|
|
60
62
|
return key.length === 4 && key.startsWith("Key") && event.key.toLowerCase() === key[3]!.toLowerCase()
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
// A set of specs with a reactive "how many are held" count
|
|
65
|
+
// A set of specs with a reactive "how many are held" count, and the time
|
|
66
|
+
// of the key event that last changed which are held.
|
|
64
67
|
function held(specs: Spec[]) {
|
|
65
68
|
let down = new Set<string>()
|
|
69
|
+
let changedAt: number | null = null
|
|
66
70
|
// ownedWrite: a map forwards key events from wherever its handlers sit.
|
|
67
71
|
let [count, setCount] = createSignal(0, { ownedWrite: true })
|
|
68
72
|
return {
|
|
@@ -72,14 +76,18 @@ function held(specs: Spec[]) {
|
|
|
72
76
|
// on its most specific matches (a repeat under a newly held Shift
|
|
73
77
|
// moves "Tab" to "Shift+Tab" and back), an up frees them all.
|
|
74
78
|
let onKey = specs.filter(s => matches(event, s.key))
|
|
79
|
+
let before = [...down].join()
|
|
75
80
|
for (let s of onKey) down.delete(s.text)
|
|
76
81
|
if (isDown) for (let s of mostSpecific(onKey, s => s.mods, event)) down.add(s.text)
|
|
82
|
+
if ([...down].join() !== before) changedAt = event.timeStamp
|
|
77
83
|
setCount(down.size)
|
|
78
84
|
},
|
|
79
85
|
blur() {
|
|
86
|
+
if (down.size > 0) changedAt = null
|
|
80
87
|
down.clear()
|
|
81
88
|
setCount(0)
|
|
82
89
|
},
|
|
90
|
+
changedAt: () => changedAt,
|
|
83
91
|
has(text: string) {
|
|
84
92
|
return down.has(text)
|
|
85
93
|
},
|
|
@@ -95,6 +103,7 @@ function key(spec: string): InputSource<"button"> {
|
|
|
95
103
|
id: `${DEVICE}:key:${spec}`,
|
|
96
104
|
device: DEVICE,
|
|
97
105
|
rate: () => state.count() > 0,
|
|
106
|
+
changedAt: state.changedAt,
|
|
98
107
|
key: state.key,
|
|
99
108
|
blur: state.blur,
|
|
100
109
|
}
|
package/src/input-map.ts
CHANGED
|
@@ -76,7 +76,8 @@ export type DeviceName = "keyboard" | "gamepad" | "pointer"
|
|
|
76
76
|
* One device control as an input source. `rate` reads its current value
|
|
77
77
|
* (reactive); `deltas` subscribes a sink to the amounts a gesture
|
|
78
78
|
* produces; `key`/`blur` receive the key events a map forwards
|
|
79
|
-
* (InputMap.handlers)
|
|
79
|
+
* (InputMap.handlers); `changedAt` says when the rate last changed. A
|
|
80
|
+
* source may carry any subset.
|
|
80
81
|
*/
|
|
81
82
|
export interface InputSource<K extends ActionKind = ActionKind> {
|
|
82
83
|
readonly kind: K
|
|
@@ -90,6 +91,16 @@ export interface InputSource<K extends ActionKind = ActionKind> {
|
|
|
90
91
|
* may leave it out and never counts as the active device. */
|
|
91
92
|
readonly device?: DeviceName
|
|
92
93
|
rate?: () => ActionValue<K>
|
|
94
|
+
/**
|
|
95
|
+
* When `rate` last changed: the `timeStamp` (ms) of the input event that
|
|
96
|
+
* changed it, or null when the value was dropped and not let go by the
|
|
97
|
+
* player (a blur releases every held key with no event), and before any
|
|
98
|
+
* change. What `tap` and `doubleTap` measure with, so a button source
|
|
99
|
+
* has to carry it to sit under one: the time travels with the value,
|
|
100
|
+
* since whoever sees the change sees it after the event is gone. Not
|
|
101
|
+
* reactive.
|
|
102
|
+
*/
|
|
103
|
+
changedAt?: () => number | null
|
|
93
104
|
deltas?: (sink: DeltaSink) => () => void
|
|
94
105
|
key?: (event: KeyEvent, down: boolean) => void
|
|
95
106
|
blur?: () => void
|
package/src/input-pointer.ts
CHANGED
|
@@ -454,7 +454,7 @@ export function createPointerFeed(options: PointerFeedOptions = {}): PointerFeed
|
|
|
454
454
|
if (downs.size === 1) {
|
|
455
455
|
opening = { id: e.pointerId, x: e.clientX, y: e.clientY, mods: mods(e), alone: true }
|
|
456
456
|
finger.reset()
|
|
457
|
-
finger.push(e.clientX, e.clientY)
|
|
457
|
+
finger.push(e.clientX, e.clientY, e.timeStamp)
|
|
458
458
|
} else if (opening) opening.alone = false
|
|
459
459
|
}
|
|
460
460
|
},
|
|
@@ -465,7 +465,7 @@ export function createPointerFeed(options: PointerFeedOptions = {}): PointerFeed
|
|
|
465
465
|
transform.handlers.onPointerMove(e)
|
|
466
466
|
hold.move(e)
|
|
467
467
|
taps.move(e)
|
|
468
|
-
if (opening && opening.id === e.pointerId) finger.push(e.clientX, e.clientY)
|
|
468
|
+
if (opening && opening.id === e.pointerId && !e.predicted) finger.push(e.clientX, e.clientY, e.timeStamp)
|
|
469
469
|
},
|
|
470
470
|
onPointerUp(e) {
|
|
471
471
|
if (downs.get(e.pointerId) !== (e.button ?? PRIMARY)) return
|
|
@@ -475,7 +475,7 @@ export function createPointerFeed(options: PointerFeedOptions = {}): PointerFeed
|
|
|
475
475
|
taps.up(e)
|
|
476
476
|
if (opening && opening.id === e.pointerId) {
|
|
477
477
|
if (opening.alone && downs.size === 0) {
|
|
478
|
-
let direction: SwipeDirection | null = classifySwipe(finger.velocity(), { dx: e.clientX - opening.x, dy: e.clientY - opening.y })
|
|
478
|
+
let direction: SwipeDirection | null = classifySwipe(finger.velocity(e.timeStamp), { dx: e.clientX - opening.x, dy: e.clientY - opening.y })
|
|
479
479
|
if (direction) swipe.fire(opening.mods, direction)
|
|
480
480
|
}
|
|
481
481
|
opening = null
|
package/src/input-processors.ts
CHANGED
|
@@ -6,11 +6,15 @@
|
|
|
6
6
|
// the plain press on another action, and the consumer reads one bool.
|
|
7
7
|
//
|
|
8
8
|
// Timing: an interaction watches its source's rate under a root of its
|
|
9
|
-
// own
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
9
|
+
// own, so it works headless and needs no frame loop; the root is disposed
|
|
10
|
+
// with the creating scope (a component body) and lives for the session
|
|
11
|
+
// without one. `hold` keeps time with setTimeout. `tap` and `doubleTap`
|
|
12
|
+
// measure between the source's edges with the source's own time
|
|
13
|
+
// (`changedAt`, the timeStamp of the input event behind each edge): they
|
|
14
|
+
// see an edge at the flush, after the event is gone, so they read no
|
|
15
|
+
// clock of their own, and a source without `changedAt` cannot sit under
|
|
16
|
+
// them. They are edges, not levels: they read pressed for one task, long
|
|
17
|
+
// enough for a map's onPress to fire, then release on their own.
|
|
14
18
|
//
|
|
15
19
|
// Every processor composes the source's id (`invert(gamepad:leftStick)`,
|
|
16
20
|
// `hold(400,keyboard:key:Space)`), so a saved binding restores through
|
|
@@ -49,6 +53,13 @@ let checkButtonSource = (what: string, source: InputSource): void => {
|
|
|
49
53
|
if (source.kind !== "button") throw new Error(`${what}: "${source.label}" is a ${source.kind}, not a button`)
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
let checkTimedSource = (what: string, source: InputSource): void => {
|
|
57
|
+
checkButtonSource(what, source)
|
|
58
|
+
if (typeof source.changedAt !== "function") {
|
|
59
|
+
throw new Error(`${what}: "${source.label}" does not say when it changes (no changedAt), so its presses cannot be timed`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
let checkMs = (what: string, ms: number): void => {
|
|
53
64
|
if (!Number.isFinite(ms) || ms < 0) throw new Error(`${what}: ms must be a non-negative number, got ${String(ms)}`)
|
|
54
65
|
}
|
|
@@ -98,11 +109,13 @@ export function scale<K extends "axis" | "vec2">(source: InputSource<K>, factor:
|
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
// A button source derived from one button source's press and release
|
|
101
|
-
// edges: `edge(down,
|
|
102
|
-
// state and `later` for a timer that the disposal clears.
|
|
112
|
+
// edges: `edge(down, at)` runs on each, with `set` for the derived
|
|
113
|
+
// state and `later` for a timer that the disposal clears. `at` is the
|
|
114
|
+
// source's time for the edge (its changedAt), null when it has none: a
|
|
115
|
+
// source that carries no time, or a value that was dropped (a blur).
|
|
103
116
|
type EdgeHooks = { set: (pressed: boolean) => void; later: (ms: number, run: () => void) => void; clearLater: () => void }
|
|
104
117
|
|
|
105
|
-
function derived(source: InputSource<"button">, label: string, id: string, edge: (down: boolean,
|
|
118
|
+
function derived(source: InputSource<"button">, label: string, id: string, edge: (down: boolean, at: number | null, hooks: EdgeHooks) => void): InputSource<"button"> {
|
|
106
119
|
// ownedWrite: the edges land inside an effect and timers.
|
|
107
120
|
let [pressed, setPressed] = createSignal(false, { ownedWrite: true })
|
|
108
121
|
let timer: ReturnType<typeof setTimeout> | null = null
|
|
@@ -124,7 +137,7 @@ function derived(source: InputSource<"button">, label: string, id: string, edge:
|
|
|
124
137
|
createEffect(
|
|
125
138
|
() => source.rate!(),
|
|
126
139
|
(down, prev) => {
|
|
127
|
-
if (down !== prev) edge(down,
|
|
140
|
+
if (down !== prev) edge(down, source.changedAt?.() ?? null, hooks)
|
|
128
141
|
},
|
|
129
142
|
{ defer: true },
|
|
130
143
|
)
|
|
@@ -151,7 +164,7 @@ let pulse = (hooks: EdgeHooks): void => {
|
|
|
151
164
|
export function hold(source: InputSource<"button">, ms = HOLD_MS): InputSource<"button"> {
|
|
152
165
|
checkButtonSource("hold", source)
|
|
153
166
|
checkMs("hold", ms)
|
|
154
|
-
return derived(source, `${source.label} (hold ${ms} ms)`, `hold(${ms},${source.id})`, (down,
|
|
167
|
+
return derived(source, `${source.label} (hold ${ms} ms)`, `hold(${ms},${source.id})`, (down, _at, hooks) => {
|
|
155
168
|
if (down) hooks.later(ms, () => hooks.set(true))
|
|
156
169
|
else {
|
|
157
170
|
hooks.clearLater()
|
|
@@ -161,40 +174,55 @@ export function hold(source: InputSource<"button">, ms = HOLD_MS): InputSource<"
|
|
|
161
174
|
}
|
|
162
175
|
|
|
163
176
|
/** A press released within `ms` (default 200): pressed for one task on the
|
|
164
|
-
* release, so onPress fires once per tap and a long press is not one.
|
|
177
|
+
* release, so onPress fires once per tap and a long press is not one. A
|
|
178
|
+
* press the player did not let go (a blur dropped it) is no tap. The
|
|
179
|
+
* source has to say when it changes (`changedAt`). */
|
|
165
180
|
export function tap(source: InputSource<"button">, ms = TAP_MS): InputSource<"button"> {
|
|
166
|
-
|
|
181
|
+
checkTimedSource("tap", source)
|
|
167
182
|
checkMs("tap", ms)
|
|
168
|
-
let downAt =
|
|
169
|
-
return derived(source, `${source.label} (tap)`, `tap(${ms},${source.id})`, (down,
|
|
170
|
-
if (down) downAt =
|
|
171
|
-
else
|
|
183
|
+
let downAt: number | null = null
|
|
184
|
+
return derived(source, `${source.label} (tap)`, `tap(${ms},${source.id})`, (down, at, hooks) => {
|
|
185
|
+
if (down) downAt = at
|
|
186
|
+
else {
|
|
187
|
+
if (at !== null && downAt !== null && at - downAt <= ms) pulse(hooks)
|
|
188
|
+
downAt = null
|
|
189
|
+
}
|
|
172
190
|
})
|
|
173
191
|
}
|
|
174
192
|
|
|
175
193
|
/** Two taps (each within `tapMs`, default 200) at most `gapMs` (default
|
|
176
|
-
* 300) apart: pressed for one task on the second release.
|
|
194
|
+
* 300) apart: pressed for one task on the second release. A press the
|
|
195
|
+
* player did not let go (a blur dropped it) is no tap and ends the pair.
|
|
196
|
+
* The source has to say when it changes (`changedAt`). */
|
|
177
197
|
export function doubleTap(source: InputSource<"button">, gapMs = DOUBLE_TAP_GAP_MS, tapMs = TAP_MS): InputSource<"button"> {
|
|
178
|
-
|
|
198
|
+
checkTimedSource("doubleTap", source)
|
|
179
199
|
checkMs("doubleTap", gapMs)
|
|
180
200
|
checkMs("doubleTap", tapMs)
|
|
181
|
-
let downAt =
|
|
201
|
+
let downAt: number | null = null
|
|
182
202
|
let lastTap = -Infinity
|
|
183
|
-
return derived(source, `${source.label} (double tap)`, `doubleTap(${gapMs},${tapMs},${source.id})`, (down,
|
|
203
|
+
return derived(source, `${source.label} (double tap)`, `doubleTap(${gapMs},${tapMs},${source.id})`, (down, at, hooks) => {
|
|
184
204
|
if (down) {
|
|
185
|
-
downAt =
|
|
205
|
+
downAt = at
|
|
186
206
|
return
|
|
187
207
|
}
|
|
188
|
-
|
|
189
|
-
|
|
208
|
+
let pressedAt = downAt
|
|
209
|
+
downAt = null
|
|
210
|
+
if (at === null || pressedAt === null) {
|
|
211
|
+
lastTap = -Infinity
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
if (at - pressedAt > tapMs) return
|
|
215
|
+
if (at - lastTap <= gapMs) {
|
|
190
216
|
lastTap = -Infinity
|
|
191
217
|
pulse(hooks)
|
|
192
|
-
} else lastTap =
|
|
218
|
+
} else lastTap = at
|
|
193
219
|
})
|
|
194
220
|
}
|
|
195
221
|
|
|
196
222
|
/** Pressed while every source is: Shift+click on one action, a two-button
|
|
197
|
-
* combo on a pad. Key events reach each keyboard source among them.
|
|
223
|
+
* combo on a pad. Key events reach each keyboard source among them. It
|
|
224
|
+
* changes when the last of its sources to change did (`changedAt`, when
|
|
225
|
+
* each of them carries one). */
|
|
198
226
|
export function chord(...sources: InputSource<"button">[]): InputSource<"button"> {
|
|
199
227
|
if (sources.length < 2) throw new Error("chord: needs at least two button sources")
|
|
200
228
|
for (let s of sources) checkButtonSource("chord", s)
|
|
@@ -204,6 +232,18 @@ export function chord(...sources: InputSource<"button">[]): InputSource<"button"
|
|
|
204
232
|
id: `chord(${sources.map(s => s.id).join(",")})`,
|
|
205
233
|
device: sources[0]!.device,
|
|
206
234
|
rate: () => sources.every(s => s.rate?.() === true),
|
|
235
|
+
changedAt: sources.every(s => s.changedAt)
|
|
236
|
+
? () => {
|
|
237
|
+
let latest: number | null = null
|
|
238
|
+
for (let s of sources) {
|
|
239
|
+
let at = s.changedAt!()
|
|
240
|
+
// One source dropped (a blur): so was the chord.
|
|
241
|
+
if (at === null) return null
|
|
242
|
+
if (latest === null || at > latest) latest = at
|
|
243
|
+
}
|
|
244
|
+
return latest
|
|
245
|
+
}
|
|
246
|
+
: undefined,
|
|
207
247
|
key: (event, down) => {
|
|
208
248
|
for (let s of sources) s.key?.(event, down)
|
|
209
249
|
},
|
package/src/pan.ts
CHANGED
|
@@ -108,7 +108,7 @@ export function createPan(options: PanOptions) {
|
|
|
108
108
|
armed = null
|
|
109
109
|
origin = { x: e.parentX, y: e.parentY }
|
|
110
110
|
tracker.reset()
|
|
111
|
-
tracker.push(e.parentX, e.parentY)
|
|
111
|
+
if (!e.predicted) tracker.push(e.parentX, e.parentY, e.timeStamp)
|
|
112
112
|
options.onPanStart?.()
|
|
113
113
|
} else {
|
|
114
114
|
// The arena is resolved against us; the drag belongs elsewhere.
|
|
@@ -117,7 +117,8 @@ export function createPan(options: PanOptions) {
|
|
|
117
117
|
return
|
|
118
118
|
}
|
|
119
119
|
if (active === e.pointerId && origin) {
|
|
120
|
-
|
|
120
|
+
// The pan follows a predicted position; its velocity does not.
|
|
121
|
+
if (!e.predicted) tracker.push(e.parentX, e.parentY, e.timeStamp)
|
|
121
122
|
options.onPanMove?.(e.parentX - origin.x, e.parentY - origin.y)
|
|
122
123
|
origin = { x: e.parentX, y: e.parentY }
|
|
123
124
|
}
|
|
@@ -126,7 +127,7 @@ export function createPan(options: PanOptions) {
|
|
|
126
127
|
if (active === e.pointerId) {
|
|
127
128
|
// The up is not a sample: it sits where the last move left the
|
|
128
129
|
// pointer, and its age is what tells a rest from a flick.
|
|
129
|
-
let velocity = flingVelocity(tracker.velocity())
|
|
130
|
+
let velocity = flingVelocity(tracker.velocity(e.timeStamp))
|
|
130
131
|
reset()
|
|
131
132
|
options.onPanEnd?.(velocity)
|
|
132
133
|
} else if (armed === e.pointerId) {
|
package/src/renderer.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// createErrorBoundary comes from signals, not solid-js: solid-js marks it
|
|
2
2
|
// @internal (an app writes <Errored>, a renderer reaches for the primitive)
|
|
3
|
-
// and
|
|
3
|
+
// and leaves it out of its public types.
|
|
4
4
|
import { createErrorBoundary, createRoot, onCleanup, NotReadyError, untrack } from "@solidjs/signals"
|
|
5
5
|
import { createRenderer } from "@solidjs/universal"
|
|
6
6
|
import type { Element } from "solid-js"
|
package/src/runtime-modules.d.ts
CHANGED
|
@@ -61,7 +61,8 @@ declare module "*.srtm" {
|
|
|
61
61
|
// stream ("pointerMove"/"pointerDown"/... consumed by window.ts),
|
|
62
62
|
// "pointerFrame" (the move-batch terminator: fires after all of a frame's
|
|
63
63
|
// pointer moves have dispatched, every pointer the same age - multi-pointer
|
|
64
|
-
// recognizers measure there), and "render" (the
|
|
64
|
+
// recognizers measure there; its `timeStamp` is theirs), and "render" (the
|
|
65
|
+
// per-frame signal).
|
|
65
66
|
declare module "srt:events" {
|
|
66
67
|
export function on(event: string, callback: (data: any) => void): () => void
|
|
67
68
|
export function once(event: string, callback: (data: any) => void): () => void
|
|
@@ -235,3 +236,69 @@ declare module "srt:apps" {
|
|
|
235
236
|
declare module "srt:render" {
|
|
236
237
|
export function renderFrame(): void
|
|
237
238
|
}
|
|
239
|
+
|
|
240
|
+
// The engine verbs of an app test (lattice, dev client only), which
|
|
241
|
+
// @solidrt/test builds its surface on; tests import that, not this.
|
|
242
|
+
// The verbs work only in an engine a test host built (`srt test`) and throw
|
|
243
|
+
// anywhere else. App time there is frame / frameRate and nothing else: no
|
|
244
|
+
// frame runs unless one is asked for.
|
|
245
|
+
declare module "srt:test" {
|
|
246
|
+
/**
|
|
247
|
+
* Runs one frame: timers due by its time fire, then frame callbacks and
|
|
248
|
+
* the flush, then the draw if anything demanded one. Fulfills once the
|
|
249
|
+
* frame has run. One frame at a time: throws while the previous one is
|
|
250
|
+
* still pending.
|
|
251
|
+
*/
|
|
252
|
+
export function frame(): Promise<void>
|
|
253
|
+
/** The frames per second this engine steps at (60 unless set). */
|
|
254
|
+
export function frameRate(): number
|
|
255
|
+
/** Another frame rate, a positive integer. Throws once a frame has run. */
|
|
256
|
+
export function setFrameRate(fps: number): void
|
|
257
|
+
/** App time as of the last frame, in milliseconds; 0 before the first. */
|
|
258
|
+
export function time(): number
|
|
259
|
+
/**
|
|
260
|
+
* Fulfills once the window's size has reached the engine, at once when
|
|
261
|
+
* it already has. No frame runs for it and no time passes: a mounted
|
|
262
|
+
* window builds its first frame on that size.
|
|
263
|
+
*/
|
|
264
|
+
export function windowReady(): Promise<void>
|
|
265
|
+
/**
|
|
266
|
+
* Expands `events` (JSON text, the control API's `/input` event shape)
|
|
267
|
+
* into the steps that are sent and returns what passes before each:
|
|
268
|
+
* `[ms, frames]` per step, at least that much time and that many frames.
|
|
269
|
+
* Throws on an invalid event, before anything is sent. The steps are then
|
|
270
|
+
* sent with `inputStep`, in order.
|
|
271
|
+
*/
|
|
272
|
+
export function inputPlan(events: string): [ms: number, frames: number][]
|
|
273
|
+
/**
|
|
274
|
+
* Sends step `index` of the current plan into the input pipeline: a
|
|
275
|
+
* down, an up, a key or a wheel is dispatched on arrival, ahead of the
|
|
276
|
+
* next frame; a move with that frame.
|
|
277
|
+
*/
|
|
278
|
+
export function inputStep(index: number): void
|
|
279
|
+
/**
|
|
280
|
+
* Runs frames until the app is at rest: nothing in flight, no timer due,
|
|
281
|
+
* no frame demanded. Work in flight is waited for with no app time
|
|
282
|
+
* passing. Rejects once `maxMs` of app time have passed without rest,
|
|
283
|
+
* with what is left in the message.
|
|
284
|
+
*/
|
|
285
|
+
export function settle(maxMs: number): Promise<void>
|
|
286
|
+
/**
|
|
287
|
+
* Delivers a link the way an OS-routed one arrives (the raw string, on
|
|
288
|
+
* the `link` event, ahead of the next frame). Returns whether anything
|
|
289
|
+
* listens for links.
|
|
290
|
+
*/
|
|
291
|
+
export function link(link: string): boolean
|
|
292
|
+
/**
|
|
293
|
+
* Calls a debug command the app registered with `registerDebug`. `args`
|
|
294
|
+
* and the result are JSON text (null for no args). Throws on an unknown
|
|
295
|
+
* name, a throwing command and a result JSON cannot carry.
|
|
296
|
+
*/
|
|
297
|
+
export function debug(name: string, args: string | null): string
|
|
298
|
+
/**
|
|
299
|
+
* The pixels a node's subtree paints: RGBA8, premultiplied, rows top to
|
|
300
|
+
* bottom, at the display scale. Drawn at once from the tree as it is; no
|
|
301
|
+
* frame runs and no app time passes.
|
|
302
|
+
*/
|
|
303
|
+
export function capture(node: number): { width: number; height: number; data: Uint8Array }
|
|
304
|
+
}
|
package/src/swipe.ts
CHANGED
|
@@ -80,8 +80,8 @@ export function createSwipe(options: SwipeOptions) {
|
|
|
80
80
|
// The down's window position and the finger's window-px velocity, over
|
|
81
81
|
// every event the wrapped pan sees.
|
|
82
82
|
let down: { id: number; x: number; y: number } | null = null
|
|
83
|
-
// The lift's window position, set just before the pan's up runs.
|
|
84
|
-
let lift: { x: number; y: number } | null = null
|
|
83
|
+
// The lift's window position and time, set just before the pan's up runs.
|
|
84
|
+
let lift: { x: number; y: number; at: number } | null = null
|
|
85
85
|
let finger = createVelocityTracker()
|
|
86
86
|
let pan = createPan({
|
|
87
87
|
get axis() {
|
|
@@ -92,7 +92,7 @@ export function createSwipe(options: SwipeOptions) {
|
|
|
92
92
|
onPanEnd: velocity => {
|
|
93
93
|
let direction: SwipeDirection | null = null
|
|
94
94
|
if (down && lift) {
|
|
95
|
-
direction = classifySwipe(finger.velocity(), { dx: lift.x - down.x, dy: lift.y - down.y }, directions())
|
|
95
|
+
direction = classifySwipe(finger.velocity(lift.at), { dx: lift.x - down.x, dy: lift.y - down.y }, directions())
|
|
96
96
|
}
|
|
97
97
|
if (direction) options.onSwipe?.(direction, velocity)
|
|
98
98
|
options.onSwipeEnd?.(velocity)
|
|
@@ -104,16 +104,16 @@ export function createSwipe(options: SwipeOptions) {
|
|
|
104
104
|
if (down === null && (e.button == null || e.button === 0)) {
|
|
105
105
|
down = { id: e.pointerId, x: e.clientX, y: e.clientY }
|
|
106
106
|
finger.reset()
|
|
107
|
-
finger.push(e.clientX, e.clientY)
|
|
107
|
+
finger.push(e.clientX, e.clientY, e.timeStamp)
|
|
108
108
|
}
|
|
109
109
|
pan.handlers.onPointerDown(e)
|
|
110
110
|
},
|
|
111
111
|
onPointerMove: (e: PointerEvent) => {
|
|
112
|
-
if (down && down.id === e.pointerId) finger.push(e.clientX, e.clientY)
|
|
112
|
+
if (down && down.id === e.pointerId && !e.predicted) finger.push(e.clientX, e.clientY, e.timeStamp)
|
|
113
113
|
pan.handlers.onPointerMove(e)
|
|
114
114
|
},
|
|
115
115
|
onPointerUp: (e: PointerEvent) => {
|
|
116
|
-
if (down && down.id === e.pointerId) lift = { x: e.clientX, y: e.clientY }
|
|
116
|
+
if (down && down.id === e.pointerId) lift = { x: e.clientX, y: e.clientY, at: e.timeStamp }
|
|
117
117
|
pan.handlers.onPointerUp(e)
|
|
118
118
|
if (down && down.id === e.pointerId) {
|
|
119
119
|
down = null
|