UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.

171 lines • 6.71 kB
// import { Canvas } from './Canvas.js'; import { AxesHelper } from 'three'; import * as ThreeMeshUI from 'three-mesh-ui'; import { showGizmos } from '../../engine/engine_default_parameters.js'; import { getParam } from '../../engine/engine_utils.js'; import { Behaviour, GameObject } from "../Component.js"; import { EventSystem } from "./EventSystem.js"; import { $shadowDomOwner } from './Symbols.js'; export const includesDir = "./include"; const debug = getParam("debugshadowcomponents"); // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy ThreeMeshUI.Block.prototype["interactable"] = { get() { return this.interactive; }, set(value) { this.interactable = value; } }; /** * Derive from this class if you want to implement your own UI components. * It provides utility methods and simplifies managing the underlying three-mesh-ui hierarchy. * @category User Interface * @group Components */ export class BaseUIComponent extends Behaviour { /** Is this object on the root of the UI hierarchy ? */ isRoot() { return this.Root?.gameObject === this.gameObject; } /** Access the parent canvas component */ get canvas() { const cv = this.Root; if (cv?.isCanvas) return cv; return null; } /** @deprecated use `canvas` */ get Canvas() { return this.canvas; } /** Mark the UI dirty which will trigger an THREE-Mesh-UI update */ markDirty() { EventSystem.markUIDirty(this.context); } /** the underlying three-mesh-ui */ get shadowComponent() { return this._shadowComponent; } set shadowComponent(val) { this._shadowComponent = val; } _shadowComponent = null; _controlsChildLayout = true; get controlsChildLayout() { return this._controlsChildLayout; } set controlsChildLayout(val) { this._controlsChildLayout = val; if (this.shadowComponent) { //@ts-ignore this.shadowComponent.autoLayout = val; } } _root = undefined; get Root() { if (this._root === undefined) { this._root = GameObject.getComponentInParent(this.gameObject, UIRootComponent); } return this._root; } // private _intermediate?: Object3D; _parentComponent = undefined; __internalNewInstanceCreated(args) { super.__internalNewInstanceCreated(args); this.shadowComponent = null; this._root = undefined; this._parentComponent = undefined; return this; } onEnable() { super.onEnable(); } /** Add a three-mesh-ui object to the UI hierarchy * @param container the three-mesh-ui object to add * @param parent the parent component to add the object to */ addShadowComponent(container, parent) { if (!container) return; this.removeShadowComponent(); // instead of inserting here, we attach to the matching shadow hierarchy starting with the Canvas component. const searchFrom = this.isRoot() ? this.gameObject : this.gameObject.parent; this._parentComponent = GameObject.getComponentInParent(searchFrom, BaseUIComponent); if (!this._parentComponent) { console.warn(`Component \"${this.name}\" doesn't have a UI parent anywhere. Do you have an UI element outside a Canvas? UI components must be a child of a Canvas component`, this); return; } container.name = this.name + " (" + (this.constructor.name ?? "UI") + ")"; container.autoLayout = this._parentComponent.controlsChildLayout; container[$shadowDomOwner] = this; // TODO: raycastTarget doesnt work anymore -> i think we need to set the gameObject layer and then check in the raycaster if the shadowComponentOwner is on the correct layer?! // const raycastTarget = (this as unknown as IGraphic).raycastTarget; // this.gameObject.layers.set(2) this.setShadowComponentOwner(container); let needsUpdate = false; if (this.Root?.gameObject === this.gameObject) { this.gameObject.add(container); } else { const targetShadowComponent = this._parentComponent.shadowComponent; if (targetShadowComponent) { // console.log("ADD", this.name, "to", this._parentComponent.name, targetShadowComponent); targetShadowComponent?.add(container); needsUpdate = true; } } this.shadowComponent = container; if (parent && parent.shadowComponent && this.shadowComponent) { parent.shadowComponent.add(this.shadowComponent); } // this.applyTransform(); if (showGizmos) { container.add(new AxesHelper(.5)); } this.onAfterAddedToScene(); // make sure to update the layout when adding content // otherwise it will fail when object are enabled at runtime if (needsUpdate) ThreeMeshUI.update(); if (debug) console.warn("Added shadow component", this.shadowComponent); } setShadowComponentOwner(current) { if (!current) return; // TODO: only traverse our own hierarchy, we can stop if we find another owner if (current[$shadowDomOwner] === undefined || current[$shadowDomOwner] === this) { current[$shadowDomOwner] = this; if (current.children) { for (const ch of current.children) { this.setShadowComponentOwner(ch); } } } } traverseOwnedShadowComponents(current, owner, callback) { if (!current) return; if (current[$shadowDomOwner] === owner) { callback(current); for (const ch of current.children) { this.traverseOwnedShadowComponents(ch, owner, callback); } } } /** Remove the underlying UI object from the hierarchy */ removeShadowComponent() { if (this.shadowComponent) { this.shadowComponent.removeFromParent(); } } onAfterAddedToScene() { } setInteractable(value) { if (this.shadowComponent) { //@ts-ignore this.shadowComponent.interactable = value; } } } export class UIRootComponent extends BaseUIComponent { awake() { super.awake(); } } //# sourceMappingURL=BaseUIComponent.js.map