diff --git a/src/pyj/read_book/flow_mode.pyj b/src/pyj/read_book/flow_mode.pyj index 22671bc943..5393728c24 100644 --- a/src/pyj/read_book/flow_mode.pyj +++ b/src/pyj/read_book/flow_mode.pyj @@ -63,8 +63,11 @@ last_change_spine_item_request = {} def _check_for_scroll_end(func, obj, args, report): before = window.pageYOffset func.apply(obj, args) + + now = performance.now() + scroll_animator.sync(now) + if window.pageYOffset is before: - now = Date.now() csi = current_spine_item() if last_change_spine_item_request.name is csi.name and now - last_change_spine_item_request.at < 2000: return False @@ -128,10 +131,10 @@ def scroll_by_page(direction): def handle_shortcut(sc_name, evt): if sc_name is 'down': - scroll_animator.start(DIRECTION.Down) + scroll_animator.start(DIRECTION.Down, False) return True if sc_name is 'up': - scroll_animator.start(DIRECTION.Up) + scroll_animator.start(DIRECTION.Up, False) return True if sc_name is 'start_of_file': goto_boundary(-1) @@ -157,13 +160,29 @@ def handle_shortcut(sc_name, evt): if sc_name is 'pagedown': scroll_by_page(1) 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 def layout(is_single_page): + cancel_scroll() set_css(document.body, margin='0', border_width='0', padding='0') 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} class ScrollAnimator: @@ -171,17 +190,23 @@ class ScrollAnimator: def __init__(self): 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() 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.auto = auto self.direction = direction self.start_time = now 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): duration = (self.end_time - self.start_time) @@ -194,14 +219,40 @@ class ScrollAnimator: if progress < 1: self.animation_id = window.requestAnimationFrame(self.smooth_scroll) else: - self.animation_id = None 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) + elif self.paused: + self.start(self.paused, True) else: + self.animation_id = None 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): + self.auto = False if self.animation_id is not None: window.cancelAnimationFrame(self.animation_id) self.animation_id = None diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 0b16865ff3..2969963d5a 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -12,7 +12,7 @@ from read_book.extract import get_elements from read_book.flow_mode import ( anchor_funcs as flow_anchor_funcs, 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 + 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.globals import ( @@ -108,6 +108,7 @@ class IframeBoss: 'set_reference_mode': self.set_reference_mode, 'wheel_from_margin': self.wheel_from_margin, 'window_size': self.received_window_size, + 'overlay_shown': cancel_scroll, } self.comm = IframeClient(handlers) self.last_window_ypos = 0 @@ -245,8 +246,8 @@ class IframeBoss: apply_font_size() def change_scroll_speed(self, data): - if data.lines_per_sec_smooth?: - opts.lines_per_sec_smooth = data.lines_per_sec_smooth + if data.lines_per_sec_auto?: + opts.lines_per_sec_auto = data.lines_per_sec_auto def change_stylesheet(self, data): opts.user_stylesheet = data.sheet or '' diff --git a/src/pyj/read_book/prefs/scrolling.pyj b/src/pyj/read_book/prefs/scrolling.pyj index 8fdd1263a6..7066b9cb71 100644 --- a/src/pyj/read_book/prefs/scrolling.pyj +++ b/src/pyj/read_book/prefs/scrolling.pyj @@ -12,7 +12,7 @@ from session import defaults CONTAINER = unique_id('standalone-scrolling-settings') MIN_SCROLL_SPEED = 0.5 -MAX_SCROLL_SPEED = 50 +MAX_SCROLL_SPEED = 5 def restore_defaults(): diff --git a/src/pyj/read_book/settings.pyj b/src/pyj/read_book/settings.pyj index 4126ddc62f..adfb516d6d 100644 --- a/src/pyj/read_book/settings.pyj +++ b/src/pyj/read_book/settings.pyj @@ -17,6 +17,7 @@ def update_settings(settings): opts.cover_preserve_aspect_ratio = v'!!settings.cover_preserve_aspect_ratio' opts.hide_tooltips = settings.hide_tooltips 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.margin_left = max(0, settings.margin_left) opts.margin_right = max(0, settings.margin_right) diff --git a/src/pyj/read_book/shortcuts.pyj b/src/pyj/read_book/shortcuts.pyj index f2827fb0ae..0100fe813d 100644 --- a/src/pyj/read_book/shortcuts.pyj +++ b/src/pyj/read_book/shortcuts.pyj @@ -270,16 +270,22 @@ def shortcuts_definition(): _('Go to a specified book location or position'), ), + 'toggle_autoscroll': desc( + "Ctrl+ ", + 'scroll', + _('Toggle auto-scroll'), + ), + 'scrollspeed_increase': desc( "Alt+ArrowUp", 'scroll', - _('Smooth scroll faster'), + _('Auto scroll faster'), ), 'scrollspeed_decrease': desc( "Alt+ArrowDown", 'scroll', - _('Smooth scroll slower'), + _('Auto scroll slower'), ), } diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 2335debba1..715855fecb 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -339,6 +339,8 @@ class View: def overlay_visibility_changed(self, visible): 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') if ui_operations.overlay_visibility_changed: ui_operations.overlay_visibility_changed(visible) @@ -708,6 +710,7 @@ class View: 'hide_tooltips': sd.get('hide_tooltips'), 'cover_preserve_aspect_ratio': sd.get('cover_preserve_aspect_ratio'), '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'), } @@ -1000,7 +1003,7 @@ class View: self.iframe_wrapper.send_message('change_font_size', base_font_size=get_session_data().get('base_font_size')) 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): cs = self.get_color_scheme(True) diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index c224e3463d..1a17109508 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -37,6 +37,7 @@ defaults = { 'header': {}, 'hide_tooltips': False, 'keyboard_shortcuts': {}, + 'lines_per_sec_auto': 1, 'lines_per_sec_smooth': 30, 'margin_bottom': 20, 'margin_left': 20, @@ -64,6 +65,7 @@ is_local_setting = { 'columns_per_screen': True, 'controls_help_shown_count': True, 'current_color_scheme': True, + 'lines_per_sec_auto': True, 'lines_per_sec_smooth': True, 'margin_bottom': True, 'margin_left': True,