@solidrt/core 0.0.5 → 0.0.6
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 +53 -26
- package/src/window.ts +74 -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.6",
|
|
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.6"
|
|
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, 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,6 +193,15 @@ 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"
|
package/src/window.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { onCleanup, onSettled } from "@solidjs/signals"
|
|
2
|
-
import { getEventHandler } from "./
|
|
3
|
-
import { getFocusedNodeId, setFocus } from "./focus"
|
|
1
|
+
import { onCleanup, onSettled, flush } from "@solidjs/signals"
|
|
2
|
+
import { getEventHandler, getFocusedNodeId, setFocus } from "./core"
|
|
4
3
|
|
|
5
4
|
// ------ Animation frames ----------------
|
|
6
5
|
|
|
@@ -8,10 +7,11 @@ let nextFrameId = 1
|
|
|
8
7
|
let animationFrames = new Map<number, Function>()
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
|
-
* Calls `fn`
|
|
12
|
-
*
|
|
10
|
+
* Calls `fn` before every frame is painted. The first call receives tick=0 (game time).
|
|
11
|
+
* Returns a cleanup function to stop updates. When called within a reactive scope
|
|
12
|
+
* (e.g. a component or createEffect), cleanup is also automatic.
|
|
13
13
|
*/
|
|
14
|
-
export function
|
|
14
|
+
export function onFrame(fn: (tick: number, frame: number) => void) {
|
|
15
15
|
let frameId: number = null!
|
|
16
16
|
|
|
17
17
|
let extendedFn = (tick: number, frame: number) => {
|
|
@@ -86,57 +86,72 @@ export function attachWindow(_nodeId: number) {
|
|
|
86
86
|
let unsubKeyUp: () => void = null!
|
|
87
87
|
let unsubTextInput: () => void = null!
|
|
88
88
|
let unsubKeyboardVisibility: () => void = null!
|
|
89
|
+
let unsubFirstResize: (() => void) | null = null
|
|
90
|
+
|
|
91
|
+
function runFrame(t: number, frame: number) {
|
|
92
|
+
if (animationFrames.size > 0) {
|
|
93
|
+
let frames = animationFrames
|
|
94
|
+
animationFrames = new Map()
|
|
95
|
+
for (let fn of frames.values()) fn(t, frame)
|
|
96
|
+
}
|
|
97
|
+
flush()
|
|
98
|
+
draw()
|
|
99
|
+
}
|
|
89
100
|
|
|
90
101
|
onSettled(() => {
|
|
91
|
-
unsubscribe = Flux.on("render", ({ time, frame }: { time: number
|
|
92
|
-
|
|
93
|
-
let frames = animationFrames
|
|
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()
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
unsubDown = Flux.on("pointerDown", ({ targets, ...e }: { targets: number[], [k: string]: any }) => {
|
|
104
|
-
for (let nodeId of targets) {
|
|
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
|
-
}
|
|
102
|
+
unsubscribe = Flux.on("render", ({ time, frame }: { time: number; frame: number }) => {
|
|
103
|
+
runFrame((time * 1000) | 0, frame)
|
|
113
104
|
})
|
|
114
105
|
|
|
115
|
-
|
|
106
|
+
unsubDown = Flux.on(
|
|
107
|
+
"pointerDown",
|
|
108
|
+
({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
109
|
+
for (let nodeId of targets) {
|
|
110
|
+
getEventHandler(nodeId, "onPointerDown")?.(e)
|
|
111
|
+
}
|
|
112
|
+
// Outside-tap blur. Read focus AFTER per-node handlers so a tap that
|
|
113
|
+
// moves focus to a new node is not immediately blurred again.
|
|
114
|
+
let focused = getFocusedNodeId()
|
|
115
|
+
if (focused != null && !targets.includes(focused)) {
|
|
116
|
+
setFocus(null)
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
unsubUp = Flux.on("pointerUp", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
116
122
|
for (let nodeId of targets) {
|
|
117
123
|
getEventHandler(nodeId, "onPointerUp")?.(e)
|
|
118
124
|
}
|
|
119
125
|
})
|
|
120
126
|
|
|
121
|
-
unsubMove = Flux.on(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
unsubMove = Flux.on(
|
|
128
|
+
"pointerMove",
|
|
129
|
+
({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
130
|
+
for (let nodeId of targets) {
|
|
131
|
+
getEventHandler(nodeId, "onPointerMove")?.(e)
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
unsubEnter = Flux.on(
|
|
137
|
+
"pointerEnter",
|
|
138
|
+
({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
139
|
+
for (let nodeId of targets) {
|
|
140
|
+
getEventHandler(nodeId, "onPointerEnter")?.(e)
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
unsubLeave = Flux.on(
|
|
146
|
+
"pointerLeave",
|
|
147
|
+
({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
148
|
+
for (let nodeId of targets) {
|
|
149
|
+
getEventHandler(nodeId, "onPointerLeave")?.(e)
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
unsubWheel = Flux.on("wheel", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
140
155
|
for (let nodeId of targets) {
|
|
141
156
|
getEventHandler(nodeId, "onWheel")?.(e)
|
|
142
157
|
}
|
|
@@ -169,8 +184,16 @@ export function attachWindow(_nodeId: number) {
|
|
|
169
184
|
if (!shown) setFocus(null)
|
|
170
185
|
})
|
|
171
186
|
|
|
172
|
-
//
|
|
173
|
-
|
|
187
|
+
// Bootstrap the first frame on the first resize event: by then any
|
|
188
|
+
// onResize subscribers (which run earlier in the dispatch list) have
|
|
189
|
+
// set their initial signal values, so runFrame's flush sees a fully
|
|
190
|
+
// initialized graph. Resize is a sticky event in Flux, so it can replay
|
|
191
|
+
// synchronously here, while we are still inside this onSettled callback
|
|
192
|
+
// where flush() is illegal (not reentrant). Defer runFrame to a microtask
|
|
193
|
+
// so the first frame always runs after this callback returns.
|
|
194
|
+
unsubFirstResize = Flux.once("resize", () => {
|
|
195
|
+
queueMicrotask(() => runFrame(0, 0))
|
|
196
|
+
})
|
|
174
197
|
})
|
|
175
198
|
|
|
176
199
|
onCleanup(() => {
|
|
@@ -185,5 +208,6 @@ export function attachWindow(_nodeId: number) {
|
|
|
185
208
|
if (unsubKeyUp) unsubKeyUp()
|
|
186
209
|
if (unsubTextInput) unsubTextInput()
|
|
187
210
|
if (unsubKeyboardVisibility) unsubKeyboardVisibility()
|
|
211
|
+
if (unsubFirstResize) unsubFirstResize()
|
|
188
212
|
})
|
|
189
213
|
}
|
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
|
-
}
|