@solidrt/core 0.0.42 → 0.0.44
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 +26 -4
- package/package.json +2 -2
- package/src/gpu.ts +29 -18
- package/src/sound.ts +50 -15
- package/src/types.d.ts +18 -2
package/AGENTS.md
CHANGED
|
@@ -24,6 +24,15 @@ hardcode desktop pixels.
|
|
|
24
24
|
(<600), `medium` (600-840), `expanded` (>=840). Drive column counts / layout
|
|
25
25
|
switches off it (see the responsive-grid example).
|
|
26
26
|
|
|
27
|
+
Exception - fixed-aspect content. For content with fixed internal geometry
|
|
28
|
+
(diagrams, slides, dashboards, games, emulators), do not branch on window size
|
|
29
|
+
at all: author everything in one design space and let `viewBox` fit it.
|
|
30
|
+
`<view flex={1} viewBox={[1280, 800]}>` uniformly scales and centers the
|
|
31
|
+
children (letterboxed), pointer events on them arrive in design coordinates,
|
|
32
|
+
and the same code runs unchanged from a desktop window to a phone. Reach for
|
|
33
|
+
`windowSizeClass` branching only when the layout genuinely reflows across form
|
|
34
|
+
factors.
|
|
35
|
+
|
|
27
36
|
`env` and `capabilities` (both exported from `@solidrt/core`) are the two
|
|
28
37
|
objects that expose this. They are plain objects with REACTIVE GETTERS, not
|
|
29
38
|
functions - read them as `capabilities.windowSizeClass`, `env.displayScale`
|
|
@@ -104,10 +113,11 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
|
|
|
104
113
|
Outlines: `drawStyle="stroke"` (or "stroke-and-fill") plus `strokeWidth`.
|
|
105
114
|
Corner radius on draw primitives: `radius` (number or [tl, tr, br, bl]).
|
|
106
115
|
|
|
107
|
-
- Registered JSX intrinsics: `window`, `view`, `text`, `rect`, `oval`, `
|
|
108
|
-
`texture`, `audio`, plus the `d-` variants `d-view`, `d-rect`,
|
|
109
|
-
`d-path`, `d-texture`, `d-text`.
|
|
110
|
-
|
|
116
|
+
- Registered JSX intrinsics: `window`, `view`, `text`, `rect`, `oval`, `line`,
|
|
117
|
+
`path`, `texture`, `audio`, plus the `d-` variants `d-view`, `d-rect`,
|
|
118
|
+
`d-oval`, `d-line`, `d-path`, `d-texture`, `d-text`. Line endpoints
|
|
119
|
+
(`x1`/`y1`/`x2`/`y2`) exist only on `d-line`; a laid-out `<line>` has no
|
|
120
|
+
endpoint props and spans its layout box corner to corner.
|
|
111
121
|
|
|
112
122
|
- Plain vs `d-` variant (the `d-` prefix means "detached" - detached from the
|
|
113
123
|
layout engine, Taffy): a plain element (e.g. `rect`) is `RectProps &
|
|
@@ -119,6 +129,13 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
|
|
|
119
129
|
directly-positioned, often-animating elements (e.g. hundreds of balls), `d-`
|
|
120
130
|
skips the per-element layout that plain elements would incur.
|
|
121
131
|
|
|
132
|
+
- Transform origin on a `d-view`: with `originX`/`originY` unset, scale/rotate
|
|
133
|
+
pivot at the view's local (0,0) - the origin its children's coordinates are
|
|
134
|
+
authored against - not at a box center (a laid-out view pivots at its own
|
|
135
|
+
box center; a d-view has no box). To scale a detached group around its
|
|
136
|
+
content's center, set the origin explicitly in pixels, e.g.
|
|
137
|
+
`originX={100} originY={50}` for content drawn in a 200x100 local space.
|
|
138
|
+
|
|
122
139
|
- Layout-affecting vs not (this matters for per-frame work). Props fall in three
|
|
123
140
|
buckets, split by where they take effect:
|
|
124
141
|
- `LayoutProps` - width/height, min/max sizes, margin, padding, `position` and
|
|
@@ -137,6 +154,11 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
|
|
|
137
154
|
`position:absolute` at `left:0,top:0`, or just let normal flow place it) and
|
|
138
155
|
then translate it with `x`/`y`.
|
|
139
156
|
|
|
157
|
+
- JSX text children collapse whitespace (ordinary JSX semantics): runs of
|
|
158
|
+
spaces become one, so space-padding a mono label collapses silently. An
|
|
159
|
+
expression container preserves it - `<d-text>{"one two"}</d-text>` - and
|
|
160
|
+
`\n` inside one produces a hard line break.
|
|
161
|
+
|
|
140
162
|
- Events: there is NO `onClick`/`onPress`. A "button" is a `<view>`/`<rect>`
|
|
141
163
|
with `onPointerDown`. Handlers: onPointerDown/Up/Move/Enter/Leave, onWheel,
|
|
142
164
|
onKeyDown/Up, onTextInput, onFocus/onBlur. Text entry: focus a node with an
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.44",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Antoine van Wel",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"colord": "^2.9.3"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@solidrt/flux-types": "0.0.
|
|
30
|
+
"@solidrt/flux-types": "0.0.44"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@solidjs/signals": "2.0.0-beta.26",
|
package/src/gpu.ts
CHANGED
|
@@ -60,6 +60,13 @@ export type CreateOptions = { manual?: boolean; label?: string }
|
|
|
60
60
|
export type SamplerOptions = { filter?: gpu.FilterMode; wrap?: gpu.WrapMode }
|
|
61
61
|
export type { FilterMode, WrapMode } from "flux:gpu"
|
|
62
62
|
|
|
63
|
+
// Pixel format option for the pixel-upload creates (createTexture,
|
|
64
|
+
// createMutableTexture), fixed for the id's lifetime like the sampler state.
|
|
65
|
+
// "rgba8" (default) or "r8" - see TextureFormat in flux:gpu for the r8
|
|
66
|
+
// contract (1 byte/pixel, sampled as `(v, 0, 0, 1)`, any width).
|
|
67
|
+
export type TextureFormatOptions = { format?: gpu.TextureFormat }
|
|
68
|
+
export type { TextureFormat } from "flux:gpu"
|
|
69
|
+
|
|
63
70
|
// The branded id types, one per id space (see flux:gpu): plain numbers at
|
|
64
71
|
// runtime, distinct types to the checker, so a cross-space slip like
|
|
65
72
|
// destroyBuffer(textureId) fails to compile. Exported so apps can annotate
|
|
@@ -166,21 +173,22 @@ export let glsl = String.raw
|
|
|
166
173
|
export { captureSnapshot, readTexture } from "flux:gpu"
|
|
167
174
|
|
|
168
175
|
/**
|
|
169
|
-
* Uploads raw
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
176
|
+
* Uploads raw pixels to an immutable GPU texture and returns its id (use it
|
|
177
|
+
* as `<texture src={id} />`). `data` must be exactly `width * height` pixels
|
|
178
|
+
* at the declared format's size (`* 4` bytes for the default "rgba8", `* 1`
|
|
179
|
+
* for "r8"); a mismatch throws. For pixels you intend to mutate and
|
|
180
|
+
* re-upload, use `createMutableTexture` instead. When called inside a
|
|
181
|
+
* reactive scope the texture is freed automatically once that owner is
|
|
182
|
+
* disposed; when called outside one (e.g. after an `await`, where the owner
|
|
183
|
+
* is no longer current) nothing is registered and you must call
|
|
184
|
+
* `destroyTexture` (from flux:gpu) yourself. Pass `{ manual: true }` to skip
|
|
185
|
+
* the auto-free and own the disposal yourself even inside a reactive scope.
|
|
178
186
|
*/
|
|
179
187
|
export function createTexture(
|
|
180
188
|
data: Uint8Array,
|
|
181
189
|
width: number,
|
|
182
190
|
height: number,
|
|
183
|
-
opts?: CreateOptions & SamplerOptions,
|
|
191
|
+
opts?: CreateOptions & SamplerOptions & TextureFormatOptions,
|
|
184
192
|
): gpu.TextureId {
|
|
185
193
|
let id = gpu.createTexture(data, width, height, opts)
|
|
186
194
|
if (!opts?.manual && getOwner()) onCleanup(() => gpu.destroyTexture(id))
|
|
@@ -189,18 +197,19 @@ export function createTexture(
|
|
|
189
197
|
|
|
190
198
|
/**
|
|
191
199
|
* Creates a GPU texture you intend to update over time: seed it with `data`,
|
|
192
|
-
* then call `uploadTexture(id, data)` (from flux:gpu) to push new pixels.
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
200
|
+
* then call `uploadTexture(id, data)` (from flux:gpu) to push new pixels.
|
|
201
|
+
* `data` must hold at least `width * height` pixels at the declared format's
|
|
202
|
+
* size (`* 4` bytes for the default "rgba8", `* 1` for "r8"; it may hold
|
|
203
|
+
* several frames). Like `createTexture`, the texture is freed automatically
|
|
204
|
+
* when the reactive owner is disposed (opt out with `{ manual: true }`);
|
|
205
|
+
* created outside a reactive scope you must call `destroyTexture` (from
|
|
206
|
+
* flux:gpu) yourself.
|
|
198
207
|
*/
|
|
199
208
|
export function createMutableTexture(
|
|
200
209
|
data: Uint8Array,
|
|
201
210
|
width: number,
|
|
202
211
|
height: number,
|
|
203
|
-
opts?: CreateOptions & SamplerOptions,
|
|
212
|
+
opts?: CreateOptions & SamplerOptions & TextureFormatOptions,
|
|
204
213
|
): gpu.TextureId {
|
|
205
214
|
let id = gpu.createMutableTexture(data, width, height, opts)
|
|
206
215
|
if (!opts?.manual && getOwner()) onCleanup(() => gpu.destroyTexture(id))
|
|
@@ -237,7 +246,9 @@ export function createMutableTexture(
|
|
|
237
246
|
* as written, so a shader carrying its own uniform names - one ported from
|
|
238
247
|
* elsewhere - runs unchanged here without dropping to compileShader /
|
|
239
248
|
* linkProgram. The built-in vertex stage still supplies `vUV`; declare
|
|
240
|
-
* `in vec2 vUV;` yourself to read it.
|
|
249
|
+
* `in vec2 vUV;` yourself to read it. One naming trap: GLSL ES reserves
|
|
250
|
+
* `packed` as a keyword, so `vec4 packed = texture(...)` fails with a syntax
|
|
251
|
+
* error that does not name the identifier - pick another name.
|
|
241
252
|
*/
|
|
242
253
|
export function createShaderTexture(
|
|
243
254
|
fragmentSrc: string,
|
package/src/sound.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// a large track from a file path on demand instead of decoding it into memory.
|
|
6
6
|
//
|
|
7
7
|
// The imperative primitive lives in the `flux:audio` module; import
|
|
8
|
-
// { play, load, stream } from "flux:audio" for non-reactive use.
|
|
8
|
+
// { play, load, loadPcm, stream } from "flux:audio" for non-reactive use.
|
|
9
9
|
|
|
10
10
|
import { createSignal, onCleanup } from "@solidjs/signals"
|
|
11
11
|
import { load, stream } from "flux:audio"
|
|
@@ -13,13 +13,19 @@ import { file } from "flux:fs"
|
|
|
13
13
|
|
|
14
14
|
type FluxFile = ReturnType<typeof file>
|
|
15
15
|
|
|
16
|
-
type
|
|
16
|
+
type Clip = ReturnType<typeof load>
|
|
17
|
+
type Playback = ReturnType<Clip["play"]>
|
|
17
18
|
|
|
18
19
|
export type SoundOptions = {
|
|
19
20
|
/** Repeat the clip until stopped. Defaults to false. */
|
|
20
21
|
loop?: boolean
|
|
21
22
|
/** Volume scale, 1.0 leaves the clip unchanged. Defaults to 1.0. */
|
|
22
23
|
gain?: number
|
|
24
|
+
/**
|
|
25
|
+
* Stereo position in [-1, 1], -1 = left, 0 = center, 1 = right (equal-power).
|
|
26
|
+
* Omitted means unspatialized.
|
|
27
|
+
*/
|
|
28
|
+
pan?: number
|
|
23
29
|
/**
|
|
24
30
|
* Let play() stack overlapping voices instead of restarting. Defaults to
|
|
25
31
|
* true: rapid triggers overlap. Set false for a single-voice sound where each
|
|
@@ -34,6 +40,8 @@ export type SoundStreamOptions = {
|
|
|
34
40
|
loop?: boolean
|
|
35
41
|
/** Volume scale, 1.0 leaves the track unchanged. Defaults to 1.0. */
|
|
36
42
|
gain?: number
|
|
43
|
+
/** Stereo position in [-1, 1] (see {@link SoundOptions.pan}). */
|
|
44
|
+
pan?: number
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
/** A decoded sound with reactive lifecycle. */
|
|
@@ -42,30 +50,45 @@ export type Sound = {
|
|
|
42
50
|
play(): void
|
|
43
51
|
/** Stop every voice started from this sound. */
|
|
44
52
|
stop(): void
|
|
53
|
+
/** Set the volume of every live voice, and of voices started later. */
|
|
54
|
+
setGain(gain: number): void
|
|
55
|
+
/** Set the stereo position of every live voice, and of voices started later. */
|
|
56
|
+
setPan(pan: number): void
|
|
45
57
|
/** True after play() until stop() (does not track natural completion). */
|
|
46
58
|
playing(): boolean
|
|
47
59
|
/** Set if loading failed. */
|
|
48
60
|
error(): Error | undefined
|
|
49
61
|
}
|
|
50
62
|
|
|
51
|
-
// Shared reactive wrapper: owns the loaded
|
|
63
|
+
// Shared reactive wrapper: owns the loaded clip, tracks live voices, and
|
|
52
64
|
// disposes both on cleanup. `loader` runs once (may throw -> error signal).
|
|
65
|
+
// Gain and pan are remembered so later voices start where setGain/setPan left
|
|
66
|
+
// the sound, not back at the initial options.
|
|
53
67
|
function reactiveSound(
|
|
54
|
-
loader: () =>
|
|
68
|
+
loader: () => Clip,
|
|
55
69
|
overlap: boolean,
|
|
56
|
-
|
|
70
|
+
initial: { loop?: boolean; gain?: number; pan?: number },
|
|
57
71
|
): Sound {
|
|
58
72
|
let [error, setError] = createSignal<Error | undefined>(undefined, { ownedWrite: true })
|
|
59
73
|
let [playing, setPlaying] = createSignal(false, { ownedWrite: true })
|
|
60
74
|
|
|
61
|
-
let
|
|
62
|
-
let voices:
|
|
75
|
+
let clip: Clip | undefined
|
|
76
|
+
let voices: Playback[] = []
|
|
77
|
+
let loop = initial.loop
|
|
78
|
+
let gain = initial.gain
|
|
79
|
+
let pan = initial.pan
|
|
63
80
|
try {
|
|
64
|
-
|
|
81
|
+
clip = loader()
|
|
65
82
|
} catch (e) {
|
|
66
83
|
setError(e instanceof Error ? e : new Error(String(e)))
|
|
67
84
|
}
|
|
68
85
|
|
|
86
|
+
// Voices that finished on their own keep a dead handle in `voices` until the
|
|
87
|
+
// next call here; ended() lets each touch point clear them out.
|
|
88
|
+
let prune = () => {
|
|
89
|
+
voices = voices.filter((v) => !v.ended())
|
|
90
|
+
}
|
|
91
|
+
|
|
69
92
|
let stopAll = () => {
|
|
70
93
|
for (let v of voices) v.stop()
|
|
71
94
|
voices = []
|
|
@@ -74,20 +97,31 @@ function reactiveSound(
|
|
|
74
97
|
|
|
75
98
|
onCleanup(() => {
|
|
76
99
|
stopAll()
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
100
|
+
if (clip) {
|
|
101
|
+
clip.unload()
|
|
102
|
+
clip = undefined
|
|
80
103
|
}
|
|
81
104
|
})
|
|
82
105
|
|
|
83
106
|
return {
|
|
84
107
|
play() {
|
|
85
|
-
if (!
|
|
86
|
-
if (
|
|
87
|
-
|
|
108
|
+
if (!clip) return
|
|
109
|
+
if (overlap) prune()
|
|
110
|
+
else stopAll()
|
|
111
|
+
voices.push(clip.play({ loop, gain, pan }))
|
|
88
112
|
setPlaying(true)
|
|
89
113
|
},
|
|
90
114
|
stop: stopAll,
|
|
115
|
+
setGain(value) {
|
|
116
|
+
gain = value
|
|
117
|
+
prune()
|
|
118
|
+
for (let v of voices) v.setGain(value)
|
|
119
|
+
},
|
|
120
|
+
setPan(value) {
|
|
121
|
+
pan = value
|
|
122
|
+
prune()
|
|
123
|
+
for (let v of voices) v.setPan(value)
|
|
124
|
+
},
|
|
91
125
|
playing,
|
|
92
126
|
error,
|
|
93
127
|
}
|
|
@@ -102,6 +136,7 @@ export function createSound(source: Uint8Array, options: SoundOptions = {}): Sou
|
|
|
102
136
|
return reactiveSound(() => load(source), options.overlap ?? true, {
|
|
103
137
|
loop: options.loop,
|
|
104
138
|
gain: options.gain,
|
|
139
|
+
pan: options.pan,
|
|
105
140
|
})
|
|
106
141
|
}
|
|
107
142
|
|
|
@@ -115,5 +150,5 @@ export function createSound(source: Uint8Array, options: SoundOptions = {}): Sou
|
|
|
115
150
|
*/
|
|
116
151
|
export function createSoundStream(source: string | FluxFile, options: SoundStreamOptions = {}): Sound {
|
|
117
152
|
let src = typeof source === "string" ? file(source) : source
|
|
118
|
-
return reactiveSound(() => stream(src), false, { loop: options.loop, gain: options.gain })
|
|
153
|
+
return reactiveSound(() => stream(src), false, { loop: options.loop, gain: options.gain, pan: options.pan })
|
|
119
154
|
}
|
package/src/types.d.ts
CHANGED
|
@@ -157,7 +157,12 @@ export type Pct = { readonly __unit: "pct"; v: number }
|
|
|
157
157
|
// One axis of the transform origin (the point rotate/scale/3D pivot around),
|
|
158
158
|
// split per axis to match the engine's x/y prop convention. A bare number is
|
|
159
159
|
// pixels; `pct(50)` is a fraction of the box, so a percentage origin tracks the
|
|
160
|
-
// layout size with no reactive wiring. Unset defaults to the axis center
|
|
160
|
+
// layout size with no reactive wiring. Unset defaults to the axis center on a
|
|
161
|
+
// laid-out view; on a d-view (no box of its own) it defaults to the view's
|
|
162
|
+
// local (0,0) - the origin its children's coordinates are authored against, so
|
|
163
|
+
// the pivot never depends on the inherited box. To pivot a d-view around its
|
|
164
|
+
// content's center, set the origin explicitly in pixels; pct()/keywords on a
|
|
165
|
+
// d-view resolve against the inherited box, which is rarely what you want.
|
|
161
166
|
type OriginX = number | Pct | "left" | "center" | "right"
|
|
162
167
|
type OriginY = number | Pct | "top" | "center" | "bottom"
|
|
163
168
|
|
|
@@ -185,7 +190,11 @@ export interface TransformProps {
|
|
|
185
190
|
originX?: OriginX
|
|
186
191
|
originY?: OriginY
|
|
187
192
|
// Group opacity in 0..1: children are composited together, then faded as a
|
|
188
|
-
// whole (CSS `opacity`). Does not affect hit testing.
|
|
193
|
+
// whole (CSS `opacity`). Does not affect hit testing. Costs a compositing
|
|
194
|
+
// layer (save_layer around the subtree) while below 1, except on a
|
|
195
|
+
// repaintBoundary view, where it is hoisted to composite time for free. To
|
|
196
|
+
// fade a single primitive, put the alpha in its `color` (rgba) instead -
|
|
197
|
+
// paint alpha costs nothing.
|
|
189
198
|
opacity?: number
|
|
190
199
|
scrollX?: number
|
|
191
200
|
scrollY?: number
|
|
@@ -458,6 +467,13 @@ export interface RectProps extends PaintProps, PointerProps {
|
|
|
458
467
|
// Strokes paint inside the box, same as `RectProps`.
|
|
459
468
|
export interface OvalProps extends PaintProps, PointerProps {}
|
|
460
469
|
|
|
470
|
+
// A line's geometry is numbers, not a path string: the segment primitive to
|
|
471
|
+
// reach for when endpoints move (each endpoint is one property write; a path
|
|
472
|
+
// animates by rebuilding its `d` string). Endpoints (x1/y1/x2/y2) exist on
|
|
473
|
+
// the detached `d-line` only. A laid-out `<line>` is practically a rule -
|
|
474
|
+
// give it a thin box (length x strokeWidth); in general it draws its layout
|
|
475
|
+
// box's top-left-to-bottom-right diagonal. For arbitrary angles and
|
|
476
|
+
// connectors use `d-line`; for polylines and curves, a path.
|
|
461
477
|
export interface LineProps extends PaintProps, PointerProps {
|
|
462
478
|
/** Dash pattern in local units: the drawn segment length. Both onLength and offLength must be set to dash; with either unset the line is solid. */
|
|
463
479
|
onLength?: number
|