@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.
@@ -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"
@@ -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
+ }