@solidrt/core 0.0.55 → 0.0.57
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 +150 -20
- package/agents/painting.md +29 -12
- package/agents/performance.md +14 -3
- package/docs/reference/detached.md +8 -5
- package/docs/reference/drawing.md +1 -1
- package/docs/reference/elements.md +3 -1
- package/docs/reference/gpu.md +12 -1
- package/docs/reference/layout.md +28 -0
- package/examples/README.md +3 -0
- package/examples/detached-positioning.tsx +3 -1
- package/examples/gamepad.tsx +94 -0
- package/examples/view-design-size.tsx +5 -0
- package/package.json +7 -5
- package/src/core.ts +15 -2
- package/src/environment.ts +87 -69
- package/src/gamepad.ts +10 -8
- package/src/gpu.ts +122 -18
- package/src/index.ts +14 -2
- package/src/input-axes.ts +180 -0
- package/src/input-gamepad-device.ts +161 -0
- package/src/input-gamepad.ts +24 -0
- package/src/input-keyboard.ts +156 -0
- package/src/input-map.ts +467 -0
- package/src/input-pointer.ts +213 -0
- package/src/input.ts +12 -0
- package/src/pan.ts +20 -5
- package/src/runtime-modules.d.ts +7 -7
- package/src/text-input.ts +43 -8
- package/src/transform.ts +52 -23
- package/src/types.d.ts +84 -16
- package/src/window.ts +54 -38
package/src/input.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The runtime-free half of the input surface, importable as
|
|
2
|
+
// `@solidrt/core/input` by modules that must stay headless (the camera
|
|
3
|
+
// controls and their checks run on the bare flux binary, which has no
|
|
4
|
+
// event bus). The map, the control-side axes contract and the keyboard
|
|
5
|
+
// device are pure; the gamepad device and the pointer feed need the
|
|
6
|
+
// runtime and live on the main entry.
|
|
7
|
+
export { createInputMap, invert, scale } from "./input-map"
|
|
8
|
+
export type { ActionKind, ActionValue, ActionsDecl, Binding, DeltaSink, GestureListener, InputMap, InputSource } from "./input-map"
|
|
9
|
+
export { createAxes, combineRates } from "./input-axes"
|
|
10
|
+
export type { Axes, AxesDecl, AxesHooks, AxisKind, AxisValue, Vec2 } from "./input-axes"
|
|
11
|
+
export { keyboard } from "./input-keyboard"
|
|
12
|
+
export type { KeyboardDevice, KeyboardVec2Keys } from "./input-keyboard"
|
package/src/pan.ts
CHANGED
|
@@ -18,7 +18,13 @@ export interface PanOptions {
|
|
|
18
18
|
*/
|
|
19
19
|
axis?: PanAxis
|
|
20
20
|
onPanStart?: () => void
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Pointer movement since the previous event, in the handler node's PARENT
|
|
23
|
+
* frame (the frame its own x/y live in: window pixels under no scaling
|
|
24
|
+
* ancestor, design pixels under a designSize fit), so it applies 1:1
|
|
25
|
+
* whether the consumer moves the node itself or content inside it.
|
|
26
|
+
* Positive dx is rightward, positive dy downward.
|
|
27
|
+
*/
|
|
22
28
|
onPanMove?: (dx: number, dy: number) => void
|
|
23
29
|
onPanEnd?: () => void
|
|
24
30
|
}
|
|
@@ -35,8 +41,17 @@ export interface PanOptions {
|
|
|
35
41
|
// cancel() is the external-cancel hook; it ends an active pan without
|
|
36
42
|
// onPanEnd. Options are read at event time. Single-pointer by design; for
|
|
37
43
|
// multi-pointer pinch/rotate compose createTransform instead.
|
|
44
|
+
//
|
|
45
|
+
// Frames: the slop is finger travel, so it is measured in window pixels
|
|
46
|
+
// (clientX/clientY); the deltas are measured in the node's parent frame
|
|
47
|
+
// (parentX/parentY), the frame the node's own coordinates live in, so a
|
|
48
|
+
// scaled ancestor (a designSize fit) is accounted for and both the drag
|
|
49
|
+
// idiom (move the node) and the content idiom (scroll what is inside it)
|
|
50
|
+
// apply the delta unchanged. The event carries both frames exactly, on
|
|
51
|
+
// and off the node.
|
|
38
52
|
export function createPan(options: PanOptions) {
|
|
39
|
-
// Down position while armed; last delivered position
|
|
53
|
+
// Down position (window pixels) while armed; last delivered position
|
|
54
|
+
// (parent frame) while active.
|
|
40
55
|
let origin: { x: number; y: number } | null = null
|
|
41
56
|
let active: number | null = null
|
|
42
57
|
let armed: number | null = null
|
|
@@ -78,7 +93,7 @@ export function createPan(options: PanOptions) {
|
|
|
78
93
|
if (arena.steal(e.pointerId, owner)) {
|
|
79
94
|
active = e.pointerId
|
|
80
95
|
armed = null
|
|
81
|
-
origin = { x: e.
|
|
96
|
+
origin = { x: e.parentX, y: e.parentY }
|
|
82
97
|
options.onPanStart?.()
|
|
83
98
|
} else {
|
|
84
99
|
// The arena is resolved against us; the drag belongs elsewhere.
|
|
@@ -87,8 +102,8 @@ export function createPan(options: PanOptions) {
|
|
|
87
102
|
return
|
|
88
103
|
}
|
|
89
104
|
if (active === e.pointerId && origin) {
|
|
90
|
-
options.onPanMove?.(e.
|
|
91
|
-
origin = { x: e.
|
|
105
|
+
options.onPanMove?.(e.parentX - origin.x, e.parentY - origin.y)
|
|
106
|
+
origin = { x: e.parentX, y: e.parentY }
|
|
92
107
|
}
|
|
93
108
|
},
|
|
94
109
|
onPointerUp: (e: PointerEvent) => {
|
package/src/runtime-modules.d.ts
CHANGED
|
@@ -66,8 +66,8 @@ declare module "srt:events" {
|
|
|
66
66
|
// The running application's own surface (lattice), present in every build.
|
|
67
67
|
declare module "srt:app" {
|
|
68
68
|
/**
|
|
69
|
-
* Leave the current app, unconditionally: back to the
|
|
70
|
-
* client, quit when standalone or at the
|
|
69
|
+
* Leave the current app, unconditionally: back to the player in a dev
|
|
70
|
+
* client, quit when standalone or at the player root (on Android the
|
|
71
71
|
* client backgrounds instead of dying). The default action of an
|
|
72
72
|
* unprevented `back` event; prefer the @solidrt/core re-export.
|
|
73
73
|
*/
|
|
@@ -81,7 +81,7 @@ declare module "srt:dev" {
|
|
|
81
81
|
export const canDiscover: boolean
|
|
82
82
|
export const recents: string[]
|
|
83
83
|
/**
|
|
84
|
-
* The dev-server address the client was launched with (so the
|
|
84
|
+
* The dev-server address the client was launched with (so the player can
|
|
85
85
|
* auto-connect without on-device interaction), or null when launched without
|
|
86
86
|
* one.
|
|
87
87
|
*/
|
|
@@ -101,7 +101,7 @@ declare module "srt:dev" {
|
|
|
101
101
|
export function registerDebug(name: string, fn: (args?: any) => unknown): void
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
// Installed-app management (lattice), the
|
|
104
|
+
// Installed-app management (lattice), the player's surface over the client's
|
|
105
105
|
// version store. Present only in go/dev client builds; elsewhere `available`
|
|
106
106
|
// is false, `list` returns [], and launch/remove are no-ops.
|
|
107
107
|
declare module "srt:apps" {
|
|
@@ -166,7 +166,7 @@ declare module "srt:apps" {
|
|
|
166
166
|
export function info(id: string): AppInfo
|
|
167
167
|
/**
|
|
168
168
|
* Boot the app's installed current version, replacing the running app (the
|
|
169
|
-
*
|
|
169
|
+
* player). Throws when the app is not installed. Custom fonts of the
|
|
170
170
|
* launched app register at client startup only, not mid-session.
|
|
171
171
|
*/
|
|
172
172
|
export function launch(id: string): void
|
|
@@ -182,8 +182,8 @@ declare module "srt:apps" {
|
|
|
182
182
|
*/
|
|
183
183
|
export function clearCache(id: string): void
|
|
184
184
|
/**
|
|
185
|
-
* Build identity of this runtime, for the
|
|
186
|
-
* app-specific, but surfaced here since the
|
|
185
|
+
* Build identity of this runtime, for the player's settings screen. Not
|
|
186
|
+
* app-specific, but surfaced here since the player already imports this
|
|
187
187
|
* module. `version` is the release version (git describe; "0.0.0-dev" in a
|
|
188
188
|
* plain build), `profile` is "debug" or "release", `platform` is the OS
|
|
189
189
|
* (std::env::consts::OS, e.g. "linux", "android", "windows", "macos").
|
package/src/text-input.ts
CHANGED
|
@@ -214,6 +214,9 @@ export type EditorLine = {
|
|
|
214
214
|
/** The caret's box in content coordinates (before scroll). */
|
|
215
215
|
export type CaretRect = { x: number; y: number; height: number }
|
|
216
216
|
|
|
217
|
+
/** One line's slice of a selection highlight, in content coordinates (before scroll). */
|
|
218
|
+
export type SelectionRect = { x: number; y: number; width: number; height: number }
|
|
219
|
+
|
|
217
220
|
export type TextEditorLayout = {
|
|
218
221
|
lines(): EditorLine[]
|
|
219
222
|
caret(): CaretRect
|
|
@@ -221,6 +224,8 @@ export type TextEditorLayout = {
|
|
|
221
224
|
caretLine(): number
|
|
222
225
|
/** The caret position (grapheme boundary) on line `line` nearest to content x. */
|
|
223
226
|
offsetAtX(line: number, x: number): number
|
|
227
|
+
/** Highlight boxes for the anchor..focus range, one per touched line; empty when collapsed. */
|
|
228
|
+
selectionRects(anchor: number, focus: number): SelectionRect[]
|
|
224
229
|
/** Index of the line at content y (clamped to the first/last line). */
|
|
225
230
|
lineAtY(y: number): number
|
|
226
231
|
/** The caret position one grapheme left/right of `offset`; a break sequence is one step. For createTextBuffer's `step`. */
|
|
@@ -322,18 +327,22 @@ export function createTextEditorLayout(
|
|
|
322
327
|
}
|
|
323
328
|
let caretLine = createMemo(() => lineOf(input().caret))
|
|
324
329
|
|
|
325
|
-
// The
|
|
326
|
-
// a grapheme, e.g. from a controlled value, snaps back).
|
|
327
|
-
let
|
|
328
|
-
let offset = input().caret
|
|
329
|
-
let index = caretLine()
|
|
330
|
-
let line = lines()[index]!
|
|
330
|
+
// The x of the last stop at or before `offset` on line `index` (an offset
|
|
331
|
+
// inside a grapheme, e.g. from a controlled value, snaps back).
|
|
332
|
+
let xAt = (index: number, offset: number): number => {
|
|
331
333
|
let x = 0
|
|
332
334
|
for (let stop of lineStops(index)) {
|
|
333
335
|
if (stop.offset > offset) break
|
|
334
336
|
x = stop.x
|
|
335
337
|
}
|
|
336
|
-
return
|
|
338
|
+
return x
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let caret = createMemo((): CaretRect => {
|
|
342
|
+
let offset = input().caret
|
|
343
|
+
let index = caretLine()
|
|
344
|
+
let line = lines()[index]!
|
|
345
|
+
return { x: xAt(index, offset), y: line.y, height: line.height }
|
|
337
346
|
})
|
|
338
347
|
|
|
339
348
|
// Only positions that show on this line are candidates: a boundary offset
|
|
@@ -352,6 +361,32 @@ export function createTextEditorLayout(
|
|
|
352
361
|
return best
|
|
353
362
|
}
|
|
354
363
|
|
|
364
|
+
// One box per line the range touches, offsets snapped to stops like the
|
|
365
|
+
// caret. A line the selection continues past (every one but the last)
|
|
366
|
+
// highlights to its ink end plus a space width, so the selected break - a
|
|
367
|
+
// newline, or the wrap the range crosses - is visible, and a selected empty
|
|
368
|
+
// line shows as that sliver alone. The last line ends at the focus stop; a
|
|
369
|
+
// range ending on a wrap boundary owns a zero-width box there (the boundary
|
|
370
|
+
// offset displays on the next line) and drops it.
|
|
371
|
+
let selectionRects = (anchor: number, focus: number): SelectionRect[] => {
|
|
372
|
+
let start = Math.min(anchor, focus)
|
|
373
|
+
let end = Math.max(anchor, focus)
|
|
374
|
+
if (start >= end) return []
|
|
375
|
+
let ls = lines()
|
|
376
|
+
let first = lineOf(start)
|
|
377
|
+
let last = lineOf(end)
|
|
378
|
+
let breakWidth = measureText(" ", input().font).width
|
|
379
|
+
let out: SelectionRect[] = []
|
|
380
|
+
for (let i = first; i <= last; i++) {
|
|
381
|
+
let line = ls[i]!
|
|
382
|
+
let x = i === first ? xAt(i, start) : 0
|
|
383
|
+
let width = i === last ? xAt(i, end) - x : Math.max(line.width - x, 0) + breakWidth
|
|
384
|
+
if (width <= 0) continue
|
|
385
|
+
out.push({ x, y: line.y, width, height: line.height })
|
|
386
|
+
}
|
|
387
|
+
return out
|
|
388
|
+
}
|
|
389
|
+
|
|
355
390
|
let lineAtY = (y: number): number => {
|
|
356
391
|
let ls = lines()
|
|
357
392
|
let index = 0
|
|
@@ -399,7 +434,7 @@ export function createTextEditorLayout(
|
|
|
399
434
|
setScrollY(follow(scrollY(), c.y, c.height, vh, contentHeight))
|
|
400
435
|
})
|
|
401
436
|
|
|
402
|
-
return { lines, caret, caretLine, offsetAtX, lineAtY, step, scrollX, scrollY }
|
|
437
|
+
return { lines, caret, caretLine, offsetAtX, selectionRects, lineAtY, step, scrollX, scrollY }
|
|
403
438
|
}
|
|
404
439
|
|
|
405
440
|
// Wrap units wider than `width` (through their glued pieces) split into one
|
package/src/transform.ts
CHANGED
|
@@ -26,14 +26,18 @@ const ENGAGE = 0.6
|
|
|
26
26
|
const QUIET_SMOOTH = 0.19
|
|
27
27
|
|
|
28
28
|
export type TransformDelta = {
|
|
29
|
-
/** Focal-point movement since the previous frame,
|
|
29
|
+
/** Focal-point movement since the previous frame, in the handler node's
|
|
30
|
+
* PARENT frame (the frame its own x/y live in: window pixels under no
|
|
31
|
+
* scaling ancestor, design pixels under a designSize fit), so it applies
|
|
32
|
+
* 1:1 to the node or to content inside it. */
|
|
30
33
|
dx: number
|
|
31
34
|
dy: number
|
|
32
35
|
/** Multiplicative span change since the previous frame (1 = unchanged, >1 = fingers spreading). */
|
|
33
36
|
scale: number
|
|
34
37
|
/** Rotation of the pointer pair since the previous frame, radians (0 with a single pointer). */
|
|
35
38
|
rotation: number
|
|
36
|
-
/** Current focal point in
|
|
39
|
+
/** Current focal point in the handler node's LOCAL frame (its own
|
|
40
|
+
* pixels) - the zoom-about anchor for content inside it. */
|
|
37
41
|
x: number
|
|
38
42
|
y: number
|
|
39
43
|
/** How many pointers are down right now. Consumers that give one- and
|
|
@@ -106,12 +110,25 @@ export interface TransformOptions {
|
|
|
106
110
|
// path, so an active transform survives leaving the node or the window.
|
|
107
111
|
// cancel() is the external-cancel hook; it ends an active gesture without
|
|
108
112
|
// onTransformEnd. Options are read at event time.
|
|
113
|
+
//
|
|
114
|
+
// Frames: the slop, the span filter and its rate gates are finger-travel
|
|
115
|
+
// thresholds, so they are measured in window pixels (clientX/clientY);
|
|
116
|
+
// the focal movement (dx/dy) and the pair angle are measured in the node's
|
|
117
|
+
// parent frame (parentX/parentY), the frame the node's own coordinates
|
|
118
|
+
// live in, so a scaled ancestor (a designSize fit) is accounted for and
|
|
119
|
+
// the delta applies unchanged to the node or to content inside it; the
|
|
120
|
+
// focal point (x/y) is reported in the node's local frame (localX/localY),
|
|
121
|
+
// the anchor a zoom-about-the-fingers needs. Scale and rotation are ratios
|
|
122
|
+
// and angles, the same in every uniformly scaled frame. The event carries
|
|
123
|
+
// all three frames exactly, on and off the node.
|
|
109
124
|
export function createTransform(options: TransformOptions) {
|
|
125
|
+
type Tracked = { cx: number; cy: number; px: number; py: number; lx: number; ly: number }
|
|
126
|
+
let at = (e: PointerEvent): Tracked => ({ cx: e.clientX, cy: e.clientY, px: e.parentX, py: e.parentY, lx: e.localX, ly: e.localY })
|
|
110
127
|
// All tracked pointers, in down order (the first two define the rotation pair).
|
|
111
|
-
let pointers = new Map<number,
|
|
128
|
+
let pointers = new Map<number, Tracked>()
|
|
112
129
|
let active = false
|
|
113
130
|
// Slop baseline while armed; last delivered configuration while active.
|
|
114
|
-
let ref: {
|
|
131
|
+
let ref: { cx: number; cy: number; px: number; py: number; span: number; angle: number } | null = null
|
|
115
132
|
// The zoom gate and filter (see the zoom-noise note above): scale stays 1
|
|
116
133
|
// until the SMOOTHED span leaves spanBase by SLOP, then streams smoothed
|
|
117
134
|
// ratios for the rest of the gesture.
|
|
@@ -128,27 +145,41 @@ export function createTransform(options: TransformOptions) {
|
|
|
128
145
|
// emits nothing.
|
|
129
146
|
let rebase = false
|
|
130
147
|
|
|
148
|
+
// Centroids in all three frames, the span in window pixels (it feeds the
|
|
149
|
+
// finger-travel gates), the pair angle in the parent frame.
|
|
131
150
|
let measure = () => {
|
|
132
151
|
let n = pointers.size
|
|
133
|
-
let
|
|
134
|
-
let
|
|
152
|
+
let cx = 0
|
|
153
|
+
let cy = 0
|
|
154
|
+
let px = 0
|
|
155
|
+
let py = 0
|
|
156
|
+
let lx = 0
|
|
157
|
+
let ly = 0
|
|
135
158
|
for (let p of pointers.values()) {
|
|
136
|
-
|
|
137
|
-
|
|
159
|
+
cx += p.cx
|
|
160
|
+
cy += p.cy
|
|
161
|
+
px += p.px
|
|
162
|
+
py += p.py
|
|
163
|
+
lx += p.lx
|
|
164
|
+
ly += p.ly
|
|
138
165
|
}
|
|
139
|
-
|
|
140
|
-
|
|
166
|
+
cx /= n
|
|
167
|
+
cy /= n
|
|
168
|
+
px /= n
|
|
169
|
+
py /= n
|
|
170
|
+
lx /= n
|
|
171
|
+
ly /= n
|
|
141
172
|
let span = 0
|
|
142
|
-
for (let p of pointers.values()) span += Math.hypot(p.
|
|
173
|
+
for (let p of pointers.values()) span += Math.hypot(p.cx - cx, p.cy - cy)
|
|
143
174
|
span /= n
|
|
144
175
|
let angle = 0
|
|
145
176
|
if (n >= 2) {
|
|
146
177
|
let pair = pointers.values()
|
|
147
178
|
let a = pair.next().value!
|
|
148
179
|
let b = pair.next().value!
|
|
149
|
-
angle = Math.atan2(b.
|
|
180
|
+
angle = Math.atan2(b.py - a.py, b.px - a.px)
|
|
150
181
|
}
|
|
151
|
-
return {
|
|
182
|
+
return { cx, cy, px, py, lx, ly, span, angle }
|
|
152
183
|
}
|
|
153
184
|
|
|
154
185
|
let reset = () => {
|
|
@@ -202,10 +233,10 @@ export function createTransform(options: TransformOptions) {
|
|
|
202
233
|
if (rotation > Math.PI) rotation -= 2 * Math.PI
|
|
203
234
|
else if (rotation < -Math.PI) rotation += 2 * Math.PI
|
|
204
235
|
}
|
|
205
|
-
let dx = m.
|
|
206
|
-
let dy = m.
|
|
236
|
+
let dx = m.px - ref.px
|
|
237
|
+
let dy = m.py - ref.py
|
|
207
238
|
ref = m
|
|
208
|
-
options.onTransformMove?.({ dx, dy, scale, rotation, x: m.
|
|
239
|
+
options.onTransformMove?.({ dx, dy, scale, rotation, x: m.lx, y: m.ly, pointers: pointers.size })
|
|
209
240
|
}
|
|
210
241
|
|
|
211
242
|
// An unmount mid-gesture must not leave resolved claims behind (or a live
|
|
@@ -226,24 +257,22 @@ export function createTransform(options: TransformOptions) {
|
|
|
226
257
|
// A finger joining an established gesture belongs to it outright; if
|
|
227
258
|
// the arena refuses (resolved elsewhere) the finger stays out.
|
|
228
259
|
if (!arena.steal(e.pointerId, owner)) return
|
|
229
|
-
pointers.set(e.pointerId,
|
|
260
|
+
pointers.set(e.pointerId, at(e))
|
|
230
261
|
rebase = true
|
|
231
262
|
return
|
|
232
263
|
}
|
|
233
|
-
pointers.set(e.pointerId,
|
|
264
|
+
pointers.set(e.pointerId, at(e))
|
|
234
265
|
ref = measure()
|
|
235
266
|
},
|
|
236
267
|
onPointerMove: (e: PointerEvent) => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
p.x = e.clientX
|
|
240
|
-
p.y = e.clientY
|
|
268
|
+
if (!pointers.has(e.pointerId) || !ref) return
|
|
269
|
+
pointers.set(e.pointerId, at(e))
|
|
241
270
|
if (!active) {
|
|
242
271
|
// Arming runs per event: the slop test tolerates a mixed-age
|
|
243
272
|
// measure (8px against <=1 frame of staleness), and the arena
|
|
244
273
|
// steal must happen synchronously inside the dispatch walk.
|
|
245
274
|
let m = measure()
|
|
246
|
-
let travel = Math.hypot(m.
|
|
275
|
+
let travel = Math.hypot(m.cx - ref.cx, m.cy - ref.cy)
|
|
247
276
|
if (travel < SLOP && Math.abs(m.span - ref.span) < SLOP) return
|
|
248
277
|
let taken: number[] = []
|
|
249
278
|
let refused = false
|
package/src/types.d.ts
CHANGED
|
@@ -175,6 +175,48 @@ export interface PaintProps {
|
|
|
175
175
|
strokeWidth?: number
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* A drop shadow behind a shape (CSS box-shadow field semantics, one shadow):
|
|
180
|
+
* the shape's outer geometry, offset by x/y, grown by `spread`, softened by
|
|
181
|
+
* `blur` (a CSS-style radius in logical px), painted in `color` under the
|
|
182
|
+
* shape. It casts from the shape's outer box whatever the draw style (strokes
|
|
183
|
+
* paint inside, so the box is the outer edge) - except on `path`, where the
|
|
184
|
+
* shadow mirrors the element's own fill/stroke silhouette and `spread` is
|
|
185
|
+
* rejected (an arbitrary path cannot be inflated exactly). `color` is
|
|
186
|
+
* required; there is no currentColor to inherit.
|
|
187
|
+
*/
|
|
188
|
+
export interface ShadowProps {
|
|
189
|
+
x?: number
|
|
190
|
+
y?: number
|
|
191
|
+
blur?: number
|
|
192
|
+
spread?: number
|
|
193
|
+
color: Color
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* A subtree filter (the CSS filter functions, object form): the view's
|
|
198
|
+
* children are composited together, run through the set color operations -
|
|
199
|
+
* fused into one color matrix, applied in the fixed order grayscale, sepia,
|
|
200
|
+
* saturate, hueRotate, brightness, contrast, invert - and then blurred.
|
|
201
|
+
* `blur` is a CSS-style radius in logical px; `hueRotate` is radians (this
|
|
202
|
+
* API's angle convention); the rest are the CSS amounts (1 = unchanged for
|
|
203
|
+
* saturate/brightness/contrast, 0 = unchanged for the others; grayscale,
|
|
204
|
+
* sepia and invert saturate at 1). There is no `opacity` key - the view's
|
|
205
|
+
* own `opacity` prop is the same compositing layer. Like opacity, a filter
|
|
206
|
+
* on a non-boundary view costs a save_layer; on a repaintBoundary view it
|
|
207
|
+
* rides the composite for free. Hit testing is unaffected.
|
|
208
|
+
*/
|
|
209
|
+
export interface FilterProps {
|
|
210
|
+
blur?: number
|
|
211
|
+
grayscale?: number
|
|
212
|
+
sepia?: number
|
|
213
|
+
saturate?: number
|
|
214
|
+
hueRotate?: number
|
|
215
|
+
brightness?: number
|
|
216
|
+
contrast?: number
|
|
217
|
+
invert?: number
|
|
218
|
+
}
|
|
219
|
+
|
|
178
220
|
/** A percentage value, from `pct(50)`. Resolves against the element box. */
|
|
179
221
|
export type Pct = { readonly __unit: "pct"; v: number }
|
|
180
222
|
|
|
@@ -548,9 +590,10 @@ export interface TransitionProps {
|
|
|
548
590
|
* `all` covers every animatable property the element has, and a bare
|
|
549
591
|
* string is shorthand for it: `transition="300ms ease-out"`. Only
|
|
550
592
|
* properties the element carries animate (a d-rect has x, a view's x is
|
|
551
|
-
* its transform); the initial
|
|
552
|
-
*
|
|
553
|
-
*
|
|
593
|
+
* its transform); the initial values (every write before the element's
|
|
594
|
+
* first paint) never animate unless the entry sets `from` (an enter
|
|
595
|
+
* animation), and a non-numeric write (e.g. null) cancels the running
|
|
596
|
+
* animation and snaps. `null` clears the
|
|
554
597
|
* declaration; already-running animations finish. A spec built in a
|
|
555
598
|
* conditional widens `curve` to `string` for TypeScript; write
|
|
556
599
|
* `satisfies Transition` on the branch.
|
|
@@ -607,11 +650,13 @@ export interface WindowShaderProps {
|
|
|
607
650
|
program: ProgramId
|
|
608
651
|
/**
|
|
609
652
|
* Uniforms filled by name, paced to the next real repaint. A number drives
|
|
610
|
-
* a scalar (`float`/`int`); a flat number array
|
|
611
|
-
* type: 2/3/4 for
|
|
653
|
+
* a scalar (`float`/`int`); a flat number array (or a Float32Array /
|
|
654
|
+
* Float64Array) drives the declared GLSL type: 2/3/4 for
|
|
655
|
+
* `vec2`/`vec3`/`vec4`, 16 (column-major) for `mat4`. An undefined or null
|
|
656
|
+
* entry is skipped.
|
|
612
657
|
*/
|
|
613
|
-
params?: Record<string, number | number[]>
|
|
614
|
-
/** Extra sampler2D inputs: uniform name to texture id, or `{ id, filter?, wrap? }` for a per-binding sampling override. */
|
|
658
|
+
params?: Record<string, number | number[] | Float32Array | Float64Array>
|
|
659
|
+
/** Extra sampler2D inputs: uniform name to texture id, or `{ id, filter?, wrap? }` for a per-binding sampling override. An undefined or null entry is skipped. */
|
|
615
660
|
textures?: TextureBindings
|
|
616
661
|
/** Vertices drawn (attributeless triangles). Default 3, the covering triangle. */
|
|
617
662
|
vertexCount?: number
|
|
@@ -671,7 +716,9 @@ export interface ViewOwnProps extends TransformProps, PointerProps {
|
|
|
671
716
|
* skipping rasterization entirely. Costs texture memory and re-rasterizes
|
|
672
717
|
* on layout-size or display-scale changes. Content painted outside the
|
|
673
718
|
* element's layout box is cropped, and ancestor scale animations smear the
|
|
674
|
-
* bitmap; best for screen-aligned, static, raster-expensive content.
|
|
719
|
+
* bitmap; best for screen-aligned, static, raster-expensive content. On a
|
|
720
|
+
* d-view the box is the inherited frame (the nearest laid-out ancestor's
|
|
721
|
+
* box, or the design size under a designSize view), same as a capture.
|
|
675
722
|
*
|
|
676
723
|
* "snapshot-no-aa" is "snapshot" rasterized without anti-aliasing: cheaper
|
|
677
724
|
* (no multisampled scratch, one render pass), but vector content - svg
|
|
@@ -701,6 +748,19 @@ export interface ViewOwnProps extends TransformProps, PointerProps {
|
|
|
701
748
|
* unrotated, opaque content.
|
|
702
749
|
*/
|
|
703
750
|
shader?: ViewShaderProps | null
|
|
751
|
+
filter?: FilterProps
|
|
752
|
+
/**
|
|
753
|
+
* Filters the pixels already painted BENEATH the view's box, in place,
|
|
754
|
+
* before the view's own content draws - frosted glass over live content
|
|
755
|
+
* (CSS `backdrop-filter`). Same keys and semantics as `filter`. The
|
|
756
|
+
* region is the layout box; give the view an overflow clip with
|
|
757
|
+
* `clipRadius` for rounded glass. Costs an offscreen capture of the
|
|
758
|
+
* pixels beneath at that point in the frame, so treat it as a deliberate
|
|
759
|
+
* panel, not a casual style. Inside a `repaintBoundary="snapshot"`
|
|
760
|
+
* subtree it sees only that boundary's own offscreen content, never what
|
|
761
|
+
* is behind the boundary (the same containment `blendMode` has there).
|
|
762
|
+
*/
|
|
763
|
+
backdropFilter?: FilterProps
|
|
704
764
|
}
|
|
705
765
|
|
|
706
766
|
/**
|
|
@@ -716,11 +776,13 @@ export interface ViewShaderProps {
|
|
|
716
776
|
program: ProgramId
|
|
717
777
|
/**
|
|
718
778
|
* Uniforms filled by name, paced to the next real repaint. A number drives
|
|
719
|
-
* a scalar (`float`/`int`); a flat number array
|
|
720
|
-
* type: 2/3/4 for
|
|
779
|
+
* a scalar (`float`/`int`); a flat number array (or a Float32Array /
|
|
780
|
+
* Float64Array) drives the declared GLSL type: 2/3/4 for
|
|
781
|
+
* `vec2`/`vec3`/`vec4`, 16 (column-major) for `mat4`. An undefined or null
|
|
782
|
+
* entry is skipped.
|
|
721
783
|
*/
|
|
722
|
-
params?: Record<string, number | number[]>
|
|
723
|
-
/** Extra sampler2D inputs: uniform name to texture id, or `{ id, filter?, wrap? }` for a per-binding sampling override. */
|
|
784
|
+
params?: Record<string, number | number[] | Float32Array | Float64Array>
|
|
785
|
+
/** Extra sampler2D inputs: uniform name to texture id, or `{ id, filter?, wrap? }` for a per-binding sampling override. An undefined or null entry is skipped. */
|
|
724
786
|
textures?: TextureBindings
|
|
725
787
|
/**
|
|
726
788
|
* Transparent margin in logical px on every side of the layout box, for
|
|
@@ -761,10 +823,13 @@ export interface RectProps extends PaintProps, PointerProps, DashProps {
|
|
|
761
823
|
// number applies to all four corners; an array is [top-left, top-right,
|
|
762
824
|
// bottom-right, bottom-left] (CSS border-radius order).
|
|
763
825
|
radius?: number | [number, number, number, number]
|
|
826
|
+
shadow?: ShadowProps
|
|
764
827
|
}
|
|
765
828
|
|
|
766
829
|
// Strokes paint inside the box, same as `RectProps`.
|
|
767
|
-
export interface OvalProps extends PaintProps, PointerProps, DashProps {
|
|
830
|
+
export interface OvalProps extends PaintProps, PointerProps, DashProps {
|
|
831
|
+
shadow?: ShadowProps
|
|
832
|
+
}
|
|
768
833
|
|
|
769
834
|
/**
|
|
770
835
|
* A stroke's dash pattern, on every stroked primitive (`rect`, `oval`,
|
|
@@ -847,6 +912,8 @@ export interface LineProps extends PaintProps, PointerProps, DashProps {
|
|
|
847
912
|
export interface PathProps extends PaintProps, PointerProps, DashProps {
|
|
848
913
|
d?: string
|
|
849
914
|
fillRule?: "nonzero" | "evenodd"
|
|
915
|
+
/** Shadows the drawn silhouette (fill and/or stroke); `spread` is rejected here. */
|
|
916
|
+
shadow?: ShadowProps
|
|
850
917
|
}
|
|
851
918
|
|
|
852
919
|
export type FontFamily = "sans" | "serif" | "mono" | (string & {})
|
|
@@ -957,7 +1024,8 @@ export interface TextureProps extends PaintProps, PointerProps {
|
|
|
957
1024
|
// throws, and set src before params: a write with no src to route to
|
|
958
1025
|
// throws). However often a signal writes, the target renders once per
|
|
959
1026
|
// frame at the raster flush. A number drives a scalar (`float`/`int`); a
|
|
960
|
-
// flat number array
|
|
961
|
-
// `vec2`/`vec3`/`vec4`, 16 (column-major) for `mat4`.
|
|
962
|
-
|
|
1027
|
+
// flat number array (or a Float32Array / Float64Array) drives the declared
|
|
1028
|
+
// GLSL type: 2/3/4 for `vec2`/`vec3`/`vec4`, 16 (column-major) for `mat4`.
|
|
1029
|
+
// An undefined or null entry is skipped.
|
|
1030
|
+
params?: Record<string, number | number[] | Float32Array | Float64Array>
|
|
963
1031
|
}
|