UNPKG

@babylonjs/gui

Version:

Babylon.js GUI module =====================

401 lines 12.9 kB
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