diff --git a/imgsrc/srv/selection-handle.svg b/imgsrc/srv/selection-handle.svg new file mode 100644 index 0000000000..ab4611e539 --- /dev/null +++ b/imgsrc/srv/selection-handle.svg @@ -0,0 +1,7 @@ + + + diff --git a/src/pyj/read_book/create_annotation.pyj b/src/pyj/read_book/create_annotation.pyj index 69a7cc8f84..ed90855d57 100644 --- a/src/pyj/read_book/create_annotation.pyj +++ b/src/pyj/read_book/create_annotation.pyj @@ -2,6 +2,31 @@ # License: GPL v3 Copyright: 2020, Kovid Goyal from __python__ import bound_methods, hash_literals +from dom import svgicon, ensure_id + +WAITING_FOR_CLICK = 0 +WAITING_FOR_DRAG = 1 +DRAGGING_LEFT = 2 +DRAGGING_RIGHT = 3 + + +def selection_handle(invert): + ans = svgicon('selection-handle') + s = ans.style + if invert: + s.transform = 'scaleX(-1)' + s.position = 'absolute' + s.boxSizing = 'border-box' + return ans + + +def map_from_iframe_coords(point): + l = document.getElementById('book-left-margin') + point.x += l.offsetWidth + t = document.getElementById('book-top-margin') + point.y += t.offsetHeight + return point + class CreateAnnotation: @@ -9,11 +34,28 @@ class CreateAnnotation: def __init__(self, view): self.view = view + self.state = WAITING_FOR_CLICK + container = self.container + + lh = selection_handle() + self.left_handle_id = ensure_id(lh, 'handle') + container.appendChild(lh) + rh = selection_handle(True) + self.right_handle_id = ensure_id(rh, 'handle') + container.appendChild(rh) @property def container(self): return document.getElementById(self.container_id) + @property + def left_handle(self): + return document.getElementById(self.left_handle_id) + + @property + def right_handle(self): + return document.getElementById(self.right_handle_id) + @property def is_visible(self): return self.container.style.display is not 'none' @@ -29,6 +71,38 @@ class CreateAnnotation: def handle_message(self, msg): if msg.type is 'create-annotation': + if not self.is_visible: + self.view.hide_overlays() + self.state = WAITING_FOR_CLICK self.show() + self.hide_handles() + if msg.extents.start.x is not None: + self.place_handles(msg.extents) else: print('Ignoring annotations message with unknown type:', msg.type) + + def hide_handles(self): + self.left_handle.style.display = 'none' + self.right_handle.style.display = 'none' + + def place_handles(self, extents): + lh, rh = self.left_handle, self.right_handle + + def do_it(handle, data): + map_from_iframe_coords(data) + s = handle.style + s.display = 'block' + height = data.height * 3 + width = data.height * 2 + s.width = f'{width}px' + s.height = f'{height}px' + bottom = min(max(0, data.y + data.height), window.innerHeight) + top = bottom - height + s.top = f'{top}px' + return s, width + + style, width = do_it(lh, extents.start) + style.left = min(max(0, extents.start.x), window.innerWidth - width // 2) + 'px' + style, width = do_it(rh, extents.end) + style.left = (min(max(width // 2, extents.end.x), window.innerWidth) - width) + 'px' + self.state = WAITING_FOR_DRAG diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 3adcace5d3..66ff20b341 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -12,9 +12,9 @@ from read_book.extract import get_elements from read_book.find import reset_find_caches, select_search_result from read_book.flow_mode import ( anchor_funcs as flow_anchor_funcs, auto_scroll_action as flow_auto_scroll_action, - flow_onwheel, flow_to_scroll_fraction, handle_gesture as flow_handle_gesture, - handle_shortcut as flow_handle_shortcut, layout as flow_layout, - scroll_by_page as flow_scroll_by_page, ensure_selection_visible + ensure_selection_visible, flow_onwheel, flow_to_scroll_fraction, + handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut, + layout as flow_layout, scroll_by_page as flow_scroll_by_page ) from read_book.footnotes import is_footnote_link from read_book.globals import ( @@ -50,7 +50,7 @@ from read_book.touch import ( create_handlers as create_touch_handlers, reset_handlers as reset_touch_handlers ) from read_book.viewport import scroll_viewport -from utils import debounce, html_escape, is_ios +from utils import debounce, html_escape, is_ios, selection_extents FORCE_FLOW_MODE = False CALIBRE_VERSION = '__CALIBRE_VERSION__' @@ -495,7 +495,7 @@ class IframeBoss: if self.handle_navigation_shortcut(sc_name, evt): evt.preventDefault() elif sc_name is 'create_annotation': - self.send_message('annotations', type='create-annotation') + self.initiate_creation_of_annotation() else: self.send_message('handle_shortcut', name=sc_name) @@ -600,6 +600,14 @@ class IframeBoss: else: end_reference_mode() + def initiate_creation_of_annotation(self): + self.send_message( + 'annotations', + type='create-annotation', + in_flow_mode=current_layout_mode() is 'flow', + extents=selection_extents(), + ) + def main(): main.boss = IframeBoss() diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index d234ba87f3..30cfd45abd 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -560,6 +560,7 @@ class View: self.search_overlay.hide() self.content_popup_overlay.hide() self.reference_mode_overlay.style.display = 'none' + self.create_annotation.hide() self.focus_iframe() def focus_iframe(self): diff --git a/src/pyj/utils.pyj b/src/pyj/utils.pyj index 93e1747e10..f2af531594 100644 --- a/src/pyj/utils.pyj +++ b/src/pyj/utils.pyj @@ -252,6 +252,30 @@ def sandboxed_html(html, style, sandbox): return ans +def selection_extents(): + ans = {'start': {'x': None, 'y': None, 'height': None}, 'end': {'x': None, 'y': None, 'height': None}} + sel = window.getSelection() + if not sel or not sel.rangeCount: + return ans + start = sel.getRangeAt(0).cloneRange() + end = sel.getRangeAt(sel.rangeCount - 1).cloneRange() + start.collapse(True) + end.collapse(False) + + def for_boundary(r, ans): + rects = r.getClientRects() + if not rects.length: + return + rect = rects[0] + ans.x = rect.left + ans.y = rect.top + ans.height = rect.bottom - rect.top + + for_boundary(start, ans.start) + for_boundary(end, ans.end) + return ans + + if __name__ is '__main__': from pythonize import strings strings()