onejs-react 0.1.0 → 0.1.2
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 +265 -0
- package/package.json +3 -1
- package/src/__tests__/components.test.tsx +36 -0
- package/src/__tests__/host-config.test.ts +141 -99
- package/src/__tests__/mocks.ts +17 -1
- package/src/__tests__/setup.ts +23 -11
- package/src/components.tsx +77 -48
- package/src/error-boundary.tsx +175 -0
- package/src/host-config.ts +503 -162
- package/src/index.ts +46 -2
- package/src/renderer.ts +50 -23
- package/src/screen.tsx +1 -1
- package/src/style-parser.ts +171 -79
- package/src/types.ts +326 -8
- package/src/vector.ts +312 -0
package/src/vector.ts
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector drawing utilities for OneJS.
|
|
3
|
+
*
|
|
4
|
+
* Provides Transform2D for applying 2D transformations to drawing coordinates,
|
|
5
|
+
* since Unity's Painter2D doesn't have built-in transform support.
|
|
6
|
+
*
|
|
7
|
+
* Also provides useVectorContent hook for automatic repaint on dependency changes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useRef, useEffect, useCallback, type DependencyList, type RefObject } from 'react'
|
|
11
|
+
import type { Vector2, VisualElement, MeshGenerationContext, GenerateVisualContentCallback } from './types'
|
|
12
|
+
|
|
13
|
+
// Global declarations for Unity interop
|
|
14
|
+
declare const CS: {
|
|
15
|
+
UnityEngine: {
|
|
16
|
+
Vector2: new (x: number, y: number) => Vector2;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 2D transformation helper for vector drawing.
|
|
22
|
+
*
|
|
23
|
+
* Unity's Painter2D doesn't support transforms (translate, rotate, scale).
|
|
24
|
+
* This class provides client-side matrix math to transform coordinates
|
|
25
|
+
* before passing them to Painter2D methods.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* import { Transform2D } from "onejs-react"
|
|
29
|
+
*
|
|
30
|
+
* <View
|
|
31
|
+
* style={{ width: 200, height: 200 }}
|
|
32
|
+
* onGenerateVisualContent={(mgc) => {
|
|
33
|
+
* const p = mgc.painter2D
|
|
34
|
+
* const t = new Transform2D()
|
|
35
|
+
*
|
|
36
|
+
* // Center and rotate 45 degrees
|
|
37
|
+
* t.translate(100, 100)
|
|
38
|
+
* t.rotate(Math.PI / 4)
|
|
39
|
+
*
|
|
40
|
+
* // Draw a square using transformed coordinates
|
|
41
|
+
* p.BeginPath()
|
|
42
|
+
* p.MoveTo(t.point(-40, -40))
|
|
43
|
+
* p.LineTo(t.point(40, -40))
|
|
44
|
+
* p.LineTo(t.point(40, 40))
|
|
45
|
+
* p.LineTo(t.point(-40, 40))
|
|
46
|
+
* p.ClosePath()
|
|
47
|
+
* p.Fill()
|
|
48
|
+
* }}
|
|
49
|
+
* />
|
|
50
|
+
*/
|
|
51
|
+
export class Transform2D {
|
|
52
|
+
// Current transformation matrix (a, b, c, d, e, f)
|
|
53
|
+
// [ a c e ] [ x ] [ a*x + c*y + e ]
|
|
54
|
+
// [ b d f ] * [ y ] = [ b*x + d*y + f ]
|
|
55
|
+
// [ 0 0 1 ] [ 1 ] [ 1 ]
|
|
56
|
+
private _a: number = 1
|
|
57
|
+
private _b: number = 0
|
|
58
|
+
private _c: number = 0
|
|
59
|
+
private _d: number = 1
|
|
60
|
+
private _e: number = 0
|
|
61
|
+
private _f: number = 0
|
|
62
|
+
|
|
63
|
+
// State stack for save/restore
|
|
64
|
+
private _stack: Array<[number, number, number, number, number, number]> = []
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Save the current transformation state to the stack.
|
|
68
|
+
* Use restore() to return to this state later.
|
|
69
|
+
*/
|
|
70
|
+
save(): void {
|
|
71
|
+
this._stack.push([this._a, this._b, this._c, this._d, this._e, this._f])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Restore the most recently saved transformation state.
|
|
76
|
+
* If the stack is empty, resets to identity.
|
|
77
|
+
*/
|
|
78
|
+
restore(): void {
|
|
79
|
+
const state = this._stack.pop()
|
|
80
|
+
if (state) {
|
|
81
|
+
[this._a, this._b, this._c, this._d, this._e, this._f] = state
|
|
82
|
+
} else {
|
|
83
|
+
this.reset()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reset the transformation to identity (no transformation).
|
|
89
|
+
*/
|
|
90
|
+
reset(): void {
|
|
91
|
+
this._a = 1
|
|
92
|
+
this._b = 0
|
|
93
|
+
this._c = 0
|
|
94
|
+
this._d = 1
|
|
95
|
+
this._e = 0
|
|
96
|
+
this._f = 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Apply a translation (move the origin).
|
|
101
|
+
* @param x - Horizontal translation
|
|
102
|
+
* @param y - Vertical translation
|
|
103
|
+
*/
|
|
104
|
+
translate(x: number, y: number): void {
|
|
105
|
+
// new_e = a*x + c*y + e
|
|
106
|
+
// new_f = b*x + d*y + f
|
|
107
|
+
this._e += this._a * x + this._c * y
|
|
108
|
+
this._f += this._b * x + this._d * y
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Apply a rotation around the current origin.
|
|
113
|
+
* @param angle - Rotation angle in radians (clockwise)
|
|
114
|
+
*/
|
|
115
|
+
rotate(angle: number): void {
|
|
116
|
+
const cos = Math.cos(angle)
|
|
117
|
+
const sin = Math.sin(angle)
|
|
118
|
+
|
|
119
|
+
// Multiply current matrix by rotation matrix
|
|
120
|
+
const a = this._a * cos + this._c * sin
|
|
121
|
+
const b = this._b * cos + this._d * sin
|
|
122
|
+
const c = this._a * -sin + this._c * cos
|
|
123
|
+
const d = this._b * -sin + this._d * cos
|
|
124
|
+
|
|
125
|
+
this._a = a
|
|
126
|
+
this._b = b
|
|
127
|
+
this._c = c
|
|
128
|
+
this._d = d
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Apply a scale transformation.
|
|
133
|
+
* @param x - Horizontal scale factor
|
|
134
|
+
* @param y - Vertical scale factor (defaults to x for uniform scale)
|
|
135
|
+
*/
|
|
136
|
+
scale(x: number, y?: number): void {
|
|
137
|
+
const sy = y ?? x
|
|
138
|
+
|
|
139
|
+
this._a *= x
|
|
140
|
+
this._b *= x
|
|
141
|
+
this._c *= sy
|
|
142
|
+
this._d *= sy
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Transform a point using the current transformation matrix.
|
|
147
|
+
* @param x - X coordinate in local space
|
|
148
|
+
* @param y - Y coordinate in local space
|
|
149
|
+
* @returns Transformed point as Unity Vector2
|
|
150
|
+
*/
|
|
151
|
+
point(x: number, y: number): Vector2 {
|
|
152
|
+
const tx = this._a * x + this._c * y + this._e
|
|
153
|
+
const ty = this._b * x + this._d * y + this._f
|
|
154
|
+
return new CS.UnityEngine.Vector2(tx, ty)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Transform multiple points at once.
|
|
159
|
+
* @param coords - Array of [x, y] coordinate pairs
|
|
160
|
+
* @returns Array of transformed Vector2 points
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* const corners = t.points([-40, -40], [40, -40], [40, 40], [-40, 40])
|
|
164
|
+
* p.MoveTo(corners[0])
|
|
165
|
+
* for (let i = 1; i < corners.length; i++) p.LineTo(corners[i])
|
|
166
|
+
*/
|
|
167
|
+
points(...coords: [number, number][]): Vector2[] {
|
|
168
|
+
return coords.map(([x, y]) => this.point(x, y))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get the raw transformation values.
|
|
173
|
+
* Useful for debugging or advanced matrix operations.
|
|
174
|
+
*
|
|
175
|
+
* Returns [a, b, c, d, e, f] where:
|
|
176
|
+
* - a, d: scale
|
|
177
|
+
* - b, c: rotation/skew
|
|
178
|
+
* - e, f: translation
|
|
179
|
+
*/
|
|
180
|
+
get values(): [number, number, number, number, number, number] {
|
|
181
|
+
return [this._a, this._b, this._c, this._d, this._e, this._f]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Set the transformation matrix directly.
|
|
186
|
+
* @param a - Horizontal scale (1 = no scale)
|
|
187
|
+
* @param b - Vertical skew
|
|
188
|
+
* @param c - Horizontal skew
|
|
189
|
+
* @param d - Vertical scale (1 = no scale)
|
|
190
|
+
* @param e - Horizontal translation
|
|
191
|
+
* @param f - Vertical translation
|
|
192
|
+
*/
|
|
193
|
+
setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void {
|
|
194
|
+
this._a = a
|
|
195
|
+
this._b = b
|
|
196
|
+
this._c = c
|
|
197
|
+
this._d = d
|
|
198
|
+
this._e = e
|
|
199
|
+
this._f = f
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Multiply the current matrix by another matrix.
|
|
204
|
+
* @param a - Horizontal scale
|
|
205
|
+
* @param b - Vertical skew
|
|
206
|
+
* @param c - Horizontal skew
|
|
207
|
+
* @param d - Vertical scale
|
|
208
|
+
* @param e - Horizontal translation
|
|
209
|
+
* @param f - Vertical translation
|
|
210
|
+
*/
|
|
211
|
+
transform(a: number, b: number, c: number, d: number, e: number, f: number): void {
|
|
212
|
+
const a_ = this._a * a + this._c * b
|
|
213
|
+
const b_ = this._b * a + this._d * b
|
|
214
|
+
const c_ = this._a * c + this._c * d
|
|
215
|
+
const d_ = this._b * c + this._d * d
|
|
216
|
+
const e_ = this._a * e + this._c * f + this._e
|
|
217
|
+
const f_ = this._b * e + this._d * f + this._f
|
|
218
|
+
|
|
219
|
+
this._a = a_
|
|
220
|
+
this._b = b_
|
|
221
|
+
this._c = c_
|
|
222
|
+
this._d = d_
|
|
223
|
+
this._e = e_
|
|
224
|
+
this._f = f_
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Hook for vector drawing with automatic repaint on dependency changes.
|
|
230
|
+
*
|
|
231
|
+
* Returns a ref to attach to a VisualElement. When dependencies change,
|
|
232
|
+
* automatically calls MarkDirtyRepaint() to trigger a redraw.
|
|
233
|
+
*
|
|
234
|
+
* @param draw - Drawing callback that receives MeshGenerationContext
|
|
235
|
+
* @param deps - Dependency array (like useEffect) - repaint when these change
|
|
236
|
+
* @returns Ref to attach to the element
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```tsx
|
|
240
|
+
* function AnimatedCircle() {
|
|
241
|
+
* const [radius, setRadius] = useState(50)
|
|
242
|
+
*
|
|
243
|
+
* const ref = useVectorContent((mgc) => {
|
|
244
|
+
* const p = mgc.painter2D
|
|
245
|
+
* const Angle = CS.UnityEngine.UIElements.Angle
|
|
246
|
+
*
|
|
247
|
+
* p.fillColor = new CS.UnityEngine.Color(1, 0, 0, 1)
|
|
248
|
+
* p.BeginPath()
|
|
249
|
+
* p.Arc(
|
|
250
|
+
* new CS.UnityEngine.Vector2(100, 100),
|
|
251
|
+
* radius,
|
|
252
|
+
* Angle.Degrees(0),
|
|
253
|
+
* Angle.Degrees(360),
|
|
254
|
+
* CS.UnityEngine.UIElements.ArcDirection.Clockwise
|
|
255
|
+
* )
|
|
256
|
+
* p.Fill()
|
|
257
|
+
* }, [radius]) // Auto-repaints when radius changes
|
|
258
|
+
*
|
|
259
|
+
* return <View ref={ref} style={{ width: 200, height: 200 }} />
|
|
260
|
+
* }
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
export function useVectorContent(
|
|
264
|
+
draw: GenerateVisualContentCallback,
|
|
265
|
+
deps: DependencyList = []
|
|
266
|
+
): RefObject<VisualElement | null> {
|
|
267
|
+
const ref = useRef<VisualElement | null>(null)
|
|
268
|
+
const drawRef = useRef(draw)
|
|
269
|
+
|
|
270
|
+
// Keep drawRef current
|
|
271
|
+
drawRef.current = draw
|
|
272
|
+
|
|
273
|
+
// Register callback and handle updates
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
const element = ref.current
|
|
276
|
+
if (!element) return
|
|
277
|
+
|
|
278
|
+
// Create a stable wrapper that always calls the latest draw function
|
|
279
|
+
const callback: GenerateVisualContentCallback = (mgc) => {
|
|
280
|
+
drawRef.current(mgc)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Assign the callback to generateVisualContent
|
|
284
|
+
// Use unknown cast because VisualElement interface doesn't expose this property directly
|
|
285
|
+
const el = element as unknown as { generateVisualContent: GenerateVisualContentCallback | null }
|
|
286
|
+
el.generateVisualContent = callback
|
|
287
|
+
|
|
288
|
+
// Initial repaint to render content
|
|
289
|
+
element.MarkDirtyRepaint()
|
|
290
|
+
|
|
291
|
+
return () => {
|
|
292
|
+
// Clear callback on cleanup
|
|
293
|
+
el.generateVisualContent = null
|
|
294
|
+
}
|
|
295
|
+
}, []) // Only run once on mount
|
|
296
|
+
|
|
297
|
+
// Trigger repaint when dependencies change (but not on first render)
|
|
298
|
+
const isFirstRender = useRef(true)
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
if (isFirstRender.current) {
|
|
301
|
+
isFirstRender.current = false
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const element = ref.current
|
|
306
|
+
if (element) {
|
|
307
|
+
element.MarkDirtyRepaint()
|
|
308
|
+
}
|
|
309
|
+
}, deps)
|
|
310
|
+
|
|
311
|
+
return ref
|
|
312
|
+
}
|