Add auto-scroll with keyboard shortcut

This commit is contained in:
Michael Ziminsky (Z) 2019-12-24 00:29:01 -07:00
parent 0229c684f3
commit e028bbb3f6
7 changed files with 79 additions and 15 deletions

View File

@ -63,8 +63,11 @@ last_change_spine_item_request = {}
def _check_for_scroll_end(func, obj, args, report): def _check_for_scroll_end(func, obj, args, report):
before = window.pageYOffset before = window.pageYOffset
func.apply(obj, args) func.apply(obj, args)
now = performance.now()
scroll_animator.sync(now)
if window.pageYOffset is before: if window.pageYOffset is before:
now = Date.now()
csi = current_spine_item() csi = current_spine_item()
if last_change_spine_item_request.name is csi.name and now - last_change_spine_item_request.at < 2000: if last_change_spine_item_request.name is csi.name and now - last_change_spine_item_request.at < 2000:
return False return False
@ -128,10 +131,10 @@ def scroll_by_page(direction):
def handle_shortcut(sc_name, evt): def handle_shortcut(sc_name, evt):
if sc_name is 'down': if sc_name is 'down':
scroll_animator.start(DIRECTION.Down) scroll_animator.start(DIRECTION.Down, False)
return True return True
if sc_name is 'up': if sc_name is 'up':
scroll_animator.start(DIRECTION.Up) scroll_animator.start(DIRECTION.Up, False)
return True return True
if sc_name is 'start_of_file': if sc_name is 'start_of_file':
goto_boundary(-1) goto_boundary(-1)
@ -157,13 +160,29 @@ def handle_shortcut(sc_name, evt):
if sc_name is 'pagedown': if sc_name is 'pagedown':
scroll_by_page(1) scroll_by_page(1)
return True return True
if sc_name is 'toggle_autoscroll':
if scroll_animator.auto and scroll_animator.is_running():
cancel_scroll()
else:
scroll_animator.start(DIRECTION.Down, True)
return True
if sc_name.startsWith('scrollspeed_'):
scroll_animator.sync()
return False return False
def layout(is_single_page): def layout(is_single_page):
cancel_scroll()
set_css(document.body, margin='0', border_width='0', padding='0') set_css(document.body, margin='0', border_width='0', padding='0')
line_height.doc_style = window.getComputedStyle(document.body) line_height.doc_style = window.getComputedStyle(document.body)
def cancel_scroll():
scroll_animator.stop()
def is_scroll_end(pos):
return !(0 <= pos <= document_height() - window.innerHeight)
DIRECTION = {'Up': -1, 'Down': 1} DIRECTION = {'Up': -1, 'Down': 1}
class ScrollAnimator: class ScrollAnimator:
@ -171,17 +190,23 @@ class ScrollAnimator:
def __init__(self): def __init__(self):
self.animation_id = None self.animation_id = None
self.auto = False
def start(self, direction): def is_running(self):
return self.animation_id != None
def start(self, direction, auto):
now = performance.now() now = performance.now()
self.end_time = now + self.DURATION self.end_time = now + self.DURATION
if self.animation_id is None or direction != self.direction: if !self.is_running() or direction != self.direction or auto != self.auto:
self.paused = self.direction if self.auto and not auto else False
self.stop() self.stop()
self.auto = auto
self.direction = direction self.direction = direction
self.start_time = now self.start_time = now
self.start_offset = window.pageYOffset self.start_offset = window.pageYOffset
self.animation_id = window.requestAnimationFrame(self.smooth_scroll) self.animation_id = window.requestAnimationFrame(self.auto_scroll if auto else self.smooth_scroll)
def smooth_scroll(self, ts): def smooth_scroll(self, ts):
duration = (self.end_time - self.start_time) duration = (self.end_time - self.start_time)
@ -194,14 +219,40 @@ class ScrollAnimator:
if progress < 1: if progress < 1:
self.animation_id = window.requestAnimationFrame(self.smooth_scroll) self.animation_id = window.requestAnimationFrame(self.smooth_scroll)
else: else:
self.animation_id = None
amt = window.pageYOffset - self.start_offset amt = window.pageYOffset - self.start_offset
if abs(amt) < 3 and duration is self.DURATION and !(0 <= scroll_target <= document_height() - window.innerHeight): if abs(amt) < 3 and duration is self.DURATION and is_scroll_end(scroll_target):
get_boss().send_message('next_spine_item', previous=self.direction is DIRECTION.Up) get_boss().send_message('next_spine_item', previous=self.direction is DIRECTION.Up)
elif self.paused:
self.start(self.paused, True)
else: else:
self.animation_id = None
report_human_scroll(amt) report_human_scroll(amt)
def auto_scroll(self, ts):
elapsed = max(0, ts - self.start_time) # max to account for jitter
scroll_target = self.start_offset
scroll_target += Math.trunc(self.direction * elapsed * line_height() * opts.lines_per_sec_auto) / 1000
window.scrollTo(0, scroll_target)
scroll_finished = is_scroll_end(scroll_target)
# report every second
if elapsed >= 1000:
self.sync(ts)
if scroll_finished:
self.stop()
else:
self.animation_id = window.requestAnimationFrame(self.auto_scroll)
def sync(self, ts):
if self.auto:
report_human_scroll(window.pageYOffset - self.start_offset)
self.start_time = ts or performance.now()
self.start_offset = window.pageYOffset
def stop(self): def stop(self):
self.auto = False
if self.animation_id is not None: if self.animation_id is not None:
window.cancelAnimationFrame(self.animation_id) window.cancelAnimationFrame(self.animation_id)
self.animation_id = None self.animation_id = None

