@solidrt/core 0.0.5 → 0.0.7
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/README.md +2 -2
- package/jsx-runtime.d.ts +5 -2
- package/package.json +4 -4
- package/src/core.ts +78 -0
- package/src/index.ts +8 -3
- package/src/renderer.ts +5 -7
- package/src/types.d.ts +54 -27
- package/src/window.ts +107 -50
- package/src/color.ts +0 -9
- package/src/events.ts +0 -22
- package/src/focus.ts +0 -27
- package/src/text.ts +0 -5
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ Run the app:
|
|
|
36
36
|
bunx srt run src/index.tsx
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Optionally, create a `tsconfig.json` to enable
|
|
39
|
+
Optionally, create a `tsconfig.json` to enable type recognition for SolidRT elements:
|
|
40
40
|
|
|
41
41
|
```json
|
|
42
42
|
{
|
|
@@ -51,7 +51,7 @@ Optionally, create a `tsconfig.json` to enable JSX support and type recognition
|
|
|
51
51
|
|
|
52
52
|
## API
|
|
53
53
|
|
|
54
|
-
See [docs/
|
|
54
|
+
See [docs/core.md](https://github.com/wellawaretech/solidrt/blob/main/docs/core.md) for the full API reference.
|
|
55
55
|
|
|
56
56
|
## License
|
|
57
57
|
|
package/jsx-runtime.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
WindowProps,
|
|
3
|
-
CircleProps,
|
|
4
3
|
RectProps,
|
|
5
4
|
OvalProps,
|
|
6
5
|
PathProps,
|
|
@@ -16,7 +15,10 @@ export namespace JSX {
|
|
|
16
15
|
type Element = SolidJSX.Element
|
|
17
16
|
type ElementChildrenAttribute = SolidJSX.ElementChildrenAttribute
|
|
18
17
|
|
|
19
|
-
type
|
|
18
|
+
// Return type is unconstrained: a ref callback's return value is ignored, so
|
|
19
|
+
// an arrow like `ref={n => (this.node = n)}` (which returns the assignment)
|
|
20
|
+
// is accepted as readily as a void-returning block body.
|
|
21
|
+
type RefCallback<T> = (el: T) => unknown
|
|
20
22
|
type Ref<T> = T | RefCallback<T>
|
|
21
23
|
|
|
22
24
|
interface IntrinsicAttributes {
|
|
@@ -32,6 +34,7 @@ export namespace JSX {
|
|
|
32
34
|
path: PathProps & LayoutProps
|
|
33
35
|
texture: TextureProps & LayoutProps
|
|
34
36
|
audio: AudioProps
|
|
37
|
+
"d-view": ViewProps
|
|
35
38
|
"d-rect": RectProps
|
|
36
39
|
"d-oval": OvalProps
|
|
37
40
|
"d-path": PathProps
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Antoine van Wel",
|
|
6
6
|
"type": "module",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"colord": "^2.9.3"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@solidrt/flux-types": "0.0.
|
|
23
|
+
"@solidrt/flux-types": "0.0.7"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@solidjs/signals": "2.0.0-beta.
|
|
27
|
-
"@solidjs/universal": "2.0.0-beta.
|
|
26
|
+
"@solidjs/signals": "2.0.0-beta.14",
|
|
27
|
+
"@solidjs/universal": "2.0.0-beta.14"
|
|
28
28
|
}
|
|
29
29
|
}
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { colord, extend } from "colord"
|
|
2
|
+
import namesPlugin from "colord/plugins/names"
|
|
3
|
+
import type { MeasureTextOptions } from "./types"
|
|
4
|
+
extend([namesPlugin])
|
|
5
|
+
|
|
6
|
+
export function parseColorToU32(color: string): number {
|
|
7
|
+
let { r, g, b, a } = colord(color).toRgb()
|
|
8
|
+
return (((r & 0xFF) << 24) | ((g & 0xFF) << 16) | ((b & 0xFF) << 8) | ((a * 255) & 0xFF)) >>> 0
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let handlers = new Map<number, Map<string, Function>>()
|
|
12
|
+
|
|
13
|
+
export function setEventHandler(nodeId: number, name: string, fn: Function | null | undefined): void {
|
|
14
|
+
if (fn == null) {
|
|
15
|
+
handlers.get(nodeId)?.delete(name)
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
let nodeHandlers = handlers.get(nodeId)
|
|
19
|
+
if (!nodeHandlers) {
|
|
20
|
+
nodeHandlers = new Map()
|
|
21
|
+
handlers.set(nodeId, nodeHandlers)
|
|
22
|
+
}
|
|
23
|
+
nodeHandlers.set(name, fn)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getEventHandler(nodeId: number, name: string): Function | undefined {
|
|
27
|
+
return handlers.get(nodeId)?.get(name)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function cleanupNodeHandlers(nodeId: number): void {
|
|
31
|
+
handlers.delete(nodeId)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Currently-focused node id. Reset to null automatically across engine
|
|
35
|
+
// reloads because the JS environment is rebuilt from scratch.
|
|
36
|
+
let focusedNodeId: number | null = null
|
|
37
|
+
let textInputActive = false
|
|
38
|
+
|
|
39
|
+
export function setFocus(nodeId: number | null): void {
|
|
40
|
+
if (nodeId === focusedNodeId) return
|
|
41
|
+
let oldId = focusedNodeId
|
|
42
|
+
focusedNodeId = nodeId
|
|
43
|
+
if (oldId != null) {
|
|
44
|
+
getEventHandler(oldId, "onBlur")?.()
|
|
45
|
+
}
|
|
46
|
+
if (nodeId != null) {
|
|
47
|
+
getEventHandler(nodeId, "onFocus")?.()
|
|
48
|
+
}
|
|
49
|
+
let wantActive = nodeId != null && getEventHandler(nodeId, "onTextInput") != null
|
|
50
|
+
if (wantActive !== textInputActive) {
|
|
51
|
+
textInputActive = wantActive
|
|
52
|
+
ffi.setTextInputActive(wantActive)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getFocusedNodeId(): number | null {
|
|
57
|
+
return focusedNodeId
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface BoundingBox {
|
|
61
|
+
x: number
|
|
62
|
+
y: number
|
|
63
|
+
width: number
|
|
64
|
+
height: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Returns the node's window-relative bounding box from the most recently
|
|
68
|
+
// computed layout, or null if the node has no layout or has not been laid out
|
|
69
|
+
// yet. This is a snapshot read, not reactive: call it inside onLayout (or an
|
|
70
|
+
// event handler) to get values for the current frame. Phase 1 composes only
|
|
71
|
+
// translations; x/y are wrong when a rotate/scale sits anywhere above the node.
|
|
72
|
+
export function getBoundingBox(node: { id: number }): BoundingBox | null {
|
|
73
|
+
return ffi.getBoundingBox(node.id)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function measureText(text: string, options?: MeasureTextOptions): { width: number, height: number } {
|
|
77
|
+
return ffi.measureText(text, options)
|
|
78
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
export * from "./renderer"
|
|
2
|
-
export { setFocus, getFocusedNodeId } from "./
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
2
|
+
export { setFocus, getFocusedNodeId, measureText, getBoundingBox } from "./core"
|
|
3
|
+
export type { BoundingBox } from "./core"
|
|
4
|
+
export { onFrame, createPacedClock, onLayout, onResize, onWindowFocus, onWindowBlur } from "./window"
|
|
5
5
|
export { createTexture, decodeImage } from "./gpu"
|
|
6
6
|
export type { DecodedImage } from "./gpu"
|
|
7
7
|
export type {
|
|
8
8
|
LayoutProps,
|
|
9
9
|
TransformProps,
|
|
10
10
|
PointerProps,
|
|
11
|
+
PointerEvent,
|
|
12
|
+
WheelEvent,
|
|
13
|
+
KeyEvent,
|
|
14
|
+
TextEvent,
|
|
11
15
|
PaintProps,
|
|
12
16
|
WindowProps,
|
|
13
17
|
ViewProps,
|
|
14
18
|
RectProps,
|
|
15
19
|
OvalProps,
|
|
20
|
+
LineProps,
|
|
16
21
|
PathProps,
|
|
17
22
|
TextProps,
|
|
18
23
|
TextureProps,
|
package/src/renderer.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { createRoot, createEffect } from "@solidjs/signals"
|
|
2
2
|
import { createRenderer } from "@solidjs/universal"
|
|
3
3
|
import { attachWindow } from "./window"
|
|
4
|
-
import { parseColorToU32,
|
|
5
|
-
import { setEventHandler, cleanupNodeHandlers } from "./events"
|
|
6
|
-
import { getFocusedNodeId, setFocus } from "./focus"
|
|
4
|
+
import { parseColorToU32, setEventHandler, cleanupNodeHandlers, getFocusedNodeId, setFocus } from "./core"
|
|
7
5
|
|
|
8
|
-
export { getEventHandler } from "./
|
|
6
|
+
export { getEventHandler } from "./core"
|
|
9
7
|
|
|
10
8
|
export let nodes = new Map()
|
|
11
9
|
|
|
@@ -56,9 +54,9 @@ export let {
|
|
|
56
54
|
},
|
|
57
55
|
|
|
58
56
|
createTextNode: (value: string): ProxyNode => {
|
|
59
|
-
let proxy = createProxyNode("span")
|
|
57
|
+
let proxy = createProxyNode("d-span")
|
|
60
58
|
// console.debug("[srt] createTextNode", proxy.id, value)
|
|
61
|
-
ffi.createNode(proxy.id, "span")
|
|
59
|
+
ffi.createNode(proxy.id, "d-span")
|
|
62
60
|
ffi.setProperty(proxy.id, "text", "" + value)
|
|
63
61
|
return proxy
|
|
64
62
|
},
|
|
@@ -68,7 +66,7 @@ export let {
|
|
|
68
66
|
ffi.setProperty(node.id, "text", "" + value)
|
|
69
67
|
},
|
|
70
68
|
|
|
71
|
-
isTextNode: (node: ProxyNode): boolean => node?.elementType === "span",
|
|
69
|
+
isTextNode: (node: ProxyNode): boolean => node?.elementType === "d-span",
|
|
72
70
|
setProperty: <T>(node: ProxyNode, name: string, value: T): void => {
|
|
73
71
|
if (!node) return
|
|
74
72
|
|
package/src/types.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/// <reference types="@solidrt/flux-types" />
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { JSX as SolidJSX } from "@solidjs/signals"
|
|
4
4
|
|
|
5
5
|
declare global {
|
|
6
|
-
|
|
7
6
|
let ffi: {
|
|
8
7
|
createRoot(id: number): void
|
|
9
8
|
createNode(id: number, kind: string): void
|
|
@@ -12,6 +11,7 @@ declare global {
|
|
|
12
11
|
setProperty(nodeId: number, name: string, value: unknown): void
|
|
13
12
|
setTextInputActive(active: boolean): void
|
|
14
13
|
measureText(text: string, options?: MeasureTextOptions): { width: number, height: number }
|
|
14
|
+
getBoundingBox(id: number): { x: number, y: number, width: number, height: number } | null
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
let gpu: {
|
|
@@ -30,8 +30,6 @@ export interface MeasureTextOptions {
|
|
|
30
30
|
|
|
31
31
|
type Children = SolidJSX.Element
|
|
32
32
|
|
|
33
|
-
type OA<T> = T | Accessor<T>
|
|
34
|
-
|
|
35
33
|
interface FlexboxProps {
|
|
36
34
|
gap?: number
|
|
37
35
|
rowGap?: number
|
|
@@ -39,7 +37,7 @@ interface FlexboxProps {
|
|
|
39
37
|
flex?: number | "none" | "auto" | (string & {})
|
|
40
38
|
flexGrow?: number
|
|
41
39
|
flexShrink?: number
|
|
42
|
-
flexBasis?:
|
|
40
|
+
flexBasis?: Dimension
|
|
43
41
|
|
|
44
42
|
flexDirection?: "row" | "column" | "row-reverse" | "column-reverse"
|
|
45
43
|
flexWrap?: "nowrap" | "wrap" | "wrap-reverse"
|
|
@@ -96,8 +94,8 @@ export interface LayoutProps extends FlexboxProps, GridProps {
|
|
|
96
94
|
overflowY?: "visible" | "clip" | "hidden" | "scroll"
|
|
97
95
|
}
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
export type Color = string
|
|
97
|
+
// Colors are CSS color strings, parsed to a packed u32 by parseColorToU32.
|
|
98
|
+
export type Color = string
|
|
101
99
|
|
|
102
100
|
export interface PaintProps {
|
|
103
101
|
color?: Color
|
|
@@ -110,28 +108,48 @@ export interface PaintProps {
|
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
export interface TransformProps {
|
|
113
|
-
rotate?:
|
|
114
|
-
scale?:
|
|
115
|
-
x?:
|
|
116
|
-
y?:
|
|
117
|
-
cx?:
|
|
118
|
-
cy?:
|
|
119
|
-
scrollX?:
|
|
120
|
-
scrollY?:
|
|
111
|
+
rotate?: number
|
|
112
|
+
scale?: number
|
|
113
|
+
x?: number
|
|
114
|
+
y?: number
|
|
115
|
+
cx?: number
|
|
116
|
+
cy?: number
|
|
117
|
+
scrollX?: number
|
|
118
|
+
scrollY?: number
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface PointerEvent {
|
|
122
|
+
x: number
|
|
123
|
+
y: number
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface WheelEvent {
|
|
127
|
+
x: number
|
|
128
|
+
y: number
|
|
129
|
+
deltaX: number
|
|
130
|
+
deltaY: number
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface KeyEvent {
|
|
134
|
+
key: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface TextEvent {
|
|
138
|
+
text: string
|
|
121
139
|
}
|
|
122
140
|
|
|
123
141
|
export interface PointerProps {
|
|
124
|
-
onPointerDown?:
|
|
125
|
-
onPointerUp?:
|
|
126
|
-
onPointerMove?:
|
|
127
|
-
onPointerEnter?:
|
|
128
|
-
onPointerLeave?:
|
|
129
|
-
onWheel?:
|
|
130
|
-
onFocus?:
|
|
131
|
-
onBlur?:
|
|
132
|
-
onKeyDown?:
|
|
133
|
-
onKeyUp?:
|
|
134
|
-
onTextInput?:
|
|
142
|
+
onPointerDown?: (event: PointerEvent) => void
|
|
143
|
+
onPointerUp?: (event: PointerEvent) => void
|
|
144
|
+
onPointerMove?: (event: PointerEvent) => void
|
|
145
|
+
onPointerEnter?: (event: PointerEvent) => void
|
|
146
|
+
onPointerLeave?: (event: PointerEvent) => void
|
|
147
|
+
onWheel?: (event: WheelEvent) => void
|
|
148
|
+
onFocus?: () => void
|
|
149
|
+
onBlur?: () => void
|
|
150
|
+
onKeyDown?: (event: KeyEvent) => void
|
|
151
|
+
onKeyUp?: (event: KeyEvent) => void
|
|
152
|
+
onTextInput?: (event: TextEvent) => void
|
|
135
153
|
pointerEvents?: "auto" | "none" | "all"
|
|
136
154
|
}
|
|
137
155
|
|
|
@@ -175,12 +193,21 @@ export interface OvalProps extends Position, PaintProps, PointerProps {
|
|
|
175
193
|
h?: number
|
|
176
194
|
}
|
|
177
195
|
|
|
196
|
+
export interface LineProps extends PaintProps, PointerProps {
|
|
197
|
+
x1?: number
|
|
198
|
+
y1?: number
|
|
199
|
+
x2?: number
|
|
200
|
+
y2?: number
|
|
201
|
+
onLength?: number
|
|
202
|
+
offLength?: number
|
|
203
|
+
}
|
|
204
|
+
|
|
178
205
|
export interface PathProps extends Position, PaintProps, PointerProps {
|
|
179
206
|
d?: string
|
|
180
207
|
fillRule?: "nonZero" | "evenOdd"
|
|
181
208
|
}
|
|
182
209
|
|
|
183
|
-
export interface TextProps extends PaintProps {
|
|
210
|
+
export interface TextProps extends PaintProps, PointerProps {
|
|
184
211
|
children?: Children
|
|
185
212
|
fontFamily?: "sans" | "mono" | (string & {})
|
|
186
213
|
fontSize?: number
|
package/src/window.ts
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
import { onCleanup, onSettled } from "@solidjs/signals"
|
|
2
|
-
import { getEventHandler } from "./
|
|
3
|
-
import { getFocusedNodeId, setFocus } from "./focus"
|
|
1
|
+
import { onCleanup, onSettled, flush, createSignal } from "@solidjs/signals"
|
|
2
|
+
import { getEventHandler, getFocusedNodeId, setFocus } from "./core"
|
|
4
3
|
|
|
5
4
|
// ------ Animation frames ----------------
|
|
6
5
|
|
|
7
6
|
let nextFrameId = 1
|
|
8
7
|
let animationFrames = new Map<number, Function>()
|
|
9
8
|
|
|
9
|
+
// Latest display refresh rate (Hz) reported by the runtime, passed to onFrame
|
|
10
|
+
// callbacks. Defaults to 60 until the first displayRefreshRate event arrives.
|
|
11
|
+
let refreshRate = 60
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
|
-
* Calls `fn`
|
|
12
|
-
*
|
|
14
|
+
* Calls `fn` before every frame is painted, with the raw runtime signals: `tick`
|
|
15
|
+
* is the unsmoothed wall-clock time in ms sampled at present, `frame` is the
|
|
16
|
+
* present count, and `rate` is the current refresh rate in Hz. No pacing is
|
|
17
|
+
* applied; see createPacedClock for an opt-in smooth clock.
|
|
18
|
+
* Returns a cleanup function; also auto-cleans within a reactive scope.
|
|
13
19
|
*/
|
|
14
|
-
export function
|
|
20
|
+
export function onFrame(fn: (tick: number, frame: number, rate: number) => void) {
|
|
15
21
|
let frameId: number = null!
|
|
16
22
|
|
|
17
|
-
let extendedFn = (tick: number, frame: number) => {
|
|
18
|
-
fn(tick, frame)
|
|
23
|
+
let extendedFn = (tick: number, frame: number, rate: number) => {
|
|
24
|
+
fn(tick, frame, rate)
|
|
19
25
|
frameId = nextFrameId++
|
|
20
26
|
animationFrames.set(frameId, extendedFn)
|
|
21
27
|
}
|
|
@@ -28,6 +34,26 @@ export function onRender(fn: (tick: number, frame: number) => void) {
|
|
|
28
34
|
return cleanup
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Opt-in smooth clock built on the raw onFrame signals. Paces by present count
|
|
39
|
+
* (one refresh period per frame) and slowly corrects toward the raw wall-clock
|
|
40
|
+
* tick, so it stays smooth while keeping up and tracks real time when the
|
|
41
|
+
* framerate drops. `gain` (0..1) trades convergence speed for jitter. Returns an
|
|
42
|
+
* accessor for the paced time in milliseconds.
|
|
43
|
+
*/
|
|
44
|
+
export function createPacedClock(opts?: { gain?: number }) {
|
|
45
|
+
let gain = opts?.gain ?? 0.05
|
|
46
|
+
let [time, setTime] = createSignal(0)
|
|
47
|
+
let clock = 0
|
|
48
|
+
onFrame((tick, _frame, rate) => {
|
|
49
|
+
let period = 1000 / rate
|
|
50
|
+
clock += period
|
|
51
|
+
clock += (tick - clock) * gain
|
|
52
|
+
setTime(clock)
|
|
53
|
+
})
|
|
54
|
+
return time
|
|
55
|
+
}
|
|
56
|
+
|
|
31
57
|
// ------ Resize ----------------
|
|
32
58
|
|
|
33
59
|
interface SafeArea {
|
|
@@ -86,57 +112,78 @@ export function attachWindow(_nodeId: number) {
|
|
|
86
112
|
let unsubKeyUp: () => void = null!
|
|
87
113
|
let unsubTextInput: () => void = null!
|
|
88
114
|
let unsubKeyboardVisibility: () => void = null!
|
|
115
|
+
let unsubRefreshRate: () => void = null!
|
|
116
|
+
let unsubFirstResize: (() => void) | null = null
|
|
117
|
+
|
|
118
|
+
function runFrame(t: number, frame: number) {
|
|
119
|
+
if (animationFrames.size > 0) {
|
|
120
|
+
let frames = animationFrames
|
|
121
|
+
animationFrames = new Map()
|
|
122
|
+
for (let fn of frames.values()) fn(t, frame, refreshRate)
|
|
123
|
+
}
|
|
124
|
+
flush()
|
|
125
|
+
draw()
|
|
126
|
+
}
|
|
89
127
|
|
|
90
128
|
onSettled(() => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
animationFrames = new Map()
|
|
95
|
-
|
|
96
|
-
let t = (time * 1000) | 0
|
|
97
|
-
for (let fn of frames.values()) fn(t, frame)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
draw()
|
|
129
|
+
// Sticky event: a late subscriber still receives the current rate.
|
|
130
|
+
unsubRefreshRate = Flux.on("displayRefreshRate", ({ hz }: { hz: number }) => {
|
|
131
|
+
if (hz > 0) refreshRate = hz
|
|
101
132
|
})
|
|
102
133
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
getEventHandler(nodeId, "onPointerDown")?.(e)
|
|
106
|
-
}
|
|
107
|
-
// Outside-tap blur. Read focus AFTER per-node handlers so a tap that
|
|
108
|
-
// moves focus to a new node is not immediately blurred again.
|
|
109
|
-
let focused = getFocusedNodeId()
|
|
110
|
-
if (focused != null && !targets.includes(focused)) {
|
|
111
|
-
setFocus(null)
|
|
112
|
-
}
|
|
134
|
+
unsubscribe = Flux.on("render", ({ time, frame }: { time: number; frame: number }) => {
|
|
135
|
+
runFrame(time * 1000, frame)
|
|
113
136
|
})
|
|
114
137
|
|
|
115
|
-
|
|
138
|
+
unsubDown = Flux.on(
|
|
139
|
+
"pointerDown",
|
|
140
|
+
({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
141
|
+
for (let nodeId of targets) {
|
|
142
|
+
getEventHandler(nodeId, "onPointerDown")?.(e)
|
|
143
|
+
}
|
|
144
|
+
// Outside-tap blur. Read focus AFTER per-node handlers so a tap that
|
|
145
|
+
// moves focus to a new node is not immediately blurred again.
|
|
146
|
+
let focused = getFocusedNodeId()
|
|
147
|
+
if (focused != null && !targets.includes(focused)) {
|
|
148
|
+
setFocus(null)
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
unsubUp = Flux.on("pointerUp", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
116
154
|
for (let nodeId of targets) {
|
|
117
155
|
getEventHandler(nodeId, "onPointerUp")?.(e)
|
|
118
156
|
}
|
|
119
157
|
})
|
|
120
158
|
|
|
121
|
-
unsubMove = Flux.on(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
159
|
+
unsubMove = Flux.on(
|
|
160
|
+
"pointerMove",
|
|
161
|
+
({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
162
|
+
for (let nodeId of targets) {
|
|
163
|
+
getEventHandler(nodeId, "onPointerMove")?.(e)
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
unsubEnter = Flux.on(
|
|
169
|
+
"pointerEnter",
|
|
170
|
+
({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
171
|
+
for (let nodeId of targets) {
|
|
172
|
+
getEventHandler(nodeId, "onPointerEnter")?.(e)
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
unsubLeave = Flux.on(
|
|
178
|
+
"pointerLeave",
|
|
179
|
+
({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
180
|
+
for (let nodeId of targets) {
|
|
181
|
+
getEventHandler(nodeId, "onPointerLeave")?.(e)
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
unsubWheel = Flux.on("wheel", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
140
187
|
for (let nodeId of targets) {
|
|
141
188
|
getEventHandler(nodeId, "onWheel")?.(e)
|
|
142
189
|
}
|
|
@@ -169,8 +216,16 @@ export function attachWindow(_nodeId: number) {
|
|
|
169
216
|
if (!shown) setFocus(null)
|
|
170
217
|
})
|
|
171
218
|
|
|
172
|
-
//
|
|
173
|
-
|
|
219
|
+
// Bootstrap the first frame on the first resize event: by then any
|
|
220
|
+
// onResize subscribers (which run earlier in the dispatch list) have
|
|
221
|
+
// set their initial signal values, so runFrame's flush sees a fully
|
|
222
|
+
// initialized graph. Resize is a sticky event in Flux, so it can replay
|
|
223
|
+
// synchronously here, while we are still inside this onSettled callback
|
|
224
|
+
// where flush() is illegal (not reentrant). Defer runFrame to a microtask
|
|
225
|
+
// so the first frame always runs after this callback returns.
|
|
226
|
+
unsubFirstResize = Flux.once("resize", () => {
|
|
227
|
+
queueMicrotask(() => runFrame(0, 0))
|
|
228
|
+
})
|
|
174
229
|
})
|
|
175
230
|
|
|
176
231
|
onCleanup(() => {
|
|
@@ -185,5 +240,7 @@ export function attachWindow(_nodeId: number) {
|
|
|
185
240
|
if (unsubKeyUp) unsubKeyUp()
|
|
186
241
|
if (unsubTextInput) unsubTextInput()
|
|
187
242
|
if (unsubKeyboardVisibility) unsubKeyboardVisibility()
|
|
243
|
+
if (unsubRefreshRate) unsubRefreshRate()
|
|
244
|
+
if (unsubFirstResize) unsubFirstResize()
|
|
188
245
|
})
|
|
189
246
|
}
|
package/src/color.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { colord, extend } from "colord"
|
|
2
|
-
import namesPlugin from "colord/plugins/names"
|
|
3
|
-
extend([namesPlugin])
|
|
4
|
-
|
|
5
|
-
// Parses any CSS color string and returns a packed u32: 0xRRGGBBAA
|
|
6
|
-
export function parseColorToU32(color: string): number {
|
|
7
|
-
let { r, g, b, a } = colord(color).toRgb()
|
|
8
|
-
return (((r & 0xFF) << 24) | ((g & 0xFF) << 16) | ((b & 0xFF) << 8) | ((a * 255) & 0xFF)) >>> 0
|
|
9
|
-
}
|
package/src/events.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
let handlers = new Map<number, Map<string, Function>>()
|
|
2
|
-
|
|
3
|
-
export function setEventHandler(nodeId: number, name: string, fn: Function | null | undefined): void {
|
|
4
|
-
if (fn == null) {
|
|
5
|
-
handlers.get(nodeId)?.delete(name)
|
|
6
|
-
return
|
|
7
|
-
}
|
|
8
|
-
let nodeHandlers = handlers.get(nodeId)
|
|
9
|
-
if (!nodeHandlers) {
|
|
10
|
-
nodeHandlers = new Map()
|
|
11
|
-
handlers.set(nodeId, nodeHandlers)
|
|
12
|
-
}
|
|
13
|
-
nodeHandlers.set(name, fn)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function getEventHandler(nodeId: number, name: string): Function | undefined {
|
|
17
|
-
return handlers.get(nodeId)?.get(name)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function cleanupNodeHandlers(nodeId: number): void {
|
|
21
|
-
handlers.delete(nodeId)
|
|
22
|
-
}
|
package/src/focus.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { getEventHandler } from "./events"
|
|
2
|
-
|
|
3
|
-
// Currently-focused node id. Reset to null automatically across engine
|
|
4
|
-
// reloads because the JS environment is rebuilt from scratch.
|
|
5
|
-
let focusedNodeId: number | null = null
|
|
6
|
-
let textInputActive = false
|
|
7
|
-
|
|
8
|
-
export function setFocus(nodeId: number | null): void {
|
|
9
|
-
if (nodeId === focusedNodeId) return
|
|
10
|
-
let oldId = focusedNodeId
|
|
11
|
-
focusedNodeId = nodeId
|
|
12
|
-
if (oldId != null) {
|
|
13
|
-
getEventHandler(oldId, "onBlur")?.()
|
|
14
|
-
}
|
|
15
|
-
if (nodeId != null) {
|
|
16
|
-
getEventHandler(nodeId, "onFocus")?.()
|
|
17
|
-
}
|
|
18
|
-
let wantActive = nodeId != null && getEventHandler(nodeId, "onTextInput") != null
|
|
19
|
-
if (wantActive !== textInputActive) {
|
|
20
|
-
textInputActive = wantActive
|
|
21
|
-
ffi.setTextInputActive(wantActive)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function getFocusedNodeId(): number | null {
|
|
26
|
-
return focusedNodeId
|
|
27
|
-
}
|