mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Allow tap/click on word to skip to speaking from it
This commit is contained in:
parent
65445427fd
commit
fbeb0230ff
@ -28,25 +28,6 @@ def text_nodes_in_range(r):
|
||||
return ans
|
||||
|
||||
|
||||
def first_non_empty_text_node_in_range(r):
|
||||
parent = r.commonAncestorContainer
|
||||
doc = parent.ownerDocument or document
|
||||
iterator = doc.createNodeIterator(parent)
|
||||
in_range = False
|
||||
while True:
|
||||
node = iterator.nextNode()
|
||||
if not node:
|
||||
break
|
||||
if not in_range and node.isSameNode(r.startContainer):
|
||||
in_range = True
|
||||
if in_range:
|
||||
if is_non_empty_text_node(node):
|
||||
return node
|
||||
if node.isSameNode(r.endContainer):
|
||||
break
|
||||
|
||||
|
||||
|
||||
def first_annot_in_range(r, annot_id_uuid_map):
|
||||
parent = r.commonAncestorContainer
|
||||
doc = parent.ownerDocument or document
|
||||
|
@ -7,7 +7,7 @@ from fs_images import fix_fullscreen_svg_images
|
||||
from gettext import gettext as _
|
||||
from iframe_comm import IframeClient
|
||||
from range_utils import (
|
||||
first_non_empty_text_node_in_range, highlight_associated_with_selection,
|
||||
highlight_associated_with_selection,
|
||||
last_span_for_crw, reset_highlight_counter, select_crw, unwrap_all_crw,
|
||||
unwrap_crw, wrap_text_in_range
|
||||
)
|
||||
@ -64,7 +64,7 @@ from read_book.touch import (
|
||||
)
|
||||
from read_book.viewport import scroll_viewport
|
||||
from select import (
|
||||
move_end_of_selection, range_for_tts, selection_extents, word_at_point
|
||||
move_end_of_selection, selection_extents, word_at_point
|
||||
)
|
||||
from utils import debounce, is_ios
|
||||
|
||||
@ -900,15 +900,16 @@ class IframeBoss:
|
||||
if data.type is 'mark':
|
||||
self.mark_word_being_spoken(data.num)
|
||||
elif data.type is 'play':
|
||||
if data.x? and data.y?:
|
||||
r = range_for_tts(data.x, data.y)
|
||||
text_node, offset = first_non_empty_text_node_in_range(r)
|
||||
else:
|
||||
text_node, offset = None, 0
|
||||
if data.pos:
|
||||
r = word_at_point(data.pos.x, data.pos.y)
|
||||
if r:
|
||||
if r.startContainer?.nodeType is Node.TEXT_NODE:
|
||||
text_node, offset = r.startContainer, r.startOffset
|
||||
marked_text = tts_data(text_node, offset)
|
||||
sel = window.getSelection()
|
||||
sel.removeAllRanges()
|
||||
self.send_message('tts', type='text-extracted', marked_text=marked_text)
|
||||
self.send_message('tts', type='text-extracted', marked_text=marked_text, pos=data.pos)
|
||||
|
||||
def mark_word_being_spoken(self, occurrence_number):
|
||||
self.last_search_at = window.performance.now()
|
||||
|
@ -9,7 +9,7 @@ from dom import clear, svgicon, unique_id
|
||||
from gettext import gettext as _
|
||||
from read_book.globals import runtime, ui_operations
|
||||
from read_book.highlights import ICON_SIZE
|
||||
from read_book.selection_bar import BUTTON_MARGIN
|
||||
from read_book.selection_bar import BUTTON_MARGIN, get_margins, map_to_iframe_coords
|
||||
from read_book.shortcuts import shortcut_for_key_event
|
||||
|
||||
HIDDEN = 0
|
||||
@ -35,6 +35,7 @@ class ReadAloud:
|
||||
'display: inline-flex; flex-direction: column; margin: 1rem;'
|
||||
))
|
||||
container.addEventListener('keydown', self.on_keydown, {'passive': False})
|
||||
container.addEventListener('click', self.container_clicked, {'passive': False})
|
||||
|
||||
@property
|
||||
def container(self):
|
||||
@ -83,17 +84,16 @@ class ReadAloud:
|
||||
return
|
||||
bar_container = self.bar
|
||||
clear(bar_container)
|
||||
bar_container.style.maxWidth = 'min(50rem, 90vw)' if self.supports_css_min_max else '50rem'
|
||||
bar_container.style.maxWidth = 'min(40rem, 80vw)' if self.supports_css_min_max else '40rem'
|
||||
bar_container.style.backgroundColor = get_color("window-background")
|
||||
notes_container = E.div()
|
||||
for x in [
|
||||
E.div(style='height: 4ex; display: flex; align-items: center; padding: 5px; justify-content: center'),
|
||||
|
||||
E.hr(style='border-top: solid 1px; margin: 0; padding: 0; display: none'),
|
||||
|
||||
E.div(
|
||||
style='display: none; padding: 5px;',
|
||||
notes_container,
|
||||
style='display: none; padding: 5px; font-size: smaller',
|
||||
E.div()
|
||||
)
|
||||
]:
|
||||
bar_container.appendChild(x)
|
||||
@ -118,6 +118,16 @@ class ReadAloud:
|
||||
else:
|
||||
bar.appendChild(cb('play', 'play', _('Start reading') if self.state is STOPPED else _('Resume reading')))
|
||||
bar.appendChild(cb('hide', 'close', _('Close Read aloud')))
|
||||
if self.state is not WAITING_FOR_PLAY_TO_START:
|
||||
notes_container = bar_container.lastChild
|
||||
notes_container.style.display = notes_container.previousSibling.style.display = 'block'
|
||||
notes_container = notes_container.lastChild
|
||||
if self.state is STOPPED:
|
||||
notes_container.textContent = _('Tap/click on a word to start from there')
|
||||
elif self.state is PLAYING:
|
||||
notes_container.textContent = _('Tap/click on a word to skip to it')
|
||||
else:
|
||||
notes_container.textContent = _('Tap/click on a word to continue from there')
|
||||
|
||||
def play(self):
|
||||
if self.state is PAUSED:
|
||||
@ -143,6 +153,15 @@ class ReadAloud:
|
||||
elif self.state is PAUSED or self.state is STOPPED:
|
||||
self.play()
|
||||
|
||||
def container_clicked(self, ev):
|
||||
if ev.button is not 0:
|
||||
return
|
||||
ev.stopPropagation(), ev.preventDefault()
|
||||
margins = get_margins()
|
||||
pos = {'x': ev.clientX, 'y': ev.clientY}
|
||||
pos = map_to_iframe_coords(pos, margins)
|
||||
self.send_message('play', pos=pos)
|
||||
|
||||
def on_keydown(self, ev):
|
||||
ev.stopPropagation(), ev.preventDefault()
|
||||
if ev.key is 'Escape':
|
||||
@ -183,4 +202,6 @@ class ReadAloud:
|
||||
|
||||
def handle_message(self, msg):
|
||||
if msg.type is 'text-extracted':
|
||||
if msg.pos:
|
||||
self.stop()
|
||||
ui_operations.tts('play', {'marked_text': msg.marked_text})
|
||||
|
@ -191,17 +191,3 @@ def move_end_of_selection(pos, start):
|
||||
else:
|
||||
if r.endContainer is not p.offsetNode or r.endOffset is not p.offset:
|
||||
r.setEnd(p.offsetNode, p.offset)
|
||||
|
||||
|
||||
def range_for_tts(x, y):
|
||||
p = None
|
||||
if x? and y?:
|
||||
p = caret_position_from_point(x, y)
|
||||
if not p:
|
||||
p = caret_position_from_point(0, 0)
|
||||
if not p:
|
||||
p = {'offsetNode': document.body, 'offset': 0}
|
||||
r = document.createRange()
|
||||
r.setStart(p.offsetNode, p.offset)
|
||||
r.setEndAfter(document.body)
|
||||
return r
|
||||
|
Loading…
x
Reference in New Issue
Block a user