Implement TouchpadMode gesture handling.

This commit is contained in:
Jared Goodwin 2024-02-04 11:17:43 -08:00
parent 810d294b18
commit 6175985967
6 changed files with 457 additions and 41 deletions

View File

@ -88,7 +88,7 @@ const UI = {
});
// Adapt the interface for touch screen devices
if (isTouchDevice) {
if (isTouchDevice()) {
// Remove the address bar
setTimeout(() => window.scrollTo(0, 1), 100);
}
@ -464,6 +464,12 @@ const UI = {
.classList.remove('noVNC_open');
},
/**
* @param {string} text
* @param { "normal" | "info" | "warn" | "warning" | "error" } statusType
* @param {number} time
* @returns
*/
showStatus(text, statusType, time) {
const statusElem = document.getElementById('noVNC_status');
@ -1064,8 +1070,10 @@ const UI = {
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
UI.rfb.showDotCursor = UI.getSetting('show_dot');
UI.rfb.touchpadMode = WebUtil.readSetting('touchpad_mode', 'false') === 'true';
UI.updateViewOnly(); // requires UI.rfb
UI.updateTouchpadMode();
},
disconnect() {
@ -1119,6 +1127,12 @@ const UI = {
// Do this last because it can only be used on rendered elements
UI.rfb.focus();
// In touchpad mode, we want the cursor centered in the
// viewport at the start so we can see it.
if (UI.rfb.touchpadMode) {
UI.rfb.centerCursorInViewport();
}
},
disconnectFinished(e) {
@ -1348,7 +1362,7 @@ const UI = {
// Can't be clipping if viewport is scaled to fit
UI.forceSetting('view_clip', false);
UI.rfb.clipViewport = false;
} else if (brokenScrollbars) {
} else if (brokenScrollbars || UI.rfb.touchpadMode) {
UI.forceSetting('view_clip', true);
UI.rfb.clipViewport = true;
} else {
@ -1372,6 +1386,7 @@ const UI = {
UI.rfb.dragViewport = !UI.rfb.dragViewport;
UI.updateViewDrag();
UI.updateTouchpadMode();
},
updateViewDrag() {
@ -1379,6 +1394,10 @@ const UI = {
const viewDragButton = document.getElementById('noVNC_view_drag_button');
if (UI.rfb.dragViewport) {
UI.rfb.touchpadMode = false;
}
if ((!UI.rfb.clipViewport || !UI.rfb.clippingViewport) &&
UI.rfb.dragViewport) {
// We are no longer clipping the viewport. Make sure
@ -1432,7 +1451,7 @@ const UI = {
* ------v------*/
showVirtualKeyboard() {
if (!isTouchDevice) return;
if (!isTouchDevice()) return;
const input = document.getElementById('noVNC_keyboardinput');
@ -1450,7 +1469,7 @@ const UI = {
},
hideVirtualKeyboard() {
if (!isTouchDevice) return;
if (!isTouchDevice()) return;
const input = document.getElementById('noVNC_keyboardinput');
@ -1599,12 +1618,33 @@ const UI = {
if (!UI.rfb) return;
UI.rfb.touchpadMode = !UI.rfb.touchpadMode;
UI.updateTouchpadButton();
WebUtil.writeSetting('touchpad_mode', UI.rfb.touchpadMode);
UI.updateTouchpadMode();
UI.updateViewDrag();
},
updateTouchpadButton() {
updateTouchpadMode() {
if (UI.rfb.touchpadMode) {
UI.rfb.dragViewport = false;
UI.forceSetting('resize', 'off');
UI.forceSetting('view_clip', true);
UI.forceSetting('show_dot', true);
UI.rfb.clipViewport = true;
UI.rfb.scaleViewport = false;
UI.rfb.resizeSession = false;
UI.rfb.showDotCursor = true;
}
else {
UI.enableSetting('resize');
UI.enableSetting('view_clip');
UI.enableSetting('show_dot');
}
UI.updateViewDrag
const touchpadButton = document.getElementById('noVNC_touchpad_button');
if (UI.rfb.touchpadMode) {
touchpadButton.classList.add("noVNC_selected");
} else {
@ -1730,12 +1770,14 @@ const UI = {
.classList.add('noVNC_hidden');
document.getElementById('noVNC_clipboard_button')
.classList.add('noVNC_hidden');
document.getElementById('noVNC_clipboard_button')
.classList.add('noVNC_hidden');
} else {
document.getElementById('noVNC_keyboard_button')
.classList.remove('noVNC_hidden');
document.getElementById('noVNC_toggle_extra_keys_button')
.classList.remove('noVNC_hidden');
document.getElementById('noVNC_clipboard_button')
document.getElementById('noVNC_touchpad_button')
.classList.remove('noVNC_hidden');
}
},

View File

@ -87,8 +87,38 @@ export default class Display {
return this._fbHeight;
}
get viewportLocation() {
return this._viewportLoc;
}
// ===== PUBLIC METHODS =====
/**
* Attempt to move the viewport by the specified amounts
* and returns the amount of actual position change.
* @param {number} moveByX
* @param {number} moveByY
* @return {{ x: number, y: number }}
*/
viewportTryMoveBy(moveByX, moveByY) {
if (moveByX === 0 && moveByY === 0) {
return {
x: 0,
y: 0
}
}
const vpX = this._viewportLoc.x;
const vpY = this._viewportLoc.y;
this.viewportChangePos(moveByX, moveByY);
return {
x: this._viewportLoc.x - vpX,
y: this._viewportLoc.y - vpY
}
}
viewportChangePos(deltaX, deltaY) {
const vp = this._viewportLoc;
deltaX = Math.floor(deltaX);
@ -433,6 +463,10 @@ export default class Display {
this._rescale(scaleRatio);
}
rescale(factor) {
this._rescale(factor);
}
// ===== PRIVATE METHODS =====
_rescale(factor) {

View File

@ -18,7 +18,6 @@ const GH_PINCH = 64;
const GH_INITSTATE = 127;
const GH_MOVE_THRESHOLD = 50;
const GH_ANGLE_THRESHOLD = 90; // Degrees
// Timeout when waiting for gestures (ms)
@ -38,6 +37,7 @@ export default class GestureHandler {
this._target = null;
this._state = GH_INITSTATE;
this._touchpadMode = false;
this._tracked = [];
this._ignored = [];
@ -51,6 +51,37 @@ export default class GestureHandler {
this._boundEventHandler = this._eventHandler.bind(this);
}
// ===== PROPERTIES =====
/**
* @returns {boolean}
*/
get touchpadMode() {
return this._touchpadMode;
}
/**
* @param {boolean} enabled
*/
set touchpadMode(enabled) {
this._touchpadMode = enabled;
}
/**
* @returns {number}
*/
get _ghMoveThreshold() {
// In TouchpadMode, we want movements to be very precise,
// so we'll reduce the movement threshold.
if (this._touchpadMode) {
return 5;
}
return 50;
}
// ===== PUBLIC METHODS =====
attach(target) {
this.detach();
@ -64,7 +95,6 @@ export default class GestureHandler {
this._target.addEventListener('touchcancel',
this._boundEventHandler);
}
detach() {
if (!this._target) {
return;
@ -84,6 +114,10 @@ export default class GestureHandler {
this._target = null;
}
/**
*
* @param {TouchEvent} e
*/
_eventHandler(e) {
let fn;
@ -102,7 +136,6 @@ export default class GestureHandler {
fn = this._touchEnd;
break;
}
for (let i = 0; i < e.changedTouches.length; i++) {
let touch = e.changedTouches[i];
fn.call(this, touch.identifier, touch.clientX, touch.clientY);
@ -142,9 +175,11 @@ export default class GestureHandler {
firstY: y,
lastX: x,
lastY: y,
angle: 0
movementX: 0,
movementY: 0,
angle: 0,
});
switch (this._tracked.length) {
case 1:
this._startLongpressTimeout();
@ -164,7 +199,7 @@ export default class GestureHandler {
}
}
_touchMove(id, x, y) {
_touchMove(id, x, y) {
let touch = this._tracked.find(t => t.id === id);
// If this is an update for a touch we're not tracking, ignore it
@ -173,6 +208,8 @@ export default class GestureHandler {
}
// Update the touches last position with the event coordinates
touch.movementX = x - touch.lastX;
touch.movementY = y - touch.lastY;
touch.lastX = x;
touch.lastY = y;
@ -187,7 +224,7 @@ export default class GestureHandler {
if (!this._hasDetectedGesture()) {
// Ignore moves smaller than the minimum threshold
if (Math.hypot(deltaX, deltaY) < GH_MOVE_THRESHOLD) {
if (Math.hypot(deltaX, deltaY) < this._ghMoveThreshold) {
return;
}
@ -216,7 +253,7 @@ export default class GestureHandler {
// We know that the current touch moved far enough,
// but unless both touches moved further than their
// threshold we don't want to disqualify any gestures
if (prevDeltaMove > GH_MOVE_THRESHOLD) {
if (prevDeltaMove > this._ghMoveThreshold) {
// The angle difference between the direction of the touch points
let deltaAngle = Math.abs(touch.angle - prevTouch.angle);
@ -458,6 +495,15 @@ export default class GestureHandler {
detail['clientX'] = pos.x;
detail['clientY'] = pos.y;
if (this._touchpadMode &&
this._tracked.length === 1) {
const touch = this._tracked[0];
detail['movementX'] = touch.movementX;
detail['movementY'] = touch.movementY;
}
// FIXME: other coordinates?
// Some gestures also have a magnitude

View File

@ -188,12 +188,16 @@ export default class RFB extends EventTargetMixin {
this._viewportHasMoved = false;
this._accumulatedWheelDeltaX = 0;
this._accumulatedWheelDeltaY = 0;
// Gesture state
this._gestureLastTapTime = null;
this._gestureFirstDoubleTapEv = null;
this._gestureLastMagnitudeX = 0;
this._gestureLastMagnitudeY = 0;
this._isTouchpadDragging = false;
this._touchpadTapTimeoutId = null;
this._lastTouchpadPinchMagnitude = 0;
this._currentPinchScale = 1;
// Bound event handlers
this._eventHandlers = {
@ -290,7 +294,7 @@ export default class RFB extends EventTargetMixin {
this._clippingViewport = false;
this._scaleViewport = false;
this._resizeSession = false;
this.touchpadMode = false;
this._touchpadMode = false;
this._showDotCursor = false;
if (options.showDotCursor !== undefined) {
@ -409,6 +413,24 @@ export default class RFB extends EventTargetMixin {
this._sendEncodings();
}
}
/**
* @returns {boolean}
*/
get touchpadMode() {
return this._touchpadMode;
}
/**
* @param {boolean} enabled
*/
set touchpadMode(enabled) {
if (!this._gestures) {
return;
}
this._touchpadMode = enabled;
this._gestures.touchpadMode = enabled;
}
// ===== PUBLIC METHODS =====
@ -530,6 +552,17 @@ export default class RFB extends EventTargetMixin {
}
}
centerCursorInViewport() {
const container = document.getElementById('noVNC_container');
const containerBounds = container.getBoundingClientRect();
const x = containerBounds.left + (containerBounds.width * .5);
const y = containerBounds.top + (containerBounds.height * .5)
this._cursor.move(x, y);
const elementPos = clientToElement(x, y, this._canvas);
this._handleMouseMove(elementPos.x, elementPos.y);
}
getImageData() {
return this._display.getImageData();
}
@ -1264,21 +1297,57 @@ export default class RFB extends EventTargetMixin {
case 'gesturestart':
switch (ev.detail.type) {
case 'onetap':
this._handleTapEvent(ev, 0x1);
if (this._touchpadMode) {
this._handleTouchpadOneTapEvent();
}
else {
this._handleTapEvent(ev, 0x1);
}
break;
case 'twotap':
if (this._touchpadMode) {
this._sendTouchpadTwoTap();
break;
}
this._handleTapEvent(ev, 0x4);
break;
case 'threetap':
if (this._touchpadMode) {
this._sendTouchpadThreeTap();
break;
}
this._handleTapEvent(ev, 0x2);
break;
case 'drag':
// In TouchpadMode, we don't want to move the cursor
// at the start of dragging. It should remain at its
// current location. We'll only press the left mouse
// button if this is the second tap in a double-tap
// sequence.
if (this._touchpadMode) {
if (this._touchpadTapTimeoutId > 0) {
this._clearTouchpadTapTimeoutId();
this._isTouchpadDragging = true;
this._mouseButtonMask = 0x1;
const cursorPos = this._getCursorPositionToCanvas();
this._sendMouse(cursorPos.x, cursorPos.y, 0x1);
}
break;
}
this._fakeMouseMove(ev, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, true, 0x1);
break;
case 'longpress':
this._fakeMouseMove(ev, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, true, 0x4);
// In TouchpadMode, we want to start the right-click at the
// current cursor location.
if (this._touchpadMode) {
const cursorPos = this._getCursorPositionToCanvas();
this._handleMouseButton(cursorPos.x, cursorPos.y, true, 0x4);
}
else {
this._fakeMouseMove(ev, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, true, 0x4);
}
break;
case 'twodrag':
@ -1287,8 +1356,15 @@ export default class RFB extends EventTargetMixin {
this._fakeMouseMove(ev, pos.x, pos.y);
break;
case 'pinch':
this._gestureLastMagnitudeX = Math.hypot(ev.detail.magnitudeX,
ev.detail.magnitudeY);
magnitude = Math.hypot(
ev.detail.magnitudeX,
ev.detail.magnitudeY);
if (this._touchpadMode) {
this._lastTouchpadPinchMagnitude = magnitude;
break;
}
this._gestureLastMagnitudeX = magnitude;
this._fakeMouseMove(ev, pos.x, pos.y);
break;
}
@ -1302,13 +1378,22 @@ export default class RFB extends EventTargetMixin {
break;
case 'drag':
case 'longpress':
this._fakeMouseMove(ev, pos.x, pos.y);
// In TouchpadMode, we want to move the cursor from its
// current position, not to where the touch currently is.
if (this._touchpadMode) {
this._handleTouchpadMove(ev.detail.movementX, ev.detail.movementY);
}
else {
this._fakeMouseMove(ev, pos.x, pos.y);
}
break;
case 'twodrag':
// Always scroll in the same position.
// We don't know if the mouse was moved so we need to move it
// every update.
this._fakeMouseMove(ev, pos.x, pos.y);
if (!this._touchpadMode) {
this._fakeMouseMove(ev, pos.x, pos.y);
}
while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) {
this._handleMouseButton(pos.x, pos.y, true, 0x8);
this._handleMouseButton(pos.x, pos.y, false, 0x8);
@ -1331,11 +1416,18 @@ export default class RFB extends EventTargetMixin {
}
break;
case 'pinch':
magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY);
if (this._touchpadMode) {
this._handleTouchpadPinchZoom(magnitude);
break;
}
// Always scroll in the same position.
// We don't know if the mouse was moved so we need to move it
// every update.
this._fakeMouseMove(ev, pos.x, pos.y);
magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY);
if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
@ -1363,18 +1455,204 @@ export default class RFB extends EventTargetMixin {
case 'twodrag':
break;
case 'drag':
if (this._touchpadMode) {
if (this._isTouchpadDragging) {
this._mouseButtonMask = 0;
const cursorPos = this._getCursorPositionToCanvas();
this._sendMouse(cursorPos.x, cursorPos.y, 0);
this._isTouchpadDragging = false;
}
break;
}
this._fakeMouseMove(ev, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, false, 0x1);
break;
case 'longpress':
this._fakeMouseMove(ev, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, false, 0x4);
// In TouchPad mode, we want to finish at the current cursor location.
if (this._touchpadMode) {
const cursorPos = this._getCursorPositionToCanvas();
this._handleMouseButton(cursorPos.x, cursorPos.y, false, 0x4);
}
else {
this._fakeMouseMove(ev, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, false, 0x4);
}
break;
}
break;
}
}
// TouchpadMode Private Methods
/**
* @param {number} movementX
* @param {number} movementY
*/
_handleTouchpadMove(movementX, movementY) {
// Add a multiplier to higher-velocity movements to
// traverse the screen quicker.
const xMultiplier = Math.max(5, Math.abs(movementX)) / 5;
movementX *= Math.min(xMultiplier, 4);
const yMultiplier = Math.max(5, Math.abs(movementY)) / 5;
movementY *= Math.min(yMultiplier, 4);
// Get the desired new location for the cursor.
let cursorPos = this._cursor.position;
let targetX = cursorPos.x + movementX;
let targetY = cursorPos.y + movementY;
// Constrain the location to the canvas bounds.
const canvasBounds = this._canvas.getBoundingClientRect();
const safeX = Math.max(canvasBounds.left, Math.min(targetX, canvasBounds.right));
const safeY = Math.max(canvasBounds.top, Math.min(targetY, canvasBounds.bottom));
// See if the cursor has moved outside the center deadzone.
const deadzone = this._getTouchpadCursorDeadZone();
const moveViewportX =
Math.min(safeX - deadzone.left, 0) +
Math.max(safeX - deadzone.right, 0);
const moveViewportY =
Math.min(safeY - deadzone.top, 0) +
Math.max(safeY - deadzone.bottom, 0);
// Try moving the viewport, getting the actual amount it moved.
const viewportChange = this._display.viewportTryMoveBy(moveViewportX, moveViewportY);
// Subtract the viewport position change from the target
// cursor position. This will cause it to stay at the
// edge of the deadzone if we're pushing against it, or
// move past it to the edge of the screen if the viewport
// can pan no further.
this._cursor.move(safeX - viewportChange.x, safeY - viewportChange.y);
// Finally, translate the coordinates to those relative to the
// canvas and send the pointer move event to the remote machine.
const posFromCanvas = clientToElement(safeX, safeY, this._canvas);
this._sendMouse(posFromCanvas.x, posFromCanvas.y, this._mouseButtonMask);
}
_handleTouchpadOneTapEvent() {
if (this._touchpadTapTimeoutId > 0) {
// A double-tap occurred.
this._clearTouchpadTapTimeoutId();
this._sendTouchpadTap();
this._sendTouchpadTap();
return;
}
this._touchpadTapTimeoutId = window.setTimeout(() => {
this._clearTouchpadTapTimeoutId();
this._sendTouchpadTap();
}, 250);
}
/**
*
* @param {number} magnitude
*/
_handleTouchpadPinchZoom(magnitude) {
if (this._lastTouchpadPinchMagnitude > 0) {
// Calculate the new pinch scale.
const container = document.getElementById('noVNC_container');
const magnitudeChange = this._lastTouchpadPinchMagnitude / magnitude;
const newScale = this._currentPinchScale * magnitudeChange;
this._currentPinchScale = Math.max(.25, Math.min(4, newScale));
// Capture the current viewport size.
const originalVpW = this._display.viewportLocation.w;
const originalVpH = this._display.viewportLocation.h;
// Change viewport size based on new scale.
const newWidth = container.clientWidth * this._currentPinchScale;
const newHeight = container.clientHeight * this._currentPinchScale;
this._display.viewportChangeSize(newWidth, newHeight);
// Apply scaling to CSS.
const visualScale = container.clientWidth / newWidth;
this._display.rescale(visualScale);
// Adjust viewport location to keep it centered.
const moveX = (originalVpW - this._display.viewportLocation.w) / 2;
const moveY = (originalVpH - this._display.viewportLocation.h) / 2;
this._display.viewportChangePos(moveX, moveY);
}
this._lastTouchpadPinchMagnitude = magnitude;
}
_clearTouchpadTapTimeoutId() {
window.clearTimeout(this._touchpadTapTimeoutId);
this._touchpadTapTimeoutId = 0;
}
_sendTouchpadTap() {
const cursorPos = this._getCursorPositionToCanvas();
this._sendMouse(cursorPos.x, cursorPos.y, 0x1);
this._sendMouse(cursorPos.x, cursorPos.y, 0);
this._mouseButtonMask = 0;
}
_sendTouchpadTwoTap() {
this._clearTouchpadTapTimeoutId();
const cursorPos = this._getCursorPositionToCanvas();
this._sendMouse(cursorPos.x, cursorPos.y, 0x4);
this._sendMouse(cursorPos.x, cursorPos.y, 0);
this._mouseButtonMask = 0;
}
_sendTouchpadThreeTap() {
this._clearTouchpadTapTimeoutId();
const cursorPos = this._getCursorPositionToCanvas();
this._sendMouse(cursorPos.x, cursorPos.y, 0x2);
this._sendMouse(cursorPos.x, cursorPos.y, 0);
this._mouseButtonMask = 0;
}
/**
* Gets the current cursor position, offset by the canvas client bounds.
* @returns {{x: number, y: number}}
*/
_getCursorPositionToCanvas() {
const cursorPos = this._cursor.position;
return clientToElement(cursorPos.x, cursorPos.y, this._canvas);
}
/**
* Returns the center area within the canvas bounds where
* cursor movement won't trigger viewport movement.
* @returns {{
* top: number,
* bottom: number,
* left: number,
* right: number,
* width: number,
* height: number
* }}
*/
_getTouchpadCursorDeadZone() {
const canvasBounds = this._canvas.getBoundingClientRect();
const canvasCenter = {
x: canvasBounds.width * .5,
y: canvasBounds.height * .5
}
const xFromCenter = canvasBounds.width * .1;
const yFromCenter = canvasBounds.height * .1;
const innerWidth = xFromCenter * 2;
const innerHeight = yFromCenter * 2;
return {
top: canvasCenter.y - yFromCenter,
bottom: canvasCenter.y + yFromCenter,
height: innerHeight,
left: canvasCenter.x - xFromCenter,
right: canvasCenter.x + xFromCenter,
width: innerWidth
}
}
// Message Handlers
_negotiateProtocolVersion() {

View File

@ -11,17 +11,26 @@
import * as Log from './logging.js';
// Touch detection
export let isTouchDevice = ('ontouchstart' in document.documentElement) ||
// requried for Chrome debugger
(document.ontouchstart !== undefined) ||
// required for MS Surface
(navigator.maxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0);
let _touchEventOccurred = false;
window.addEventListener('touchstart', function onFirstTouch() {
isTouchDevice = true;
_touchEventOccurred = true;
window.removeEventListener('touchstart', onFirstTouch, false);
}, false);
// This needs to be a function to allow the exported value
// to update if touchstart event fires. Also, the other
// values are dynamic and can change without a page reload
// (e.g. opening the emulator in dev tools), so we don't want
// to assign them to a variable that captures their current value.
export function isTouchDevice() {
return _touchEventOccurred ||
('ontouchstart' in document.documentElement) ||
// requried for Chrome debugger
(document.ontouchstart !== undefined) ||
// required for MS Surface
(navigator.maxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0);
};
// The goal is to find a certain physical width, the devicePixelRatio
// brings us a bit closer but is not optimal.

View File

@ -6,7 +6,7 @@
import { supportsCursorURIs, isTouchDevice } from './browser.js';
const useFallback = !supportsCursorURIs || isTouchDevice;
const useFallback = () => !supportsCursorURIs || isTouchDevice();
export default class Cursor {
constructor() {
@ -14,7 +14,7 @@ export default class Cursor {
this._canvas = document.createElement('canvas');
if (useFallback) {
if (useFallback()) {
this._canvas.style.position = 'fixed';
this._canvas.style.zIndex = '65535';
this._canvas.style.pointerEvents = 'none';
@ -37,6 +37,13 @@ export default class Cursor {
};
}
/**
* @returns {{ x: number, y: number }}
*/
get position() {
return this._position;
}
attach(target) {
if (this._target) {
this.detach();
@ -44,7 +51,7 @@ export default class Cursor {
this._target = target;
if (useFallback) {
if (useFallback()) {
document.body.appendChild(this._canvas);
const options = { capture: true, passive: true };
@ -62,7 +69,7 @@ export default class Cursor {
return;
}
if (useFallback) {
if (useFallback()) {
const options = { capture: true, passive: true };
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
@ -95,7 +102,7 @@ export default class Cursor {
ctx.clearRect(0, 0, w, h);
ctx.putImageData(img, 0, 0);
if (useFallback) {
if (useFallback()) {
this._updatePosition();
} else {
let url = this._canvas.toDataURL();
@ -116,7 +123,7 @@ export default class Cursor {
// Mouse events might be emulated, this allows
// moving the cursor in such cases
move(clientX, clientY) {
if (!useFallback) {
if (!useFallback()) {
return;
}
// clientX/clientY are relative the _visual viewport_,