Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/common/CommonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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;
Expand Down
51 changes: 50 additions & 1 deletion src/core/CoreNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
});
});
});
11 changes: 11 additions & 0 deletions src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type {
NodeTextureFailedPayload,
NodeTextureFreedPayload,
NodeTextureLoadedPayload,
NodeRenderablePayload,
} from '../common/CommonTypes.js';
import { EventEmitter } from '../common/EventEmitter.js';
import {
Expand Down Expand Up @@ -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 &&
Expand Down