@solidtv/renderer 1.0.5 → 1.0.7

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 (43) hide show
  1. package/dist/src/core/CoreNode.d.ts +2 -1
  2. package/dist/src/core/CoreNode.js +42 -58
  3. package/dist/src/core/CoreNode.js.map +1 -1
  4. package/dist/src/core/CoreTextNode.d.ts +2 -0
  5. package/dist/src/core/CoreTextNode.js +35 -18
  6. package/dist/src/core/CoreTextNode.js.map +1 -1
  7. package/dist/src/core/CoreTextureManager.js +0 -1
  8. package/dist/src/core/CoreTextureManager.js.map +1 -1
  9. package/dist/src/core/TextureMemoryManager.d.ts +0 -13
  10. package/dist/src/core/TextureMemoryManager.js +0 -24
  11. package/dist/src/core/TextureMemoryManager.js.map +1 -1
  12. package/dist/src/core/animations/AnimationManager.d.ts +1 -0
  13. package/dist/src/core/animations/AnimationManager.js.map +1 -1
  14. package/dist/src/core/animations/CoreAnimation.d.ts +1 -0
  15. package/dist/src/core/animations/CoreAnimation.js +1 -0
  16. package/dist/src/core/animations/CoreAnimation.js.map +1 -1
  17. package/dist/src/core/renderers/webgl/WebGlRenderer.d.ts +15 -0
  18. package/dist/src/core/renderers/webgl/WebGlRenderer.js +35 -6
  19. package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
  20. package/dist/src/core/text-rendering/CanvasTextRenderer.js +12 -1
  21. package/dist/src/core/text-rendering/CanvasTextRenderer.js.map +1 -1
  22. package/dist/src/core/text-rendering/SdfFontHandler.js +5 -4
  23. package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
  24. package/dist/src/core/text-rendering/SdfTextRenderer.js +16 -6
  25. package/dist/src/core/text-rendering/SdfTextRenderer.js.map +1 -1
  26. package/dist/src/core/text-rendering/TextRenderer.d.ts +0 -31
  27. package/dist/src/main-api/Renderer.js +4 -2
  28. package/dist/src/main-api/Renderer.js.map +1 -1
  29. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  30. package/package.json +1 -1
  31. package/src/core/CoreNode.test.ts +47 -46
  32. package/src/core/CoreNode.ts +46 -57
  33. package/src/core/CoreTextNode.ts +38 -23
  34. package/src/core/CoreTextureManager.ts +0 -2
  35. package/src/core/TextureMemoryManager.ts +0 -27
  36. package/src/core/animations/AnimationManager.ts +1 -0
  37. package/src/core/animations/CoreAnimation.ts +2 -1
  38. package/src/core/renderers/webgl/WebGlRenderer.ts +37 -6
  39. package/src/core/text-rendering/CanvasTextRenderer.ts +14 -1
  40. package/src/core/text-rendering/SdfFontHandler.ts +6 -5
  41. package/src/core/text-rendering/SdfTextRenderer.ts +19 -6
  42. package/src/core/text-rendering/TextRenderer.ts +0 -31
  43. package/src/main-api/Renderer.ts +4 -2
@@ -9,7 +9,7 @@ import { ImageTexture } from './textures/ImageTexture.js';
9
9
  import { Matrix3d } from './lib/Matrix3d.js';
10
10
 
