mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement drag scrolling for highlight handles
This commit is contained in:
parent
86f5976ea5
commit
1148208f45
@ -177,6 +177,7 @@ def map_to_iframe_coords(point):
|
|||||||
|
|
||||||
|
|
||||||
BAR_SIZE = 32
|
BAR_SIZE = 32
|
||||||
|
DRAG_SCROLL_ZONE_MIN_HEIGHT = 10
|
||||||
|
|
||||||
|
|
||||||
def create_bar():
|
def create_bar():
|
||||||
@ -194,6 +195,8 @@ class CreateAnnotation:
|
|||||||
def __init__(self, view):
|
def __init__(self, view):
|
||||||
self.view = view
|
self.view = view
|
||||||
self.active_touch = None
|
self.active_touch = None
|
||||||
|
self.drag_scroll_timer = None
|
||||||
|
self.last_drag_scroll_at = -100000
|
||||||
self.editing_annot_uuid = None
|
self.editing_annot_uuid = None
|
||||||
self.current_notes = ''
|
self.current_notes = ''
|
||||||
self.annotations_manager = self.view.annotations_manager
|
self.annotations_manager = self.view.annotations_manager
|
||||||
@ -227,7 +230,7 @@ class CreateAnnotation:
|
|||||||
tb = create_bar()
|
tb = create_bar()
|
||||||
container.appendChild(tb)
|
container.appendChild(tb)
|
||||||
button(tb, 'close', _('Cancel creation of highlight'), self.hide)
|
button(tb, 'close', _('Cancel creation of highlight'), self.hide)
|
||||||
button(tb, 'chevron-up', _('Scroll up'), self.scroll_up)
|
button(tb, 'chevron-up', _('Scroll up'), self.button_scroll.bind(None, True))
|
||||||
tb.appendChild(E.span(style=f'height: {tb.style.height}'))
|
tb.appendChild(E.span(style=f'height: {tb.style.height}'))
|
||||||
button(tb.lastChild, 'trash', _('Remove this highlight'), self.delete_highlight)
|
button(tb.lastChild, 'trash', _('Remove this highlight'), self.delete_highlight)
|
||||||
tb.lastChild.appendChild(E.span('\xa0\xa0\xa0'))
|
tb.lastChild.appendChild(E.span('\xa0\xa0\xa0'))
|
||||||
@ -243,7 +246,7 @@ class CreateAnnotation:
|
|||||||
bb = create_bar()
|
bb = create_bar()
|
||||||
container.appendChild(bb)
|
container.appendChild(bb)
|
||||||
button(bb, 'fg', _('Change highlight color'), self.choose_color)
|
button(bb, 'fg', _('Change highlight color'), self.choose_color)
|
||||||
button(bb, 'chevron-down', _('Scroll down'), self.scroll_down)
|
button(bb, 'chevron-down', _('Scroll down'), self.button_scroll)
|
||||||
button(bb, 'pencil', _('Add a note'), self.add_notes)
|
button(bb, 'pencil', _('Add a note'), self.add_notes)
|
||||||
|
|
||||||
sd = get_session_data()
|
sd = get_session_data()
|
||||||
@ -272,12 +275,6 @@ class CreateAnnotation:
|
|||||||
def copy_to_clipboard(self):
|
def copy_to_clipboard(self):
|
||||||
self.view.iframe_wrapper.send_message('copy_selection')
|
self.view.iframe_wrapper.send_message('copy_selection')
|
||||||
|
|
||||||
def scroll_up(self):
|
|
||||||
self.send_message('scroll', backwards=True)
|
|
||||||
|
|
||||||
def scroll_down(self):
|
|
||||||
self.send_message('scroll', backwards=False)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def middle(self):
|
def middle(self):
|
||||||
return document.getElementById(self.middle_id)
|
return document.getElementById(self.middle_id)
|
||||||
@ -455,12 +452,16 @@ class CreateAnnotation:
|
|||||||
if sc_name is 'show_chrome':
|
if sc_name is 'show_chrome':
|
||||||
self.hide()
|
self.hide()
|
||||||
elif sc_name in ('up', 'down', 'pageup', 'pagedown'):
|
elif sc_name in ('up', 'down', 'pageup', 'pagedown'):
|
||||||
self.send_message('scroll', backwards=bool('up' in sc_name))
|
backwards = 'up' in sc_name
|
||||||
|
if 'page' in sc_name or not self.in_flow_mode:
|
||||||
|
self.paged_scroll(backwards)
|
||||||
|
else:
|
||||||
|
self.send_drag_scroll_message(backwards, 'left' if backwards else 'right', False)
|
||||||
elif sc_name in ('left', 'right'):
|
elif sc_name in ('left', 'right'):
|
||||||
if self.in_flow_mode:
|
if self.in_flow_mode:
|
||||||
self.send_message('perp-scroll', backwards=bool(sc_name is 'left'))
|
self.send_message('perp-scroll', backwards=bool(sc_name is 'left'))
|
||||||
else:
|
else:
|
||||||
self.send_message('scroll', backwards=bool(sc_name is 'left'))
|
self.paged_scroll(sc_name is 'left')
|
||||||
|
|
||||||
def container_clicked(self, ev):
|
def container_clicked(self, ev):
|
||||||
ev.stopPropagation(), ev.preventDefault()
|
ev.stopPropagation(), ev.preventDefault()
|
||||||
@ -495,9 +496,46 @@ class CreateAnnotation:
|
|||||||
self.start_handle_drag(touch, ev.currentTarget.id)
|
self.start_handle_drag(touch, ev.currentTarget.id)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def button_scroll(self, backwards):
|
||||||
|
if self.in_flow_mode:
|
||||||
|
self.send_drag_scroll_message(backwards, 'left' if backwards else 'right', False)
|
||||||
|
else:
|
||||||
|
self.paged_scroll(backwards)
|
||||||
|
|
||||||
|
def paged_scroll(self, backwards):
|
||||||
|
self.send_message('paged-scroll', backwards=backwards)
|
||||||
|
|
||||||
|
def run_drag_scroll(self, mouse_y, top, bottom):
|
||||||
|
backwards = mouse_y <= top
|
||||||
|
self.do_one_drag_scroll(backwards, top - mouse_y if backwards else mouse_y - bottom)
|
||||||
|
|
||||||
|
def do_one_drag_scroll(self, backwards, distance_from_boundary):
|
||||||
|
window.clearTimeout(self.drag_scroll_timer)
|
||||||
|
self.drag_scroll_timer = None
|
||||||
|
if self.state not in (DRAGGING_RIGHT, DRAGGING_LEFT):
|
||||||
|
return
|
||||||
|
interval = 100 if self.in_flow_mode else 1200
|
||||||
|
self.drag_scroll_timer = window.setTimeout(self.do_one_drag_scroll.bind(None, backwards, distance_from_boundary), interval)
|
||||||
|
now = window.performance.now()
|
||||||
|
if now - self.last_drag_scroll_at > interval:
|
||||||
|
self.send_drag_scroll_message(backwards, 'left' if self.state is DRAGGING_LEFT else 'right', True)
|
||||||
|
self.last_drag_scroll_at = now
|
||||||
|
|
||||||
|
def send_drag_scroll_message(self, backwards, handle, extend_selection):
|
||||||
|
self.send_message(
|
||||||
|
'drag-scroll', backwards=backwards, handle=handle, extents=self.current_handle_position,
|
||||||
|
extend_selection=extend_selection)
|
||||||
|
|
||||||
|
def end_drag_scroll(self):
|
||||||
|
if self.drag_scroll_timer is not None:
|
||||||
|
window.clearTimeout(self.drag_scroll_timer)
|
||||||
|
self.drag_scroll_timer = None
|
||||||
|
self.last_drag_scroll_at = -10000
|
||||||
|
|
||||||
def mouseup_on_container(self, ev):
|
def mouseup_on_container(self, ev):
|
||||||
if self.state in (DRAGGING_RIGHT, DRAGGING_LEFT):
|
if self.state in (DRAGGING_RIGHT, DRAGGING_LEFT):
|
||||||
self.state = WAITING_FOR_DRAG
|
self.state = WAITING_FOR_DRAG
|
||||||
|
self.end_drag_scroll()
|
||||||
ev.preventDefault(), ev.stopPropagation()
|
ev.preventDefault(), ev.stopPropagation()
|
||||||
|
|
||||||
def touchend_on_container(self, ev):
|
def touchend_on_container(self, ev):
|
||||||
@ -507,6 +545,7 @@ class CreateAnnotation:
|
|||||||
if touch.identifier is self.active_touch:
|
if touch.identifier is self.active_touch:
|
||||||
self.active_touch = None
|
self.active_touch = None
|
||||||
self.state = WAITING_FOR_DRAG
|
self.state = WAITING_FOR_DRAG
|
||||||
|
self.end_drag_scroll()
|
||||||
return
|
return
|
||||||
|
|
||||||
def handle_moved(self, ev):
|
def handle_moved(self, ev):
|
||||||
@ -518,6 +557,16 @@ class CreateAnnotation:
|
|||||||
pos.start = map_to_iframe_coords(pos.start)
|
pos.start = map_to_iframe_coords(pos.start)
|
||||||
pos.end = map_to_iframe_coords(pos.end)
|
pos.end = map_to_iframe_coords(pos.end)
|
||||||
self.send_message('set-selection', extents=pos)
|
self.send_message('set-selection', extents=pos)
|
||||||
|
c = self.container
|
||||||
|
rect = c.getBoundingClientRect()
|
||||||
|
t = document.getElementById('book-top-margin').offsetHeight
|
||||||
|
top = rect.top + max(t, DRAG_SCROLL_ZONE_MIN_HEIGHT)
|
||||||
|
t = document.getElementById('book-bottom-margin').offsetHeight
|
||||||
|
bottom = rect.bottom - max(t, DRAG_SCROLL_ZONE_MIN_HEIGHT)
|
||||||
|
if ev.clientY < top or ev.clientY > bottom:
|
||||||
|
self.run_drag_scroll(ev.clientY, top, bottom)
|
||||||
|
else:
|
||||||
|
self.end_drag_scroll()
|
||||||
|
|
||||||
def mousemove_on_container(self, ev):
|
def mousemove_on_container(self, ev):
|
||||||
if self.state not in (DRAGGING_RIGHT, DRAGGING_LEFT):
|
if self.state not in (DRAGGING_RIGHT, DRAGGING_LEFT):
|
||||||
@ -626,17 +675,10 @@ class CreateAnnotation:
|
|||||||
self.editing_annot_uuid = msg.existing or None
|
self.editing_annot_uuid = msg.existing or None
|
||||||
if self.editing_annot_uuid:
|
if self.editing_annot_uuid:
|
||||||
self.current_notes = self.annotations_manager.notes_for_highlight(self.editing_annot_uuid) or ''
|
self.current_notes = self.annotations_manager.notes_for_highlight(self.editing_annot_uuid) or ''
|
||||||
|
elif msg.type is 'scrolled':
|
||||||
|
self.place_handles_after_scroll(msg.extents, msg.handle, msg.extended)
|
||||||
elif msg.type is 'update-handles':
|
elif msg.type is 'update-handles':
|
||||||
self.place_handles(msg.extents)
|
self.place_handles(msg.extents)
|
||||||
if msg.from_scroll and not msg.selection_extended:
|
|
||||||
middle = map_from_iframe_coords({
|
|
||||||
'x': msg.page_rect.left + msg.page_rect.width // 2,
|
|
||||||
'y': msg.page_rect.top + msg.page_rect.height // 2
|
|
||||||
})
|
|
||||||
handle = self.left_handle if msg.backwards else self.right_handle
|
|
||||||
handle.style.display = 'block'
|
|
||||||
handle.style.left = f'{middle.x}px'
|
|
||||||
handle.style.top = f'{middle.y}px'
|
|
||||||
elif msg.type is 'highlight-applied':
|
elif msg.type is 'highlight-applied':
|
||||||
if not msg.ok:
|
if not msg.ok:
|
||||||
return error_dialog(
|
return error_dialog(
|
||||||
@ -653,30 +695,42 @@ class CreateAnnotation:
|
|||||||
self.left_handle.style.display = 'none'
|
self.left_handle.style.display = 'none'
|
||||||
self.right_handle.style.display = 'none'
|
self.right_handle.style.display = 'none'
|
||||||
|
|
||||||
|
def place_single_handle(self, handle, data):
|
||||||
|
map_from_iframe_coords(data)
|
||||||
|
s = handle.style
|
||||||
|
s.display = 'block' if data.onscreen else 'none'
|
||||||
|
height = data.height * 3
|
||||||
|
width = data.height * 2
|
||||||
|
s.width = f'{width}px'
|
||||||
|
s.height = f'{height}px'
|
||||||
|
bottom = data.y + data.height
|
||||||
|
top = bottom - height
|
||||||
|
s.top = f'{top}px'
|
||||||
|
if handle.id is self.left_handle_id:
|
||||||
|
s.left = (data.x - width) + 'px'
|
||||||
|
else:
|
||||||
|
s.left = data.x + 'px'
|
||||||
|
|
||||||
def place_handles(self, extents):
|
def place_handles(self, extents):
|
||||||
lh, rh = self.left_handle, self.right_handle
|
self.place_single_handle(self.left_handle, extents.start)
|
||||||
|
self.place_single_handle(self.right_handle, extents.end)
|
||||||
def do_it(handle, data):
|
|
||||||
map_from_iframe_coords(data)
|
|
||||||
s = handle.style
|
|
||||||
s.display = 'block' if data.onscreen else 'none'
|
|
||||||
height = data.height * 3
|
|
||||||
width = data.height * 2
|
|
||||||
s.width = f'{width}px'
|
|
||||||
s.height = f'{height}px'
|
|
||||||
bottom = data.y + data.height
|
|
||||||
top = bottom - height
|
|
||||||
s.top = f'{top}px'
|
|
||||||
return s, width
|
|
||||||
|
|
||||||
style, width = do_it(lh, extents.start)
|
|
||||||
style.left = (extents.start.x - width) + 'px'
|
|
||||||
style, width = do_it(rh, extents.end)
|
|
||||||
style.left = extents.end.x + 'px'
|
|
||||||
self.state = WAITING_FOR_DRAG
|
self.state = WAITING_FOR_DRAG
|
||||||
self.left_line_height = extents.start.height
|
self.left_line_height = extents.start.height
|
||||||
self.right_line_height = extents.end.height
|
self.right_line_height = extents.end.height
|
||||||
|
|
||||||
|
def place_handles_after_scroll(self, extents, handle, extended):
|
||||||
|
if extended:
|
||||||
|
if handle is 'right':
|
||||||
|
h = self.left_handle
|
||||||
|
data = extents.start
|
||||||
|
else:
|
||||||
|
h = self.right_handle
|
||||||
|
data = extents.end
|
||||||
|
self.place_single_handle(h, data)
|
||||||
|
else:
|
||||||
|
self.place_single_handle(self.left_handle, extents.start)
|
||||||
|
self.place_single_handle(self.right_handle, extents.end)
|
||||||
|
|
||||||
|
|
||||||
class ViewAnnotation:
|
class ViewAnnotation:
|
||||||
|
|
||||||
|
@ -123,13 +123,15 @@ def scroll_by_page(direction):
|
|||||||
h = scroll_viewport.height() - 10
|
h = scroll_viewport.height() - 10
|
||||||
window.scrollBy(0, h * direction)
|
window.scrollBy(0, h * direction)
|
||||||
|
|
||||||
def scroll_to_extend_annotation(backward, horizontal):
|
def scroll_to_extend_annotation(backward, horizontal, by_page):
|
||||||
direction = -1 if backward else 1
|
direction = -1 if backward else 1
|
||||||
|
h = line_height()
|
||||||
|
if by_page:
|
||||||
|
h = (window.innerWidth if horizontal else window.innerHeight) - h
|
||||||
if horizontal:
|
if horizontal:
|
||||||
before = window.pageXOffset
|
before = window.pageXOffset
|
||||||
window.scrollBy(15 * direction, 0)
|
window.scrollBy(h * direction, 0)
|
||||||
return window.pageXOffset is not before
|
return window.pageXOffset is not before
|
||||||
h = scroll_viewport.height() - 10
|
|
||||||
before = window.pageYOffset
|
before = window.pageYOffset
|
||||||
window.scrollBy(0, h * direction)
|
window.scrollBy(0, h * direction)
|
||||||
return window.pageYOffset is not before
|
return window.pageYOffset is not before
|
||||||
|
@ -5,7 +5,7 @@ from __python__ import bound_methods, hash_literals
|
|||||||
import traceback
|
import traceback
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from select import (
|
from select import (
|
||||||
extend_selection_after_scroll, selection_extents, selection_extents_at_point,
|
extend_selection_to_limit, selection_extents, selection_extents_at_point,
|
||||||
set_selections_extents_to, word_at_point
|
set_selections_extents_to, word_at_point
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,11 +38,11 @@ from read_book.mathjax import apply_mathjax
|
|||||||
from read_book.paged_mode import (
|
from read_book.paged_mode import (
|
||||||
anchor_funcs as paged_anchor_funcs,
|
anchor_funcs as paged_anchor_funcs,
|
||||||
auto_scroll_action as paged_auto_scroll_action, calc_columns_per_screen,
|
auto_scroll_action as paged_auto_scroll_action, calc_columns_per_screen,
|
||||||
current_cfi, current_page_width, get_columns_per_screen_data,
|
current_cfi, get_columns_per_screen_data, handle_gesture as paged_handle_gesture,
|
||||||
handle_gesture as paged_handle_gesture, handle_shortcut as paged_handle_shortcut,
|
handle_shortcut as paged_handle_shortcut, jump_to_cfi as paged_jump_to_cfi,
|
||||||
jump_to_cfi as paged_jump_to_cfi, layout as paged_layout,
|
layout as paged_layout, onwheel as paged_onwheel,
|
||||||
onwheel as paged_onwheel, prepare_for_resize as paged_prepare_for_resize,
|
prepare_for_resize as paged_prepare_for_resize, progress_frac,
|
||||||
progress_frac, reset_paged_mode_globals, resize_done as paged_resize_done,
|
reset_paged_mode_globals, resize_done as paged_resize_done,
|
||||||
scroll_by_page as paged_scroll_by_page, scroll_to_elem,
|
scroll_by_page as paged_scroll_by_page, scroll_to_elem,
|
||||||
scroll_to_extend_annotation as paged_annotation_scroll,
|
scroll_to_extend_annotation as paged_annotation_scroll,
|
||||||
scroll_to_fraction as paged_scroll_to_fraction, snap_to_selection,
|
scroll_to_fraction as paged_scroll_to_fraction, snap_to_selection,
|
||||||
@ -707,13 +707,21 @@ class IframeBoss:
|
|||||||
extents = selection_extents(in_flow_mode)
|
extents = selection_extents(in_flow_mode)
|
||||||
self.send_message(
|
self.send_message(
|
||||||
'annotations', type='position-handles', extents=extents, existing=annot_id_uuid_map[annot_id])
|
'annotations', type='position-handles', extents=extents, existing=annot_id_uuid_map[annot_id])
|
||||||
elif data.type is 'scroll':
|
elif data.type is 'drag-scroll':
|
||||||
if self.scroll_to_extend_annotation(data.backwards):
|
if self.scroll_to_extend_annotation(data.backwards):
|
||||||
page_rect = {'width': current_page_width()}
|
if data.extend_selection:
|
||||||
extended = extend_selection_after_scroll(data.backwards, in_flow_mode, page_rect)
|
extend_selection_to_limit(data.handle is 'left', data.backwards)
|
||||||
|
extents = selection_extents(in_flow_mode)
|
||||||
self.send_message(
|
self.send_message(
|
||||||
'annotations', type='update-handles', extents=selection_extents(in_flow_mode),
|
'annotations', type='scrolled',
|
||||||
backwards=data.backwards, from_scroll=True, selection_extended=extended, page_rect=page_rect
|
backwards=data.backwards, handle=data.handle, extents=extents, extended=data.extend_selection
|
||||||
|
)
|
||||||
|
elif data.type is 'paged-scroll':
|
||||||
|
if self.scroll_to_extend_annotation(data.backwards, False, True):
|
||||||
|
extents = selection_extents(in_flow_mode)
|
||||||
|
self.send_message(
|
||||||
|
'annotations', type='scrolled', backwards=data.backwards, handle=None,
|
||||||
|
extents=extents, extended=False
|
||||||
)
|
)
|
||||||
elif data.type is 'perp-scroll':
|
elif data.type is 'perp-scroll':
|
||||||
if in_flow_mode and flow_annotation_scroll(data.backwards, True):
|
if in_flow_mode and flow_annotation_scroll(data.backwards, True):
|
||||||
|
@ -98,53 +98,32 @@ def selection_extents_at_point(x, y, in_flow_mode):
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def extend_selection_after_scroll(backwards, in_flow_mode, page_rect):
|
def range_at_limit(invert_x, invert_y):
|
||||||
page_rect.top = page_rect.left = 0
|
step = 10
|
||||||
page_rect.bottom = window.innerHeight
|
for y in range(0, window.innerHeight + step, step):
|
||||||
page_rect.right = page_rect.width
|
if invert_y:
|
||||||
if not in_flow_mode and not backwards:
|
y = max(0, window.innerHeight - y)
|
||||||
page_rect.right = window.innerWidth
|
for x in range(0, window.innerWidth + step, step):
|
||||||
page_rect.left = window.innerWidth - page_rect.width
|
if invert_x:
|
||||||
page_rect.width = page_rect.right - page_rect.left
|
x = max(0, window.innerWidth - x)
|
||||||
page_rect.height = page_rect.bottom - page_rect.top
|
r = range_from_point(x, y)
|
||||||
|
if r:
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def extend_selection_to_limit(left, top):
|
||||||
sel = window.getSelection()
|
sel = window.getSelection()
|
||||||
if not sel.rangeCount:
|
if not sel or not sel.rangeCount or sel.isCollapsed:
|
||||||
|
return False
|
||||||
|
new_limit = range_at_limit(not left, not top)
|
||||||
|
if not new_limit:
|
||||||
return False
|
return False
|
||||||
if in_flow_mode:
|
|
||||||
page_rect.width = window.innerWidth
|
|
||||||
r = sel.getRangeAt(0)
|
r = sel.getRangeAt(0)
|
||||||
q = r.cloneRange()
|
if left:
|
||||||
q.collapse(backwards)
|
r.setStart(new_limit.startContainer, new_limit.startOffset)
|
||||||
rects = q.getClientRects()
|
else:
|
||||||
if not rects.length:
|
r.setEnd(new_limit.startContainer, new_limit.startOffset)
|
||||||
return False
|
return True
|
||||||
rect = rects[0]
|
|
||||||
|
|
||||||
in_page_already = rect.left >= page_rect.left and rect.top >= page_rect.top and rect.bottom <= page_rect.bottom and rect.right <= page_rect.right
|
|
||||||
if in_page_already:
|
|
||||||
return True
|
|
||||||
dx = page_rect.width // 10
|
|
||||||
dy = page_rect.height // 10
|
|
||||||
middle_x = page_rect.left + page_rect.width // 2
|
|
||||||
for yi in range(1, 10):
|
|
||||||
if backwards:
|
|
||||||
y = page_rect.bottom - dy * yi
|
|
||||||
else:
|
|
||||||
y = page_rect.top + dy * yi
|
|
||||||
for xi in range(4):
|
|
||||||
xvals = v'[-1, 1]' if xi else v'[1]'
|
|
||||||
for xw in xvals:
|
|
||||||
x = middle_x + xw * dx
|
|
||||||
p = range_from_point(x, y)
|
|
||||||
if p:
|
|
||||||
if backwards:
|
|
||||||
r.setStart(p.startContainer, p.startOffset)
|
|
||||||
else:
|
|
||||||
r.setEnd(p.startContainer, p.startOffset)
|
|
||||||
return True
|
|
||||||
# could not find any content on page
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def set_selections_extents_to(extents):
|
def set_selections_extents_to(extents):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user