From 0a9e52d212d433c542d61956573a5841ac7485b9 Mon Sep 17 00:00:00 2001 From: "Michael Ziminsky (Z)" Date: Thu, 26 Dec 2019 12:28:42 -0700 Subject: [PATCH] Additional scroll options... Option for whether or not to load next file after reaching the start/end when using key based scrolling Configurable delay before loading the next file after reaching the end with auto-scroll --- src/pyj/read_book/flow_mode.pyj | 76 ++++++++++++++++++++------- src/pyj/read_book/iframe.pyj | 2 +- src/pyj/read_book/prefs/scrolling.pyj | 18 ++++++- src/pyj/read_book/settings.pyj | 2 + src/pyj/read_book/view.pyj | 2 + src/pyj/session.pyj | 4 ++ 6 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/pyj/read_book/flow_mode.pyj b/src/pyj/read_book/flow_mode.pyj index 2015f4feab..24c057b16a 100644 --- a/src/pyj/read_book/flow_mode.pyj +++ b/src/pyj/read_book/flow_mode.pyj @@ -51,9 +51,9 @@ def add_small_scroll(amt): def report_human_scroll(amt): - if amt > 0: + if abs(amt) > 0: h = scroll_viewport.height() - is_large_scroll = (amt / h) >= 0.5 + is_large_scroll = (abs(amt) / h) >= 0.5 if is_large_scroll: clear_small_scrolls() get_boss().report_human_scroll(amt / document_height()) @@ -180,10 +180,19 @@ def handle_shortcut(sc_name, evt): def layout(is_single_page): - cancel_scroll() + scroll_animator.wait = False + scroll_animator.sync() set_css(document.body, margin='0', border_width='0', padding='0') line_height.doc_style = window.getComputedStyle(document.body) +# Pause auto-scroll while minimized +document.addEventListener("visibilitychange", def(): + if (document.visibilityState is 'visible'): + scroll_animator.sync() + else: + scroll_animator.pause() +) + def cancel_scroll(): scroll_animator.stop() @@ -202,16 +211,22 @@ class ScrollAnimator: return self.animation_id != None def start(self, direction, auto): + if self.wait: + return + now = performance.now() self.end_time = now + self.DURATION + clearTimeout(self.auto_timer) if !self.is_running() or direction != self.direction or auto != self.auto: - self.paused = self.direction if self.auto and not auto else False + if self.auto and not auto: + self.pause() self.stop() self.auto = auto self.direction = direction self.start_time = now self.start_offset = window.pageYOffset + self.csi_idx = current_spine_item().index self.animation_id = window.requestAnimationFrame(self.auto_scroll if auto else self.smooth_scroll) def smooth_scroll(self, ts): @@ -221,18 +236,22 @@ class ScrollAnimator: scroll_target += Math.trunc(self.direction * progress * duration * line_height() * opts.lines_per_sec_smooth) / 1000 window.scrollTo(0, scroll_target) + amt = window.pageYOffset - self.start_offset - if progress < 1: + if is_scroll_end(scroll_target) and (not opts.scroll_stop_boundaries or (abs(amt) < 3 and duration is self.DURATION)): + # "Turn the page" if stop at boundaries option is false or + # this is a new scroll action and we were already at the end + self.animation_id = None + self.wait = True + report_human_scroll(amt) + get_boss().send_message('next_spine_item', previous=self.direction is DIRECTION.Up) + elif progress < 1: self.animation_id = window.requestAnimationFrame(self.smooth_scroll) + elif self.paused: + self.resume() else: - amt = window.pageYOffset - self.start_offset - 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) + 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 @@ -247,24 +266,45 @@ class ScrollAnimator: self.sync(ts) if scroll_finished: - self.stop() + self.pause() + if opts.scroll_auto_boundary_delay: + self.auto_timer = setTimeout(def(): get_boss().send_message('next_spine_item', previous=self.direction is DIRECTION.Up);, opts.scroll_auto_boundary_delay * 1000) else: self.animation_id = window.requestAnimationFrame(self.auto_scroll) + def report(self): + amt = window.pageYOffset - self.start_offset + if abs(amt) > 0 and self.csi_idx is current_spine_item().index: + report_human_scroll(amt) + def sync(self, ts): if self.auto: - report_human_scroll(window.pageYOffset - self.start_offset) + self.report() + self.csi_idx = current_spine_item().index self.start_time = ts or performance.now() self.start_offset = window.pageYOffset + else: + self.resume() def stop(self): self.auto = False if self.animation_id is not None: window.cancelAnimationFrame(self.animation_id) self.animation_id = None - amt = window.pageYOffset - self.start_offset - if amt > 0: - report_human_scroll(amt) + self.report() + + def pause(self): + if self.auto: + self.paused = self.direction + self.stop() + else: + self.paused = False + + # Resume auto-scroll + def resume(self): + if self.paused: + self.start(self.paused, True) + self.paused = False scroll_animator = ScrollAnimator() diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 2969963d5a..542e4ada75 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -270,7 +270,6 @@ class IframeBoss: self.last_window_width, self.last_window_height = scroll_viewport.width(), scroll_viewport.height() apply_settings() fix_fullscreen_svg_images() - self.do_layout(self.is_titlepage) if self.mathjax: return apply_mathjax(self.mathjax, self.book.manifest.link_uid, self.content_loaded_stage2) # window.setTimeout(self.content_loaded_stage2, 1000) @@ -307,6 +306,7 @@ class IframeBoss: if si: self.length_before += files[si]?.length or 0 self.onscroll() + self.do_layout(self.is_titlepage) self.send_message('content_loaded', progress_frac=self.calculate_progress_frac(), file_progress_frac=progress_frac()) self.last_cfi = None window.setTimeout(self.update_cfi, 0) diff --git a/src/pyj/read_book/prefs/scrolling.pyj b/src/pyj/read_book/prefs/scrolling.pyj index 3d4d5e35f3..fcadb4deab 100644 --- a/src/pyj/read_book/prefs/scrolling.pyj +++ b/src/pyj/read_book/prefs/scrolling.pyj @@ -16,6 +16,9 @@ CONTAINER = unique_id('standalone-scrolling-settings') MIN_SCROLL_SPEED_AUTO = 0.25 MAX_SCROLL_SPEED_AUTO = 5 +MIN_SCROLL_AUTO_DELAY = 0 +MAX_SCROLL_AUTO_DELAY = 10 + MIN_SCROLL_SPEED_SMOOTH = 10 MAX_SCROLL_SPEED_SMOOTH = 50 @@ -57,7 +60,7 @@ def create_scrolling_panel(container, apply_func, cancel_func): ans = E.input(type='number', name=name, id=name) for key, val in Object.entries(kwargs): ans[key] = val - ans.valueAsNumber = sd.get(name) or defaults[name] + ans.valueAsNumber = sd.get(name, defaults[name]) return E.label("for"=name, text), ans container.appendChild(E.div(style='margin-top:1ex', _('Control how mouse based scrolling works in paged mode'))) @@ -68,8 +71,12 @@ def create_scrolling_panel(container, apply_func, cancel_func): container.appendChild(E.hr()) container.appendChild(E.div(style='margin-top:1ex', _('Control how smooth scrolling works in flow mode'))) + container.appendChild(cb( + 'scroll_stop_boundaries', + _('Stop at file boundaries when continuous scrolling while holding down the scroll key') + )) container.appendChild( - E.div(style='display:grid;margin-top:1ex;align-items:center;grid-template-columns:auto auto;grid-gap:1ex;justify-content:flex-start;', + E.div(style='display:grid;margin-top:1ex;align-items:center;grid-template-columns:25em min-content;grid-gap:1ex', *spinner( 'lines_per_sec_smooth', _('Smooth scrolling speed in lines/sec'), @@ -83,6 +90,13 @@ def create_scrolling_panel(container, apply_func, cancel_func): step=MIN_SCROLL_SPEED_AUTO, min=MIN_SCROLL_SPEED_AUTO, max=MAX_SCROLL_SPEED_AUTO + ), + *spinner( + 'scroll_auto_boundary_delay', + _('Seconds to wait before loading the next file after auto-scroll reaches the end; 0 to disable'), + step=0.25, + min=MIN_SCROLL_AUTO_DELAY, + max=MAX_SCROLL_AUTO_DELAY ) ) ) diff --git a/src/pyj/read_book/settings.pyj b/src/pyj/read_book/settings.pyj index 0c5defb9dd..c3a1905153 100644 --- a/src/pyj/read_book/settings.pyj +++ b/src/pyj/read_book/settings.pyj @@ -23,6 +23,8 @@ def update_settings(settings): opts.margin_right = max(0, settings.margin_right) opts.override_book_colors = settings.override_book_colors opts.paged_wheel_scrolls_by_screen = v'!!settings.paged_wheel_scrolls_by_screen' + opts.scroll_auto_boundary_delay = settings.scroll_auto_boundary_delay + opts.scroll_stop_boundaries = v'!!settings.scroll_stop_boundaries' opts.user_stylesheet = settings.user_stylesheet update_settings() diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 7ed54af7e6..97f7324bdf 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -712,6 +712,8 @@ class View: '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'), + 'scroll_auto_boundary_delay': sd.get('scroll_auto_boundary_delay'), + 'scroll_stop_boundaries': sd.get('scroll_stop_boundaries'), } def show_name(self, name, initial_position=None): diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index 1a17109508..760d44c506 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -49,6 +49,8 @@ defaults = { 'paged_margin_clicks_scroll_by_screen': True, 'paged_wheel_scrolls_by_screen': False, 'read_mode': 'paged', + 'scroll_auto_boundary_delay': 5, + 'scroll_stop_boundaries': False, 'standalone_font_settings': {}, 'standalone_misc_settings': {}, 'standalone_recently_opened': v'[]', @@ -75,6 +77,8 @@ is_local_setting = { 'max_text_width': True, 'override_book_colors': True, 'read_mode': 'paged', + 'scroll_auto_boundary_delay': True, + 'scroll_stop_boundaries': True, 'standalone_font_settings': True, 'standalone_misc_settings': True, 'standalone_recently_opened': True,