@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
JavaScript
// 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