summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/bg/data/options-schema.json5
-rw-r--r--ext/bg/js/options.js2
-rw-r--r--ext/bg/settings.html4
-rw-r--r--ext/fg/js/frontend.js1
-rw-r--r--ext/mixed/js/display.js1
-rw-r--r--ext/mixed/js/text-scanner.js234
6 files changed, 241 insertions, 6 deletions
diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json
index 9cc8837d..240305b5 100644
--- a/ext/bg/data/options-schema.json
+++ b/ext/bg/data/options-schema.json
@@ -320,6 +320,7 @@
"required": [
"inputs",
"touchInputEnabled",
+ "pointerEventsEnabled",
"selectText",
"alphanumeric",
"autoHideResults",
@@ -430,6 +431,10 @@
"type": "boolean",
"default": true
},
+ "pointerEventsEnabled": {
+ "type": "boolean",
+ "default": false
+ },
"selectText": {
"type": "boolean",
"default": true
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js
index 3470da58..89538b3e 100644
--- a/ext/bg/js/options.js
+++ b/ext/bg/js/options.js
@@ -505,6 +505,7 @@ class OptionsUtil {
// Updated handlebars templates to include "clipboard-image" definition.
// Added hideDelay.
// Added inputs to profileOptions.scanning.
+ // Added pointerEventsEnabled to profileOptions.scanning.
for (const {conditionGroups} of options.profiles) {
for (const {conditions} of conditionGroups) {
for (const condition of conditions) {
@@ -526,6 +527,7 @@ class OptionsUtil {
for (const {options: profileOptions} of options.profiles) {
profileOptions.general.usePopupWindow = false;
profileOptions.scanning.hideDelay = 0;
+ profileOptions.scanning.pointerEventsEnabled = false;
const {modifier, middleMouse, touchInputEnabled} = profileOptions.scanning;
const scanningInputs = [];
diff --git a/ext/bg/settings.html b/ext/bg/settings.html
index cc6f93e0..cc209c8a 100644
--- a/ext/bg/settings.html
+++ b/ext/bg/settings.html
@@ -407,6 +407,10 @@
</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">
<label><input type="checkbox" id="deep-dom-scan" data-setting="scanning.deepDomScan"> Deep content scan</label>
</div>
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 6b0b50a6..da620136 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -326,6 +326,7 @@ class Frontend {
selectText: scanningOptions.selectText,
delay: scanningOptions.delay,
touchInputEnabled: scanningOptions.touchInputEnabled,
+ pointerEventsEnabled: scanningOptions.pointerEventsEnabled,
scanLength: scanningOptions.length,
sentenceExtent: options.anki.sentenceExt,
layoutAwareScan: scanningOptions.layoutAwareScan
diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js
index 92e27faa..ef56f4aa 100644
--- a/ext/mixed/js/display.js
+++ b/ext/mixed/js/display.js
@@ -252,6 +252,7 @@ class Display extends EventDispatcher {
selectText: scanning.selectText,
delay: scanning.delay,
touchInputEnabled: scanning.touchInputEnabled,
+ pointerEventsEnabled: scanning.pointerEventsEnabled,
scanLength: scanning.length,
sentenceExtent: options.anki.sentenceExt,
layoutAwareScan: scanning.layoutAwareScan
diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js
index d2383af4..c2016807 100644
--- a/ext/mixed/js/text-scanner.js
+++ b/ext/mixed/js/text-scanner.js
@@ -45,6 +45,7 @@ class TextScanner extends EventDispatcher {
this._selectText = false;
this._delay = 0;
this._touchInputEnabled = false;
+ this._pointerEventsEnabled = false;
this._scanLength = 1;
this._sentenceExtent = 1;
this._layoutAwareScan = false;
@@ -59,6 +60,8 @@ class TextScanner extends EventDispatcher {
this._preventNextMouseDown = false;
this._preventNextClick = false;
this._preventScroll = false;
+ this._penPointerPressed = false;
+ this._penPointerReleased = false;
this._canClearSelection = true;
}
@@ -96,6 +99,8 @@ class TextScanner extends EventDispatcher {
this._preventNextMouseDown = false;
this._preventNextClick = false;
this._preventScroll = false;
+ this._penPointerPressed = false;
+ this._penPointerReleased = false;
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)) {
- this._inputs = inputs.map(({include, exclude, types}) => ({
+ this._inputs = inputs.map(({
+ include,
+ exclude,
+ types,
+ options: {scanOnPenHover, scanOnPenPress, scanOnPenRelease}
+ }) => ({
include: this._getInputArray(include),
exclude: this._getInputArray(exclude),
- types: this._getInputTypeSet(types)
+ types: this._getInputTypeSet(types),
+ options: {scanOnPenHover, scanOnPenPress, scanOnPenRelease}
}));
}
if (typeof deepContentScan === 'boolean') {
@@ -126,6 +137,9 @@ class TextScanner extends EventDispatcher {
if (typeof touchInputEnabled === 'boolean') {
this._touchInputEnabled = touchInputEnabled;
}
+ if (typeof pointerEventsEnabled === 'boolean') {
+ this._pointerEventsEnabled = pointerEventsEnabled;
+ }
if (typeof scanLength === 'number') {
this._scanLength = scanLength;
}
@@ -374,6 +388,162 @@ class TextScanner extends EventDispatcher {
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() {
const delay = this._delay;
const promise = promiseTimeout(delay, true);
@@ -394,10 +564,19 @@ class TextScanner extends EventDispatcher {
}
}
+ _arePointerEventsSupported() {
+ return (this._pointerEventsEnabled && typeof PointerEvent !== 'undefined');
+ }
+
_hookEvents() {
- const eventListenerInfos = this._getMouseEventListeners();
- if (this._touchInputEnabled) {
- eventListenerInfos.push(...this._getTouchEventListeners());
+ let eventListenerInfos;
+ if (this._arePointerEventsSupported()) {
+ eventListenerInfos = this._getPointerEventListeners();
+ } else {
+ eventListenerInfos = this._getMouseEventListeners();
+ if (this._touchInputEnabled) {
+ eventListenerInfos.push(...this._getTouchEventListeners());
+ }
}
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() {
return [
[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) {
const modifiers = DocumentUtil.getActiveModifiersAndButtons(event);
this.trigger('activeModifiersChanged', {modifiers});