@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
162 lines (149 loc) • 5.3 kB
text/typescript
import {BaseNodeClassWithDisplayFlag, BaseNodeType} from '../_Base';
import {CoreGraphNode} from '../../../core/graph/CoreGraphNode';
type DisplayControllerCallback = () => void;
export interface DisplayNodeControllerCallbacks {
onDisplayNodeRemove: DisplayControllerCallback;
onDisplayNodeSet: DisplayControllerCallback;
onDisplayNodeUpdate: DisplayControllerCallback;
}
/*
handles callbacks when the children's display flag is updated
*/
interface DisplayNodeControllerOptions {
dependsOnDisplayNode: boolean;
}
const DEFAULT_DISPLAY_NODE_CONTROLLER_OPTIONS: DisplayNodeControllerOptions = {
dependsOnDisplayNode: true,
};
function _warnNotInitialized(node: BaseNodeType) {
console.error('displayNodeController not initialized', node);
}
function _warnAlreadyInitialized(node: BaseNodeType) {
console.error('displayNodeController already initialed', node);
}
export class DisplayNodeController {
private _initialized: boolean = false;
private _graphNode: CoreGraphNode;
private _displayNode: BaseNodeClassWithDisplayFlag | undefined = undefined;
private _displayNodeOverride: BaseNodeClassWithDisplayFlag | undefined = undefined;
private _onDisplayNodeRemoveCallback: DisplayControllerCallback | undefined;
private _onDisplayNodeSetCallback: DisplayControllerCallback | undefined;
private _onDisplayNodeUpdateCallback: DisplayControllerCallback | undefined;
// TODO: the node could be a different than BaseNodeType
// at least there should be a way to infer that it is a node
// with children that have a display flag. This would avoid all the flags?.display?... below
constructor(
protected node: BaseNodeType,
callbacks: DisplayNodeControllerCallbacks,
private options: DisplayNodeControllerOptions = DEFAULT_DISPLAY_NODE_CONTROLLER_OPTIONS
) {
this._graphNode = new CoreGraphNode(node.scene(), `DisplayNodeController-${node.name()}`);
this._onDisplayNodeRemoveCallback = callbacks.onDisplayNodeRemove;
this._onDisplayNodeSetCallback = callbacks.onDisplayNodeSet;
this._onDisplayNodeUpdateCallback = callbacks.onDisplayNodeUpdate;
}
dispose() {
this._graphNode.dispose();
}
displayNode() {
return this._displayNodeOverride || this._displayNode;
}
firstNonBypassedDisplayNode() {
return this.displayNode()?.containerController.firstNonBypassedNode();
}
initializeNode() {
if (this._initialized) {
_warnAlreadyInitialized(this.node);
return;
}
this._initialized = true;
this.node.lifecycle.onChildAdd((childNode) => {
if (!this._displayNode) {
childNode.flags?.display?.set(true);
}
});
this.node.lifecycle.onChildRemove((childNode) => {
if (childNode.graphNodeId() == this._displayNode?.graphNodeId()) {
const children = this.node.children();
// Go through each child in reverse until one has a display flag.
// we should not simoly check the last child, as it may not have a display flag
// (like a network node).
for (let i = children.length - 1; i >= 0; i--) {
const child = children[i];
const displayFlag = child.flags?.display;
if (displayFlag) {
displayFlag.set(true);
return;
}
}
this.setDisplayNode(undefined);
}
});
this._graphNode.dirtyController.addPostDirtyHook('_requestDisplayNodeContainer', () => {
if (this._onDisplayNodeUpdateCallback) {
this._onDisplayNodeUpdateCallback();
}
});
}
setDisplayNodeOverride(newDisplayNodeOverride: BaseNodeClassWithDisplayFlag | undefined) {
if (!this._initialized) {
_warnNotInitialized(this.node);
}
const currentDisplayNode = this._displayNodeOverride;
if (currentDisplayNode != newDisplayNodeOverride) {
const oldDisplayNode = currentDisplayNode;
if (oldDisplayNode) {
// oldDisplayNode.flags.display.set(false);
if (this.options.dependsOnDisplayNode) {
this._graphNode.removeGraphInput(oldDisplayNode);
}
if (this._onDisplayNodeRemoveCallback) {
this._onDisplayNodeRemoveCallback();
}
}
this._displayNodeOverride = newDisplayNodeOverride;
if (newDisplayNodeOverride) {
if (this.options.dependsOnDisplayNode) {
this._graphNode.addGraphInput(newDisplayNodeOverride);
}
if (this._onDisplayNodeSetCallback) {
this._onDisplayNodeSetCallback();
}
} else {
if (this._displayNode) {
this._commitDisplayNode(this._displayNode);
}
}
}
}
setDisplayNode(newDisplayNode: BaseNodeClassWithDisplayFlag | undefined) {
if (!this._initialized) {
_warnNotInitialized(this.node);
}
const currentDisplayNode = this._displayNode;
if (currentDisplayNode != newDisplayNode) {
const oldDisplayNode = currentDisplayNode;
if (oldDisplayNode) {
oldDisplayNode.flags.display.set(false);
if (this.options.dependsOnDisplayNode) {
this._graphNode.removeGraphInput(oldDisplayNode);
}
if (this._onDisplayNodeRemoveCallback) {
this._onDisplayNodeRemoveCallback();
}
}
this._displayNode = newDisplayNode;
if (newDisplayNode) {
this._commitDisplayNode(newDisplayNode);
}
}
}
private _commitDisplayNode(newDisplayNode: BaseNodeClassWithDisplayFlag) {
if (this.options.dependsOnDisplayNode) {
this._graphNode.addGraphInput(newDisplayNode);
}
if (this._onDisplayNodeSetCallback) {
this._onDisplayNodeSetCallback();
}
}
}