From 00bdfe8c6b94b716556ebe4f09581ffce268ea11 Mon Sep 17 00:00:00 2001 From: wouterlucas Date: Thu, 11 Dec 2025 15:57:13 +0100 Subject: [PATCH] feat: add renderable event to CoreNode --- src/common/CommonTypes.ts | 16 ++++++++++++ src/core/CoreNode.test.ts | 51 ++++++++++++++++++++++++++++++++++++++- src/core/CoreNode.ts | 11 +++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/common/CommonTypes.ts b/src/common/CommonTypes.ts index 65940171..df36c5e1 100644 --- a/src/common/CommonTypes.ts +++ b/src/common/CommonTypes.ts @@ -81,6 +81,14 @@ export type NodeTextureFreedPayload = { type: 'texture'; }; +/** + * Payload for when node renderable status changes + */ +export type NodeRenderablePayload = { + type: 'renderable'; + isRenderable: boolean; +}; + /** * Combined type for all failed payloads */ @@ -104,6 +112,14 @@ export type NodeFailedEventHandler = ( payload: NodeFailedPayload, ) => void; +/** + * Event handler for when the renderable status of a node changes + */ +export type NodeRenderableEventHandler = ( + target: any, + payload: NodeRenderablePayload, +) => void; + export type NodeRenderStatePayload = { type: 'renderState'; payload: CoreNodeRenderState; diff --git a/src/core/CoreNode.test.ts b/src/core/CoreNode.test.ts index 4e519d70..eaf3a1c4 100644 --- a/src/core/CoreNode.test.ts +++ b/src/core/CoreNode.test.ts @@ -17,7 +17,7 @@ * limitations under the License. */ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { CoreNode, type CoreNodeProps, UpdateType } from './CoreNode.js'; import { Stage } from './Stage.js'; import { CoreRenderer } from './renderers/CoreRenderer.js'; @@ -199,5 +199,54 @@ describe('set color()', () => { node.update(0, clippingRect); expect(node.isRenderable).toBe(false); }); + + it('should emit renderable event when isRenderable status changes', () => { + const node = new CoreNode(stage, defaultProps); + const eventCallback = vi.fn(); + + // Listen for the renderableChanged event + node.on('renderable', eventCallback); + + // Set up node as a color texture that should be renderable + node.alpha = 1; + node.x = 0; + node.y = 0; + node.w = 100; + node.h = 100; + node.color = 0xffffffff; + + // Initial state should be false + expect(node.isRenderable).toBe(false); + expect(eventCallback).not.toHaveBeenCalled(); + + // Update should make it renderable (false -> true) + node.update(0, clippingRect); + expect(node.isRenderable).toBe(true); + expect(eventCallback).toHaveBeenCalledWith(node, { + type: 'renderable', + isRenderable: true, + }); + + // Reset the mock + eventCallback.mockClear(); + + // Make node invisible (alpha = 0) to make it not renderable (true -> false) + node.alpha = 0; + node.update(1, clippingRect); + expect(node.isRenderable).toBe(false); + expect(eventCallback).toHaveBeenCalledWith(node, { + type: 'renderable', + isRenderable: false, + }); + + // Reset the mock again + eventCallback.mockClear(); + + // Setting same value shouldn't trigger event + node.alpha = 0; + node.update(2, clippingRect); + expect(node.isRenderable).toBe(false); + expect(eventCallback).not.toHaveBeenCalled(); + }); }); }); diff --git a/src/core/CoreNode.ts b/src/core/CoreNode.ts index fb844f61..bf40cc4b 100644 --- a/src/core/CoreNode.ts +++ b/src/core/CoreNode.ts @@ -37,6 +37,7 @@ import type { NodeTextureFailedPayload, NodeTextureFreedPayload, NodeTextureLoadedPayload, + NodeRenderablePayload, } from '../common/CommonTypes.js'; import { EventEmitter } from '../common/EventEmitter.js'; import { @@ -1486,7 +1487,17 @@ export class CoreNode extends EventEmitter { * @param isRenderable - The new renderable state */ setRenderable(isRenderable: boolean) { + const previousIsRenderable = this.isRenderable; this.isRenderable = isRenderable; + + // Emit event if renderable status has changed + if (previousIsRenderable !== isRenderable) { + this.emit('renderable', { + type: 'renderable', + isRenderable, + } satisfies NodeRenderablePayload); + } + if ( isRenderable === true && this.stage.calculateTextureCoord === true &&