Viewer: In flow mode, implement drag scrolling. Fixes #1880707 [there is no auto scroll when to try text select](https://bugs.launchpad.net/calibre/+bug/1880707)

This commit is contained in:
Kovid Goyal 2020-06-04 07:32:29 +05:30
parent ddceafc9ac
commit 0ce941104d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 98 additions and 4 deletions

View File

@ -254,6 +254,7 @@ class ScrollAnimator:
return self.is_running() and (self.auto or self.auto_timer is not None)
def start(self, direction, auto):
cancel_drag_scroll()
if self.wait:
return
@ -274,6 +275,9 @@ class ScrollAnimator:
def smooth_scroll(self, ts):
duration = self.end_time - self.start_time
if duration <= 0:
self.animation_id = None
return
progress = max(0, min(1, (ts - self.start_time) / duration)) # max/min to account for jitter
scroll_target = self.start_offset
scroll_target += Math.trunc(self.direction * progress * duration * line_height() * opts.lines_per_sec_smooth) / 1000
@ -374,6 +378,7 @@ class FlickAnimator:
self.animation_id = None
def start(self, gesture):
cancel_drag_scroll()
self.vertical = gesture.axis is 'vertical'
now = window.performance.now()
points = times = None
@ -411,6 +416,70 @@ class FlickAnimator:
flick_animator = FlickAnimator()
class DragScroller:
DURATION = 100 # milliseconds
def __init__(self):
self.animation_id = None
self.direction = 1
self.speed_factor = 1
self.start_time = self.end_time = 0
self.start_offset = 0
def is_running(self):
return self.animation_id is not None
def smooth_scroll(self, ts):
duration = self.end_time - self.start_time
if duration <= 0:
self.animation_id = None
self.start(self.direction, self.speed_factor)
return
progress = max(0, min(1, (ts - self.start_time) / duration)) # max/min to account for jitter
scroll_target = self.start_offset
scroll_target += Math.trunc(self.direction * progress * duration * line_height() * opts.lines_per_sec_smooth * self.speed_factor) / 1000
window.scrollTo(0, scroll_target)
if progress < 1:
self.animation_id = window.requestAnimationFrame(self.smooth_scroll)
else:
self.animation_id = None
self.start(self.direction, self.speed_factor)
def start(self, direction, speed_factor):
now = window.performance.now()
self.end_time = now + self.DURATION
if not self.is_running() or direction is not self.direction or speed_factor is not self.speed_factor:
self.stop()
self.direction = direction
self.speed_factor = speed_factor
self.start_time = now
self.start_offset = window.pageYOffset
self.animation_id = window.requestAnimationFrame(self.smooth_scroll)
def stop(self):
if self.animation_id is not None:
window.cancelAnimationFrame(self.animation_id)
self.animation_id = None
drag_scroller = DragScroller()
def cancel_drag_scroll():
drag_scroller.stop()
def start_drag_scroll(delta):
limit = opts.margin_top if delta < 0 else opts.margin_bottom
direction = 1 if delta >= 0 else -1
speed_factor = min(abs(delta), limit) / limit
speed_factor *= (2 - speed_factor) # QuadOut Easing curve
drag_scroller.start(direction, 2 * speed_factor)
def handle_gesture(gesture):
flick_animator.stop()
if gesture.type is 'swipe':

View File

@ -22,10 +22,11 @@ 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,
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,
scroll_to_extend_annotation as flow_annotation_scroll
cancel_drag_scroll, 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,
scroll_to_extend_annotation as flow_annotation_scroll, start_drag_scroll
)
from read_book.footnotes import is_footnote_link
from read_book.globals import (
@ -143,6 +144,7 @@ class IframeBoss:
self.length_before = None
def on_overlay_visibility_changed(self, data):
cancel_drag_scroll()
if data.visible:
self.forward_keypresses = True
if self.auto_scroll_action:
@ -166,6 +168,8 @@ class IframeBoss:
window.addEventListener('resize', debounce(self.onresize, 500))
window.addEventListener('wheel', self.onwheel, {'passive': False})
window.addEventListener('keydown', self.onkeydown, {'passive': False})
window.addEventListener('mousemove', self.onmousemove, {'passive': True})
window.addEventListener('mouseup', self.onmouseup, {'passive': True})
document.documentElement.addEventListener('contextmenu', self.oncontextmenu, {'passive': False})
document.addEventListener('selectionchange', self.onselectionchange)
self.color_scheme = data.color_scheme
@ -201,6 +205,7 @@ class IframeBoss:
(console.error or console.log)('There was an error in the JavaScript from within the book')
def display(self, data):
cancel_drag_scroll()
self.length_before = None
self.content_ready = False
clear_annot_id_uuid_map()
@ -535,6 +540,22 @@ class IframeBoss:
else:
self.handle_wheel(evt)
def onmousemove(self, evt):
if evt.buttons is not 1:
return
if 0 <= evt.clientY <= window.innerHeight or current_layout_mode() is not 'flow':
cancel_drag_scroll()
return
sel = window.getSelection()
if not sel:
cancel_drag_scroll()
return
delta = evt.clientY if evt.clientY < 0 else (evt.clientY - window.innerHeight)
start_drag_scroll(delta)
def onmouseup(self, evt):
cancel_drag_scroll()
def onkeydown(self, evt):
if current_layout_mode() is not 'flow' and evt.key is 'Tab':
# Prevent the TAB key from shifting focus as it causes partial scrolling

View File

@ -22,6 +22,8 @@ def update_settings(settings):
opts.lines_per_sec_smooth = settings.lines_per_sec_smooth
opts.margin_left = max(0, settings.margin_left)
opts.margin_right = max(0, settings.margin_right)
opts.margin_top = max(0, settings.margin_top)
opts.margin_bottom = max(0, settings.margin_bottom)
opts.override_book_colors = settings.override_book_colors
opts.paged_wheel_scrolls_by_screen = v'!!settings.paged_wheel_scrolls_by_screen'
opts.paged_taps_scroll_by_screen = v'!!settings.paged_taps_scroll_by_screen'

View File

@ -840,6 +840,8 @@ class View:
return {
'margin_left': 0 if name is self.book.manifest.title_page_name else sd.get('margin_left'),
'margin_right': 0 if name is self.book.manifest.title_page_name else sd.get('margin_right'),
'margin_top': 0 if name is self.book.manifest.title_page_name else sd.get('margin_top'),
'margin_bottom': 0 if name is self.book.manifest.title_page_name else sd.get('margin_bottom'),
'read_mode': sd.get('read_mode'),
'columns_per_screen': sd.get('columns_per_screen'),
'color_scheme': cs,