lazy-widgets
Version:
Typescript retained mode GUI for the HTML canvas API
212 lines • 8.69 kB
JavaScript
import { getPointerEventNormPos } from '../helpers/getPointerEventNormPos.js';
import { parseDOMDeltaMode } from '../events/PointerWheelEvent.js';
import { PointerDriver } from './PointerDriver.js';
import { Msg } from '../core/Strings.js';
/**
* Unpack a MouseEvent into a 3-tuple containing the event's modifier key state.
* The 3-tuple contains, respectively, whether shift is being held, whether ctrl
* is being held, and whether alt is being held.
*
* @category Driver
*/
function unpackModifiers(event) {
return [event.shiftKey, event.ctrlKey, event.altKey];
}
/**
* A {@link PointerDriver} which listens for pointer events from HTML DOM
* elements. Each HTML DOM element is bound to a specific root, which synergizes
* well with DOMRoot.
*
* Automatically registers a pointer to be used by the mouse.
*
* @category Driver
*/
export class DOMPointerDriver extends PointerDriver {
constructor() {
super();
/** The HTML DOM element and listeners that each root is bound to */
this.domElems = new WeakMap();
/** The mapping between each DOM pointer ID and internal pointer ID */
this.pointers = new Map();
this.mousePointerID = this.registerPointer(false);
}
/**
* Bind an HTML DOM element to a specific root.
*
* If the root was already bound,
* {@link DOMPointerDriver#removeListeners} is called, replacing the old
* listeners. Populates {@link DOMPointerDriver#domElems} with the new bind.
* Calls {@link DOMPointerDriver#addListeners} if root is enabled.
*/
bindDOMElem(root, domElem) {
let rootBind = this.domElems.get(root);
if (rootBind !== undefined) {
console.warn(Msg.DOM_DRIVER_REBIND);
this.removeListeners(rootBind);
}
else {
rootBind = {
domElem,
pointerListen: null,
pointerleaveListen: null,
wheelListen: null,
contextMenuListen: null,
};
this.domElems.set(root, rootBind);
}
if (root.enabled) {
this.addListeners(root, rootBind);
}
}
/**
* Unbind a HTML DOM element from this pointer driver that is bound to a
* given Root. Removes all used listeners.
*/
unbindDOMElem(root) {
const rootBind = this.domElems.get(root);
if (rootBind === undefined) {
return;
}
this.removeListeners(rootBind);
this.domElems.delete(root);
}
/**
* Get the internal pointer ID of a given event. If the event has a pointer
* which hasn't been registered yet, then it is registered automatically
*/
getPointerID(event) {
let pointerID = this.pointers.get(event.pointerId);
if (pointerID === undefined) {
if (event.pointerType === 'mouse') {
pointerID = this.mousePointerID;
}
else {
pointerID = this.registerPointer(true);
}
this.pointers.set(event.pointerId, pointerID);
}
return pointerID;
}
/** Add pointer event listeners to root's DOM element. */
addListeners(root, rootBind) {
// Make listeners for mouse events, dispatching events. Add them to the
// root DOM bind so they can be removed later when needed
const domElem = rootBind.domElem;
if (rootBind.pointerListen === null) {
rootBind.pointerListen = (event) => {
this.handleCaptureWithTouchAction(event, domElem, this.movePointer(root, this.getPointerID(event), ...getPointerEventNormPos(event, domElem), event.buttons, ...unpackModifiers(event)));
// HACK prevent virtual keyboard flashes when moving cursor
if (root.textInputHandler) {
event.preventDefault();
}
};
domElem.addEventListener('pointermove', rootBind.pointerListen);
domElem.addEventListener('pointerdown', rootBind.pointerListen);
domElem.addEventListener('pointerup', rootBind.pointerListen);
}
if (rootBind.pointerleaveListen === null) {
rootBind.pointerleaveListen = (event) => {
const inputHandler = root.currentTextInputHandler;
if (inputHandler) {
const curFocus = document.activeElement;
if (curFocus && inputHandler.domElems.indexOf(curFocus) >= 0) {
// HACK prevent VK from stealing focus of root,
// preventing double-clicks in TextInput widgets
event.preventDefault();
}
}
this.handleCapture(event, this.leavePointer(root, this.getPointerID(event)));
};
domElem.addEventListener('pointerleave', rootBind.pointerleaveListen);
}
if (rootBind.wheelListen === null) {
rootBind.wheelListen = (event) => {
const deltaMode = parseDOMDeltaMode(event.deltaMode);
if (deltaMode === null) {
return;
}
this.handleCapture(event, this.wheelPointer(root, this.mousePointerID, ...getPointerEventNormPos(event, domElem), event.deltaX, event.deltaY, event.deltaZ, deltaMode, ...unpackModifiers(event)));
};
domElem.addEventListener('wheel', rootBind.wheelListen, { passive: false });
}
if (rootBind.contextMenuListen === null) {
rootBind.contextMenuListen = (event) => {
// Prevent right-click/long-tap from opening context menu
event.preventDefault();
};
domElem.addEventListener('contextmenu', rootBind.contextMenuListen);
}
}
/**
* Remove pointer event listeners from root's DOM element and unset tracked
* listeners in root's bind.
*/
removeListeners(rootBind) {
if (rootBind.pointerListen !== null) {
rootBind.domElem.removeEventListener('pointermove', rootBind.pointerListen);
rootBind.domElem.removeEventListener('pointerdown', rootBind.pointerListen);
rootBind.domElem.removeEventListener('pointerup', rootBind.pointerListen);
rootBind.pointerListen = null;
}
if (rootBind.pointerleaveListen !== null) {
rootBind.domElem.removeEventListener('pointerleave', rootBind.pointerleaveListen);
rootBind.pointerleaveListen = null;
}
if (rootBind.wheelListen !== null) {
rootBind.domElem.removeEventListener('wheel', rootBind.wheelListen);
rootBind.wheelListen = null;
}
if (rootBind.contextMenuListen !== null) {
rootBind.domElem.removeEventListener('contextmenu', rootBind.contextMenuListen);
rootBind.contextMenuListen = null;
}
}
/**
* Calls {@link PointerDriver#onEnable} and
* {@link DOMPointerDriver#addListeners} to each bound root.
*/
onEnable(root) {
super.onEnable(root);
// Add event listeners for pointer when root is enabled, if the root is
// bound to a DOM element
const rootBind = this.domElems.get(root);
if (rootBind !== undefined) {
this.addListeners(root, rootBind);
}
}
/**
* Calls {@link PointerDriver#onDisable} and
* {@link DOMPointerDriver#removeListeners} to each bound root.
*/
onDisable(root) {
super.onDisable(root);
// Remove event listeners for pointer when root is disabled, if the root
// is bound to a DOM element
const rootBind = this.domElems.get(root);
if (rootBind !== undefined) {
this.removeListeners(rootBind);
}
}
/**
* Handle the capture of a DOM event. If captured, then the event will be
* stopImmediatePropagation'ed, and preventDefault'ed if it's a wheel event.
*/
handleCapture(event, captured) {
if (captured) {
event.stopImmediatePropagation();
if (event.type === 'wheel') {
event.preventDefault();
}
}
}
/**
* Similar to {@link DOMPointerDriver#handleCapture}, but also updates
* the DOM element's touchAction property to prevent scrolling if the event
* was captured.
*/
handleCaptureWithTouchAction(event, domElem, captured) {
this.handleCapture(event, captured);
domElem.style.touchAction = captured ? 'none' : 'auto';
}
}
//# sourceMappingURL=DOMPointerDriver.js.map