Hotkey dictionary navigation fix (#1970)

* Add getRect function to ScrollElement

* Update _focusEntry to take a definition index

* Update behaviour of (next|previous)EntryDifferentDictionary
This commit is contained in:
toasted-nutbread 2021-09-30 19:31:45 -04:00 committed by GitHub
parent b0f6c41f5d
commit 55a7e7f9a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 23 deletions

View File

@ -116,8 +116,8 @@ class Display extends EventDispatcher {
['close', () => { this._onHotkeyClose(); }], ['close', () => { this._onHotkeyClose(); }],
['nextEntry', this._onHotkeyActionMoveRelative.bind(this, 1)], ['nextEntry', this._onHotkeyActionMoveRelative.bind(this, 1)],
['previousEntry', this._onHotkeyActionMoveRelative.bind(this, -1)], ['previousEntry', this._onHotkeyActionMoveRelative.bind(this, -1)],
['lastEntry', () => { this._focusEntry(this._dictionaryEntries.length - 1, true); }], ['lastEntry', () => { this._focusEntry(this._dictionaryEntries.length - 1, 0, true); }],
['firstEntry', () => { this._focusEntry(0, true); }], ['firstEntry', () => { this._focusEntry(0, 0, true); }],
['historyBackward', () => { this._sourceTermView(); }], ['historyBackward', () => { this._sourceTermView(); }],
['historyForward', () => { this._nextTermView(); }], ['historyForward', () => { this._nextTermView(); }],
['playAudio', () => { this._playAudioCurrent(); }], ['playAudio', () => { this._playAudioCurrent(); }],
@ -756,7 +756,7 @@ class Display extends EventDispatcher {
_onWheel(e) { _onWheel(e) {
if (e.altKey) { if (e.altKey) {
if (e.deltaY !== 0) { if (e.deltaY !== 0) {
this._focusEntry(this._index + (e.deltaY > 0 ? 1 : -1), true); this._focusEntry(this._index + (e.deltaY > 0 ? 1 : -1), 0, true);
e.preventDefault(); e.preventDefault();
} }
} else if (e.shiftKey) { } else if (e.shiftKey) {
@ -997,7 +997,7 @@ class Display extends EventDispatcher {
this._displayAnki.setupEntry(entry, i); this._displayAnki.setupEntry(entry, i);
container.appendChild(entry); container.appendChild(entry);
if (focusEntry === i) { if (focusEntry === i) {
this._focusEntry(i, false); this._focusEntry(i, 0, false);
} }
this._elementOverflowController.addElements(entry); this._elementOverflowController.addElements(entry);
@ -1112,15 +1112,21 @@ class Display extends EventDispatcher {
} }
this._index = index; this._index = index;
return entry;
} }
_focusEntry(index, smooth) { _focusEntry(index, definitionIndex, smooth) {
index = Math.max(Math.min(index, this._dictionaryEntries.length - 1), 0); index = Math.max(Math.min(index, this._dictionaryEntries.length - 1), 0);
const entry = this._entrySetCurrent(index); this._entrySetCurrent(index);
let target = index === 0 || entry === null ? 0 : this._getElementTop(entry);
let node = (index >= 0 && index < this._dictionaryEntryNodes.length ? this._dictionaryEntryNodes[index] : null);
if (definitionIndex > 0) {
const definitionNodes = this._getDictionaryEntryDefinitionNodes(index);
if (definitionIndex < definitionNodes.length) {
node = definitionNodes[definitionIndex];
}
}
let target = (index === 0 && definitionIndex <= 0) || node === null ? 0 : this._getElementTop(node);
if (this._navigationHeader !== null) { if (this._navigationHeader !== null) {
target -= this._navigationHeader.getBoundingClientRect().height; target -= this._navigationHeader.getBoundingClientRect().height;
@ -1135,31 +1141,68 @@ class Display extends EventDispatcher {
} }
_focusEntryWithDifferentDictionary(offset, smooth) { _focusEntryWithDifferentDictionary(offset, smooth) {
const offsetSign = Math.sign(offset); const sign = Math.sign(offset);
if (offsetSign === 0) { return false; } if (sign === 0) { return false; }
let index = this._index; let index = this._index;
const dictionaryEntryCount = this._dictionaryEntries.length; const count = Math.min(this._dictionaryEntries.length, this._dictionaryEntryNodes.length);
if (index < 0 || index >= dictionaryEntryCount) { return false; } if (index < 0 || index >= count) { return false; }
const {dictionary} = this._dictionaryEntries[index]; const dictionaryEntry = this._dictionaryEntries[index];
for (let indexNext = index + offsetSign; indexNext >= 0 && indexNext < dictionaryEntryCount; indexNext += offsetSign) { const visibleDefinitionIndex = this._getDictionaryEntryVisibleDefinitionIndex(index, sign);
const {dictionaryNames} = this._dictionaryEntries[indexNext]; if (visibleDefinitionIndex === null) { return false; }
if (dictionaryNames.length > 1 || !dictionaryNames.includes(dictionary)) {
offset -= offsetSign; const {dictionary} = dictionaryEntry.definitions[visibleDefinitionIndex];
if (Math.sign(offsetSign) !== offset) { let focusDefinitionIndex = null;
index = indexNext; for (let i = index; i >= 0 && i < count; i += sign) {
const {definitions} = this._dictionaryEntries[i];
const jj = definitions.length;
let j = (i === index ? visibleDefinitionIndex + sign : (sign > 0 ? 0 : jj - 1));
for (; j >= 0 && j < jj; j += sign) {
if (definitions[j].dictionary !== dictionary) {
focusDefinitionIndex = j;
index = i;
i = -2; // Terminate outer loop
break; break;
} }
} }
} }
if (index === this._index) { return false; } if (focusDefinitionIndex === null) { return false; }
this._focusEntry(index, smooth); this._focusEntry(index, focusDefinitionIndex, smooth);
return true; return true;
} }
_getDictionaryEntryVisibleDefinitionIndex(index, sign) {
const {top: scrollTop, bottom: scrollBottom} = this._windowScroll.getRect();
const {definitions} = this._dictionaryEntries[index];
const nodes = this._getDictionaryEntryDefinitionNodes(index);
const definitionCount = Math.min(definitions.length, nodes.length);
if (definitionCount <= 0) { return null; }
let visibleIndex = null;
let visibleCoverage = 0;
for (let i = (sign > 0 ? 0 : definitionCount - 1); i >= 0 && i < definitionCount; i += sign) {
const {top, bottom} = nodes[i].getBoundingClientRect();
if (bottom <= scrollTop || top >= scrollBottom) { continue; }
const top2 = Math.max(scrollTop, Math.min(scrollBottom, top));
const bottom2 = Math.max(scrollTop, Math.min(scrollBottom, bottom));
const coverage = (bottom2 - top2) / (bottom - top);
if (coverage >= visibleCoverage) {
visibleCoverage = coverage;
visibleIndex = i;
}
}
return visibleIndex !== null ? visibleIndex : (sign > 0 ? definitionCount - 1 : 0);
}
_getDictionaryEntryDefinitionNodes(index) {
return this._dictionaryEntryNodes[index].querySelectorAll('.definition-item');
}
_sourceTermView() { _sourceTermView() {
this._relativeTermView(false); this._relativeTermView(false);
} }
@ -1529,7 +1572,7 @@ class Display extends EventDispatcher {
let count = Number.parseInt(argument, 10); let count = Number.parseInt(argument, 10);
if (!Number.isFinite(count)) { count = 1; } if (!Number.isFinite(count)) { count = 1; }
count = Math.max(0, Math.floor(count)); count = Math.max(0, Math.floor(count));
this._focusEntry(this._index + count * sign, true); this._focusEntry(this._index + count * sign, 0, true);
} }
_onHotkeyActionPlayAudioFromSource(source) { _onHotkeyActionPlayAudioFromSource(source) {

View File

@ -68,6 +68,10 @@ class ScrollElement {
this._animationRequestId = null; this._animationRequestId = null;
} }
getRect() {
return this._node.getBoundingClientRect();
}
// Private // Private
_onAnimationFrame(time) { _onAnimationFrame(time) {