From c3329902b069281b447482e426776326759a194e Mon Sep 17 00:00:00 2001 From: jfboeve Date: Fri, 24 Apr 2026 12:58:34 +0200 Subject: [PATCH] changed the way interactive nodes are tracked. --- examples/tests/mouse-interactive.ts | 54 +++++++++++++++++++++++++++ src/core/CoreNode.ts | 8 ++-- src/core/Stage.ts | 44 ++++++++++++++++++++-- src/core/platforms/web/WebPlatform.ts | 1 + 4 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 examples/tests/mouse-interactive.ts diff --git a/examples/tests/mouse-interactive.ts b/examples/tests/mouse-interactive.ts new file mode 100644 index 000000000..26095dc04 --- /dev/null +++ b/examples/tests/mouse-interactive.ts @@ -0,0 +1,54 @@ +import type { ExampleSettings } from '../common/ExampleSettings.js'; + +export default async function ({ renderer, testRoot }: ExampleSettings) { + const APP = renderer.createNode({ + x: 0, + y: 0, + w: 1920, + h: 1080, + color: 0x000000ff, + parent: testRoot, + interactive: false, + }); + + const MOUSE = renderer.createNode({ + x: 40, + y: 40, + w: 200, + h: 200, + color: 0xffffffff, + parent: APP, + interactive: true, + }); + + const MOUSE_RING = renderer.createNode({ + x: 40, + y: 40, + w: 200, + h: 200, + color: 0xff000000, + parent: APP, + interactive: false, + shader: renderer.createShader('Border', { w: 20, color: 0xff0000ff }), + }); + + //print node ids: + console.log('APP node id:', APP.id); + console.log('MOUSE node id:', MOUSE.id); + console.log('MOUSE_RING node id:', MOUSE_RING.id); + + window.addEventListener('keydown', (e) => { + if (e.key === 'c') { + APP.interactive = !APP.interactive; + console.log('APP interactive:', APP.interactive); + } + }); + + window.addEventListener('mousemove', (e) => { + const node = renderer.stage.getNodeFromPosition({ + x: e.clientX, + y: e.clientY, + }); + console.log('node at position', node); + }); +} diff --git a/src/core/CoreNode.ts b/src/core/CoreNode.ts index 363666666..19bb51684 100644 --- a/src/core/CoreNode.ts +++ b/src/core/CoreNode.ts @@ -2680,11 +2680,13 @@ export class CoreNode extends EventEmitter { } set interactive(value: boolean | undefined) { + if (this.props.interactive === value) { + return; + } + this.props.interactive = value; // Update Stage's interactive Set - if (value === true) { - this.stage.interactiveNodes.add(this); - } + this.stage.hasInteractiveNodeChanges = true; } get interactive(): boolean | undefined { diff --git a/src/core/Stage.ts b/src/core/Stage.ts index c8220df3a..b5e81bb89 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -99,7 +99,9 @@ export class Stage { public readonly shManager: CoreShaderManager; public readonly renderer: CoreRenderer; public readonly root: CoreNode; - public readonly interactiveNodes: Set = new Set(); + public readonly interactiveNodes: CoreNode[] = []; + public hasInteractiveNodeChanges = false; + public boundsMargin: [number, number, number, number]; public readonly defShaderNode: CoreShaderNode | null = null; public strictBound: Bound; @@ -396,6 +398,33 @@ export class Stage { }); } + updateInteractiveNodes() { + if (this.hasInteractiveNodeChanges === false) { + return; + } + + this.interactiveNodes.length = 0; + + this.findInteractiveNodes(this.root); + + this.hasInteractiveNodeChanges = false; + } + + findInteractiveNodes(node: CoreNode) { + if (node.interactive === true && node.isRenderable === true) { + this.interactiveNodes.push(node); + } + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + if (child === undefined) { + continue; + } + if (child.renderState === CoreNodeRenderState.InViewport) { + this.findInteractiveNodes(child); + } + } + } + /** * Create default PixelTexture */ @@ -476,12 +505,14 @@ export class Stage { renderer.renderRTTNodes(); } + this.interactiveNodes.length = 0; // Fill quads buffer this.addQuads(this.root); // Perform render pass renderer.render(); + this.hasInteractiveNodeChanges = false; this.calculateFps(); this.calculateQuads(); @@ -576,6 +607,13 @@ export class Stage { // If the node is renderable and has a loaded texture, render it if (node.isRenderable === true) { node.renderQuads(this.renderer); + + if ( + node.interactive === true && + node.renderState === CoreNodeRenderState.InViewport + ) { + this.interactiveNodes.push(node); + } } for (let i = 0; i < node.children.length; i++) { @@ -733,10 +771,8 @@ export class Stage { const x = data.x / this.options.deviceLogicalPixelRatio; const y = data.y / this.options.deviceLogicalPixelRatio; const nodes: CoreNode[] = []; + for (const node of this.interactiveNodes) { - if (node.isRenderable === false) { - continue; - } if (pointInBound(x, y, node.renderBound!) === true) { nodes.push(node); } diff --git a/src/core/platforms/web/WebPlatform.ts b/src/core/platforms/web/WebPlatform.ts index c0d8c92d8..dcb34ac4f 100644 --- a/src/core/platforms/web/WebPlatform.ts +++ b/src/core/platforms/web/WebPlatform.ts @@ -145,6 +145,7 @@ export class WebPlatform extends Platform { stage.txMemManager.cleanup(); } + stage.updateInteractiveNodes(); stage.flushFrameEvents(); return; }