123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718 |
- import { IsNavigatorAvailable } from "../Misc/domManagement.js";
- import { Tools } from "../Misc/tools.js";
- import { DeviceEventFactory } from "./eventFactory.js";
- import { DeviceType, PointerInput } from "./InputDevices/deviceEnums.js";
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const MAX_KEYCODES = 255;
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const MAX_POINTER_INPUTS = Object.keys(PointerInput).length / 2;
- /** @internal */
- export class WebDeviceInputSystem {
- /**
- * Constructor for the WebDeviceInputSystem
- * @param engine Engine to reference
- * @param onDeviceConnected Callback to execute when device is connected
- * @param onDeviceDisconnected Callback to execute when device is disconnected
- * @param onInputChanged Callback to execute when input changes on device
- */
- constructor(engine, onDeviceConnected, onDeviceDisconnected, onInputChanged) {
- // Private Members
- this._inputs = [];
- this._keyboardActive = false;
- this._pointerActive = false;
- this._usingSafari = Tools.IsSafari();
- // Found solution for determining if MacOS is being used here:
- // https://stackoverflow.com/questions/10527983/best-way-to-detect-mac-os-x-or-windows-computers-with-javascript-or-jquery
- this._usingMacOS = IsNavigatorAvailable() && /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._keyboardDownEvent = (evt) => { };
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._keyboardUpEvent = (evt) => { };
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._keyboardBlurEvent = (evt) => { };
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._pointerMoveEvent = (evt) => { };
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._pointerDownEvent = (evt) => { };
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._pointerUpEvent = (evt) => { };
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._pointerCancelEvent = (evt) => { };
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._pointerWheelEvent = (evt) => { };
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._pointerBlurEvent = (evt) => { };
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._pointerMacOSChromeOutEvent = (evt) => { };
- this._eventsAttached = false;
- this._mouseId = -1;
- this._isUsingFirefox = IsNavigatorAvailable() && navigator.userAgent && navigator.userAgent.indexOf("Firefox") !== -1;
- this._isUsingChromium = IsNavigatorAvailable() && navigator.userAgent && navigator.userAgent.indexOf("Chrome") !== -1;
- this._maxTouchPoints = 0;
- this._pointerInputClearObserver = null;
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._gamepadConnectedEvent = (evt) => { };
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- this._gamepadDisconnectedEvent = (evt) => { };
- this._eventPrefix = Tools.GetPointerPrefix(engine);
- this._engine = engine;
- this._onDeviceConnected = onDeviceConnected;
- this._onDeviceDisconnected = onDeviceDisconnected;
- this._onInputChanged = onInputChanged;
- // If we need a pointerId, set one for future use
- this._mouseId = this._isUsingFirefox ? 0 : 1;
- this._enableEvents();
- if (this._usingMacOS) {
- this._metaKeys = [];
- }
- // Set callback to enable event handler switching when inputElement changes
- if (!this._engine._onEngineViewChanged) {
- this._engine._onEngineViewChanged = () => {
- this._enableEvents();
- };
- }
- }
- // Public functions
- /**
- * Checks for current device input value, given an id and input index. Throws exception if requested device not initialized.
- * @param deviceType Enum specifying device type
- * @param deviceSlot "Slot" or index that device is referenced in
- * @param inputIndex Id of input to be checked
- * @returns Current value of input
- */
- pollInput(deviceType, deviceSlot, inputIndex) {
- const device = this._inputs[deviceType][deviceSlot];
- if (!device) {
- // eslint-disable-next-line no-throw-literal
- throw `Unable to find device ${DeviceType[deviceType]}`;
- }
- if (deviceType >= DeviceType.DualShock && deviceType <= DeviceType.DualSense) {
- this._updateDevice(deviceType, deviceSlot, inputIndex);
- }
- const currentValue = device[inputIndex];
- if (currentValue === undefined) {
- // eslint-disable-next-line no-throw-literal
- throw `Unable to find input ${inputIndex} for device ${DeviceType[deviceType]} in slot ${deviceSlot}`;
- }
- if (inputIndex === PointerInput.Move) {
- Tools.Warn(`Unable to provide information for PointerInput.Move. Try using PointerInput.Horizontal or PointerInput.Vertical for move data.`);
- }
- return currentValue;
- }
- /**
- * Check for a specific device in the DeviceInputSystem
- * @param deviceType Type of device to check for
- * @returns bool with status of device's existence
- */
- isDeviceAvailable(deviceType) {
- return this._inputs[deviceType] !== undefined;
- }
- /**
- * Dispose of all the eventlisteners
- */
- dispose() {
- // Callbacks
- this._onDeviceConnected = () => { };
- this._onDeviceDisconnected = () => { };
- this._onInputChanged = () => { };
- delete this._engine._onEngineViewChanged;
- if (this._elementToAttachTo) {
- this._disableEvents();
- }
- }
- /**
- * Enable listening for user input events
- */
- _enableEvents() {
- const inputElement = this?._engine.getInputElement();
- if (inputElement && (!this._eventsAttached || this._elementToAttachTo !== inputElement)) {
- // Remove events before adding to avoid double events or simultaneous events on multiple canvases
- this._disableEvents();
- // If the inputs array has already been created, zero it out to before setting up events
- if (this._inputs) {
- for (const inputs of this._inputs) {
- if (inputs) {
- for (const deviceSlotKey in inputs) {
- const deviceSlot = +deviceSlotKey;
- const device = inputs[deviceSlot];
- if (device) {
- for (let inputIndex = 0; inputIndex < device.length; inputIndex++) {
- device[inputIndex] = 0;
- }
- }
- }
- }
- }
- }
- this._elementToAttachTo = inputElement;
- // Set tab index for the inputElement to the engine's canvasTabIndex, if and only if the element's tab index is -1
- this._elementToAttachTo.tabIndex = this._elementToAttachTo.tabIndex !== -1 ? this._elementToAttachTo.tabIndex : this._engine.canvasTabIndex;
- this._handleKeyActions();
- this._handlePointerActions();
- this._handleGamepadActions();
- this._eventsAttached = true;
- // Check for devices that are already connected but aren't registered. Currently, only checks for gamepads and mouse
- this._checkForConnectedDevices();
- }
- }
- /**
- * Disable listening for user input events
- */
- _disableEvents() {
- if (this._elementToAttachTo) {
- // Blur Events
- this._elementToAttachTo.removeEventListener("blur", this._keyboardBlurEvent);
- this._elementToAttachTo.removeEventListener("blur", this._pointerBlurEvent);
- // Keyboard Events
- this._elementToAttachTo.removeEventListener("keydown", this._keyboardDownEvent);
- this._elementToAttachTo.removeEventListener("keyup", this._keyboardUpEvent);
- // Pointer Events
- this._elementToAttachTo.removeEventListener(this._eventPrefix + "move", this._pointerMoveEvent);
- this._elementToAttachTo.removeEventListener(this._eventPrefix + "down", this._pointerDownEvent);
- this._elementToAttachTo.removeEventListener(this._eventPrefix + "up", this._pointerUpEvent);
- this._elementToAttachTo.removeEventListener(this._eventPrefix + "cancel", this._pointerCancelEvent);
- this._elementToAttachTo.removeEventListener(this._wheelEventName, this._pointerWheelEvent);
- if (this._usingMacOS && this._isUsingChromium) {
- this._elementToAttachTo.removeEventListener("lostpointercapture", this._pointerMacOSChromeOutEvent);
- }
- // Gamepad Events
- window.removeEventListener("gamepadconnected", this._gamepadConnectedEvent);
- window.removeEventListener("gamepaddisconnected", this._gamepadDisconnectedEvent);
- }
- if (this._pointerInputClearObserver) {
- this._engine.onEndFrameObservable.remove(this._pointerInputClearObserver);
- }
- this._eventsAttached = false;
- }
- /**
- * Checks for existing connections to devices and register them, if necessary
- * Currently handles gamepads and mouse
- */
- _checkForConnectedDevices() {
- if (navigator.getGamepads) {
- const gamepads = navigator.getGamepads();
- for (const gamepad of gamepads) {
- if (gamepad) {
- this._addGamePad(gamepad);
- }
- }
- }
- // If the device in use has mouse capabilities, pre-register mouse
- if (typeof matchMedia === "function" && matchMedia("(pointer:fine)").matches) {
- // This will provide a dummy value for the cursor position and is expected to be overridden when the first mouse event happens.
- // There isn't any good way to get the current position outside of a pointer event so that's why this was done.
- this._addPointerDevice(DeviceType.Mouse, 0, 0, 0);
- }
- }
- // Private functions
- /**
- * Add a gamepad to the DeviceInputSystem
- * @param gamepad A single DOM Gamepad object
- */
- _addGamePad(gamepad) {
- const deviceType = this._getGamepadDeviceType(gamepad.id);
- const deviceSlot = gamepad.index;
- this._gamepads = this._gamepads || new Array(gamepad.index + 1);
- this._registerDevice(deviceType, deviceSlot, gamepad.buttons.length + gamepad.axes.length);
- this._gamepads[deviceSlot] = deviceType;
- }
- /**
- * Add pointer device to DeviceInputSystem
- * @param deviceType Type of Pointer to add
- * @param deviceSlot Pointer ID (0 for mouse, pointerId for Touch)
- * @param currentX Current X at point of adding
- * @param currentY Current Y at point of adding
- */
- _addPointerDevice(deviceType, deviceSlot, currentX, currentY) {
- if (!this._pointerActive) {
- this._pointerActive = true;
- }
- this._registerDevice(deviceType, deviceSlot, MAX_POINTER_INPUTS);
- const pointer = this._inputs[deviceType][deviceSlot]; /* initialize our pointer position immediately after registration */
- pointer[0] = currentX;
- pointer[1] = currentY;
- }
- /**
- * Add device and inputs to device array
- * @param deviceType Enum specifying device type
- * @param deviceSlot "Slot" or index that device is referenced in
- * @param numberOfInputs Number of input entries to create for given device
- */
- _registerDevice(deviceType, deviceSlot, numberOfInputs) {
- if (deviceSlot === undefined) {
- // eslint-disable-next-line no-throw-literal
- throw `Unable to register device ${DeviceType[deviceType]} to undefined slot.`;
- }
- if (!this._inputs[deviceType]) {
- this._inputs[deviceType] = {};
- }
- if (!this._inputs[deviceType][deviceSlot]) {
- const device = new Array(numberOfInputs);
- device.fill(0);
- this._inputs[deviceType][deviceSlot] = device;
- this._onDeviceConnected(deviceType, deviceSlot);
- }
- }
- /**
- * Given a specific device name, remove that device from the device map
- * @param deviceType Enum specifying device type
- * @param deviceSlot "Slot" or index that device is referenced in
- */
- _unregisterDevice(deviceType, deviceSlot) {
- if (this._inputs[deviceType][deviceSlot]) {
- delete this._inputs[deviceType][deviceSlot];
- this._onDeviceDisconnected(deviceType, deviceSlot);
- }
- }
- /**
- * Handle all actions that come from keyboard interaction
- */
- _handleKeyActions() {
- this._keyboardDownEvent = (evt) => {
- if (!this._keyboardActive) {
- this._keyboardActive = true;
- this._registerDevice(DeviceType.Keyboard, 0, MAX_KEYCODES);
- }
- const kbKey = this._inputs[DeviceType.Keyboard][0];
- if (kbKey) {
- kbKey[evt.keyCode] = 1;
- const deviceEvent = evt;
- deviceEvent.inputIndex = evt.keyCode;
- if (this._usingMacOS && evt.metaKey && evt.key !== "Meta") {
- if (!this._metaKeys.includes(evt.keyCode)) {
- this._metaKeys.push(evt.keyCode);
- }
- }
- this._onInputChanged(DeviceType.Keyboard, 0, deviceEvent);
- }
- };
- this._keyboardUpEvent = (evt) => {
- if (!this._keyboardActive) {
- this._keyboardActive = true;
- this._registerDevice(DeviceType.Keyboard, 0, MAX_KEYCODES);
- }
- const kbKey = this._inputs[DeviceType.Keyboard][0];
- if (kbKey) {
- kbKey[evt.keyCode] = 0;
- const deviceEvent = evt;
- deviceEvent.inputIndex = evt.keyCode;
- if (this._usingMacOS && evt.key === "Meta" && this._metaKeys.length > 0) {
- for (const keyCode of this._metaKeys) {
- const deviceEvent = DeviceEventFactory.CreateDeviceEvent(DeviceType.Keyboard, 0, keyCode, 0, this, this._elementToAttachTo);
- kbKey[keyCode] = 0;
- this._onInputChanged(DeviceType.Keyboard, 0, deviceEvent);
- }
- this._metaKeys.splice(0, this._metaKeys.length);
- }
- this._onInputChanged(DeviceType.Keyboard, 0, deviceEvent);
- }
- };
- this._keyboardBlurEvent = () => {
- if (this._keyboardActive) {
- const kbKey = this._inputs[DeviceType.Keyboard][0];
- for (let i = 0; i < kbKey.length; i++) {
- if (kbKey[i] !== 0) {
- kbKey[i] = 0;
- const deviceEvent = DeviceEventFactory.CreateDeviceEvent(DeviceType.Keyboard, 0, i, 0, this, this._elementToAttachTo);
- this._onInputChanged(DeviceType.Keyboard, 0, deviceEvent);
- }
- }
- if (this._usingMacOS) {
- this._metaKeys.splice(0, this._metaKeys.length);
- }
- }
- };
- this._elementToAttachTo.addEventListener("keydown", this._keyboardDownEvent);
- this._elementToAttachTo.addEventListener("keyup", this._keyboardUpEvent);
- this._elementToAttachTo.addEventListener("blur", this._keyboardBlurEvent);
- }
- /**
- * Handle all actions that come from pointer interaction
- */
- _handlePointerActions() {
- // If maxTouchPoints is defined, use that value. Otherwise, allow for a minimum for supported gestures like pinch
- this._maxTouchPoints = (IsNavigatorAvailable() && navigator.maxTouchPoints) || 2;
- if (!this._activeTouchIds) {
- this._activeTouchIds = new Array(this._maxTouchPoints);
- }
- for (let i = 0; i < this._maxTouchPoints; i++) {
- this._activeTouchIds[i] = -1;
- }
- this._pointerMoveEvent = (evt) => {
- const deviceType = this._getPointerType(evt);
- let deviceSlot = deviceType === DeviceType.Mouse ? 0 : this._activeTouchIds.indexOf(evt.pointerId);
- // In the event that we're gettting pointermove events from touch inputs that we aren't tracking,
- // look for an available slot and retroactively connect it.
- if (deviceType === DeviceType.Touch && deviceSlot === -1) {
- const idx = this._activeTouchIds.indexOf(-1);
- if (idx >= 0) {
- deviceSlot = idx;
- this._activeTouchIds[idx] = evt.pointerId;
- // Because this is a "new" input, inform the connected callback
- this._onDeviceConnected(deviceType, deviceSlot);
- }
- else {
- // We can't find an open slot to store new pointer so just return (can only support max number of touches)
- Tools.Warn(`Max number of touches exceeded. Ignoring touches in excess of ${this._maxTouchPoints}`);
- return;
- }
- }
- if (!this._inputs[deviceType]) {
- this._inputs[deviceType] = {};
- }
- if (!this._inputs[deviceType][deviceSlot]) {
- this._addPointerDevice(deviceType, deviceSlot, evt.clientX, evt.clientY);
- }
- const pointer = this._inputs[deviceType][deviceSlot];
- if (pointer) {
- const deviceEvent = evt;
- deviceEvent.inputIndex = PointerInput.Move;
- pointer[PointerInput.Horizontal] = evt.clientX;
- pointer[PointerInput.Vertical] = evt.clientY;
- // For touches that aren't started with a down, we need to set the button state to 1
- if (deviceType === DeviceType.Touch && pointer[PointerInput.LeftClick] === 0) {
- pointer[PointerInput.LeftClick] = 1;
- }
- if (evt.pointerId === undefined) {
- evt.pointerId = this._mouseId;
- }
- this._onInputChanged(deviceType, deviceSlot, deviceEvent);
- // Lets Propagate the event for move with same position.
- if (!this._usingSafari && evt.button !== -1) {
- deviceEvent.inputIndex = evt.button + 2;
- pointer[evt.button + 2] = pointer[evt.button + 2] ? 0 : 1; // Reverse state of button if evt.button has value
- this._onInputChanged(deviceType, deviceSlot, deviceEvent);
- }
- }
- };
- this._pointerDownEvent = (evt) => {
- const deviceType = this._getPointerType(evt);
- let deviceSlot = deviceType === DeviceType.Mouse ? 0 : evt.pointerId;
- if (deviceType === DeviceType.Touch) {
- const idx = this._activeTouchIds.indexOf(-1);
- if (idx >= 0) {
- deviceSlot = idx;
- this._activeTouchIds[idx] = evt.pointerId;
- }
- else {
- // We can't find an open slot to store new pointer so just return (can only support max number of touches)
- Tools.Warn(`Max number of touches exceeded. Ignoring touches in excess of ${this._maxTouchPoints}`);
- return;
- }
- }
- if (!this._inputs[deviceType]) {
- this._inputs[deviceType] = {};
- }
- if (!this._inputs[deviceType][deviceSlot]) {
- this._addPointerDevice(deviceType, deviceSlot, evt.clientX, evt.clientY);
- }
- else if (deviceType === DeviceType.Touch) {
- this._onDeviceConnected(deviceType, deviceSlot);
- }
- const pointer = this._inputs[deviceType][deviceSlot];
- if (pointer) {
- const previousHorizontal = pointer[PointerInput.Horizontal];
- const previousVertical = pointer[PointerInput.Vertical];
- if (deviceType === DeviceType.Mouse) {
- // Mouse; Set pointerId if undefined
- if (evt.pointerId === undefined) {
- evt.pointerId = this._mouseId;
- }
- if (!document.pointerLockElement) {
- try {
- this._elementToAttachTo.setPointerCapture(this._mouseId);
- }
- catch (e) {
- // DO NOTHING
- }
- }
- }
- else {
- // Touch; Since touches are dynamically assigned, only set capture if we have an id
- if (evt.pointerId && !document.pointerLockElement) {
- try {
- this._elementToAttachTo.setPointerCapture(evt.pointerId);
- }
- catch (e) {
- // DO NOTHING
- }
- }
- }
- pointer[PointerInput.Horizontal] = evt.clientX;
- pointer[PointerInput.Vertical] = evt.clientY;
- pointer[evt.button + 2] = 1;
- const deviceEvent = evt;
- // NOTE: The +2 used here to is because PointerInput has the same value progression for its mouse buttons as PointerEvent.button
- // However, we have our X and Y values front-loaded to group together the touch inputs but not break this progression
- // EG. ([X, Y, Left-click], Middle-click, etc...)
- deviceEvent.inputIndex = evt.button + 2;
- this._onInputChanged(deviceType, deviceSlot, deviceEvent);
- if (previousHorizontal !== evt.clientX || previousVertical !== evt.clientY) {
- deviceEvent.inputIndex = PointerInput.Move;
- this._onInputChanged(deviceType, deviceSlot, deviceEvent);
- }
- }
- };
- this._pointerUpEvent = (evt) => {
- const deviceType = this._getPointerType(evt);
- const deviceSlot = deviceType === DeviceType.Mouse ? 0 : this._activeTouchIds.indexOf(evt.pointerId);
- if (deviceType === DeviceType.Touch) {
- // If we're getting a pointerup event for a touch that isn't active, just return.
- if (deviceSlot === -1) {
- return;
- }
- else {
- this._activeTouchIds[deviceSlot] = -1;
- }
- }
- const pointer = this._inputs[deviceType]?.[deviceSlot];
- if (pointer && pointer[evt.button + 2] !== 0) {
- const previousHorizontal = pointer[PointerInput.Horizontal];
- const previousVertical = pointer[PointerInput.Vertical];
- pointer[PointerInput.Horizontal] = evt.clientX;
- pointer[PointerInput.Vertical] = evt.clientY;
- pointer[evt.button + 2] = 0;
- const deviceEvent = evt;
- if (evt.pointerId === undefined) {
- evt.pointerId = this._mouseId;
- }
- if (previousHorizontal !== evt.clientX || previousVertical !== evt.clientY) {
- deviceEvent.inputIndex = PointerInput.Move;
- this._onInputChanged(deviceType, deviceSlot, deviceEvent);
- }
- // NOTE: The +2 used here to is because PointerInput has the same value progression for its mouse buttons as PointerEvent.button
- // However, we have our X and Y values front-loaded to group together the touch inputs but not break this progression
- // EG. ([X, Y, Left-click], Middle-click, etc...)
- deviceEvent.inputIndex = evt.button + 2;
- if (deviceType === DeviceType.Mouse && this._mouseId >= 0 && this._elementToAttachTo.hasPointerCapture?.(this._mouseId)) {
- this._elementToAttachTo.releasePointerCapture(this._mouseId);
- }
- else if (evt.pointerId && this._elementToAttachTo.hasPointerCapture?.(evt.pointerId)) {
- this._elementToAttachTo.releasePointerCapture(evt.pointerId);
- }
- this._onInputChanged(deviceType, deviceSlot, deviceEvent);
- if (deviceType === DeviceType.Touch) {
- this._onDeviceDisconnected(deviceType, deviceSlot);
- }
- }
- };
- this._pointerCancelEvent = (evt) => {
- if (evt.pointerType === "mouse") {
- const pointer = this._inputs[DeviceType.Mouse][0];
- if (this._mouseId >= 0 && this._elementToAttachTo.hasPointerCapture?.(this._mouseId)) {
- this._elementToAttachTo.releasePointerCapture(this._mouseId);
- }
- for (let inputIndex = PointerInput.LeftClick; inputIndex <= PointerInput.BrowserForward; inputIndex++) {
- if (pointer[inputIndex] === 1) {
- pointer[inputIndex] = 0;
- const deviceEvent = DeviceEventFactory.CreateDeviceEvent(DeviceType.Mouse, 0, inputIndex, 0, this, this._elementToAttachTo);
- this._onInputChanged(DeviceType.Mouse, 0, deviceEvent);
- }
- }
- }
- else {
- const deviceSlot = this._activeTouchIds.indexOf(evt.pointerId);
- // If we're getting a pointercancel event for a touch that isn't active, just return
- if (deviceSlot === -1) {
- return;
- }
- if (this._elementToAttachTo.hasPointerCapture?.(evt.pointerId)) {
- this._elementToAttachTo.releasePointerCapture(evt.pointerId);
- }
- this._inputs[DeviceType.Touch][deviceSlot][PointerInput.LeftClick] = 0;
- const deviceEvent = DeviceEventFactory.CreateDeviceEvent(DeviceType.Touch, deviceSlot, PointerInput.LeftClick, 0, this, this._elementToAttachTo, evt.pointerId);
- this._onInputChanged(DeviceType.Touch, deviceSlot, deviceEvent);
- this._activeTouchIds[deviceSlot] = -1;
- this._onDeviceDisconnected(DeviceType.Touch, deviceSlot);
- }
- };
- // Set Wheel Event Name, code originally from scene.inputManager
- this._wheelEventName =
- "onwheel" in document.createElement("div")
- ? "wheel" // Modern browsers support "wheel"
- : document.onmousewheel !== undefined
- ? "mousewheel" // Webkit and IE support at least "mousewheel"
- : "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox
- // Code originally in scene.inputManager.ts
- // Chrome reports warning in console if wheel listener doesn't set an explicit passive option.
- // IE11 only supports captureEvent:boolean, not options:object, and it defaults to false.
- // Feature detection technique copied from: https://github.com/github/eventlistener-polyfill (MIT license)
- let passiveSupported = false;
- const noop = function () { };
- try {
- const options = Object.defineProperty({}, "passive", {
- get: function () {
- passiveSupported = true;
- },
- });
- this._elementToAttachTo.addEventListener("test", noop, options);
- this._elementToAttachTo.removeEventListener("test", noop, options);
- }
- catch (e) {
- /* */
- }
- this._pointerBlurEvent = () => {
- // Handle mouse buttons
- if (this.isDeviceAvailable(DeviceType.Mouse)) {
- const pointer = this._inputs[DeviceType.Mouse][0];
- if (this._mouseId >= 0 && this._elementToAttachTo.hasPointerCapture?.(this._mouseId)) {
- this._elementToAttachTo.releasePointerCapture(this._mouseId);
- }
- for (let inputIndex = PointerInput.LeftClick; inputIndex <= PointerInput.BrowserForward; inputIndex++) {
- if (pointer[inputIndex] === 1) {
- pointer[inputIndex] = 0;
- const deviceEvent = DeviceEventFactory.CreateDeviceEvent(DeviceType.Mouse, 0, inputIndex, 0, this, this._elementToAttachTo);
- this._onInputChanged(DeviceType.Mouse, 0, deviceEvent);
- }
- }
- }
- // Handle Active Touches
- if (this.isDeviceAvailable(DeviceType.Touch)) {
- const pointer = this._inputs[DeviceType.Touch];
- for (let deviceSlot = 0; deviceSlot < this._activeTouchIds.length; deviceSlot++) {
- const pointerId = this._activeTouchIds[deviceSlot];
- if (this._elementToAttachTo.hasPointerCapture?.(pointerId)) {
- this._elementToAttachTo.releasePointerCapture(pointerId);
- }
- if (pointerId !== -1 && pointer[deviceSlot]?.[PointerInput.LeftClick] === 1) {
- pointer[deviceSlot][PointerInput.LeftClick] = 0;
- const deviceEvent = DeviceEventFactory.CreateDeviceEvent(DeviceType.Touch, deviceSlot, PointerInput.LeftClick, 0, this, this._elementToAttachTo, pointerId);
- this._onInputChanged(DeviceType.Touch, deviceSlot, deviceEvent);
- this._activeTouchIds[deviceSlot] = -1;
- this._onDeviceDisconnected(DeviceType.Touch, deviceSlot);
- }
- }
- }
- };
- this._pointerWheelEvent = (evt) => {
- const deviceType = DeviceType.Mouse;
- const deviceSlot = 0;
- if (!this._inputs[deviceType]) {
- this._inputs[deviceType] = [];
- }
- if (!this._inputs[deviceType][deviceSlot]) {
- this._pointerActive = true;
- this._registerDevice(deviceType, deviceSlot, MAX_POINTER_INPUTS);
- }
- const pointer = this._inputs[deviceType][deviceSlot];
- if (pointer) {
- pointer[PointerInput.MouseWheelX] = evt.deltaX || 0;
- pointer[PointerInput.MouseWheelY] = evt.deltaY || evt.wheelDelta || 0;
- pointer[PointerInput.MouseWheelZ] = evt.deltaZ || 0;
- const deviceEvent = evt;
- // By default, there is no pointerId for mouse wheel events so we'll add one here
- // This logic was originally in the InputManager but was added here to make the
- // InputManager more platform-agnostic
- if (evt.pointerId === undefined) {
- evt.pointerId = this._mouseId;
- }
- if (pointer[PointerInput.MouseWheelX] !== 0) {
- deviceEvent.inputIndex = PointerInput.MouseWheelX;
- this._onInputChanged(deviceType, deviceSlot, deviceEvent);
- }
- if (pointer[PointerInput.MouseWheelY] !== 0) {
- deviceEvent.inputIndex = PointerInput.MouseWheelY;
- this._onInputChanged(deviceType, deviceSlot, deviceEvent);
- }
- if (pointer[PointerInput.MouseWheelZ] !== 0) {
- deviceEvent.inputIndex = PointerInput.MouseWheelZ;
- this._onInputChanged(deviceType, deviceSlot, deviceEvent);
- }
- }
- };
- // Workaround for MacOS Chromium Browsers for lost pointer capture bug
- if (this._usingMacOS && this._isUsingChromium) {
- this._pointerMacOSChromeOutEvent = (evt) => {
- if (evt.buttons > 1) {
- this._pointerCancelEvent(evt);
- }
- };
- this._elementToAttachTo.addEventListener("lostpointercapture", this._pointerMacOSChromeOutEvent);
- }
- this._elementToAttachTo.addEventListener(this._eventPrefix + "move", this._pointerMoveEvent);
- this._elementToAttachTo.addEventListener(this._eventPrefix + "down", this._pointerDownEvent);
- this._elementToAttachTo.addEventListener(this._eventPrefix + "up", this._pointerUpEvent);
- this._elementToAttachTo.addEventListener(this._eventPrefix + "cancel", this._pointerCancelEvent);
- this._elementToAttachTo.addEventListener("blur", this._pointerBlurEvent);
- this._elementToAttachTo.addEventListener(this._wheelEventName, this._pointerWheelEvent, passiveSupported ? { passive: false } : false);
- // Since there's no up or down event for mouse wheel or delta x/y, clear mouse values at end of frame
- this._pointerInputClearObserver = this._engine.onEndFrameObservable.add(() => {
- if (this.isDeviceAvailable(DeviceType.Mouse)) {
- const pointer = this._inputs[DeviceType.Mouse][0];
- pointer[PointerInput.MouseWheelX] = 0;
- pointer[PointerInput.MouseWheelY] = 0;
- pointer[PointerInput.MouseWheelZ] = 0;
- }
- });
- }
- /**
- * Handle all actions that come from gamepad interaction
- */
- _handleGamepadActions() {
- this._gamepadConnectedEvent = (evt) => {
- this._addGamePad(evt.gamepad);
- };
- this._gamepadDisconnectedEvent = (evt) => {
- if (this._gamepads) {
- const deviceType = this._getGamepadDeviceType(evt.gamepad.id);
- const deviceSlot = evt.gamepad.index;
- this._unregisterDevice(deviceType, deviceSlot);
- delete this._gamepads[deviceSlot];
- }
- };
- window.addEventListener("gamepadconnected", this._gamepadConnectedEvent);
- window.addEventListener("gamepaddisconnected", this._gamepadDisconnectedEvent);
- }
- /**
- * Update all non-event based devices with each frame
- * @param deviceType Enum specifying device type
- * @param deviceSlot "Slot" or index that device is referenced in
- * @param inputIndex Id of input to be checked
- */
- _updateDevice(deviceType, deviceSlot, inputIndex) {
- // Gamepads
- const gp = navigator.getGamepads()[deviceSlot];
- if (gp && deviceType === this._gamepads[deviceSlot]) {
- const device = this._inputs[deviceType][deviceSlot];
- if (inputIndex >= gp.buttons.length) {
- device[inputIndex] = gp.axes[inputIndex - gp.buttons.length].valueOf();
- }
- else {
- device[inputIndex] = gp.buttons[inputIndex].value;
- }
- }
- }
- /**
- * Gets DeviceType from the device name
- * @param deviceName Name of Device from DeviceInputSystem
- * @returns DeviceType enum value
- */
- _getGamepadDeviceType(deviceName) {
- if (deviceName.indexOf("054c") !== -1) {
- // DualShock 4 Gamepad
- return deviceName.indexOf("0ce6") !== -1 ? DeviceType.DualSense : DeviceType.DualShock;
- }
- else if (deviceName.indexOf("Xbox One") !== -1 || deviceName.search("Xbox 360") !== -1 || deviceName.search("xinput") !== -1) {
- // Xbox Gamepad
- return DeviceType.Xbox;
- }
- else if (deviceName.indexOf("057e") !== -1) {
- // Switch Gamepad
- return DeviceType.Switch;
- }
- return DeviceType.Generic;
- }
- /**
- * Get DeviceType from a given pointer/mouse/touch event.
- * @param evt PointerEvent to evaluate
- * @returns DeviceType interpreted from event
- */
- _getPointerType(evt) {
- let deviceType = DeviceType.Mouse;
- if (evt.pointerType === "touch" || evt.pointerType === "pen" || evt.touches) {
- deviceType = DeviceType.Touch;
- }
- return deviceType;
- }
- }
- //# sourceMappingURL=webDeviceInputSystem.js.map
|