@solidrt/core 0.0.50 → 0.0.51
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 +13 -1
- package/README.md +1 -1
- package/agents/painting.md +61 -0
- package/agents/performance.md +216 -0
- package/examples/README.md +2 -2
- package/examples/{sound.tsx → audio.tsx} +1 -1
- package/examples/gpu-sprites.tsx +102 -0
- package/jsx-runtime.d.ts +15 -14
- package/package.json +7 -6
- package/src/{sound.ts → audio.ts} +68 -15
- package/src/color.ts +17 -18
- package/src/core.ts +21 -2
- package/src/data.ts +99 -0
- package/src/gpu.ts +20 -2
- package/src/index.ts +2 -2
- package/src/renderer.ts +38 -24
- package/src/runtime-modules.d.ts +3 -2
- package/src/scroll.ts +1 -1
- package/src/text-input.ts +297 -60
- package/src/types.d.ts +171 -3
- package/src/window.ts +58 -7
package/src/window.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createSignal, onCleanup, onSettled, flush } from "@solidjs/signals"
|
|
2
|
-
import { requestFrame } from "flux:rendertree"
|
|
2
|
+
import { requestFrame, setPointerLock } from "flux:rendertree"
|
|
3
3
|
import { renderFrame } from "srt:render"
|
|
4
4
|
import { on, once } from "srt:events"
|
|
5
5
|
import { exit } from "srt:app"
|
|
@@ -38,9 +38,12 @@ let refreshRate = 60
|
|
|
38
38
|
* the present count, and `rate` is the current refresh rate in Hz. `tick` is paced
|
|
39
39
|
* by the runtime (one refresh period per present, slow-corrected toward the wall
|
|
40
40
|
* clock) so animations driven off it stay smooth even when swap-return times
|
|
41
|
-
* jitter
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* jitter, and it is continuous across hot reloads: every tick an instance
|
|
42
|
+
* sees is on one timebase, so dt is well-defined from the second call on.
|
|
43
|
+
* Timers freeze together with frame callbacks under the dev tools' clock
|
|
44
|
+
* control, but measure their delays against the wall clock, not this paced
|
|
45
|
+
* timeline. performance.now() is not on it either: it is real elapsed time
|
|
46
|
+
* for measuring work; Date.now() is calendar time.
|
|
44
47
|
* Returns a cleanup function; also auto-cleans within a reactive scope.
|
|
45
48
|
*/
|
|
46
49
|
export function onFrame(fn: (tick: number, frame: number, rate: number) => void) {
|
|
@@ -161,6 +164,33 @@ export function windowFocused(): boolean {
|
|
|
161
164
|
return focusedAccessor()
|
|
162
165
|
}
|
|
163
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Lock the pointer to the window (relative mouse mode) or release it. While
|
|
169
|
+
* locked the cursor is hidden and confined, clientX/clientY freeze at the
|
|
170
|
+
* lock point, and mouse motion keeps reporting through the pointer events'
|
|
171
|
+
* movementX/movementY - the mouse-look primitive. Window-level, no
|
|
172
|
+
* permission dance: one app, one window. Observe the applied state through
|
|
173
|
+
* pointerLocked(); the platform can refuse (no relative mode) and the OS
|
|
174
|
+
* may drop the lock, e.g. on focus loss.
|
|
175
|
+
*/
|
|
176
|
+
export function lockPointer(locked: boolean) {
|
|
177
|
+
if (typeof locked !== "boolean") throw new Error(`lockPointer: expected a boolean, got ${typeof locked}`)
|
|
178
|
+
setPointerLock(locked)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let pointerLockedAccessor: (() => boolean) | undefined
|
|
182
|
+
|
|
183
|
+
/** Whether the pointer is currently locked (relative mouse mode), as a reactive accessor. */
|
|
184
|
+
export function pointerLocked(): boolean {
|
|
185
|
+
if (!pointerLockedAccessor) {
|
|
186
|
+
let [locked, setLocked] = createSignal(false)
|
|
187
|
+
// Sticky event: a subscriber after the lock still observes the state.
|
|
188
|
+
on("pointerLock", ({ locked }: { locked: boolean }) => setLocked(locked))
|
|
189
|
+
pointerLockedAccessor = locked
|
|
190
|
+
}
|
|
191
|
+
return pointerLockedAccessor()
|
|
192
|
+
}
|
|
193
|
+
|
|
164
194
|
let keyboardHeightAccessor: (() => number) | undefined
|
|
165
195
|
|
|
166
196
|
/**
|
|
@@ -255,6 +285,7 @@ export function attachWindow(nodeId: number) {
|
|
|
255
285
|
let unsubEnter: () => void = null!
|
|
256
286
|
let unsubLeave: () => void = null!
|
|
257
287
|
let unsubWheel: () => void = null!
|
|
288
|
+
let unsubTransitionEnd: () => void = null!
|
|
258
289
|
let unsubKeyDown: () => void = null!
|
|
259
290
|
let unsubKeyUp: () => void = null!
|
|
260
291
|
let unsubBack: () => void = null!
|
|
@@ -263,8 +294,16 @@ export function attachWindow(nodeId: number) {
|
|
|
263
294
|
let unsubRefreshRate: () => void = null!
|
|
264
295
|
let unsubFirstResize: (() => void) | null = null
|
|
265
296
|
|
|
266
|
-
|
|
267
|
-
|
|
297
|
+
// `bootstrap` marks the synthetic first frame (see the first-resize
|
|
298
|
+
// subscription below): it flushes and paints the freshly initialized graph
|
|
299
|
+
// but does not invoke onFrame callbacks, because its timestamp is not a
|
|
300
|
+
// reading of the frame timeline - handing apps a zero tick gave every
|
|
301
|
+
// reloaded instance one enormous dt on the next real frame
|
|
302
|
+
// (okf/done/onframe-tick-reset-on-reload.md). The callbacks stay
|
|
303
|
+
// registered and run on the first real render event, before the first
|
|
304
|
+
// paint with their writes applied.
|
|
305
|
+
function runFrame(t: number, frame: number, bootstrap = false) {
|
|
306
|
+
if (!bootstrap && animationFrames.size > 0) {
|
|
268
307
|
let frames = animationFrames
|
|
269
308
|
animationFrames = new Map()
|
|
270
309
|
for (let fn of frames.values()) fn(t, frame, refreshRate)
|
|
@@ -368,6 +407,17 @@ export function attachWindow(nodeId: number) {
|
|
|
368
407
|
bubble(raw, "onWheel")
|
|
369
408
|
})
|
|
370
409
|
|
|
410
|
+
// A native transition finished (runtime-side animation; see the
|
|
411
|
+
// `transition` prop). Target-only delivery, no bubbling: the element
|
|
412
|
+
// that declared the transition is the one interested in its end.
|
|
413
|
+
unsubTransitionEnd = on("transitionEnd", (raw: { target: number; property: string }) => {
|
|
414
|
+
try {
|
|
415
|
+
getEventHandler(raw.target, "onTransitionEnd")?.({ property: raw.property })
|
|
416
|
+
} catch (err) {
|
|
417
|
+
console.error("Error in onTransitionEnd handler:", err)
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
|
|
371
421
|
// Key events dispatch along the focused node's ancestor chain, leaf->root
|
|
372
422
|
// (the pointer bubbling contract), so a container hears keys from focused
|
|
373
423
|
// descendants and the window root hears everything: <window onKeyDown> is
|
|
@@ -429,7 +479,7 @@ export function attachWindow(nodeId: number) {
|
|
|
429
479
|
// where flush() is illegal (not reentrant). Defer runFrame to a microtask
|
|
430
480
|
// so the first frame always runs after this callback returns.
|
|
431
481
|
unsubFirstResize = once("resize", () => {
|
|
432
|
-
queueMicrotask(() => runFrame(0, 0))
|
|
482
|
+
queueMicrotask(() => runFrame(0, 0, true))
|
|
433
483
|
})
|
|
434
484
|
})
|
|
435
485
|
|
|
@@ -442,6 +492,7 @@ export function attachWindow(nodeId: number) {
|
|
|
442
492
|
if (unsubEnter) unsubEnter()
|
|
443
493
|
if (unsubLeave) unsubLeave()
|
|
444
494
|
if (unsubWheel) unsubWheel()
|
|
495
|
+
if (unsubTransitionEnd) unsubTransitionEnd()
|
|
445
496
|
if (unsubKeyDown) unsubKeyDown()
|
|
446
497
|
if (unsubKeyUp) unsubKeyUp()
|
|
447
498
|
if (unsubBack) unsubBack()
|