View File

@ -12,7 +12,7 @@ from read_book.extract import get_elements
from read_book.flow_mode import ( from read_book.flow_mode import (
anchor_funcs as flow_anchor_funcs, flow_onwheel, flow_to_scroll_fraction, anchor_funcs as flow_anchor_funcs, flow_onwheel, flow_to_scroll_fraction,
handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut, handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut,
layout as flow_layout, scroll_by_page as flow_scroll_by_page layout as flow_layout, scroll_by_page as flow_scroll_by_page, cancel_scroll
) )
from read_book.footnotes import is_footnote_link from read_book.footnotes import is_footnote_link
from read_book.globals import ( from read_book.globals import (
@ -108,6 +108,7 @@ class IframeBoss:
'set_reference_mode': self.set_reference_mode, 'set_reference_mode': self.set_reference_mode,
'wheel_from_margin': self.wheel_from_margin, 'wheel_from_margin': self.wheel_from_margin,
'window_size': self.received_window_size, 'window_size': self.received_window_size,
'overlay_shown': cancel_scroll,
} }
self.comm = IframeClient(handlers) self.comm = IframeClient(handlers)
self.last_window_ypos = 0 self.last_window_ypos = 0
@ -245,8 +246,8 @@ class IframeBoss:
apply_font_size() apply_font_size()
def change_scroll_speed(self, data): def change_scroll_speed(self, data):
if data.lines_per_sec_smooth?: if data.lines_per_sec_auto?:
opts.lines_per_sec_smooth = data.lines_per_sec_smooth opts.lines_per_sec_auto = data.lines_per_sec_auto
def change_stylesheet(self, data): def change_stylesheet(self, data):
opts.user_stylesheet = data.sheet or '' opts.user_stylesheet = data.sheet or ''

View File

@ -12,7 +12,7 @@ from session import defaults
CONTAINER = unique_id('standalone-scrolling-settings') CONTAINER = unique_id('standalone-scrolling-settings')
MIN_SCROLL_SPEED = 0.5 MIN_SCROLL_SPEED = 0.5
MAX_SCROLL_SPEED = 50 MAX_SCROLL_SPEED = 5
def restore_defaults(): def restore_defaults():

View File

@ -17,6 +17,7 @@ def update_settings(settings):
opts.cover_preserve_aspect_ratio = v'!!settings.cover_preserve_aspect_ratio' opts.cover_preserve_aspect_ratio = v'!!settings.cover_preserve_aspect_ratio'
opts.hide_tooltips = settings.hide_tooltips opts.hide_tooltips = settings.hide_tooltips
opts.is_dark_theme = v'!!settings.is_dark_theme' opts.is_dark_theme = v'!!settings.is_dark_theme'
opts.lines_per_sec_auto = settings.lines_per_sec_auto
opts.lines_per_sec_smooth = settings.lines_per_sec_smooth opts.lines_per_sec_smooth = settings.lines_per_sec_smooth
opts.margin_left = max(0, settings.margin_left) opts.margin_left = max(0, settings.margin_left)
opts.margin_right = max(0, settings.margin_right) opts.margin_right = max(0, settings.margin_right)

