@solidrt/core 0.0.51 → 0.0.52
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 +89 -20
- package/README.md +1 -1
- package/docs/index.md +154 -0
- package/docs/reference/detached.md +85 -0
- package/docs/reference/drawing.md +95 -0
- package/docs/reference/elements.md +56 -0
- package/docs/reference/gpu.md +204 -0
- package/docs/reference/index.md +50 -0
- package/docs/reference/input.md +58 -0
- package/docs/reference/layout.md +44 -0
- package/docs/reference/shaders.md +46 -0
- package/docs/reference/text.md +46 -0
- package/docs/reference/transforms.md +35 -0
- package/docs/reference/types.md +34 -0
- package/examples/README.md +5 -3
- package/examples/gpu-pipeline.tsx +2 -2
- package/examples/line-points.tsx +145 -0
- package/examples/parse-svg.tsx +6 -6
- package/examples/responsive-grid.tsx +1 -1
- package/examples/scroll.tsx +2 -2
- package/examples/snapshot-texture.tsx +72 -0
- package/examples/{view-viewbox.tsx → view-design-size.tsx} +33 -15
- package/package.json +6 -5
- package/src/core.ts +19 -1
- package/src/gpu.ts +68 -32
- package/src/index.ts +8 -2
- package/src/logo.tsx +92 -0
- package/src/renderer.ts +181 -29
- package/src/runtime-modules.d.ts +4 -0
- package/src/scroll.ts +50 -14
- package/src/svg.ts +1 -1
- package/src/text-input.ts +0 -1
- package/src/types.d.ts +121 -29
- package/src/window.ts +52 -7
package/AGENTS.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @solidrt/core - agent notes
|
|
2
2
|
|
|
3
|
-
Dense, self-contained facts for writing a SolidRT app.
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Dense, self-contained facts for writing a SolidRT app. The prose lives in
|
|
4
|
+
docs/ (also the website); when this conflicts with it, trust this file and
|
|
5
|
+
the types in src/types.d.ts and jsx-runtime.d.ts.
|
|
6
6
|
|
|
7
7
|
SolidRT is a custom SolidJS renderer: it paints through a Rust runtime, not the
|
|
8
8
|
DOM. There is no HTML, no CSS cascade, no `className`.
|
|
@@ -35,12 +35,19 @@ hardcode desktop pixels.
|
|
|
35
35
|
|
|
36
36
|
Exception - fixed-aspect content. For content with fixed internal geometry
|
|
37
37
|
(diagrams, slides, dashboards, games, emulators), do not branch on window size
|
|
38
|
-
at all: author everything in one design space and let `
|
|
39
|
-
`<view flex={1}
|
|
38
|
+
at all: author everything in one design space and let `designSize` fit it.
|
|
39
|
+
`<view flex={1} designSize={[1280, 800]}>` uniformly scales and centers the
|
|
40
40
|
children (letterboxed), pointer events on them arrive in design coordinates,
|
|
41
|
-
and the same code runs unchanged from a desktop window to a phone.
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
and the same code runs unchanged from a desktop window to a phone. Laid-out
|
|
42
|
+
children (flex, percentages, text wrap) resolve against the design size too,
|
|
43
|
+
so a whole panel scales into a smaller box without reflowing; the view itself
|
|
44
|
+
sizes like a replaced element whose intrinsic size is the design size. One
|
|
45
|
+
trap from flexbox, not from designSize: in a flex row a width-only design-size view
|
|
46
|
+
is stretched to the line's height under the default alignment, so give the
|
|
47
|
+
view `alignSelf="flex-start"` (or the row a non-stretch `alignItems`) to get
|
|
48
|
+
the design aspect - `aspectRatio` does not override stretch. Reach for
|
|
49
|
+
`windowSizeClass` branching only when the layout genuinely reflows across
|
|
50
|
+
form factors.
|
|
44
51
|
|
|
45
52
|
`env` and `capabilities` (both exported from `@solidrt/core`) are the two
|
|
46
53
|
objects that expose this. They are plain objects with REACTIVE GETTERS, not
|
|
@@ -73,7 +80,7 @@ you need the raw fact (e.g. `env.displayScale` for asset sizing below).
|
|
|
73
80
|
Because the drawn size is fluid and the display DPI varies, asset format is a
|
|
74
81
|
real design decision, not an afterthought:
|
|
75
82
|
|
|
76
|
-
- Prefer VECTORS (`parseSvg` draws mapped to `<d-path>` in a `
|
|
83
|
+
- Prefer VECTORS (`parseSvg` draws mapped to `<d-path>` in a `designSize` view)
|
|
77
84
|
whenever the render size is fluid or DPI varies - they stay crisp at any
|
|
78
85
|
size x `displayScale()`.
|
|
79
86
|
- RASTER (`<texture>` / `createImage`) needs source resolution >= displayed size
|
|
@@ -88,8 +95,9 @@ bun add @solidrt/core # the renderer
|
|
|
88
95
|
bun add -d @solidrt/cli # the `srt` tool (see its AGENTS.md)
|
|
89
96
|
```
|
|
90
97
|
|
|
91
|
-
|
|
92
|
-
(
|
|
98
|
+
Core primitives alone are enough to build a full app. The optional extensions
|
|
99
|
+
(each with its own AGENTS.md) build on it: `@solidrt/components` (themed
|
|
100
|
+
widgets), `@solidrt/2d` (2D graphics and games), `@solidrt/3d` (scene graph).
|
|
93
101
|
|
|
94
102
|
tsconfig.json - the two load-bearing lines are jsx + jsxImportSource:
|
|
95
103
|
|
|
@@ -103,13 +111,23 @@ tsconfig.json - the two load-bearing lines are jsx + jsxImportSource:
|
|
|
103
111
|
```
|
|
104
112
|
|
|
105
113
|
Peer deps @solidjs/signals and @solidjs/universal must match (currently
|
|
106
|
-
2.0.0-rc.
|
|
114
|
+
2.0.0-rc.1); bun resolves them from peerDependencies.
|
|
107
115
|
|
|
108
116
|
## Element model (the parts that are easy to get wrong)
|
|
109
117
|
|
|
110
118
|
- `render(() => <App />)`. The returned root MUST be a `<window>` or it throws.
|
|
111
119
|
Call render once, at the top level.
|
|
112
120
|
|
|
121
|
+
- Errors never halt the app. One thrown while computing an element's props or
|
|
122
|
+
a child expression is contained at that element: it keeps its last good
|
|
123
|
+
value, one `Contained error` log line names the node and the .tsx line,
|
|
124
|
+
and it recovers when the expression computes again. Anything unclaimed
|
|
125
|
+
beyond that (a throwing `createEffect`, an error while mounting) reaches
|
|
126
|
+
render()'s root boundary, which replaces the app's window with an error
|
|
127
|
+
window (message, stack, a Reset button that retries the failed
|
|
128
|
+
computations) and logs `Uncaught error`. `<Errored>` gives a subtree its
|
|
129
|
+
own in-place fallback.
|
|
130
|
+
|
|
113
131
|
- Two kinds of element:
|
|
114
132
|
- Containers - `<window>`, `<view>`. Do layout + transform + pointer events.
|
|
115
133
|
THEY DO NOT PAINT. A `<view>` has no background/fill prop.
|
|
@@ -122,11 +140,13 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
|
|
|
122
140
|
Outlines: `drawStyle="stroke"` (or "stroke-and-fill") plus `strokeWidth`.
|
|
123
141
|
Corner radius on draw primitives: `radius` (number or [tl, tr, br, bl]).
|
|
124
142
|
|
|
125
|
-
- Registered JSX intrinsics: `window`, `view`, `text`, `
|
|
126
|
-
`path`, `texture`, `audio`, plus the `d-` variants `d-view`, `d-rect`,
|
|
143
|
+
- Registered JSX intrinsics: `window`, `view`, `text`, `span`, `rect`, `oval`,
|
|
144
|
+
`line`, `path`, `texture`, `audio`, plus the `d-` variants `d-view`, `d-rect`,
|
|
127
145
|
`d-oval`, `d-line`, `d-path`, `d-texture`, `d-text`. Line endpoints
|
|
128
146
|
(`x1`/`y1`/`x2`/`y2`) exist only on `d-line`; a laid-out `<line>` has no
|
|
129
|
-
endpoint props and spans its layout box corner to corner.
|
|
147
|
+
endpoint props and spans its layout box corner to corner. `points` (a flat
|
|
148
|
+
`[x0, y0, x1, y1, ...]` array, plus `closed`) turns either form into a
|
|
149
|
+
polyline and wins over the endpoints while set.
|
|
130
150
|
|
|
131
151
|
- Plain vs `d-` variant (the `d-` prefix means "detached" - detached from the
|
|
132
152
|
layout engine, Taffy): a plain element (e.g. `rect`) is `RectProps &
|
|
@@ -165,6 +185,10 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
|
|
|
165
185
|
`position:absolute` at `left:0,top:0`, or just let normal flow place it) and
|
|
166
186
|
then translate it with `x`/`y`.
|
|
167
187
|
|
|
188
|
+
- Text `lineHeight` is a MULTIPLIER of fontSize (1.3-1.6 is typical), not
|
|
189
|
+
pixels. A CSS-reflex value like 22 makes each line box 22x the font size:
|
|
190
|
+
the text becomes blank space and the parent balloons.
|
|
191
|
+
|
|
168
192
|
- JSX text children collapse whitespace (ordinary JSX semantics): runs of
|
|
169
193
|
spaces become one, so space-padding a mono label collapses silently. An
|
|
170
194
|
expression container preserves it - `<d-text>{"one two"}</d-text>` - and
|
|
@@ -218,9 +242,55 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
|
|
|
218
242
|
TRACKED compute that reads signals and returns a value, then an UNTRACKED
|
|
219
243
|
effect that receives it - `createEffect(() => count(), (c) => ...)`. The 1.x
|
|
220
244
|
single-callback form `createEffect(() => { ...count()... })` does NOT track
|
|
221
|
-
here.
|
|
222
|
-
|
|
223
|
-
|
|
245
|
+
here.
|
|
246
|
+
Reading a signal/prop/store at the top level of a component body (not
|
|
247
|
+
inside JSX, a `createMemo`, or an effect's compute phase) reads it
|
|
248
|
+
untracked: it silently freezes at the initial value.
|
|
249
|
+
Writing a signal or store from inside an owned scope (a component body, a
|
|
250
|
+
`createMemo`, an effect's compute phase) throws
|
|
251
|
+
`REACTIVE_WRITE_IN_OWNED_SCOPE` in dev; a loader called in the component
|
|
252
|
+
body that sets state is the classic React / Solid 1.x reflex that hits
|
|
253
|
+
this. Move the write into an event handler, an effect's apply phase,
|
|
254
|
+
`onSettled`, or an `untrack` block; opt in narrowly with
|
|
255
|
+
`createSignal(v, { ownedWrite: true })` for a signal that genuinely is
|
|
256
|
+
internal state.
|
|
257
|
+
Signal writes flush on a microtask: a handler that sets a signal and
|
|
258
|
+
immediately reads it back gets the OLD value. Read it in an effect, or
|
|
259
|
+
call `flush()` (from @solidjs/signals) to force it through.
|
|
260
|
+
|
|
261
|
+
- An element-valued prop (children, a content/icon slot) compiles to a getter
|
|
262
|
+
that builds a fresh native subtree on EVERY read, and a subtree that is
|
|
263
|
+
never inserted is never freed - native nodes are not garbage collected, so
|
|
264
|
+
what is only wasted work in DOM Solid is a permanent memory leak here. Read
|
|
265
|
+
such props exactly once, at the place they are mounted. To inspect
|
|
266
|
+
children (a typeof probe, counting), resolve them first with the
|
|
267
|
+
`children()` helper (re-exported from @solidrt/core) and probe the resolved
|
|
268
|
+
memo - never `typeof props.children` on the raw prop.
|
|
269
|
+
|
|
270
|
+
- Animation is target-shaped first: declare `transition` on the element and
|
|
271
|
+
write targets, and the runtime animates natively with no per-frame JS.
|
|
272
|
+
Reach for per-frame work only for genuinely procedural motion:
|
|
273
|
+
`onFrame((tick, frame) => {})` is the native hook (runtime-paced, returns a
|
|
274
|
+
cleanup, auto-cleaned inside a reactive scope); `requestAnimationFrame`
|
|
275
|
+
exists as a web-standard one-shot but is not the preferred driver. A JS
|
|
276
|
+
tween loop or an animation library pushing interpolated values through
|
|
277
|
+
signals is the single most expensive mistake available here - read
|
|
278
|
+
agents/performance.md before writing either.
|
|
279
|
+
Window state: onResize, onLayout, onWindowFocus, onWindowBlur exist, but
|
|
280
|
+
prefer the reactive reads (`env`/`capabilities` above, or the accessors
|
|
281
|
+
`windowSize()`, `safeArea()`, `displayScale()`, `windowFocused()`,
|
|
282
|
+
`keyboardHeight()`, `pointerLocked()`) for reading layout and window
|
|
283
|
+
state. For mouse look, `lockPointer(true)` enters relative mouse mode
|
|
284
|
+
(cursor hidden and confined, positions freeze) and pointer events keep
|
|
285
|
+
reporting motion through `movementX`/`movementY`.
|
|
286
|
+
|
|
287
|
+
- `createPortal` cannot mount during the app's initial render (it throws
|
|
288
|
+
"no mount target"): gate portal content behind a signal that starts false
|
|
289
|
+
and open it after startup - overlay content is opened, not born open.
|
|
290
|
+
`createScroll` containers need an explicit main-axis size (a height, or
|
|
291
|
+
flex inside a sized parent); with neither they resolve to 0 and the
|
|
292
|
+
content silently vanishes (`maxHeight` alone does not size it). The
|
|
293
|
+
runtime warns when this happens.
|
|
224
294
|
|
|
225
295
|
- Device/GPU access via subpath imports: @solidrt/core/camera, /microphone,
|
|
226
296
|
/speech, /gpu. Image flow: `decodeImage(bytes)` ->
|
|
@@ -253,5 +323,4 @@ render(() => <App />)
|
|
|
253
323
|
Note the two `<d-rect>` underlays: a `<view>`/`<window>` does not paint, so a
|
|
254
324
|
background is a draw-primitive child placed behind the content.
|
|
255
325
|
|
|
256
|
-
To run and verify (incl. headless), see @solidrt/cli (its AGENTS.md).
|
|
257
|
-
higher-level components, see @solidrt/components (its AGENTS.md).
|
|
326
|
+
To run and verify (incl. headless), see @solidrt/cli (its AGENTS.md).
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ Optionally, create a `tsconfig.json` to enable type recognition for SolidRT elem
|
|
|
53
53
|
|
|
54
54
|
## API
|
|
55
55
|
|
|
56
|
-
See [docs/
|
|
56
|
+
See [docs/](https://github.com/wellawaretech/solidrt/blob/main/packages/core/docs/index.md) (shipped in the package) for the full API reference.
|
|
57
57
|
|
|
58
58
|
## License
|
|
59
59
|
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Core
|
|
2
|
+
|
|
3
|
+
`@solidrt/core` is the spine of SolidRT. It links SolidJS reactivity
|
|
4
|
+
to the native rendertree: an element vocabulary, layout, input, frames, and
|
|
5
|
+
the environment model for adapting to the device you are running on.
|
|
6
|
+
|
|
7
|
+
If you only learn one layer, learn this one. Extensions and tools are built
|
|
8
|
+
on it and are replaceable; Core is the part that changes least (SolidRT is
|
|
9
|
+
in alpha, so "least" is not "never").
|
|
10
|
+
|
|
11
|
+
## Elements
|
|
12
|
+
|
|
13
|
+
There is no DOM. JSX elements are native rendertree nodes, and the
|
|
14
|
+
vocabulary is deliberately small:
|
|
15
|
+
|
|
16
|
+
| Element | Purpose |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `window` | The app window. One per app, the root of the tree. |
|
|
19
|
+
| `view` | Layout and input. Boxes, flex containers, hit targets. |
|
|
20
|
+
| `text`, `span` | A shaped paragraph, and a styled run inside it. |
|
|
21
|
+
| `rect`, `oval`, `line`, `path` | Painted shapes. |
|
|
22
|
+
| `texture` | A GPU texture: a decoded image, a camera frame, a shader target. |
|
|
23
|
+
|
|
24
|
+
Layout and paint are separate jobs, which is the one place the vocabulary
|
|
25
|
+
diverges sharply from HTML. A `view` never paints, so there is no
|
|
26
|
+
`backgroundColor`; you put a `rect` behind the content, and by default a
|
|
27
|
+
shape fills the layout box it sits in:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
<view padding={16} alignItems="center">
|
|
31
|
+
<rect color="#1b2440" radius={12} />
|
|
32
|
+
<text color="white">Boxed</text>
|
|
33
|
+
</view>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Each painting element also has a detached twin: `d-view`, `d-rect`,
|
|
37
|
+
`d-path`, `d-text`, and so on. Detached elements are positioned by their
|
|
38
|
+
parent's coordinate system rather than by layout, so changing one costs no
|
|
39
|
+
reflow. Use them for anything that moves at animation frequency.
|
|
40
|
+
|
|
41
|
+
## Reactivity
|
|
42
|
+
|
|
43
|
+
Props are reactive values, not snapshots. A signal read inside JSX
|
|
44
|
+
subscribes exactly one native property to exactly one signal, and an update
|
|
45
|
+
writes that property directly. Nothing re-renders, and there is no virtual
|
|
46
|
+
DOM to diff:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
let [x, setX] = createSignal(0)
|
|
50
|
+
|
|
51
|
+
<d-rect x={x()} w={40} h={40} color="tomato" />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The reactive and control-flow vocabulary comes from SolidJS 2.0 and is
|
|
55
|
+
re-exported from `@solidrt/core`, so an app imports everything from one
|
|
56
|
+
place: `createSignal`, `createMemo`, `createEffect`, `createStore`,
|
|
57
|
+
`onCleanup`, and the control-flow components `For`, `Show`, `Switch`,
|
|
58
|
+
`Match`, `Loading`, `Errored`.
|
|
59
|
+
|
|
60
|
+
Because props are values rather than accessors, the usual Solid rules apply:
|
|
61
|
+
do not destructure props, and read reactive values inside the expression
|
|
62
|
+
that uses them.
|
|
63
|
+
|
|
64
|
+
An error thrown while computing an element's props or a child expression is
|
|
65
|
+
contained at that element: it keeps its last good value, the error is logged
|
|
66
|
+
once with the node and source line, and the element recovers when the
|
|
67
|
+
expression computes again. An error nothing claims (a throwing effect, an
|
|
68
|
+
error during mount) replaces the app's window with an error window showing
|
|
69
|
+
the message and stack, with a Reset that retries; the reactive system keeps
|
|
70
|
+
running either way. `<Errored>` gives a subtree its own fallback.
|
|
71
|
+
|
|
72
|
+
## Layout
|
|
73
|
+
|
|
74
|
+
Layout is flexbox, plus a line-based subset of CSS grid, over the whole
|
|
75
|
+
element tree. Prop names match CSS: `flexDirection`, `alignItems`,
|
|
76
|
+
`justifyContent`, `gap`, `padding`, `width`, `position`, `top`.
|
|
77
|
+
|
|
78
|
+
Units are simpler than CSS. A bare number is pixels; a percentage is
|
|
79
|
+
`pct(50)`, a branded value rather than a parsed string:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<view flexDirection="row" gap={8} padding={16}>
|
|
83
|
+
<view width={pct(50)} />
|
|
84
|
+
</view>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`position` has `relative` and `absolute` only, and an absolute element does
|
|
88
|
+
not itself become a containing block: it resolves against the nearest
|
|
89
|
+
ancestor with `position="relative"`.
|
|
90
|
+
|
|
91
|
+
## Input
|
|
92
|
+
|
|
93
|
+
Pointer, wheel, and key events are props on any element:
|
|
94
|
+
`onPointerDown`, `onPointerMove`, `onPointerUp`, `onPointerEnter`,
|
|
95
|
+
`onPointerLeave`, `onWheel`, `onKeyDown`, `onKeyUp`. Events travel from the
|
|
96
|
+
hit leaf up to the root, and `stopPropagation()` ends the walk. Key events
|
|
97
|
+
bubble the same way, starting at the focused node - or at the window root
|
|
98
|
+
when nothing is focused, so `onKeyDown` on the window is where app-global
|
|
99
|
+
shortcuts live.
|
|
100
|
+
|
|
101
|
+
Text entry goes to the focused node's `onTextInput`. Focusing a field never
|
|
102
|
+
raises the on-screen keyboard by itself - a tap on the field (or an explicit
|
|
103
|
+
`startTextInput()`) does, and never while a physical keyboard is attached.
|
|
104
|
+
|
|
105
|
+
Coordinates are logical points, so a handler reads the same numbers on a
|
|
106
|
+
high-density phone screen as on a desktop monitor.
|
|
107
|
+
|
|
108
|
+
## Frames and animation
|
|
109
|
+
|
|
110
|
+
`onFrame(callback)` runs before every painted frame with the frame time in
|
|
111
|
+
ms, the frame count, and the display refresh rate; it returns a disposer and
|
|
112
|
+
cleans itself up with the reactive scope it was called in. Rendering is
|
|
113
|
+
demand-driven: the runtime does not spin a render loop when nothing changed,
|
|
114
|
+
so an idle app is genuinely idle.
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
let [t, setT] = createSignal(0)
|
|
118
|
+
onFrame((tick) => setT(tick))
|
|
119
|
+
|
|
120
|
+
<d-view rotate={t() / 1000}>...</d-view>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Environment and devices
|
|
124
|
+
|
|
125
|
+
`env` and `capabilities` describe where the app is running: `env` is what
|
|
126
|
+
is observed (system theme, text scale, orientation, visibility, connected
|
|
127
|
+
input devices), `capabilities` what follows from it for behavior (hover,
|
|
128
|
+
touch, precise pointer, keyboard navigation, window size class). Which
|
|
129
|
+
runtime features exist on this build is `Flux.capabilities`, by name, never
|
|
130
|
+
by guessing from the OS.
|
|
131
|
+
|
|
132
|
+
Window-shaped values are reactive too: `windowSize()`, `safeArea()`,
|
|
133
|
+
`displayScale()`, `keyboardHeight()`, `windowFocused()`.
|
|
134
|
+
|
|
135
|
+
Device access follows the same reactive shape, as `create*` primitives
|
|
136
|
+
imported from Core subpaths rather than an imperative API:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import { createCamera } from "@solidrt/core/camera"
|
|
140
|
+
|
|
141
|
+
let camera = createCamera()
|
|
142
|
+
|
|
143
|
+
<texture src={camera.texture()} fit="cover" />
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The same pattern covers `@solidrt/core/microphone`, `/sound`,
|
|
147
|
+
`/speech-recognition`, `/text-input`, `/image`, `/color`, and `/gpu`.
|
|
148
|
+
|
|
149
|
+
## Reference
|
|
150
|
+
|
|
151
|
+
The [reference](/core/reference/) covers the API by subject: the element
|
|
152
|
+
vocabulary, drawing, text, detached elements, layout, transforms, input,
|
|
153
|
+
shaders, the GPU module, and the shared types. It shows the shipped declarations themselves,
|
|
154
|
+
so it says exactly what your editor says.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Detached elements
|
|
2
|
+
|
|
3
|
+
Every painting element has a detached twin: `d-view`, `d-rect`, `d-oval`,
|
|
4
|
+
`d-line`, `d-path`, `d-text`, `d-texture`. A detached element has no layout
|
|
5
|
+
box. It is not part of the layout at all, and instead owns its geometry in
|
|
6
|
+
paint-space pixels, positioned in the coordinate system of its parent.
|
|
7
|
+
|
|
8
|
+
That is the whole idea, and the reason to reach for one:
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
let [x, setX] = createSignal(0)
|
|
12
|
+
onFrame((now) => setX(Math.sin(now / 500) * 100))
|
|
13
|
+
|
|
14
|
+
<view width={pct(100)} height={200}>
|
|
15
|
+
<d-rect x={x()} y={20} w={40} h={40} color="tomato" />
|
|
16
|
+
</view>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Moving that rect writes one number to one native property. Nothing reflows,
|
|
20
|
+
because there is nothing in the layout to reflow. The same content as a
|
|
21
|
+
laid-out `<rect>` with `left={x()}` would put the layout engine on the path of
|
|
22
|
+
every frame.
|
|
23
|
+
|
|
24
|
+
**Reach for the detached form first for anything that moves at animation
|
|
25
|
+
frequency**, and for content authored in fixed design units - a chart, a
|
|
26
|
+
diagram, an svg drawing, a particle field. Reach for the layout form when you
|
|
27
|
+
want the element to participate in a flex or grid arrangement, which is most
|
|
28
|
+
static UI.
|
|
29
|
+
|
|
30
|
+
## What changes
|
|
31
|
+
|
|
32
|
+
A detached element composes exactly the same paint, text and pointer props as
|
|
33
|
+
its layout twin. One thing is swapped: the [layout](/core/reference/layout/)
|
|
34
|
+
props are replaced by geometry props.
|
|
35
|
+
|
|
36
|
+
| | Layout form | Detached form |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Position | flex or grid placement, `top`/`left`, margins | `x`, `y` in the parent's coordinates |
|
|
39
|
+
| Size | `width`/`height` and the box it is given | `w`, `h`, defaulting to the inherited box |
|
|
40
|
+
| Costs a reflow | yes | no |
|
|
41
|
+
|
|
42
|
+
`d-view` is the container case: it composes `ViewOwnProps` directly (see
|
|
43
|
+
[elements](/core/reference/elements/)), so it still transforms, clips and
|
|
44
|
+
takes input, but a layout prop on it is dropped with a one-time warning
|
|
45
|
+
rather than silently ignored.
|
|
46
|
+
|
|
47
|
+
`<span>` has no detached form, since an inline run never has a box of its own
|
|
48
|
+
to detach from.
|
|
49
|
+
|
|
50
|
+
## Geometry
|
|
51
|
+
|
|
52
|
+
Detached geometry is paint-space pixels and never affects layout.
|
|
53
|
+
|
|
54
|
+
{{ decl packages/core/src/types.d.ts PositionProps }}
|
|
55
|
+
|
|
56
|
+
Most primitives add a size, defaulting to the box they inherit from their
|
|
57
|
+
ancestor, so a `d-rect` with only `x`/`y` still has something to draw:
|
|
58
|
+
|
|
59
|
+
{{ decl packages/core/src/types.d.ts GeometryProps }}
|
|
60
|
+
|
|
61
|
+
`d-oval` measures its box rather than a radius:
|
|
62
|
+
|
|
63
|
+
{{ decl packages/core/src/types.d.ts OvalGeometryProps }}
|
|
64
|
+
|
|
65
|
+
For `d-text`, width is the shaping width and height is reported bounds only,
|
|
66
|
+
since a paragraph's height always falls out of the text itself:
|
|
67
|
+
|
|
68
|
+
{{ decl packages/core/src/types.d.ts TextGeometryProps }}
|
|
69
|
+
|
|
70
|
+
`d-path` takes only a position, since its size is whatever its `d` string
|
|
71
|
+
draws. `d-line` has no width and height either: its geometry is its two
|
|
72
|
+
endpoints (or, as a polyline, its `points`), which is what makes it the
|
|
73
|
+
primitive to reach for when the geometry moves. Both report their bounds
|
|
74
|
+
(getBoundingBox, the tree, a capture) as that geometry plus the stroke, not
|
|
75
|
+
the inherited box:
|
|
76
|
+
|
|
77
|
+
{{ decl packages/core/src/types.d.ts LineGeometryProps }}
|
|
78
|
+
|
|
79
|
+
## The other x and y
|
|
80
|
+
|
|
81
|
+
`x` and `y` also appear on [transform](/core/reference/transforms/) props,
|
|
82
|
+
where they mean a post-layout subtree translation and exist on laid-out views
|
|
83
|
+
too. The two never collide on one element: on a `d-*` primitive `x`/`y` is
|
|
84
|
+
where the geometry is drawn, and on a `view` or `d-view` it is a translation
|
|
85
|
+
applied to the whole subtree. Both are cheap; neither triggers layout.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Drawing
|
|
2
|
+
|
|
3
|
+
The primitives that put pixels on the screen: `rect`, `oval`, `line`, `path`
|
|
4
|
+
and `texture`. They share one paint vocabulary and differ only in the geometry
|
|
5
|
+
they draw.
|
|
6
|
+
|
|
7
|
+
In their layout form they take no geometry props at all. A shape derives its
|
|
8
|
+
geometry from the layout box it sits in, which is why a `rect` with no props
|
|
9
|
+
fills its parent. To place one freely, use the
|
|
10
|
+
[detached form](/core/reference/detached/) instead.
|
|
11
|
+
|
|
12
|
+
## Paint
|
|
13
|
+
|
|
14
|
+
Fill, stroke and blending, shared by every drawing primitive:
|
|
15
|
+
|
|
16
|
+
{{ decl packages/core/src/types.d.ts PaintProps }}
|
|
17
|
+
|
|
18
|
+
`color` takes a CSS color string or a gradient from `createLinearGradient` /
|
|
19
|
+
`createRadialGradient`. `drawStyle` picks fill, stroke, or both, and the
|
|
20
|
+
stroke props apply to the stroked part.
|
|
21
|
+
|
|
22
|
+
Where the stroke sits relative to the geometry differs by primitive, and it is
|
|
23
|
+
deliberate: a stroked `rect` or `oval` paints *inside* its box like a CSS
|
|
24
|
+
border, so nothing bleeds past the box for a clip to cut, while `line` and
|
|
25
|
+
`path` strokes stay centered on their geometry, where the geometry is the
|
|
26
|
+
stroke rather than a box.
|
|
27
|
+
|
|
28
|
+
## Dashing
|
|
29
|
+
|
|
30
|
+
A stroke's dash pattern, on `line` and `path`:
|
|
31
|
+
|
|
32
|
+
{{ decl packages/core/src/types.d.ts DashProps }}
|
|
33
|
+
|
|
34
|
+
The pattern is walked along the geometry itself: through a polyline's
|
|
35
|
+
vertices and along a path's curves, restarting at each subpath of a path.
|
|
36
|
+
`dashOffset` slides it - write it every frame for marching ants, or
|
|
37
|
+
transition it for a one-shot slide. A dashed stroke keeps its caps on every
|
|
38
|
+
dash, and a stroke-and-fill path dashes only the stroke.
|
|
39
|
+
|
|
40
|
+
`pathLength` declares what the geometry's length counts as, so the pattern
|
|
41
|
+
can be written in fractions of it: with `pathLength={1}`, `onLength={0.77}
|
|
42
|
+
offLength={1}` draws the first 77%, and transitioning `onLength` from 0 to
|
|
43
|
+
1 draws the geometry on (the SVG line-drawing trick, without having to know
|
|
44
|
+
the length).
|
|
45
|
+
|
|
46
|
+
## rect
|
|
47
|
+
|
|
48
|
+
{{ decl packages/core/src/types.d.ts RectProps }}
|
|
49
|
+
|
|
50
|
+
## oval
|
|
51
|
+
|
|
52
|
+
{{ decl packages/core/src/types.d.ts OvalProps }}
|
|
53
|
+
|
|
54
|
+
## line
|
|
55
|
+
|
|
56
|
+
{{ decl packages/core/src/types.d.ts LineProps }}
|
|
57
|
+
|
|
58
|
+
A laid-out `<line>` without `points` draws its box's top-left-to-bottom-right
|
|
59
|
+
diagonal, so in practice it is a rule: give it a thin box. Endpoints are a
|
|
60
|
+
detached-only concept, so arbitrary angles and connectors want `d-line`.
|
|
61
|
+
`points` makes either form a polyline - a flat `[x0, y0, x1, y1, ...]` array
|
|
62
|
+
(or a `Float32Array`), optionally `closed` - the numeric middle ground between
|
|
63
|
+
a segment and a path: animate it by writing a new array, nothing is parsed.
|
|
64
|
+
Curves want a path.
|
|
65
|
+
|
|
66
|
+
## path
|
|
67
|
+
|
|
68
|
+
{{ decl packages/core/src/types.d.ts PathProps }}
|
|
69
|
+
|
|
70
|
+
`d` is an SVG path string. Reach for `line` instead when the geometry is
|
|
71
|
+
numbers that animate (endpoints, or a polyline's `points`): a path animates
|
|
72
|
+
by rebuilding its `d` string, where a line moves one number or one array.
|
|
73
|
+
A path dashes like a line (see Dashing above), the pattern restarting at
|
|
74
|
+
each subpath; the dash props are paint-only writes, the `d` is not re-parsed.
|
|
75
|
+
|
|
76
|
+
## texture
|
|
77
|
+
|
|
78
|
+
A GPU texture: a decoded image, a camera frame, a video frame, or a shader
|
|
79
|
+
render target. `src` is a texture id, never a URL, which keeps one currency
|
|
80
|
+
for every pixel source in the engine.
|
|
81
|
+
|
|
82
|
+
{{ decl packages/core/src/types.d.ts TextureProps }}
|
|
83
|
+
|
|
84
|
+
## Logo
|
|
85
|
+
|
|
86
|
+
The SolidRT brand mark, the same seven-segment puzzle the scaffold's welcome
|
|
87
|
+
screen and the launcher draw, as a component: a square `view` of `size`
|
|
88
|
+
pixels with the segments as gradient-filled `d-path`s.
|
|
89
|
+
|
|
90
|
+
{{ decl packages/core/src/logo.tsx LogoProps }}
|
|
91
|
+
|
|
92
|
+
The default is static and requests no frames; `"once"` and `"loop"` drive a
|
|
93
|
+
staggered per-segment fade through `onFrame`, so the animated forms hold a
|
|
94
|
+
frame request only while they run (`"once"` releases it when the last
|
|
95
|
+
segment is in).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Elements
|
|
2
|
+
|
|
3
|
+
The two structural elements. Neither one paints: `window` is the root, `view`
|
|
4
|
+
is a box that lays out, clips, transforms and receives input. Everything
|
|
5
|
+
visible comes from the [drawing](/core/reference/drawing/) and
|
|
6
|
+
[text](/core/reference/text/) primitives inside them.
|
|
7
|
+
|
|
8
|
+
## window
|
|
9
|
+
|
|
10
|
+
One per app, the root of the tree. It composes
|
|
11
|
+
[layout](/core/reference/layout/) props, so the window is itself the outermost
|
|
12
|
+
flex container, and [pointer](/core/reference/input/) props, which is where
|
|
13
|
+
app-global key handling lives: key events always end their walk at the window
|
|
14
|
+
root.
|
|
15
|
+
|
|
16
|
+
{{ decl packages/core/src/types.d.ts WindowProps }}
|
|
17
|
+
|
|
18
|
+
The `shader` prop runs the finished frame through a GPU program as the last
|
|
19
|
+
step before the screen; see [shaders](/core/reference/shaders/).
|
|
20
|
+
|
|
21
|
+
## view
|
|
22
|
+
|
|
23
|
+
A `view` never paints. There is no `backgroundColor`: you put a `rect` behind
|
|
24
|
+
the content, and a shape with no geometry of its own fills the layout box it
|
|
25
|
+
sits in.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<view padding={16} alignItems="center">
|
|
29
|
+
<rect color="#1b2440" radius={12} />
|
|
30
|
+
<text color="white">Boxed</text>
|
|
31
|
+
</view>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Its props split in two. `ViewOwnProps` is everything a view offers besides
|
|
35
|
+
layout, and it is what the detached `d-view` composes on its own:
|
|
36
|
+
|
|
37
|
+
{{ decl packages/core/src/types.d.ts ViewOwnProps }}
|
|
38
|
+
|
|
39
|
+
The laid-out `view` is that plus the layout props:
|
|
40
|
+
|
|
41
|
+
{{ decl packages/core/src/types.d.ts ViewProps }}
|
|
42
|
+
|
|
43
|
+
Three of those props are worth knowing before you need them:
|
|
44
|
+
|
|
45
|
+
- `designSize` fits a design-space coordinate system into the element's box,
|
|
46
|
+
scaled uniformly and centered. Everything under the view - layout, paint,
|
|
47
|
+
input - happens in design units, and the view itself sizes like a
|
|
48
|
+
replaced element whose intrinsic size is the design size. It is the
|
|
49
|
+
natural wrapper for `parseSvg` output, any `d-*` subtree authored in
|
|
50
|
+
fixed units, or a whole laid-out panel that should scale rather than
|
|
51
|
+
reflow.
|
|
52
|
+
- `repaintBoundary` retains the subtree's display list, and in its
|
|
53
|
+
`"snapshot"` forms its rasterized pixels too. It is the lever for putting
|
|
54
|
+
heavy static content next to content that changes every frame.
|
|
55
|
+
- `clipRadius` rounds the clip, and only does anything when `overflow` is
|
|
56
|
+
non-visible.
|