@nexart/ui-renderer 0.2.1 → 0.3.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.
Files changed (38) hide show
  1. package/README.md +78 -4
  2. package/dist/compiler.d.ts +18 -2
  3. package/dist/compiler.d.ts.map +1 -1
  4. package/dist/compiler.js +25 -11
  5. package/dist/index.d.ts +19 -8
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +18 -7
  8. package/dist/presets/backgrounds.d.ts +14 -0
  9. package/dist/presets/backgrounds.d.ts.map +1 -0
  10. package/dist/presets/backgrounds.js +222 -0
  11. package/dist/presets/index.d.ts +7 -0
  12. package/dist/presets/index.d.ts.map +1 -0
  13. package/dist/presets/index.js +6 -0
  14. package/dist/presets/primitives.d.ts +16 -0
  15. package/dist/presets/primitives.d.ts.map +1 -0
  16. package/dist/presets/primitives.js +282 -0
  17. package/dist/presets/sketch-wrapper.d.ts +14 -0
  18. package/dist/presets/sketch-wrapper.d.ts.map +1 -0
  19. package/dist/presets/sketch-wrapper.js +70 -0
  20. package/dist/preview/code-renderer.d.ts +25 -0
  21. package/dist/preview/code-renderer.d.ts.map +1 -0
  22. package/dist/preview/code-renderer.js +651 -0
  23. package/dist/preview/primitives/sketch.d.ts +14 -0
  24. package/dist/preview/primitives/sketch.d.ts.map +1 -0
  25. package/dist/preview/primitives/sketch.js +407 -0
  26. package/dist/preview/renderer.d.ts +1 -1
  27. package/dist/preview/renderer.d.ts.map +1 -1
  28. package/dist/preview/renderer.js +23 -13
  29. package/dist/preview/unified-renderer.d.ts +16 -0
  30. package/dist/preview/unified-renderer.d.ts.map +1 -0
  31. package/dist/preview/unified-renderer.js +270 -0
  32. package/dist/system.d.ts +7 -3
  33. package/dist/system.d.ts.map +1 -1
  34. package/dist/system.js +187 -11
  35. package/dist/types.d.ts +125 -5
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/types.js +12 -3
  38. package/package.json +2 -2
