@solidrt/core 0.0.6 → 0.0.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "license": "MIT",
5
5
  "author": "Antoine van Wel",
6
6
  "type": "module",
@@ -20,7 +20,7 @@
20
20
  "colord": "^2.9.3"
21
21
  },
22
22
  "devDependencies": {
23
- "@solidrt/flux-types": "0.0.6"
23
+ "@solidrt/flux-types": "0.0.8"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "@solidjs/signals": "2.0.0-beta.14",
package/src/types.d.ts CHANGED
@@ -3,6 +3,15 @@
3
3
  import type { JSX as SolidJSX } from "@solidjs/signals"
4
4
 
5
5
  declare global {
6
+ function requestAnimationFrame(callback: (time: number) => void): number
7
+ function cancelAnimationFrame(id: number): void
8
+
9
+ // UI event bus (lattice). on/once return an unsubscribe function.
10
+ let srt: {
11
+ on(event: string, callback: (data: any) => void): () => void
12
+ once(event: string, callback: (data: any) => void): () => void
13
+ }
14
+
6
15
  let ffi: {
7
16
  createRoot(id: number): void
8
17
  createNode(id: number, kind: string): void
@@ -207,7 +216,7 @@ export interface PathProps extends Position, PaintProps, PointerProps {
207
216
  fillRule?: "nonZero" | "evenOdd"
208
217
  }
209
218
 
210
- export interface TextProps extends PaintProps {
219
+ export interface TextProps extends PaintProps, PointerProps {
211
220
  children?: Children
212
221
  fontFamily?: "sans" | "mono" | (string & {})
213
222
  fontSize?: number
package/src/window.ts CHANGED
@@ -6,16 +6,24 @@ import { getEventHandler, getFocusedNodeId, setFocus } from "./core"
6
6
  let nextFrameId = 1
7
7
  let animationFrames = new Map<number, Function>()
8
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
+
9
13
  /**
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.
14
+ * Calls `fn` before every frame is painted: `tick` is the time in ms, `frame` is
15
+ * the present count, and `rate` is the current refresh rate in Hz. `tick` is paced
16
+ * by the runtime (one refresh period per present, slow-corrected toward the wall
17
+ * clock) so animations driven off it stay smooth even when swap-return times
18
+ * jitter. For raw, continuous wall-clock time (e.g. measuring code), use
19
+ * performance.now() instead.
20
+ * Returns a cleanup function; also auto-cleans within a reactive scope.
13
21
  */
14
- export function onFrame(fn: (tick: number, frame: number) => void) {
22
+ export function onFrame(fn: (tick: number, frame: number, rate: number) => void) {
15
23
  let frameId: number = null!
16
24
 
17
- let extendedFn = (tick: number, frame: number) => {
18
- fn(tick, frame)
25
+ let extendedFn = (tick: number, frame: number, rate: number) => {
26
+ fn(tick, frame, rate)
19
27
  frameId = nextFrameId++
20
28
  animationFrames.set(frameId, extendedFn)
21
29
  }
@@ -45,7 +53,7 @@ interface ResizeEvent {
45
53
  }
46
54
 
47
55
  export function onResize(fn: (data: ResizeEvent) => void) {
48
- let unsubscribe = Flux.on("resize", fn)
56
+ let unsubscribe = srt.on("resize", fn)
49
57
  onCleanup(unsubscribe)
50
58
  return unsubscribe
51
59
  }
@@ -55,19 +63,19 @@ export function onResize(fn: (data: ResizeEvent) => void) {
55
63
  // by a re-layout pass before painting (one extra pass; cascades beyond that
56
64
  // paint stale).
57
65
  export function onLayout(fn: () => void) {
58
- let unsubscribe = Flux.on("postLayout", fn)
66
+ let unsubscribe = srt.on("postLayout", fn)
59
67
  onCleanup(unsubscribe)
60
68
  return unsubscribe
61
69
  }
62
70
 
63
71
  export function onWindowFocus(fn: () => void) {
64
- let unsubscribe = Flux.on("windowFocus", fn)
72
+ let unsubscribe = srt.on("windowFocus", fn)
65
73
  onCleanup(unsubscribe)
66
74
  return unsubscribe
67
75
  }
68
76
 
69
77
  export function onWindowBlur(fn: () => void) {
70
- let unsubscribe = Flux.on("windowBlur", fn)
78
+ let unsubscribe = srt.on("windowBlur", fn)
71
79
  onCleanup(unsubscribe)
72
80
  return unsubscribe
73
81
  }
@@ -86,24 +94,30 @@ export function attachWindow(_nodeId: number) {
86
94
  let unsubKeyUp: () => void = null!
87
95
  let unsubTextInput: () => void = null!
88
96
  let unsubKeyboardVisibility: () => void = null!
97
+ let unsubRefreshRate: () => void = null!
89
98
  let unsubFirstResize: (() => void) | null = null
90
99
 
91
100
  function runFrame(t: number, frame: number) {
92
101
  if (animationFrames.size > 0) {
93
102
  let frames = animationFrames
94
103
  animationFrames = new Map()
95
- for (let fn of frames.values()) fn(t, frame)
104
+ for (let fn of frames.values()) fn(t, frame, refreshRate)
96
105
  }
97
106
  flush()
98
107
  draw()
99
108
  }
100
109
 
101
110
  onSettled(() => {
102
- unsubscribe = Flux.on("render", ({ time, frame }: { time: number; frame: number }) => {
103
- runFrame((time * 1000) | 0, frame)
111
+ // Sticky event: a late subscriber still receives the current rate.
112
+ unsubRefreshRate = srt.on("displayRefreshRate", ({ hz }: { hz: number }) => {
113
+ if (hz > 0) refreshRate = hz
114
+ })
115
+
116
+ unsubscribe = srt.on("render", ({ time, frame }: { time: number; frame: number }) => {
117
+ runFrame(time * 1000, frame)
104
118
  })
105
119
 
106
- unsubDown = Flux.on(
120
+ unsubDown = srt.on(
107
121
  "pointerDown",
108
122
  ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
109
123
  for (let nodeId of targets) {
@@ -118,13 +132,13 @@ export function attachWindow(_nodeId: number) {
118
132
  },
119
133
  )
120
134
 
121
- unsubUp = Flux.on("pointerUp", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
135
+ unsubUp = srt.on("pointerUp", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
122
136
  for (let nodeId of targets) {
123
137
  getEventHandler(nodeId, "onPointerUp")?.(e)
124
138
  }
125
139
  })
126
140
 
127
- unsubMove = Flux.on(
141
+ unsubMove = srt.on(
128
142
  "pointerMove",
129
143
  ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
130
144
  for (let nodeId of targets) {
@@ -133,7 +147,7 @@ export function attachWindow(_nodeId: number) {
133
147
  },
134
148
  )
135
149
 
136
- unsubEnter = Flux.on(
150
+ unsubEnter = srt.on(
137
151
  "pointerEnter",
138
152
  ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
139
153
  for (let nodeId of targets) {
@@ -142,7 +156,7 @@ export function attachWindow(_nodeId: number) {
142
156
  },
143
157
  )
144
158
 
145
- unsubLeave = Flux.on(
159
+ unsubLeave = srt.on(
146
160
  "pointerLeave",
147
161
  ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
148
162
  for (let nodeId of targets) {
@@ -151,27 +165,27 @@ export function attachWindow(_nodeId: number) {
151
165
  },
152
166
  )
153
167
 
154
- unsubWheel = Flux.on("wheel", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
168
+ unsubWheel = srt.on("wheel", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
155
169
  for (let nodeId of targets) {
156
170
  getEventHandler(nodeId, "onWheel")?.(e)
157
171
  }
158
172
  })
159
173
 
160
- unsubKeyDown = Flux.on("keydown", (e: any) => {
174
+ unsubKeyDown = srt.on("keydown", (e: any) => {
161
175
  let id = getFocusedNodeId()
162
176
  if (id != null) {
163
177
  getEventHandler(id, "onKeyDown")?.(e)
164
178
  }
165
179
  })
166
180
 
167
- unsubKeyUp = Flux.on("keyup", (e: any) => {
181
+ unsubKeyUp = srt.on("keyup", (e: any) => {
168
182
  let id = getFocusedNodeId()
169
183
  if (id != null) {
170
184
  getEventHandler(id, "onKeyUp")?.(e)
171
185
  }
172
186
  })
173
187
 
174
- unsubTextInput = Flux.on("textInput", (e: any) => {
188
+ unsubTextInput = srt.on("textInput", (e: any) => {
175
189
  let id = getFocusedNodeId()
176
190
  if (id != null) {
177
191
  getEventHandler(id, "onTextInput")?.(e)
@@ -180,7 +194,7 @@ export function attachWindow(_nodeId: number) {
180
194
 
181
195
  // When the user dismisses the on-screen keyboard (swipe down, "Done",
182
196
  // back button), blur the focused node so the app's UI state catches up.
183
- unsubKeyboardVisibility = Flux.on("keyboardVisibility", ({ shown }: { shown: boolean }) => {
197
+ unsubKeyboardVisibility = srt.on("keyboardVisibility", ({ shown }: { shown: boolean }) => {
184
198
  if (!shown) setFocus(null)
185
199
  })
186
200
 
@@ -191,7 +205,7 @@ export function attachWindow(_nodeId: number) {
191
205
  // synchronously here, while we are still inside this onSettled callback
192
206
  // where flush() is illegal (not reentrant). Defer runFrame to a microtask
193
207
  // so the first frame always runs after this callback returns.
194
- unsubFirstResize = Flux.once("resize", () => {
208
+ unsubFirstResize = srt.once("resize", () => {
195
209
  queueMicrotask(() => runFrame(0, 0))
196
210
  })
197
211
  })
@@ -208,6 +222,7 @@ export function attachWindow(_nodeId: number) {
208
222
  if (unsubKeyUp) unsubKeyUp()
209
223
  if (unsubTextInput) unsubTextInput()
210
224
  if (unsubKeyboardVisibility) unsubKeyboardVisibility()
225
+ if (unsubRefreshRate) unsubRefreshRate()
211
226
  if (unsubFirstResize) unsubFirstResize()
212
227
  })
213
228
  }