View File

@ -270,16 +270,22 @@ def shortcuts_definition():
_('Go to a specified book location or position'), _('Go to a specified book location or position'),
), ),
'toggle_autoscroll': desc(
"Ctrl+ ",
'scroll',
_('Toggle auto-scroll'),
),
'scrollspeed_increase': desc( 'scrollspeed_increase': desc(
"Alt+ArrowUp", "Alt+ArrowUp",
'scroll', 'scroll',
_('Smooth scroll faster'), _('Auto scroll faster'),
), ),
'scrollspeed_decrease': desc( 'scrollspeed_decrease': desc(
"Alt+ArrowDown", "Alt+ArrowDown",
'scroll', 'scroll',
_('Smooth scroll slower'), _('Auto scroll slower'),
), ),
} }

View File

@ -339,6 +339,8 @@ class View:
def overlay_visibility_changed(self, visible): def overlay_visibility_changed(self, visible):
if self.iframe_wrapper.send_message: if self.iframe_wrapper.send_message:
if visible:
self.iframe_wrapper.send_message('overlay_shown')
self.iframe_wrapper.send_message('set_forward_keypresses', forward=v'!!visible') self.iframe_wrapper.send_message('set_forward_keypresses', forward=v'!!visible')
if ui_operations.overlay_visibility_changed: if ui_operations.overlay_visibility_changed:
ui_operations.overlay_visibility_changed(visible) ui_operations.overlay_visibility_changed(visible)
@ -708,6 +710,7 @@ class View:
'hide_tooltips': sd.get('hide_tooltips'), 'hide_tooltips': sd.get('hide_tooltips'),
'cover_preserve_aspect_ratio': sd.get('cover_preserve_aspect_ratio'), 'cover_preserve_aspect_ratio': sd.get('cover_preserve_aspect_ratio'),
'paged_wheel_scrolls_by_screen': sd.get('paged_wheel_scrolls_by_screen'), 'paged_wheel_scrolls_by_screen': sd.get('paged_wheel_scrolls_by_screen'),
'lines_per_sec_auto': sd.get('lines_per_sec_auto'),
'lines_per_sec_smooth': sd.get('lines_per_sec_smooth'), 'lines_per_sec_smooth': sd.get('lines_per_sec_smooth'),
} }
@ -1000,7 +1003,7 @@ class View:
self.iframe_wrapper.send_message('change_font_size', base_font_size=get_session_data().get('base_font_size')) self.iframe_wrapper.send_message('change_font_size', base_font_size=get_session_data().get('base_font_size'))
def update_scroll_speed(self, amt): def update_scroll_speed(self, amt):
self.iframe_wrapper.send_message('change_scroll_speed', lines_per_sec_smooth=change_scroll_speed(amt)) self.iframe_wrapper.send_message('change_scroll_speed', lines_per_sec_auto=change_scroll_speed(amt))
def update_color_scheme(self): def update_color_scheme(self):
cs = self.get_color_scheme(True) cs = self.get_color_scheme(True)

View File

@ -37,6 +37,7 @@ defaults = {
'header': {}, 'header': {},
'hide_tooltips': False, 'hide_tooltips': False,
'keyboard_shortcuts': {}, 'keyboard_shortcuts': {},
'lines_per_sec_auto': 1,
'lines_per_sec_smooth': 30, 'lines_per_sec_smooth': 30,
'margin_bottom': 20, 'margin_bottom': 20,
'margin_left': 20, 'margin_left': 20,
@ -64,6 +65,7 @@ is_local_setting = {
'columns_per_screen': True, 'columns_per_screen': True,
'controls_help_shown_count': True, 'controls_help_shown_count': True,
'current_color_scheme': True, 'current_color_scheme': True,
'lines_per_sec_auto': True,
'lines_per_sec_smooth': True, 'lines_per_sec_smooth': True,
'margin_bottom': True, 'margin_bottom': True,
'margin_left': True, 'margin_left': True,