@@ -0,0 +1,651 @@
1
+ /**
2
+ * @nexart/ui-renderer v0.3.0 - Code Mode Renderer
3
+ *
4
+ * Renders Code Mode systems using the canonical NexArt p5-like runtime.
5
+ * This uses the exact same execution logic as nexart.xyz for determinism.
6
+ */
7
+ let activeRendererInstance = null;
8
+ function createSeededRNG(seed = 123456) {
9
+ let a = seed >>> 0;
10
+ return () => {
11
+ a += 0x6D2B79F5;
12
+ let t = Math.imul(a ^ (a >>> 15), a | 1);
13
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
14
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
15
+ };
16
+ }
17
+ function createSeededNoise(seed = 0) {
18
+ const permutation = [];
19
+ const rng = createSeededRNG(seed);
20
+ for (let i = 0; i < 256; i++) {
21
+ permutation[i] = i;
22
+ }
23
+ for (let i = 255; i > 0; i--) {
24
+ const j = Math.floor(rng() * (i + 1));
25
+ [permutation[i], permutation[j]] = [permutation[j], permutation[i]];
26
+ }
27
+ for (let i = 0; i < 256; i++) {
28
+ permutation[256 + i] = permutation[i];
29
+ }
30
+ const fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);
31
+ const lerp = (a, b, t) => a + t * (b - a);
32
+ const grad = (hash, x, y, z) => {
33
+ const h = hash & 15;
34
+ const u = h < 8 ? x : y;
35
+ const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
36
+ return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
37
+ };
38
+ return (x, y = 0, z = 0) => {
39
+ const X = Math.floor(x) & 255;
40
+ const Y = Math.floor(y) & 255;
41
+ const Z = Math.floor(z) & 255;
42
+ x -= Math.floor(x);
43
+ y -= Math.floor(y);
44
+ z -= Math.floor(z);
45
+ const u = fade(x);
46
+ const v = fade(y);
47
+ const w = fade(z);
48
+ const A = permutation[X] + Y;
49
+ const AA = permutation[A] + Z;
50
+ const AB = permutation[A + 1] + Z;
51
+ const B = permutation[X + 1] + Y;
52
+ const BA = permutation[B] + Z;
53
+ const BB = permutation[B + 1] + Z;
54
+ return (lerp(lerp(lerp(grad(permutation[AA], x, y, z), grad(permutation[BA], x - 1, y, z), u), lerp(grad(permutation[AB], x, y - 1, z), grad(permutation[BB], x - 1, y - 1, z), u), v), lerp(lerp(grad(permutation[AA + 1], x, y, z - 1), grad(permutation[BA + 1], x - 1, y, z - 1), u), lerp(grad(permutation[AB + 1], x, y - 1, z - 1), grad(permutation[BB + 1], x - 1, y - 1, z - 1), u), v), w) + 1) / 2;
55
+ };
56
+ }
57
+ export function createP5Runtime(canvas, width, height, seed) {
58
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
59
+ let currentFill = 'rgba(255, 255, 255, 1)';
60
+ let currentStroke = 'rgba(0, 0, 0, 1)';
61
+ let strokeEnabled = true;
62
+ let fillEnabled = true;
63
+ let currentStrokeWeight = 1;
64
+ let colorModeSettings = { mode: 'RGB', maxR: 255, maxG: 255, maxB: 255, maxA: 255 };
65
+ let shapeStarted = false;
66
+ let randomSeedValue = seed;
67
+ let rng = createSeededRNG(randomSeedValue);
68
+ let noiseSeedValue = seed;
69
+ let noiseFunc = createSeededNoise(noiseSeedValue);
70
+ let noiseOctaves = 4;
71
+ let noiseFalloff = 0.5;
72
+ const parseColor = (...args) => {
73
+ if (args.length === 0)
74
+ return 'rgba(0, 0, 0, 1)';
75
+ const { mode, maxR, maxG, maxB, maxA } = colorModeSettings;
76
+ if (args.length === 1) {
77
+ const val = args[0];
78
+ if (typeof val === 'string')
79
+ return val;
80
+ if (mode === 'HSB') {
81
+ return `hsla(${val}, 100%, 50%, 1)`;
82
+ }
83
+ const gray = Math.round((val / maxR) * 255);
84
+ return `rgba(${gray}, ${gray}, ${gray}, 1)`;
85
+ }
86
+ if (args.length === 2) {
87
+ const [gray, alpha] = args;
88
+ const g = Math.round((gray / maxR) * 255);
89
+ const a = alpha / maxA;
90
+ return `rgba(${g}, ${g}, ${g}, ${a})`;
91
+ }
92
+ if (args.length === 3) {
93
+ const [r, g, b] = args;
94
+ if (mode === 'HSB') {
95
+ return `hsla(${(r / maxR) * 360}, ${(g / maxG) * 100}%, ${(b / maxB) * 100}%, 1)`;
96
+ }
97
+ return `rgba(${Math.round((r / maxR) * 255)}, ${Math.round((g / maxG) * 255)}, ${Math.round((b / maxB) * 255)}, 1)`;
98
+ }
99
+ if (args.length === 4) {
100
+ const [r, g, b, a] = args;
101
+ if (mode === 'HSB') {
102
+ return `hsla(${(r / maxR) * 360}, ${(g / maxG) * 100}%, ${(b / maxB) * 100}%, ${a / maxA})`;
103
+ }
104
+ return `rgba(${Math.round((r / maxR) * 255)}, ${Math.round((g / maxG) * 255)}, ${Math.round((b / maxB) * 255)}, ${a / maxA})`;
105
+ }
106
+ return 'rgba(0, 0, 0, 1)';
107
+ };
108
+ const p = {
109
+ width,
110
+ height,
111
+ frameCount: 0,
112
+ PI: Math.PI,
113
+ TWO_PI: Math.PI * 2,
114
+ HALF_PI: Math.PI / 2,
115
+ QUARTER_PI: Math.PI / 4,
116
+ CORNER: 'corner',
117
+ CENTER: 'center',
118
+ CORNERS: 'corners',
119
+ RADIUS: 'radius',
120
+ ROUND: 'round',
121
+ SQUARE: 'square',
122
+ PROJECT: 'butt',
123
+ MITER: 'miter',
124
+ BEVEL: 'bevel',
125
+ CLOSE: 'close',
126
+ background: (...args) => {
127
+ ctx.fillStyle = parseColor(...args);
128
+ ctx.fillRect(0, 0, width, height);
129
+ },
130
+ fill: (...args) => {
131
+ currentFill = parseColor(...args);
132
+ fillEnabled = true;
133
+ },
134
+ noFill: () => {
135
+ fillEnabled = false;
136
+ },
137
+ stroke: (...args) => {
138
+ currentStroke = parseColor(...args);
139
+ strokeEnabled = true;
140
+ },
141
+ noStroke: () => {
142
+ strokeEnabled = false;
143
+ },
144
+ strokeWeight: (w) => {
145
+ currentStrokeWeight = w;
146
+ ctx.lineWidth = w;
147
+ },
148
+ strokeCap: (cap) => {
149
+ ctx.lineCap = cap;
150
+ },
151
+ strokeJoin: (join) => {
152
+ ctx.lineJoin = join;
153
+ },
154
+ colorMode: (mode, max1, max2, max3, maxA) => {
155
+ colorModeSettings.mode = mode;
156
+ if (max1 !== undefined) {
157
+ colorModeSettings.maxR = max1;
158
+ colorModeSettings.maxG = max2 ?? max1;
159
+ colorModeSettings.maxB = max3 ?? max1;
160
+ colorModeSettings.maxA = maxA ?? max1;
161
+ }
162
+ },
163
+ push: () => {
164
+ ctx.save();
165
+ },
166
+ pop: () => {
167
+ ctx.restore();
168
+ },
169
+ translate: (x, y) => {
170
+ ctx.translate(x, y);
171
+ },
172
+ rotate: (angle) => {
173
+ ctx.rotate(angle);
174
+ },
175
+ scale: (sx, sy) => {
176
+ ctx.scale(sx, sy ?? sx);
177
+ },
178
+ ellipse: (x, y, w, h) => {
179
+ const rw = w / 2;
180
+ const rh = (h ?? w) / 2;
181
+ ctx.beginPath();
182
+ ctx.ellipse(x, y, rw, rh, 0, 0, Math.PI * 2);
183
+ if (fillEnabled) {
184
+ ctx.fillStyle = currentFill;
185
+ ctx.fill();
186
+ }
187
+ if (strokeEnabled) {
188
+ ctx.strokeStyle = currentStroke;
189
+ ctx.lineWidth = currentStrokeWeight;
190
+ ctx.stroke();
191
+ }
192
+ },
193
+ circle: (x, y, d) => {
194
+ p.ellipse(x, y, d, d);
195
+ },
196
+ rect: (x, y, w, h, r) => {
197
+ const height = h ?? w;
198
+ ctx.beginPath();
199
+ if (r && r > 0) {
200
+ ctx.roundRect(x, y, w, height, r);
201
+ }
202
+ else {
203
+ ctx.rect(x, y, w, height);
204
+ }
205
+ if (fillEnabled) {
206
+ ctx.fillStyle = currentFill;
207
+ ctx.fill();
208
+ }
209
+ if (strokeEnabled) {
210
+ ctx.strokeStyle = currentStroke;
211
+ ctx.lineWidth = currentStrokeWeight;
212
+ ctx.stroke();
213
+ }
214
+ },
215
+ square: (x, y, s, r) => {
216
+ p.rect(x, y, s, s, r);
217
+ },
218
+ line: (x1, y1, x2, y2) => {
219
+ ctx.beginPath();
220
+ ctx.moveTo(x1, y1);
221
+ ctx.lineTo(x2, y2);
222
+ if (strokeEnabled) {
223
+ ctx.strokeStyle = currentStroke;
224
+ ctx.lineWidth = currentStrokeWeight;
225
+ ctx.stroke();
226
+ }
227
+ },
228
+ point: (x, y) => {
229
+ ctx.beginPath();
230
+ ctx.arc(x, y, currentStrokeWeight / 2, 0, Math.PI * 2);
231
+ ctx.fillStyle = currentStroke;
232
+ ctx.fill();
233
+ },
234
+ triangle: (x1, y1, x2, y2, x3, y3) => {
235
+ ctx.beginPath();
236
+ ctx.moveTo(x1, y1);
237
+ ctx.lineTo(x2, y2);
238
+ ctx.lineTo(x3, y3);
239
+ ctx.closePath();
240
+ if (fillEnabled) {
241
+ ctx.fillStyle = currentFill;
242
+ ctx.fill();
243
+ }
244
+ if (strokeEnabled) {
245
+ ctx.strokeStyle = currentStroke;
246
+ ctx.lineWidth = currentStrokeWeight;
247
+ ctx.stroke();
248
+ }
249
+ },
250
+ quad: (x1, y1, x2, y2, x3, y3, x4, y4) => {
251
+ ctx.beginPath();
252
+ ctx.moveTo(x1, y1);
253
+ ctx.lineTo(x2, y2);
254
+ ctx.lineTo(x3, y3);
255
+ ctx.lineTo(x4, y4);
256
+ ctx.closePath();
257
+ if (fillEnabled) {
258
+ ctx.fillStyle = currentFill;
259
+ ctx.fill();
260
+ }
261
+ if (strokeEnabled) {
262
+ ctx.strokeStyle = currentStroke;
263
+ ctx.lineWidth = currentStrokeWeight;
264
+ ctx.stroke();
265
+ }
266
+ },
267
+ arc: (x, y, w, h, start, stop, mode) => {
268
+ ctx.beginPath();
269
+ ctx.ellipse(x, y, w / 2, h / 2, 0, start, stop);
270
+ if (mode === 'close' || mode === 'chord') {
271
+ ctx.closePath();
272
+ }
273
+ else if (mode === 'pie') {
274
+ ctx.lineTo(x, y);
275
+ ctx.closePath();
276
+ }
277
+ if (fillEnabled) {
278
+ ctx.fillStyle = currentFill;
279
+ ctx.fill();
280
+ }
281
+ if (strokeEnabled) {
282
+ ctx.strokeStyle = currentStroke;
283
+ ctx.lineWidth = currentStrokeWeight;
284
+ ctx.stroke();
285
+ }
286
+ },
287
+ beginShape: () => {
288
+ ctx.beginPath();
289
+ shapeStarted = true;
290
+ },
291
+ vertex: (x, y) => {
292
+ if (!shapeStarted) {
293
+ ctx.beginPath();
294
+ ctx.moveTo(x, y);
295
+ shapeStarted = true;
296
+ }
297
+ else {
298
+ ctx.lineTo(x, y);
299
+ }
300
+ },
301
+ curveVertex: (x, y) => {
302
+ ctx.lineTo(x, y);
303
+ },
304
+ bezierVertex: (x2, y2, x3, y3, x4, y4) => {
305
+ ctx.bezierCurveTo(x2, y2, x3, y3, x4, y4);
306
+ },
307
+ quadraticVertex: (cx, cy, x3, y3) => {
308
+ ctx.quadraticCurveTo(cx, cy, x3, y3);
309
+ },
310
+ endShape: (close) => {
311
+ if (close === 'close') {
312
+ ctx.closePath();
313
+ }
314
+ if (fillEnabled) {
315
+ ctx.fillStyle = currentFill;
316
+ ctx.fill();
317
+ }
318
+ if (strokeEnabled) {
319
+ ctx.strokeStyle = currentStroke;
320
+ ctx.lineWidth = currentStrokeWeight;
321
+ ctx.stroke();
322
+ }
323
+ shapeStarted = false;
324
+ },
325
+ bezier: (x1, y1, x2, y2, x3, y3, x4, y4) => {
326
+ ctx.beginPath();
327
+ ctx.moveTo(x1, y1);
328
+ ctx.bezierCurveTo(x2, y2, x3, y3, x4, y4);
329
+ if (strokeEnabled) {
330
+ ctx.strokeStyle = currentStroke;
331
+ ctx.lineWidth = currentStrokeWeight;
332
+ ctx.stroke();
333
+ }
334
+ },
335
+ curve: (x1, y1, x2, y2, x3, y3, x4, y4) => {
336
+ ctx.beginPath();
337
+ ctx.moveTo(x2, y2);
338
+ ctx.bezierCurveTo(x2, y2, x3, y3, x3, y3);
339
+ if (strokeEnabled) {
340
+ ctx.strokeStyle = currentStroke;
341
+ ctx.lineWidth = currentStrokeWeight;
342
+ ctx.stroke();
343
+ }
344
+ },
345
+ text: (str, x, y) => {
346
+ if (fillEnabled) {
347
+ ctx.fillStyle = currentFill;
348
+ ctx.fillText(String(str), x, y);
349
+ }
350
+ if (strokeEnabled) {
351
+ ctx.strokeStyle = currentStroke;
352
+ ctx.strokeText(String(str), x, y);
353
+ }
354
+ },
355
+ textSize: (size) => {
356
+ ctx.font = `${size}px sans-serif`;
357
+ },
358
+ textAlign: (horizAlign, vertAlign) => {
359
+ ctx.textAlign = horizAlign;
360
+ if (vertAlign) {
361
+ ctx.textBaseline = vertAlign;
362
+ }
363
+ },
364
+ random: (min, max) => {
365
+ if (min === undefined) {
366
+ return rng();
367
+ }
368
+ if (max === undefined) {
369
+ return rng() * min;
370
+ }
371
+ return min + rng() * (max - min);
372
+ },
373
+ randomSeed: (s) => {
374
+ randomSeedValue = s;
375
+ rng = createSeededRNG(s);
376
+ },
377
+ noise: (x, y, z) => {
378
+ let total = 0;
379
+ let freq = 1;
380
+ let amp = 1;
381
+ let maxValue = 0;
382
+ for (let i = 0; i < noiseOctaves; i++) {
383
+ total += noiseFunc(x * freq, (y ?? 0) * freq, (z ?? 0) * freq) * amp;
384
+ maxValue += amp;
385
+ amp *= noiseFalloff;
386
+ freq *= 2;
387
+ }
388
+ return total / maxValue;
389
+ },
390
+ noiseSeed: (s) => {
391
+ noiseSeedValue = s;
392
+ noiseFunc = createSeededNoise(s);
393
+ },
394
+ noiseDetail: (octaves, falloff) => {
395
+ noiseOctaves = octaves;
396
+ if (falloff !== undefined) {
397
+ noiseFalloff = falloff;
398
+ }
399
+ },
400
+ sin: Math.sin,
401
+ cos: Math.cos,
402
+ tan: Math.tan,
403
+ asin: Math.asin,
404
+ acos: Math.acos,
405
+ atan: Math.atan,
406
+ atan2: Math.atan2,
407
+ abs: Math.abs,
408
+ ceil: Math.ceil,
409
+ floor: Math.floor,
410
+ round: Math.round,
411
+ min: Math.min,
412
+ max: Math.max,
413
+ pow: Math.pow,
414
+ sqrt: Math.sqrt,
415
+ exp: Math.exp,
416
+ log: Math.log,
417
+ sq: (n) => n * n,
418
+ map: (value, start1, stop1, start2, stop2) => {
419
+ return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
420
+ },
421
+ constrain: (value, low, high) => {
422
+ return Math.max(low, Math.min(high, value));
423
+ },
424
+ lerp: (start, stop, amt) => {
425
+ return start + (stop - start) * amt;
426
+ },
427
+ dist: (x1, y1, x2, y2) => {
428
+ return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
429
+ },
430
+ mag: (x, y) => {
431
+ return Math.sqrt(x * x + y * y);
432
+ },
433
+ norm: (value, start, stop) => {
434
+ return (value - start) / (stop - start);
435
+ },
436
+ radians: (degrees) => degrees * (Math.PI / 180),
437
+ degrees: (radians) => radians * (180 / Math.PI),
438
+ color: (...args) => parseColor(...args),
439
+ lerpColor: (c1, c2, amt) => {
440
+ return c1;
441
+ },
442
+ red: (c) => {
443
+ const match = c.match(/rgba?\((\d+)/);
444
+ if (match)
445
+ return parseInt(match[1], 10);
446
+ const rgbMatch = c.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
447
+ if (rgbMatch)
448
+ return parseInt(rgbMatch[1], 10);
449
+ return 0;
450
+ },
451
+ green: (c) => {
452
+ const match = c.match(/rgba?\(\d+,\s*(\d+)/);
453
+ if (match)
454
+ return parseInt(match[1], 10);
455
+ const rgbMatch = c.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
456
+ if (rgbMatch)
457
+ return parseInt(rgbMatch[2], 10);
458
+ return 0;
459
+ },
460
+ blue: (c) => {
461
+ const match = c.match(/rgba?\(\d+,\s*\d+,\s*(\d+)/);
462
+ if (match)
463
+ return parseInt(match[1], 10);
464
+ const rgbMatch = c.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
465
+ if (rgbMatch)
466
+ return parseInt(rgbMatch[3], 10);
467
+ return 0;
468
+ },
469
+ alpha: (c) => {
470
+ const match = c.match(/rgba\(\d+,\s*\d+,\s*\d+,\s*([\d.]+)\)/);
471
+ if (match)
472
+ return Math.round(parseFloat(match[1]) * 255);
473
+ return 255;
474
+ },
475
+ hue: (c) => 0,
476
+ saturation: (c) => 0,
477
+ brightness: (c) => 0,
478
+ blendMode: (mode) => {
479
+ ctx.globalCompositeOperation = mode;
480
+ },
481
+ clear: () => {
482
+ ctx.clearRect(0, 0, width, height);
483
+ },
484
+ print: console.log,
485
+ println: console.log,
486
+ };
487
+ return p;
488
+ }
489
+ function injectTimeVariables(p, time) {
490
+ p.frameCount = time.frameCount;
491
+ p.t = time.t;
492
+ p.time = time.time;
493
+ p.tGlobal = time.tGlobal;
494
+ }
495
+ export function renderCodeModeSystem(system, canvas, options = {}) {
496
+ if (activeRendererInstance) {
497
+ activeRendererInstance.destroy();
498
+ activeRendererInstance = null;
499
+ }
500
+ const { showBadge = true, onPreview, onComplete, onError } = options;
501
+ canvas.width = system.width;
502
+ canvas.height = system.height;
503
+ const ctx = canvas.getContext('2d');
504
+ let animationId = null;
505
+ let isRunning = false;
506
+ let isDestroyed = false;
507
+ const extractFunctions = (code) => {
508
+ const setupMatch = code.match(/function\s+setup\s*\(\s*\)\s*\{([\s\S]*?)\}(?=\s*function|\s*$)/);
509
+ const drawMatch = code.match(/function\s+draw\s*\(\s*\)\s*\{([\s\S]*?)\}(?=\s*function|\s*$)/);
510
+ return {
511
+ setupCode: setupMatch ? setupMatch[1].trim() : code,
512
+ drawCode: drawMatch ? drawMatch[1].trim() : null,
513
+ };
514
+ };
515
+ const validateCode = (code) => {
516
+ const forbiddenPatterns = ['setTimeout', 'setInterval', 'requestAnimationFrame'];
517
+ for (const pattern of forbiddenPatterns) {
518
+ if (code.includes(pattern)) {
519
+ throw new Error(`Forbidden async timing function: ${pattern}`);
520
+ }
521
+ }
522
+ };
523
+ const drawBadge = () => {
524
+ if (!showBadge)
525
+ return;
526
+ const text = '⚠️ Preview Renderer (Non-Canonical)';
527
+ ctx.font = '12px -apple-system, sans-serif';
528
+ const metrics = ctx.measureText(text);
529
+ const padding = 8;
530
+ const badgeWidth = metrics.width + padding * 2;
531
+ const badgeHeight = 24;
532
+ const x = system.width - badgeWidth - 10;
533
+ const y = 10;
534
+ ctx.fillStyle = 'rgba(255, 100, 100, 0.15)';
535
+ ctx.strokeStyle = 'rgba(255, 100, 100, 0.4)';
536
+ ctx.lineWidth = 1;
537
+ ctx.beginPath();
538
+ ctx.roundRect(x, y, badgeWidth, badgeHeight, 4);
539
+ ctx.fill();
540
+ ctx.stroke();
541
+ ctx.fillStyle = '#ff9999';
542
+ ctx.fillText(text, x + padding, y + 16);
543
+ };
544
+ const renderStatic = () => {
545
+ try {
546
+ validateCode(system.source);
547
+ const { setupCode } = extractFunctions(system.source);
548
+ const p = createP5Runtime(canvas, system.width, system.height, system.seed);
549
+ injectTimeVariables(p, { frameCount: 0, t: 0, time: 0, tGlobal: 0 });
550
+ const wrappedSetup = new Function('p', 'frameCount', 't', 'time', 'tGlobal', `with(p) { ${setupCode} }`);
551
+ wrappedSetup(p, 0, 0, 0, 0);
552
+ drawBadge();
553
+ onPreview?.(canvas);
554
+ canvas.toBlob((blob) => {
555
+ if (blob) {
556
+ onComplete?.({ type: 'image', blob });
557
+ }
558
+ }, 'image/png');
559
+ }
560
+ catch (error) {
561
+ const err = error instanceof Error ? error : new Error(String(error));
562
+ onError?.(err);
563
+ throw err;
564
+ }
565
+ };
566
+ const renderLoop = () => {
567
+ if (isDestroyed)
568
+ return;
569
+ try {
570
+ validateCode(system.source);
571
+ const { setupCode, drawCode } = extractFunctions(system.source);
572
+ if (!drawCode) {
573
+ throw new Error('Loop mode requires a draw() function');
574
+ }
575
+ const totalFrames = system.totalFrames ?? 60;
576
+ let frame = 0;
577
+ const p = createP5Runtime(canvas, system.width, system.height, system.seed);
578
+ const frozenP = Object.freeze({ ...p });
579
+ const wrappedSetup = new Function('p', 'frameCount', 't', 'time', 'tGlobal', `with(p) { ${setupCode} }`);
580
+ const wrappedDraw = new Function('p', 'frameCount', 't', 'time', 'tGlobal', `with(p) { ${drawCode} }`);
581
+ wrappedSetup(p, 0, 0, 0, 0);
582
+ const loop = () => {
583
+ if (!isRunning || isDestroyed)
584
+ return;
585
+ const t = frame / totalFrames;
586
+ const time = t * (system.totalFrames ? system.totalFrames / 30 : 2);
587
+ ctx.clearRect(0, 0, system.width, system.height);
588
+ injectTimeVariables(p, { frameCount: frame, t, time, tGlobal: t });
589
+ p.randomSeed(system.seed);
590
+ p.noiseSeed(system.seed);
591
+ wrappedDraw(p, frame, t, time, t);
592
+ drawBadge();
593
+ frame = (frame + 1) % totalFrames;
594
+ animationId = requestAnimationFrame(loop);
595
+ };
596
+ isRunning = true;
597
+ animationId = requestAnimationFrame(loop);
598
+ }
599
+ catch (error) {
600
+ const err = error instanceof Error ? error : new Error(String(error));
601
+ onError?.(err);
602
+ throw err;
603
+ }
604
+ };
605
+ const render = () => {
606
+ if (isDestroyed)
607
+ return;
608
+ if (system.mode === 'static') {
609
+ renderStatic();
610
+ }
611
+ else {
612
+ renderLoop();
613
+ }
614
+ };
615
+ const start = () => {
616
+ if (isDestroyed)
617
+ return;
618
+ stop();
619
+ if (system.mode === 'loop') {
620
+ renderLoop();
621
+ }
622
+ else {
623
+ renderStatic();
624
+ }
625
+ };
626
+ const stop = () => {
627
+ isRunning = false;
628
+ if (animationId !== null) {
629
+ cancelAnimationFrame(animationId);
630
+ animationId = null;
631
+ }
632
+ };
633
+ const destroy = () => {
634
+ isDestroyed = true;
635
+ stop();
636
+ ctx.clearRect(0, 0, system.width, system.height);
637
+ if (activeRendererInstance === renderer) {
638
+ activeRendererInstance = null;
639
+ }
640
+ };
641
+ const renderer = {
642
+ render,
643
+ start,
644
+ stop,
645
+ destroy,
646
+ isCanonical: false,
647
+ isArchival: false,
648
+ };
649
+ activeRendererInstance = renderer;
650
+ return renderer;
651
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @nexart/ui-renderer v0.2.1 - Sketch Primitive Renderer
3
+ *
4
+ * Executes raw Code Mode p5-like sketches.
5
+ * This is NOT canonical output - for preview/exploration only.
6
+ */
7
+ import type { SketchElement } from '../../types';
8
+ export interface SketchRenderer {
9
+ render: (frameCount: number) => void;
10
+ hasSetup: boolean;
11
+ hasDraw: boolean;
12
+ }
13
+ export declare function createSketchRenderer(element: SketchElement, ctx: CanvasRenderingContext2D, width: number, height: number, seed: number): SketchRenderer;
14
+ //# sourceMappingURL=sketch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sketch.d.ts","sourceRoot":"","sources":["../../../src/preview/primitives/sketch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA2ajD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,wBAAwB,EAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,cAAc,CAwChB"}