@solidrt/core 0.0.1-exp.1
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/jsx-runtime.d.ts +34 -0
- package/package.json +19 -0
- package/src/index.ts +1 -0
- package/src/renderer.ts +143 -0
- package/src/types.d.ts +172 -0
- package/src/window.ts +55 -0
package/jsx-runtime.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WindowProps,
|
|
3
|
+
CircleProps,
|
|
4
|
+
RectProps,
|
|
5
|
+
OvalProps,
|
|
6
|
+
PathProps,
|
|
7
|
+
ViewProps,
|
|
8
|
+
TextProps,
|
|
9
|
+
TextureProps,
|
|
10
|
+
AudioProps,
|
|
11
|
+
LayoutProps
|
|
12
|
+
} from "./src/types"
|
|
13
|
+
import type { JSX as SolidJSX } from "@solidjs/signals"
|
|
14
|
+
|
|
15
|
+
export namespace JSX {
|
|
16
|
+
type Element = SolidJSX.Element
|
|
17
|
+
type ElementChildrenAttribute = SolidJSX.ElementChildrenAttribute
|
|
18
|
+
|
|
19
|
+
interface IntrinsicElements {
|
|
20
|
+
window: WindowProps
|
|
21
|
+
view: ViewProps
|
|
22
|
+
text: TextProps & LayoutProps
|
|
23
|
+
rect: RectProps & LayoutProps
|
|
24
|
+
oval: OvalProps & LayoutProps
|
|
25
|
+
path: PathProps & LayoutProps
|
|
26
|
+
texture: TextureProps & LayoutProps
|
|
27
|
+
audio: AudioProps
|
|
28
|
+
"d-rect": RectProps
|
|
29
|
+
"d-oval": OvalProps
|
|
30
|
+
"d-path": PathProps
|
|
31
|
+
"d-texture": TextureProps
|
|
32
|
+
"d-text": TextProps
|
|
33
|
+
}
|
|
34
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@solidrt/core",
|
|
3
|
+
"version": "0.0.1-exp.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./jsx-runtime": "./jsx-runtime.d.ts",
|
|
9
|
+
"./jsx-runtime-dev": "./jsx-runtime.d.ts"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src/",
|
|
13
|
+
"jsx-runtime.d.ts"
|
|
14
|
+
],
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@solidjs/signals": "2.0.0-beta.7",
|
|
17
|
+
"@solidjs/universal": "2.0.0-beta.7"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./renderer"
|
package/src/renderer.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createRoot, createEffect } from "@solidjs/signals"
|
|
2
|
+
import { createRenderer } from "@solidjs/universal"
|
|
3
|
+
import { attachWindow } from "./window"
|
|
4
|
+
|
|
5
|
+
export let nodes = new Map()
|
|
6
|
+
|
|
7
|
+
// export { registerPropHandler, registerNodeCleanup } from "./hooks"
|
|
8
|
+
|
|
9
|
+
type ElementType = string
|
|
10
|
+
|
|
11
|
+
// ProxyNode: Lightweight proxy for Rust-side nodes
|
|
12
|
+
// Caches parent/child references to avoid FFI calls for tree queries
|
|
13
|
+
interface ProxyNode {
|
|
14
|
+
readonly id: number
|
|
15
|
+
readonly elementType: ElementType
|
|
16
|
+
parent?: ProxyNode
|
|
17
|
+
children: ProxyNode[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let id = 1
|
|
21
|
+
function createProxyNode(elementType: ElementType): ProxyNode {
|
|
22
|
+
let node = { id, elementType, children: [] }
|
|
23
|
+
nodes.set(id, node)
|
|
24
|
+
id += 1
|
|
25
|
+
return node
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export let {
|
|
29
|
+
effect,
|
|
30
|
+
memo,
|
|
31
|
+
createComponent,
|
|
32
|
+
createElement,
|
|
33
|
+
createTextNode,
|
|
34
|
+
insertNode,
|
|
35
|
+
insert,
|
|
36
|
+
spread,
|
|
37
|
+
setProp,
|
|
38
|
+
mergeProps,
|
|
39
|
+
applyRef,
|
|
40
|
+
ref,
|
|
41
|
+
} = createRenderer<ProxyNode>({
|
|
42
|
+
createElement: (elementType: string): ProxyNode => {
|
|
43
|
+
let proxy = createProxyNode(elementType)
|
|
44
|
+
|
|
45
|
+
console.log("createElement", proxy.id, elementType)
|
|
46
|
+
|
|
47
|
+
if (elementType === "window") ffi.createRoot(proxy.id)
|
|
48
|
+
else ffi.createNode(proxy.id, elementType)
|
|
49
|
+
|
|
50
|
+
return proxy
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
createTextNode: (value: string): ProxyNode => {
|
|
54
|
+
let proxy = createProxyNode("span")
|
|
55
|
+
console.log("createTextNode", proxy.id, value)
|
|
56
|
+
ffi.createNode(proxy.id, "span")
|
|
57
|
+
ffi.setProperty(proxy.id, "text", "" + value)
|
|
58
|
+
return proxy
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
replaceText: (node: ProxyNode, value: string): void => {
|
|
62
|
+
console.log("replaceText", node.id, value)
|
|
63
|
+
ffi.setProperty(node.id, "text", "" + value)
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
isTextNode: (node: ProxyNode): boolean => node?.elementType === "span",
|
|
67
|
+
setProperty: <T>(node: ProxyNode, name: string, value: T, prev?: T): void => {
|
|
68
|
+
if (!node) return
|
|
69
|
+
|
|
70
|
+
console.log("setProperty", node.id, name, value)
|
|
71
|
+
// if (runPropHandlers(node.id, name, value, prev)) return
|
|
72
|
+
|
|
73
|
+
ffi.setProperty(node.id, name, value)
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
insertNode: (parent: ProxyNode, node: ProxyNode, anchor?: ProxyNode): void => {
|
|
77
|
+
if (!node) return
|
|
78
|
+
|
|
79
|
+
if (parent) {
|
|
80
|
+
node.parent = parent
|
|
81
|
+
|
|
82
|
+
if (!anchor) {
|
|
83
|
+
parent.children.push(node)
|
|
84
|
+
} else {
|
|
85
|
+
let index = parent.children.indexOf(anchor)
|
|
86
|
+
if (index === -1) {
|
|
87
|
+
parent.children.push(node)
|
|
88
|
+
} else {
|
|
89
|
+
parent.children.splice(index, 0, node)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log("insertNode", parent.id, node.id, anchor?.id ?? "")
|
|
94
|
+
|
|
95
|
+
if (anchor) ffi.insertNode(parent.id, node.id, anchor.id)
|
|
96
|
+
else ffi.insertNode(parent.id, node.id)
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
removeNode: (parent: ProxyNode, node: ProxyNode): void => {
|
|
101
|
+
if (!node || !parent) return
|
|
102
|
+
|
|
103
|
+
console.log("removeNode", parent.id, node.id)
|
|
104
|
+
|
|
105
|
+
// Update JS tree references
|
|
106
|
+
let index = parent.children.indexOf(node)
|
|
107
|
+
if (index !== -1) {
|
|
108
|
+
parent.children.splice(index, 1)
|
|
109
|
+
}
|
|
110
|
+
node.parent = undefined
|
|
111
|
+
|
|
112
|
+
ffi.deleteNode(parent.id, node.id)
|
|
113
|
+
|
|
114
|
+
// Recursively clean up node and all descendants
|
|
115
|
+
// let cleanup = (n: ProxyNode) => {
|
|
116
|
+
// for (let child of n.children) cleanup(child)
|
|
117
|
+
// nodes.delete(n.id)
|
|
118
|
+
// runNodeCleanup(n.id)
|
|
119
|
+
// }
|
|
120
|
+
// cleanup(node)
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
getParentNode: (node: ProxyNode) => node?.parent,
|
|
124
|
+
getFirstChild: (node: ProxyNode) => node?.children[0],
|
|
125
|
+
getNextSibling: (node: ProxyNode) => {
|
|
126
|
+
let parent = node?.parent
|
|
127
|
+
if (!parent) return undefined
|
|
128
|
+
let index = parent.children.indexOf(node)
|
|
129
|
+
if (index === -1) return undefined
|
|
130
|
+
return parent.children[index + 1]
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
export function render(code: () => any) {
|
|
135
|
+
createRoot(() => {
|
|
136
|
+
let root = code()
|
|
137
|
+
if (!root || root.elementType !== "window") {
|
|
138
|
+
throw new Error("render() root must be a <window> element")
|
|
139
|
+
}
|
|
140
|
+
attachWindow(root.id)
|
|
141
|
+
insert(null, root)
|
|
142
|
+
})
|
|
143
|
+
}
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { Accessor, JSX as SolidJSX } from "@solidjs/signals"
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
let ffi: {
|
|
5
|
+
createRoot(id: number): void
|
|
6
|
+
createNode(id: number, kind: string): void
|
|
7
|
+
insertNode(parentId: number, nodeId: number, anchorId?: number): void
|
|
8
|
+
deleteNode(parentId: number, nodeId: number): void
|
|
9
|
+
setProperty(nodeId: number, name: string, value: unknown): void
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Children = SolidJSX.Element
|
|
14
|
+
|
|
15
|
+
type OA<T> = T | Accessor<T>
|
|
16
|
+
|
|
17
|
+
interface FlexboxProps {
|
|
18
|
+
gap?: number
|
|
19
|
+
rowGap?: number
|
|
20
|
+
columnGap?: number
|
|
21
|
+
flexGrow?: number
|
|
22
|
+
flexShrink?: number
|
|
23
|
+
flexBasis?: number
|
|
24
|
+
|
|
25
|
+
flexDirection?: "row" | "column" | "row-reverse" | "column-reverse"
|
|
26
|
+
flexWrap?: "nowrap" | "wrap" | "wrap-reverse"
|
|
27
|
+
alignSelf?: "start" | "end" | "flex-start" | "flex-end" | "center" | "baseline" | "stretch"
|
|
28
|
+
alignItems?: "start" | "end" | "flex-start" | "flex-end" | "center" | "baseline" | "stretch"
|
|
29
|
+
alignContent?: "start" | "end" | "flex-start" | "flex-end" | "center" | "stretch" | "space-between" | "space-evenly" | "space-around"
|
|
30
|
+
justifyContent?: "start" | "end" | "flex-start" | "flex-end" | "center" | "stretch" | "space-between" | "space-evenly" | "space-around"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface GridProps {
|
|
34
|
+
gridAutoFlow?: "row" | "column" | "row-dense" | "column-dense"
|
|
35
|
+
gridAutoColumns?: number
|
|
36
|
+
gridAutoRows?: number
|
|
37
|
+
gridColumnStart?: number
|
|
38
|
+
gridColumnEnd?: number
|
|
39
|
+
gridRowStart?: number
|
|
40
|
+
gridRowEnd?: number
|
|
41
|
+
gridTemplateColumns?: string
|
|
42
|
+
gridTemplateRows?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type Dimension = number | "auto" | `${number}%`
|
|
46
|
+
|
|
47
|
+
export interface LayoutProps extends FlexboxProps, GridProps {
|
|
48
|
+
display?: "block" | "flex" | "grid" | "none"
|
|
49
|
+
position?: "relative" | "absolute"
|
|
50
|
+
|
|
51
|
+
top?: Dimension
|
|
52
|
+
right?: Dimension
|
|
53
|
+
bottom?: Dimension
|
|
54
|
+
left?: Dimension
|
|
55
|
+
|
|
56
|
+
width?: Dimension
|
|
57
|
+
height?: Dimension
|
|
58
|
+
minWidth?: Dimension
|
|
59
|
+
minHeight?: Dimension
|
|
60
|
+
maxWidth?: Dimension
|
|
61
|
+
maxHeight?: Dimension
|
|
62
|
+
|
|
63
|
+
padding?: Dimension
|
|
64
|
+
paddingTop?: Dimension
|
|
65
|
+
paddingRight?: Dimension
|
|
66
|
+
paddingBottom?: Dimension
|
|
67
|
+
paddingLeft?: Dimension
|
|
68
|
+
|
|
69
|
+
margin?: Dimension
|
|
70
|
+
marginTop?: Dimension
|
|
71
|
+
marginRight?: Dimension
|
|
72
|
+
marginBottom?: Dimension
|
|
73
|
+
marginLeft?: Dimension
|
|
74
|
+
|
|
75
|
+
overflow?: "visible" | "clip" | "hidden" | "scroll"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
import type { LCH } from "./color"
|
|
79
|
+
export type Color = string | LCH
|
|
80
|
+
|
|
81
|
+
export interface PaintProps {
|
|
82
|
+
color?: Color
|
|
83
|
+
blendMode?: "clear" | "source" | "destination" | "source-over" | "destination-over" | "source-in" | "destination-in" | "source-out" | "destination-out" | "source-atop" | "destination-atop" | "xor" | "plus" | "modulate" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "multiply" | "hue" | "saturation" | "color" | "luminosity"
|
|
84
|
+
drawStyle?: "fill" | "stroke" | "stroke-and-fill"
|
|
85
|
+
strokeCap?: "butt" | "round" | "square"
|
|
86
|
+
strokeJoin?: "miter" | "round" | "bevel"
|
|
87
|
+
strokeMiter?: number
|
|
88
|
+
strokeWidth?: number
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface TransformProps {
|
|
92
|
+
rotate?: OA<number>
|
|
93
|
+
scale?: OA<number>
|
|
94
|
+
x?: OA<number>
|
|
95
|
+
y?: OA<number>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface PointerProps {
|
|
99
|
+
onPointerDown?: Function
|
|
100
|
+
onPointerUp?: Function
|
|
101
|
+
onPointerMove?: Function
|
|
102
|
+
onPointerEnter?: Function
|
|
103
|
+
onPointerLeave?: Function
|
|
104
|
+
onWheel?: Function
|
|
105
|
+
onFocus?: Function
|
|
106
|
+
onBlur?: Function
|
|
107
|
+
onKeyDown?: Function
|
|
108
|
+
onKeyUp?: Function
|
|
109
|
+
onTextInput?: Function
|
|
110
|
+
pointerEvents?: "auto" | "none" | "all"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface Position {
|
|
114
|
+
x?: number
|
|
115
|
+
y?: number
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Primitives
|
|
119
|
+
|
|
120
|
+
export interface WindowProps extends LayoutProps {
|
|
121
|
+
children?: Children
|
|
122
|
+
title?: string
|
|
123
|
+
vsync?: boolean
|
|
124
|
+
fps?: boolean
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ViewProps extends LayoutProps, TransformProps, PointerProps {
|
|
128
|
+
children?: Children
|
|
129
|
+
trace?: boolean
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface AudioProps {
|
|
133
|
+
src?: Uint8Array
|
|
134
|
+
play?: number
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// draw primitives
|
|
138
|
+
|
|
139
|
+
export interface RectProps extends Position, PaintProps, PointerProps {
|
|
140
|
+
w?: number
|
|
141
|
+
h?: number
|
|
142
|
+
r?: number
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface OvalProps extends Position, PaintProps, PointerProps {
|
|
146
|
+
w?: number
|
|
147
|
+
h?: number
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface PathProps extends Position, PaintProps, PointerProps {
|
|
151
|
+
d?: string
|
|
152
|
+
fillRule?: "nonZero" | "evenOdd"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface TextProps extends PaintProps {
|
|
156
|
+
children: Children
|
|
157
|
+
fontSize?: number
|
|
158
|
+
fontStyle?: "normal" | "italic"
|
|
159
|
+
textAlign?: "left" | "right" | "center" | "justify"
|
|
160
|
+
maxLines?: number
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface TextureProps extends Position {
|
|
164
|
+
src?: number
|
|
165
|
+
imageWidth?: number
|
|
166
|
+
imageHeight?: number
|
|
167
|
+
srcX?: number
|
|
168
|
+
srcY?: number
|
|
169
|
+
srcW?: number
|
|
170
|
+
srcH?: number
|
|
171
|
+
params?: Record<string, number>
|
|
172
|
+
}
|
package/src/window.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { onCleanup, onSettled } from "@solidjs/signals"
|
|
2
|
+
|
|
3
|
+
// ------ Animation frames ----------------
|
|
4
|
+
|
|
5
|
+
let nextFrameId = 1
|
|
6
|
+
let animationFrames = new Map<number, Function>()
|
|
7
|
+
|
|
8
|
+
export let requestAnimationFrame = (fn: (tick: number) => void) => {
|
|
9
|
+
let id = nextFrameId++
|
|
10
|
+
animationFrames.set(id, fn)
|
|
11
|
+
return id
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export let cancelAnimationFrame = (id: number) => {
|
|
15
|
+
animationFrames.delete(id)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function onRender(fn: (tick: number) => void) {
|
|
19
|
+
let frameId: number = null!
|
|
20
|
+
|
|
21
|
+
let extendedFn = (tick: number) => {
|
|
22
|
+
fn(tick)
|
|
23
|
+
frameId = requestAnimationFrame(extendedFn)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
frameId = requestAnimationFrame(extendedFn)
|
|
27
|
+
onCleanup(() => cancelAnimationFrame(frameId))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ------ Window ----------------
|
|
31
|
+
|
|
32
|
+
export function attachWindow(_nodeId: number) {
|
|
33
|
+
let unsubscribe: () => void = null!
|
|
34
|
+
|
|
35
|
+
onSettled(() => {
|
|
36
|
+
unsubscribe = Flux.on("render", (time: number) => {
|
|
37
|
+
if (animationFrames.size > 0) {
|
|
38
|
+
let frames = animationFrames
|
|
39
|
+
animationFrames = new Map()
|
|
40
|
+
|
|
41
|
+
let t = (time * 1000) | 0
|
|
42
|
+
for (let fn of frames.values()) fn(t)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
draw()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// trigger first draw
|
|
49
|
+
draw()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
onCleanup(() => {
|
|
53
|
+
if (unsubscribe) unsubscribe()
|
|
54
|
+
})
|
|
55
|
+
}
|