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
|
||||
DRAG_SCROLL_ZONE_MIN_HEIGHT = 10
|
||||
|
||||
|
||||
def create_bar():
|
||||
@ -194,6 +195,8 @@ class CreateAnnotation:
|
||||
def __init__(self, view):
|
||||
self.view = view
|
||||
self.active_touch = None
|
||||
self.drag_scroll_timer = None
|
||||
self.last_drag_scroll_at = -100000
|
||||
self.editing_annot_uuid = None
|
||||
self.current_notes = ''
|
||||
self.annotations_manager = self.view.annotations_manager
|
||||
@ -227,7 +230,7 @@ class CreateAnnotation:
|
||||
tb = create_bar()
|
||||
container.appendChild(tb)
|
||||
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}'))
|
||||
button(tb.lastChild, 'trash', _('Remove this highlight'), self.delete_highlight)
|
||||
tb.lastChild.appendChild(E.span('\xa0\xa0\xa0'))
|
||||
@ -243,7 +246,7 @@ class CreateAnnotation:
|
||||
bb = create_bar()
|
||||
container.appendChild(bb)
|
||||
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)
|
||||
|
||||
sd = get_session_data()
|
||||
@ -272,12 +275,6 @@ class CreateAnnotation:
|
||||
def copy_to_clipboard(self):
|
||||
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
|
||||
def middle(self):
|
||||
return document.getElementById(self.middle_id)
|
||||
@ -455,12 +452,16 @@ class CreateAnnotation:
|
||||
if sc_name is 'show_chrome':
|
||||
self.hide()
|
||||
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'):
|
||||
if self.in_flow_mode:
|
||||
self.send_message('perp-scroll', backwards=bool(sc_name is 'left'))
|
||||
else:
|
||||
self.send_message('scroll', backwards=bool(sc_name is 'left'))
|
||||
self.paged_scroll(sc_name is 'left')
|
||||
|
||||
def container_clicked(self, ev):
|
||||
ev.stopPropagation(), ev.preventDefault()
|
||||
@ -495,9 +496,46 @@ class CreateAnnotation:
|
||||
self.start_handle_drag(touch, ev.currentTarget.id)
|
||||
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):
|
||||
if self.state in (DRAGGING_RIGHT, DRAGGING_LEFT):
|
||||
self.state = WAITING_FOR_DRAG
|
||||
self.end_drag_scroll()
|
||||
ev.preventDefault(), ev.stopPropagation()
|
||||
|
||||
def touchend_on_container(self, ev):
|
||||
@ -507,6 +545,7 @@ class CreateAnnotation:
|
||||
if touch.identifier is self.active_touch:
|
||||
self.active_touch = None
|
||||
self.state = WAITING_FOR_DRAG
|
||||
self.end_drag_scroll()
|
||||
return
|
||||
|
||||
def handle_moved(self, ev):
|
||||
@ -518,6 +557,16 @@ class CreateAnnotation:
|
||||
pos.start = map_to_iframe_coords(pos.start)
|
||||
pos.end = map_to_iframe_coords(pos.end)
|
||||
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):
|
||||
if self.state not in (DRAGGING_RIGHT, DRAGGING_LEFT):
|
||||
@ -626,17 +675,10 @@ class CreateAnnotation:
|
||||
self.editing_annot_uuid = msg.existing or None
|
||||
if self.editing_annot_uuid:
|
||||
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':
|
||||
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':
|
||||
if not msg.ok:
|
||||
return error_dialog(
|
||||
@ -653,30 +695,42 @@ class CreateAnnotation:
|
||||
self.left_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):
|
||||
lh, rh = self.left_handle, self.right_handle
|
||||
|
||||
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.place_single_handle(self.left_handle, extents.start)
|
||||
self.place_single_handle(self.right_handle, extents.end)
|
||||
self.state = WAITING_FOR_DRAG
|
||||
self.left_line_height = extents.start.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:
|
||||
|
||||
|
@ -123,13 +123,15 @@ def scroll_by_page(direction):
|
||||
h = scroll_viewport.height() - 10
|
||||
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
|
||||
h = line_height()
|
||||
if by_page:
|
||||
h = (window.innerWidth if horizontal else window.innerHeight) - h
|
||||
if horizontal:
|
||||
before = window.pageXOffset
|
||||
window.scrollBy(15 * direction, 0)
|
||||
window.scrollBy(h * direction, 0)
|
||||
return window.pageXOffset is not before
|
||||
h = scroll_viewport.height() - 10
|
||||
before = window.pageYOffset
|
||||
window.scrollBy(0, h * direction)
|
||||
return window.pageYOffset is not before
|
||||
|
@ -5,7 +5,7 @@ from __python__ import bound_methods, hash_literals
|
||||
import traceback
|
||||
from gettext import gettext as _
|
||||
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
|
||||
)
|
||||
|
||||
@ -38,11 +38,11 @@ from read_book.mathjax import apply_mathjax
|
||||
from read_book.paged_mode import (
|
||||
anchor_funcs as paged_anchor_funcs,
|
||||
auto_scroll_action as paged_auto_scroll_action, calc_columns_per_screen,
|
||||
current_cfi, current_page_width, get_columns_per_screen_data,
|
||||
handle_gesture as paged_handle_gesture, handle_shortcut as paged_handle_shortcut,
|
||||
jump_to_cfi as paged_jump_to_cfi, layout as paged_layout,
|
||||
onwheel as paged_onwheel, prepare_for_resize as paged_prepare_for_resize,
|
||||
progress_frac, reset_paged_mode_globals, resize_done as paged_resize_done,
|
||||
current_cfi, get_columns_per_screen_data, handle_gesture as paged_handle_gesture,
|
||||
handle_shortcut as paged_handle_shortcut, jump_to_cfi as paged_jump_to_cfi,
|
||||
layout as paged_layout, onwheel as paged_onwheel,
|
||||
prepare_for_resize as paged_prepare_for_resize, progress_frac,
|
||||
reset_paged_mode_globals, resize_done as paged_resize_done,
|
||||
scroll_by_page as paged_scroll_by_page, scroll_to_elem,
|
||||
scroll_to_extend_annotation as paged_annotation_scroll,
|
||||
scroll_to_fraction as paged_scroll_to_fraction, snap_to_selection,
|
||||
@ -707,13 +707,21 @@ class IframeBoss:
|
||||
extents = selection_extents(in_flow_mode)
|
||||
self.send_message(
|
||||
'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):
|
||||
page_rect = {'width': current_page_width()}
|
||||
extended = extend_selection_after_scroll(data.backwards, in_flow_mode, page_rect)
|
||||
if data.extend_selection:
|
||||
extend_selection_to_limit(data.handle is 'left', data.backwards)
|
||||
extents = selection_extents(in_flow_mode)
|
||||
self.send_message(
|
||||
'annotations', type='update-handles', extents=selection_extents(in_flow_mode),
|
||||
backwards=data.backwards, from_scroll=True, selection_extended=extended, page_rect=page_rect
|
||||
'annotations', type='scrolled',
|
||||
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':
|
||||
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
|
||||
|
||||
|
||||
def extend_selection_after_scroll(backwards, in_flow_mode, page_rect):
|
||||
page_rect.top = page_rect.left = 0
|
||||
page_rect.bottom = window.innerHeight
|
||||
page_rect.right = page_rect.width
|
||||
if not in_flow_mode and not backwards:
|
||||
page_rect.right = window.innerWidth
|
||||
page_rect.left = window.innerWidth - page_rect.width
|
||||
page_rect.width = page_rect.right - page_rect.left
|
||||
page_rect.height = page_rect.bottom - page_rect.top
|
||||
def range_at_limit(invert_x, invert_y):
|
||||
step = 10
|
||||
for y in range(0, window.innerHeight + step, step):
|
||||
if invert_y:
|
||||
y = max(0, window.innerHeight - y)
|
||||
for x in range(0, window.innerWidth + step, step):
|
||||
if invert_x:
|
||||
x = max(0, window.innerWidth - x)
|
||||
r = range_from_point(x, y)
|
||||
if r:
|
||||
return r
|
||||
|
||||
|
||||
def extend_selection_to_limit(left, top):
|
||||
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
|
||||
if in_flow_mode:
|
||||
page_rect.width = window.innerWidth
|
||||
r = sel.getRangeAt(0)
|
||||
q = r.cloneRange()
|
||||
q.collapse(backwards)
|
||||
rects = q.getClientRects()
|
||||
if not rects.length:
|
||||
return False
|
||||
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
|
||||
if left:
|
||||
r.setStart(new_limit.startContainer, new_limit.startOffset)
|
||||
else:
|
||||
r.setEnd(new_limit.startContainer, new_limit.startOffset)
|
||||
return True
|
||||
|
||||
|
||||
def set_selections_extents_to(extents):
|
||||
|
Loading…
x
Reference in New Issue
Block a user