123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969 |
- import { PointerInfoPre, PointerInfo, PointerEventTypes } from "../Events/pointerEvents.js";
- import { AbstractActionManager } from "../Actions/abstractActionManager.js";
- import { PickingInfo } from "../Collisions/pickingInfo.js";
- import { Vector2, Matrix } from "../Maths/math.vector.js";
- import { ActionEvent } from "../Actions/actionEvent.js";
- import { KeyboardEventTypes, KeyboardInfoPre, KeyboardInfo } from "../Events/keyboardEvents.js";
- import { DeviceType, PointerInput } from "../DeviceInput/InputDevices/deviceEnums.js";
- import { DeviceSourceManager } from "../DeviceInput/InputDevices/deviceSourceManager.js";
- import { EngineStore } from "../Engines/engineStore.js";
- /** @internal */
- // eslint-disable-next-line @typescript-eslint/naming-convention
- class _ClickInfo {
- constructor() {
- this._singleClick = false;
- this._doubleClick = false;
- this._hasSwiped = false;
- this._ignore = false;
- }
- get singleClick() {
- return this._singleClick;
- }
- get doubleClick() {
- return this._doubleClick;
- }
- get hasSwiped() {
- return this._hasSwiped;
- }
- get ignore() {
- return this._ignore;
- }
- set singleClick(b) {
- this._singleClick = b;
- }
- set doubleClick(b) {
- this._doubleClick = b;
- }
- set hasSwiped(b) {
- this._hasSwiped = b;
- }
- set ignore(b) {
- this._ignore = b;
- }
- }
- /**
- * Class used to manage all inputs for the scene.
- */
- export class InputManager {
- /**
- * Creates a new InputManager
- * @param scene - defines the hosting scene
- */
- constructor(scene) {
- /** This is a defensive check to not allow control attachment prior to an already active one. If already attached, previous control is unattached before attaching the new one. */
- this._alreadyAttached = false;
- this._meshPickProceed = false;
- this._currentPickResult = null;
- this._previousPickResult = null;
- this._totalPointersPressed = 0;
- this._doubleClickOccured = false;
- this._isSwiping = false;
- this._swipeButtonPressed = -1;
- this._skipPointerTap = false;
- this._isMultiTouchGesture = false;
- this._pointerX = 0;
- this._pointerY = 0;
- this._startingPointerPosition = new Vector2(0, 0);
- this._previousStartingPointerPosition = new Vector2(0, 0);
- this._startingPointerTime = 0;
- this._previousStartingPointerTime = 0;
- this._pointerCaptures = {};
- this._meshUnderPointerId = {};
- this._movePointerInfo = null;
- this._cameraObserverCount = 0;
- this._delayedClicks = [null, null, null, null, null];
- this._deviceSourceManager = null;
- this._scene = scene || EngineStore.LastCreatedScene;
- if (!this._scene) {
- return;
- }
- }
- /**
- * Gets the mesh that is currently under the pointer
- * @returns Mesh that the pointer is pointer is hovering over
- */
- get meshUnderPointer() {
- if (this._movePointerInfo) {
- // Because _pointerOverMesh is populated as part of _pickMove, we need to force a pick to update it.
- // Calling _pickMove calls _setCursorAndPointerOverMesh which calls setPointerOverMesh
- this._movePointerInfo._generatePickInfo();
- // Once we have what we need, we can clear _movePointerInfo because we don't need it anymore
- this._movePointerInfo = null;
- }
- return this._pointerOverMesh;
- }
- /**
- * When using more than one pointer (for example in XR) you can get the mesh under the specific pointer
- * @param pointerId - the pointer id to use
- * @returns The mesh under this pointer id or null if not found
- */
- getMeshUnderPointerByPointerId(pointerId) {
- return this._meshUnderPointerId[pointerId] || null;
- }
- /**
- * Gets the pointer coordinates in 2D without any translation (ie. straight out of the pointer event)
- * @returns Vector with X/Y values directly from pointer event
- */
- get unTranslatedPointer() {
- return new Vector2(this._unTranslatedPointerX, this._unTranslatedPointerY);
- }
- /**
- * Gets or sets the current on-screen X position of the pointer
- * @returns Translated X with respect to screen
- */
- get pointerX() {
- return this._pointerX;
- }
- set pointerX(value) {
- this._pointerX = value;
- }
- /**
- * Gets or sets the current on-screen Y position of the pointer
- * @returns Translated Y with respect to screen
- */
- get pointerY() {
- return this._pointerY;
- }
- set pointerY(value) {
- this._pointerY = value;
- }
- _updatePointerPosition(evt) {
- const canvasRect = this._scene.getEngine().getInputElementClientRect();
- if (!canvasRect) {
- return;
- }
- this._pointerX = evt.clientX - canvasRect.left;
- this._pointerY = evt.clientY - canvasRect.top;
- this._unTranslatedPointerX = this._pointerX;
- this._unTranslatedPointerY = this._pointerY;
- }
- _processPointerMove(pickResult, evt) {
- const scene = this._scene;
- const engine = scene.getEngine();
- const canvas = engine.getInputElement();
- if (canvas) {
- canvas.tabIndex = engine.canvasTabIndex;
- // Restore pointer
- if (!scene.doNotHandleCursors) {
- canvas.style.cursor = scene.defaultCursor;
- }
- }
- this._setCursorAndPointerOverMesh(pickResult, evt, scene);
- for (const step of scene._pointerMoveStage) {
- // If _pointerMoveState is defined, we have an active spriteManager and can't use Lazy Picking
- // Therefore, we need to force a pick to update the pickResult
- pickResult = pickResult || this._pickMove(evt);
- const isMeshPicked = pickResult?.pickedMesh ? true : false;
- pickResult = step.action(this._unTranslatedPointerX, this._unTranslatedPointerY, pickResult, isMeshPicked, canvas);
- }
- const type = evt.inputIndex >= PointerInput.MouseWheelX && evt.inputIndex <= PointerInput.MouseWheelZ ? PointerEventTypes.POINTERWHEEL : PointerEventTypes.POINTERMOVE;
- if (scene.onPointerMove) {
- // Because of lazy picking, we need to force a pick to update the pickResult
- pickResult = pickResult || this._pickMove(evt);
- scene.onPointerMove(evt, pickResult, type);
- }
- let pointerInfo;
- if (pickResult) {
- pointerInfo = new PointerInfo(type, evt, pickResult);
- this._setRayOnPointerInfo(pickResult, evt);
- }
- else {
- pointerInfo = new PointerInfo(type, evt, null, this);
- this._movePointerInfo = pointerInfo;
- }
- if (scene.onPointerObservable.hasObservers()) {
- scene.onPointerObservable.notifyObservers(pointerInfo, type);
- }
- }
- // Pointers handling
- /** @internal */
- _setRayOnPointerInfo(pickInfo, event) {
- const scene = this._scene;
- if (pickInfo && scene._pickingAvailable) {
- if (!pickInfo.ray) {
- pickInfo.ray = scene.createPickingRay(event.offsetX, event.offsetY, Matrix.Identity(), scene.activeCamera);
- }
- }
- }
- /** @internal */
- _addCameraPointerObserver(observer, mask) {
- this._cameraObserverCount++;
- return this._scene.onPointerObservable.add(observer, mask);
- }
- /** @internal */
- _removeCameraPointerObserver(observer) {
- this._cameraObserverCount--;
- return this._scene.onPointerObservable.remove(observer);
- }
- _checkForPicking() {
- return !!(this._scene.onPointerObservable.observers.length > this._cameraObserverCount || this._scene.onPointerPick);
- }
- _checkPrePointerObservable(pickResult, evt, type) {
- const scene = this._scene;
- const pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
- if (pickResult) {
- pi.originalPickingInfo = pickResult;
- pi.ray = pickResult.ray;
- if (evt.pointerType === "xr-near" && pickResult.originMesh) {
- pi.nearInteractionPickingInfo = pickResult;
- }
- }
- scene.onPrePointerObservable.notifyObservers(pi, type);
- if (pi.skipOnPointerObservable) {
- return true;
- }
- else {
- return false;
- }
- }
- /** @internal */
- _pickMove(evt) {
- const scene = this._scene;
- const pickResult = scene.pick(this._unTranslatedPointerX, this._unTranslatedPointerY, scene.pointerMovePredicate, scene.pointerMoveFastCheck, scene.cameraToUseForPointers, scene.pointerMoveTrianglePredicate);
- this._setCursorAndPointerOverMesh(pickResult, evt, scene);
- return pickResult;
- }
- _setCursorAndPointerOverMesh(pickResult, evt, scene) {
- const engine = scene.getEngine();
- const canvas = engine.getInputElement();
- if (pickResult?.pickedMesh) {
- this.setPointerOverMesh(pickResult.pickedMesh, evt.pointerId, pickResult, evt);
- if (!scene.doNotHandleCursors && canvas && this._pointerOverMesh) {
- const actionManager = this._pointerOverMesh._getActionManagerForTrigger();
- if (actionManager && actionManager.hasPointerTriggers) {
- canvas.style.cursor = actionManager.hoverCursor || scene.hoverCursor;
- }
- }
- }
- else {
- this.setPointerOverMesh(null, evt.pointerId, pickResult, evt);
- }
- }
- /**
- * Use this method to simulate a pointer move on a mesh
- * The pickResult parameter can be obtained from a scene.pick or scene.pickWithRay
- * @param pickResult - pickingInfo of the object wished to simulate pointer event on
- * @param pointerEventInit - pointer event state to be used when simulating the pointer event (eg. pointer id for multitouch)
- */
- simulatePointerMove(pickResult, pointerEventInit) {
- const evt = new PointerEvent("pointermove", pointerEventInit);
- evt.inputIndex = PointerInput.Move;
- if (this._checkPrePointerObservable(pickResult, evt, PointerEventTypes.POINTERMOVE)) {
- return;
- }
- this._processPointerMove(pickResult, evt);
- }
- /**
- * Use this method to simulate a pointer down on a mesh
- * The pickResult parameter can be obtained from a scene.pick or scene.pickWithRay
- * @param pickResult - pickingInfo of the object wished to simulate pointer event on
- * @param pointerEventInit - pointer event state to be used when simulating the pointer event (eg. pointer id for multitouch)
- */
- simulatePointerDown(pickResult, pointerEventInit) {
- const evt = new PointerEvent("pointerdown", pointerEventInit);
- evt.inputIndex = evt.button + 2;
- if (this._checkPrePointerObservable(pickResult, evt, PointerEventTypes.POINTERDOWN)) {
- return;
- }
- this._processPointerDown(pickResult, evt);
- }
- _processPointerDown(pickResult, evt) {
- const scene = this._scene;
- if (pickResult?.pickedMesh) {
- this._pickedDownMesh = pickResult.pickedMesh;
- const actionManager = pickResult.pickedMesh._getActionManagerForTrigger();
- if (actionManager) {
- if (actionManager.hasPickTriggers) {
- actionManager.processTrigger(5, ActionEvent.CreateNew(pickResult.pickedMesh, evt, pickResult));
- switch (evt.button) {
- case 0:
- actionManager.processTrigger(2, ActionEvent.CreateNew(pickResult.pickedMesh, evt, pickResult));
- break;
- case 1:
- actionManager.processTrigger(4, ActionEvent.CreateNew(pickResult.pickedMesh, evt, pickResult));
- break;
- case 2:
- actionManager.processTrigger(3, ActionEvent.CreateNew(pickResult.pickedMesh, evt, pickResult));
- break;
- }
- }
- if (actionManager.hasSpecificTrigger(8)) {
- window.setTimeout(() => {
- const pickResult = scene.pick(this._unTranslatedPointerX, this._unTranslatedPointerY, (mesh) => ((mesh.isPickable &&
- mesh.isVisible &&
- mesh.isReady() &&
- mesh.actionManager &&
- mesh.actionManager.hasSpecificTrigger(8) &&
- mesh === this._pickedDownMesh)), false, scene.cameraToUseForPointers);
- if (pickResult?.pickedMesh && actionManager) {
- if (this._totalPointersPressed !== 0 && Date.now() - this._startingPointerTime > InputManager.LongPressDelay && !this._isPointerSwiping()) {
- this._startingPointerTime = 0;
- actionManager.processTrigger(8, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
- }
- }
- }, InputManager.LongPressDelay);
- }
- }
- }
- else {
- for (const step of scene._pointerDownStage) {
- pickResult = step.action(this._unTranslatedPointerX, this._unTranslatedPointerY, pickResult, evt, false);
- }
- }
- let pointerInfo;
- const type = PointerEventTypes.POINTERDOWN;
- if (pickResult) {
- if (scene.onPointerDown) {
- scene.onPointerDown(evt, pickResult, type);
- }
- pointerInfo = new PointerInfo(type, evt, pickResult);
- this._setRayOnPointerInfo(pickResult, evt);
- }
- else {
- pointerInfo = new PointerInfo(type, evt, null, this);
- }
- if (scene.onPointerObservable.hasObservers()) {
- scene.onPointerObservable.notifyObservers(pointerInfo, type);
- }
- }
- /**
- * @internal
- * @internals Boolean if delta for pointer exceeds drag movement threshold
- */
- _isPointerSwiping() {
- return this._isSwiping;
- }
- /**
- * Use this method to simulate a pointer up on a mesh
- * The pickResult parameter can be obtained from a scene.pick or scene.pickWithRay
- * @param pickResult - pickingInfo of the object wished to simulate pointer event on
- * @param pointerEventInit - pointer event state to be used when simulating the pointer event (eg. pointer id for multitouch)
- * @param doubleTap - indicates that the pointer up event should be considered as part of a double click (false by default)
- */
- simulatePointerUp(pickResult, pointerEventInit, doubleTap) {
- const evt = new PointerEvent("pointerup", pointerEventInit);
- evt.inputIndex = PointerInput.Move;
- const clickInfo = new _ClickInfo();
- if (doubleTap) {
- clickInfo.doubleClick = true;
- }
- else {
- clickInfo.singleClick = true;
- }
- if (this._checkPrePointerObservable(pickResult, evt, PointerEventTypes.POINTERUP)) {
- return;
- }
- this._processPointerUp(pickResult, evt, clickInfo);
- }
- _processPointerUp(pickResult, evt, clickInfo) {
- const scene = this._scene;
- if (pickResult?.pickedMesh) {
- this._pickedUpMesh = pickResult.pickedMesh;
- if (this._pickedDownMesh === this._pickedUpMesh) {
- if (scene.onPointerPick) {
- scene.onPointerPick(evt, pickResult);
- }
- if (clickInfo.singleClick && !clickInfo.ignore && scene.onPointerObservable.observers.length > this._cameraObserverCount) {
- const type = PointerEventTypes.POINTERPICK;
- const pi = new PointerInfo(type, evt, pickResult);
- this._setRayOnPointerInfo(pickResult, evt);
- scene.onPointerObservable.notifyObservers(pi, type);
- }
- }
- const actionManager = pickResult.pickedMesh._getActionManagerForTrigger();
- if (actionManager && !clickInfo.ignore) {
- actionManager.processTrigger(7, ActionEvent.CreateNew(pickResult.pickedMesh, evt, pickResult));
- if (!clickInfo.hasSwiped && clickInfo.singleClick) {
- actionManager.processTrigger(1, ActionEvent.CreateNew(pickResult.pickedMesh, evt, pickResult));
- }
- const doubleClickActionManager = pickResult.pickedMesh._getActionManagerForTrigger(6);
- if (clickInfo.doubleClick && doubleClickActionManager) {
- doubleClickActionManager.processTrigger(6, ActionEvent.CreateNew(pickResult.pickedMesh, evt, pickResult));
- }
- }
- }
- else {
- if (!clickInfo.ignore) {
- for (const step of scene._pointerUpStage) {
- pickResult = step.action(this._unTranslatedPointerX, this._unTranslatedPointerY, pickResult, evt, clickInfo.doubleClick);
- }
- }
- }
- if (this._pickedDownMesh && this._pickedDownMesh !== this._pickedUpMesh) {
- const pickedDownActionManager = this._pickedDownMesh._getActionManagerForTrigger(16);
- if (pickedDownActionManager) {
- pickedDownActionManager.processTrigger(16, ActionEvent.CreateNew(this._pickedDownMesh, evt));
- }
- }
- if (!clickInfo.ignore) {
- const pi = new PointerInfo(PointerEventTypes.POINTERUP, evt, pickResult);
- // Set ray on picking info. Note that this info will also be reused for the tap notification.
- this._setRayOnPointerInfo(pickResult, evt);
- scene.onPointerObservable.notifyObservers(pi, PointerEventTypes.POINTERUP);
- if (scene.onPointerUp) {
- scene.onPointerUp(evt, pickResult, PointerEventTypes.POINTERUP);
- }
- if (!clickInfo.hasSwiped && !this._skipPointerTap && !this._isMultiTouchGesture) {
- let type = 0;
- if (clickInfo.singleClick) {
- type = PointerEventTypes.POINTERTAP;
- }
- else if (clickInfo.doubleClick) {
- type = PointerEventTypes.POINTERDOUBLETAP;
- }
- if (type) {
- const pi = new PointerInfo(type, evt, pickResult);
- if (scene.onPointerObservable.hasObservers() && scene.onPointerObservable.hasSpecificMask(type)) {
- scene.onPointerObservable.notifyObservers(pi, type);
- }
- }
- }
- }
- }
- /**
- * Gets a boolean indicating if the current pointer event is captured (meaning that the scene has already handled the pointer down)
- * @param pointerId - defines the pointer id to use in a multi-touch scenario (0 by default)
- * @returns true if the pointer was captured
- */
- isPointerCaptured(pointerId = 0) {
- return this._pointerCaptures[pointerId];
- }
- /**
- * Attach events to the canvas (To handle actionManagers triggers and raise onPointerMove, onPointerDown and onPointerUp
- * @param attachUp - defines if you want to attach events to pointerup
- * @param attachDown - defines if you want to attach events to pointerdown
- * @param attachMove - defines if you want to attach events to pointermove
- * @param elementToAttachTo - defines the target DOM element to attach to (will use the canvas by default)
- */
- attachControl(attachUp = true, attachDown = true, attachMove = true, elementToAttachTo = null) {
- const scene = this._scene;
- const engine = scene.getEngine();
- if (!elementToAttachTo) {
- elementToAttachTo = engine.getInputElement();
- }
- if (this._alreadyAttached) {
- this.detachControl();
- }
- if (elementToAttachTo) {
- this._alreadyAttachedTo = elementToAttachTo;
- }
- this._deviceSourceManager = new DeviceSourceManager(engine);
- // Because this is only called from _initClickEvent, which is called in _onPointerUp, we'll use the pointerUpPredicate for the pick call
- this._initActionManager = (act) => {
- if (!this._meshPickProceed) {
- const pickResult = scene.skipPointerUpPicking || (scene._registeredActions === 0 && !this._checkForPicking() && !scene.onPointerUp)
- ? null
- : scene.pick(this._unTranslatedPointerX, this._unTranslatedPointerY, scene.pointerUpPredicate, scene.pointerUpFastCheck, scene.cameraToUseForPointers, scene.pointerUpTrianglePredicate);
- this._currentPickResult = pickResult;
- if (pickResult) {
- act = pickResult.hit && pickResult.pickedMesh ? pickResult.pickedMesh._getActionManagerForTrigger() : null;
- }
- this._meshPickProceed = true;
- }
- return act;
- };
- this._delayedSimpleClick = (btn, clickInfo, cb) => {
- // double click delay is over and that no double click has been raised since, or the 2 consecutive keys pressed are different
- if ((Date.now() - this._previousStartingPointerTime > InputManager.DoubleClickDelay && !this._doubleClickOccured) || btn !== this._previousButtonPressed) {
- this._doubleClickOccured = false;
- clickInfo.singleClick = true;
- clickInfo.ignore = false;
- // If we have a delayed click, we need to resolve the TAP event
- if (this._delayedClicks[btn]) {
- const evt = this._delayedClicks[btn].evt;
- const type = PointerEventTypes.POINTERTAP;
- const pi = new PointerInfo(type, evt, this._currentPickResult);
- if (scene.onPointerObservable.hasObservers() && scene.onPointerObservable.hasSpecificMask(type)) {
- scene.onPointerObservable.notifyObservers(pi, type);
- }
- // Clear the delayed click
- this._delayedClicks[btn] = null;
- }
- }
- };
- this._initClickEvent = (obs1, obs2, evt, cb) => {
- const clickInfo = new _ClickInfo();
- this._currentPickResult = null;
- let act = null;
- let checkPicking = obs1.hasSpecificMask(PointerEventTypes.POINTERPICK) ||
- obs2.hasSpecificMask(PointerEventTypes.POINTERPICK) ||
- obs1.hasSpecificMask(PointerEventTypes.POINTERTAP) ||
- obs2.hasSpecificMask(PointerEventTypes.POINTERTAP) ||
- obs1.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP) ||
- obs2.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP);
- if (!checkPicking && AbstractActionManager) {
- act = this._initActionManager(act, clickInfo);
- if (act) {
- checkPicking = act.hasPickTriggers;
- }
- }
- let needToIgnoreNext = false;
- if (checkPicking) {
- const btn = evt.button;
- clickInfo.hasSwiped = this._isPointerSwiping();
- if (!clickInfo.hasSwiped) {
- let checkSingleClickImmediately = !InputManager.ExclusiveDoubleClickMode;
- if (!checkSingleClickImmediately) {
- checkSingleClickImmediately = !obs1.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP) && !obs2.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP);
- if (checkSingleClickImmediately && !AbstractActionManager.HasSpecificTrigger(6)) {
- act = this._initActionManager(act, clickInfo);
- if (act) {
- checkSingleClickImmediately = !act.hasSpecificTrigger(6);
- }
- }
- }
- if (checkSingleClickImmediately) {
- // single click detected if double click delay is over or two different successive keys pressed without exclusive double click or no double click required
- if (Date.now() - this._previousStartingPointerTime > InputManager.DoubleClickDelay || btn !== this._previousButtonPressed) {
- clickInfo.singleClick = true;
- cb(clickInfo, this._currentPickResult);
- needToIgnoreNext = true;
- }
- }
- // at least one double click is required to be check and exclusive double click is enabled
- else {
- // Queue up a delayed click, just in case this isn't a double click
- // It should be noted that while this delayed event happens
- // because of user input, it shouldn't be considered as a direct,
- // timing-dependent result of that input. It's meant to just fire the TAP event
- const delayedClick = {
- evt: evt,
- clickInfo: clickInfo,
- timeoutId: window.setTimeout(this._delayedSimpleClick.bind(this, btn, clickInfo, cb), InputManager.DoubleClickDelay),
- };
- this._delayedClicks[btn] = delayedClick;
- }
- let checkDoubleClick = obs1.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP) || obs2.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP);
- if (!checkDoubleClick && AbstractActionManager.HasSpecificTrigger(6)) {
- act = this._initActionManager(act, clickInfo);
- if (act) {
- checkDoubleClick = act.hasSpecificTrigger(6);
- }
- }
- if (checkDoubleClick) {
- // two successive keys pressed are equal, double click delay is not over and double click has not just occurred
- if (btn === this._previousButtonPressed && Date.now() - this._previousStartingPointerTime < InputManager.DoubleClickDelay && !this._doubleClickOccured) {
- // pointer has not moved for 2 clicks, it's a double click
- if (!clickInfo.hasSwiped && !this._isPointerSwiping()) {
- this._previousStartingPointerTime = 0;
- this._doubleClickOccured = true;
- clickInfo.doubleClick = true;
- clickInfo.ignore = false;
- // If we have a pending click, we need to cancel it
- if (InputManager.ExclusiveDoubleClickMode && this._delayedClicks[btn]) {
- clearTimeout(this._delayedClicks[btn]?.timeoutId);
- this._delayedClicks[btn] = null;
- }
- cb(clickInfo, this._currentPickResult);
- }
- // if the two successive clicks are too far, it's just two simple clicks
- else {
- this._doubleClickOccured = false;
- this._previousStartingPointerTime = this._startingPointerTime;
- this._previousStartingPointerPosition.x = this._startingPointerPosition.x;
- this._previousStartingPointerPosition.y = this._startingPointerPosition.y;
- this._previousButtonPressed = btn;
- if (InputManager.ExclusiveDoubleClickMode) {
- // If we have a delayed click, we need to cancel it
- if (this._delayedClicks[btn]) {
- clearTimeout(this._delayedClicks[btn]?.timeoutId);
- this._delayedClicks[btn] = null;
- }
- cb(clickInfo, this._previousPickResult);
- }
- else {
- cb(clickInfo, this._currentPickResult);
- }
- }
- needToIgnoreNext = true;
- }
- // just the first click of the double has been raised
- else {
- this._doubleClickOccured = false;
- this._previousStartingPointerTime = this._startingPointerTime;
- this._previousStartingPointerPosition.x = this._startingPointerPosition.x;
- this._previousStartingPointerPosition.y = this._startingPointerPosition.y;
- this._previousButtonPressed = btn;
- }
- }
- }
- }
- // Even if ExclusiveDoubleClickMode is true, we need to always handle
- // up events at time of execution, unless we're explicitly ignoring them.
- if (!needToIgnoreNext) {
- cb(clickInfo, this._currentPickResult);
- }
- };
- this._onPointerMove = (evt) => {
- this._updatePointerPosition(evt);
- // Check if pointer leaves DragMovementThreshold range to determine if swipe is occurring
- if (!this._isSwiping && this._swipeButtonPressed !== -1) {
- this._isSwiping =
- Math.abs(this._startingPointerPosition.x - this._pointerX) > InputManager.DragMovementThreshold ||
- Math.abs(this._startingPointerPosition.y - this._pointerY) > InputManager.DragMovementThreshold;
- }
- // Because there's a race condition between pointermove and pointerlockchange events, we need to
- // verify that the pointer is still locked after each pointermove event.
- if (engine.isPointerLock) {
- engine._verifyPointerLock();
- }
- // PreObservable support
- if (this._checkPrePointerObservable(null, evt, evt.inputIndex >= PointerInput.MouseWheelX && evt.inputIndex <= PointerInput.MouseWheelZ ? PointerEventTypes.POINTERWHEEL : PointerEventTypes.POINTERMOVE)) {
- return;
- }
- if (!scene.cameraToUseForPointers && !scene.activeCamera) {
- return;
- }
- if (scene.skipPointerMovePicking) {
- this._processPointerMove(new PickingInfo(), evt);
- return;
- }
- if (!scene.pointerMovePredicate) {
- scene.pointerMovePredicate = (mesh) => mesh.isPickable &&
- mesh.isVisible &&
- mesh.isReady() &&
- mesh.isEnabled() &&
- (mesh.enablePointerMoveEvents || scene.constantlyUpdateMeshUnderPointer || mesh._getActionManagerForTrigger() !== null) &&
- (!scene.cameraToUseForPointers || (scene.cameraToUseForPointers.layerMask & mesh.layerMask) !== 0);
- }
- const pickResult = scene._registeredActions > 0 || scene.constantlyUpdateMeshUnderPointer ? this._pickMove(evt) : null;
- this._processPointerMove(pickResult, evt);
- };
- this._onPointerDown = (evt) => {
- this._totalPointersPressed++;
- this._pickedDownMesh = null;
- this._meshPickProceed = false;
- // If ExclusiveDoubleClickMode is true, we need to resolve any pending delayed clicks
- if (InputManager.ExclusiveDoubleClickMode) {
- for (let i = 0; i < this._delayedClicks.length; i++) {
- if (this._delayedClicks[i]) {
- // If the button that was pressed is the same as the one that was released,
- // just clear the timer. This will be resolved in the up event.
- if (evt.button === i) {
- clearTimeout(this._delayedClicks[i]?.timeoutId);
- }
- else {
- // Otherwise, we need to resolve the click
- const clickInfo = this._delayedClicks[i].clickInfo;
- this._doubleClickOccured = false;
- clickInfo.singleClick = true;
- clickInfo.ignore = false;
- const prevEvt = this._delayedClicks[i].evt;
- const type = PointerEventTypes.POINTERTAP;
- const pi = new PointerInfo(type, prevEvt, this._currentPickResult);
- if (scene.onPointerObservable.hasObservers() && scene.onPointerObservable.hasSpecificMask(type)) {
- scene.onPointerObservable.notifyObservers(pi, type);
- }
- // Clear the delayed click
- this._delayedClicks[i] = null;
- }
- }
- }
- }
- this._updatePointerPosition(evt);
- if (this._swipeButtonPressed === -1) {
- this._swipeButtonPressed = evt.button;
- }
- if (scene.preventDefaultOnPointerDown && elementToAttachTo) {
- evt.preventDefault();
- elementToAttachTo.focus();
- }
- this._startingPointerPosition.x = this._pointerX;
- this._startingPointerPosition.y = this._pointerY;
- this._startingPointerTime = Date.now();
- // PreObservable support
- if (this._checkPrePointerObservable(null, evt, PointerEventTypes.POINTERDOWN)) {
- return;
- }
- if (!scene.cameraToUseForPointers && !scene.activeCamera) {
- return;
- }
- this._pointerCaptures[evt.pointerId] = true;
- if (!scene.pointerDownPredicate) {
- scene.pointerDownPredicate = (mesh) => {
- return (mesh.isPickable &&
- mesh.isVisible &&
- mesh.isReady() &&
- mesh.isEnabled() &&
- (!scene.cameraToUseForPointers || (scene.cameraToUseForPointers.layerMask & mesh.layerMask) !== 0));
- };
- }
- // Meshes
- this._pickedDownMesh = null;
- let pickResult;
- if (scene.skipPointerDownPicking || (scene._registeredActions === 0 && !this._checkForPicking() && !scene.onPointerDown)) {
- pickResult = new PickingInfo();
- }
- else {
- pickResult = scene.pick(this._unTranslatedPointerX, this._unTranslatedPointerY, scene.pointerDownPredicate, scene.pointerDownFastCheck, scene.cameraToUseForPointers, scene.pointerDownTrianglePredicate);
- }
- this._processPointerDown(pickResult, evt);
- };
- this._onPointerUp = (evt) => {
- if (this._totalPointersPressed === 0) {
- // We are attaching the pointer up to windows because of a bug in FF
- return; // So we need to test it the pointer down was pressed before.
- }
- this._totalPointersPressed--;
- this._pickedUpMesh = null;
- this._meshPickProceed = false;
- this._updatePointerPosition(evt);
- if (scene.preventDefaultOnPointerUp && elementToAttachTo) {
- evt.preventDefault();
- elementToAttachTo.focus();
- }
- this._initClickEvent(scene.onPrePointerObservable, scene.onPointerObservable, evt, (clickInfo, pickResult) => {
- // PreObservable support
- if (scene.onPrePointerObservable.hasObservers()) {
- this._skipPointerTap = false;
- if (!clickInfo.ignore) {
- if (this._checkPrePointerObservable(null, evt, PointerEventTypes.POINTERUP)) {
- // If we're skipping the next observable, we need to reset the swipe state before returning
- if (this._swipeButtonPressed === evt.button) {
- this._isSwiping = false;
- this._swipeButtonPressed = -1;
- }
- // If we're going to skip the POINTERUP, we need to reset the pointer capture
- if (evt.buttons === 0) {
- this._pointerCaptures[evt.pointerId] = false;
- }
- return;
- }
- if (!clickInfo.hasSwiped) {
- if (clickInfo.singleClick && scene.onPrePointerObservable.hasSpecificMask(PointerEventTypes.POINTERTAP)) {
- if (this._checkPrePointerObservable(null, evt, PointerEventTypes.POINTERTAP)) {
- this._skipPointerTap = true;
- }
- }
- if (clickInfo.doubleClick && scene.onPrePointerObservable.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP)) {
- if (this._checkPrePointerObservable(null, evt, PointerEventTypes.POINTERDOUBLETAP)) {
- this._skipPointerTap = true;
- }
- }
- }
- }
- }
- // There should be a pointer captured at this point so if there isn't we should reset and return
- if (!this._pointerCaptures[evt.pointerId]) {
- if (this._swipeButtonPressed === evt.button) {
- this._isSwiping = false;
- this._swipeButtonPressed = -1;
- }
- return;
- }
- // Only release capture if all buttons are released
- if (evt.buttons === 0) {
- this._pointerCaptures[evt.pointerId] = false;
- }
- if (!scene.cameraToUseForPointers && !scene.activeCamera) {
- return;
- }
- if (!scene.pointerUpPredicate) {
- scene.pointerUpPredicate = (mesh) => {
- return (mesh.isPickable &&
- mesh.isVisible &&
- mesh.isReady() &&
- mesh.isEnabled() &&
- (!scene.cameraToUseForPointers || (scene.cameraToUseForPointers.layerMask & mesh.layerMask) !== 0));
- };
- }
- // Meshes
- if (!this._meshPickProceed && ((AbstractActionManager && AbstractActionManager.HasTriggers) || this._checkForPicking() || scene.onPointerUp)) {
- this._initActionManager(null, clickInfo);
- }
- if (!pickResult) {
- pickResult = this._currentPickResult;
- }
- this._processPointerUp(pickResult, evt, clickInfo);
- this._previousPickResult = this._currentPickResult;
- if (this._swipeButtonPressed === evt.button) {
- this._isSwiping = false;
- this._swipeButtonPressed = -1;
- }
- });
- };
- this._onKeyDown = (evt) => {
- const type = KeyboardEventTypes.KEYDOWN;
- if (scene.onPreKeyboardObservable.hasObservers()) {
- const pi = new KeyboardInfoPre(type, evt);
- scene.onPreKeyboardObservable.notifyObservers(pi, type);
- if (pi.skipOnKeyboardObservable) {
- return;
- }
- }
- if (scene.onKeyboardObservable.hasObservers()) {
- const pi = new KeyboardInfo(type, evt);
- scene.onKeyboardObservable.notifyObservers(pi, type);
- }
- if (scene.actionManager) {
- scene.actionManager.processTrigger(14, ActionEvent.CreateNewFromScene(scene, evt));
- }
- };
- this._onKeyUp = (evt) => {
- const type = KeyboardEventTypes.KEYUP;
- if (scene.onPreKeyboardObservable.hasObservers()) {
- const pi = new KeyboardInfoPre(type, evt);
- scene.onPreKeyboardObservable.notifyObservers(pi, type);
- if (pi.skipOnKeyboardObservable) {
- return;
- }
- }
- if (scene.onKeyboardObservable.hasObservers()) {
- const pi = new KeyboardInfo(type, evt);
- scene.onKeyboardObservable.notifyObservers(pi, type);
- }
- if (scene.actionManager) {
- scene.actionManager.processTrigger(15, ActionEvent.CreateNewFromScene(scene, evt));
- }
- };
- // If a device connects that we can handle, wire up the observable
- this._deviceSourceManager.onDeviceConnectedObservable.add((deviceSource) => {
- if (deviceSource.deviceType === DeviceType.Mouse) {
- deviceSource.onInputChangedObservable.add((eventData) => {
- if (eventData.inputIndex === PointerInput.LeftClick ||
- eventData.inputIndex === PointerInput.MiddleClick ||
- eventData.inputIndex === PointerInput.RightClick ||
- eventData.inputIndex === PointerInput.BrowserBack ||
- eventData.inputIndex === PointerInput.BrowserForward) {
- if (attachDown && deviceSource.getInput(eventData.inputIndex) === 1) {
- this._onPointerDown(eventData);
- }
- else if (attachUp && deviceSource.getInput(eventData.inputIndex) === 0) {
- this._onPointerUp(eventData);
- }
- }
- else if (attachMove) {
- if (eventData.inputIndex === PointerInput.Move) {
- this._onPointerMove(eventData);
- }
- else if (eventData.inputIndex === PointerInput.MouseWheelX ||
- eventData.inputIndex === PointerInput.MouseWheelY ||
- eventData.inputIndex === PointerInput.MouseWheelZ) {
- this._onPointerMove(eventData);
- }
- }
- });
- }
- else if (deviceSource.deviceType === DeviceType.Touch) {
- deviceSource.onInputChangedObservable.add((eventData) => {
- if (eventData.inputIndex === PointerInput.LeftClick) {
- if (attachDown && deviceSource.getInput(eventData.inputIndex) === 1) {
- this._onPointerDown(eventData);
- if (this._totalPointersPressed > 1) {
- this._isMultiTouchGesture = true;
- }
- }
- else if (attachUp && deviceSource.getInput(eventData.inputIndex) === 0) {
- this._onPointerUp(eventData);
- if (this._totalPointersPressed === 0) {
- this._isMultiTouchGesture = false;
- }
- }
- }
- if (attachMove && eventData.inputIndex === PointerInput.Move) {
- this._onPointerMove(eventData);
- }
- });
- }
- else if (deviceSource.deviceType === DeviceType.Keyboard) {
- deviceSource.onInputChangedObservable.add((eventData) => {
- if (eventData.type === "keydown") {
- this._onKeyDown(eventData);
- }
- else if (eventData.type === "keyup") {
- this._onKeyUp(eventData);
- }
- });
- }
- });
- this._alreadyAttached = true;
- }
- /**
- * Detaches all event handlers
- */
- detachControl() {
- if (this._alreadyAttached) {
- this._deviceSourceManager.dispose();
- this._deviceSourceManager = null;
- // Cursor
- if (this._alreadyAttachedTo && !this._scene.doNotHandleCursors) {
- this._alreadyAttachedTo.style.cursor = this._scene.defaultCursor;
- }
- this._alreadyAttached = false;
- this._alreadyAttachedTo = null;
- }
- }
- /**
- * Force the value of meshUnderPointer
- * @param mesh - defines the mesh to use
- * @param pointerId - optional pointer id when using more than one pointer. Defaults to 0
- * @param pickResult - optional pickingInfo data used to find mesh
- * @param evt - optional pointer event
- */
- setPointerOverMesh(mesh, pointerId = 0, pickResult, evt) {
- if (this._meshUnderPointerId[pointerId] === mesh && (!mesh || !mesh._internalAbstractMeshDataInfo._pointerOverDisableMeshTesting)) {
- return;
- }
- const underPointerMesh = this._meshUnderPointerId[pointerId];
- let actionManager;
- if (underPointerMesh) {
- actionManager = underPointerMesh._getActionManagerForTrigger(10);
- if (actionManager) {
- actionManager.processTrigger(10, ActionEvent.CreateNew(underPointerMesh, evt, { pointerId }));
- }
- }
- if (mesh) {
- this._meshUnderPointerId[pointerId] = mesh;
- this._pointerOverMesh = mesh;
- actionManager = mesh._getActionManagerForTrigger(9);
- if (actionManager) {
- actionManager.processTrigger(9, ActionEvent.CreateNew(mesh, evt, { pointerId, pickResult }));
- }
- }
- else {
- delete this._meshUnderPointerId[pointerId];
- this._pointerOverMesh = null;
- }
- }
- /**
- * Gets the mesh under the pointer
- * @returns a Mesh or null if no mesh is under the pointer
- */
- getPointerOverMesh() {
- return this.meshUnderPointer;
- }
- /**
- * @param mesh - Mesh to invalidate
- * @internal
- */
- _invalidateMesh(mesh) {
- if (this._pointerOverMesh === mesh) {
- this._pointerOverMesh = null;
- }
- if (this._pickedDownMesh === mesh) {
- this._pickedDownMesh = null;
- }
- if (this._pickedUpMesh === mesh) {
- this._pickedUpMesh = null;
- }
- for (const pointerId in this._meshUnderPointerId) {
- if (this._meshUnderPointerId[pointerId] === mesh) {
- delete this._meshUnderPointerId[pointerId];
- }
- }
- }
- }
- /** The distance in pixel that you have to move to prevent some events */
- InputManager.DragMovementThreshold = 10; // in pixels
- /** Time in milliseconds to wait to raise long press events if button is still pressed */
- InputManager.LongPressDelay = 500; // in milliseconds
- /** Time in milliseconds with two consecutive clicks will be considered as a double click */
- InputManager.DoubleClickDelay = 300; // in milliseconds
- /**
- * This flag will modify the behavior so that, when true, a click will happen if and only if
- * another click DOES NOT happen within the DoubleClickDelay time frame. If another click does
- * happen within that time frame, the first click will not fire an event and and a double click will occur.
- */
- InputManager.ExclusiveDoubleClickMode = false;
- //# sourceMappingURL=scene.inputManager.js.map
|