Pointer events support (#819)

* Add option pointerEventsEnabled

* Add _pointerEventsEnabled option to TextScanner

* Add additional options

* Mouse pointer events

* Touch pointer events

* Pen pointer events
This commit is contained in:
toasted-nutbread 2020-09-13 11:33:10 -04:00 committed by GitHub
parent efd0de6bc0
commit 5b49cf4398
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 241 additions and 6 deletions

View File

@ -320,6 +320,7 @@
"required": [ "required": [
"inputs", "inputs",
"touchInputEnabled", "touchInputEnabled",
"pointerEventsEnabled",
"selectText", "selectText",
"alphanumeric", "alphanumeric",
"autoHideResults", "autoHideResults",
@ -430,6 +431,10 @@
"type": "boolean", "type": "boolean",
"default": true "default": true
}, },
"pointerEventsEnabled": {
"type": "boolean",
"default": false
},
"selectText": { "selectText": {
"type": "boolean", "type": "boolean",
"default": true "default": true

View File

@ -505,6 +505,7 @@ class OptionsUtil {
// Updated handlebars templates to include "clipboard-image" definition. // Updated handlebars templates to include "clipboard-image" definition.
// Added hideDelay. // Added hideDelay.
// Added inputs to profileOptions.scanning. // Added inputs to profileOptions.scanning.
// Added pointerEventsEnabled to profileOptions.scanning.
for (const {conditionGroups} of options.profiles) { for (const {conditionGroups} of options.profiles) {
for (const {conditions} of conditionGroups) { for (const {conditions} of conditionGroups) {
for (const condition of conditions) { for (const condition of conditions) {
@ -526,6 +527,7 @@ class OptionsUtil {
for (const {options: profileOptions} of options.profiles) { for (const {options: profileOptions} of options.profiles) {
profileOptions.general.usePopupWindow = false; profileOptions.general.usePopupWindow = false;
profileOptions.scanning.hideDelay = 0; profileOptions.scanning.hideDelay = 0;
profileOptions.scanning.pointerEventsEnabled = false;
const {modifier, middleMouse, touchInputEnabled} = profileOptions.scanning; const {modifier, middleMouse, touchInputEnabled} = profileOptions.scanning;
const scanningInputs = []; const scanningInputs = [];

View File

@ -406,6 +406,10 @@
<label><input type="checkbox" id="touch-input-enabled" data-setting="scanning.touchInputEnabled"> Touch input enabled</label> <label><input type="checkbox" id="touch-input-enabled" data-setting="scanning.touchInputEnabled"> Touch input enabled</label>
</div> </div>
<div class="checkbox options-advanced">
<label><input type="checkbox" data-setting="scanning.pointerEventsEnabled"> Pointer events input enabled</label>
</div>
<div class="checkbox options-advanced"> <div class="checkbox options-advanced">
<label><input type="checkbox" id="deep-dom-scan" data-setting="scanning.deepDomScan"> Deep content scan</label> <label><input type="checkbox" id="deep-dom-scan" data-setting="scanning.deepDomScan"> Deep content scan</label>
</div> </div>

View File

@ -326,6 +326,7 @@ class Frontend {
selectText: scanningOptions.selectText, selectText: scanningOptions.selectText,
delay: scanningOptions.delay, delay: scanningOptions.delay,
touchInputEnabled: scanningOptions.touchInputEnabled, touchInputEnabled: scanningOptions.touchInputEnabled,
pointerEventsEnabled: scanningOptions.pointerEventsEnabled,
scanLength: scanningOptions.length, scanLength: scanningOptions.length,
sentenceExtent: options.anki.sentenceExt, sentenceExtent: options.anki.sentenceExt,
layoutAwareScan: scanningOptions.layoutAwareScan layoutAwareScan: scanningOptions.layoutAwareScan

View File

@ -252,6 +252,7 @@ class Display extends EventDispatcher {
selectText: scanning.selectText, selectText: scanning.selectText,
delay: scanning.delay, delay: scanning.delay,
touchInputEnabled: scanning.touchInputEnabled, touchInputEnabled: scanning.touchInputEnabled,
pointerEventsEnabled: scanning.pointerEventsEnabled,
scanLength: scanning.length, scanLength: scanning.length,
sentenceExtent: options.anki.sentenceExt, sentenceExtent: options.anki.sentenceExt,
layoutAwareScan: scanning.layoutAwareScan layoutAwareScan: scanning.layoutAwareScan

View File

@ -45,6 +45,7 @@ class TextScanner extends EventDispatcher {
this._selectText = false; this._selectText = false;
this._delay = 0; this._delay = 0;
this._touchInputEnabled = false; this._touchInputEnabled = false;
this._pointerEventsEnabled = false;
this._scanLength = 1; this._scanLength = 1;
this._sentenceExtent = 1; this._sentenceExtent = 1;
this._layoutAwareScan = false; this._layoutAwareScan = false;
@ -59,6 +60,8 @@ class TextScanner extends EventDispatcher {
this._preventNextMouseDown = false; this._preventNextMouseDown = false;
this._preventNextClick = false; this._preventNextClick = false;
this._preventScroll = false; this._preventScroll = false;
this._penPointerPressed = false;
this._penPointerReleased = false;
this._canClearSelection = true; this._canClearSelection = true;
} }
@ -96,6 +99,8 @@ class TextScanner extends EventDispatcher {
this._preventNextMouseDown = false; this._preventNextMouseDown = false;
this._preventNextClick = false; this._preventNextClick = false;
this._preventScroll = false; this._preventScroll = false;
this._penPointerPressed = false;
this._penPointerReleased = false;
this._enabledValue = value; this._enabledValue = value;
@ -106,12 +111,18 @@ class TextScanner extends EventDispatcher {
} }
} }
setOptions({inputs, deepContentScan, selectText, delay, touchInputEnabled, scanLength, sentenceExtent, layoutAwareScan}) { setOptions({inputs, deepContentScan, selectText, delay, touchInputEnabled, pointerEventsEnabled, scanLength, sentenceExtent, layoutAwareScan}) {
if (Array.isArray(inputs)) { if (Array.isArray(inputs)) {
this._inputs = inputs.map(({include, exclude, types}) => ({ this._inputs = inputs.map(({
include,
exclude,
types,
options: {scanOnPenHover, scanOnPenPress, scanOnPenRelease}
}) => ({
include: this._getInputArray(include), include: this._getInputArray(include),
exclude: this._getInputArray(exclude), exclude: this._getInputArray(exclude),
types: this._getInputTypeSet(types) types: this._getInputTypeSet(types),
options: {scanOnPenHover, scanOnPenPress, scanOnPenRelease}
})); }));
} }
if (typeof deepContentScan === 'boolean') { if (typeof deepContentScan === 'boolean') {
@ -126,6 +137,9 @@ class TextScanner extends EventDispatcher {
if (typeof touchInputEnabled === 'boolean') { if (typeof touchInputEnabled === 'boolean') {
this._touchInputEnabled = touchInputEnabled; this._touchInputEnabled = touchInputEnabled;
} }
if (typeof pointerEventsEnabled === 'boolean') {
this._pointerEventsEnabled = pointerEventsEnabled;
}
if (typeof scanLength === 'number') { if (typeof scanLength === 'number') {
this._scanLength = scanLength; this._scanLength = scanLength;
} }
@ -374,6 +388,162 @@ class TextScanner extends EventDispatcher {
e.preventDefault(); // Disable scroll e.preventDefault(); // Disable scroll
} }
_onPointerOver(e) {
if (!e.isPrimary) { return; }
switch (e.pointerType) {
case 'mouse': return this._onMousePointerOver(e);
case 'touch': return this._onTouchPointerOver(e);
case 'pen': return this._onPenPointerOver(e);
}
}
_onPointerDown(e) {
if (!e.isPrimary) { return; }
switch (e.pointerType) {
case 'mouse': return this._onMousePointerDown(e);
case 'touch': return this._onTouchPointerDown(e);
case 'pen': return this._onPenPointerDown(e);
}
}
_onPointerMove(e) {
if (!e.isPrimary) { return; }
switch (e.pointerType) {
case 'mouse': return this._onMousePointerMove(e);
case 'touch': return this._onTouchPointerMove(e);
case 'pen': return this._onPenPointerMove(e);
}
}
_onPointerUp(e) {
if (!e.isPrimary) { return; }
switch (e.pointerType) {
case 'mouse': return this._onMousePointerUp(e);
case 'touch': return this._onTouchPointerUp(e);
case 'pen': return this._onPenPointerUp(e);
}
}
_onPointerCancel(e) {
if (!e.isPrimary) { return; }
switch (e.pointerType) {
case 'mouse': return this._onMousePointerCancel(e);
case 'touch': return this._onTouchPointerCancel(e);
case 'pen': return this._onPenPointerCancel(e);
}
}
_onPointerOut(e) {
if (!e.isPrimary) { return; }
switch (e.pointerType) {
case 'mouse': return this._onMousePointerOut(e);
case 'touch': return this._onTouchPointerOut(e);
case 'pen': return this._onPenPointerOut(e);
}
}
_onMousePointerOver(e) {
return this._onMouseOver(e);
}
_onMousePointerDown(e) {
return this._onMouseDown(e);
}
_onMousePointerMove(e) {
return this._onMouseMove(e);
}
_onMousePointerUp() {
// NOP
}
_onMousePointerCancel(e) {
return this._onMouseOut(e);
}
_onMousePointerOut(e) {
return this._onMouseOut(e);
}
_onTouchPointerOver() {
// NOP
}
_onTouchPointerDown(e) {
const {clientX, clientY, pointerId} = e;
return this._onPrimaryTouchStart(e, clientX, clientY, pointerId);
}
_onTouchPointerMove(e) {
if (!this._preventScroll || !e.cancelable) {
return;
}
const inputInfo = this._getMatchingInputGroupFromEvent(e, 'touch');
if (inputInfo === null) { return; }
const {index, empty} = inputInfo;
this._searchAt(e.clientX, e.clientY, {type: 'touch', cause: 'touchMove', index, empty});
}
_onTouchPointerUp() {
return this._onPrimaryTouchEnd();
}
_onTouchPointerCancel() {
return this._onPrimaryTouchEnd();
}
_onTouchPointerOut() {
// NOP
}
_onTouchMovePreventScroll(e) {
if (!this._preventScroll) { return; }
if (e.cancelable) {
e.preventDefault();
} else {
this._preventScroll = false;
}
}
_onPenPointerOver(e) {
this._penPointerPressed = false;
this._penPointerReleased = false;
this._searchAtFromPen(e, e.clientX, e.clientY, 'pointerOver', false);
}
_onPenPointerDown(e) {
this._penPointerPressed = true;
this._searchAtFromPen(e, e.clientX, e.clientY, 'pointerDown', true);
}
_onPenPointerMove(e) {
if (this._penPointerPressed && (!this._preventScroll || !e.cancelable)) { return; }
this._searchAtFromPen(e, e.clientX, e.clientY, 'pointerMove', true);
}
_onPenPointerUp() {
this._penPointerPressed = false;
this._penPointerReleased = true;
this._preventScroll = false;
}
_onPenPointerCancel(e) {
this._onPenPointerOut(e);
}
_onPenPointerOut() {
this._penPointerPressed = false;
this._penPointerReleased = false;
this._preventScroll = false;
this._preventNextContextMenu = false;
this._preventNextMouseDown = false;
this._preventNextClick = false;
}
async _scanTimerWait() { async _scanTimerWait() {
const delay = this._delay; const delay = this._delay;
const promise = promiseTimeout(delay, true); const promise = promiseTimeout(delay, true);
@ -394,10 +564,19 @@ class TextScanner extends EventDispatcher {
} }
} }
_arePointerEventsSupported() {
return (this._pointerEventsEnabled && typeof PointerEvent !== 'undefined');
}
_hookEvents() { _hookEvents() {
const eventListenerInfos = this._getMouseEventListeners(); let eventListenerInfos;
if (this._touchInputEnabled) { if (this._arePointerEventsSupported()) {
eventListenerInfos.push(...this._getTouchEventListeners()); eventListenerInfos = this._getPointerEventListeners();
} else {
eventListenerInfos = this._getMouseEventListeners();
if (this._touchInputEnabled) {
eventListenerInfos.push(...this._getTouchEventListeners());
}
} }
for (const [node, type, listener, options] of eventListenerInfos) { for (const [node, type, listener, options] of eventListenerInfos) {
@ -405,6 +584,21 @@ class TextScanner extends EventDispatcher {
} }
} }
_getPointerEventListeners() {
return [
[this._node, 'pointerover', this._onPointerOver.bind(this)],
[this._node, 'pointerdown', this._onPointerDown.bind(this)],
[this._node, 'pointermove', this._onPointerMove.bind(this)],
[this._node, 'pointerup', this._onPointerUp.bind(this)],
[this._node, 'pointercancel', this._onPointerCancel.bind(this)],
[this._node, 'pointerout', this._onPointerOut.bind(this)],
[this._node, 'touchmove', this._onTouchMovePreventScroll.bind(this), {passive: false}],
[this._node, 'mousedown', this._onMouseDown.bind(this)],
[this._node, 'click', this._onClick.bind(this)],
[this._node, 'auxclick', this._onAuxClick.bind(this)]
];
}
_getMouseEventListeners() { _getMouseEventListeners() {
return [ return [
[this._node, 'mousedown', this._onMouseDown.bind(this)], [this._node, 'mousedown', this._onMouseDown.bind(this)],
@ -543,6 +737,34 @@ class TextScanner extends EventDispatcher {
} }
} }
async _searchAtFromPen(e, x, y, cause, prevent) {
if (this._pendingLookup) { return; }
const type = 'pen';
const inputInfo = this._getMatchingInputGroupFromEvent(e, type);
if (inputInfo === null) { return; }
const {index, empty, input: {options}} = inputInfo;
if (
(!options.scanOnPenRelease && this._penPointerReleased) ||
!(this._penPointerPressed ? options.scanOnPenPress : options.scanOnPenHover)
) {
return;
}
await this._searchAt(x, y, {type, cause, index, empty});
if (
prevent &&
this._textSourceCurrent !== null
) {
this._preventScroll = true;
this._preventNextContextMenu = true;
this._preventNextMouseDown = true;
this._preventNextClick = true;
}
}
_getMatchingInputGroupFromEvent(event, type) { _getMatchingInputGroupFromEvent(event, type) {
const modifiers = DocumentUtil.getActiveModifiersAndButtons(event); const modifiers = DocumentUtil.getActiveModifiersAndButtons(event);
this.trigger('activeModifiersChanged', {modifiers}); this.trigger('activeModifiersChanged', {modifiers});