@babylonjs/gui
Version:
Babylon.js GUI module =====================
401 lines • 12.9 kB
JavaScript
import { Observable } from "@babylonjs/core/Misc/observable.js";
import { Vector3 } from "@babylonjs/core/Maths/math.vector.js";
import { PointerEventTypes } from "@babylonjs/core/Events/pointerEvents.js";
import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh.js";
import { Vector3WithInfo } from "../vector3WithInfo.js";
/**
* Class used as base class for controls
*/
export class Control3D {
/** Gets or sets the control position in world space */
get position() {
if (!this._node) {
this._position = this._position || Vector3.Zero();
return this._position;
}
return this._node.position;
}
set position(value) {
this._position = value;
if (!this._node) {
return;
}
this._node.position = this._position;
}
/** Gets or sets the control scaling in world space */
get scaling() {
if (!this._node) {
this._scaling = this.scaling || new Vector3(1, 1, 1);
return this._scaling;
}
return this._node.scaling;
}
set scaling(value) {
this._scaling = value;
if (!this._node) {
return;
}
this._isScaledByManager = false;
this._node.scaling = this._scaling;
}
/**
* Gets the list of attached behaviors
* @see https://doc.babylonjs.com/features/featuresDeepDive/behaviors
*/
get behaviors() {
return this._behaviors;
}
/**
* Attach a behavior to the control
* @see https://doc.babylonjs.com/features/featuresDeepDive/behaviors
* @param behavior defines the behavior to attach
* @returns the current control
*/
addBehavior(behavior) {
const index = this._behaviors.indexOf(behavior);
if (index !== -1) {
return this;
}
behavior.init();
const scene = this._host.scene;
if (scene.isLoading) {
// We defer the attach when the scene will be loaded
scene.onDataLoadedObservable.addOnce(() => {
behavior.attach(this);
});
}
else {
behavior.attach(this);
}
this._behaviors.push(behavior);
return this;
}
/**
* Remove an attached behavior
* @see https://doc.babylonjs.com/features/featuresDeepDive/behaviors
* @param behavior defines the behavior to attach
* @returns the current control
*/
removeBehavior(behavior) {
const index = this._behaviors.indexOf(behavior);
if (index === -1) {
return this;
}
this._behaviors[index].detach();
this._behaviors.splice(index, 1);
return this;
}
/**
* Gets an attached behavior by name
* @param name defines the name of the behavior to look for
* @see https://doc.babylonjs.com/features/featuresDeepDive/behaviors
* @returns null if behavior was not found else the requested behavior
*/
getBehaviorByName(name) {
for (const behavior of this._behaviors) {
if (behavior.name === name) {
return behavior;
}
}
return null;
}
/** Gets or sets a boolean indicating if the control is visible */
get isVisible() {
return this._isVisible;
}
set isVisible(value) {
if (this._isVisible === value) {
return;
}
this._isVisible = value;
const mesh = this.mesh;
if (mesh) {
mesh.setEnabled(value);
}
}
/**
* Creates a new control
* @param name defines the control name
*/
constructor(
/** Defines the control name */
name) {
this.name = name;
this._downCount = 0;
this._enterCount = -1;
this._downPointerIds = {}; // Store number of pointer downs per ID, from near and far interactions
this._isVisible = true;
/** @internal */
this._isScaledByManager = false;
/**
* An event triggered when the pointer moves over the control
*/
this.onPointerMoveObservable = new Observable();
/**
* An event triggered when the pointer moves out of the control
*/
this.onPointerOutObservable = new Observable();
/**
* An event triggered when the pointer taps the control
*/
this.onPointerDownObservable = new Observable();
/**
* An event triggered when pointer is up
*/
this.onPointerUpObservable = new Observable();
/**
* An event triggered when a control is clicked on (with a mouse)
*/
this.onPointerClickObservable = new Observable();
/**
* An event triggered when pointer enters the control
*/
this.onPointerEnterObservable = new Observable();
// Behaviors
this._behaviors = new Array();
}
/**
* Gets a string representing the class name
*/
get typeName() {
return this._getTypeName();
}
/**
* Get the current class name of the control.
* @returns current class name
*/
getClassName() {
return this._getTypeName();
}
_getTypeName() {
return "Control3D";
}
/**
* Gets the transform node used by this control
*/
get node() {
return this._node;
}
/**
* Gets the mesh used to render this control
*/
get mesh() {
if (this._node instanceof AbstractMesh) {
return this._node;
}
return null;
}
/**
* Link the control as child of the given node
* @param node defines the node to link to. Use null to unlink the control
* @returns the current control
*/
linkToTransformNode(node) {
if (this._node) {
this._node.parent = node;
}
return this;
}
/**
* @internal
*/
_prepareNode(scene) {
if (!this._node) {
this._node = this._createNode(scene);
if (!this.node) {
return;
}
if (this._position) {
this.node.position = this._position;
}
if (this._scaling) {
this.node.scaling = this._scaling;
}
this._injectGUI3DReservedDataStore(this.node).control = this; // Store the control on the reservedDataStore field in order to get it when picking
const mesh = this.mesh;
if (mesh) {
mesh.isPickable = true;
this._affectMaterial(mesh);
}
}
}
_injectGUI3DReservedDataStore(node) {
node.reservedDataStore = node.reservedDataStore ?? {};
node.reservedDataStore.GUI3D = node.reservedDataStore.GUI3D ?? {};
return node.reservedDataStore.GUI3D;
}
/**
* Node creation.
* Can be overriden by children
* @param scene defines the scene where the node must be attached
* @returns the attached node or null if none. Must return a Mesh or AbstractMesh if there is an attached visible object
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_createNode(scene) {
// Do nothing by default
return null;
}
/**
* Affect a material to the given mesh
* @param mesh defines the mesh which will represent the control
*/
_affectMaterial(mesh) {
mesh.material = null;
}
_isTouchButton3D(control) {
return control._generatePointerEventType !== undefined;
}
// Pointers
/**
* @internal
*/
_onPointerMove(target, coordinates) {
this.onPointerMoveObservable.notifyObservers(coordinates, -1, target, this);
}
/**
* @internal
*/
_onPointerEnter(target) {
if (this._enterCount === -1) {
// -1 is for touch input, we are now sure we are with a mouse or pencil
this._enterCount = 0;
}
this._enterCount++;
if (this._enterCount > 1) {
return false;
}
this.onPointerEnterObservable.notifyObservers(this, -1, target, this);
if (this.pointerEnterAnimation) {
this.pointerEnterAnimation();
}
return true;
}
/**
* @internal
*/
_onPointerOut(target) {
this._enterCount--;
if (this._enterCount > 0) {
return;
}
this._enterCount = 0;
this.onPointerOutObservable.notifyObservers(this, -1, target, this);
if (this.pointerOutAnimation) {
this.pointerOutAnimation();
}
}
/**
* @internal
*/
_onPointerDown(target, coordinates, pointerId, buttonIndex) {
this._downCount++;
this._downPointerIds[pointerId] = this._downPointerIds[pointerId] + 1 || 1;
if (this._downCount !== 1) {
return false;
}
this.onPointerDownObservable.notifyObservers(new Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
if (this.pointerDownAnimation) {
this.pointerDownAnimation();
}
return true;
}
/**
* @internal
*/
_onPointerUp(target, coordinates, pointerId, buttonIndex, notifyClick) {
this._downCount--;
this._downPointerIds[pointerId]--;
if (this._downPointerIds[pointerId] <= 0) {
delete this._downPointerIds[pointerId];
}
if (this._downCount < 0) {
// Handle if forcePointerUp was called prior to this
this._downCount = 0;
return;
}
if (this._downCount == 0) {
if (notifyClick && (this._enterCount > 0 || this._enterCount === -1)) {
this.onPointerClickObservable.notifyObservers(new Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
}
this.onPointerUpObservable.notifyObservers(new Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
if (this.pointerUpAnimation) {
this.pointerUpAnimation();
}
}
}
/**
* @internal
*/
forcePointerUp(pointerId = null) {
if (pointerId !== null) {
this._onPointerUp(this, Vector3.Zero(), pointerId, 0, true);
}
else {
for (const key in this._downPointerIds) {
this._onPointerUp(this, Vector3.Zero(), +key, 0, true);
}
if (this._downCount > 0) {
this._downCount = 1;
this._onPointerUp(this, Vector3.Zero(), 0, 0, true);
}
}
}
/**
* @internal
*/
_processObservables(type, pickedPoint, originMeshPosition, pointerId, buttonIndex) {
if (this._isTouchButton3D(this) && originMeshPosition) {
type = this._generatePointerEventType(type, originMeshPosition, this._downCount);
}
if (type === PointerEventTypes.POINTERMOVE) {
this._onPointerMove(this, pickedPoint);
const previousControlOver = this._host._lastControlOver[pointerId];
if (previousControlOver && previousControlOver !== this) {
previousControlOver._onPointerOut(this);
}
if (previousControlOver !== this) {
this._onPointerEnter(this);
}
this._host._lastControlOver[pointerId] = this;
return true;
}
if (type === PointerEventTypes.POINTERDOWN) {
this._onPointerDown(this, pickedPoint, pointerId, buttonIndex);
this._host._lastControlDown[pointerId] = this;
this._host._lastPickedControl = this;
return true;
}
if (type === PointerEventTypes.POINTERUP || type === PointerEventTypes.POINTERDOUBLETAP) {
if (this._host._lastControlDown[pointerId]) {
this._host._lastControlDown[pointerId]._onPointerUp(this, pickedPoint, pointerId, buttonIndex, true);
}
delete this._host._lastControlDown[pointerId];
return true;
}
return false;
}
/** @internal */
_disposeNode() {
if (this._node) {
this._node.dispose();
this._node = null;
}
}
/**
* Releases all associated resources
*/
dispose() {
this.onPointerDownObservable.clear();
this.onPointerEnterObservable.clear();
this.onPointerMoveObservable.clear();
this.onPointerOutObservable.clear();
this.onPointerUpObservable.clear();
this.onPointerClickObservable.clear();
this._disposeNode();
// Behaviors
for (const behavior of this._behaviors) {
behavior.detach();
}
}
}
//# sourceMappingURL=control3D.js.map