11
11
  describe('set color()', () => {
12
- const defaultProps: CoreNodeProps = {
12
+ const defaultProps = (overrides?: Partial<CoreNodeProps>): CoreNodeProps => ({
13
13
  alpha: 0,
14
14
  autosize: false,
15
15
  boundsMargin: null,
@@ -45,7 +45,8 @@ describe('set color()', () => {
45
45
  y: 0,
46
46
  zIndex: 0,
47
47
  preventDestroy: false,
48
- };
48
+ ...overrides,
49
+ });
49
50
 
50
51
  const clippingRect = {
51
52
  x: 0,
@@ -66,7 +67,7 @@ describe('set color()', () => {
66
67
 
67
68
  describe('set color()', () => {
68
69
  it('should set all color subcomponents.', () => {
69
- const node = new CoreNode(stage, defaultProps);
70
+ const node = new CoreNode(stage, defaultProps());
70
71
  node.colorBl = 0x99aabbff;
71
72
  node.colorBr = 0xaabbccff;
72
73
  node.colorTl = 0xbbcceeff;
@@ -87,7 +88,7 @@ describe('set color()', () => {
87
88
  });
88
89
 
89
90
  it('should set update type.', () => {
90
- const node = new CoreNode(stage, defaultProps);
91
+ const node = new CoreNode(stage, defaultProps());
91
92
  node.updateType = 0;
92
93
  node.color = 0xffffffff;
93
94
 
@@ -99,17 +100,17 @@ describe('set color()', () => {
99
100
 
100
101
  describe('isRenderable checks', () => {
101
102
  it('should return false if node is not renderable', () => {
102
- const node = new CoreNode(stage, defaultProps);
103
+ const node = new CoreNode(stage, defaultProps());
103
104
  expect(node.isRenderable).toBe(false);
104
105
  });
105
106
 
106
107
  it('visible node that is a color texture', () => {
107
- const parent = new CoreNode(stage, defaultProps);
108
+ const parent = new CoreNode(stage, defaultProps());
108
109
  // Manually set parent properties that update() might read
109
110
  parent.globalTransform = Matrix3d.identity();
110
111
  parent.worldAlpha = 1;
111
112
 
112
- const node = new CoreNode(stage, { ...defaultProps, parent });
113
+ const node = new CoreNode(stage, defaultProps({ parent }));
113
114
  node.alpha = 1;
114
115
  node.x = 0;
115
116
  node.y = 0;
@@ -122,11 +123,11 @@ describe('set color()', () => {
122
123
  });
123
124
 
124
125
  it('visible node that is a texture', () => {
125
- const parent = new CoreNode(stage, defaultProps);
126
+ const parent = new CoreNode(stage, defaultProps());
126
127
  parent.globalTransform = Matrix3d.identity();
127
128
  parent.worldAlpha = 1;
128
129
 
129
- const node = new CoreNode(stage, { ...defaultProps, parent });
130
+ const node = new CoreNode(stage, defaultProps({ parent }));
130
131
  node.alpha = 1;
131
132
  node.x = 0;
132
133
  node.y = 0;
@@ -148,11 +149,11 @@ describe('set color()', () => {
148
149
  });
149
150
 
150
151
  it('a node with a texture with alpha 0 should not be renderable', () => {
151
- const parent = new CoreNode(stage, defaultProps);
152
+ const parent = new CoreNode(stage, defaultProps());
152
153
  parent.globalTransform = Matrix3d.identity();
153
154
  parent.worldAlpha = 1;
154
155
 
155
- const node = new CoreNode(stage, { ...defaultProps, parent });
156
+ const node = new CoreNode(stage, defaultProps({ parent }));
156
157
  expect(node.isRenderable).toBe(false);
157
158
  node.alpha = 0;
158
159
  node.x = 0;
@@ -169,11 +170,11 @@ describe('set color()', () => {
169
170
  });
170
171
 
171
172
  it('a node with a texture that is OutOfBounds should not be renderable', () => {
172
- const parent = new CoreNode(stage, defaultProps);
173
+ const parent = new CoreNode(stage, defaultProps());
173
174
  parent.globalTransform = Matrix3d.identity();
174
175
  parent.worldAlpha = 1;
175
176
 
176
- const node = new CoreNode(stage, { ...defaultProps, parent });
177
+ const node = new CoreNode(stage, defaultProps({ parent }));
177
178
  node.alpha = 1;
178
179
  node.x = 300;
179
180
  node.y = 300;
@@ -188,11 +189,11 @@ describe('set color()', () => {
188
189
  });
189
190
 
190
191
  it('a node with a freed texture should not be renderable', () => {
191
- const parent = new CoreNode(stage, defaultProps);
192
+ const parent = new CoreNode(stage, defaultProps());
192
193
  parent.globalTransform = Matrix3d.identity();
193
194
  parent.worldAlpha = 1;
194
195
 
195
- const node = new CoreNode(stage, { ...defaultProps, parent });
196
+ const node = new CoreNode(stage, defaultProps({ parent }));
196
197
  node.alpha = 1;
197
198
  node.x = 0;
198
199
  node.y = 0;
@@ -207,11 +208,11 @@ describe('set color()', () => {
207
208
  });
208
209
 
209
210
  it('should emit renderable event when isRenderable status changes', () => {
210
- const parent = new CoreNode(stage, defaultProps);
211
+ const parent = new CoreNode(stage, defaultProps());
211
212
  parent.globalTransform = Matrix3d.identity();
212
213
  parent.worldAlpha = 1;
213
214
 
214
- const node = new CoreNode(stage, { ...defaultProps, parent });
215
+ const node = new CoreNode(stage, defaultProps({ parent }));
215
216
  const eventCallback = vi.fn();
216
217
 
217
218
  // Listen for the renderableChanged event
@@ -262,12 +263,12 @@ describe('set color()', () => {
262
263
 
263
264
  describe('autosize system', () => {
264
265
  it('should initialize with autosize disabled', () => {
265
- const node = new CoreNode(stage, defaultProps);
266
+ const node = new CoreNode(stage, defaultProps());
266
267
  expect(node.autosize).toBe(false);
267
268
  });
268
269
 
269
270
  it('should enable texture autosize when texture is present', () => {
270
- const node = new CoreNode(stage, defaultProps);
271
+ const node = new CoreNode(stage, defaultProps());
271
272
  const mockTexture = mock<ImageTexture>();
272
273
  mockTexture.state = 'loading';
273
274
 
@@ -279,8 +280,8 @@ describe('set color()', () => {
279
280
  });
280
281
 
281
282
  it('should enable children autosize when no texture but has children', () => {
282
- const parent = new CoreNode(stage, defaultProps);
283
- const child = new CoreNode(stage, defaultProps);
283
+ const parent = new CoreNode(stage, defaultProps());
284
+ const child = new CoreNode(stage, defaultProps());
284
285
 
285
286
  parent.autosize = true;
286
287
  child.parent = parent;
@@ -290,8 +291,8 @@ describe('set color()', () => {
290
291
  });
291
292
 
292
293
  it('should prioritize texture autosize over children autosize', () => {
293
- const parent = new CoreNode(stage, defaultProps);
294
- const child = new CoreNode(stage, defaultProps);
294
+ const parent = new CoreNode(stage, defaultProps());
295
+ const child = new CoreNode(stage, defaultProps());
295
296
  const mockTexture = mock<ImageTexture>();
296
297
  mockTexture.state = 'loading';
297
298
 
@@ -305,8 +306,8 @@ describe('set color()', () => {
305
306
  });
306
307
 
307
308
  it('should switch from children to texture autosize when texture is added', () => {
308
- const parent = new CoreNode(stage, defaultProps);
309
- const child = new CoreNode(stage, defaultProps);
309
+ const parent = new CoreNode(stage, defaultProps());
310
+ const child = new CoreNode(stage, defaultProps());
310
311
 
311
312
  child.parent = parent;
312
313
  parent.autosize = true;
@@ -321,8 +322,8 @@ describe('set color()', () => {
321
322
  });
322
323
 
323
324
  it('should switch from texture to children autosize when texture is removed', () => {
324
- const parent = new CoreNode(stage, defaultProps);
325
- const child = new CoreNode(stage, defaultProps);
325
+ const parent = new CoreNode(stage, defaultProps());
326
+ const child = new CoreNode(stage, defaultProps());
326
327
  const mockTexture = mock<ImageTexture>();
327
328
  mockTexture.state = 'loading';
328
329
 
@@ -337,8 +338,8 @@ describe('set color()', () => {
337
338
  });
338
339
 
339
340
  it('should cleanup autosize manager when disabled', () => {
340
- const parent = new CoreNode(stage, defaultProps);
341
- const child = new CoreNode(stage, defaultProps);
341
+ const parent = new CoreNode(stage, defaultProps());
342
+ const child = new CoreNode(stage, defaultProps());
342
343
 
343
344
  child.parent = parent;
344
345
  parent.autosize = true;
@@ -349,8 +350,8 @@ describe('set color()', () => {
349
350
  });
350
351
 
351
352
  it('should establish autosize chain when child is added to autosize parent', () => {
352
- const parent = new CoreNode(stage, defaultProps);
353
- const child = new CoreNode(stage, defaultProps);
353
+ const parent = new CoreNode(stage, defaultProps());
354
+ const child = new CoreNode(stage, defaultProps());
354
355
 
355
356
  // Enable autosize BEFORE adding child
356
357
  parent.autosize = true;
@@ -361,8 +362,8 @@ describe('set color()', () => {
361
362
  });
362
363
 
363
364
  it('should remove from autosize chain when child is removed', () => {
364
- const parent = new CoreNode(stage, defaultProps);
365
- const child = new CoreNode(stage, defaultProps);
365
+ const parent = new CoreNode(stage, defaultProps());
366
+ const child = new CoreNode(stage, defaultProps());
366
367
 
367
368
  // Enable autosize BEFORE adding child
368
369
  parent.autosize = true;
@@ -376,12 +377,12 @@ describe('set color()', () => {
376
377
 
377
378
  describe('isSimple optimization', () => {
378
379
  it('should be simple by default', () => {
379
- const node = new CoreNode(stage, defaultProps);
380
+ const node = new CoreNode(stage, defaultProps());
380
381
  expect(node.isSimple).toBe(true);
381
382
  });
382
383
 
383
384
  it('should not be simple if rotated', () => {
384
- const node = new CoreNode(stage, defaultProps);
385
+ const node = new CoreNode(stage, defaultProps());
385
386
  node.rotation = 0.1;
386
387
  expect(node.isSimple).toBe(false);
387
388
  node.rotation = 0;
@@ -389,7 +390,7 @@ describe('set color()', () => {
389
390
  });
390
391
 
391
392
  it('should not be simple if scaled', () => {
392
- const node = new CoreNode(stage, defaultProps);
393
+ const node = new CoreNode(stage, defaultProps());
393
394
  node.scale = 1.1;
394
395
  expect(node.isSimple).toBe(false);
395
396
  node.scale = 1;
@@ -407,7 +408,7 @@ describe('set color()', () => {
407
408
  });
408
409
 
409
410
  it('should not be simple if mounted', () => {
410
- const node = new CoreNode(stage, defaultProps);
411
+ const node = new CoreNode(stage, defaultProps());
411
412
  node.mount = 0.5;
412
413
  expect(node.isSimple).toBe(false);
413
414
  node.mount = 0;
@@ -425,7 +426,7 @@ describe('set color()', () => {
425
426
  });
426
427
 
427
428
  it('should not be simple if texture is contained', () => {
428
- const node = new CoreNode(stage, defaultProps);
429
+ const node = new CoreNode(stage, defaultProps());
429
430
  const mockTexture = mock<ImageTexture>({
430
431
  state: 'loaded',
431
432
  dimensions: { w: 100, h: 100 },
@@ -442,9 +443,9 @@ describe('set color()', () => {
442
443
  });
443
444
 
444
445
  it('should update local transform correctly when simple', () => {
445
- const parent = new CoreNode(stage, defaultProps);
446
+ const parent = new CoreNode(stage, defaultProps());
446
447
  parent.globalTransform = Matrix3d.identity();
447
- const node = new CoreNode(stage, { ...defaultProps, parent });
448
+ const node = new CoreNode(stage, defaultProps({ parent }));
448
449
  node.x = 100;
449
450
  node.y = 50;
450
451
  node.props.w = 50;
@@ -459,9 +460,9 @@ describe('set color()', () => {
459
460
  });
460
461
 
461
462
  it('should update local transform correctly when not simple (rotation)', () => {
462
- const parent = new CoreNode(stage, defaultProps);
463
+ const parent = new CoreNode(stage, defaultProps());
463
464
  parent.globalTransform = Matrix3d.identity();
464
- const node = new CoreNode(stage, { ...defaultProps, parent });
465
+ const node = new CoreNode(stage, defaultProps({ parent }));
465
466
  node.x = 100;
466
467
  node.y = 50;
467
468
  node.props.w = 100; // use props.w directly to avoid trigger setters if exist (though setters exist)
@@ -482,10 +483,10 @@ describe('set color()', () => {
482
483
 
483
484
  describe('isSimple Global Transform', () => {
484
485
  it('should calculate global transform correctly when simple', () => {
485
- const parent = new CoreNode(stage, defaultProps);
486
+ const parent = new CoreNode(stage, defaultProps());
486
487
  parent.globalTransform = Matrix3d.translate(10, 20);
487
488
 
488
- const node = new CoreNode(stage, { ...defaultProps, parent });
489
+ const node = new CoreNode(stage, defaultProps({ parent }));
489
490
  node.x = 100;
490
491
  node.y = 50;
491
492
  // node is simple by default
@@ -501,10 +502,10 @@ describe('set color()', () => {
501
502
  });
502
503
 
503
504
  it('should calculate global transform correctly when not simple', () => {
504
- const parent = new CoreNode(stage, defaultProps);
505
+ const parent = new CoreNode(stage, defaultProps());
505
506
  parent.globalTransform = Matrix3d.translate(10, 20);
506
507
 
507
- const node = new CoreNode(stage, { ...defaultProps, parent });
508
+ const node = new CoreNode(stage, defaultProps({ parent }));
508
509
  node.x = 100;
509
510
  node.y = 50;
510
511
  node.w = 100;
@@ -13,7 +13,8 @@ import type { BufferCollection } from './renderers/webgl/internal/BufferCollecti
13
13
  import type { CoreRenderer } from './renderers/CoreRenderer.js';
14
14
  import type { Stage } from './Stage.js';
15
15
  import {
16
- type Texture,
16
+ Texture,
17
+ TextureType,
17
18
  type TextureCoords,
18
19
  type TextureFailedEventHandler,
19
20
  type TextureFreedEventHandler,
@@ -838,53 +839,19 @@ export class CoreNode extends EventEmitter {
838
839
  let initialUpdateType =
839
840
  UpdateType.Local | UpdateType.RenderBounds | UpdateType.RenderState;
840
841
 
841
- // Fast-path assign only known keys
842
- const p = (this.props = {
843
- x: props.x,
844
- y: props.y,
845
- w: props.w,
846
- h: props.h,
847
- alpha: props.alpha,
848
- autosize: props.autosize,
849
- clipping: props.clipping,
850
- color: props.color,
851
- colorTop: props.colorTop,
852
- colorBottom: props.colorBottom,
853
- colorLeft: props.colorLeft,
854
- colorRight: props.colorRight,
855
- colorTl: props.colorTl,
856
- colorTr: props.colorTr,
857
- colorBl: props.colorBl,
858
- colorBr: props.colorBr,
859
- scaleX: props.scaleX,
860
- scaleY: props.scaleY,
861
- rotation: props.rotation,
862
- pivotX: props.pivotX,
863
- pivotY: props.pivotY,
864
- mountX: props.mountX,
865
- mountY: props.mountY,
866
- mount: props.mount,
867
- pivot: props.pivot,
868
- zIndex: props.zIndex,
869
- textureOptions: props.textureOptions,
870
- data: props.data,
871
- imageType: props.imageType,
872
- srcX: props.srcX,
873
- srcY: props.srcY,
874
- srcWidth: props.srcWidth,
875
- srcHeight: props.srcHeight,
876
- parent: props.parent,
877
- texture: null,
878
- shader: null,
879
- src: null,
880
- rtt: false,
881
- boundsMargin: null,
882
- scale: null,
883
- interactive: props.interactive,
884
- preventDestroy: props.preventDestroy,
885
- componentName: props.componentName,
886
- componentLocation: props.componentLocation,
887
- });
842
+ // Use the incoming props object directly — resolveNodeDefaults already
843
+ // creates a fresh object with a consistent shape. Save fields that are
844
+ // re-applied through setters, then null them on props so the setters
845
+ // detect the change.
846
+ const { texture, shader, src, rtt, boundsMargin, interactive, parent } =
847
+ props;
848
+ const p = (this.props = props);
849
+ p.texture = null;
850
+ p.shader = null;
851
+ p.src = null;
852
+ p.rtt = false;
853
+ p.boundsMargin = null;
854
+ p.scale = null;
888
855
 
889
856
  //check if any color props are set for premultiplied color updates
890
857
  if (
@@ -907,17 +874,17 @@ export class CoreNode extends EventEmitter {
907
874
  this.zIndex = p.zIndex;
908
875
  }
909
876
 
910
- if (props.parent !== null) {
911
- props.parent.addChild(this);
877
+ if (parent !== null) {
878
+ parent.addChild(this);
912
879
  }
913
880
 
914
- // Assign props to instances
915
- this.texture = props.texture;
916
- this.shader = props.shader;
917
- this.src = props.src;
918
- this.rtt = props.rtt;
919
- this.boundsMargin = props.boundsMargin;
920
- this.interactive = props.interactive;
881
+ // Assign saved values through setters
882
+ this.texture = texture;
883
+ this.shader = shader;
884
+ this.src = src;
885
+ this.rtt = rtt;
886
+ this.boundsMargin = boundsMargin;
887
+ this.interactive = interactive;
921
888
 
922
889
  // Initialize autosize if enabled
923
890
  if (p.autosize === true) {
@@ -2774,6 +2741,11 @@ export class CoreNode extends EventEmitter {
2774
2741
  this.loadTexture();
2775
2742
  }
2776
2743
 
2744
+ if (this.texture?.type === TextureType.subTexture && this.textureLoaded) {
2745
+ // When setting the texture value of a subtexture but the atlas is already loaded,
2746
+ // requestRenderListUpdate is not triggered, but we still need to update the quad
2747
+ this.isQuadDirty = true;
2748
+ }
2777
2749
  this.setUpdateType(UpdateType.IsRenderable);
2778
2750
  this.updateIsSimple();
2779
2751
  }
@@ -2837,6 +2809,15 @@ export class CoreNode extends EventEmitter {
2837
2809
  values.start = (this as any)[name] ?? 0;
2838
2810
  values.target = value;
2839
2811
  controller.progress = 0;
2812
+
2813
+ if (settings.adaptiveDuration === true) {
2814
+ const now = performance.now();
2815
+ const elapsed = now - controller.lastRunTime;
2816
+ controller.lastRunTime = now;
2817
+ const duration = settings.duration ?? controller.duration;
2818
+ controller.duration = elapsed < duration ? elapsed : duration;
2819
+ }
2820
+
2840
2821
  return controller.start();
2841
2822
  }
2842
2823
  }
@@ -2852,6 +2833,14 @@ export class CoreNode extends EventEmitter {
2852
2833
  return controller.start();
2853
2834
  }
2854
2835
 
2836
+ animateToTarget(prop: string): number | undefined {
2837
+ const animation = this._animations[prop];
2838
+ if (!animation) {
2839
+ return undefined;
2840
+ }
2841
+ return animation.controller.props?.[prop]?.target;
2842
+ }
2843
+
2855
2844
  flush() {
2856
2845
  // no-op
2857
2846
  }
@@ -6,7 +6,7 @@ import type {
6
6
  TextRenderInfo,
7
7
  SdfVertexCache,
8
8
  } from './text-rendering/TextRenderer.js';
9
- import { USE_RTT } from '../utils.js';
9
+ import { USE_RTT, premultiplyColorABGR } from '../utils.js';
10
10
  import {
11
11
  CoreNode,
12
12
  CoreNodeRenderState,
@@ -239,27 +239,23 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
239
239
  // Handle Canvas renderer (uses ImageData)
240
240
  if (textRendererType === 'canvas') {
241
241
  if (result.imageData === undefined) {
242
- this.emit('failed', {
243
- type: 'text',
244
- error: new Error(
245
- 'Canvas text rendering failed, no image data returned',
246
- ),
247
- } satisfies NodeTextFailedPayload);
248
- return;
249
- }
250
-
251
- this.texture = this.stage.txManager.createTexture('ImageTexture', {
252
- premultiplyAlpha: true,
253
- src: result.imageData as ImageData,
254
- });
255
- // It isn't renderable until the texture is loaded we have to set it to false here to avoid it
256
- // being detected as a renderable default color node in the next frame
257
- // it will be corrected once the texture is loaded
258
- this.setRenderable(false);
259
-
260
- if (this.renderState > CoreNodeRenderState.OutOfBounds) {
261
- // We do want the texture to load immediately
262
- this.texture.setRenderableOwner(this._id, true);
242
+ // Empty text returns no imageData — mark not renderable and continue
243
+ // to update dimensions (w=0, h=0) rather than emitting a failure.
244
+ this.setRenderable(false);
245
+ } else {
246
+ this.texture = this.stage.txManager.createTexture('ImageTexture', {
247
+ premultiplyAlpha: true,
248
+ src: result.imageData as ImageData,
249
+ });
250
+ // It isn't renderable until the texture is loaded we have to set it to false here to avoid it
251
+ // being detected as a renderable default color node in the next frame
252
+ // it will be corrected once the texture is loaded
253
+ this.setRenderable(false);
254
+
255
+ if (this.renderState > CoreNodeRenderState.OutOfBounds) {
256
+ // We do want the texture to load immediately
257
+ this.texture.setRenderableOwner(this._id, true);
258
+ }
263
259
  }
264
260
  }
265
261
 
@@ -300,8 +296,15 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
300
296
  return;
301
297
  }
302
298
 
303
- // Canvas renderer: use standard texture rendering via CoreNode
299
+ // Canvas renderer: color is baked into the texture to preserve
300
+ // native emoji colors. Use white tint with worldAlpha only.
304
301
  if (this._type === 'canvas') {
302
+ const white = premultiplyColorABGR(0xffffffff, this.worldAlpha);
303
+ this.premultipliedColorTl =
304
+ this.premultipliedColorTr =
305
+ this.premultipliedColorBl =
306
+ this.premultipliedColorBr =
307
+ white;
305
308
  super.renderQuads(renderer);
306
309
  return;
307
310
  }
@@ -558,6 +561,18 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
558
561
  }
559
562
  }
560
563
 
564
+ override get color(): number {
565
+ return this.props.color;
566
+ }
567
+
568
+ override set color(value: number) {
569
+ super.color = value;
570
+ if (this._type === 'canvas') {
571
+ this._layoutGenerated = false;
572
+ this.setUpdateType(UpdateType.Local);
573
+ }
574
+ }
575
+
561
576
  get forceLoad() {
562
577
  return this.textProps.forceLoad;
563
578
  }
@@ -340,8 +340,6 @@ export class CoreTextureManager extends EventEmitter {
340
340
  * @param immediate - Whether to prioritize the texture for immediate loading
341
341
  */
342
342
  async loadTexture(texture: Texture, priority?: boolean): Promise<void> {
343
- this.stage.txMemManager.removeFromOrphanedTextures(texture);
344
-
345
343
  if (texture.type === TextureType.subTexture) {
346
344
  // ignore subtextures - they get loaded through their parent
347
345
  return;
@@ -98,7 +98,6 @@ export interface MemoryInfo {
98
98
  export class TextureMemoryManager {
99
99
  private memUsed = 0;
100
100
  private loadedTextures: Set<Texture> = new Set();
101
- private orphanedTextures: Set<Texture> = new Set();
102
101
  private criticalThreshold: number = 124e6;
103
102
  private targetThreshold: number = 0.5;
104
103
  private cleanupInterval: number = 5000;
@@ -127,32 +126,6 @@ export class TextureMemoryManager {
127
126
  this.updateSettings(settings);
128
127
  }
129
128
 
130
- /**
131
- * Add a texture to the orphaned textures list
132
- *
133
- * @param texture - The texture to add to the orphaned textures list
134
- */
135
- addToOrphanedTextures(texture: Texture) {
136
- // if the texture is already in the orphaned textures list add it at the end
137
- if (this.orphanedTextures.has(texture)) {
138
- this.orphanedTextures.delete(texture);
139
- }
140
-
141
- // If the texture can be cleaned up, add it to the orphaned textures list
142
- if (texture.preventCleanup === false) {
143
- this.orphanedTextures.add(texture);
144
- }
145
- }
146
-
147
- /**
148
- * Remove a texture from the orphaned textures list
149
- *
150
- * @param texture - The texture to remove from the orphaned textures list
151
- */
152
- removeFromOrphanedTextures(texture: Texture) {
153
- this.orphanedTextures.delete(texture);
154
- }
155
-
156
129
  /**
157
130
  * Set the memory usage of a texture
158
131
  *
@@ -16,6 +16,7 @@ export interface AnimationConfig extends IAnimationController {
16
16
  manager: AnimationManager;
17
17
  node: CoreNode;
18
18
  duration: number;
19
+ lastRunTime: number;
19
20
  delayFor: number;
20
21
  delay: number;
21
22
  progress: number;
@@ -8,6 +8,7 @@ import type {
8
8
 
9
9
  export interface AnimationSettings {
10
10
  duration: number;
11
+ adaptiveDuration: boolean;
11
12
  delay: number;
12
13
  easing: string | TimingFunction;
13
14
  loop: boolean;
@@ -23,7 +24,6 @@ export function createAnimation(
23
24
  ): AnimationConfig {
24
25
  const easing = settings.easing || 'linear';
25
26
  const delay = settings.delay ?? 0;
26
-
27
27
  let propValues: Record<string, PropValues> | null = null;
28
28
  let shaderPropValues: Record<string, PropValues> | null = null;
29
29
 
@@ -73,6 +73,7 @@ export function createAnimation(
73
73
  shaderProps: shaderPropValues,
74
74
  stoppedResolve: null,
75
75
  stoppedPromise: null,
76
+ lastRunTime: settings.adaptiveDuration ? performance.now() : 0,
76
77
 
77
78
  start() {
78
79
  if (this.state !== 'running' && this.state !== 'scheduled') {