From bce5112268a9251b12f6456a19474931da9e5c0d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2017 11:10:54 +0530 Subject: [PATCH] Refactor scroll viewport handling --- src/pyj/read_book/cfi.pyj | 2 +- src/pyj/read_book/flow_mode.pyj | 6 +- src/pyj/read_book/globals.pyj | 110 ++--------------------------- src/pyj/read_book/iframe.pyj | 15 ++-- src/pyj/read_book/paged_mode.pyj | 20 ++++-- src/pyj/read_book/toc.pyj | 3 +- src/pyj/read_book/touch.pyj | 5 +- src/pyj/read_book/viewport.pyj | 115 +++++++++++++++++++++++++++++++ 8 files changed, 151 insertions(+), 125 deletions(-) create mode 100644 src/pyj/read_book/viewport.pyj diff --git a/src/pyj/read_book/cfi.pyj b/src/pyj/read_book/cfi.pyj index fbd4f9a2ad..dd388f21c3 100644 --- a/src/pyj/read_book/cfi.pyj +++ b/src/pyj/read_book/cfi.pyj @@ -21,7 +21,7 @@ from __python__ import hash_literals # scroll_to(cfi): which scrolls the browser to a point corresponding to the # given cfi, and returns the x and y co-ordinates of the point. -from read_book.globals import scroll_viewport +from read_book.viewport import scroll_viewport # CFI escaping {{{ escape_pat = /[\[\],^();~@!-]/g diff --git a/src/pyj/read_book/flow_mode.pyj b/src/pyj/read_book/flow_mode.pyj index 2ae68e08c7..d98c548adb 100644 --- a/src/pyj/read_book/flow_mode.pyj +++ b/src/pyj/read_book/flow_mode.pyj @@ -1,12 +1,14 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal -from __python__ import hash_literals, bound_methods +from __python__ import bound_methods, hash_literals from dom import set_css -from read_book.globals import get_boss, scroll_viewport from keycodes import get_key +from read_book.globals import get_boss +from read_book.viewport import scroll_viewport from utils import document_height, document_width, viewport_to_document + def flow_to_scroll_fraction(frac): scroll_viewport.scroll_to(0, document_height() * frac) diff --git a/src/pyj/read_book/globals.pyj b/src/pyj/read_book/globals.pyj index 1edcf1e9c3..8977cf6691 100644 --- a/src/pyj/read_book/globals.pyj +++ b/src/pyj/read_book/globals.pyj @@ -5,7 +5,6 @@ from __python__ import hash_literals from aes import GCM, random_bytes from encodings import hexlify from gettext import gettext as _, register_callback, gettext as gt -from utils import is_ios _boss = None @@ -39,97 +38,11 @@ class Messenger: messenger = Messenger() iframe_id = 'read-book-iframe' uid = 'calibre-' + hexlify(random_bytes(12)) -scroll_viewport = {} - -def flow_viewport_x(): - return window.pageXOffset - -def flow_viewport_y(): - return window.pageYOffset - -def flow_viewport_scroll_to(x, y): - window.scrollTo(x, y) - -def flow_viewport_scroll_into_view(elem): - elem.scrollIntoView() - -def flow_reset_globals(): - pass - -def flow_reset_transforms(): - pass - -def paged_viewport_y(): - return 0 - -scroll_viewport.x = flow_viewport_x -scroll_viewport.y = flow_viewport_y -scroll_viewport.scroll_to = flow_viewport_scroll_to -scroll_viewport.scroll_into_view = flow_viewport_scroll_into_view -scroll_viewport.reset_globals = flow_reset_globals -scroll_viewport.reset_transforms = flow_reset_transforms - -if is_ios: - window_width = def (): - return window_width.from_parent or window.innerWidth - window_height = def(): - return window_height.from_parent or window.innerHeight - paged_viewport_scroll_to = def (x, y): - if x is 0: - document.documentElement.style.transform = 'none' - else: - x *= -1 - document.documentElement.style.transform = f'translateX({x}px)' - boss = get_boss() - if boss: - boss.onscroll() - paged_viewport_x = def(): - raw = document.documentElement.style.transform - if not raw or raw is 'none': - return 0 - raw = raw[raw.indexOf('(') + 1:] - ans = parseInt(raw) - if isNaN(ans): - return 0 - ans *= -1 - return ans - paged_viewport_scroll_into_view = def(elem): - left = elem.offsetLeft - if left is None: - return # element has display: none - elem.scrollIntoView() - window.scrollTo(0, 0) - p = elem.offsetParent - while p: - left += p.offsetLeft - p = p.offsetParent - # left -= window_width() // 2 - paged_viewport_scroll_to(max(0, left), 0) - paged_content_width = def(): - return document.documentElement.scrollWidth - paged_reset_transforms = def(): - document.documentElement.style.transform = 'none' - paged_reset_globals = def(): - paged_reset_transforms() - -else: - window_width = def(): - return window.innerWidth - window_height = def(): - return window.innerHeight - paged_viewport_scroll_to = flow_viewport_scroll_to - paged_viewport_x = flow_viewport_x - paged_viewport_scroll_into_view = flow_viewport_scroll_into_view - paged_reset_globals = flow_reset_globals - paged_content_width = def(): - return document.documentElement.scrollWidth - paged_reset_transforms = def(): - pass - -scroll_viewport.width = window_width -scroll_viewport.height = window_height -scroll_viewport.paged_content_width = paged_content_width +def viewport_mode_changer(val): + if val: + viewport_mode_changer.val = val + return viewport_mode_changer.val def current_layout_mode(): return current_layout_mode.value @@ -137,20 +50,7 @@ current_layout_mode.value = 'flow' def set_layout_mode(val): current_layout_mode.value = val - if val is 'flow': - scroll_viewport.x = flow_viewport_x - scroll_viewport.y = flow_viewport_y - scroll_viewport.scroll_to = flow_viewport_scroll_to - scroll_viewport.scroll_into_view = flow_viewport_scroll_into_view - scroll_viewport.reset_globals = flow_reset_globals - scroll_viewport.reset_transforms = flow_reset_transforms - else: - scroll_viewport.x = paged_viewport_x - scroll_viewport.y = paged_viewport_y - scroll_viewport.scroll_to = paged_viewport_scroll_to - scroll_viewport.scroll_into_view = paged_viewport_scroll_into_view - scroll_viewport.reset_globals = paged_reset_globals - scroll_viewport.reset_transforms = paged_reset_transforms + viewport_mode_changer()(val) def current_spine_item(): return current_spine_item.value diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 19dc220db8..8e8088b427 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -13,8 +13,8 @@ from read_book.flow_mode import ( layout as flow_layout, scroll_by_page as flow_scroll_by_page ) from read_book.globals import ( - current_book, current_layout_mode, current_spine_item, scroll_viewport, set_boss, - set_current_spine_item, set_layout_mode, window_height, window_width + current_book, current_layout_mode, current_spine_item, set_boss, + set_current_spine_item, set_layout_mode ) from read_book.mathjax import apply_mathjax from read_book.paged_mode import ( @@ -28,6 +28,7 @@ from read_book.resources import finalize_resources, unserialize_html from read_book.settings import apply_settings, opts from read_book.toc import update_visible_toc_anchors from read_book.touch import create_handlers as create_touch_handlers +from read_book.viewport import scroll_viewport from utils import debounce, html_escape, is_ios FORCE_FLOW_MODE = False @@ -118,7 +119,7 @@ class IframeBoss: def initialize(self, data): self.gcm_from_parent, self.gcm_to_parent = GCM(data.secret.subarray(0, 32)), GCM(data.secret.subarray(32)) - window_width.from_parent, window_height.from_parent = data.width, data.height + scroll_viewport.update_window_size(data.width, data.height) if data.translations: install(data.translations) window.onerror = self.onerror @@ -224,7 +225,7 @@ class IframeBoss: # document.body.appendChild( # E.style() # TODO: User style sheet # ) - self.last_window_width, self.last_window_height = window_width(), window_height() + self.last_window_width, self.last_window_height = scroll_viewport.width(), scroll_viewport.height() self.apply_colors() self.apply_font_size() self.do_layout() @@ -313,10 +314,10 @@ class IframeBoss: self.onresize_stage2() def onresize_stage2(self): - if window_width() is self.last_window_width and window_height() is self.last_window_height: + if scroll_viewport.width() is self.last_window_width and scroll_viewport.height() is self.last_window_height: # Safari at least, generates lots of spurious resize events return - self.last_window_width, self.last_window_height = window_width(), window_height() + self.last_window_width, self.last_window_height = scroll_viewport.width(), scroll_viewport.height() if current_layout_mode() is not 'flow': self.do_layout() if self.last_cfi: @@ -327,7 +328,7 @@ class IframeBoss: self.update_toc_position() def received_window_size(self, data): - window_width.from_parent, window_height.from_parent = data.width, data.height + scroll_viewport.update_window_size(data.width, data.height) if self.content_ready: self.onresize_stage2() diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index 52da86d3f0..949adaac17 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -2,14 +2,20 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal from __python__ import hash_literals -from dom import set_css -from elementmaker import E -from keycodes import get_key -from read_book.cfi import scroll_to as cfi_scroll_to, at_point as cfi_at_point, at_current as cfi_at_current -from read_book.globals import get_boss, scroll_viewport -from read_book.settings import opts import traceback -from utils import get_elem_data, set_elem_data, viewport_to_document, document_width +from elementmaker import E + +from dom import set_css +from keycodes import get_key +from read_book.cfi import ( + at_current as cfi_at_current, at_point as cfi_at_point, + scroll_to as cfi_scroll_to +) +from read_book.globals import get_boss +from read_book.settings import opts +from read_book.viewport import scroll_viewport +from utils import document_width, get_elem_data, set_elem_data, viewport_to_document + def first_child(parent): c = parent.firstChild diff --git a/src/pyj/read_book/toc.pyj b/src/pyj/read_book/toc.pyj index c3ce7058bd..67d1c5f186 100644 --- a/src/pyj/read_book/toc.pyj +++ b/src/pyj/read_book/toc.pyj @@ -8,7 +8,8 @@ from elementmaker import E from gettext import gettext as _ from modals import error_dialog from widgets import create_tree, find_text_in_tree, scroll_tree_item_into_view -from read_book.globals import toc_anchor_map, set_toc_anchor_map, current_spine_item, current_layout_mode, current_book, scroll_viewport +from read_book.globals import toc_anchor_map, set_toc_anchor_map, current_spine_item, current_layout_mode, current_book +from read_book.viewport import scroll_viewport def update_visible_toc_nodes(visible_anchors): diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index 793282c7a9..5515d634c3 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -1,9 +1,10 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal -from __python__ import hash_literals, bound_methods +from __python__ import bound_methods, hash_literals -from read_book.globals import get_boss, scroll_viewport from book_list.globals import get_read_ui +from read_book.globals import get_boss +from read_book.viewport import scroll_viewport HOLD_THRESHOLD = 750 # milliseconds TAP_THRESHOLD = 5 # pixels diff --git a/src/pyj/read_book/viewport.pyj b/src/pyj/read_book/viewport.pyj new file mode 100644 index 0000000000..7ef476d0e8 --- /dev/null +++ b/src/pyj/read_book/viewport.pyj @@ -0,0 +1,115 @@ +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2017, Kovid Goyal +from __python__ import bound_methods, hash_literals + +FUNCTIONS = 'x y scroll_to scroll_into_view reset_globals reset_transforms'.split(' ') + +from read_book.globals import get_boss, viewport_mode_changer +from utils import is_ios + +class ScrollViewport: + + def __init__(self): + self.set_mode('flow') + self.window_width_from_parent = self.window_height_from_parent = None + + def set_mode(self, mode): + prefix = ('flow' if mode is 'flow' else 'paged') + '_' + for attr in FUNCTIONS: + self[attr] = self[prefix + attr] + + def flow_x(self): + return window.pageXOffset + + def flow_y(self): + return window.pageYOffset + + def paged_y(self): + return 0 + + def flow_scroll_to(self, x, y): + window.scrollTo(x, y) + + def flow_scroll_into_view(self, elem): + elem.scrollIntoView() + + def flow_reset_globals(self): + pass + + def flow_reset_transforms(self): + pass + + def paged_content_width(self): + return document.documentElement.scrollWidth + + def update_window_size(self, w, h): + self.window_width_from_parent = w + self.window_height_from_parent = h + + def width(self): + return window.innerWidth + + def height(self): + return window.innerHeight + + +class IOSScrollViewport(ScrollViewport): + + def width(self): + return self.window_width_from_parent or window.innerWidth + + def height(self): + return self.window_height_from_parent or window.innerHeight + + def _scroll_implementation(self, x): + if x is 0: + document.documentElement.style.transform = 'none' + else: + x *= -1 + document.documentElement.style.transform = f'translateX({x}px)' + + def paged_scroll_to(self, x, y): + self._scroll_implementation(x) + boss = get_boss() + if boss: + boss.onscroll() + + def paged_x(self): + raw = document.documentElement.style.transform + if not raw or raw is 'none': + return 0 + raw = raw[raw.indexOf('(') + 1:] + ans = parseInt(raw) + if isNaN(ans): + return 0 + ans *= -1 + return ans + + def paged_scroll_into_view(self, elem): + left = elem.offsetLeft + if left is None: + return # element has display: none + elem.scrollIntoView() + window.scrollTo(0, 0) + p = elem.offsetParent + while p: + left += p.offsetLeft + p = p.offsetParent + # left -= window_width() // 2 + self._scroll_implementation(max(0, left)) + + def paged_reset_transforms(self): + document.documentElement.style.transform = 'none' + + def paged_reset_globals(self): + self.paged_reset_transforms() + + +if is_ios: + scroll_viewport = IOSScrollViewport() +else: + scroll_viewport = ScrollViewport() +for attr in FUNCTIONS: + if not scroll_viewport['paged_' + attr]: + scroll_viewport['paged_' + attr] = scroll_viewport[attr] +viewport_mode_changer(scroll_viewport.set_mode)